From d1bb10a66b582c1b4c1f5d62e0946dc40fbd3e98 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 9 May 2025 17:39:15 +0100 Subject: [PATCH 001/487] [ty] Understand classes that inherit from subscripted `Protocol[]` as generic (#17832) --- crates/ruff_benchmark/benches/ty.rs | 24 ++++- .../resources/mdtest/annotations/self.md | 4 +- .../resources/mdtest/annotations/starred.md | 2 +- .../annotations/stdlib_typing_aliases.md | 51 +++++------ .../annotations/unsupported_special_forms.md | 4 +- .../mdtest/assignment/annotations.md | 4 +- .../resources/mdtest/attributes.md | 2 +- .../resources/mdtest/expression/lambda.md | 3 +- .../resources/mdtest/function/parameters.md | 7 +- .../mdtest/generics/legacy/classes.md | 29 +++++- .../mdtest/generics/legacy/variables.md | 1 + .../mdtest/generics/pep695/classes.md | 2 +- .../resources/mdtest/import/dunder_all.md | 2 +- .../mdtest/literal/collections/list.md | 2 +- .../mdtest/literal/collections/set.md | 2 +- .../resources/mdtest/named_tuple.md | 4 +- .../resources/mdtest/narrow/match.md | 6 +- .../resources/mdtest/protocols.md | 5 +- .../mdtest/scopes/moduletype_attrs.md | 3 +- .../resources/mdtest/subscript/lists.md | 6 +- .../resources/mdtest/subscript/tuple.md | 10 +- .../mdtest/type_properties/is_subtype_of.md | 3 +- .../resources/primer/bad.txt | 6 ++ .../resources/primer/good.txt | 6 -- crates/ty_python_semantic/src/types.rs | 40 ++++---- crates/ty_python_semantic/src/types/class.rs | 48 +++++----- .../src/types/class_base.rs | 37 ++++---- .../ty_python_semantic/src/types/display.rs | 51 ++++++++++- .../ty_python_semantic/src/types/generics.rs | 81 ++++++++++++++++- crates/ty_python_semantic/src/types/infer.rs | 91 ++++++++++++++----- .../ty_python_semantic/src/types/instance.rs | 24 +++++ .../src/types/known_instance.rs | 20 ++-- crates/ty_python_semantic/src/types/mro.rs | 4 +- .../src/types/protocol_class.rs | 32 ++++++- .../src/types/type_ordering.rs | 18 ++-- 35 files changed, 451 insertions(+), 183 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index 83c58e5e823bd9..fe282249a5e8d4 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -59,7 +59,29 @@ type KeyDiagnosticFields = ( Severity, ); -static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[]; +static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ + ( + DiagnosticId::lint("invalid-argument-type"), + Some("/src/tomllib/_parser.py"), + Some(8224..8254), + "Argument to this function is incorrect", + Severity::Error, + ), + ( + DiagnosticId::lint("invalid-argument-type"), + Some("/src/tomllib/_parser.py"), + Some(16914..16948), + "Argument to this function is incorrect", + Severity::Error, + ), + ( + DiagnosticId::lint("invalid-argument-type"), + Some("/src/tomllib/_parser.py"), + Some(17319..17363), + "Argument to this function is incorrect", + Severity::Error, + ), +]; fn tomllib_path(file: &TestFile) -> SystemPathBuf { SystemPathBuf::from("src").join(file.name()) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 46f3343701e6ef..87c73f57177777 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -33,7 +33,9 @@ class Shape: reveal_type(self) # revealed: Unknown return self -reveal_type(Shape().nested_type()) # revealed: @Todo(specialized non-generic class) +# TODO: should be `list[Shape]` +reveal_type(Shape().nested_type()) # revealed: list[Self] + reveal_type(Shape().nested_func()) # revealed: Shape class Circle(Shape): diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md index 61887e7f29edec..e102a79a2d5296 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md @@ -14,7 +14,7 @@ Ts = TypeVarTuple("Ts") def append_int(*args: *Ts) -> tuple[*Ts, int]: # TODO: tuple[*Ts] - reveal_type(args) # revealed: tuple + reveal_type(args) # revealed: tuple[Unknown, ...] return (*args, 1) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md index 82057136f81056..94d6a9ec7fe81c 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md @@ -30,24 +30,22 @@ def f( ordered_dict_bare: typing.OrderedDict, ordered_dict_parametrized: typing.OrderedDict[int, str], ): - # TODO: revealed: list[Unknown] - reveal_type(list_bare) # revealed: list + reveal_type(list_bare) # revealed: list[Unknown] # TODO: revealed: list[int] - reveal_type(list_parametrized) # revealed: list + reveal_type(list_parametrized) # revealed: list[Unknown] reveal_type(dict_bare) # revealed: dict[Unknown, Unknown] # TODO: revealed: dict[int, str] reveal_type(dict_parametrized) # revealed: dict[Unknown, Unknown] - # TODO: revealed: set[Unknown] - reveal_type(set_bare) # revealed: set + reveal_type(set_bare) # revealed: set[Unknown] # TODO: revealed: set[int] - reveal_type(set_parametrized) # revealed: set + reveal_type(set_parametrized) # revealed: set[Unknown] # TODO: revealed: frozenset[Unknown] - reveal_type(frozen_set_bare) # revealed: frozenset + reveal_type(frozen_set_bare) # revealed: frozenset[Unknown] # TODO: revealed: frozenset[str] - reveal_type(frozen_set_parametrized) # revealed: frozenset + reveal_type(frozen_set_parametrized) # revealed: frozenset[Unknown] reveal_type(chain_map_bare) # revealed: ChainMap[Unknown, Unknown] # TODO: revealed: ChainMap[str, int] @@ -61,10 +59,9 @@ def f( # TODO: revealed: defaultdict[str, int] reveal_type(default_dict_parametrized) # revealed: defaultdict[Unknown, Unknown] - # TODO: revealed: deque[Unknown] - reveal_type(deque_bare) # revealed: deque + reveal_type(deque_bare) # revealed: deque[Unknown] # TODO: revealed: deque[str] - reveal_type(deque_parametrized) # revealed: deque + reveal_type(deque_parametrized) # revealed: deque[Unknown] reveal_type(ordered_dict_bare) # revealed: OrderedDict[Unknown, Unknown] # TODO: revealed: OrderedDict[int, str] @@ -84,26 +81,23 @@ import typing class ListSubclass(typing.List): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, ] +# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] reveal_type(ListSubclass.__mro__) class DictSubclass(typing.Dict): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], ] +# TODO: should not have multiple `Generic[]` elements +# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] reveal_type(DictSubclass.__mro__) class SetSubclass(typing.Set): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, ] +# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] reveal_type(SetSubclass.__mro__) class FrozenSetSubclass(typing.FrozenSet): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , @Todo(`Protocol[]` subscript), typing.Generic, ] +# revealed: tuple[, , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] reveal_type(FrozenSetSubclass.__mro__) #################### @@ -112,31 +106,30 @@ reveal_type(FrozenSetSubclass.__mro__) class ChainMapSubclass(typing.ChainMap): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], ] +# TODO: should not have multiple `Generic[]` elements +# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] reveal_type(ChainMapSubclass.__mro__) class CounterSubclass(typing.Counter): ... -# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) -# revealed: tuple[, , , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], typing.Generic[_T], ] +# TODO: Should have one `Generic[]` element, not three(!) +# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], ] reveal_type(CounterSubclass.__mro__) class DefaultDictSubclass(typing.DefaultDict): ... -# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) -# revealed: tuple[, , , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], ] +# TODO: Should not have multiple `Generic[]` elements +# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] reveal_type(DefaultDictSubclass.__mro__) class DequeSubclass(typing.Deque): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, ] +# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] reveal_type(DequeSubclass.__mro__) class OrderedDictSubclass(typing.OrderedDict): ... -# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) -# revealed: tuple[, , , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], ] +# TODO: Should not have multiple `Generic[]` elements +# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] reveal_type(OrderedDictSubclass.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index e1df6e97cee37a..c225220e010a13 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -16,7 +16,7 @@ Alias: TypeAlias = int def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: # TODO: should understand the annotation - reveal_type(args) # revealed: tuple + reveal_type(args) # revealed: tuple[Unknown, ...] reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`) @@ -24,7 +24,7 @@ def g() -> TypeGuard[int]: ... def h() -> TypeIs[int]: ... def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co: # TODO: should understand the annotation - reveal_type(args) # revealed: tuple + reveal_type(args) # revealed: tuple[Unknown, ...] reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)] return callback(42, *args, **kwargs) diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index 52c51ea490c758..3045460ec46d99 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -61,7 +61,7 @@ reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]] reveal_type(e) # revealed: @Todo(full tuple[...] support) reveal_type(f) # revealed: @Todo(full tuple[...] support) reveal_type(g) # revealed: @Todo(full tuple[...] support) -reveal_type(h) # revealed: tuple[@Todo(specialized non-generic class), @Todo(specialized non-generic class)] +reveal_type(h) # revealed: tuple[list[int], list[int]] reveal_type(i) # revealed: tuple[str | int, str | int] reveal_type(j) # revealed: tuple[str | int] @@ -76,7 +76,7 @@ a: tuple[()] = (1, 2) # error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`" b: tuple[int] = ("foo",) -# error: [invalid-assignment] "Object of type `tuple[list, Literal["foo"]]` is not assignable to `tuple[str | int, str]`" +# error: [invalid-assignment] "Object of type `tuple[list[Unknown], Literal["foo"]]` is not assignable to `tuple[str | int, str]`" c: tuple[str | int, str] = ([], "foo") ``` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 75af3ec2d884c5..3c655db8e3868a 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1728,7 +1728,7 @@ reveal_type(False.real) # revealed: Literal[0] All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`: ```py -# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: @Todo(specialized non-generic class), /) -> bytes +# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes reveal_type(b"foo".join) # revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool reveal_type(b"foo".endswith) diff --git a/crates/ty_python_semantic/resources/mdtest/expression/lambda.md b/crates/ty_python_semantic/resources/mdtest/expression/lambda.md index d7d01ed8f575c7..dbafc217acfde6 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/lambda.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/lambda.md @@ -79,8 +79,7 @@ lambda x=1: reveal_type(x) # revealed: Unknown | Literal[1] Using a variadic parameter: ```py -# TODO: should be `tuple[Unknown, ...]` (needs generics) -lambda *args: reveal_type(args) # revealed: tuple +lambda *args: reveal_type(args) # revealed: tuple[Unknown, ...] ``` Using a keyword-variadic parameter: diff --git a/crates/ty_python_semantic/resources/mdtest/function/parameters.md b/crates/ty_python_semantic/resources/mdtest/function/parameters.md index b4d7b8cd14bc98..d61a320c6204b0 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/parameters.md +++ b/crates/ty_python_semantic/resources/mdtest/function/parameters.md @@ -25,8 +25,8 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5, reveal_type(f) # revealed: Literal[4] reveal_type(g) # revealed: Unknown | Literal[5] reveal_type(h) # revealed: Literal[6] - # TODO: should be `tuple[object, ...]` (needs generics) - reveal_type(args) # revealed: tuple + # TODO: should be `tuple[object, ...]` + reveal_type(args) # revealed: tuple[Unknown, ...] reveal_type(kwargs) # revealed: dict[str, str] ``` @@ -36,8 +36,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5, ```py def g(*args, **kwargs): - # TODO: should be `tuple[Unknown, ...]` (needs generics) - reveal_type(args) # revealed: tuple + reveal_type(args) # revealed: tuple[Unknown, ...] reveal_type(kwargs) # revealed: dict[str, Unknown] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 4d2b3d5fe9ba20..2ddf11d0d17da3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -399,7 +399,7 @@ In a specialized generic alias, the specialization is applied to the attributes class. ```py -from typing import Generic, TypeVar +from typing import Generic, TypeVar, Protocol T = TypeVar("T") U = TypeVar("U") @@ -425,6 +425,33 @@ reveal_type(c.y) # revealed: str reveal_type(c.method1()) # revealed: int reveal_type(c.method2()) # revealed: str reveal_type(c.method3()) # revealed: LinkedList[int] + +class SomeProtocol(Protocol[T]): + x: T + +class Foo: + x: int + +class D(Generic[T, U]): + x: T + y: U + + def method1(self) -> T: + return self.x + + def method2(self) -> U: + return self.y + + def method3(self) -> SomeProtocol[T]: + return Foo() + +d = D[int, str]() +reveal_type(d.x) # revealed: int +reveal_type(d.y) # revealed: str +reveal_type(d.method1()) # revealed: int +reveal_type(d.method2()) # revealed: str +reveal_type(d.method3()) # revealed: SomeProtocol[int] +reveal_type(d.method3().x) # revealed: int ``` ## Cyclic class definitions diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 651c9bee47cc18..35cadfd39968bd 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -38,6 +38,7 @@ T = TypeVar("T") U: TypeVar = TypeVar("U") # error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable" +# error: [invalid-type-form] "Function calls are not allowed in type expressions" TestList = list[TypeVar("W")] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 6162470aba941e..b85d0f8960c9d1 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -65,7 +65,7 @@ from typing import Generic, TypeVar T = TypeVar("T") -# error: [invalid-generic-class] "Cannot both inherit from `Generic` and use PEP 695 type variables" +# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables" class BothGenericSyntaxes[U](Generic[T]): ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/dunder_all.md b/crates/ty_python_semantic/resources/mdtest/import/dunder_all.md index f10b33d40b8c6b..d77973265b0ac0 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/dunder_all.md +++ b/crates/ty_python_semantic/resources/mdtest/import/dunder_all.md @@ -785,7 +785,7 @@ from subexporter import * # TODO: Should be `list[str]` # TODO: Should we avoid including `Unknown` for this case? -reveal_type(__all__) # revealed: Unknown | list +reveal_type(__all__) # revealed: Unknown | list[Unknown] __all__.append("B") diff --git a/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md b/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md index 70c98aa3a60d91..53915f27c29615 100644 --- a/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md +++ b/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md @@ -3,5 +3,5 @@ ## Empty list ```py -reveal_type([]) # revealed: list +reveal_type([]) # revealed: list[Unknown] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/literal/collections/set.md b/crates/ty_python_semantic/resources/mdtest/literal/collections/set.md index 452fc719dbca7e..85acd78e3e1d28 100644 --- a/crates/ty_python_semantic/resources/mdtest/literal/collections/set.md +++ b/crates/ty_python_semantic/resources/mdtest/literal/collections/set.md @@ -3,5 +3,5 @@ ## Basic set ```py -reveal_type({1, 2}) # revealed: set +reveal_type({1, 2}) # revealed: set[Unknown] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index db3aab7bfd404a..845ef71cdf40e0 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -48,8 +48,8 @@ alice2 = Person2(1, "Alice") # TODO: should be an error Person2(1) -reveal_type(alice2.id) # revealed: @Todo(GenericAlias instance) -reveal_type(alice2.name) # revealed: @Todo(GenericAlias instance) +reveal_type(alice2.id) # revealed: @Todo(functional `NamedTuple` syntax) +reveal_type(alice2.name) # revealed: @Todo(functional `NamedTuple` syntax) ``` ### Multiple Inheritance diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/match.md b/crates/ty_python_semantic/resources/mdtest/narrow/match.md index 8fd2f7cfdde2a4..a1cd8842f393c9 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/match.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/match.md @@ -133,11 +133,11 @@ match x: case "foo" | 42 | None: reveal_type(x) # revealed: Literal["foo", 42] | None case "foo" | tuple(): - reveal_type(x) # revealed: tuple + reveal_type(x) # revealed: tuple[Unknown, ...] case True | False: reveal_type(x) # revealed: bool case 3.14 | 2.718 | 1.414: - reveal_type(x) # revealed: float & ~tuple + reveal_type(x) # revealed: float & ~tuple[Unknown, ...] reveal_type(x) # revealed: object ``` @@ -155,7 +155,7 @@ reveal_type(x) # revealed: object match x: case "foo" | 42 | None if reveal_type(x): # revealed: Literal["foo", 42] | None pass - case "foo" | tuple() if reveal_type(x): # revealed: Literal["foo"] | tuple + case "foo" | tuple() if reveal_type(x): # revealed: Literal["foo"] | tuple[Unknown, ...] pass case True | False if reveal_type(x): # revealed: bool pass diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 47cd3632ef7167..28b12d8d859444 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -58,6 +58,7 @@ class Bar1(Protocol[T], Generic[T]): class Bar2[T](Protocol): x: T +# error: [invalid-generic-class] "Cannot both inherit from subscripted `typing.Protocol` and use PEP 695 type variables" class Bar3[T](Protocol[T]): x: T ``` @@ -70,8 +71,8 @@ simultaneously: class DuplicateBases(Protocol, Protocol[T]): x: T -# TODO: should not have `Protocol` multiple times -# revealed: tuple[, typing.Protocol, @Todo(`Protocol[]` subscript), typing.Generic, ] +# TODO: should not have `Protocol` or `Generic` multiple times +# revealed: tuple[, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], ] reveal_type(DuplicateBases.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 4448d7042836f1..71c298b8d9b254 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -13,8 +13,7 @@ reveal_type(__loader__) # revealed: LoaderProtocol | None reveal_type(__package__) # revealed: str | None reveal_type(__doc__) # revealed: str | None reveal_type(__spec__) # revealed: ModuleSpec | None - -reveal_type(__path__) # revealed: @Todo(specialized non-generic class) +reveal_type(__path__) # revealed: MutableSequence[str] class X: reveal_type(__name__) # revealed: str diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/lists.md b/crates/ty_python_semantic/resources/mdtest/subscript/lists.md index d074d1b82669a8..dff1905d0d9deb 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/lists.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/lists.md @@ -9,13 +9,13 @@ A list can be indexed into with: ```py x = [1, 2, 3] -reveal_type(x) # revealed: list +reveal_type(x) # revealed: list[Unknown] # TODO reveal int reveal_type(x[0]) # revealed: Unknown -# TODO reveal list -reveal_type(x[0:1]) # revealed: @Todo(specialized non-generic class) +# TODO reveal list[int] +reveal_type(x[0:1]) # revealed: list[Unknown] # error: [call-non-callable] reveal_type(x["a"]) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index 2e733a8921b520..20d01d4fb8257d 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -83,9 +83,8 @@ python-version = "3.9" ```py class A(tuple[int, str]): ... -# Runtime value: `(A, tuple, object)` -# TODO: Generics -reveal_type(A.__mro__) # revealed: tuple[, @Todo(GenericAlias instance), ] +# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +reveal_type(A.__mro__) ``` ## `typing.Tuple` @@ -100,7 +99,7 @@ from typing import Any, Tuple class A: ... def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]): - reveal_type(c) # revealed: tuple + reveal_type(c) # revealed: tuple[Unknown, ...] reveal_type(d) # revealed: tuple[int, A] reveal_type(e) # revealed: @Todo(full tuple[...] support) ``` @@ -115,7 +114,6 @@ from typing import Tuple class C(Tuple): ... -# TODO: generic protocols -# revealed: tuple[, , , , , , , @Todo(`Protocol[]` subscript), typing.Generic, ] +# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] reveal_type(C.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 3132989bdaee48..22fba74fe54967 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -151,7 +151,8 @@ static_assert(not is_subtype_of(tuple[B1, B2], tuple[()])) static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1])) static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1, A2, Unrelated])) -static_assert(is_subtype_of(tuple[int], tuple)) +# TODO: should pass +static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error] ``` ## Union types diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index 23e7e6e43e5e1b..d54f0407e84202 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -6,14 +6,20 @@ cpython # access to field whilst being initialized, too many cycle iterations discord.py # some kind of hang, only when multi-threaded? freqtrade # hangs hydpy # too many iterations +ibis # too many iterations +jax # too many iterations +packaging # too many iterations pandas # slow pandas-stubs # hangs/slow, or else https://github.com/salsa-rs/salsa/issues/831 pandera # stack overflow +pip # vendors packaging, see above prefect # slow pylint # cycle panics (self-recursive type alias) +pyodide # too many cycle iterations pywin32 # bad use-def map (binding with definitely-visible unbound) schemathesis # https://github.com/salsa-rs/salsa/issues/831 scikit-learn # success, but mypy-primer hangs processing the output +setuptools # vendors packaging, see above spack # success, but mypy-primer hangs processing the output spark # too many iterations steam.py # hangs diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index a437e24ade3980..abaebc99b119e5 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -42,13 +42,11 @@ git-revise graphql-core httpx-caching hydra-zen -ibis ignite imagehash isort itsdangerous janus -jax jinja koda-validate kopf @@ -70,11 +68,9 @@ openlibrary operator optuna paasta -packaging paroxython parso pegen -pip poetry porcupine ppb-vector @@ -86,7 +82,6 @@ pydantic pyinstrument pyjwt pylox -pyodide pyp pyppeteer pytest @@ -100,7 +95,6 @@ rotki schema_salad scipy scrapy -setuptools sockeye speedrun.com_global_scoreboard_webapp sphinx diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ed2f79231e4e12..dcc0932e9aed8e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -662,7 +662,7 @@ impl<'db> Type<'db> { pub fn contains_todo(&self, db: &'db dyn Db) -> bool { match self { - Self::Dynamic(DynamicType::Todo(_) | DynamicType::SubscriptedProtocol) => true, + Self::Dynamic(DynamicType::Todo(_)) => true, Self::AlwaysFalsy | Self::AlwaysTruthy @@ -703,9 +703,7 @@ impl<'db> Type<'db> { } Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() { - SubclassOfInner::Dynamic( - DynamicType::Todo(_) | DynamicType::SubscriptedProtocol, - ) => true, + SubclassOfInner::Dynamic(DynamicType::Todo(_)) => true, SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false, SubclassOfInner::Class(_) => false, }, @@ -722,12 +720,10 @@ impl<'db> Type<'db> { Self::BoundSuper(bound_super) => { matches!( bound_super.pivot_class(db), - ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::SubscriptedProtocol) + ClassBase::Dynamic(DynamicType::Todo(_)) ) || matches!( bound_super.owner(db), - SuperOwnerKind::Dynamic( - DynamicType::Todo(_) | DynamicType::SubscriptedProtocol - ) + SuperOwnerKind::Dynamic(DynamicType::Todo(_)) ) } @@ -3939,6 +3935,9 @@ impl<'db> Type<'db> { ); Signatures::single(signature) } + Some(KnownClass::NamedTuple) => { + Signatures::single(CallableSignature::todo("functional `NamedTuple` syntax")) + } Some(KnownClass::Object) => { // ```py // class object: @@ -4328,6 +4327,12 @@ impl<'db> Type<'db> { return Ok(UnionType::from_elements(db, tuple_type.elements(db))); } + if let Type::GenericAlias(alias) = self { + if alias.origin(db).is_known(db, KnownClass::Tuple) { + return Ok(todo_type!("*tuple[] annotations")); + } + } + let try_call_dunder_getitem = || { self.try_call_dunder( db, @@ -4826,7 +4831,7 @@ impl<'db> Type<'db> { KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")), - KnownInstanceType::Protocol => Err(InvalidTypeExpressionError { + KnownInstanceType::Protocol(_) => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], fallback_type: Type::unknown(), }), @@ -4931,9 +4936,6 @@ impl<'db> Type<'db> { Some(KnownClass::UnionType) => Ok(todo_type!( "Support for `types.UnionType` instances in type expressions" )), - Some(KnownClass::NamedTuple) => Ok(todo_type!( - "Support for functional `typing.NamedTuple` syntax" - )), _ => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType( *self @@ -5093,6 +5095,10 @@ impl<'db> Type<'db> { instance.apply_type_mapping(db, type_mapping), ), + Type::ProtocolInstance(instance) => { + Type::ProtocolInstance(instance.apply_specialization(db, type_mapping)) + } + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet( function.apply_type_mapping(db, type_mapping), @@ -5176,8 +5182,6 @@ impl<'db> Type<'db> { | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::BoundSuper(_) - // Same for `ProtocolInstance` - | Type::ProtocolInstance(_) | Type::KnownInstance(_) => self, } } @@ -5498,9 +5502,6 @@ pub enum DynamicType { /// /// This variant should be created with the `todo_type!` macro. Todo(TodoType), - /// Temporary type until we support generic protocols. - /// We use a separate variant (instead of `Todo(…)`) in order to be able to match on them explicitly. - SubscriptedProtocol, } impl std::fmt::Display for DynamicType { @@ -5511,11 +5512,6 @@ impl std::fmt::Display for DynamicType { // `DynamicType::Todo`'s display should be explicit that is not a valid display of // any other type DynamicType::Todo(todo) => write!(f, "@Todo{todo}"), - DynamicType::SubscriptedProtocol => f.write_str(if cfg!(debug_assertions) { - "@Todo(`Protocol[]` subscript)" - } else { - "@Todo" - }), } } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index e06fc9e8904da7..422eea1ea95b9a 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -312,7 +312,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic(_) => false, + ClassBase::Protocol(_) | ClassBase::Generic(_) => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -350,7 +350,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic(_) => false, + ClassBase::Protocol(_) | ClassBase::Generic(_) => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -536,7 +536,10 @@ impl<'db> ClassLiteral<'db> { pub(crate) fn legacy_generic_context(self, db: &'db dyn Db) -> Option> { self.explicit_bases(db).iter().find_map(|base| match base { - Type::KnownInstance(KnownInstanceType::Generic(generic_context)) => *generic_context, + Type::KnownInstance( + KnownInstanceType::Generic(generic_context) + | KnownInstanceType::Protocol(generic_context), + ) => *generic_context, _ => None, }) } @@ -608,6 +611,17 @@ impl<'db> ClassLiteral<'db> { } } + /// Returns a specialization of this class with a `@Todo`-type + pub(crate) fn todo_specialization(self, db: &'db dyn Db, todo: &'static str) -> ClassType<'db> { + match self.generic_context(db) { + None => ClassType::NonGeneric(self), + Some(generic_context) => { + let specialization = generic_context.todo_specialization(db, todo); + ClassType::Generic(GenericAlias::new(db, self, specialization)) + } + } + } + /// Returns the unknown specialization of this class. For non-generic classes, the class is /// returned unchanged. For a non-specialized generic class, we return a generic alias that /// maps each of the class's typevars to `Unknown`. @@ -678,13 +692,11 @@ impl<'db> ClassLiteral<'db> { // - OR be the last-but-one base (with the final base being `Generic[]` or `object`) // - OR be the last-but-two base (with the penultimate base being `Generic[]` // and the final base being `object`) - self.explicit_bases(db).iter().rev().take(3).any(|base| { - matches!( - base, - Type::KnownInstance(KnownInstanceType::Protocol) - | Type::Dynamic(DynamicType::SubscriptedProtocol) - ) - }) + self.explicit_bases(db) + .iter() + .rev() + .take(3) + .any(|base| matches!(base, Type::KnownInstance(KnownInstanceType::Protocol(_)))) }) } @@ -1011,12 +1023,8 @@ impl<'db> ClassLiteral<'db> { for superclass in mro_iter { match superclass { - ClassBase::Dynamic(DynamicType::SubscriptedProtocol) - | ClassBase::Generic(_) - | ClassBase::Protocol => { - // TODO: We currently skip `Protocol` when looking up class members, in order to - // avoid creating many dynamic types in our test suite that would otherwise - // result from looking up attributes on builtin types like `str`, `list`, `tuple` + ClassBase::Generic(_) | ClassBase::Protocol(_) => { + // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { // Note: calling `Type::from(superclass).member()` would be incorrect here. @@ -1354,12 +1362,8 @@ impl<'db> ClassLiteral<'db> { for superclass in self.iter_mro(db, specialization) { match superclass { - ClassBase::Dynamic(DynamicType::SubscriptedProtocol) - | ClassBase::Generic(_) - | ClassBase::Protocol => { - // TODO: We currently skip these when looking up instance members, in order to - // avoid creating many dynamic types in our test suite that would otherwise - // result from looking up attributes on builtin types like `str`, `list`, `tuple` + ClassBase::Generic(_) | ClassBase::Protocol(_) => { + // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { return SymbolAndQualifiers::todo( diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index be27959c501fb7..551f47dafb3ad2 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -18,7 +18,7 @@ pub enum ClassBase<'db> { Class(ClassType<'db>), /// Although `Protocol` is not a class in typeshed's stubs, it is at runtime, /// and can appear in the MRO of a class. - Protocol, + Protocol(Option>), /// Bare `Generic` cannot be subclassed directly in user code, /// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`, /// `Protocol[T]`, or bare `Protocol`. @@ -50,11 +50,17 @@ impl<'db> ClassBase<'db> { ClassBase::Class(ClassType::Generic(alias)) => { write!(f, "", alias.display(self.db)) } - ClassBase::Protocol => f.write_str("typing.Protocol"), + ClassBase::Protocol(generic_context) => { + f.write_str("typing.Protocol")?; + if let Some(generic_context) = generic_context { + generic_context.display(self.db).fmt(f)?; + } + Ok(()) + } ClassBase::Generic(generic_context) => { f.write_str("typing.Generic")?; if let Some(generic_context) = generic_context { - write!(f, "{}", generic_context.display(self.db))?; + generic_context.display(self.db).fmt(f)?; } Ok(()) } @@ -71,9 +77,7 @@ impl<'db> ClassBase<'db> { ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", ClassBase::Dynamic(DynamicType::Todo(_)) => "@Todo", - ClassBase::Protocol | ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => { - "Protocol" - } + ClassBase::Protocol(_) => "Protocol", ClassBase::Generic(_) => "Generic", } } @@ -199,7 +203,9 @@ impl<'db> ClassBase<'db> { KnownInstanceType::Callable => { Self::try_from_type(db, todo_type!("Support for Callable as a base class")) } - KnownInstanceType::Protocol => Some(ClassBase::Protocol), + KnownInstanceType::Protocol(generic_context) => { + Some(ClassBase::Protocol(generic_context)) + } KnownInstanceType::Generic(generic_context) => { Some(ClassBase::Generic(generic_context)) } @@ -210,14 +216,14 @@ impl<'db> ClassBase<'db> { pub(super) fn into_class(self) -> Option> { match self { Self::Class(class) => Some(class), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => None, + Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None, } } fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { match self { Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => self, + Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self, } } @@ -241,7 +247,7 @@ impl<'db> ClassBase<'db> { .try_mro(db, specialization) .is_err_and(MroError::is_cycle) } - ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol => false, + ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false, } } @@ -252,11 +258,8 @@ impl<'db> ClassBase<'db> { additional_specialization: Option>, ) -> impl Iterator> { match self { - ClassBase::Protocol => { - ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(None)) - } - ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => { - ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(None)) + ClassBase::Protocol(context) => { + ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context)) } ClassBase::Dynamic(_) | ClassBase::Generic(_) => { ClassBaseMroIterator::length_2(db, self) @@ -279,7 +282,9 @@ impl<'db> From> for Type<'db> { match value { ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Class(class) => class.into(), - ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol), + ClassBase::Protocol(generic_context) => { + Type::KnownInstance(KnownInstanceType::Protocol(generic_context)) + } ClassBase::Generic(generic_context) => { Type::KnownInstance(KnownInstanceType::Generic(generic_context)) } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index ea7cb78618bc30..b5bc35e0e2a2a5 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -174,7 +174,9 @@ impl Display for DisplayRepresentation<'_> { function = function.name(self.db), specialization = if let Some(specialization) = function.specialization(self.db) { - specialization.display_short(self.db).to_string() + specialization + .display_short(self.db, TupleSpecialization::No) + .to_string() } else { String::new() }, @@ -187,7 +189,9 @@ impl Display for DisplayRepresentation<'_> { function = function.name(self.db), specialization = if let Some(specialization) = function.specialization(self.db) { - specialization.display_short(self.db).to_string() + specialization + .display_short(self.db, TupleSpecialization::No) + .to_string() } else { String::new() }, @@ -274,7 +278,10 @@ impl Display for DisplayGenericAlias<'_> { f, "{origin}{specialization}", origin = self.origin.name(self.db), - specialization = self.specialization.display_short(self.db), + specialization = self.specialization.display_short( + self.db, + TupleSpecialization::from_class(self.db, self.origin) + ), ) } } @@ -327,22 +334,32 @@ impl Display for DisplayGenericContext<'_> { impl<'db> Specialization<'db> { /// Renders the specialization in full, e.g. `{T = int, U = str}`. - pub fn display(&'db self, db: &'db dyn Db) -> DisplaySpecialization<'db> { + pub fn display( + &'db self, + db: &'db dyn Db, + tuple_specialization: TupleSpecialization, + ) -> DisplaySpecialization<'db> { DisplaySpecialization { typevars: self.generic_context(db).variables(db), types: self.types(db), db, full: true, + tuple_specialization, } } /// Renders the specialization as it would appear in a subscript expression, e.g. `[int, str]`. - pub fn display_short(&'db self, db: &'db dyn Db) -> DisplaySpecialization<'db> { + pub fn display_short( + &'db self, + db: &'db dyn Db, + tuple_specialization: TupleSpecialization, + ) -> DisplaySpecialization<'db> { DisplaySpecialization { typevars: self.generic_context(db).variables(db), types: self.types(db), db, full: false, + tuple_specialization, } } } @@ -352,6 +369,7 @@ pub struct DisplaySpecialization<'db> { types: &'db [Type<'db>], db: &'db dyn Db, full: bool, + tuple_specialization: TupleSpecialization, } impl Display for DisplaySpecialization<'_> { @@ -373,11 +391,34 @@ impl Display for DisplaySpecialization<'_> { } ty.display(self.db).fmt(f)?; } + if self.tuple_specialization.is_yes() { + f.write_str(", ...")?; + } f.write_char(']') } } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TupleSpecialization { + Yes, + No, +} + +impl TupleSpecialization { + const fn is_yes(self) -> bool { + matches!(self, Self::Yes) + } + + fn from_class(db: &dyn Db, class: ClassLiteral) -> Self { + if class.is_known(db, KnownClass::Tuple) { + Self::Yes + } else { + Self::No + } + } +} + impl<'db> CallableType<'db> { pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayCallableType<'db> { DisplayCallableType { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 5c5b4de549e2da..cacad480c82f4f 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -4,8 +4,8 @@ use rustc_hash::FxHashMap; use crate::semantic_index::SemanticIndex; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - declaration_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance, - TypeVarVariance, UnionType, + declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, + TypeVarInstance, TypeVarVariance, UnionType, }; use crate::{Db, FxOrderSet}; @@ -17,6 +17,7 @@ use crate::{Db, FxOrderSet}; pub struct GenericContext<'db> { #[returns(ref)] pub(crate) variables: FxOrderSet>, + pub(crate) origin: GenericContextOrigin, } impl<'db> GenericContext<'db> { @@ -30,7 +31,7 @@ impl<'db> GenericContext<'db> { .iter() .filter_map(|type_param| Self::variable_from_type_param(db, index, type_param)) .collect(); - Self::new(db, variables) + Self::new(db, variables, GenericContextOrigin::TypeParameterList) } fn variable_from_type_param( @@ -76,7 +77,11 @@ impl<'db> GenericContext<'db> { if variables.is_empty() { return None; } - Some(Self::new(db, variables)) + Some(Self::new( + db, + variables, + GenericContextOrigin::LegacyGenericFunction, + )) } /// Creates a generic context from the legacy `TypeVar`s that appear in class's base class @@ -92,7 +97,7 @@ impl<'db> GenericContext<'db> { if variables.is_empty() { return None; } - Some(Self::new(db, variables)) + Some(Self::new(db, variables, GenericContextOrigin::Inherited)) } pub(crate) fn len(self, db: &'db dyn Db) -> usize { @@ -133,6 +138,20 @@ impl<'db> GenericContext<'db> { self.specialize_partial(db, &vec![None; self.variables(db).len()]) } + #[allow(unused_variables)] // Only unused in release builds + pub(crate) fn todo_specialization( + self, + db: &'db dyn Db, + todo: &'static str, + ) -> Specialization<'db> { + let types = self + .variables(db) + .iter() + .map(|typevar| typevar.default_ty(db).unwrap_or(todo_type!(todo))) + .collect(); + self.specialize(db, types) + } + pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> { let types = self .variables(db) @@ -209,6 +228,58 @@ impl<'db> GenericContext<'db> { } } +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum GenericContextOrigin { + LegacyBase(LegacyGenericBase), + Inherited, + LegacyGenericFunction, + TypeParameterList, +} + +impl GenericContextOrigin { + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::LegacyBase(base) => base.as_str(), + Self::Inherited => "inherited", + Self::LegacyGenericFunction => "legacy generic function", + Self::TypeParameterList => "type parameter list", + } + } +} + +impl std::fmt::Display for GenericContextOrigin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum LegacyGenericBase { + Generic, + Protocol, +} + +impl LegacyGenericBase { + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::Generic => "`typing.Generic`", + Self::Protocol => "subscripted `typing.Protocol`", + } + } +} + +impl std::fmt::Display for LegacyGenericBase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl From for GenericContextOrigin { + fn from(base: LegacyGenericBase) -> Self { + Self::LegacyBase(base) + } +} + /// An assignment of a specific type to each type variable in a generic scope. /// /// TODO: Handle nested specializations better, with actual parent links to the specialization of diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 7d8530f0ec4ede..e071c60306f4e9 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -107,6 +107,7 @@ use super::diagnostic::{ report_unresolved_reference, INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, REDUNDANT_CAST, STATIC_ASSERT_ERROR, SUBCLASS_OF_FINAL_CLASS, TYPE_ASSERTION_FAILURE, }; +use super::generics::{GenericContextOrigin, LegacyGenericBase}; use super::slots::check_class_slots; use super::string_annotation::{ parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, @@ -1020,14 +1021,16 @@ impl<'db> TypeInferenceBuilder<'db> { // (5) Check that a generic class does not have invalid or conflicting generic // contexts. - if class.pep695_generic_context(self.db()).is_some() - && class.legacy_generic_context(self.db()).is_some() - { - if let Some(builder) = self.context.report_lint(&INVALID_GENERIC_CLASS, class_node) - { - builder.into_diagnostic( - "Cannot both inherit from `Generic` and use PEP 695 type variables", - ); + if class.pep695_generic_context(self.db()).is_some() { + if let Some(legacy_context) = class.legacy_generic_context(self.db()) { + if let Some(builder) = + self.context.report_lint(&INVALID_GENERIC_CLASS, class_node) + { + builder.into_diagnostic(format_args!( + "Cannot both inherit from {} and use PEP 695 type variables", + legacy_context.origin(self.db()) + )); + } } } @@ -4734,6 +4737,7 @@ impl<'db> TypeInferenceBuilder<'db> { | KnownClass::Property | KnownClass::Super | KnownClass::TypeVar + | KnownClass::NamedTuple ) ) // temporary special-casing for all subclasses of `enum.Enum` @@ -5795,8 +5799,6 @@ impl<'db> TypeInferenceBuilder<'db> { | (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown), (todo @ Type::Dynamic(DynamicType::Todo(_)), _, _) | (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo), - (todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _, _) - | (_, todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _) => Some(todo), (Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never), (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some( @@ -6884,6 +6886,13 @@ impl<'db> TypeInferenceBuilder<'db> { // special cases, too. let value_ty = self.infer_expression(value); if let Type::ClassLiteral(class) = value_ty { + if class.is_known(self.db(), KnownClass::Tuple) { + self.infer_expression(slice); + // TODO heterogeneous and homogeneous tuples in value expressions + return Type::from( + class.todo_specialization(self.db(), "Generic tuple specializations"), + ); + } if let Some(generic_context) = class.generic_context(self.db()) { return self.infer_explicit_class_specialization( subscript, @@ -7072,14 +7081,44 @@ impl<'db> TypeInferenceBuilder<'db> { value_ty, Type::IntLiteral(i64::from(bool)), ), - (Type::KnownInstance(KnownInstanceType::Protocol), _, _) => { - Type::Dynamic(DynamicType::SubscriptedProtocol) + (Type::KnownInstance(KnownInstanceType::Protocol(None)), Type::Tuple(typevars), _) => { + self.legacy_generic_class_context( + value_node, + typevars.elements(self.db()), + LegacyGenericBase::Protocol, + ) + .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) + .unwrap_or_else(Type::unknown) + } + (Type::KnownInstance(KnownInstanceType::Protocol(None)), typevar, _) => self + .legacy_generic_class_context( + value_node, + std::slice::from_ref(&typevar), + LegacyGenericBase::Protocol, + ) + .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) + .unwrap_or_else(Type::unknown), + (Type::KnownInstance(KnownInstanceType::Protocol(Some(_))), _, _) => { + // TODO: emit a diagnostic + todo_type!("doubly-specialized typing.Protocol") } (Type::KnownInstance(KnownInstanceType::Generic(None)), Type::Tuple(typevars), _) => { - self.infer_subscript_legacy_generic_class(value_node, typevars.elements(self.db())) + self.legacy_generic_class_context( + value_node, + typevars.elements(self.db()), + LegacyGenericBase::Generic, + ) + .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) + .unwrap_or_else(Type::unknown) } (Type::KnownInstance(KnownInstanceType::Generic(None)), typevar, _) => self - .infer_subscript_legacy_generic_class(value_node, std::slice::from_ref(&typevar)), + .legacy_generic_class_context( + value_node, + std::slice::from_ref(&typevar), + LegacyGenericBase::Generic, + ) + .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) + .unwrap_or_else(Type::unknown), (Type::KnownInstance(KnownInstanceType::Generic(Some(_))), _, _) => { // TODO: emit a diagnostic todo_type!("doubly-specialized typing.Generic") @@ -7238,11 +7277,12 @@ impl<'db> TypeInferenceBuilder<'db> { } } - fn infer_subscript_legacy_generic_class( + fn legacy_generic_class_context( &mut self, value_node: &ast::Expr, typevars: &[Type<'db>], - ) -> Type<'db> { + origin: LegacyGenericBase, + ) -> Option> { let typevars: Option> = typevars .iter() .map(|typevar| match typevar { @@ -7252,7 +7292,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node) { builder.into_diagnostic(format_args!( - "`{}` is not a valid argument to `typing.Generic`", + "`{}` is not a valid argument to {origin}", typevar.display(self.db()), )); } @@ -7260,11 +7300,9 @@ impl<'db> TypeInferenceBuilder<'db> { } }) .collect(); - let Some(typevars) = typevars else { - return Type::unknown(); - }; - let generic_context = GenericContext::new(self.db(), typevars); - Type::KnownInstance(KnownInstanceType::Generic(Some(generic_context))) + typevars.map(|typevars| { + GenericContext::new(self.db(), typevars, GenericContextOrigin::from(origin)) + }) } fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> { @@ -8420,9 +8458,14 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_type_expression(arguments_slice); todo_type!("`Unpack[]` special form") } - KnownInstanceType::Protocol => { + KnownInstanceType::Protocol(_) => { self.infer_type_expression(arguments_slice); - Type::Dynamic(DynamicType::SubscriptedProtocol) + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`typing.Protocol` is not allowed in type expressions", + )); + } + Type::unknown() } KnownInstanceType::Generic(_) => { self.infer_type_expression(arguments_slice); diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index c834927d6b2773..59de7274672dc0 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -260,6 +260,21 @@ impl<'db> ProtocolInstanceType<'db> { .unwrap_or_else(|| KnownClass::Object.to_instance(db).instance_member(db, name)), } } + + pub(super) fn apply_specialization<'a>( + self, + db: &'db dyn Db, + type_mapping: TypeMapping<'a, 'db>, + ) -> Self { + match self.0 { + Protocol::FromClass(class) => Self(Protocol::FromClass( + class.apply_type_mapping(db, type_mapping), + )), + Protocol::Synthesized(synthesized) => Self(Protocol::Synthesized( + synthesized.apply_type_mapping(db, type_mapping), + )), + } + } } /// An enumeration of the two kinds of protocol types: those that originate from a class @@ -287,6 +302,7 @@ impl<'db> Protocol<'db> { mod synthesized_protocol { use crate::db::Db; + use crate::types::generics::TypeMapping; use crate::types::protocol_class::ProtocolInterface; /// A "synthesized" protocol type that is dissociated from a class definition in source code. @@ -306,6 +322,14 @@ mod synthesized_protocol { Self(interface.normalized(db)) } + pub(super) fn apply_type_mapping<'a>( + self, + db: &'db dyn Db, + type_mapping: TypeMapping<'a, 'db>, + ) -> Self { + Self(self.0.specialized_and_normalized(db, type_mapping)) + } + pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> { self.0 } diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index 41849ddc6549e6..a9966fc2a4ca34 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -60,7 +60,7 @@ pub enum KnownInstanceType<'db> { /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) OrderedDict, /// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`) - Protocol, + Protocol(Option>), /// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`) Generic(Option>), /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) @@ -146,7 +146,7 @@ impl<'db> KnownInstanceType<'db> { | Self::Deque | Self::ChainMap | Self::OrderedDict - | Self::Protocol + | Self::Protocol(_) | Self::Generic(_) | Self::ReadOnly | Self::TypeAliasType(_) @@ -203,7 +203,7 @@ impl<'db> KnownInstanceType<'db> { Self::Deque => KnownClass::StdlibAlias, Self::ChainMap => KnownClass::StdlibAlias, Self::OrderedDict => KnownClass::StdlibAlias, - Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says + Self::Protocol(_) => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says Self::Generic(_) => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeAliasType(_) => KnownClass::TypeAliasType, @@ -249,7 +249,7 @@ impl<'db> KnownInstanceType<'db> { "ChainMap" => Self::ChainMap, "OrderedDict" => Self::OrderedDict, "Generic" => Self::Generic(None), - "Protocol" => Self::Protocol, + "Protocol" => Self::Protocol(None), "Optional" => Self::Optional, "Union" => Self::Union, "NoReturn" => Self::NoReturn, @@ -311,7 +311,7 @@ impl<'db> KnownInstanceType<'db> { | Self::Generic(_) | Self::Callable => module.is_typing(), Self::Annotated - | Self::Protocol + | Self::Protocol(_) | Self::Literal | Self::LiteralString | Self::Never @@ -384,11 +384,17 @@ impl Display for KnownInstanceRepr<'_> { KnownInstanceType::Deque => f.write_str("typing.Deque"), KnownInstanceType::ChainMap => f.write_str("typing.ChainMap"), KnownInstanceType::OrderedDict => f.write_str("typing.OrderedDict"), - KnownInstanceType::Protocol => f.write_str("typing.Protocol"), + KnownInstanceType::Protocol(generic_context) => { + f.write_str("typing.Protocol")?; + if let Some(generic_context) = generic_context { + generic_context.display(self.db).fmt(f)?; + } + Ok(()) + } KnownInstanceType::Generic(generic_context) => { f.write_str("typing.Generic")?; if let Some(generic_context) = generic_context { - write!(f, "{}", generic_context.display(self.db))?; + generic_context.display(self.db).fmt(f)?; } Ok(()) } diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 8167337669d6ae..69196234648c1e 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -174,7 +174,9 @@ impl<'db> Mro<'db> { continue; } match base { - ClassBase::Class(_) | ClassBase::Generic(_) | ClassBase::Protocol => { + ClassBase::Class(_) + | ClassBase::Generic(_) + | ClassBase::Protocol(_) => { errors.push(DuplicateBaseError { duplicate_base: base, first_index: *first_index, diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index da43422d1416d3..871b6d83516f92 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -8,7 +8,7 @@ use crate::{ db::Db, semantic_index::{symbol_table, use_def_map}, symbol::{symbol_from_bindings, symbol_from_declarations}, - types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeQualifiers}, + types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers}, }; impl<'db> ClassLiteral<'db> { @@ -146,6 +146,29 @@ impl<'db> ProtocolInterface<'db> { Self::SelfReference => Self::SelfReference, } } + + pub(super) fn specialized_and_normalized<'a>( + self, + db: &'db dyn Db, + type_mapping: TypeMapping<'a, 'db>, + ) -> Self { + match self { + Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( + db, + members + .inner(db) + .iter() + .map(|(name, data)| { + ( + name.clone(), + data.apply_type_mapping(db, type_mapping).normalized(db), + ) + }) + .collect::>(), + )), + Self::SelfReference => Self::SelfReference, + } + } } #[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update)] @@ -161,6 +184,13 @@ impl<'db> ProtocolMemberData<'db> { qualifiers: self.qualifiers, } } + + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + Self { + ty: self.ty.apply_type_mapping(db, type_mapping), + qualifiers: self.qualifiers, + } + } } /// A single member of a protocol interface. diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index c61ca001fd79d2..2099badb0d519a 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -153,11 +153,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right), (ClassBase::Class(_), _) => Ordering::Less, (_, ClassBase::Class(_)) => Ordering::Greater, - (ClassBase::Protocol, _) => Ordering::Less, - (_, ClassBase::Protocol) => Ordering::Greater, + + (ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right), + (ClassBase::Protocol(_), _) => Ordering::Less, + (_, ClassBase::Protocol(_)) => Ordering::Greater, + (ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right), (ClassBase::Generic(_), _) => Ordering::Less, (_, ClassBase::Generic(_)) => Ordering::Greater, + (ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => { dynamic_elements_ordering(left, right) } @@ -253,8 +257,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (KnownInstanceType::Generic(_), _) => Ordering::Less, (_, KnownInstanceType::Generic(_)) => Ordering::Greater, - (KnownInstanceType::Protocol, _) => Ordering::Less, - (_, KnownInstanceType::Protocol) => Ordering::Greater, + (KnownInstanceType::Protocol(left), KnownInstanceType::Protocol(right)) => { + left.cmp(right) + } + (KnownInstanceType::Protocol(_), _) => Ordering::Less, + (_, KnownInstanceType::Protocol(_)) => Ordering::Greater, (KnownInstanceType::NoReturn, _) => Ordering::Less, (_, KnownInstanceType::NoReturn) => Ordering::Greater, @@ -379,8 +386,5 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering #[cfg(not(debug_assertions))] (DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal, - - (DynamicType::SubscriptedProtocol, _) => Ordering::Less, - (_, DynamicType::SubscriptedProtocol) => Ordering::Greater, } } From 2c4cbb6e29d29669a86296deffd84aea1b03dd85 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 10:32:03 -0400 Subject: [PATCH 002/487] ty: get rid of `lint:` prefix in ID for diagnostic rendering In #289, we seem to have consensus that this prefix isn't really pulling its weight. Ref #289 --- crates/ruff_db/src/diagnostic/mod.rs | 14 +++ crates/ruff_db/src/diagnostic/render.rs | 100 +++++++++--------- crates/ty/tests/cli.rs | 80 +++++++------- crates/ty_ide/src/goto.rs | 34 +++--- crates/ty_ide/src/hover.rs | 36 +++---- ..._-_Invalid_`__set__`_method_signature.snap | 2 +- ...a_descriptors_-_Invalid_argument_type.snap | 2 +- ..._attributes_with_class-level_defaults.snap | 4 +- ...ignment_-_Possibly-unbound_attributes.snap | 4 +- ...assignment_-_Pure_instance_attributes.snap | 4 +- ...t_-_Setting_attributes_on_union_types.snap | 2 +- ...ibute_assignment_-_Unknown_attributes.snap | 4 +- ..._-_Attribute_assignment_-_`ClassVar`s.snap | 4 +- ...ts_imported_from_an_unresolved_module.snap | 2 +- ...ructures_-_Unresolvable_module_import.snap | 2 +- ...ures_-_Unresolvable_submodule_imports.snap | 4 +- ..._For_loops_-_Bad_`__getitem__`_method.snap | 2 +- ...for.md_-_For_loops_-_Invalid_iterable.snap | 2 +- ...New_over_old_style_iteration_protocol.snap | 2 +- ...hod_and_`__getitem__`_is_not_callable.snap | 2 +- ...bly-not-callable_`__getitem__`_method.snap | 4 +- ...ossibly_invalid_`__getitem__`_methods.snap | 4 +- ...-_Possibly_invalid_`__iter__`_methods.snap | 4 +- ..._-_Possibly_invalid_`__next__`_method.snap | 4 +- ..._iter__`_and_bad_`__getitem__`_method.snap | 2 +- ..._`_and_possibly_invalid_`__getitem__`.snap | 4 +- ..._`_and_possibly_unbound_`__getitem__`.snap | 2 +- ...element_has_invalid_`__iter__`_method.snap | 2 +- ...nion_element_has_no_`__iter__`_method.snap | 2 +- ...or_loops_-_With_non-callable_iterator.snap | 4 +- ...__iter__`_does_not_return_an_iterator.snap | 2 +- ...__iter__`_method_with_a_bad_signature.snap | 2 +- ...tor_with_an_invalid_`__next__`_method.snap | 4 +- ...cy_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...95_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...types_with_invalid_`__bool__`_methods.snap | 2 +- ...lid_argument_type_diagnostics_-_Basic.snap | 2 +- ...t_type_diagnostics_-_Calls_to_methods.snap | 2 +- ...nt_type_diagnostics_-_Different_files.snap | 2 +- ..._diagnostics_-_Different_source_order.snap | 2 +- ...nt_type_diagnostics_-_Many_parameters.snap | 2 +- ...Many_parameters_across_multiple_lines.snap | 2 +- ...eters_with_multiple_invalid_arguments.snap | 6 +- ...hose_type_is_vendored_from_`typeshed`.snap | 2 +- ...gument_types_-_Keyword_only_arguments.snap | 2 +- ..._of_argument_types_-_Mix_of_arguments.snap | 2 +- ...argument_types_-_One_keyword_argument.snap | 2 +- ...y_of_argument_types_-_Only_positional.snap | 2 +- ..._argument_types_-_Synthetic_arguments.snap | 2 +- ...f_argument_types_-_Variadic_arguments.snap | 2 +- ...nt_types_-_Variadic_keyword_arguments.snap | 2 +- ...oesn't_implement_`__bool__`_correctly.snap | 4 +- ...__bases__`_lists_with_duplicate_bases.snap | 18 ++-- ...stics_-_Calls_to_overloaded_functions.snap | 2 +- ...hat_implements_`__bool__`_incorrectly.snap | 2 +- ...ds_-_Invalid_-_At_least_two_overloads.snap | 4 +- ...onsistent_decorators_-_`@classmethod`.snap | 6 +- ..._-_Inconsistent_decorators_-_`@final`.snap | 6 +- ...Inconsistent_decorators_-_`@override`.snap | 6 +- ...t_an_implementation_-_Regular_modules.snap | 4 +- ...Protocols_-_Calls_to_protocol_classes.snap | 6 +- ...lid_calls_to_`get_protocol_members()`.snap | 4 +- ..._-_Protocols_-_Narrowing_of_protocols.snap | 4 +- ...ion_return_type_-_Generator_functions.snap | 4 +- ...ype_-_Invalid_conditional_return_type.snap | 6 +- ...n_type_-_Invalid_implicit_return_type.snap | 8 +- ...ion_return_type_-_Invalid_return_type.snap | 8 +- ...pe_-_Invalid_return_type_in_stub_file.snap | 6 +- ..._don't_implement_`__bool__`_correctly.snap | 4 +- ..._Shadowing_-_Implicit_class_shadowing.snap | 2 +- ...adowing_-_Implicit_function_shadowing.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...ng_-_Exactly_too_few_values_to_unpack.snap | 2 +- ...g_-_Exactly_too_many_values_to_unpack.snap | 2 +- ...acking_-_Right_hand_side_not_iterable.snap | 2 +- ..._Unpacking_-_Too_few_values_to_unpack.snap | 2 +- ...vable_import_that_does_not_use_`from`.snap | 2 +- ...solvable_module_but_unresolvable_item.snap | 2 +- ...`from`_with_an_unknown_current_module.snap | 2 +- ..._`from`_with_an_unknown_nested_module.snap | 2 +- ...ng_`from`_with_an_unresolvable_module.snap | 2 +- ...ing_`from`_with_too_many_leading_dots.snap | 2 +- ...l__`_attribute,_but_it's_not_callable.snap | 2 +- ...hod,_but_has_an_incorrect_return_type.snap | 2 +- ..._method,_but_has_incorrect_parameters.snap | 2 +- ...ember_has_incorrect_`__bool__`_method.snap | 2 +- 89 files changed, 271 insertions(+), 257 deletions(-) diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 2f61c8a15d2887..c04964e26d187d 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -649,6 +649,20 @@ impl DiagnosticId { }) } + /// Returns a "concise" description of this diagnostic ID. + /// + /// Specifically, this avoids adding a `lint:` prefix (or other + /// possible category prefixes, although `lint` is the only one + /// as of 2025-05-09) to the diagnostic ID when this is a lint + /// identifier. This is useful in diagnostic rendering where we + /// want to elide this prefix. + fn as_concise_str(&self) -> &str { + match self.as_str() { + Ok(name) => name, + Err(DiagnosticAsStrError::Category { name, .. }) => name, + } + } + pub fn is_invalid_syntax(&self) -> bool { matches!(self, Self::InvalidSyntax) } diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index c02565c684469d..ede5a28c4a7381 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -79,7 +79,7 @@ impl std::fmt::Display for DisplayDiagnostic<'_> { f, "{severity}[{id}]", severity = fmt_styled(severity, severity_style), - id = fmt_styled(self.diag.id(), stylesheet.emphasis) + id = fmt_styled(self.diag.id().as_concise_str(), stylesheet.emphasis) )?; if let Some(span) = self.diag.primary_span() { @@ -152,7 +152,7 @@ impl<'a> Resolved<'a> { for sub in &diag.inner.subs { diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub)); } - let id = diag.inner.id.to_string(); + let id = diag.inner.id.as_concise_str().to_string(); Resolved { id, diagnostics } } @@ -198,14 +198,14 @@ impl<'a> ResolvedDiagnostic<'a> { }) .collect(); let message = if diag.inner.message.as_str().is_empty() { - diag.inner.id.to_string() + diag.inner.id.as_concise_str().to_string() } else { // TODO: See the comment on `Renderable::id` for // a plausible better idea than smushing the ID // into the diagnostic message. format!( "{id}: {message}", - id = diag.inner.id, + id = diag.inner.id.as_concise_str(), message = diag.inner.message.as_str(), ) }; @@ -799,7 +799,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -823,7 +823,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - warning: lint:test-diagnostic: main diagnostic message + warning: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -843,7 +843,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - info: lint:test-diagnostic: main diagnostic message + info: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -870,7 +870,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -889,7 +889,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -910,7 +910,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> non-ascii:5:1 | 3 | ΔΔΔΔΔΔΔΔΔΔΔΔ @@ -929,7 +929,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> non-ascii:2:2 | 1 | ☃☃☃☃☃☃☃☃☃☃☃☃ @@ -953,7 +953,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 4 | dog @@ -970,7 +970,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 5 | elephant @@ -985,7 +985,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1002,7 +1002,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:11:1 | 9 | inchworm @@ -1019,7 +1019,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 1 | aardvark @@ -1052,7 +1052,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1096,7 +1096,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1121,7 +1121,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1149,7 +1149,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1177,7 +1177,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1202,7 +1202,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1233,7 +1233,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1271,7 +1271,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> spacey-animals:8:1 | 7 | dog @@ -1288,7 +1288,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> spacey-animals:12:1 | 11 | gorilla @@ -1306,7 +1306,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> spacey-animals:13:1 | 11 | gorilla @@ -1346,7 +1346,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> spacey-animals:3:1 | 3 | beetle @@ -1375,7 +1375,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1412,7 +1412,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1449,7 +1449,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1477,7 +1477,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1513,7 +1513,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1552,7 +1552,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1600,7 +1600,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1636,7 +1636,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1659,7 +1659,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1679,7 +1679,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1699,7 +1699,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:4 | 3 | canary @@ -1721,7 +1721,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:4 | 3 | canary @@ -1753,7 +1753,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:4:1 | 2 | beetle @@ -1782,7 +1782,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:4:1 | 2 | beetle @@ -1813,7 +1813,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1848,7 +1848,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1876,7 +1876,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 3 | canary @@ -1908,7 +1908,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:3 | 3 | canary @@ -1930,7 +1930,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:3 | 3 | canary @@ -1963,7 +1963,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:8:1 | 6 | finch @@ -2003,7 +2003,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:5:1 | 5 | elephant @@ -2047,7 +2047,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> fruits:1:1 | 1 | apple @@ -2082,7 +2082,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: lint:test-diagnostic: main diagnostic message + error: test-diagnostic: main diagnostic message --> animals:11:1 | 11 | kangaroo diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 1a1e2de7406fc6..7b84193af6364b 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -144,7 +144,7 @@ fn config_override_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-attribute: Type `` has no attribute `last_exc` + error: unresolved-attribute: Type `` has no attribute `last_exc` --> test.py:5:7 | 4 | # Access `sys.last_exc` that was only added in Python 3.12 @@ -278,7 +278,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-import: Cannot resolve imported module `utils` + error: unresolved-import: Cannot resolve imported module `utils` --> test.py:2:6 | 2 | from utils import add @@ -379,7 +379,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + error: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -389,7 +389,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: lint:unresolved-reference: Name `prin` used when not defined + error: unresolved-reference: Name `prin` used when not defined --> test.py:7:1 | 5 | x = a @@ -417,7 +417,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -458,7 +458,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-import: Cannot resolve imported module `does_not_exit` + error: unresolved-import: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit @@ -468,7 +468,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + error: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit @@ -480,7 +480,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: lint:unresolved-reference: Name `prin` used when not defined + error: unresolved-reference: Name `prin` used when not defined --> test.py:9:1 | 7 | x = a @@ -508,7 +508,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:unresolved-import: Cannot resolve imported module `does_not_exit` + warning: unresolved-import: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit @@ -518,7 +518,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:unresolved-import` was selected on the command line - warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit @@ -561,7 +561,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + error: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -571,7 +571,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: lint:unresolved-reference: Name `prin` used when not defined + error: unresolved-reference: Name `prin` used when not defined --> test.py:7:1 | 5 | x = a @@ -600,7 +600,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -680,7 +680,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -764,7 +764,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -797,7 +797,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any success: false exit_code: 1 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -827,7 +827,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -836,7 +836,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { | info: `lint:unresolved-reference` was selected on the command line - error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -867,7 +867,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: success: false exit_code: 1 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -876,7 +876,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: | info: `lint:unresolved-reference` was selected on the command line - error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -907,7 +907,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -916,7 +916,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { | info: `lint:unresolved-reference` was selected on the command line - error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -969,7 +969,7 @@ fn user_configuration() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 @@ -979,7 +979,7 @@ fn user_configuration() -> anyhow::Result<()> { | info: `lint:division-by-zero` was selected in the configuration file - error: lint:unresolved-reference: Name `prin` used when not defined + error: unresolved-reference: Name `prin` used when not defined --> main.py:7:1 | 5 | x = a @@ -1013,7 +1013,7 @@ fn user_configuration() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 @@ -1023,7 +1023,7 @@ fn user_configuration() -> anyhow::Result<()> { | info: `lint:division-by-zero` was selected in the configuration file - warning: lint:unresolved-reference: Name `prin` used when not defined + warning: unresolved-reference: Name `prin` used when not defined --> main.py:7:1 | 5 | x = a @@ -1073,7 +1073,7 @@ fn check_specific_paths() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero + error: division-by-zero: Cannot divide object of type `Literal[4]` by zero --> project/main.py:2:5 | 2 | y = 4 / 0 # error: division-by-zero @@ -1081,7 +1081,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: lint:unresolved-import: Cannot resolve imported module `main2` + error: unresolved-import: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import @@ -1091,7 +1091,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` + error: unresolved-import: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import @@ -1113,7 +1113,7 @@ fn check_specific_paths() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-import: Cannot resolve imported module `main2` + error: unresolved-import: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import @@ -1123,7 +1123,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` + error: unresolved-import: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import @@ -1185,8 +1185,8 @@ fn concise_diagnostics() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning[lint:unresolved-reference] test.py:2:7: Name `x` used when not defined - error[lint:non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + warning[unresolved-reference] test.py:2:7: Name `x` used when not defined + error[non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method Found 2 diagnostics ----- stderr ----- @@ -1292,7 +1292,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-attribute: Type `` has no attribute `grantpt` + error: unresolved-attribute: Type `` has no attribute `grantpt` --> main.py:4:1 | 2 | import os @@ -1347,7 +1347,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1358,14 +1358,14 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- - "); + "); // Short flag assert_cmd_snapshot!(case.command().arg("-c").arg("terminal.error-on-warning=true"), @r" success: false exit_code: 1 ----- stdout ----- - error: lint:unresolved-reference: Name `x` used when not defined + error: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1376,7 +1376,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- - "); + "); Ok(()) } @@ -1397,7 +1397,7 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1420,7 +1420,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: lint:unresolved-reference: Name `x` used when not defined + warning: unresolved-reference: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index 33fb78a3df1087..e74237e7e0cbed 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -273,7 +273,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:2:19 | 2 | class Test: ... @@ -305,7 +305,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:2:17 | 2 | def foo(a, b): ... @@ -343,7 +343,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:3:17 | 3 | def foo(a, b): ... @@ -360,7 +360,7 @@ mod tests { | ^ | - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:5:17 | 3 | def foo(a, b): ... @@ -394,7 +394,7 @@ mod tests { test.write_file("lib.py", "a = 10").unwrap(); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> lib.py:1:1 | 1 | a = 10 @@ -422,7 +422,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -451,7 +451,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -479,7 +479,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:2:24 | 2 | type Alias[T: int = bool] = list[T] @@ -533,7 +533,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -568,7 +568,7 @@ mod tests { // the keyword is typed as a string. It's only the passed argument that // is an int. Navigating to `str` would match pyright's behavior. assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:231:7 | 229 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed @@ -602,7 +602,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r#" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:1086:7 | 1084 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -633,7 +633,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -667,7 +667,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:2:19 | 2 | class X: @@ -696,7 +696,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> main.py:2:17 | 2 | def foo(a, b): ... @@ -726,7 +726,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -757,7 +757,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/types.pyi:671:11 | 669 | if sys.version_info >= (3, 10): @@ -774,7 +774,7 @@ f(**kwargs) | ^ | - info: lint:goto-type-definition: Type definition + info: goto-type-definition: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 7ddd4212c4fa4a..2be8078f97fc5f 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -156,7 +156,7 @@ mod tests { Literal[10] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:9 | 2 | a = 10 @@ -192,7 +192,7 @@ mod tests { int ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:10:9 | 9 | foo = Foo() @@ -222,7 +222,7 @@ mod tests { def foo(a, b) -> Unknown ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:13 | 2 | def foo(a, b): ... @@ -251,7 +251,7 @@ mod tests { bool ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:3:17 | 2 | def foo(a: int, b: int, c: int): @@ -282,7 +282,7 @@ mod tests { Literal[123] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:18 | 2 | def test(a: int): ... @@ -320,7 +320,7 @@ mod tests { (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown) ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:12:13 | 10 | a = bar @@ -352,7 +352,7 @@ mod tests { ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:13 | 2 | import lib @@ -381,7 +381,7 @@ mod tests { T ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:2:46 | 2 | type Alias[T: int = bool] = list[T] @@ -407,7 +407,7 @@ mod tests { @Todo ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:2:53 | 2 | type Alias[**P = [int, str]] = Callable[P, int] @@ -433,7 +433,7 @@ mod tests { @Todo ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:2:43 | 2 | type Alias[*Ts = ()] = tuple[*Ts] @@ -459,7 +459,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:2:13 | 2 | value = 1 @@ -490,7 +490,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:3:13 | 2 | value = 1 @@ -520,7 +520,7 @@ mod tests { Literal[2] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:5:13 | 3 | attr: int = 1 @@ -553,7 +553,7 @@ mod tests { Unknown | Literal[1] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:5:13 | 3 | attr = 1 @@ -582,7 +582,7 @@ mod tests { int ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:3:13 | 2 | class Foo: @@ -610,7 +610,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:3:13 | 2 | class Foo: @@ -639,7 +639,7 @@ mod tests { int ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:17 | 2 | class Foo: @@ -669,7 +669,7 @@ mod tests { str ``` --------------------------------------------- - info: lint:hover: Hovered content is + info: hover: Hovered content is --> main.py:4:27 | 2 | def foo(a: str | None, b): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap index b53dcdd1999720..b9d100775dcb8c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method +error: invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method --> src/mdtest_snippet.py:11:1 | 10 | # TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap index a20903b9d97e7f..fdd3c845035b8f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap @@ -29,7 +29,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method +error: invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method --> src/mdtest_snippet.py:12:1 | 11 | # TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap index 5c751f5eed95b3..d694faad51badd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:6:1 | 4 | instance = C() @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:9:1 | 8 | C.attr = 1 # fine diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap index b89dec2de755fb..e0600e6c1d4392 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -warning: lint:possibly-unbound-attribute: Attribute `attr` on type `` is possibly unbound +warning: possibly-unbound-attribute: Attribute `attr` on type `` is possibly unbound --> src/mdtest_snippet.py:6:5 | 4 | attr: int = 0 @@ -41,7 +41,7 @@ info: `lint:possibly-unbound-attribute` is enabled by default ``` ``` -warning: lint:possibly-unbound-attribute: Attribute `attr` on type `C` is possibly unbound +warning: possibly-unbound-attribute: Attribute `attr` on type `C` is possibly unbound --> src/mdtest_snippet.py:9:5 | 8 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap index f298a50634440e..2fdbd0dbfe2e5a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:7:1 | 5 | instance = C() @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: lint:invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `` +error: invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `` --> src/mdtest_snippet.py:9:1 | 7 | instance.attr = "wrong" # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap index 12ce7acff5a5b8..c988a03105c3ca 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap @@ -37,7 +37,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type ` | ` +error: invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type ` | ` --> src/mdtest_snippet.py:11:5 | 10 | # TODO: The error message here could be improved to explain why the assignment fails. diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap index 2d718809bc7a15..64dfb57754f1d8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type ``. +error: unresolved-attribute: Unresolved attribute `non_existent` on type ``. --> src/mdtest_snippet.py:3:1 | 1 | class C: ... @@ -38,7 +38,7 @@ info: `lint:unresolved-attribute` is enabled by default ``` ``` -error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type `C`. +error: unresolved-attribute: Unresolved attribute `non_existent` on type `C`. --> src/mdtest_snippet.py:6:1 | 5 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap index 26c791b0683c6e..7abad11b9351f3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:7:1 | 6 | C.attr = 1 # fine @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: lint:invalid-attribute-access: Cannot assign to ClassVar `attr` from an instance of type `C` +error: invalid-attribute-access: Cannot assign to ClassVar `attr` from an instance of type `C` --> src/mdtest_snippet.py:10:1 | 9 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap index 55a9a4c30862c3..32a0be2dd8b82d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap @@ -19,7 +19,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` +error: unresolved-import: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:2:6 | 1 | # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap index 02b4faa4f98d18..cc29c6d9f9dca6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `zqzqzqzqzqzqzq` +error: unresolved-import: Cannot resolve imported module `zqzqzqzqzqzqzq` --> src/mdtest_snippet.py:1:8 | 1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve imported module `zqzqzqzqzqzqzq`" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap index 6aa391407a9138..bdef1302e81f17 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `a.foo` +error: unresolved-import: Cannot resolve imported module `a.foo` --> src/mdtest_snippet.py:2:8 | 1 | # Topmost component resolvable, submodule not resolvable: @@ -41,7 +41,7 @@ info: `lint:unresolved-import` is enabled by default ``` ``` -error: lint:unresolved-import: Cannot resolve imported module `b.foo` +error: unresolved-import: Cannot resolve imported module `b.foo` --> src/mdtest_snippet.py:5:8 | 4 | # Topmost component unresolvable: diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap index 65108494cc3a5b..a4d5c1325c9349 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable` is not iterable +error: not-iterable: Object of type `Iterable` is not iterable --> src/mdtest_snippet.py:10:10 | 9 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap index 7edce31fcb3b55..b60ad53e35a779 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Literal[123]` is not iterable +error: not-iterable: Object of type `Literal[123]` is not iterable --> src/mdtest_snippet.py:2:10 | 1 | nonsense = 123 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap index a3652652f452d8..ed96322d3e43d4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `NotIterable` is not iterable +error: not-iterable: Object of type `NotIterable` is not iterable --> src/mdtest_snippet.py:6:10 | 4 | __iter__: None = None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap index 342b8c8bc53c87..90cc4e0f5f9552 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Bad` is not iterable +error: not-iterable: Object of type `Bad` is not iterable --> src/mdtest_snippet.py:7:10 | 6 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap index 49cf9efece9586..6eae5a2a367fc2 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap @@ -46,7 +46,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` may not be iterable +error: not-iterable: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:22:14 | 21 | # error: [not-iterable] @@ -76,7 +76,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` may not be iterable +error: not-iterable: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:27:14 | 26 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap index 957b6387ce6498..e63d5db66ba5bd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap @@ -43,7 +43,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` may not be iterable +error: not-iterable: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:20:14 | 19 | # error: [not-iterable] @@ -73,7 +73,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` may not be iterable +error: not-iterable: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:25:14 | 24 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap index adc774f2500274..5d328986a61512 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap @@ -47,7 +47,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` may not be iterable +error: not-iterable: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:17:14 | 16 | # error: [not-iterable] @@ -77,7 +77,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` may not be iterable +error: not-iterable: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:28:14 | 27 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap index dd4825a5ee23fc..a0acdaaac55ef6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap @@ -51,7 +51,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` may not be iterable +error: not-iterable: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:28:14 | 27 | # error: [not-iterable] @@ -80,7 +80,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` may not be iterable +error: not-iterable: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:32:14 | 31 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap index f8816426e6f177..964da72329249f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap @@ -36,7 +36,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable` may not be iterable +error: not-iterable: Object of type `Iterable` may not be iterable --> src/mdtest_snippet.py:18:14 | 17 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 392888fb7a2f42..5eff683469a32d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -54,7 +54,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` may not be iterable +error: not-iterable: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:31:14 | 30 | # error: [not-iterable] @@ -83,7 +83,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` may not be iterable +error: not-iterable: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:36:14 | 35 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap index 7675649b846b2a..b6980e35397de7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable` may not be iterable +error: not-iterable: Object of type `Iterable` may not be iterable --> src/mdtest_snippet.py:17:14 | 16 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap index 8bc698888d6e8f..2534a4a67d446d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap @@ -36,7 +36,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Test | Test2` may not be iterable +error: not-iterable: Object of type `Test | Test2` may not be iterable --> src/mdtest_snippet.py:18:14 | 16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap index 2e2f459d4d4b3d..a8c8cd1e245a41 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Test | Literal[42]` may not be iterable +error: not-iterable: Object of type `Test | Literal[42]` may not be iterable --> src/mdtest_snippet.py:13:14 | 11 | def _(flag: bool): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap index 9a629a76070943..3dacb77373d44b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap @@ -33,7 +33,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `NotIterable` is not iterable +error: not-iterable: Object of type `NotIterable` is not iterable --> src/mdtest_snippet.py:11:14 | 10 | # error: [not-iterable] @@ -47,7 +47,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: lint:possibly-unresolved-reference: Name `x` used when possibly not defined +info: possibly-unresolved-reference: Name `x` used when possibly not defined --> src/mdtest_snippet.py:16:17 | 14 | # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap index 866e2c1fd0a051..8830b45e77eff0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Bad` is not iterable +error: not-iterable: Object of type `Bad` is not iterable --> src/mdtest_snippet.py:8:10 | 7 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap index a1348bde274ece..9ccb8f053d845c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable` is not iterable +error: not-iterable: Object of type `Iterable` is not iterable --> src/mdtest_snippet.py:12:10 | 11 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap index 89e7d451617a7b..363bb3b824fe34 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap @@ -41,7 +41,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Iterable1` is not iterable +error: not-iterable: Object of type `Iterable1` is not iterable --> src/mdtest_snippet.py:19:10 | 18 | # error: [not-iterable] @@ -70,7 +70,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:not-iterable: Object of type `Iterable2` is not iterable +error: not-iterable: Object of type `Iterable2` is not iterable --> src/mdtest_snippet.py:23:10 | 22 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap index 4a954abcc25b77..8a7dee8a821a2d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap @@ -68,7 +68,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:12:15 | 10 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap index 625de69be8177c..b5d7be74f0e3d8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap @@ -83,7 +83,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:13:15 | 11 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap index d6aa5e4c3eaad9..ab4ebc5baaf8d1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap @@ -65,7 +65,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:9:15 | 7 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap index ff37d59054c385..7e16af964c74f8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap @@ -80,7 +80,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:10:15 | 8 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap index 19fa88e14091a6..73bfe037a87e83 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/binary/instances.md # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:7:8 | 6 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap index dd591bfeaecc9c..0eaf0def78875d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:5 | 2 | return x * x diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap index 6afa697351d81a..85d86c07e616bf 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:6:10 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap index d50a63a7c3222e..c7000445964ba5 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:3:13 | 1 | import package diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap index 664549b08c735f..ff6d44b10c1c24 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:2:9 | 1 | def bar(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap index feeb29395e8ffe..6d5d68b1a93a4c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap index 64decc7001dd0d..65c52aa2ac5848 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:8:8 | 6 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap index 9bfbcdfa35ebb5..d1597ec1aeb986 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:7:5 | 5 | # error: [invalid-argument-type] @@ -44,7 +44,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:7:10 | 5 | # error: [invalid-argument-type] @@ -64,7 +64,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:7:15 | 5 | # error: [invalid-argument-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap index 33866a446ff2ea..4b9b03afb8b1ab 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:3:12 | 1 | import json diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap index 391d601299f848..38be05a4053c3a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap index b640b327f17099..0038ac8fe12744 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap index e2eb0006870245..d86620e46732cb 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap index 0882b9da34b061..2f6b19fcc42f64 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap index 7d171de4231ab5..aa338439676b14 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:6:3 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap index ac693e5fd8f5f4..e87dc36048037f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:14 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap index 0090d09ae32e8a..756e25e913c354 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error: invalid-argument-type: Argument to this function is incorrect --> src/mdtest_snippet.py:4:20 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap index d4fdcd30a8c26d..bcc2237e38fff9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/instances/mem # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:9:1 | 8 | # error: [unsupported-bool-conversion] @@ -43,7 +43,7 @@ info: `lint:unsupported-bool-conversion` is enabled by default ``` ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:11:1 | 9 | 10 in WithContains() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index 3a9c1f01280f1d..d6e3805b1b6f84 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -100,7 +100,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md # Diagnostics ``` -error: lint:duplicate-base: Duplicate base class `str` +error: duplicate-base: Duplicate base class `str` --> src/mdtest_snippet.py:3:7 | 1 | from typing_extensions import reveal_type @@ -141,7 +141,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:duplicate-base: Duplicate base class `Spam` +error: duplicate-base: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -179,7 +179,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -error: lint:duplicate-base: Duplicate base class `Eggs` +error: duplicate-base: Duplicate base class `Eggs` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -230,7 +230,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:duplicate-base: Duplicate base class `Mushrooms` +error: duplicate-base: Duplicate base class `Mushrooms` --> src/mdtest_snippet.py:30:7 | 29 | class Mushrooms: ... @@ -269,7 +269,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:duplicate-base: Duplicate base class `Eggs` +error: duplicate-base: Duplicate base class `Eggs` --> src/mdtest_snippet.py:37:7 | 36 | # error: [duplicate-base] "Duplicate base class `Eggs`" @@ -314,7 +314,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -error: lint:duplicate-base: Duplicate base class `A` +error: duplicate-base: Duplicate base class `A` --> src/mdtest_snippet.py:69:7 | 68 | # error: [duplicate-base] @@ -345,7 +345,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: lint:unused-ignore-comment +info: unused-ignore-comment --> src/mdtest_snippet.py:72:9 | 70 | A, @@ -358,7 +358,7 @@ info: lint:unused-ignore-comment ``` ``` -error: lint:duplicate-base: Duplicate base class `A` +error: duplicate-base: Duplicate base class `A` --> src/mdtest_snippet.py:76:7 | 75 | # error: [duplicate-base] @@ -388,7 +388,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: lint:unused-ignore-comment +info: unused-ignore-comment --> src/mdtest_snippet.py:81:13 | 79 | ): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap index 28145569b4e48c..03a8f755d63fe0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_ # Diagnostics ``` -error: lint:no-matching-overload: No overload of class `type` matches arguments +error: no-matching-overload: No overload of class `type` matches arguments --> src/mdtest_snippet.py:1:1 | 1 | type("Foo", ()) # error: [no-matching-overload] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap index cce0aae7380e97..d4035388ea1091 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/unary/not.md # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:5:1 | 4 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap index 83d8e5a05bca03..b949e23f46b90b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: lint:invalid-overload: Overloaded function `func` requires at least two overloads +error: invalid-overload: Overloaded function `func` requires at least two overloads --> src/mdtest_snippet.py:4:5 | 3 | @overload @@ -52,7 +52,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: Overloaded function `func` requires at least two overloads +error: invalid-overload: Overloaded function `func` requires at least two overloads --> src/mdtest_snippet.pyi:5:5 | 3 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap index 645793de8a420c..95f38287e10830 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap @@ -72,7 +72,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: lint:invalid-overload: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently +error: invalid-overload: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:13:9 | 11 | def try_from1(cls, x: int) -> CheckClassMethod: ... @@ -91,7 +91,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: Overloaded function `try_from2` does not use the `@classmethod` decorator consistently +error: invalid-overload: Overloaded function `try_from2` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:28:9 | 26 | @classmethod @@ -114,7 +114,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently +error: invalid-overload: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:40:9 | 38 | def try_from3(cls, x: str) -> None: ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap index 19a6d9d4b7fcfa..c439fdcc43ab7c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap @@ -65,7 +65,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: lint:invalid-overload: `@final` decorator should be applied only to the overload implementation +error: invalid-overload: `@final` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:18:9 | 16 | def method2(self, x: str) -> str: ... @@ -81,7 +81,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: `@final` decorator should be applied only to the overload implementation +error: invalid-overload: `@final` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:27:9 | 25 | def method3(self, x: str) -> str: ... @@ -97,7 +97,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: `@final` decorator should be applied only to the first overload +error: invalid-overload: `@final` decorator should be applied only to the first overload --> src/mdtest_snippet.pyi:11:9 | 10 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap index bdb11b71ca5df9..df066eb8f76fa3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap @@ -82,7 +82,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: lint:invalid-overload: `@override` decorator should be applied only to the overload implementation +error: invalid-overload: `@override` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:27:9 | 25 | def method(self, x: str) -> str: ... @@ -98,7 +98,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: `@override` decorator should be applied only to the overload implementation +error: invalid-overload: `@override` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:37:9 | 35 | def method(self, x: str) -> str: ... @@ -114,7 +114,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: `@override` decorator should be applied only to the first overload +error: invalid-overload: `@override` decorator should be applied only to the first overload --> src/mdtest_snippet.pyi:18:9 | 16 | class Sub2(Base): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap index 4bfdcf3cea2ea3..e77bb06e063dd8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: lint:invalid-overload: Overloaded non-stub function `func` must have an implementation +error: invalid-overload: Overloaded non-stub function `func` must have an implementation --> src/mdtest_snippet.py:7:5 | 5 | @overload @@ -46,7 +46,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: lint:invalid-overload: Overloaded non-stub function `method` must have an implementation +error: invalid-overload: Overloaded non-stub function `method` must have an implementation --> src/mdtest_snippet.py:14:9 | 12 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap index ea1b8bf2cf4a20..54d7a4d90a36b1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap @@ -42,7 +42,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: lint:call-non-callable: Object of type `typing.Protocol` is not callable +error: call-non-callable: Object of type `typing.Protocol` is not callable --> src/mdtest_snippet.py:4:13 | 3 | # error: [call-non-callable] @@ -69,7 +69,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:call-non-callable: Cannot instantiate class `MyProtocol` +error: call-non-callable: Cannot instantiate class `MyProtocol` --> src/mdtest_snippet.py:10:13 | 9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`" @@ -105,7 +105,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:call-non-callable: Cannot instantiate class `GenericProtocol` +error: call-non-callable: Cannot instantiate class `GenericProtocol` --> src/mdtest_snippet.py:16:13 | 15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap index b15caa71aa955e..e16368b52e39de 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap @@ -29,7 +29,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: lint:invalid-argument-type: Invalid argument to `get_protocol_members` +error: invalid-argument-type: Invalid argument to `get_protocol_members` --> src/mdtest_snippet.py:5:1 | 3 | class NotAProtocol: ... @@ -57,7 +57,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: lint:invalid-argument-type: Invalid argument to `get_protocol_members` +error: invalid-argument-type: Invalid argument to `get_protocol_members` --> src/mdtest_snippet.py:9:1 | 7 | class AlsoNotAProtocol(NotAProtocol, object): ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap index 49cd714631c98f..a94cc6a076108f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap @@ -57,7 +57,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: lint:invalid-argument-type: Class `HasX` cannot be used as the second argument to `isinstance` +error: invalid-argument-type: Class `HasX` cannot be used as the second argument to `isinstance` --> src/mdtest_snippet.py:7:8 | 6 | def f(arg: object, arg2: type): @@ -110,7 +110,7 @@ info: revealed-type: Revealed type ``` ``` -error: lint:invalid-argument-type: Class `HasX` cannot be used as the second argument to `issubclass` +error: invalid-argument-type: Class `HasX` cannot be used as the second argument to `issubclass` --> src/mdtest_snippet.py:12:8 | 10 | reveal_type(arg) # revealed: ~HasX diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap index 9df644ff80333b..20c898f52ac44d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap @@ -54,7 +54,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:19:12 | 17 | yield from i() @@ -71,7 +71,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:36:18 | 34 | yield 42 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap index ed3329dbee3c4c..50d62c9c7c558b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:1:22 | 1 | def f(cond: bool) -> str: @@ -50,7 +50,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:8:22 | 6 | return 1 @@ -69,7 +69,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:14:16 | 12 | else: diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap index 5261bc41772fc4..799092b5c4618b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap @@ -40,7 +40,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:1:12 | 1 | def f() -> None: @@ -57,7 +57,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:7:22 | 6 | # error: [invalid-return-type] @@ -71,7 +71,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:12:22 | 11 | # error: [invalid-return-type] @@ -85,7 +85,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:17:22 | 16 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index d97433cb54da13..9fda3e9907c4ff 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:2:12 | 1 | # error: [invalid-return-type] @@ -48,7 +48,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:5:12 | 3 | 1 @@ -66,7 +66,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.py:9:12 | 7 | return 1 @@ -84,7 +84,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `T` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `T` --> src/mdtest_snippet.py:18:16 | 17 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap index 28afc427859926..d63d98fe402631 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: lint:invalid-return-type: Return type does not match returned value +error: invalid-return-type: Return type does not match returned value --> src/mdtest_snippet.pyi:1:12 | 1 | def f() -> int: @@ -46,7 +46,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:6:14 | 5 | # error: [invalid-return-type] @@ -60,7 +60,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:11:14 | 10 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap index 9a125eddca26d7..0e95d28d6d4648 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap @@ -33,7 +33,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/instances/ric # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:12:1 | 11 | # error: [unsupported-bool-conversion] @@ -48,7 +48,7 @@ info: `lint:unsupported-bool-conversion` is enabled by default ``` ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:14:1 | 12 | 10 < Comparable() < 20 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap index 290d55f7d45019..6778fbe8c09542 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/shadowing.md # Diagnostics ``` -error: lint:invalid-assignment: Implicit shadowing of class `C` +error: invalid-assignment: Implicit shadowing of class `C` --> src/mdtest_snippet.py:3:1 | 1 | class C: ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap index e54313363dd238..6640dfc6b1de17 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/shadowing.md # Diagnostics ``` -error: lint:invalid-assignment: Implicit shadowing of function `f` +error: invalid-assignment: Implicit shadowing of function `f` --> src/mdtest_snippet.py:3:1 | 1 | def f(): ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap index 8224f85caee4ab..e7f48ee7ab9cc4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -34,7 +34,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]` --> src/mdtest_snippet.py:15:1 | 14 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap index 0e983586fd2a49..89e7ad321ce64b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:9:1 | 8 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap index 874dd383628d5b..60c391d10db33a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: lint:invalid-assignment: Not enough values to unpack +error: invalid-assignment: Not enough values to unpack --> src/mdtest_snippet.py:1:1 | 1 | a, b = (1,) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap index 9807ab3f975e22..52b7f514dc29fa 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: lint:invalid-assignment: Too many values to unpack +error: invalid-assignment: Too many values to unpack --> src/mdtest_snippet.py:1:1 | 1 | a, b = (1, 2, 3) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap index dd99ec9aaf6fc7..0a32434a26e463 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: lint:not-iterable: Object of type `Literal[1]` is not iterable +error: not-iterable: Object of type `Literal[1]` is not iterable --> src/mdtest_snippet.py:1:8 | 1 | a, b = 1 # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap index 449a6fe4728055..6b059c9cff7588 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: lint:invalid-assignment: Not enough values to unpack +error: invalid-assignment: Not enough values to unpack --> src/mdtest_snippet.py:1:1 | 1 | [a, *b, c, d] = (1, 2) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap index ea5db1d3b4c34a..93da9eb4a42849 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` +error: unresolved-import: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:1:8 | 1 | import does_not_exist # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap index 9ef99ec2ef8431..95f7ecb1255536 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Module `a` has no member `does_not_exist` +error: unresolved-import: Module `a` has no member `does_not_exist` --> src/mdtest_snippet.py:1:28 | 1 | from a import does_exist1, does_not_exist, does_exist2 # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap index 3f254e2e368379..bc879e8db538fa 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `.does_not_exist` +error: unresolved-import: Cannot resolve imported module `.does_not_exist` --> src/mdtest_snippet.py:1:7 | 1 | from .does_not_exist import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap index 86daf8882a7a87..6df92135db0b0d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `.does_not_exist.foo.bar` +error: unresolved-import: Cannot resolve imported module `.does_not_exist.foo.bar` --> src/mdtest_snippet.py:1:7 | 1 | from .does_not_exist.foo.bar import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap index 7f2aee8081bfa6..799b517444bab9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `does_not_exist` +error: unresolved-import: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:1:6 | 1 | from does_not_exist import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap index 5fd86887779191..fec8116bfc04ed 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: lint:unresolved-import: Cannot resolve imported module `....foo` +error: unresolved-import: Cannot resolve imported module `....foo` --> src/package/subpackage/subsubpackage/__init__.py:1:10 | 1 | from ....foo import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap index b3882d4cf25c8b..f1b0361ae2d106 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:7:8 | 6 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap index fe2b1a1687f37d..b1cf77250d391f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:8:8 | 7 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap index 53dc4e9e827107..1c00a6e8930bb0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:8:8 | 7 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap index 1be4494d3e6ea1..bcff80de7d7679 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for union `NotBoolable1 | NotBoolable2 | NotBoolable3` because `NotBoolable1` doesn't implement `__bool__` correctly +error: unsupported-bool-conversion: Boolean conversion is unsupported for union `NotBoolable1 | NotBoolable2 | NotBoolable3` because `NotBoolable1` doesn't implement `__bool__` correctly --> src/mdtest_snippet.py:15:8 | 14 | # error: [unsupported-bool-conversion] From 244ea27d5f2adb6ff45432ca4b57492369827b3b Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 11:29:20 -0400 Subject: [PATCH 003/487] ruff_db: a small tweak to remove empty message case In a subsequent commit, we're going to start using `annotate-snippets`'s functionality for diagnostic IDs in the rendering. As part of doing that, I wanted to remove this special casing of an empty message. I did that independently to see what, if anything, would change. (The changes look fine to me. They'll be tweaked again in the next commit along with a bunch of others.) --- crates/ruff_db/src/diagnostic/render.rs | 7 ++----- crates/ty/tests/cli.rs | 12 ++++++------ ...sts_-_`__bases__`_lists_with_duplicate_bases.snap | 4 ++-- ..._in_synchronous_comprehensions_-_Python_3.10.snap | 2 +- ...iagnostics_-_`match`_statement_-_Before_3.10.snap | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index ede5a28c4a7381..a6f9b6816e01b0 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -197,9 +197,7 @@ impl<'a> ResolvedDiagnostic<'a> { ResolvedAnnotation::new(path, &diagnostic_source, ann) }) .collect(); - let message = if diag.inner.message.as_str().is_empty() { - diag.inner.id.as_concise_str().to_string() - } else { + let message = // TODO: See the comment on `Renderable::id` for // a plausible better idea than smushing the ID // into the diagnostic message. @@ -207,8 +205,7 @@ impl<'a> ResolvedDiagnostic<'a> { "{id}: {message}", id = diag.inner.id.as_concise_str(), message = diag.inner.message.as_str(), - ) - }; + ); ResolvedDiagnostic { severity: diag.inner.severity, message, diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 7b84193af6364b..7f3c0e6f12e446 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -14,7 +14,7 @@ fn test_run_in_sub_directory() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax + error: invalid-syntax: --> /test.py:1:2 | 1 | ~ @@ -35,7 +35,7 @@ fn test_include_hidden_files_by_default() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax + error: invalid-syntax: --> .test.py:1:2 | 1 | ~ @@ -68,7 +68,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax + error: invalid-syntax: --> test.py:1:2 | 1 | ~ @@ -86,7 +86,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax + error: invalid-syntax: --> test.py:1:2 | 1 | ~ @@ -104,7 +104,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax + error: invalid-syntax: --> test.py:1:2 | 1 | ~ @@ -637,7 +637,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unknown-rule + warning: unknown-rule: --> pyproject.toml:3:1 | 2 | [tool.ty.rules] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index d6e3805b1b6f84..c7faf62230af6a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -345,7 +345,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: unused-ignore-comment +info: unused-ignore-comment: --> src/mdtest_snippet.py:72:9 | 70 | A, @@ -388,7 +388,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: unused-ignore-comment +info: unused-ignore-comment: --> src/mdtest_snippet.py:81:13 | 79 | ): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap index 44a166a3ed3019..6913734a306352 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syn # Diagnostics ``` -error: invalid-syntax +error: invalid-syntax: --> src/mdtest_snippet.py:6:19 | 4 | async def f(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap index ad47fde3f12ce0..85aa3b9927ba1a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/version_rela # Diagnostics ``` -error: invalid-syntax +error: invalid-syntax: --> src/mdtest_snippet.py:1:1 | 1 | match 2: # error: 1 [invalid-syntax] "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" From 50c780fc8b3cdd8c3caef6b994eed09f72fba758 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 11:37:26 -0400 Subject: [PATCH 004/487] ty: switch to use `annotate-snippets` ID functionality We just set the ID on the `Message` and it just does what we want in this case. I think I didn't do this originally because I was trying to preserve the existing rendering? I'm not sure. I might have just missed this method. --- crates/ruff_db/src/diagnostic/render.rs | 138 ++++++++---------- crates/ty/tests/cli.rs | 90 ++++++------ crates/ty_ide/src/goto.rs | 34 ++--- crates/ty_ide/src/hover.rs | 36 ++--- ..._-_Invalid_`__set__`_method_signature.snap | 2 +- ...a_descriptors_-_Invalid_argument_type.snap | 2 +- ..._attributes_with_class-level_defaults.snap | 4 +- ...ignment_-_Possibly-unbound_attributes.snap | 4 +- ...assignment_-_Pure_instance_attributes.snap | 4 +- ...t_-_Setting_attributes_on_union_types.snap | 2 +- ...ibute_assignment_-_Unknown_attributes.snap | 4 +- ..._-_Attribute_assignment_-_`ClassVar`s.snap | 4 +- ...ts_imported_from_an_unresolved_module.snap | 2 +- ...ructures_-_Unresolvable_module_import.snap | 2 +- ...ures_-_Unresolvable_submodule_imports.snap | 4 +- ..._For_loops_-_Bad_`__getitem__`_method.snap | 4 +- ...for.md_-_For_loops_-_Invalid_iterable.snap | 2 +- ...New_over_old_style_iteration_protocol.snap | 2 +- ...hod_and_`__getitem__`_is_not_callable.snap | 4 +- ...bly-not-callable_`__getitem__`_method.snap | 8 +- ...ossibly_invalid_`__getitem__`_methods.snap | 8 +- ...-_Possibly_invalid_`__iter__`_methods.snap | 8 +- ..._-_Possibly_invalid_`__next__`_method.snap | 8 +- ..._iter__`_and_bad_`__getitem__`_method.snap | 4 +- ..._`_and_possibly_invalid_`__getitem__`.snap | 8 +- ..._`_and_possibly_unbound_`__getitem__`.snap | 4 +- ...element_has_invalid_`__iter__`_method.snap | 4 +- ...nion_element_has_no_`__iter__`_method.snap | 4 +- ...or_loops_-_With_non-callable_iterator.snap | 6 +- ...__iter__`_does_not_return_an_iterator.snap | 4 +- ...__iter__`_method_with_a_bad_signature.snap | 4 +- ...tor_with_an_invalid_`__next__`_method.snap | 8 +- ...cy_syntax_-_Inferring_a_bound_typevar.snap | 8 +- ...tax_-_Inferring_a_constrained_typevar.snap | 10 +- ...95_syntax_-_Inferring_a_bound_typevar.snap | 8 +- ...tax_-_Inferring_a_constrained_typevar.snap | 10 +- ...types_with_invalid_`__bool__`_methods.snap | 2 +- ...lid_argument_type_diagnostics_-_Basic.snap | 2 +- ...t_type_diagnostics_-_Calls_to_methods.snap | 2 +- ...nt_type_diagnostics_-_Different_files.snap | 2 +- ..._diagnostics_-_Different_source_order.snap | 2 +- ...nt_type_diagnostics_-_Many_parameters.snap | 2 +- ...Many_parameters_across_multiple_lines.snap | 2 +- ...eters_with_multiple_invalid_arguments.snap | 6 +- ...hose_type_is_vendored_from_`typeshed`.snap | 2 +- ...gument_types_-_Keyword_only_arguments.snap | 2 +- ..._of_argument_types_-_Mix_of_arguments.snap | 2 +- ...argument_types_-_One_keyword_argument.snap | 2 +- ...y_of_argument_types_-_Only_positional.snap | 2 +- ..._argument_types_-_Synthetic_arguments.snap | 2 +- ...f_argument_types_-_Variadic_arguments.snap | 2 +- ...nt_types_-_Variadic_keyword_arguments.snap | 2 +- ...oesn't_implement_`__bool__`_correctly.snap | 4 +- ...__bases__`_lists_with_duplicate_bases.snap | 24 +-- ...stics_-_Calls_to_overloaded_functions.snap | 2 +- ...hat_implements_`__bool__`_incorrectly.snap | 2 +- ...ds_-_Invalid_-_At_least_two_overloads.snap | 4 +- ...onsistent_decorators_-_`@classmethod`.snap | 6 +- ..._-_Inconsistent_decorators_-_`@final`.snap | 6 +- ...Inconsistent_decorators_-_`@override`.snap | 6 +- ...t_an_implementation_-_Regular_modules.snap | 4 +- ...Protocols_-_Calls_to_protocol_classes.snap | 18 +-- ...lid_calls_to_`get_protocol_members()`.snap | 4 +- ..._-_Protocols_-_Narrowing_of_protocols.snap | 24 +-- ...ion_return_type_-_Generator_functions.snap | 4 +- ...ype_-_Invalid_conditional_return_type.snap | 6 +- ...n_type_-_Invalid_implicit_return_type.snap | 8 +- ...ion_return_type_-_Invalid_return_type.snap | 8 +- ...pe_-_Invalid_return_type_in_stub_file.snap | 6 +- ..._don't_implement_`__bool__`_correctly.snap | 4 +- ...chronous_comprehensions_-_Python_3.10.snap | 2 +- ..._Shadowing_-_Implicit_class_shadowing.snap | 2 +- ...adowing_-_Implicit_function_shadowing.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...ng_-_Exactly_too_few_values_to_unpack.snap | 2 +- ...g_-_Exactly_too_many_values_to_unpack.snap | 2 +- ...acking_-_Right_hand_side_not_iterable.snap | 2 +- ..._Unpacking_-_Too_few_values_to_unpack.snap | 2 +- ...vable_import_that_does_not_use_`from`.snap | 2 +- ...solvable_module_but_unresolvable_item.snap | 2 +- ...`from`_with_an_unknown_current_module.snap | 2 +- ..._`from`_with_an_unknown_nested_module.snap | 2 +- ...ng_`from`_with_an_unresolvable_module.snap | 2 +- ...ing_`from`_with_too_many_leading_dots.snap | 2 +- ...l__`_attribute,_but_it's_not_callable.snap | 2 +- ...hod,_but_has_an_incorrect_return_type.snap | 2 +- ..._method,_but_has_incorrect_parameters.snap | 2 +- ...ember_has_incorrect_`__bool__`_method.snap | 2 +- ...ics_-_`match`_statement_-_Before_3.10.snap | 2 +- 90 files changed, 332 insertions(+), 342 deletions(-) diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index a6f9b6816e01b0..a8ee6224912495 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -140,7 +140,6 @@ impl std::fmt::Display for DisplayDiagnostic<'_> { /// both.) #[derive(Debug)] struct Resolved<'a> { - id: String, diagnostics: Vec>, } @@ -152,14 +151,12 @@ impl<'a> Resolved<'a> { for sub in &diag.inner.subs { diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub)); } - let id = diag.inner.id.as_concise_str().to_string(); - Resolved { id, diagnostics } + Resolved { diagnostics } } /// Creates a value that is amenable to rendering directly. fn to_renderable(&self, context: usize) -> Renderable<'_> { Renderable { - id: &self.id, diagnostics: self .diagnostics .iter() @@ -177,6 +174,7 @@ impl<'a> Resolved<'a> { #[derive(Debug)] struct ResolvedDiagnostic<'a> { severity: Severity, + id: Option, message: String, annotations: Vec>, } @@ -197,17 +195,11 @@ impl<'a> ResolvedDiagnostic<'a> { ResolvedAnnotation::new(path, &diagnostic_source, ann) }) .collect(); - let message = - // TODO: See the comment on `Renderable::id` for - // a plausible better idea than smushing the ID - // into the diagnostic message. - format!( - "{id}: {message}", - id = diag.inner.id.as_concise_str(), - message = diag.inner.message.as_str(), - ); + let id = Some(diag.inner.id.as_concise_str().to_string()); + let message = diag.inner.message.as_str().to_string(); ResolvedDiagnostic { severity: diag.inner.severity, + id, message, annotations, } @@ -230,6 +222,7 @@ impl<'a> ResolvedDiagnostic<'a> { .collect(); ResolvedDiagnostic { severity: diag.inner.severity, + id: None, message: diag.inner.message.as_str().to_string(), annotations, } @@ -296,6 +289,7 @@ impl<'a> ResolvedDiagnostic<'a> { .sort_by(|snips1, snips2| snips1.has_primary.cmp(&snips2.has_primary).reverse()); RenderableDiagnostic { severity: self.severity, + id: self.id.as_deref(), message: &self.message, snippets_by_input, } @@ -375,20 +369,6 @@ impl<'a> ResolvedAnnotation<'a> { /// renderable value. This is usually the lifetime of `Resolved`. #[derive(Debug)] struct Renderable<'r> { - // TODO: This is currently unused in the rendering logic below. I'm not - // 100% sure yet where I want to put it, but I like what `rustc` does: - // - // error[E0599]: no method named `sub_builder` <..snip..> - // - // I believe in order to do this, we'll need to patch it in to - // `ruff_annotate_snippets` though. We leave it here for now with that plan - // in mind. - // - // (At time of writing, 2025-03-13, we currently render the diagnostic - // ID into the main message of the parent diagnostic. We don't use this - // specific field to do that though.) - #[expect(dead_code)] - id: &'r str, diagnostics: Vec>, } @@ -397,6 +377,12 @@ struct Renderable<'r> { struct RenderableDiagnostic<'r> { /// The severity of the diagnostic. severity: Severity, + /// The ID of the diagnostic. The ID can usually be used on the CLI or in a + /// config file to change the severity of a lint. + /// + /// An ID is always present for top-level diagnostics and always absent for + /// sub-diagnostics. + id: Option<&'r str>, /// The message emitted with the diagnostic, before any snippets are /// rendered. message: &'r str, @@ -417,7 +403,11 @@ impl RenderableDiagnostic<'_> { .iter() .map(|snippet| snippet.to_annotate(path)) }); - level.title(self.message).snippets(snippets) + let mut message = level.title(self.message); + if let Some(id) = self.id { + message = message.id(id); + } + message.snippets(snippets) } } @@ -796,7 +786,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -820,7 +810,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - warning: test-diagnostic: main diagnostic message + warning[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -840,7 +830,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - info: test-diagnostic: main diagnostic message + info[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -867,7 +857,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -886,7 +876,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -907,7 +897,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> non-ascii:5:1 | 3 | ΔΔΔΔΔΔΔΔΔΔΔΔ @@ -926,7 +916,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> non-ascii:2:2 | 1 | ☃☃☃☃☃☃☃☃☃☃☃☃ @@ -950,7 +940,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 4 | dog @@ -967,7 +957,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 5 | elephant @@ -982,7 +972,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -999,7 +989,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:11:1 | 9 | inchworm @@ -1016,7 +1006,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 1 | aardvark @@ -1049,7 +1039,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1093,7 +1083,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1118,7 +1108,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1146,7 +1136,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1174,7 +1164,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1199,7 +1189,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1230,7 +1220,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:1:1 | 1 | aardvark @@ -1268,7 +1258,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> spacey-animals:8:1 | 7 | dog @@ -1285,7 +1275,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> spacey-animals:12:1 | 11 | gorilla @@ -1303,7 +1293,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> spacey-animals:13:1 | 11 | gorilla @@ -1343,7 +1333,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> spacey-animals:3:1 | 3 | beetle @@ -1372,7 +1362,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1409,7 +1399,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1446,7 +1436,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1474,7 +1464,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1510,7 +1500,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1549,7 +1539,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1597,7 +1587,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:3:1 | 1 | aardvark @@ -1633,7 +1623,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1656,7 +1646,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1676,7 +1666,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1696,7 +1686,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:4 | 3 | canary @@ -1718,7 +1708,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:4 | 3 | canary @@ -1750,7 +1740,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:4:1 | 2 | beetle @@ -1779,7 +1769,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:4:1 | 2 | beetle @@ -1810,7 +1800,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1845,7 +1835,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1873,7 +1863,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 3 | canary @@ -1905,7 +1895,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:3 | 3 | canary @@ -1927,7 +1917,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:3 | 3 | canary @@ -1960,7 +1950,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:8:1 | 6 | finch @@ -2000,7 +1990,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:5:1 | 5 | elephant @@ -2044,7 +2034,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> fruits:1:1 | 1 | apple @@ -2079,7 +2069,7 @@ watermelon insta::assert_snapshot!( env.render(&diag), @r" - error: test-diagnostic: main diagnostic message + error[test-diagnostic]: main diagnostic message --> animals:11:1 | 11 | kangaroo diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 7f3c0e6f12e446..35bf0f4133b4b4 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -14,7 +14,7 @@ fn test_run_in_sub_directory() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax: + error[invalid-syntax] --> /test.py:1:2 | 1 | ~ @@ -35,7 +35,7 @@ fn test_include_hidden_files_by_default() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax: + error[invalid-syntax] --> .test.py:1:2 | 1 | ~ @@ -68,7 +68,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax: + error[invalid-syntax] --> test.py:1:2 | 1 | ~ @@ -86,7 +86,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax: + error[invalid-syntax] --> test.py:1:2 | 1 | ~ @@ -104,7 +104,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: invalid-syntax: + error[invalid-syntax] --> test.py:1:2 | 1 | ~ @@ -144,7 +144,7 @@ fn config_override_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-attribute: Type `` has no attribute `last_exc` + error[unresolved-attribute]: Type `` has no attribute `last_exc` --> test.py:5:7 | 4 | # Access `sys.last_exc` that was only added in Python 3.12 @@ -278,7 +278,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-import: Cannot resolve imported module `utils` + error[unresolved-import]: Cannot resolve imported module `utils` --> test.py:2:6 | 2 | from utils import add @@ -379,7 +379,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: division-by-zero: Cannot divide object of type `Literal[4]` by zero + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -389,7 +389,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: unresolved-reference: Name `prin` used when not defined + error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 | 5 | x = a @@ -417,7 +417,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -458,7 +458,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-import: Cannot resolve imported module `does_not_exit` + error[unresolved-import]: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit @@ -468,7 +468,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: division-by-zero: Cannot divide object of type `Literal[4]` by zero + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit @@ -480,7 +480,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: unresolved-reference: Name `prin` used when not defined + error[unresolved-reference]: Name `prin` used when not defined --> test.py:9:1 | 7 | x = a @@ -508,7 +508,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unresolved-import: Cannot resolve imported module `does_not_exit` + warning[unresolved-import]: Cannot resolve imported module `does_not_exit` --> test.py:2:8 | 2 | import does_not_exit @@ -518,7 +518,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { | info: `lint:unresolved-import` was selected on the command line - warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 | 2 | import does_not_exit @@ -561,7 +561,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: division-by-zero: Cannot divide object of type `Literal[4]` by zero + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -571,7 +571,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: unresolved-reference: Name `prin` used when not defined + error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 | 5 | x = a @@ -600,7 +600,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:2:5 | 2 | y = 4 / 0 @@ -637,7 +637,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unknown-rule: + warning[unknown-rule] --> pyproject.toml:3:1 | 2 | [tool.ty.rules] @@ -662,7 +662,7 @@ fn cli_unknown_rules() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unknown-rule: Unknown lint rule `division-by-zer` + warning[unknown-rule]: Unknown lint rule `division-by-zer` Found 1 diagnostic @@ -680,7 +680,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -764,7 +764,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -797,7 +797,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any success: false exit_code: 1 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -827,7 +827,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -836,7 +836,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { | info: `lint:unresolved-reference` was selected on the command line - error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -867,7 +867,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: success: false exit_code: 1 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -876,7 +876,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: | info: `lint:unresolved-reference` was selected on the command line - error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -907,7 +907,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:2:7 | 2 | print(x) # [unresolved-reference] @@ -916,7 +916,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { | info: `lint:unresolved-reference` was selected on the command line - error: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 | 2 | print(x) # [unresolved-reference] @@ -969,7 +969,7 @@ fn user_configuration() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 @@ -979,7 +979,7 @@ fn user_configuration() -> anyhow::Result<()> { | info: `lint:division-by-zero` was selected in the configuration file - error: unresolved-reference: Name `prin` used when not defined + error[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 | 5 | x = a @@ -1013,7 +1013,7 @@ fn user_configuration() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: division-by-zero: Cannot divide object of type `Literal[4]` by zero + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> main.py:2:5 | 2 | y = 4 / 0 @@ -1023,7 +1023,7 @@ fn user_configuration() -> anyhow::Result<()> { | info: `lint:division-by-zero` was selected in the configuration file - warning: unresolved-reference: Name `prin` used when not defined + warning[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 | 5 | x = a @@ -1073,7 +1073,7 @@ fn check_specific_paths() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: division-by-zero: Cannot divide object of type `Literal[4]` by zero + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> project/main.py:2:5 | 2 | y = 4 / 0 # error: division-by-zero @@ -1081,7 +1081,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:division-by-zero` is enabled by default - error: unresolved-import: Cannot resolve imported module `main2` + error[unresolved-import]: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import @@ -1091,7 +1091,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: unresolved-import: Cannot resolve imported module `does_not_exist` + error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import @@ -1113,7 +1113,7 @@ fn check_specific_paths() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-import: Cannot resolve imported module `main2` + error[unresolved-import]: Cannot resolve imported module `main2` --> project/other.py:2:6 | 2 | from main2 import z # error: unresolved-import @@ -1123,7 +1123,7 @@ fn check_specific_paths() -> anyhow::Result<()> { | info: `lint:unresolved-import` is enabled by default - error: unresolved-import: Cannot resolve imported module `does_not_exist` + error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 | 2 | import does_not_exist # error: unresolved-import @@ -1157,9 +1157,9 @@ fn check_non_existing_path() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: io: `/project/main.py`: No such file or directory (os error 2) + error[io]: `/project/main.py`: No such file or directory (os error 2) - error: io: `/project/tests`: No such file or directory (os error 2) + error[io]: `/project/tests`: No such file or directory (os error 2) Found 2 diagnostics @@ -1292,7 +1292,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-attribute: Type `` has no attribute `grantpt` + error[unresolved-attribute]: Type `` has no attribute `grantpt` --> main.py:4:1 | 2 | import os @@ -1347,7 +1347,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1365,7 +1365,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error: unresolved-reference: Name `x` used when not defined + error[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1397,7 +1397,7 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] @@ -1420,7 +1420,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning: unresolved-reference: Name `x` used when not defined + warning[unresolved-reference]: Name `x` used when not defined --> test.py:1:7 | 1 | print(x) # [unresolved-reference] diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index e74237e7e0cbed..862a371aa1a7c4 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -273,7 +273,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:2:19 | 2 | class Test: ... @@ -305,7 +305,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:2:17 | 2 | def foo(a, b): ... @@ -343,7 +343,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:3:17 | 3 | def foo(a, b): ... @@ -360,7 +360,7 @@ mod tests { | ^ | - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:5:17 | 3 | def foo(a, b): ... @@ -394,7 +394,7 @@ mod tests { test.write_file("lib.py", "a = 10").unwrap(); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> lib.py:1:1 | 1 | a = 10 @@ -422,7 +422,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -451,7 +451,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -479,7 +479,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:2:24 | 2 | type Alias[T: int = bool] = list[T] @@ -533,7 +533,7 @@ mod tests { ); assert_snapshot!(test.goto_type_definition(), @r#" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -568,7 +568,7 @@ mod tests { // the keyword is typed as a string. It's only the passed argument that // is an int. Navigating to `str` would match pyright's behavior. assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:231:7 | 229 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed @@ -602,7 +602,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r#" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:1086:7 | 1084 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -633,7 +633,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -667,7 +667,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:2:19 | 2 | class X: @@ -696,7 +696,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> main.py:2:17 | 2 | def foo(a, b): ... @@ -726,7 +726,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... @@ -757,7 +757,7 @@ f(**kwargs) ); assert_snapshot!(test.goto_type_definition(), @r" - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/types.pyi:671:11 | 669 | if sys.version_info >= (3, 10): @@ -774,7 +774,7 @@ f(**kwargs) | ^ | - info: goto-type-definition: Type definition + info[goto-type-definition]: Type definition --> stdlib/builtins.pyi:438:7 | 436 | def __getitem__(self, key: int, /) -> str | int | None: ... diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 2be8078f97fc5f..1a9a67da0d47a1 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -156,7 +156,7 @@ mod tests { Literal[10] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:9 | 2 | a = 10 @@ -192,7 +192,7 @@ mod tests { int ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:10:9 | 9 | foo = Foo() @@ -222,7 +222,7 @@ mod tests { def foo(a, b) -> Unknown ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:13 | 2 | def foo(a, b): ... @@ -251,7 +251,7 @@ mod tests { bool ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:3:17 | 2 | def foo(a: int, b: int, c: int): @@ -282,7 +282,7 @@ mod tests { Literal[123] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:18 | 2 | def test(a: int): ... @@ -320,7 +320,7 @@ mod tests { (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown) ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:12:13 | 10 | a = bar @@ -352,7 +352,7 @@ mod tests { ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:13 | 2 | import lib @@ -381,7 +381,7 @@ mod tests { T ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:2:46 | 2 | type Alias[T: int = bool] = list[T] @@ -407,7 +407,7 @@ mod tests { @Todo ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:2:53 | 2 | type Alias[**P = [int, str]] = Callable[P, int] @@ -433,7 +433,7 @@ mod tests { @Todo ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:2:43 | 2 | type Alias[*Ts = ()] = tuple[*Ts] @@ -459,7 +459,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:2:13 | 2 | value = 1 @@ -490,7 +490,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:3:13 | 2 | value = 1 @@ -520,7 +520,7 @@ mod tests { Literal[2] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:5:13 | 3 | attr: int = 1 @@ -553,7 +553,7 @@ mod tests { Unknown | Literal[1] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:5:13 | 3 | attr = 1 @@ -582,7 +582,7 @@ mod tests { int ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:3:13 | 2 | class Foo: @@ -610,7 +610,7 @@ mod tests { Literal[1] ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:3:13 | 2 | class Foo: @@ -639,7 +639,7 @@ mod tests { int ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:17 | 2 | class Foo: @@ -669,7 +669,7 @@ mod tests { str ``` --------------------------------------------- - info: hover: Hovered content is + info[hover]: Hovered content is --> main.py:4:27 | 2 | def foo(a: str | None, b): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap index b9d100775dcb8c..fbf8a4ac6ef524 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method +error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method --> src/mdtest_snippet.py:11:1 | 10 | # TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap index fdd3c845035b8f..5d9a63e85ef744 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap @@ -29,7 +29,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method +error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method --> src/mdtest_snippet.py:12:1 | 11 | # TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap index d694faad51badd..f9f08682f97c0f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:6:1 | 4 | instance = C() @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:9:1 | 8 | C.attr = 1 # fine diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap index e0600e6c1d4392..ba0abff7a8fa02 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -warning: possibly-unbound-attribute: Attribute `attr` on type `` is possibly unbound +warning[possibly-unbound-attribute]: Attribute `attr` on type `` is possibly unbound --> src/mdtest_snippet.py:6:5 | 4 | attr: int = 0 @@ -41,7 +41,7 @@ info: `lint:possibly-unbound-attribute` is enabled by default ``` ``` -warning: possibly-unbound-attribute: Attribute `attr` on type `C` is possibly unbound +warning[possibly-unbound-attribute]: Attribute `attr` on type `C` is possibly unbound --> src/mdtest_snippet.py:9:5 | 8 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap index 2fdbd0dbfe2e5a..b2163e4af432af 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:7:1 | 5 | instance = C() @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `` +error[invalid-attribute-access]: Cannot assign to instance attribute `attr` from the class object `` --> src/mdtest_snippet.py:9:1 | 7 | instance.attr = "wrong" # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap index c988a03105c3ca..934c8512b4935c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap @@ -37,7 +37,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type ` | ` +error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attribute `attr` on type ` | ` --> src/mdtest_snippet.py:11:5 | 10 | # TODO: The error message here could be improved to explain why the assignment fails. diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap index 64dfb57754f1d8..b97fe96103f263 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: unresolved-attribute: Unresolved attribute `non_existent` on type ``. +error[unresolved-attribute]: Unresolved attribute `non_existent` on type ``. --> src/mdtest_snippet.py:3:1 | 1 | class C: ... @@ -38,7 +38,7 @@ info: `lint:unresolved-attribute` is enabled by default ``` ``` -error: unresolved-attribute: Unresolved attribute `non_existent` on type `C`. +error[unresolved-attribute]: Unresolved attribute `non_existent` on type `C`. --> src/mdtest_snippet.py:6:1 | 5 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap index 7abad11b9351f3..190c43ec91bb2c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error: invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` +error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int` --> src/mdtest_snippet.py:7:1 | 6 | C.attr = 1 # fine @@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default ``` ``` -error: invalid-attribute-access: Cannot assign to ClassVar `attr` from an instance of type `C` +error[invalid-attribute-access]: Cannot assign to ClassVar `attr` from an instance of type `C` --> src/mdtest_snippet.py:10:1 | 9 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap index 32a0be2dd8b82d..8db24debd664cb 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap @@ -19,7 +19,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `does_not_exist` +error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:2:6 | 1 | # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap index cc29c6d9f9dca6..880e064002b05f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `zqzqzqzqzqzqzq` +error[unresolved-import]: Cannot resolve imported module `zqzqzqzqzqzqzq` --> src/mdtest_snippet.py:1:8 | 1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve imported module `zqzqzqzqzqzqzq`" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap index bdef1302e81f17..d58e781c8491d2 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `a.foo` +error[unresolved-import]: Cannot resolve imported module `a.foo` --> src/mdtest_snippet.py:2:8 | 1 | # Topmost component resolvable, submodule not resolvable: @@ -41,7 +41,7 @@ info: `lint:unresolved-import` is enabled by default ``` ``` -error: unresolved-import: Cannot resolve imported module `b.foo` +error[unresolved-import]: Cannot resolve imported module `b.foo` --> src/mdtest_snippet.py:5:8 | 4 | # Topmost component unresolvable: diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap index a4d5c1325c9349..8933b2414bfa96 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable` is not iterable +error[not-iterable]: Object of type `Iterable` is not iterable --> src/mdtest_snippet.py:10:10 | 9 | # error: [not-iterable] @@ -43,7 +43,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:11:17 | 9 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap index b60ad53e35a779..db63e9efeb4d80 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Literal[123]` is not iterable +error[not-iterable]: Object of type `Literal[123]` is not iterable --> src/mdtest_snippet.py:2:10 | 1 | nonsense = 123 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap index ed96322d3e43d4..5d12662834a386 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `NotIterable` is not iterable +error[not-iterable]: Object of type `NotIterable` is not iterable --> src/mdtest_snippet.py:6:10 | 4 | __iter__: None = None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap index 90cc4e0f5f9552..6161bc77a76308 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Bad` is not iterable +error[not-iterable]: Object of type `Bad` is not iterable --> src/mdtest_snippet.py:7:10 | 6 | # error: [not-iterable] @@ -39,7 +39,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:8:17 | 6 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap index 6eae5a2a367fc2..a7c5bed6ade643 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap @@ -46,7 +46,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` may not be iterable +error[not-iterable]: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:22:14 | 21 | # error: [not-iterable] @@ -62,7 +62,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:24:21 | 22 | for x in Iterable1(): @@ -76,7 +76,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` may not be iterable +error[not-iterable]: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:27:14 | 26 | # error: [not-iterable] @@ -92,7 +92,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:29:21 | 27 | for y in Iterable2(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap index e63d5db66ba5bd..10bc336b5d8334 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap @@ -43,7 +43,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` may not be iterable +error[not-iterable]: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:20:14 | 19 | # error: [not-iterable] @@ -59,7 +59,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:22:21 | 20 | for x in Iterable1(): @@ -73,7 +73,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` may not be iterable +error[not-iterable]: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:25:14 | 24 | # error: [not-iterable] @@ -88,7 +88,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:26:21 | 24 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap index 5d328986a61512..f322bbee35d7fd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap @@ -47,7 +47,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` may not be iterable +error[not-iterable]: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:17:14 | 16 | # error: [not-iterable] @@ -63,7 +63,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:18:21 | 16 | # error: [not-iterable] @@ -77,7 +77,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` may not be iterable +error[not-iterable]: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:28:14 | 27 | # error: [not-iterable] @@ -92,7 +92,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:30:21 | 28 | for x in Iterable2(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap index a0acdaaac55ef6..8c91453e985126 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap @@ -51,7 +51,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` may not be iterable +error[not-iterable]: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:28:14 | 27 | # error: [not-iterable] @@ -66,7 +66,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:29:21 | 27 | # error: [not-iterable] @@ -80,7 +80,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` may not be iterable +error[not-iterable]: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:32:14 | 31 | # error: [not-iterable] @@ -95,7 +95,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:34:21 | 32 | for y in Iterable2(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap index 964da72329249f..edde575d8a6db9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap @@ -36,7 +36,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable` may not be iterable +error[not-iterable]: Object of type `Iterable` may not be iterable --> src/mdtest_snippet.py:18:14 | 17 | # error: [not-iterable] @@ -51,7 +51,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:19:21 | 17 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 5eff683469a32d..3025233c52f0e5 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -54,7 +54,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` may not be iterable +error[not-iterable]: Object of type `Iterable1` may not be iterable --> src/mdtest_snippet.py:31:14 | 30 | # error: [not-iterable] @@ -69,7 +69,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:33:21 | 31 | for x in Iterable1(): @@ -83,7 +83,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` may not be iterable +error[not-iterable]: Object of type `Iterable2` may not be iterable --> src/mdtest_snippet.py:36:14 | 35 | # error: [not-iterable] @@ -98,7 +98,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:37:21 | 35 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap index b6980e35397de7..9a408bb99fe2ec 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable` may not be iterable +error[not-iterable]: Object of type `Iterable` may not be iterable --> src/mdtest_snippet.py:17:14 | 16 | # error: [not-iterable] @@ -49,7 +49,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:18:21 | 16 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap index 2534a4a67d446d..93a0c5d611c0d9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap @@ -36,7 +36,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Test | Test2` may not be iterable +error[not-iterable]: Object of type `Test | Test2` may not be iterable --> src/mdtest_snippet.py:18:14 | 16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989) @@ -51,7 +51,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:19:21 | 17 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap index a8c8cd1e245a41..2c259d34e8dee1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Test | Literal[42]` may not be iterable +error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable --> src/mdtest_snippet.py:13:14 | 11 | def _(flag: bool): @@ -46,7 +46,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:14:21 | 12 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap index 3dacb77373d44b..fb9f43501fb2cd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap @@ -33,7 +33,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `NotIterable` is not iterable +error[not-iterable]: Object of type `NotIterable` is not iterable --> src/mdtest_snippet.py:11:14 | 10 | # error: [not-iterable] @@ -47,7 +47,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: possibly-unresolved-reference: Name `x` used when possibly not defined +info[possibly-unresolved-reference]: Name `x` used when possibly not defined --> src/mdtest_snippet.py:16:17 | 14 | # revealed: Unknown @@ -60,7 +60,7 @@ info: `lint:possibly-unresolved-reference` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:16:17 | 14 | # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap index 8830b45e77eff0..848e1fa447f286 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Bad` is not iterable +error[not-iterable]: Object of type `Bad` is not iterable --> src/mdtest_snippet.py:8:10 | 7 | # error: [not-iterable] @@ -40,7 +40,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:9:17 | 7 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap index 9ccb8f053d845c..4ad3c5113a8013 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable` is not iterable +error[not-iterable]: Object of type `Iterable` is not iterable --> src/mdtest_snippet.py:12:10 | 11 | # error: [not-iterable] @@ -45,7 +45,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:13:17 | 11 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap index 363bb3b824fe34..a511f4a0919f78 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap @@ -41,7 +41,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md # Diagnostics ``` -error: not-iterable: Object of type `Iterable1` is not iterable +error[not-iterable]: Object of type `Iterable1` is not iterable --> src/mdtest_snippet.py:19:10 | 18 | # error: [not-iterable] @@ -56,7 +56,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:20:17 | 18 | # error: [not-iterable] @@ -70,7 +70,7 @@ info: revealed-type: Revealed type ``` ``` -error: not-iterable: Object of type `Iterable2` is not iterable +error[not-iterable]: Object of type `Iterable2` is not iterable --> src/mdtest_snippet.py:23:10 | 22 | # error: [not-iterable] @@ -84,7 +84,7 @@ info: `lint:not-iterable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:24:17 | 22 | # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap index 8a7dee8a821a2d..1ca5a30f87a244 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap @@ -29,7 +29,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function # Diagnostics ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:9:13 | 7 | return x @@ -43,7 +43,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:10:13 | 9 | reveal_type(f(1)) # revealed: Literal[1] @@ -56,7 +56,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:12:13 | 10 | reveal_type(f(True)) # revealed: Literal[True] @@ -68,7 +68,7 @@ info: revealed-type: Revealed type ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:12:15 | 10 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap index b5d7be74f0e3d8..2b1ab5c784b34d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function # Diagnostics ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:9:13 | 7 | return x @@ -44,7 +44,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:10:13 | 9 | reveal_type(f(1)) # revealed: int @@ -57,7 +57,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:11:13 | 9 | reveal_type(f(1)) # revealed: int @@ -71,7 +71,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:13:13 | 11 | reveal_type(f(None)) # revealed: None @@ -83,7 +83,7 @@ info: revealed-type: Revealed type ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:13:15 | 11 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap index ab4ebc5baaf8d1..c3a207cef059ea 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function # Diagnostics ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:6:13 | 4 | return x @@ -40,7 +40,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:7:13 | 6 | reveal_type(f(1)) # revealed: Literal[1] @@ -53,7 +53,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:9:13 | 7 | reveal_type(f(True)) # revealed: Literal[True] @@ -65,7 +65,7 @@ info: revealed-type: Revealed type ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:9:15 | 7 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap index 7e16af964c74f8..29ea8f144161bc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function # Diagnostics ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:6:13 | 4 | return x @@ -41,7 +41,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:7:13 | 6 | reveal_type(f(1)) # revealed: int @@ -54,7 +54,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:8:13 | 6 | reveal_type(f(1)) # revealed: int @@ -68,7 +68,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:10:13 | 8 | reveal_type(f(None)) # revealed: None @@ -80,7 +80,7 @@ info: revealed-type: Revealed type ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:10:15 | 8 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap index 73bfe037a87e83..876e326ed4213e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/binary/instances.md # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:7:8 | 6 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap index 0eaf0def78875d..fa66fc25e022d9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:5 | 2 | return x * x diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap index 85d86c07e616bf..8d65e97debce4a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:6:10 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap index c7000445964ba5..481a47a894371c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:3:13 | 1 | import package diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap index ff6d44b10c1c24..7f3ac4d5f16296 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:2:9 | 1 | def bar(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap index 6d5d68b1a93a4c..74d7871420f104 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap index 65c52aa2ac5848..4ea1464548d43d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:8:8 | 6 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap index d1597ec1aeb986..d229411472d663 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:7:5 | 5 | # error: [invalid-argument-type] @@ -44,7 +44,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:7:10 | 5 | # error: [invalid-argument-type] @@ -64,7 +64,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:7:15 | 5 | # error: [invalid-argument-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap index 4b9b03afb8b1ab..4ddd9738a33c32 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:3:12 | 1 | import json diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap index 38be05a4053c3a..e5c549d021cfd3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap index 0038ac8fe12744..69d59b109fc258 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap index d86620e46732cb..d779cf66bc1f5e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap index 2f6b19fcc42f64..31c1e2f08ddfdf 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap index aa338439676b14..f425e8365e1d96 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:6:3 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap index e87dc36048037f..45977a87fa6526 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:14 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap index 756e25e913c354..a3c636a55913c4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error: invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to this function is incorrect --> src/mdtest_snippet.py:4:20 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap index bcc2237e38fff9..1c7e3851938398 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/instances/mem # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:9:1 | 8 | # error: [unsupported-bool-conversion] @@ -43,7 +43,7 @@ info: `lint:unsupported-bool-conversion` is enabled by default ``` ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:11:1 | 9 | 10 in WithContains() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index c7faf62230af6a..4e35b7a2b1f152 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -100,7 +100,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md # Diagnostics ``` -error: duplicate-base: Duplicate base class `str` +error[duplicate-base]: Duplicate base class `str` --> src/mdtest_snippet.py:3:7 | 1 | from typing_extensions import reveal_type @@ -127,7 +127,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:5:13 | 3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`" @@ -141,7 +141,7 @@ info: revealed-type: Revealed type ``` ``` -error: duplicate-base: Duplicate base class `Spam` +error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -179,7 +179,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -error: duplicate-base: Duplicate base class `Eggs` +error[duplicate-base]: Duplicate base class `Eggs` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -216,7 +216,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:27:13 | 25 | # fmt: on @@ -230,7 +230,7 @@ info: revealed-type: Revealed type ``` ``` -error: duplicate-base: Duplicate base class `Mushrooms` +error[duplicate-base]: Duplicate base class `Mushrooms` --> src/mdtest_snippet.py:30:7 | 29 | class Mushrooms: ... @@ -255,7 +255,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:32:13 | 30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base] @@ -269,7 +269,7 @@ info: revealed-type: Revealed type ``` ``` -error: duplicate-base: Duplicate base class `Eggs` +error[duplicate-base]: Duplicate base class `Eggs` --> src/mdtest_snippet.py:37:7 | 36 | # error: [duplicate-base] "Duplicate base class `Eggs`" @@ -314,7 +314,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -error: duplicate-base: Duplicate base class `A` +error[duplicate-base]: Duplicate base class `A` --> src/mdtest_snippet.py:69:7 | 68 | # error: [duplicate-base] @@ -345,7 +345,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: unused-ignore-comment: +info[unused-ignore-comment] --> src/mdtest_snippet.py:72:9 | 70 | A, @@ -358,7 +358,7 @@ info: unused-ignore-comment: ``` ``` -error: duplicate-base: Duplicate base class `A` +error[duplicate-base]: Duplicate base class `A` --> src/mdtest_snippet.py:76:7 | 75 | # error: [duplicate-base] @@ -388,7 +388,7 @@ info: `lint:duplicate-base` is enabled by default ``` ``` -info: unused-ignore-comment: +info[unused-ignore-comment] --> src/mdtest_snippet.py:81:13 | 79 | ): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap index 03a8f755d63fe0..e31d4d3ba3ede9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_ # Diagnostics ``` -error: no-matching-overload: No overload of class `type` matches arguments +error[no-matching-overload]: No overload of class `type` matches arguments --> src/mdtest_snippet.py:1:1 | 1 | type("Foo", ()) # error: [no-matching-overload] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap index d4035388ea1091..1c4b27c09af9c8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/unary/not.md # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:5:1 | 4 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap index b949e23f46b90b..6e7add6b37df81 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: invalid-overload: Overloaded function `func` requires at least two overloads +error[invalid-overload]: Overloaded function `func` requires at least two overloads --> src/mdtest_snippet.py:4:5 | 3 | @overload @@ -52,7 +52,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: Overloaded function `func` requires at least two overloads +error[invalid-overload]: Overloaded function `func` requires at least two overloads --> src/mdtest_snippet.pyi:5:5 | 3 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap index 95f38287e10830..41d84d7ec2241d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap @@ -72,7 +72,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: invalid-overload: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently +error[invalid-overload]: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:13:9 | 11 | def try_from1(cls, x: int) -> CheckClassMethod: ... @@ -91,7 +91,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: Overloaded function `try_from2` does not use the `@classmethod` decorator consistently +error[invalid-overload]: Overloaded function `try_from2` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:28:9 | 26 | @classmethod @@ -114,7 +114,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently +error[invalid-overload]: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently --> src/mdtest_snippet.py:40:9 | 38 | def try_from3(cls, x: str) -> None: ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap index c439fdcc43ab7c..bf7aa13d44ea56 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap @@ -65,7 +65,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: invalid-overload: `@final` decorator should be applied only to the overload implementation +error[invalid-overload]: `@final` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:18:9 | 16 | def method2(self, x: str) -> str: ... @@ -81,7 +81,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: `@final` decorator should be applied only to the overload implementation +error[invalid-overload]: `@final` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:27:9 | 25 | def method3(self, x: str) -> str: ... @@ -97,7 +97,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: `@final` decorator should be applied only to the first overload +error[invalid-overload]: `@final` decorator should be applied only to the first overload --> src/mdtest_snippet.pyi:11:9 | 10 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap index df066eb8f76fa3..a55b66862f7b6b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap @@ -82,7 +82,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: invalid-overload: `@override` decorator should be applied only to the overload implementation +error[invalid-overload]: `@override` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:27:9 | 25 | def method(self, x: str) -> str: ... @@ -98,7 +98,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: `@override` decorator should be applied only to the overload implementation +error[invalid-overload]: `@override` decorator should be applied only to the overload implementation --> src/mdtest_snippet.py:37:9 | 35 | def method(self, x: str) -> str: ... @@ -114,7 +114,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: `@override` decorator should be applied only to the first overload +error[invalid-overload]: `@override` decorator should be applied only to the first overload --> src/mdtest_snippet.pyi:18:9 | 16 | class Sub2(Base): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap index e77bb06e063dd8..2fe8a019776add 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md # Diagnostics ``` -error: invalid-overload: Overloaded non-stub function `func` must have an implementation +error[invalid-overload]: Overloaded non-stub function `func` must have an implementation --> src/mdtest_snippet.py:7:5 | 5 | @overload @@ -46,7 +46,7 @@ info: `lint:invalid-overload` is enabled by default ``` ``` -error: invalid-overload: Overloaded non-stub function `method` must have an implementation +error[invalid-overload]: Overloaded non-stub function `method` must have an implementation --> src/mdtest_snippet.py:14:9 | 12 | @overload diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap index 54d7a4d90a36b1..de6b4fbcaac8ff 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap @@ -42,7 +42,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: call-non-callable: Object of type `typing.Protocol` is not callable +error[call-non-callable]: Object of type `typing.Protocol` is not callable --> src/mdtest_snippet.py:4:13 | 3 | # error: [call-non-callable] @@ -56,7 +56,7 @@ info: `lint:call-non-callable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:4:13 | 3 | # error: [call-non-callable] @@ -69,7 +69,7 @@ info: revealed-type: Revealed type ``` ``` -error: call-non-callable: Cannot instantiate class `MyProtocol` +error[call-non-callable]: Cannot instantiate class `MyProtocol` --> src/mdtest_snippet.py:10:13 | 9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`" @@ -92,7 +92,7 @@ info: `lint:call-non-callable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:10:13 | 9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`" @@ -105,7 +105,7 @@ info: revealed-type: Revealed type ``` ``` -error: call-non-callable: Cannot instantiate class `GenericProtocol` +error[call-non-callable]: Cannot instantiate class `GenericProtocol` --> src/mdtest_snippet.py:16:13 | 15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`" @@ -127,7 +127,7 @@ info: `lint:call-non-callable` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:16:13 | 15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`" @@ -139,7 +139,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:19:13 | 17 | class SubclassOfMyProtocol(MyProtocol): ... @@ -153,7 +153,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:23:13 | 21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ... @@ -167,7 +167,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:25:17 | 23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap index e16368b52e39de..6c4b4889343cce 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap @@ -29,7 +29,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: invalid-argument-type: Invalid argument to `get_protocol_members` +error[invalid-argument-type]: Invalid argument to `get_protocol_members` --> src/mdtest_snippet.py:5:1 | 3 | class NotAProtocol: ... @@ -57,7 +57,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -error: invalid-argument-type: Invalid argument to `get_protocol_members` +error[invalid-argument-type]: Invalid argument to `get_protocol_members` --> src/mdtest_snippet.py:9:1 | 7 | class AlsoNotAProtocol(NotAProtocol, object): ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap index a94cc6a076108f..dea0bb1a20a5db 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap @@ -57,7 +57,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md # Diagnostics ``` -error: invalid-argument-type: Class `HasX` cannot be used as the second argument to `isinstance` +error[invalid-argument-type]: Class `HasX` cannot be used as the second argument to `isinstance` --> src/mdtest_snippet.py:7:8 | 6 | def f(arg: object, arg2: type): @@ -82,7 +82,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:8:21 | 6 | def f(arg: object, arg2: type): @@ -96,7 +96,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:10:21 | 8 | reveal_type(arg) # revealed: HasX @@ -110,7 +110,7 @@ info: revealed-type: Revealed type ``` ``` -error: invalid-argument-type: Class `HasX` cannot be used as the second argument to `issubclass` +error[invalid-argument-type]: Class `HasX` cannot be used as the second argument to `issubclass` --> src/mdtest_snippet.py:12:8 | 10 | reveal_type(arg) # revealed: ~HasX @@ -136,7 +136,7 @@ info: `lint:invalid-argument-type` is enabled by default ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:13:21 | 12 | if issubclass(arg2, HasX): # error: [invalid-argument-type] @@ -149,7 +149,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:15:21 | 13 | reveal_type(arg2) # revealed: type[HasX] @@ -162,7 +162,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:24:21 | 22 | def f(arg: object): @@ -176,7 +176,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:26:21 | 24 | reveal_type(arg) # revealed: RuntimeCheckableHasX @@ -190,7 +190,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:33:21 | 31 | def f(arg1: type, arg2: type): @@ -204,7 +204,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:35:21 | 33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX] @@ -218,7 +218,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:38:21 | 37 | if issubclass(arg2, OnlyMethodMembers): # no error! @@ -231,7 +231,7 @@ info: revealed-type: Revealed type ``` ``` -info: revealed-type: Revealed type +info[revealed-type]: Revealed type --> src/mdtest_snippet.py:40:21 | 38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap index 20c898f52ac44d..f7ad20c7e74b2c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap @@ -54,7 +54,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:19:12 | 17 | yield from i() @@ -71,7 +71,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:36:18 | 34 | yield 42 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap index 50d62c9c7c558b..046c0d0e2a8c2a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:1:22 | 1 | def f(cond: bool) -> str: @@ -50,7 +50,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:8:22 | 6 | return 1 @@ -69,7 +69,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:14:16 | 12 | else: diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap index 799092b5c4618b..2bf6c6f7b3fc52 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap @@ -40,7 +40,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:1:12 | 1 | def f() -> None: @@ -57,7 +57,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:7:22 | 6 | # error: [invalid-return-type] @@ -71,7 +71,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:12:22 | 11 | # error: [invalid-return-type] @@ -85,7 +85,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:17:22 | 16 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index 9fda3e9907c4ff..2fb4ba732ad398 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:2:12 | 1 | # error: [invalid-return-type] @@ -48,7 +48,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:5:12 | 3 | 1 @@ -66,7 +66,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.py:9:12 | 7 | return 1 @@ -84,7 +84,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `T` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `T` --> src/mdtest_snippet.py:18:16 | 17 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap index d63d98fe402631..8a39adbbf6c3e1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error: invalid-return-type: Return type does not match returned value +error[invalid-return-type]: Return type does not match returned value --> src/mdtest_snippet.pyi:1:12 | 1 | def f() -> int: @@ -46,7 +46,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:6:14 | 5 | # error: [invalid-return-type] @@ -60,7 +60,7 @@ info: `lint:invalid-return-type` is enabled by default ``` ``` -error: invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:11:14 | 10 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap index 0e95d28d6d4648..8949a32efea50a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap @@ -33,7 +33,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/instances/ric # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:12:1 | 11 | # error: [unsupported-bool-conversion] @@ -48,7 +48,7 @@ info: `lint:unsupported-bool-conversion` is enabled by default ``` ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:14:1 | 12 | 10 < Comparable() < 20 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap index 6913734a306352..e1e2deae539efd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syn # Diagnostics ``` -error: invalid-syntax: +error[invalid-syntax] --> src/mdtest_snippet.py:6:19 | 4 | async def f(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap index 6778fbe8c09542..56fa5791cc658c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/shadowing.md # Diagnostics ``` -error: invalid-assignment: Implicit shadowing of class `C` +error[invalid-assignment]: Implicit shadowing of class `C` --> src/mdtest_snippet.py:3:1 | 1 | class C: ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap index 6640dfc6b1de17..d8db13c2632faa 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/shadowing.md # Diagnostics ``` -error: invalid-assignment: Implicit shadowing of function `f` +error[invalid-assignment]: Implicit shadowing of function `f` --> src/mdtest_snippet.py:3:1 | 1 | def f(): ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap index e7f48ee7ab9cc4..3adb239bfcf492 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -34,7 +34,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable | Literal[False]` --> src/mdtest_snippet.py:15:1 | 14 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap index 89e7ad321ce64b..505f027cc68606 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:9:1 | 8 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap index 60c391d10db33a..4047b1e07007cd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: invalid-assignment: Not enough values to unpack +error[invalid-assignment]: Not enough values to unpack --> src/mdtest_snippet.py:1:1 | 1 | a, b = (1,) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap index 52b7f514dc29fa..c52a7141519f7e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: invalid-assignment: Too many values to unpack +error[invalid-assignment]: Too many values to unpack --> src/mdtest_snippet.py:1:1 | 1 | a, b = (1, 2, 3) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap index 0a32434a26e463..113dcc24a17e2b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: not-iterable: Object of type `Literal[1]` is not iterable +error[not-iterable]: Object of type `Literal[1]` is not iterable --> src/mdtest_snippet.py:1:8 | 1 | a, b = 1 # error: [not-iterable] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap index 6b059c9cff7588..8527d6d1192729 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap @@ -18,7 +18,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unpacking.md # Diagnostics ``` -error: invalid-assignment: Not enough values to unpack +error[invalid-assignment]: Not enough values to unpack --> src/mdtest_snippet.py:1:1 | 1 | [a, *b, c, d] = (1, 2) # error: [invalid-assignment] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap index 93da9eb4a42849..03c93f0da75262 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `does_not_exist` +error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:1:8 | 1 | import does_not_exist # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap index 95f7ecb1255536..f347d9aaf7160a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Module `a` has no member `does_not_exist` +error[unresolved-import]: Module `a` has no member `does_not_exist` --> src/mdtest_snippet.py:1:28 | 1 | from a import does_exist1, does_not_exist, does_exist2 # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap index bc879e8db538fa..1ba4c0f24d6a34 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `.does_not_exist` +error[unresolved-import]: Cannot resolve imported module `.does_not_exist` --> src/mdtest_snippet.py:1:7 | 1 | from .does_not_exist import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap index 6df92135db0b0d..7e06ef0bbf3431 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `.does_not_exist.foo.bar` +error[unresolved-import]: Cannot resolve imported module `.does_not_exist.foo.bar` --> src/mdtest_snippet.py:1:7 | 1 | from .does_not_exist.foo.bar import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap index 799b517444bab9..1a2b51e10c7207 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `does_not_exist` +error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> src/mdtest_snippet.py:1:6 | 1 | from does_not_exist import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap index fec8116bfc04ed..8f7f635b190568 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_i # Diagnostics ``` -error: unresolved-import: Cannot resolve imported module `....foo` +error[unresolved-import]: Cannot resolve imported module `....foo` --> src/package/subpackage/subsubpackage/__init__.py:1:10 | 1 | from ....foo import add # error: [unresolved-import] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap index f1b0361ae2d106..1aaa70ea91e242 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:7:8 | 6 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap index b1cf77250d391f..6143f630fd7f91 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:8:8 | 7 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap index 1c00a6e8930bb0..e19cf348430ef8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable` +error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable` --> src/mdtest_snippet.py:8:8 | 7 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap index bcff80de7d7679..bf05df7ece8cd1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_ # Diagnostics ``` -error: unsupported-bool-conversion: Boolean conversion is unsupported for union `NotBoolable1 | NotBoolable2 | NotBoolable3` because `NotBoolable1` doesn't implement `__bool__` correctly +error[unsupported-bool-conversion]: Boolean conversion is unsupported for union `NotBoolable1 | NotBoolable2 | NotBoolable3` because `NotBoolable1` doesn't implement `__bool__` correctly --> src/mdtest_snippet.py:15:8 | 14 | # error: [unsupported-bool-conversion] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap index 85aa3b9927ba1a..2500cc4544dc3d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/version_rela # Diagnostics ``` -error: invalid-syntax: +error[invalid-syntax] --> src/mdtest_snippet.py:1:1 | 1 | match 2: # error: 1 [invalid-syntax] "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)" From b71ef8a26ec6f0d919512e2bd8e1319fb6c3b206 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 12:12:30 -0400 Subject: [PATCH 005/487] ruff_db: completely rip `lint:` prefix out This does a deeper removal of the `lint:` prefix by removing the `DiagnosticId::as_str` method and replacing it with `as_concise_str`. We remove the associated error type and simplify the `Display` impl for `DiagnosticId` as well. This turned out to catch a `lint:` that was still in the diagnostic output: the part that says why a lint is enabled. --- crates/ruff_db/src/diagnostic/mod.rs | 67 ++----------------- crates/ruff_db/src/diagnostic/render.rs | 4 +- ..._-_Invalid_`__set__`_method_signature.snap | 2 +- ...a_descriptors_-_Invalid_argument_type.snap | 2 +- ..._attributes_with_class-level_defaults.snap | 4 +- ...ignment_-_Possibly-unbound_attributes.snap | 4 +- ...assignment_-_Pure_instance_attributes.snap | 4 +- ...t_-_Setting_attributes_on_union_types.snap | 2 +- ...ibute_assignment_-_Unknown_attributes.snap | 4 +- ..._-_Attribute_assignment_-_`ClassVar`s.snap | 4 +- ...ts_imported_from_an_unresolved_module.snap | 2 +- ...ructures_-_Unresolvable_module_import.snap | 2 +- ...ures_-_Unresolvable_submodule_imports.snap | 4 +- ..._For_loops_-_Bad_`__getitem__`_method.snap | 2 +- ...for.md_-_For_loops_-_Invalid_iterable.snap | 2 +- ...New_over_old_style_iteration_protocol.snap | 2 +- ...hod_and_`__getitem__`_is_not_callable.snap | 2 +- ...bly-not-callable_`__getitem__`_method.snap | 4 +- ...ossibly_invalid_`__getitem__`_methods.snap | 4 +- ...-_Possibly_invalid_`__iter__`_methods.snap | 4 +- ..._-_Possibly_invalid_`__next__`_method.snap | 4 +- ..._iter__`_and_bad_`__getitem__`_method.snap | 2 +- ..._`_and_possibly_invalid_`__getitem__`.snap | 4 +- ..._`_and_possibly_unbound_`__getitem__`.snap | 2 +- ...element_has_invalid_`__iter__`_method.snap | 2 +- ...nion_element_has_no_`__iter__`_method.snap | 2 +- ...or_loops_-_With_non-callable_iterator.snap | 4 +- ...__iter__`_does_not_return_an_iterator.snap | 2 +- ...__iter__`_method_with_a_bad_signature.snap | 2 +- ...tor_with_an_invalid_`__next__`_method.snap | 4 +- ...cy_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...95_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...types_with_invalid_`__bool__`_methods.snap | 2 +- ...lid_argument_type_diagnostics_-_Basic.snap | 2 +- ...t_type_diagnostics_-_Calls_to_methods.snap | 2 +- ...nt_type_diagnostics_-_Different_files.snap | 2 +- ..._diagnostics_-_Different_source_order.snap | 2 +- ...nt_type_diagnostics_-_Many_parameters.snap | 2 +- ...Many_parameters_across_multiple_lines.snap | 2 +- ...eters_with_multiple_invalid_arguments.snap | 6 +- ...hose_type_is_vendored_from_`typeshed`.snap | 2 +- ...gument_types_-_Keyword_only_arguments.snap | 2 +- ..._of_argument_types_-_Mix_of_arguments.snap | 2 +- ...argument_types_-_One_keyword_argument.snap | 2 +- ...y_of_argument_types_-_Only_positional.snap | 2 +- ..._argument_types_-_Synthetic_arguments.snap | 2 +- ...f_argument_types_-_Variadic_arguments.snap | 2 +- ...nt_types_-_Variadic_keyword_arguments.snap | 2 +- ...oesn't_implement_`__bool__`_correctly.snap | 4 +- ...__bases__`_lists_with_duplicate_bases.snap | 14 ++-- ...stics_-_Calls_to_overloaded_functions.snap | 2 +- ...hat_implements_`__bool__`_incorrectly.snap | 2 +- ...ds_-_Invalid_-_At_least_two_overloads.snap | 4 +- ...onsistent_decorators_-_`@classmethod`.snap | 6 +- ..._-_Inconsistent_decorators_-_`@final`.snap | 6 +- ...Inconsistent_decorators_-_`@override`.snap | 6 +- ...t_an_implementation_-_Regular_modules.snap | 4 +- ...Protocols_-_Calls_to_protocol_classes.snap | 6 +- ...lid_calls_to_`get_protocol_members()`.snap | 4 +- ..._-_Protocols_-_Narrowing_of_protocols.snap | 4 +- ...ion_return_type_-_Generator_functions.snap | 4 +- ...ype_-_Invalid_conditional_return_type.snap | 6 +- ...n_type_-_Invalid_implicit_return_type.snap | 8 +-- ...ion_return_type_-_Invalid_return_type.snap | 8 +-- ...pe_-_Invalid_return_type_in_stub_file.snap | 6 +- ..._don't_implement_`__bool__`_correctly.snap | 4 +- ..._Shadowing_-_Implicit_class_shadowing.snap | 2 +- ...adowing_-_Implicit_function_shadowing.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...ng_-_Exactly_too_few_values_to_unpack.snap | 2 +- ...g_-_Exactly_too_many_values_to_unpack.snap | 2 +- ...acking_-_Right_hand_side_not_iterable.snap | 2 +- ..._Unpacking_-_Too_few_values_to_unpack.snap | 2 +- ...vable_import_that_does_not_use_`from`.snap | 2 +- ...solvable_module_but_unresolvable_item.snap | 2 +- ...`from`_with_an_unknown_current_module.snap | 2 +- ..._`from`_with_an_unknown_nested_module.snap | 2 +- ...ng_`from`_with_an_unresolvable_module.snap | 2 +- ...ing_`from`_with_too_many_leading_dots.snap | 2 +- ...l__`_attribute,_but_it's_not_callable.snap | 2 +- ...hod,_but_has_an_incorrect_return_type.snap | 2 +- ..._method,_but_has_incorrect_parameters.snap | 2 +- ...ember_has_incorrect_`__bool__`_method.snap | 2 +- crates/ty_test/src/matcher.rs | 21 +++--- crates/ty_wasm/tests/api.rs | 2 +- 88 files changed, 148 insertions(+), 206 deletions(-) diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index c04964e26d187d..ccb5e3d40c9455 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -2,7 +2,6 @@ use std::{fmt::Formatter, sync::Arc}; use render::{FileResolver, Input}; use ruff_source_file::{SourceCode, SourceFile}; -use thiserror::Error; use ruff_annotate_snippets::Level as AnnotateLevel; use ruff_text_size::{Ranged, TextRange}; @@ -613,53 +612,18 @@ impl DiagnosticId { code.split_once(':').map(|(_, rest)| rest) } - /// Returns `true` if this `DiagnosticId` matches the given name. + /// Returns a concise description of this diagnostic ID. /// - /// ## Examples - /// ``` - /// use ruff_db::diagnostic::DiagnosticId; - /// - /// assert!(DiagnosticId::Io.matches("io")); - /// assert!(DiagnosticId::lint("test").matches("lint:test")); - /// assert!(!DiagnosticId::lint("test").matches("test")); - /// ``` - pub fn matches(&self, expected_name: &str) -> bool { - match self.as_str() { - Ok(id) => id == expected_name, - Err(DiagnosticAsStrError::Category { category, name }) => expected_name - .strip_prefix(category) - .and_then(|prefix| prefix.strip_prefix(":")) - .is_some_and(|rest| rest == name), - } - } - - pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> { - Ok(match self { + /// Note that this doesn't include the lint's category. It + /// only includes the lint's name. + pub fn as_str(&self) -> &str { + match self { DiagnosticId::Panic => "panic", DiagnosticId::Io => "io", DiagnosticId::InvalidSyntax => "invalid-syntax", - DiagnosticId::Lint(name) => { - return Err(DiagnosticAsStrError::Category { - category: "lint", - name: name.as_str(), - }) - } + DiagnosticId::Lint(name) => name.as_str(), DiagnosticId::RevealedType => "revealed-type", DiagnosticId::UnknownRule => "unknown-rule", - }) - } - - /// Returns a "concise" description of this diagnostic ID. - /// - /// Specifically, this avoids adding a `lint:` prefix (or other - /// possible category prefixes, although `lint` is the only one - /// as of 2025-05-09) to the diagnostic ID when this is a lint - /// identifier. This is useful in diagnostic rendering where we - /// want to elide this prefix. - fn as_concise_str(&self) -> &str { - match self.as_str() { - Ok(name) => name, - Err(DiagnosticAsStrError::Category { name, .. }) => name, } } @@ -668,26 +632,9 @@ impl DiagnosticId { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] -pub enum DiagnosticAsStrError { - /// The id can't be converted to a string because it belongs to a sub-category. - #[error("id from a sub-category: {category}:{name}")] - Category { - /// The id's category. - category: &'static str, - /// The diagnostic id in this category. - name: &'static str, - }, -} - impl std::fmt::Display for DiagnosticId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.as_str() { - Ok(name) => f.write_str(name), - Err(DiagnosticAsStrError::Category { category, name }) => { - write!(f, "{category}:{name}") - } - } + write!(f, "{}", self.as_str()) } } diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index a8ee6224912495..4e95f87fbefd43 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -79,7 +79,7 @@ impl std::fmt::Display for DisplayDiagnostic<'_> { f, "{severity}[{id}]", severity = fmt_styled(severity, severity_style), - id = fmt_styled(self.diag.id().as_concise_str(), stylesheet.emphasis) + id = fmt_styled(self.diag.id(), stylesheet.emphasis) )?; if let Some(span) = self.diag.primary_span() { @@ -195,7 +195,7 @@ impl<'a> ResolvedDiagnostic<'a> { ResolvedAnnotation::new(path, &diagnostic_source, ann) }) .collect(); - let id = Some(diag.inner.id.as_concise_str().to_string()); + let id = Some(diag.inner.id.to_string()); let message = diag.inner.message.as_str().to_string(); ResolvedDiagnostic { severity: diag.inner.severity, diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap index fbf8a4ac6ef524..5f9493b6583144 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap @@ -35,6 +35,6 @@ error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr 11 | instance.attr = 1 # error: [invalid-assignment] | ^^^^^^^^^^^^^ | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap index 5d9a63e85ef744..1d390cd628969a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap @@ -36,6 +36,6 @@ error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr 12 | instance.attr = "wrong" # error: [invalid-assignment] | ^^^^^^^^^^^^^ | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap index f9f08682f97c0f..d01615b5f92c94 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap @@ -36,7 +36,7 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 7 | 8 | C.attr = 1 # fine | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` @@ -48,6 +48,6 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 9 | C.attr = "wrong" # error: [invalid-assignment] | ^^^^^^ | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap index ba0abff7a8fa02..8b89349a116a88 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap @@ -36,7 +36,7 @@ warning[possibly-unbound-attribute]: Attribute `attr` on type `` is p 7 | 8 | instance = C() | -info: `lint:possibly-unbound-attribute` is enabled by default +info: `possibly-unbound-attribute` is enabled by default ``` @@ -48,6 +48,6 @@ warning[possibly-unbound-attribute]: Attribute `attr` on type `C` is possibly un 9 | instance.attr = 1 # error: [possibly-unbound-attribute] | ^^^^^^^^^^^^^ | -info: `lint:possibly-unbound-attribute` is enabled by default +info: `possibly-unbound-attribute` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap index b2163e4af432af..74aa6f471b6160 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap @@ -36,7 +36,7 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 8 | 9 | C.attr = 1 # error: [invalid-attribute-access] | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` @@ -49,6 +49,6 @@ error[invalid-attribute-access]: Cannot assign to instance attribute `attr` from 9 | C.attr = 1 # error: [invalid-attribute-access] | ^^^^^^ | -info: `lint:invalid-attribute-access` is enabled by default +info: `invalid-attribute-access` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap index 934c8512b4935c..56edf828e9ece3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap @@ -46,6 +46,6 @@ error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attr 12 | 13 | class C2: | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap index b97fe96103f263..c17f11fdf57157 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap @@ -33,7 +33,7 @@ error[unresolved-attribute]: Unresolved attribute `non_existent` on type ` int) | None`, which is not callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap index 10bc336b5d8334..b8f211480c20a6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap @@ -54,7 +54,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable | info: It has no `__iter__` method and its `__getitem__` attribute is invalid info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -83,7 +83,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable | info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap index f322bbee35d7fd..fc7be2bc200550 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap @@ -58,7 +58,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable info: Its `__iter__` method may have an invalid signature info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)` info: Expected signature for `__iter__` is `def __iter__(self): ...` -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -87,7 +87,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable 30 | reveal_type(x) # revealed: int | Unknown | info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap index 8c91453e985126..32164a51f389f6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap @@ -61,7 +61,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable | info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method info: Expected signature for `__next__` is `def __next__(self): ...`) -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -90,7 +90,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable 34 | reveal_type(y) # revealed: int | Unknown | info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap index edde575d8a6db9..ca1328ccaa52af 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap @@ -46,7 +46,7 @@ error[not-iterable]: Object of type `Iterable` may not be iterable | info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 3025233c52f0e5..8cf36a396095e9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -64,7 +64,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable 33 | reveal_type(x) # revealed: bytes | str | Unknown | info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -93,7 +93,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable | info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap index 9a408bb99fe2ec..6441d320082c0c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap @@ -44,7 +44,7 @@ error[not-iterable]: Object of type `Iterable` may not be iterable 18 | reveal_type(x) # revealed: int | bytes | info: It may not have an `__iter__` method or a `__getitem__` method -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap index 93a0c5d611c0d9..69f9a2bad7215b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap @@ -46,7 +46,7 @@ error[not-iterable]: Object of type `Test | Test2` may not be iterable 19 | reveal_type(x) # revealed: int | info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap index 2c259d34e8dee1..1b92f8f9902e3f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap @@ -41,7 +41,7 @@ error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable 14 | reveal_type(x) # revealed: int | info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap index fb9f43501fb2cd..e7b32c95d66751 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap @@ -42,7 +42,7 @@ error[not-iterable]: Object of type `NotIterable` is not iterable 12 | pass | info: Its `__iter__` attribute has type `int | None`, which is not callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -55,7 +55,7 @@ info[possibly-unresolved-reference]: Name `x` used when possibly not defined 16 | reveal_type(x) | ^ | -info: `lint:possibly-unresolved-reference` is enabled by default +info: `possibly-unresolved-reference` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap index 848e1fa447f286..34820107ceecdd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap @@ -35,7 +35,7 @@ error[not-iterable]: Object of type `Bad` is not iterable 9 | reveal_type(x) # revealed: Unknown | info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap index 4ad3c5113a8013..2a515bad22c97a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap @@ -40,7 +40,7 @@ error[not-iterable]: Object of type `Iterable` is not iterable | info: Its `__iter__` method has an invalid signature info: Expected signature `def __iter__(self): ...` -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap index a511f4a0919f78..362ad58707288c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap @@ -51,7 +51,7 @@ error[not-iterable]: Object of type `Iterable1` is not iterable | info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method info: Expected signature for `__next__` is `def __next__(self): ...` -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` @@ -79,7 +79,7 @@ error[not-iterable]: Object of type `Iterable2` is not iterable 24 | reveal_type(y) # revealed: Unknown | info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap index 1ca5a30f87a244..cac47eee3e7539 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap @@ -86,6 +86,6 @@ info: Type variable defined here 5 | 6 | def f(x: T) -> T: | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap index 2b1ab5c784b34d..a934f8206d57a1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap @@ -101,6 +101,6 @@ info: Type variable defined here 5 | 6 | def f(x: T) -> T: | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap index c3a207cef059ea..2900a6671dc927 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap @@ -82,6 +82,6 @@ info: Type variable defined here | ^^^^^^ 4 | return x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap index 29ea8f144161bc..57eb90a95f7fb8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap @@ -97,6 +97,6 @@ info: Type variable defined here | ^^^^^^^^^^^^^^ 4 | return x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap index 876e326ed4213e..76cc458b19fe0c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap @@ -32,6 +32,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^ | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap index fa66fc25e022d9..b6d8d86d2574e3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap index 8d65e97debce4a..1be0e562f17746 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap @@ -38,6 +38,6 @@ info: Function defined here | ^^^^^^ ------ Parameter declared here 3 | return x * x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap index 481a47a894371c..9293cf184e5740 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -42,6 +42,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap index 7f3ac4d5f16296..29c466d3c31d06 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -40,6 +40,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 5 | return x * x | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap index 74d7871420f104..7de8796083c325 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap index 4ea1464548d43d..f558144b07b61a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -44,6 +44,6 @@ info: Function defined here 4 | z: int, 5 | ) -> int: | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap index d229411472d663..023fc78df1ec90 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -39,7 +39,7 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` @@ -59,7 +59,7 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` @@ -79,6 +79,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap index 4ddd9738a33c32..1e1bdd1f173f96 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -40,6 +40,6 @@ info: Function defined here 41 | *, 42 | cls: type[JSONDecoder] | None = None, | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap index e5c549d021cfd3..2b8fba2de42556 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap index 69d59b109fc258..de4b2e16c282a4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap index d779cf66bc1f5e..fb6d69d999c1cd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap index 31c1e2f08ddfdf..e222277fa9d6e9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap index f425e8365e1d96..e9d144d63014fa 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -38,6 +38,6 @@ info: Function defined here | ^^^^^^^^ ------ Parameter declared here 3 | return 1 | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap index 45977a87fa6526..7e18db62f5a54c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------------- Parameter declared here 2 | return len(numbers) | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap index a3c636a55913c4..2b36d6436368f7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ -------------- Parameter declared here 2 | return len(numbers) | -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap index 1c7e3851938398..3ab4b39aae970c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap @@ -38,7 +38,7 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 11 | 10 not in WithContains() | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` @@ -52,6 +52,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index 4e35b7a2b1f152..54f2deb8af6cac 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -122,7 +122,7 @@ info: The definition of class `Foo` will raise `TypeError` at runtime 4 | 5 | reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -174,7 +174,7 @@ info: The definition of class `Ham` will raise `TypeError` at runtime 22 | Eggs, 23 | ): ... | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -211,7 +211,7 @@ info: The definition of class `Ham` will raise `TypeError` at runtime | ^^^^ Class `Eggs` later repeated here 23 | ): ... | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -250,7 +250,7 @@ info: The definition of class `Omelette` will raise `TypeError` at runtime 31 | 32 | reveal_type(Omelette.__mro__) # revealed: tuple[, Unknown, ] | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -309,7 +309,7 @@ info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runti | ^^^^ Class `Eggs` later repeated here 47 | ): ... | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -340,7 +340,7 @@ info: The definition of class `D` will raise `TypeError` at runtime | ^ Class `A` later repeated here 73 | ): ... | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` @@ -383,7 +383,7 @@ info: The definition of class `E` will raise `TypeError` at runtime 79 | ): 80 | # error: [unused-ignore-comment] | -info: `lint:duplicate-base` is enabled by default +info: `duplicate-base` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap index e31d4d3ba3ede9..d438d5967fed8e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap @@ -24,6 +24,6 @@ error[no-matching-overload]: No overload of class `type` matches arguments 1 | type("Foo", ()) # error: [no-matching-overload] | ^^^^^^^^^^^^^^^ | -info: `lint:no-matching-overload` is enabled by default +info: `no-matching-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap index 1c4b27c09af9c8..547db0bcf087a6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap @@ -30,6 +30,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap index 6e7add6b37df81..32b3b92d714dc9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap @@ -47,7 +47,7 @@ error[invalid-overload]: Overloaded function `func` requires at least two overlo | ^^^^ 8 | return x | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -62,6 +62,6 @@ error[invalid-overload]: Overloaded function `func` requires at least two overlo | | | Only one overload defined here | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap index 41d84d7ec2241d..ab0e933c5817b4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap @@ -86,7 +86,7 @@ error[invalid-overload]: Overloaded function `try_from1` does not use the `@clas 17 | if isinstance(x, int): 18 | return cls(x) | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -109,7 +109,7 @@ error[invalid-overload]: Overloaded function `try_from2` does not use the `@clas 23 | @overload 24 | @classmethod | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -126,6 +126,6 @@ error[invalid-overload]: Overloaded function `try_from3` does not use the `@clas 41 | if isinstance(x, int): 42 | return cls(x) | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap index bf7aa13d44ea56..47a0c2744c0c75 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap @@ -76,7 +76,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo | Implementation defined here 19 | return x | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -92,7 +92,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo | Implementation defined here 28 | return x | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -109,6 +109,6 @@ error[invalid-overload]: `@final` decorator should be applied only to the first 15 | def method2(self, x: str) -> str: ... | ^^^^^^^ | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap index a55b66862f7b6b..2577e2ddf0375a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap @@ -93,7 +93,7 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove | Implementation defined here 28 | return x | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -109,7 +109,7 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove | Implementation defined here 38 | return x | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -127,6 +127,6 @@ error[invalid-overload]: `@override` decorator should be applied only to the fir 22 | def method(self, x: str) -> str: ... | ^^^^^^ | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap index 2fe8a019776add..9ced86c4f4d96e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap @@ -41,7 +41,7 @@ error[invalid-overload]: Overloaded non-stub function `func` must have an implem 8 | 9 | class Foo: | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` @@ -54,6 +54,6 @@ error[invalid-overload]: Overloaded non-stub function `method` must have an impl 14 | def method(self, x: str) -> str: ... | ^^^^^^ | -info: `lint:invalid-overload` is enabled by default +info: `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap index de6b4fbcaac8ff..8a65752dc49b44 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap @@ -51,7 +51,7 @@ error[call-non-callable]: Object of type `typing.Protocol` is not callable 5 | 6 | class MyProtocol(Protocol): | -info: `lint:call-non-callable` is enabled by default +info: `call-non-callable` is enabled by default ``` @@ -87,7 +87,7 @@ info: Protocol classes cannot be instantiated | ^^^^^^^^^^^^^^^^^^^^ `MyProtocol` declared as a protocol here 7 | x: int | -info: `lint:call-non-callable` is enabled by default +info: `call-non-callable` is enabled by default ``` @@ -122,7 +122,7 @@ info: Protocol classes cannot be instantiated | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol` declared as a protocol here 13 | x: T | -info: `lint:call-non-callable` is enabled by default +info: `call-non-callable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap index 6c4b4889343cce..c22ff204fc0b3c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap @@ -52,7 +52,7 @@ info: `NotAProtocol` is declared here, but it is not a protocol class: | info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol` info: See https://typing.python.org/en/latest/spec/protocol.html# -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` @@ -79,6 +79,6 @@ info: `AlsoNotAProtocol` is declared here, but it is not a protocol class: | info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol` info: See https://typing.python.org/en/latest/spec/protocol.html# -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap index dea0bb1a20a5db..dd7628a74d57c7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap @@ -77,7 +77,7 @@ info: `HasX` is declared as a protocol class, but it is not declared as runtime- | info: A protocol class can only be used in `isinstance` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable` info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` @@ -131,7 +131,7 @@ info: `HasX` is declared as a protocol class, but it is not declared as runtime- | info: A protocol class can only be used in `issubclass` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable` info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable -info: `lint:invalid-argument-type` is enabled by default +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap index f7ad20c7e74b2c..bcec9763b3c105 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap @@ -66,7 +66,7 @@ error[invalid-return-type]: Return type does not match returned value | info: Function is inferred as returning `types.GeneratorType` because it is a generator function info: See https://docs.python.org/3/glossary.html#term-generator for more details -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -82,6 +82,6 @@ error[invalid-return-type]: Return type does not match returned value | info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap index 046c0d0e2a8c2a..817169603d8704 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap @@ -45,7 +45,7 @@ error[invalid-return-type]: Return type does not match returned value 7 | 8 | def f(cond: bool) -> str: | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -64,7 +64,7 @@ error[invalid-return-type]: Return type does not match returned value 12 | else: 13 | # error: [invalid-return-type] | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -86,6 +86,6 @@ error[invalid-return-type]: Return type does not match returned value 9 | if cond: 10 | # error: [invalid-return-type] | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap index 2bf6c6f7b3fc52..3a97bc897bfdde 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap @@ -52,7 +52,7 @@ error[invalid-return-type]: Return type does not match returned value 5 | 6 | # error: [invalid-return-type] | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -66,7 +66,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 8 | if cond: 9 | return 1 | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -80,7 +80,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 13 | if cond: 14 | raise ValueError() | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -94,6 +94,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 18 | if cond: 19 | cond = False | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index 2fb4ba732ad398..ced45db21600e4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -43,7 +43,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not | ^^^ 3 | 1 | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -61,7 +61,7 @@ error[invalid-return-type]: Return type does not match returned value 8 | 9 | def f() -> int: | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -79,7 +79,7 @@ error[invalid-return-type]: Return type does not match returned value 12 | 13 | from typing import TypeVar | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -91,6 +91,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 18 | def m(x: T) -> T: ... | ^ | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap index 8a39adbbf6c3e1..f67b82d1b44728 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap @@ -41,7 +41,7 @@ error[invalid-return-type]: Return type does not match returned value 4 | 5 | # error: [invalid-return-type] | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -55,7 +55,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 7 | print("...") 8 | ... | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` @@ -69,6 +69,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 12 | f"""{foo} is a function that ...""" 13 | ... | -info: `lint:invalid-return-type` is enabled by default +info: `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap index 8949a32efea50a..2d0ac7ef1eaf69 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap @@ -43,7 +43,7 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 14 | 10 < Comparable() < Comparable() | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` @@ -59,6 +59,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 16 | Comparable() < Comparable() # fine | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap index 56fa5791cc658c..8dbd678be7accb 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap @@ -29,6 +29,6 @@ error[invalid-assignment]: Implicit shadowing of class `C` | ^ | info: Annotate to make it explicit if this is intentional -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap index d8db13c2632faa..ed53f7409aa735 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap @@ -29,6 +29,6 @@ error[invalid-assignment]: Implicit shadowing of function `f` | ^ | info: Annotate to make it explicit if this is intentional -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap index 3adb239bfcf492..ca136e3af056ad 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -44,6 +44,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 17 | a < b # fine | info: `__bool__` on `NotBoolable | Literal[False]` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap index 505f027cc68606..a51d35051472f6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -34,6 +34,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap index 4047b1e07007cd..f91f2bac36a982 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Not enough values to unpack | | | Expected 2 | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap index c52a7141519f7e..8ce8075bd8417a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Too many values to unpack | | | Expected 2 | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap index 113dcc24a17e2b..5b1ff5e7c4a932 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap @@ -25,6 +25,6 @@ error[not-iterable]: Object of type `Literal[1]` is not iterable | ^ | info: It doesn't have an `__iter__` method or a `__getitem__` method -info: `lint:not-iterable` is enabled by default +info: `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap index 8527d6d1192729..df56e0790960a0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Not enough values to unpack | | | Expected 3 or more | -info: `lint:invalid-assignment` is enabled by default +info: `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap index 03c93f0da75262..c466235b277697 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist` 2 | 3 | x = does_not_exist.foo | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap index f347d9aaf7160a..6ddf795bc3d2b7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap @@ -31,6 +31,6 @@ error[unresolved-import]: Module `a` has no member `does_not_exist` 1 | from a import does_exist1, does_not_exist, does_exist2 # error: [unresolved-import] | ^^^^^^^^^^^^^^ | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap index 1ba4c0f24d6a34..012ee03d727674 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist` 2 | 3 | stat = add(10, 15) | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap index 7e06ef0bbf3431..ce8f76e18b0e05 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist.foo.ba 2 | 3 | stat = add(10, 15) | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap index 1a2b51e10c7207..1295e1cd6172ad 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist` 2 | 3 | stat = add(10, 15) | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap index 8f7f635b190568..194e613487ce58 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap @@ -40,6 +40,6 @@ error[unresolved-import]: Cannot resolve imported module `....foo` 2 | 3 | stat = add(10, 15) | -info: `lint:unresolved-import` is enabled by default +info: `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap index 1aaa70ea91e242..133c9416f136c3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap @@ -32,6 +32,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^ | info: `__bool__` on `NotBoolable` must be callable -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap index 6143f630fd7f91..c32d97b04c346e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap @@ -42,6 +42,6 @@ info: `str` is not assignable to `bool` | Method defined here 3 | return "wat" | -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap index e19cf348430ef8..3c8f6dd9189765 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap @@ -42,6 +42,6 @@ info: `__bool__` methods must only have a `self` parameter | Method defined here 3 | return False | -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap index bf05df7ece8cd1..790f9a7bfe957f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap @@ -39,6 +39,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for union 15 | 10 and get() and True | ^^^^^ | -info: `lint:unsupported-bool-conversion` is enabled by default +info: `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_test/src/matcher.rs b/crates/ty_test/src/matcher.rs index 4beeb478e7857c..fa1f85320527b4 100644 --- a/crates/ty_test/src/matcher.rs +++ b/crates/ty_test/src/matcher.rs @@ -4,7 +4,7 @@ use crate::assertion::{InlineFileAssertions, ParsedAssertion, UnparsedAssertion} use crate::db::Db; use crate::diagnostic::SortedDiagnostics; use colored::Colorize; -use ruff_db::diagnostic::{Diagnostic, DiagnosticAsStrError, DiagnosticId}; +use ruff_db::diagnostic::{Diagnostic, DiagnosticId}; use ruff_db::files::File; use ruff_db::source::{line_index, source_text, SourceText}; use ruff_source_file::{LineIndex, OneIndexed}; @@ -168,29 +168,24 @@ fn maybe_add_undefined_reveal_clarification( impl Unmatched for &Diagnostic { fn unmatched(&self) -> String { - let id = self.id(); - let id = id.as_str().unwrap_or_else(|error| match error { - DiagnosticAsStrError::Category { name, .. } => name, - }); - maybe_add_undefined_reveal_clarification( self, - format_args!(r#"[{id}] "{message}""#, message = self.concise_message()), + format_args!( + r#"[{id}] "{message}""#, + id = self.id(), + message = self.concise_message() + ), ) } } impl UnmatchedWithColumn for &Diagnostic { fn unmatched_with_column(&self, column: OneIndexed) -> String { - let id = self.id(); - let id = id.as_str().unwrap_or_else(|error| match error { - DiagnosticAsStrError::Category { name, .. } => name, - }); - maybe_add_undefined_reveal_clarification( self, format_args!( r#"{column} [{id}] "{message}""#, + id = self.id(), message = self.concise_message() ), ) @@ -284,7 +279,7 @@ impl Matcher { ParsedAssertion::Error(error) => { let position = unmatched.iter().position(|diagnostic| { let lint_name_matches = !error.rule.is_some_and(|rule| { - !(diagnostic.id().is_lint_named(rule) || diagnostic.id().matches(rule)) + !(diagnostic.id().is_lint_named(rule) || diagnostic.id().as_str() == rule) }); let column_matches = error .column diff --git a/crates/ty_wasm/tests/api.rs b/crates/ty_wasm/tests/api.rs index 1d46fe8e385395..f98381f43b8fcb 100644 --- a/crates/ty_wasm/tests/api.rs +++ b/crates/ty_wasm/tests/api.rs @@ -22,7 +22,7 @@ fn check() { let diagnostic = &result[0]; - assert_eq!(diagnostic.id(), "lint:unresolved-import"); + assert_eq!(diagnostic.id(), "unresolved-import"); assert_eq!( diagnostic.to_range(&workspace).unwrap().start, Position { line: 1, column: 8 } From 861ef2504ee4946d4c94946b494f2af4673c5205 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 12:20:29 -0400 Subject: [PATCH 006/487] ty: add more snapshot updates --- crates/ty/tests/cli.rs | 82 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 35bf0f4133b4b4..3ea03222287373 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -151,7 +151,7 @@ fn config_override_python_version() -> anyhow::Result<()> { 5 | print(sys.last_exc) | ^^^^^^^^^^^^ | - info: `lint:unresolved-attribute` is enabled by default + info: `unresolved-attribute` is enabled by default Found 1 diagnostic @@ -196,7 +196,7 @@ fn config_override_python_platform() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - info: revealed-type: Revealed type + info[revealed-type]: Revealed type --> test.py:5:13 | 3 | from typing_extensions import reveal_type @@ -214,7 +214,7 @@ fn config_override_python_platform() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - info: revealed-type: Revealed type + info[revealed-type]: Revealed type --> test.py:5:13 | 3 | from typing_extensions import reveal_type @@ -286,7 +286,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { 3 | 4 | stat = add(10, 15) | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default Found 1 diagnostic @@ -387,7 +387,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` is enabled by default + info: `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 @@ -397,7 +397,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 7 | prin(x) # unresolved-reference | ^^^^ | - info: `lint:unresolved-reference` is enabled by default + info: `unresolved-reference` is enabled by default Found 2 diagnostics @@ -425,7 +425,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` was selected in the configuration file + info: `division-by-zero` was selected in the configuration file Found 1 diagnostic @@ -466,7 +466,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 @@ -478,7 +478,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 5 | 6 | for a in range(0, int(y)): | - info: `lint:division-by-zero` is enabled by default + info: `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:9:1 @@ -488,7 +488,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 9 | prin(x) # unresolved-reference | ^^^^ | - info: `lint:unresolved-reference` is enabled by default + info: `unresolved-reference` is enabled by default Found 3 diagnostics @@ -516,7 +516,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | - info: `lint:unresolved-import` was selected on the command line + info: `unresolved-import` was selected on the command line warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 @@ -528,7 +528,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 5 | 6 | for a in range(0, int(y)): | - info: `lint:division-by-zero` was selected on the command line + info: `division-by-zero` was selected on the command line Found 2 diagnostics @@ -569,7 +569,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` is enabled by default + info: `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 @@ -579,7 +579,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 7 | prin(x) # unresolved-reference | ^^^^ | - info: `lint:unresolved-reference` is enabled by default + info: `unresolved-reference` is enabled by default Found 2 diagnostics @@ -608,7 +608,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` was selected on the command line + info: `division-by-zero` was selected on the command line Found 1 diagnostic @@ -686,7 +686,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -710,7 +710,7 @@ fn exit_code_only_info() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - info: revealed-type: Revealed type + info[revealed-type]: Revealed type --> test.py:3:13 | 2 | from typing_extensions import reveal_type @@ -740,7 +740,7 @@ fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - info: revealed-type: Revealed type + info[revealed-type]: Revealed type --> test.py:3:13 | 2 | from typing_extensions import reveal_type @@ -770,7 +770,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -803,7 +803,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -834,7 +834,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -843,7 +843,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `lint:non-subscriptable` is enabled by default + info: `non-subscriptable` is enabled by default Found 2 diagnostics @@ -874,7 +874,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -883,7 +883,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `lint:non-subscriptable` is enabled by default + info: `non-subscriptable` is enabled by default Found 2 diagnostics @@ -914,7 +914,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -923,7 +923,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `lint:non-subscriptable` is enabled by default + info: `non-subscriptable` is enabled by default Found 2 diagnostics @@ -977,7 +977,7 @@ fn user_configuration() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` was selected in the configuration file + info: `division-by-zero` was selected in the configuration file error[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 @@ -987,7 +987,7 @@ fn user_configuration() -> anyhow::Result<()> { 7 | prin(x) | ^^^^ | - info: `lint:unresolved-reference` is enabled by default + info: `unresolved-reference` is enabled by default Found 2 diagnostics @@ -1021,7 +1021,7 @@ fn user_configuration() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `lint:division-by-zero` was selected in the configuration file + info: `division-by-zero` was selected in the configuration file warning[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 @@ -1031,7 +1031,7 @@ fn user_configuration() -> anyhow::Result<()> { 7 | prin(x) | ^^^^ | - info: `lint:unresolved-reference` was selected in the configuration file + info: `unresolved-reference` was selected in the configuration file Found 2 diagnostics @@ -1079,7 +1079,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | y = 4 / 0 # error: division-by-zero | ^^^^^ | - info: `lint:division-by-zero` is enabled by default + info: `division-by-zero` is enabled by default error[unresolved-import]: Cannot resolve imported module `main2` --> project/other.py:2:6 @@ -1089,7 +1089,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 @@ -1097,7 +1097,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default Found 3 diagnostics @@ -1121,7 +1121,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 @@ -1129,7 +1129,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | - info: `lint:unresolved-import` is enabled by default + info: `unresolved-import` is enabled by default Found 2 diagnostics @@ -1247,7 +1247,7 @@ fn can_handle_large_binop_expressions() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - info: revealed-type: Revealed type + info[revealed-type]: Revealed type --> test.py:4:13 | 2 | from typing_extensions import reveal_type @@ -1300,7 +1300,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { 4 | os.grantpt(1) # only available on unix, Python 3.13 or newer | ^^^^^^^^^^ | - info: `lint:unresolved-attribute` is enabled by default + info: `unresolved-attribute` is enabled by default Found 1 diagnostic @@ -1353,7 +1353,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -1371,7 +1371,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` is enabled by default + info: `unresolved-reference` is enabled by default Found 1 diagnostic @@ -1403,7 +1403,7 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -1426,7 +1426,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `lint:unresolved-reference` was selected on the command line + info: `unresolved-reference` was selected on the command line Found 1 diagnostic From 249a852a6e41e7c1fb508d7f1e0e36bc5d057ba6 Mon Sep 17 00:00:00 2001 From: InSync Date: Sat, 10 May 2025 00:06:56 +0700 Subject: [PATCH 007/487] [ty] Document nearly all lints (#17981) --- crates/ty/docs/rules.md | 363 ++++++++++++++---- .../src/types/diagnostic.rs | 265 +++++++++++-- ty.schema.json | 46 +-- 3 files changed, 558 insertions(+), 116 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index d5eab626116648..65376a9ffa87b7 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -61,7 +61,23 @@ Calling a non-callable object will raise a `TypeError` at runtime. detects when an argument is used as both a value and a type form in a call ### What it does -Checks whether an argument is used as both a value and a type form in a call +Checks whether an argument is used as both a value and a type form in a call. + +### Why is this bad? +Such calls have confusing semantics and often indicate a logic error. + +### Examples +```python +from typing import reveal_type +from ty_extensions import is_fully_static + +if flag: + f = repr # Expects a value +else: + f = is_fully_static # Expects a type form + +f(int) # error +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) @@ -83,9 +99,19 @@ A variable with two conflicting declarations likely indicates a mistake. Moreover, it could lead to incorrect or ill-defined type inference for other code that relies on these variables. +### Examples +```python +if b: + a: int +else: + a: str + +a = 1 +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L125) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141) ## `conflicting-metaclass` @@ -95,11 +121,28 @@ other code that relies on these variables.
detects conflicting metaclasses -TODO #14889 +### What it does +Checks for class definitions where the metaclass of the class +being created would not be a subclass of the metaclasses of +all the class's bases. + +### Why is it bad? +Such a class definition raises a `TypeError` at runtime. + +### Examples +```python +class M1(type): ... +class M2(type): ... +class A(metaclass=M1): ... +class B(metaclass=M2): ... + +## TypeError: metaclass conflict +class C(A, B): ... +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L140) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L166)
## `cyclic-class-definition` @@ -110,14 +153,27 @@ TODO #14889 detects cyclic class definitions ### What it does -Checks for class definitions with a cyclic inheritance chain. +Checks for class definitions in stub files that inherit +(directly or indirectly) from themselves. ### Why is it bad? -TODO #14889 +Although forward references are natively supported in stub files, +inheritance cycles are still disallowed, as it is impossible to +resolve a consistent [method resolution order] for a class that +inherits from itself. + +### Examples +```python +## foo.pyi +class A(B): ... +class B(A): ... +``` + +[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L149) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L192) ## `division-by-zero` @@ -140,7 +196,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L218) ## `duplicate-base` @@ -154,11 +210,19 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Checks for class definitions with duplicate bases. ### Why is this bad? -Class definitions with duplicate bases raise a `TypeError` at runtime. +Class definitions with duplicate bases raise `TypeError` at runtime. + +### Examples +```python +class A: ... + +## TypeError: duplicate base class +class B(A, A): ... +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L180) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236) ## `escape-character-in-forward-annotation` @@ -295,7 +359,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) ## `inconsistent-mro` @@ -306,14 +370,25 @@ TypeError: multiple bases have instance lay-out conflict detects class definitions with an inconsistent MRO ### What it does -Checks for classes with an inconsistent method resolution order (MRO). +Checks for classes with an inconsistent [method resolution order] (MRO). ### Why is this bad? Classes with an inconsistent MRO will raise a `TypeError` at runtime. +### Examples +```python +class A: ... +class B(A): ... + +## TypeError: Cannot create a consistent method resolution order +class C(A, B): ... +``` + +[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343) ## `index-out-of-bounds` @@ -330,9 +405,15 @@ a container. ### Why is this bad? Using an out of bounds index will raise an `IndexError` at runtime. +### Examples +```python +t = (0, 1, 2) +t[3] # IndexError: tuple index out of range +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L292) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L367) ## `invalid-argument-type` @@ -358,7 +439,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) ## `invalid-assignment` @@ -368,11 +449,24 @@ func("foo") # error: [invalid-argument-type]
detects invalid assignments -TODO #14889 +### What it does +Checks for assignments where the type of the value +is not [assignable to] the type of the assignee. + +### Why is this bad? +Such assignments break the rules of the type system and +weaken a type checker's ability to accurately reason about your code. + +### Examples +```python +a: int = '' +``` + +[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L346) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427)
## `invalid-attribute-access` @@ -383,20 +477,29 @@ TODO #14889 Invalid attribute access ### What it does -Makes sure that instance attribute accesses are valid. +Checks for assignments to class variables from instances +and assignments to instance variables from its class. + +### Why is this bad? +Incorrect assignments break the rules of the type system and +weaken a type checker's ability to accurately reason about your code. ### Examples ```python class C: - var: ClassVar[int] = 1 + class_var: ClassVar[int] = 1 + instance_var: int + +C.class_var = 3 # okay +C().class_var = 3 # error: Cannot assign to class variable -C.var = 3 # okay -C().var = 3 # error: Cannot assign to class variable +C().instance_var = 3 # okay +C.instance_var = 3 # error: Cannot assign to instance variable ``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1099) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) ## `invalid-base` @@ -404,13 +507,13 @@ C().var = 3 # error: Cannot assign to class variable **Default level**: error
-detects class definitions with an invalid base +detects invalid bases in class definitions TODO #14889 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L355) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449)
## `invalid-context-manager` @@ -420,11 +523,23 @@ TODO #14889
detects expressions used in with statements that don't implement the context manager protocol -TODO #14889 +### What it does +Checks for expressions used in `with` statements +that do not implement the context manager protocol. + +### Why is this bad? +Such a statement will raise `TypeError` at runtime. + +### Examples +```python +## TypeError: 'int' object does not support the context manager protocol +with 1: + print(2) +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L458)
## `invalid-declaration` @@ -434,11 +549,25 @@ TODO #14889
detects invalid declarations -TODO #14889 +### What it does +Checks for declarations where the inferred type of an existing symbol +is not [assignable to] its post-hoc declared type. + +### Why is this bad? +Such declarations break the rules of the type system and +weaken a type checker's ability to accurately reason about your code. + +### Examples +```python +a = 1 +a: str +``` + +[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L373) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L479)
## `invalid-exception-caught` @@ -479,7 +608,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L382) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L502) ## `invalid-generic-class` @@ -510,7 +639,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L418) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538) ## `invalid-legacy-type-variable` @@ -543,7 +672,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L444) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) ## `invalid-metaclass` @@ -575,7 +704,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592) ## `invalid-overload` @@ -623,7 +752,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L499) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L619) ## `invalid-parameter-default` @@ -634,14 +763,21 @@ def foo(x: int) -> int: ... detects default values that can't be assigned to the parameter's annotated type ### What it does -Checks for default values that can't be assigned to the parameter's annotated type. +Checks for default values that can't be +assigned to the parameter's annotated type. ### Why is this bad? -TODO #14889 +This breaks the rules of the type system and +weakens a type checker's ability to accurately reason about your code. + +### Examples +```python +def f(a: int = ''): ... +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L662) ## `invalid-protocol` @@ -674,7 +810,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L251) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L315) ## `invalid-raise` @@ -722,7 +858,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L555) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L682) ## `invalid-return-type` @@ -746,7 +882,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L327) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) ## `invalid-super-argument` @@ -790,7 +926,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L598) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L725) ## `invalid-syntax-in-forward-annotation` @@ -825,9 +961,15 @@ code seen only by the type checker, and not at runtime. Normally this flag is im must be assigned the value `False` at runtime; the type checker will consider its value to be `True`. If annotated, it must be annotated as a type that can accept `bool` values. +### Examples +```python +TYPE_CHECKING: str +TYPE_CHECKING = '' +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764) ## `invalid-type-form` @@ -838,14 +980,24 @@ be `True`. If annotated, it must be annotated as a type that can accept `bool` v detects invalid type forms ### What it does -Checks for invalid type expressions. +Checks for expressions that are used as type expressions +but cannot validly be interpreted as such. ### Why is this bad? -TODO #14889 +Such expressions cannot be understood by ty. +In some cases, they might raise errors at runtime. + +### Examples +```python +from typing import Annotated + +a: type[1] # `1` is not a type +b: Annotated[int] # `Annotated` expects at least two arguments +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788) ## `invalid-type-variable-constraints` @@ -855,11 +1007,31 @@ TODO #14889
detects invalid type variable constraints -TODO #14889 +### What it does +Checks for constrained [type variables] with only one constraint. + +### Why is this bad? +A constrained type variable must have at least two constraints. + +### Examples +```python +from typing import TypeVar + +T = TypeVar('T', str) # invalid constrained TypeVar +``` + +Use instead: +```python +T = TypeVar('T', str, int) # valid constrained TypeVar +## or +T = TypeVar('T', bound=str) # valid bound TypeVar +``` + +[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L668) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811)
## `missing-argument` @@ -883,7 +1055,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L840) ## `no-matching-overload` @@ -911,7 +1083,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L696) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L859) ## `non-subscriptable` @@ -934,7 +1106,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L719) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L882) ## `not-iterable` @@ -959,7 +1131,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L737) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L900) ## `parameter-already-assigned` @@ -985,7 +1157,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L951) ## `raw-string-type-annotation` @@ -1028,6 +1200,11 @@ def test(): -> "int": ### What it does Makes sure that the argument of `static_assert` is statically known to be true. +### Why is this bad? +A `static_assert` call represents an explicit request from the user +for the type checker to emit an error if the argument cannot be verified +to evaluate to `True` in a boolean context. + ### Examples ```python from ty_extensions import static_assert @@ -1039,7 +1216,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1287) ## `subclass-of-final-class` @@ -1067,7 +1244,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L858) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1042) ## `too-many-positional-arguments` @@ -1093,7 +1270,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087) ## `type-assertion-failure` @@ -1120,7 +1297,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L881) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065) ## `unavailable-implicit-super-arguments` @@ -1164,7 +1341,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1108) ## `unknown-argument` @@ -1190,7 +1367,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L979) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1165) ## `unresolved-attribute` @@ -1204,11 +1381,20 @@ f(x=1, y=2) # Error raised here Checks for unresolved attributes. ### Why is this bad? -Accessing an unbound attribute will raise an `AttributeError` at runtime. An unresolved attribute is not guaranteed to exist from the type alone, so this could also indicate that the object is not of the type that the user expects. +Accessing an unbound attribute will raise an `AttributeError` at runtime. +An unresolved attribute is not guaranteed to exist from the type alone, +so this could also indicate that the object is not of the type that the user expects. + +### Examples +```python +class A: ... + +A().foo # AttributeError: 'A' object has no attribute 'foo' +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1000) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1186) ## `unresolved-import` @@ -1222,12 +1408,17 @@ Accessing an unbound attribute will raise an `AttributeError` at runtime. An unr Checks for import statements for which the module cannot be resolved. ### Why is this bad? -Importing a module that cannot be resolved will raise an `ImportError` +Importing a module that cannot be resolved will raise a `ModuleNotFoundError` at runtime. +### Examples +```python +import foo # ModuleNotFoundError: No module named 'foo' +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1013) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208) ## `unresolved-reference` @@ -1251,7 +1442,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1027) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1227) ## `unsupported-bool-conversion` @@ -1287,7 +1478,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L757) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L920) ## `unsupported-operator` @@ -1305,9 +1496,16 @@ the operands don't support the operator. Attempting to use an unsupported operator will raise a `TypeError` at runtime. +### Examples +```python +class A: ... + +A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1046) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246) ## `zero-stepsize-in-slice` @@ -1331,7 +1529,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268) ## `call-possibly-unbound-method` @@ -1394,9 +1592,18 @@ Checks for possibly unbound attributes. ### Why is this bad? Attempting to access an unbound attribute will raise an `AttributeError` at runtime. +### Examples +```python +class A: + if b: + c = 0 + +A.c # AttributeError: type object 'A' has no attribute 'c' +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L972) ## `possibly-unbound-import` @@ -1413,9 +1620,21 @@ Checks for imports of symbols that may be unbound. Importing an unbound module or name will raise a `ModuleNotFoundError` or `ImportError` at runtime. +### Examples +```python +## module.py +import datetime + +if datetime.date.today().weekday() != 6: + a = 1 + +## main.py +from module import a # ImportError: cannot import name 'a' from 'module' +``` + ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L822) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L994) ## `redundant-cast` @@ -1441,7 +1660,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1118) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1339) ## `undefined-reveal` @@ -1458,11 +1677,13 @@ Checks for calls to `reveal_type` without importing it. Using `reveal_type` without importing it will raise a `NameError` at runtime. ### Examples -TODO #14889 +```python +reveal_type(1) # NameError: name 'reveal_type' is not defined +``` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L963) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1147) ## `unknown-rule` @@ -1519,7 +1740,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L836) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1020) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index d2d920a4e8226c..69549051f0fb69 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -114,7 +114,23 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks whether an argument is used as both a value and a type form in a call + /// Checks whether an argument is used as both a value and a type form in a call. + /// + /// ## Why is this bad? + /// Such calls have confusing semantics and often indicate a logic error. + /// + /// ## Examples + /// ```python + /// from typing import reveal_type + /// from ty_extensions import is_fully_static + /// + /// if flag: + /// f = repr # Expects a value + /// else: + /// f = is_fully_static # Expects a type form + /// + /// f(int) # error + /// ``` pub(crate) static CONFLICTING_ARGUMENT_FORMS = { summary: "detects when an argument is used as both a value and a type form in a call", status: LintStatus::preview("1.0.0"), @@ -130,6 +146,16 @@ declare_lint! { /// A variable with two conflicting declarations likely indicates a mistake. /// Moreover, it could lead to incorrect or ill-defined type inference for /// other code that relies on these variables. + /// + /// ## Examples + /// ```python + /// if b: + /// a: int + /// else: + /// a: str + /// + /// a = 1 + /// ``` pub(crate) static CONFLICTING_DECLARATIONS = { summary: "detects conflicting declarations", status: LintStatus::preview("1.0.0"), @@ -138,7 +164,24 @@ declare_lint! { } declare_lint! { - /// TODO #14889 + /// ## What it does + /// Checks for class definitions where the metaclass of the class + /// being created would not be a subclass of the metaclasses of + /// all the class's bases. + /// + /// ## Why is it bad? + /// Such a class definition raises a `TypeError` at runtime. + /// + /// ## Examples + /// ```python + /// class M1(type): ... + /// class M2(type): ... + /// class A(metaclass=M1): ... + /// class B(metaclass=M2): ... + /// + /// # TypeError: metaclass conflict + /// class C(A, B): ... + /// ``` pub(crate) static CONFLICTING_METACLASS = { summary: "detects conflicting metaclasses", status: LintStatus::preview("1.0.0"), @@ -148,10 +191,23 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for class definitions with a cyclic inheritance chain. + /// Checks for class definitions in stub files that inherit + /// (directly or indirectly) from themselves. /// /// ## Why is it bad? - /// TODO #14889 + /// Although forward references are natively supported in stub files, + /// inheritance cycles are still disallowed, as it is impossible to + /// resolve a consistent [method resolution order] for a class that + /// inherits from itself. + /// + /// ## Examples + /// ```python + /// # foo.pyi + /// class A(B): ... + /// class B(A): ... + /// ``` + /// + /// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order pub(crate) static CYCLIC_CLASS_DEFINITION = { summary: "detects cyclic class definitions", status: LintStatus::preview("1.0.0"), @@ -182,7 +238,15 @@ declare_lint! { /// Checks for class definitions with duplicate bases. /// /// ## Why is this bad? - /// Class definitions with duplicate bases raise a `TypeError` at runtime. + /// Class definitions with duplicate bases raise `TypeError` at runtime. + /// + /// ## Examples + /// ```python + /// class A: ... + /// + /// # TypeError: duplicate base class + /// class B(A, A): ... + /// ``` pub(crate) static DUPLICATE_BASE = { summary: "detects class definitions with duplicate bases", status: LintStatus::preview("1.0.0"), @@ -278,10 +342,21 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for classes with an inconsistent method resolution order (MRO). + /// Checks for classes with an inconsistent [method resolution order] (MRO). /// /// ## Why is this bad? /// Classes with an inconsistent MRO will raise a `TypeError` at runtime. + /// + /// ## Examples + /// ```python + /// class A: ... + /// class B(A): ... + /// + /// # TypeError: Cannot create a consistent method resolution order + /// class C(A, B): ... + /// ``` + /// + /// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order pub(crate) static INCONSISTENT_MRO = { summary: "detects class definitions with an inconsistent MRO", status: LintStatus::preview("1.0.0"), @@ -296,6 +371,12 @@ declare_lint! { /// /// ## Why is this bad? /// Using an out of bounds index will raise an `IndexError` at runtime. + /// + /// ## Examples + /// ```python + /// t = (0, 1, 2) + /// t[3] # IndexError: tuple index out of range + /// ``` pub(crate) static INDEX_OUT_OF_BOUNDS = { summary: "detects index out of bounds errors", status: LintStatus::preview("1.0.0"), @@ -344,7 +425,20 @@ declare_lint! { } declare_lint! { - /// TODO #14889 + /// ## What it does + /// Checks for assignments where the type of the value + /// is not [assignable to] the type of the assignee. + /// + /// ## Why is this bad? + /// Such assignments break the rules of the type system and + /// weaken a type checker's ability to accurately reason about your code. + /// + /// ## Examples + /// ```python + /// a: int = '' + /// ``` + /// + /// [assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable pub(crate) static INVALID_ASSIGNMENT = { summary: "detects invalid assignments", status: LintStatus::preview("1.0.0"), @@ -355,14 +449,26 @@ declare_lint! { declare_lint! { /// TODO #14889 pub(crate) static INVALID_BASE = { - summary: "detects class definitions with an invalid base", + summary: "detects invalid bases in class definitions", status: LintStatus::preview("1.0.0"), default_level: Level::Error, } } declare_lint! { - /// TODO #14889 + /// ## What it does + /// Checks for expressions used in `with` statements + /// that do not implement the context manager protocol. + /// + /// ## Why is this bad? + /// Such a statement will raise `TypeError` at runtime. + /// + /// ## Examples + /// ```python + /// # TypeError: 'int' object does not support the context manager protocol + /// with 1: + /// print(2) + /// ``` pub(crate) static INVALID_CONTEXT_MANAGER = { summary: "detects expressions used in with statements that don't implement the context manager protocol", status: LintStatus::preview("1.0.0"), @@ -371,7 +477,21 @@ declare_lint! { } declare_lint! { - /// TODO #14889 + /// ## What it does + /// Checks for declarations where the inferred type of an existing symbol + /// is not [assignable to] its post-hoc declared type. + /// + /// ## Why is this bad? + /// Such declarations break the rules of the type system and + /// weaken a type checker's ability to accurately reason about your code. + /// + /// ## Examples + /// ```python + /// a = 1 + /// a: str + /// ``` + /// + /// [assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable pub(crate) static INVALID_DECLARATION = { summary: "detects invalid declarations", status: LintStatus::preview("1.0.0"), @@ -541,10 +661,17 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for default values that can't be assigned to the parameter's annotated type. + /// Checks for default values that can't be + /// assigned to the parameter's annotated type. /// /// ## Why is this bad? - /// TODO #14889 + /// This breaks the rules of the type system and + /// weakens a type checker's ability to accurately reason about your code. + /// + /// ## Examples + /// ```python + /// def f(a: int = ''): ... + /// ``` pub(crate) static INVALID_PARAMETER_DEFAULT = { summary: "detects default values that can't be assigned to the parameter's annotated type", status: LintStatus::preview("1.0.0"), @@ -645,6 +772,12 @@ declare_lint! { /// `typing` or `typing_extensions`, but it can also be defined locally. If defined locally, it /// must be assigned the value `False` at runtime; the type checker will consider its value to /// be `True`. If annotated, it must be annotated as a type that can accept `bool` values. + /// + /// ## Examples + /// ```python + /// TYPE_CHECKING: str + /// TYPE_CHECKING = '' + /// ``` pub(crate) static INVALID_TYPE_CHECKING_CONSTANT = { summary: "detects invalid `TYPE_CHECKING` constant assignments", status: LintStatus::preview("1.0.0"), @@ -654,10 +787,20 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for invalid type expressions. + /// Checks for expressions that are used as type expressions + /// but cannot validly be interpreted as such. /// /// ## Why is this bad? - /// TODO #14889 + /// Such expressions cannot be understood by ty. + /// In some cases, they might raise errors at runtime. + /// + /// ## Examples + /// ```python + /// from typing import Annotated + /// + /// a: type[1] # `1` is not a type + /// b: Annotated[int] # `Annotated` expects at least two arguments + /// ``` pub(crate) static INVALID_TYPE_FORM = { summary: "detects invalid type forms", status: LintStatus::preview("1.0.0"), @@ -666,7 +809,27 @@ declare_lint! { } declare_lint! { - /// TODO #14889 + /// ## What it does + /// Checks for constrained [type variables] with only one constraint. + /// + /// ## Why is this bad? + /// A constrained type variable must have at least two constraints. + /// + /// ## Examples + /// ```python + /// from typing import TypeVar + /// + /// T = TypeVar('T', str) # invalid constrained TypeVar + /// ``` + /// + /// Use instead: + /// ```python + /// T = TypeVar('T', str, int) # valid constrained TypeVar + /// # or + /// T = TypeVar('T', bound=str) # valid bound TypeVar + /// ``` + /// + /// [type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar pub(crate) static INVALID_TYPE_VARIABLE_CONSTRAINTS = { summary: "detects invalid type variable constraints", status: LintStatus::preview("1.0.0"), @@ -812,6 +975,15 @@ declare_lint! { /// /// ## Why is this bad? /// Attempting to access an unbound attribute will raise an `AttributeError` at runtime. + /// + /// ## Examples + /// ```python + /// class A: + /// if b: + /// c = 0 + /// + /// A.c # AttributeError: type object 'A' has no attribute 'c' + /// ``` pub(crate) static POSSIBLY_UNBOUND_ATTRIBUTE = { summary: "detects references to possibly unbound attributes", status: LintStatus::preview("1.0.0"), @@ -826,6 +998,18 @@ declare_lint! { /// ## Why is this bad? /// Importing an unbound module or name will raise a `ModuleNotFoundError` /// or `ImportError` at runtime. + /// + /// ## Examples + /// ```python + /// # module.py + /// import datetime + /// + /// if datetime.date.today().weekday() != 6: + /// a = 1 + /// + /// # main.py + /// from module import a # ImportError: cannot import name 'a' from 'module' + /// ``` pub(crate) static POSSIBLY_UNBOUND_IMPORT = { summary: "detects possibly unbound imports", status: LintStatus::preview("1.0.0"), @@ -968,7 +1152,9 @@ declare_lint! { /// Using `reveal_type` without importing it will raise a `NameError` at runtime. /// /// ## Examples - /// TODO #14889 + /// ```python + /// reveal_type(1) # NameError: name 'reveal_type' is not defined + /// ``` pub(crate) static UNDEFINED_REVEAL = { summary: "detects usages of `reveal_type` without importing it", status: LintStatus::preview("1.0.0"), @@ -1002,7 +1188,16 @@ declare_lint! { /// Checks for unresolved attributes. /// /// ## Why is this bad? - /// Accessing an unbound attribute will raise an `AttributeError` at runtime. An unresolved attribute is not guaranteed to exist from the type alone, so this could also indicate that the object is not of the type that the user expects. + /// Accessing an unbound attribute will raise an `AttributeError` at runtime. + /// An unresolved attribute is not guaranteed to exist from the type alone, + /// so this could also indicate that the object is not of the type that the user expects. + /// + /// ## Examples + /// ```python + /// class A: ... + /// + /// A().foo # AttributeError: 'A' object has no attribute 'foo' + /// ``` pub(crate) static UNRESOLVED_ATTRIBUTE = { summary: "detects references to unresolved attributes", status: LintStatus::preview("1.0.0"), @@ -1015,8 +1210,13 @@ declare_lint! { /// Checks for import statements for which the module cannot be resolved. /// /// ## Why is this bad? - /// Importing a module that cannot be resolved will raise an `ImportError` + /// Importing a module that cannot be resolved will raise a `ModuleNotFoundError` /// at runtime. + /// + /// ## Examples + /// ```python + /// import foo # ModuleNotFoundError: No module named 'foo' + /// ``` pub(crate) static UNRESOLVED_IMPORT = { summary: "detects unresolved imports", status: LintStatus::preview("1.0.0"), @@ -1051,6 +1251,13 @@ declare_lint! { /// ## Why is this bad? /// Attempting to use an unsupported operator will raise a `TypeError` at /// runtime. + /// + /// ## Examples + /// ```python + /// class A: ... + /// + /// A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' + /// ``` pub(crate) static UNSUPPORTED_OPERATOR = { summary: "detects binary, unary, or comparison expressions where the operands don't support the operator", status: LintStatus::preview("1.0.0"), @@ -1081,6 +1288,11 @@ declare_lint! { /// ## What it does /// Makes sure that the argument of `static_assert` is statically known to be true. /// + /// ## Why is this bad? + /// A `static_assert` call represents an explicit request from the user + /// for the type checker to emit an error if the argument cannot be verified + /// to evaluate to `True` in a boolean context. + /// /// ## Examples /// ```python /// from ty_extensions import static_assert @@ -1098,15 +1310,24 @@ declare_lint! { declare_lint! { /// ## What it does - /// Makes sure that instance attribute accesses are valid. + /// Checks for assignments to class variables from instances + /// and assignments to instance variables from its class. + /// + /// ## Why is this bad? + /// Incorrect assignments break the rules of the type system and + /// weaken a type checker's ability to accurately reason about your code. /// /// ## Examples /// ```python /// class C: - /// var: ClassVar[int] = 1 + /// class_var: ClassVar[int] = 1 + /// instance_var: int + /// + /// C.class_var = 3 # okay + /// C().class_var = 3 # error: Cannot assign to class variable /// - /// C.var = 3 # okay - /// C().var = 3 # error: Cannot assign to class variable + /// C().instance_var = 3 # okay + /// C.instance_var = 3 # error: Cannot assign to instance variable /// ``` pub(crate) static INVALID_ATTRIBUTE_ACCESS = { summary: "Invalid attribute access", diff --git a/ty.schema.json b/ty.schema.json index de51eec818a081..ac715b050383cd 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -252,7 +252,7 @@ }, "conflicting-argument-forms": { "title": "detects when an argument is used as both a value and a type form in a call", - "description": "## What it does\nChecks whether an argument is used as both a value and a type form in a call", + "description": "## What it does\nChecks whether an argument is used as both a value and a type form in a call.\n\n## Why is this bad?\nSuch calls have confusing semantics and often indicate a logic error.\n\n## Examples\n```python\nfrom typing import reveal_type\nfrom ty_extensions import is_fully_static\n\nif flag:\n f = repr # Expects a value\nelse:\n f = is_fully_static # Expects a type form\n\nf(int) # error\n```", "default": "error", "oneOf": [ { @@ -262,7 +262,7 @@ }, "conflicting-declarations": { "title": "detects conflicting declarations", - "description": "## What it does\nChecks whether a variable has been declared as two conflicting types.\n\n## Why is this bad\nA variable with two conflicting declarations likely indicates a mistake.\nMoreover, it could lead to incorrect or ill-defined type inference for\nother code that relies on these variables.", + "description": "## What it does\nChecks whether a variable has been declared as two conflicting types.\n\n## Why is this bad\nA variable with two conflicting declarations likely indicates a mistake.\nMoreover, it could lead to incorrect or ill-defined type inference for\nother code that relies on these variables.\n\n## Examples\n```python\nif b:\n a: int\nelse:\n a: str\n\na = 1\n```", "default": "error", "oneOf": [ { @@ -272,7 +272,7 @@ }, "conflicting-metaclass": { "title": "detects conflicting metaclasses", - "description": "TODO #14889", + "description": "## What it does\nChecks for class definitions where the metaclass of the class\nbeing created would not be a subclass of the metaclasses of\nall the class's bases.\n\n## Why is it bad?\nSuch a class definition raises a `TypeError` at runtime.\n\n## Examples\n```python\nclass M1(type): ...\nclass M2(type): ...\nclass A(metaclass=M1): ...\nclass B(metaclass=M2): ...\n\n# TypeError: metaclass conflict\nclass C(A, B): ...\n```", "default": "error", "oneOf": [ { @@ -282,7 +282,7 @@ }, "cyclic-class-definition": { "title": "detects cyclic class definitions", - "description": "## What it does\nChecks for class definitions with a cyclic inheritance chain.\n\n## Why is it bad?\nTODO #14889", + "description": "## What it does\nChecks for class definitions in stub files that inherit\n(directly or indirectly) from themselves.\n\n## Why is it bad?\nAlthough forward references are natively supported in stub files,\ninheritance cycles are still disallowed, as it is impossible to\nresolve a consistent [method resolution order] for a class that\ninherits from itself.\n\n## Examples\n```python\n# foo.pyi\nclass A(B): ...\nclass B(A): ...\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", "default": "error", "oneOf": [ { @@ -302,7 +302,7 @@ }, "duplicate-base": { "title": "detects class definitions with duplicate bases", - "description": "## What it does\nChecks for class definitions with duplicate bases.\n\n## Why is this bad?\nClass definitions with duplicate bases raise a `TypeError` at runtime.", + "description": "## What it does\nChecks for class definitions with duplicate bases.\n\n## Why is this bad?\nClass definitions with duplicate bases raise `TypeError` at runtime.\n\n## Examples\n```python\nclass A: ...\n\n# TypeError: duplicate base class\nclass B(A, A): ...\n```", "default": "error", "oneOf": [ { @@ -352,7 +352,7 @@ }, "inconsistent-mro": { "title": "detects class definitions with an inconsistent MRO", - "description": "## What it does\nChecks for classes with an inconsistent method resolution order (MRO).\n\n## Why is this bad?\nClasses with an inconsistent MRO will raise a `TypeError` at runtime.", + "description": "## What it does\nChecks for classes with an inconsistent [method resolution order] (MRO).\n\n## Why is this bad?\nClasses with an inconsistent MRO will raise a `TypeError` at runtime.\n\n## Examples\n```python\nclass A: ...\nclass B(A): ...\n\n# TypeError: Cannot create a consistent method resolution order\nclass C(A, B): ...\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", "default": "error", "oneOf": [ { @@ -362,7 +362,7 @@ }, "index-out-of-bounds": { "title": "detects index out of bounds errors", - "description": "## What it does\nChecks for attempts to use an out of bounds index to get an item from\na container.\n\n## Why is this bad?\nUsing an out of bounds index will raise an `IndexError` at runtime.", + "description": "## What it does\nChecks for attempts to use an out of bounds index to get an item from\na container.\n\n## Why is this bad?\nUsing an out of bounds index will raise an `IndexError` at runtime.\n\n## Examples\n```python\nt = (0, 1, 2)\nt[3] # IndexError: tuple index out of range\n```", "default": "error", "oneOf": [ { @@ -382,7 +382,7 @@ }, "invalid-assignment": { "title": "detects invalid assignments", - "description": "TODO #14889", + "description": "## What it does\nChecks for assignments where the type of the value\nis not [assignable to] the type of the assignee.\n\n## Why is this bad?\nSuch assignments break the rules of the type system and\nweaken a type checker's ability to accurately reason about your code.\n\n## Examples\n```python\na: int = ''\n```\n\n[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable", "default": "error", "oneOf": [ { @@ -392,7 +392,7 @@ }, "invalid-attribute-access": { "title": "Invalid attribute access", - "description": "## What it does\nMakes sure that instance attribute accesses are valid.\n\n## Examples\n```python\nclass C:\n var: ClassVar[int] = 1\n\nC.var = 3 # okay\nC().var = 3 # error: Cannot assign to class variable\n```", + "description": "## What it does\nChecks for assignments to class variables from instances\nand assignments to instance variables from its class.\n\n## Why is this bad?\nIncorrect assignments break the rules of the type system and\nweaken a type checker's ability to accurately reason about your code.\n\n## Examples\n```python\nclass C:\n class_var: ClassVar[int] = 1\n instance_var: int\n\nC.class_var = 3 # okay\nC().class_var = 3 # error: Cannot assign to class variable\n\nC().instance_var = 3 # okay\nC.instance_var = 3 # error: Cannot assign to instance variable\n```", "default": "error", "oneOf": [ { @@ -401,7 +401,7 @@ ] }, "invalid-base": { - "title": "detects class definitions with an invalid base", + "title": "detects invalid bases in class definitions", "description": "TODO #14889", "default": "error", "oneOf": [ @@ -412,7 +412,7 @@ }, "invalid-context-manager": { "title": "detects expressions used in with statements that don't implement the context manager protocol", - "description": "TODO #14889", + "description": "## What it does\nChecks for expressions used in `with` statements\nthat do not implement the context manager protocol.\n\n## Why is this bad?\nSuch a statement will raise `TypeError` at runtime.\n\n## Examples\n```python\n# TypeError: 'int' object does not support the context manager protocol\nwith 1:\n print(2)\n```", "default": "error", "oneOf": [ { @@ -422,7 +422,7 @@ }, "invalid-declaration": { "title": "detects invalid declarations", - "description": "TODO #14889", + "description": "## What it does\nChecks for declarations where the inferred type of an existing symbol\nis not [assignable to] its post-hoc declared type.\n\n## Why is this bad?\nSuch declarations break the rules of the type system and\nweaken a type checker's ability to accurately reason about your code.\n\n## Examples\n```python\na = 1\na: str\n```\n\n[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable", "default": "error", "oneOf": [ { @@ -492,7 +492,7 @@ }, "invalid-parameter-default": { "title": "detects default values that can't be assigned to the parameter's annotated type", - "description": "## What it does\nChecks for default values that can't be assigned to the parameter's annotated type.\n\n## Why is this bad?\nTODO #14889", + "description": "## What it does\nChecks for default values that can't be\nassigned to the parameter's annotated type.\n\n## Why is this bad?\nThis breaks the rules of the type system and\nweakens a type checker's ability to accurately reason about your code.\n\n## Examples\n```python\ndef f(a: int = ''): ...\n```", "default": "error", "oneOf": [ { @@ -552,7 +552,7 @@ }, "invalid-type-checking-constant": { "title": "detects invalid `TYPE_CHECKING` constant assignments", - "description": "## What it does\nChecks for a value other than `False` assigned to the `TYPE_CHECKING` variable, or an\nannotation not assignable from `bool`.\n\n## Why is this bad?\nThe name `TYPE_CHECKING` is reserved for a flag that can be used to provide conditional\ncode seen only by the type checker, and not at runtime. Normally this flag is imported from\n`typing` or `typing_extensions`, but it can also be defined locally. If defined locally, it\nmust be assigned the value `False` at runtime; the type checker will consider its value to\nbe `True`. If annotated, it must be annotated as a type that can accept `bool` values.", + "description": "## What it does\nChecks for a value other than `False` assigned to the `TYPE_CHECKING` variable, or an\nannotation not assignable from `bool`.\n\n## Why is this bad?\nThe name `TYPE_CHECKING` is reserved for a flag that can be used to provide conditional\ncode seen only by the type checker, and not at runtime. Normally this flag is imported from\n`typing` or `typing_extensions`, but it can also be defined locally. If defined locally, it\nmust be assigned the value `False` at runtime; the type checker will consider its value to\nbe `True`. If annotated, it must be annotated as a type that can accept `bool` values.\n\n## Examples\n```python\nTYPE_CHECKING: str\nTYPE_CHECKING = ''\n```", "default": "error", "oneOf": [ { @@ -562,7 +562,7 @@ }, "invalid-type-form": { "title": "detects invalid type forms", - "description": "## What it does\nChecks for invalid type expressions.\n\n## Why is this bad?\nTODO #14889", + "description": "## What it does\nChecks for expressions that are used as type expressions\nbut cannot validly be interpreted as such.\n\n## Why is this bad?\nSuch expressions cannot be understood by ty.\nIn some cases, they might raise errors at runtime.\n\n## Examples\n```python\nfrom typing import Annotated\n\na: type[1] # `1` is not a type\nb: Annotated[int] # `Annotated` expects at least two arguments\n```", "default": "error", "oneOf": [ { @@ -572,7 +572,7 @@ }, "invalid-type-variable-constraints": { "title": "detects invalid type variable constraints", - "description": "TODO #14889", + "description": "## What it does\nChecks for constrained [type variables] with only one constraint.\n\n## Why is this bad?\nA constrained type variable must have at least two constraints.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar('T', str) # invalid constrained TypeVar\n```\n\nUse instead:\n```python\nT = TypeVar('T', str, int) # valid constrained TypeVar\n# or\nT = TypeVar('T', bound=str) # valid bound TypeVar\n```\n\n[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar", "default": "error", "oneOf": [ { @@ -632,7 +632,7 @@ }, "possibly-unbound-attribute": { "title": "detects references to possibly unbound attributes", - "description": "## What it does\nChecks for possibly unbound attributes.\n\n## Why is this bad?\nAttempting to access an unbound attribute will raise an `AttributeError` at runtime.", + "description": "## What it does\nChecks for possibly unbound attributes.\n\n## Why is this bad?\nAttempting to access an unbound attribute will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nclass A:\n if b:\n c = 0\n\nA.c # AttributeError: type object 'A' has no attribute 'c'\n```", "default": "warn", "oneOf": [ { @@ -642,7 +642,7 @@ }, "possibly-unbound-import": { "title": "detects possibly unbound imports", - "description": "## What it does\nChecks for imports of symbols that may be unbound.\n\n## Why is this bad?\nImporting an unbound module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.", + "description": "## What it does\nChecks for imports of symbols that may be unbound.\n\n## Why is this bad?\nImporting an unbound module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.\n\n## Examples\n```python\n# module.py\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n\n# main.py\nfrom module import a # ImportError: cannot import name 'a' from 'module'\n```", "default": "warn", "oneOf": [ { @@ -682,7 +682,7 @@ }, "static-assert-error": { "title": "Failed static assertion", - "description": "## What it does\nMakes sure that the argument of `static_assert` is statically known to be true.\n\n## Examples\n```python\nfrom ty_extensions import static_assert\n\nstatic_assert(1 + 1 == 3) # error: evaluates to `False`\n\nstatic_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known truthiness\n```", + "description": "## What it does\nMakes sure that the argument of `static_assert` is statically known to be true.\n\n## Why is this bad?\nA `static_assert` call represents an explicit request from the user\nfor the type checker to emit an error if the argument cannot be verified\nto evaluate to `True` in a boolean context.\n\n## Examples\n```python\nfrom ty_extensions import static_assert\n\nstatic_assert(1 + 1 == 3) # error: evaluates to `False`\n\nstatic_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known truthiness\n```", "default": "error", "oneOf": [ { @@ -732,7 +732,7 @@ }, "undefined-reveal": { "title": "detects usages of `reveal_type` without importing it", - "description": "## What it does\nChecks for calls to `reveal_type` without importing it.\n\n## Why is this bad?\nUsing `reveal_type` without importing it will raise a `NameError` at runtime.\n\n## Examples\nTODO #14889", + "description": "## What it does\nChecks for calls to `reveal_type` without importing it.\n\n## Why is this bad?\nUsing `reveal_type` without importing it will raise a `NameError` at runtime.\n\n## Examples\n```python\nreveal_type(1) # NameError: name 'reveal_type' is not defined\n```", "default": "warn", "oneOf": [ { @@ -762,7 +762,7 @@ }, "unresolved-attribute": { "title": "detects references to unresolved attributes", - "description": "## What it does\nChecks for unresolved attributes.\n\n## Why is this bad?\nAccessing an unbound attribute will raise an `AttributeError` at runtime. An unresolved attribute is not guaranteed to exist from the type alone, so this could also indicate that the object is not of the type that the user expects.", + "description": "## What it does\nChecks for unresolved attributes.\n\n## Why is this bad?\nAccessing an unbound attribute will raise an `AttributeError` at runtime.\nAn unresolved attribute is not guaranteed to exist from the type alone,\nso this could also indicate that the object is not of the type that the user expects.\n\n## Examples\n```python\nclass A: ...\n\nA().foo # AttributeError: 'A' object has no attribute 'foo'\n```", "default": "error", "oneOf": [ { @@ -772,7 +772,7 @@ }, "unresolved-import": { "title": "detects unresolved imports", - "description": "## What it does\nChecks for import statements for which the module cannot be resolved.\n\n## Why is this bad?\nImporting a module that cannot be resolved will raise an `ImportError`\nat runtime.", + "description": "## What it does\nChecks for import statements for which the module cannot be resolved.\n\n## Why is this bad?\nImporting a module that cannot be resolved will raise a `ModuleNotFoundError`\nat runtime.\n\n## Examples\n```python\nimport foo # ModuleNotFoundError: No module named 'foo'\n```", "default": "error", "oneOf": [ { @@ -802,7 +802,7 @@ }, "unsupported-operator": { "title": "detects binary, unary, or comparison expressions where the operands don't support the operator", - "description": "## What it does\nChecks for binary expressions, comparisons, and unary expressions where\nthe operands don't support the operator.\n\n## Why is this bad?\nAttempting to use an unsupported operator will raise a `TypeError` at\nruntime.", + "description": "## What it does\nChecks for binary expressions, comparisons, and unary expressions where\nthe operands don't support the operator.\n\n## Why is this bad?\nAttempting to use an unsupported operator will raise a `TypeError` at\nruntime.\n\n## Examples\n```python\nclass A: ...\n\nA() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'\n```", "default": "error", "oneOf": [ { From 25e13debc040555dd20a856058470e5b514cd12a Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Sat, 10 May 2025 01:08:37 +0800 Subject: [PATCH 008/487] [`airflow`] extend `AIR311` rules (#17913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * `airflow.models.Connection` → `airflow.sdk.Connection` * `airflow.models.Variable` → `airflow.sdk.Variable` ## Test Plan The test fixtures has been updated (see the first commit for easier review) --- .../test/fixtures/airflow/AIR311_names.py | 6 + .../airflow/rules/suggested_to_update_3_0.rs | 8 + ...irflow__tests__AIR311_AIR311_names.py.snap | 445 +++++++++--------- 3 files changed, 247 insertions(+), 212 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py index 2b96376395c293..242b650938f9c1 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py @@ -13,6 +13,10 @@ from airflow.io.path import ObjectStoragePath from airflow.io.storage import attach from airflow.models import DAG as DAGFromModel +from airflow.models import ( + Connection, + Variable, +) from airflow.models.baseoperator import chain, chain_linear, cross_downstream from airflow.models.baseoperatorlink import BaseOperatorLink from airflow.models.dag import DAG as DAGFromDag @@ -42,7 +46,9 @@ attach() # airflow.models +Connection() DAGFromModel() +Variable() # airflow.models.baseoperator chain() diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index 097ef27bb01980..f732ac0ea20969 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -242,6 +242,14 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { name: "attach".to_string(), }, + // airflow.models + ["airflow", "models", rest @ ("Connection" | "Variable")] => { + Replacement::SourceModuleMoved { + module: "airflow.sdk", + name: (*rest).to_string(), + } + } + // airflow.models.baseoperator ["airflow", "models", "baseoperator", rest] => match *rest { "chain" | "chain_linear" | "cross_downstream" => Replacement::SourceModuleMoved { diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap index b291f225c2f25e..326f8b37fbb371 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap @@ -1,337 +1,358 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR311_names.py:23:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:27:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -22 | # airflow -23 | DatasetFromRoot() +26 | # airflow +27 | DatasetFromRoot() | ^^^^^^^^^^^^^^^ AIR311 -24 | -25 | # airflow.datasets +28 | +29 | # airflow.datasets | = help: Use `airflow.sdk.Asset` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import Asset -21 22 | -22 23 | # airflow -23 |-DatasetFromRoot() - 24 |+Asset() -24 25 | -25 26 | # airflow.datasets -26 27 | Dataset() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import Asset +25 26 | +26 27 | # airflow +27 |-DatasetFromRoot() + 28 |+Asset() +28 29 | +29 30 | # airflow.datasets +30 31 | Dataset() -AIR311_names.py:26:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:30:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -25 | # airflow.datasets -26 | Dataset() +29 | # airflow.datasets +30 | Dataset() | ^^^^^^^ AIR311 -27 | DatasetAlias() -28 | DatasetAll() +31 | DatasetAlias() +32 | DatasetAll() | = help: Use `airflow.sdk.Asset` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import Asset -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() -24 25 | -25 26 | # airflow.datasets -26 |-Dataset() - 27 |+Asset() -27 28 | DatasetAlias() -28 29 | DatasetAll() -29 30 | DatasetAny() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import Asset +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() +28 29 | +29 30 | # airflow.datasets +30 |-Dataset() + 31 |+Asset() +31 32 | DatasetAlias() +32 33 | DatasetAll() +33 34 | DatasetAny() -AIR311_names.py:27:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -25 | # airflow.datasets -26 | Dataset() -27 | DatasetAlias() +29 | # airflow.datasets +30 | Dataset() +31 | DatasetAlias() | ^^^^^^^^^^^^ AIR311 -28 | DatasetAll() -29 | DatasetAny() +32 | DatasetAll() +33 | DatasetAny() | = help: Use `airflow.sdk.AssetAlias` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import AssetAlias -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() -24 25 | -25 26 | # airflow.datasets -26 27 | Dataset() -27 |-DatasetAlias() - 28 |+AssetAlias() -28 29 | DatasetAll() -29 30 | DatasetAny() -30 31 | Metadata() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import AssetAlias +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() +28 29 | +29 30 | # airflow.datasets +30 31 | Dataset() +31 |-DatasetAlias() + 32 |+AssetAlias() +32 33 | DatasetAll() +33 34 | DatasetAny() +34 35 | Metadata() -AIR311_names.py:28:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:32:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -26 | Dataset() -27 | DatasetAlias() -28 | DatasetAll() +30 | Dataset() +31 | DatasetAlias() +32 | DatasetAll() | ^^^^^^^^^^ AIR311 -29 | DatasetAny() -30 | Metadata() +33 | DatasetAny() +34 | Metadata() | = help: Use `airflow.sdk.AssetAll` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import AssetAll -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import AssetAll +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() -------------------------------------------------------------------------------- -25 26 | # airflow.datasets -26 27 | Dataset() -27 28 | DatasetAlias() -28 |-DatasetAll() - 29 |+AssetAll() -29 30 | DatasetAny() -30 31 | Metadata() -31 32 | expand_alias_to_datasets() +29 30 | # airflow.datasets +30 31 | Dataset() +31 32 | DatasetAlias() +32 |-DatasetAll() + 33 |+AssetAll() +33 34 | DatasetAny() +34 35 | Metadata() +35 36 | expand_alias_to_datasets() -AIR311_names.py:29:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:33:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -27 | DatasetAlias() -28 | DatasetAll() -29 | DatasetAny() +31 | DatasetAlias() +32 | DatasetAll() +33 | DatasetAny() | ^^^^^^^^^^ AIR311 -30 | Metadata() -31 | expand_alias_to_datasets() +34 | Metadata() +35 | expand_alias_to_datasets() | = help: Use `airflow.sdk.AssetAny` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import AssetAny -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import AssetAny +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() -------------------------------------------------------------------------------- -26 27 | Dataset() -27 28 | DatasetAlias() -28 29 | DatasetAll() -29 |-DatasetAny() - 30 |+AssetAny() -30 31 | Metadata() -31 32 | expand_alias_to_datasets() -32 33 | +30 31 | Dataset() +31 32 | DatasetAlias() +32 33 | DatasetAll() +33 |-DatasetAny() + 34 |+AssetAny() +34 35 | Metadata() +35 36 | expand_alias_to_datasets() +36 37 | -AIR311_names.py:30:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:34:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -28 | DatasetAll() -29 | DatasetAny() -30 | Metadata() +32 | DatasetAll() +33 | DatasetAny() +34 | Metadata() | ^^^^^^^^ AIR311 -31 | expand_alias_to_datasets() +35 | expand_alias_to_datasets() | = help: Use `airflow.sdk.Metadata` instead -AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:35:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -29 | DatasetAny() -30 | Metadata() -31 | expand_alias_to_datasets() +33 | DatasetAny() +34 | Metadata() +35 | expand_alias_to_datasets() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR311 -32 | -33 | # airflow.decorators +36 | +37 | # airflow.decorators | = help: Use `airflow.sdk.expand_alias_to_assets` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.sdk import expand_alias_to_assets -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import expand_alias_to_assets +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() -------------------------------------------------------------------------------- -28 29 | DatasetAll() -29 30 | DatasetAny() -30 31 | Metadata() -31 |-expand_alias_to_datasets() - 32 |+expand_alias_to_assets() -32 33 | -33 34 | # airflow.decorators -34 35 | dag() +32 33 | DatasetAll() +33 34 | DatasetAny() +34 35 | Metadata() +35 |-expand_alias_to_datasets() + 36 |+expand_alias_to_assets() +36 37 | +37 38 | # airflow.decorators +38 39 | dag() -AIR311_names.py:34:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:38:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -33 | # airflow.decorators -34 | dag() +37 | # airflow.decorators +38 | dag() | ^^^ AIR311 -35 | task() -36 | task_group() +39 | task() +40 | task_group() | = help: Use `airflow.sdk.dag` instead -AIR311_names.py:35:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:39:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -33 | # airflow.decorators -34 | dag() -35 | task() +37 | # airflow.decorators +38 | dag() +39 | task() | ^^^^ AIR311 -36 | task_group() -37 | setup() +40 | task_group() +41 | setup() | = help: Use `airflow.sdk.task` instead -AIR311_names.py:36:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:40:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -34 | dag() -35 | task() -36 | task_group() +38 | dag() +39 | task() +40 | task_group() | ^^^^^^^^^^ AIR311 -37 | setup() -38 | teardown() +41 | setup() +42 | teardown() | = help: Use `airflow.sdk.task_group` instead -AIR311_names.py:37:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:41:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -35 | task() -36 | task_group() -37 | setup() +39 | task() +40 | task_group() +41 | setup() | ^^^^^ AIR311 -38 | teardown() +42 | teardown() | = help: Use `airflow.sdk.setup` instead -AIR311_names.py:38:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:42:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -36 | task_group() -37 | setup() -38 | teardown() +40 | task_group() +41 | setup() +42 | teardown() | ^^^^^^^^ AIR311 -39 | -40 | # airflow.io +43 | +44 | # airflow.io | = help: Use `airflow.sdk.teardown` instead -AIR311_names.py:41:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:45:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -40 | # airflow.io -41 | ObjectStoragePath() +44 | # airflow.io +45 | ObjectStoragePath() | ^^^^^^^^^^^^^^^^^ AIR311 -42 | attach() +46 | attach() | = help: Use `airflow.sdk.ObjectStoragePath` instead -AIR311_names.py:42:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:46:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -40 | # airflow.io -41 | ObjectStoragePath() -42 | attach() +44 | # airflow.io +45 | ObjectStoragePath() +46 | attach() | ^^^^^^ AIR311 -43 | -44 | # airflow.models +47 | +48 | # airflow.models | = help: Use `airflow.sdk.io.attach` instead -AIR311_names.py:45:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +48 | # airflow.models +49 | Connection() + | ^^^^^^^^^^ AIR311 +50 | DAGFromModel() +51 | Variable() + | + = help: Use `airflow.sdk.Connection` instead + +AIR311_names.py:50:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -44 | # airflow.models -45 | DAGFromModel() +48 | # airflow.models +49 | Connection() +50 | DAGFromModel() | ^^^^^^^^^^^^ AIR311 -46 | -47 | # airflow.models.baseoperator +51 | Variable() | = help: Use `airflow.sdk.DAG` instead -AIR311_names.py:48:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +49 | Connection() +50 | DAGFromModel() +51 | Variable() + | ^^^^^^^^ AIR311 +52 | +53 | # airflow.models.baseoperator + | + = help: Use `airflow.sdk.Variable` instead + +AIR311_names.py:54:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -47 | # airflow.models.baseoperator -48 | chain() +53 | # airflow.models.baseoperator +54 | chain() | ^^^^^ AIR311 -49 | chain_linear() -50 | cross_downstream() +55 | chain_linear() +56 | cross_downstream() | = help: Use `airflow.sdk.chain` instead -AIR311_names.py:49:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:55:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -47 | # airflow.models.baseoperator -48 | chain() -49 | chain_linear() +53 | # airflow.models.baseoperator +54 | chain() +55 | chain_linear() | ^^^^^^^^^^^^ AIR311 -50 | cross_downstream() +56 | cross_downstream() | = help: Use `airflow.sdk.chain_linear` instead -AIR311_names.py:50:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -48 | chain() -49 | chain_linear() -50 | cross_downstream() +54 | chain() +55 | chain_linear() +56 | cross_downstream() | ^^^^^^^^^^^^^^^^ AIR311 -51 | -52 | # airflow.models.baseoperatolinker +57 | +58 | # airflow.models.baseoperatolinker | = help: Use `airflow.sdk.cross_downstream` instead -AIR311_names.py:56:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:62:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -55 | # airflow.models.dag -56 | DAGFromDag() +61 | # airflow.models.dag +62 | DAGFromDag() | ^^^^^^^^^^ AIR311 -57 | # airflow.timetables.datasets -58 | DatasetOrTimeSchedule() +63 | # airflow.timetables.datasets +64 | DatasetOrTimeSchedule() | = help: Use `airflow.sdk.DAG` instead -AIR311_names.py:58:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:64:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -56 | DAGFromDag() -57 | # airflow.timetables.datasets -58 | DatasetOrTimeSchedule() +62 | DAGFromDag() +63 | # airflow.timetables.datasets +64 | DatasetOrTimeSchedule() | ^^^^^^^^^^^^^^^^^^^^^ AIR311 -59 | -60 | # airflow.utils.dag_parsing_context +65 | +66 | # airflow.utils.dag_parsing_context | = help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead ℹ Safe fix -18 18 | from airflow.models.dag import DAG as DAGFromDag -19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule -20 20 | from airflow.utils.dag_parsing_context import get_parsing_context - 21 |+from airflow.timetables.assets import AssetOrTimeSchedule -21 22 | -22 23 | # airflow -23 24 | DatasetFromRoot() +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.timetables.assets import AssetOrTimeSchedule +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() -------------------------------------------------------------------------------- -55 56 | # airflow.models.dag -56 57 | DAGFromDag() -57 58 | # airflow.timetables.datasets -58 |-DatasetOrTimeSchedule() - 59 |+AssetOrTimeSchedule() -59 60 | -60 61 | # airflow.utils.dag_parsing_context -61 62 | get_parsing_context() +61 62 | # airflow.models.dag +62 63 | DAGFromDag() +63 64 | # airflow.timetables.datasets +64 |-DatasetOrTimeSchedule() + 65 |+AssetOrTimeSchedule() +65 66 | +66 67 | # airflow.utils.dag_parsing_context +67 68 | get_parsing_context() -AIR311_names.py:61:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:67:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -60 | # airflow.utils.dag_parsing_context -61 | get_parsing_context() +66 | # airflow.utils.dag_parsing_context +67 | get_parsing_context() | ^^^^^^^^^^^^^^^^^^^ AIR311 | = help: Use `airflow.sdk.get_parsing_context` instead From e9da1750a1bd70039f91fc6f3e08a7b6e0701e10 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 9 May 2025 13:32:27 -0400 Subject: [PATCH 009/487] Add progress bar for `ty check` (#17965) ## Summary Adds a simple progress bar for the `ty check` CLI command. The style is taken from uv, and like uv the bar is always shown - for smaller projects it is fast enough that it isn't noticeable. We could alternatively hide it completely based on some heuristic for the number of files, or only show it after some amount of time. I also disabled it when `--watch` is passed, cancelling inflight checks was leading to zombie progress bars. I think we can fix this by using [`MultiProgress`](https://docs.rs/indicatif/latest/indicatif/struct.MultiProgress.html) and managing all the bars globally, but I left that out for now. Resolves https://github.com/astral-sh/ty/issues/98. --- Cargo.lock | 1 + crates/ruff_benchmark/benches/ty.rs | 10 +++--- crates/ty/Cargo.toml | 1 + crates/ty/src/lib.rs | 47 +++++++++++++++++++++---- crates/ty/tests/file_watching.rs | 12 +++++-- crates/ty_project/src/db.rs | 6 ++-- crates/ty_project/src/files.rs | 4 +++ crates/ty_project/src/lib.rs | 53 ++++++++++++++++++++--------- crates/ty_wasm/src/lib.rs | 4 +-- 9 files changed, 103 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2be38f3583e003..e38a58c9ebc889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3984,6 +3984,7 @@ dependencies = [ "crossbeam", "ctrlc", "filetime", + "indicatif", "insta", "insta-cmd", "jiff", diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index fe282249a5e8d4..c67a457d640fb1 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -16,7 +16,7 @@ use ruff_python_ast::PythonVersion; use ty_project::metadata::options::{EnvironmentOptions, Options}; use ty_project::metadata::value::RangedValue; use ty_project::watch::{ChangeEvent, ChangedKind}; -use ty_project::{Db, ProjectDatabase, ProjectMetadata}; +use ty_project::{Db, DummyReporter, ProjectDatabase, ProjectMetadata}; struct Case { db: ProjectDatabase, @@ -153,7 +153,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { fn setup() -> Case { let case = setup_tomllib_case(); - let result: Vec<_> = case.db.check().unwrap(); + let result: Vec<_> = case.db.check(&DummyReporter).unwrap(); assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); @@ -181,7 +181,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { None, ); - let result = db.check().unwrap(); + let result = db.check(&DummyReporter).unwrap(); assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len()); } @@ -201,7 +201,7 @@ fn benchmark_cold(criterion: &mut Criterion) { setup_tomllib_case, |case| { let Case { db, .. } = case; - let result: Vec<_> = db.check().unwrap(); + let result: Vec<_> = db.check(&DummyReporter).unwrap(); assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); }, @@ -315,7 +315,7 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) { }, |case| { let Case { db, .. } = case; - let result = db.check().unwrap(); + let result = db.check(&DummyReporter).unwrap(); assert_eq!(result.len(), 0); }, BatchSize::SmallInput, diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index 70f698a95ec069..1a1222121d1419 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -28,6 +28,7 @@ colored = { workspace = true } countme = { workspace = true, features = ["enable"] } crossbeam = { workspace = true } ctrlc = { version = "3.4.4" } +indicatif = { workspace = true } jiff = { workspace = true } rayon = { workspace = true } salsa = { workspace = true } diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 4d85d4594ed829..87fd6178620eee 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -25,7 +25,7 @@ use ruff_db::Upcast; use salsa::plumbing::ZalsaDatabase; use ty_project::metadata::options::Options; use ty_project::watch::ProjectWatcher; -use ty_project::{watch, Db}; +use ty_project::{watch, Db, DummyReporter, Reporter}; use ty_project::{ProjectDatabase, ProjectMetadata}; use ty_server::run_server; @@ -200,22 +200,28 @@ impl MainLoop { self.watcher = Some(ProjectWatcher::new(watcher, db)); - self.run(db)?; + // Do not show progress bars with `--watch`, indicatif does not seem to + // handle cancelling independent progress bars very well. + self.run_with_progress::(db)?; Ok(ExitStatus::Success) } - fn run(mut self, db: &mut ProjectDatabase) -> Result { + fn run(self, db: &mut ProjectDatabase) -> Result { + self.run_with_progress::(db) + } + + fn run_with_progress(mut self, db: &mut ProjectDatabase) -> Result { self.sender.send(MainLoopMessage::CheckWorkspace).unwrap(); - let result = self.main_loop(db); + let result = self.main_loop::(db); tracing::debug!("Exiting main loop"); result } - fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result { + fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result { // Schedule the first check. tracing::debug!("Starting main loop"); @@ -226,11 +232,12 @@ impl MainLoop { MainLoopMessage::CheckWorkspace => { let db = db.clone(); let sender = self.sender.clone(); + let reporter = R::default(); // Spawn a new task that checks the project. This needs to be done in a separate thread // to prevent blocking the main loop here. rayon::spawn(move || { - match db.check() { + match db.check(&reporter) { Ok(result) => { // Send the result back to the main loop for printing. sender @@ -340,6 +347,34 @@ impl MainLoop { } } +/// A progress reporter for `ty check`. +struct IndicatifReporter(indicatif::ProgressBar); + +impl Default for IndicatifReporter { + fn default() -> IndicatifReporter { + let progress = indicatif::ProgressBar::new(0); + progress.set_style( + indicatif::ProgressStyle::with_template( + "{msg:8.dim} {bar:60.green/dim} {pos}/{len} files", + ) + .unwrap() + .progress_chars("--"), + ); + progress.set_message("Checking"); + IndicatifReporter(progress) + } +} + +impl ty_project::Reporter for IndicatifReporter { + fn set_files(&self, files: usize) { + self.0.set_length(files as u64); + } + + fn report_file(&self, _file: &ruff_db::files::File) { + self.0.inc(1); + } +} + #[derive(Debug)] struct MainLoopCancellationToken { sender: crossbeam_channel::Sender, diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index c8205c925195b3..56ff5e2bfc30aa 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -14,7 +14,7 @@ use ty_project::metadata::options::{EnvironmentOptions, Options}; use ty_project::metadata::pyproject::{PyProject, Tool}; use ty_project::metadata::value::{RangedValue, RelativePathBuf}; use ty_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher}; -use ty_project::{Db, ProjectDatabase, ProjectMetadata}; +use ty_project::{Db, DummyReporter, ProjectDatabase, ProjectMetadata}; use ty_python_semantic::{resolve_module, ModuleName, PythonPlatform}; struct TestCase { @@ -1117,7 +1117,10 @@ print(sys.last_exc, os.getegid()) Ok(()) })?; - let diagnostics = case.db.check().context("Failed to check project.")?; + let diagnostics = case + .db + .check(&DummyReporter) + .context("Failed to check project.")?; assert_eq!(diagnostics.len(), 2); assert_eq!( @@ -1142,7 +1145,10 @@ print(sys.last_exc, os.getegid()) }) .expect("Search path settings to be valid"); - let diagnostics = case.db.check().context("Failed to check project.")?; + let diagnostics = case + .db + .check(&DummyReporter) + .context("Failed to check project.")?; assert!(diagnostics.is_empty()); Ok(()) diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index 1da5f38ed1d459..c15f0c99c45fbd 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -2,7 +2,7 @@ use std::panic::RefUnwindSafe; use std::sync::Arc; use crate::DEFAULT_LINT_REGISTRY; -use crate::{Project, ProjectMetadata}; +use crate::{Project, ProjectMetadata, Reporter}; use ruff_db::diagnostic::Diagnostic; use ruff_db::files::{File, Files}; use ruff_db::system::System; @@ -68,8 +68,8 @@ impl ProjectDatabase { } /// Checks all open files in the project and its dependencies. - pub fn check(&self) -> Result, Cancelled> { - self.with_db(|db| db.project().check(db)) + pub fn check(&self, reporter: &impl Reporter) -> Result, Cancelled> { + self.with_db(|db| db.project().check(db, reporter)) } #[tracing::instrument(level = "debug", skip(self))] diff --git a/crates/ty_project/src/files.rs b/crates/ty_project/src/files.rs index 182e20c03a18ea..db8cc169f6f0fc 100644 --- a/crates/ty_project/src/files.rs +++ b/crates/ty_project/src/files.rs @@ -161,6 +161,10 @@ impl Indexed<'_> { pub(super) fn diagnostics(&self) -> &[IOErrorDiagnostic] { &self.inner.diagnostics } + + pub(super) fn len(&self) -> usize { + self.inner.files.len() + } } impl Deref for Indexed<'_> { diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index cac5000db1b907..547f80bf354bae 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -18,7 +18,7 @@ use rustc_hash::FxHashSet; use salsa::Durability; use salsa::Setter; use std::backtrace::BacktraceStatus; -use std::panic::{AssertUnwindSafe, UnwindSafe}; +use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe}; use std::sync::Arc; use thiserror::Error; use tracing::error; @@ -106,6 +106,24 @@ pub struct Project { settings_diagnostics: Vec, } +/// A progress reporter. +pub trait Reporter: Default + Send + Sync + RefUnwindSafe + 'static { + /// Initialize the reporter with the number of files. + fn set_files(&self, files: usize); + + /// Report the completion of a given file. + fn report_file(&self, file: &File); +} + +/// A no-op implementation of [`Reporter`]. +#[derive(Default)] +pub struct DummyReporter; + +impl Reporter for DummyReporter { + fn set_files(&self, _files: usize) {} + fn report_file(&self, _file: &File) {} +} + #[salsa::tracked] impl Project { pub fn from_metadata(db: &dyn Db, metadata: ProjectMetadata) -> Self { @@ -168,7 +186,7 @@ impl Project { } /// Checks all open files in the project and its dependencies. - pub(crate) fn check(self, db: &ProjectDatabase) -> Vec { + pub(crate) fn check(self, db: &ProjectDatabase, reporter: &impl Reporter) -> Vec { let project_span = tracing::debug_span!("Project::check"); let _span = project_span.enter(); @@ -182,6 +200,7 @@ impl Project { ); let files = ProjectFiles::new(db, self); + reporter.set_files(files.len()); diagnostics.extend( files @@ -190,36 +209,31 @@ impl Project { .map(IOErrorDiagnostic::to_diagnostic), ); - let file_diagnostics = Arc::new(std::sync::Mutex::new(vec![])); + let file_diagnostics = std::sync::Mutex::new(vec![]); { - let file_diagnostics = Arc::clone(&file_diagnostics); let db = db.clone(); - let project_span = project_span.clone(); + let file_diagnostics = &file_diagnostics; + let project_span = &project_span; rayon::scope(move |scope| { for file in &files { - let result = Arc::clone(&file_diagnostics); let db = db.clone(); - let project_span = project_span.clone(); - scope.spawn(move |_| { let check_file_span = - tracing::debug_span!(parent: &project_span, "check_file", ?file); + tracing::debug_span!(parent: project_span, "check_file", ?file); let _entered = check_file_span.entered(); - let file_diagnostics = check_file_impl(&db, file); - result.lock().unwrap().extend(file_diagnostics); + let result = check_file_impl(&db, file); + file_diagnostics.lock().unwrap().extend(result); + + reporter.report_file(&file); }); } }); } - let mut file_diagnostics = Arc::into_inner(file_diagnostics) - .unwrap() - .into_inner() - .unwrap(); - + let mut file_diagnostics = file_diagnostics.into_inner().unwrap(); file_diagnostics.sort_by(|left, right| { left.rendering_sort_key(db) .cmp(&right.rendering_sort_key(db)) @@ -493,6 +507,13 @@ impl<'a> ProjectFiles<'a> { ProjectFiles::Indexed(indexed) => indexed.diagnostics(), } } + + fn len(&self) -> usize { + match self { + ProjectFiles::OpenFiles(open_files) => open_files.len(), + ProjectFiles::Indexed(indexed) => indexed.len(), + } + } } impl<'a> IntoIterator for &'a ProjectFiles<'a> { diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 9ed86299e808c3..2a7cad6f8a267f 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -18,8 +18,8 @@ use ty_ide::{goto_type_definition, hover, inlay_hints, MarkupKind}; use ty_project::metadata::options::Options; use ty_project::metadata::value::ValueSource; use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind}; -use ty_project::ProjectMetadata; use ty_project::{Db, ProjectDatabase}; +use ty_project::{DummyReporter, ProjectMetadata}; use ty_python_semantic::Program; use wasm_bindgen::prelude::*; @@ -186,7 +186,7 @@ impl Workspace { /// Checks all open files pub fn check(&self) -> Result, Error> { - let result = self.db.check().map_err(into_error)?; + let result = self.db.check(&DummyReporter).map_err(into_error)?; Ok(result.into_iter().map(Diagnostic::wrap).collect()) } From 90272ad85af65aa6185e4ee71dbda09f7cb5b27a Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 07:51:26 -0400 Subject: [PATCH 010/487] ty_python_semantic: add snapshot tests for existing union function type diagnostics This is just capturing the status quo so that we can better see the changes. I took these tests from the (now defunct) PR #17959. --- .../mdtest/diagnostics/union_call.md | 121 ++++++++++++++++++ ...ction_types_-_A_smaller_scale_example.snap | 43 +++++++ ...iple_variants_but_only_one_is_invalid.snap | 52 ++++++++ ...over_keyword_argument_related_reasons.snap | 57 +++++++++ ...s_-_Cover_non-keyword_related_reasons.snap | 72 +++++++++++ 5 files changed, 345 insertions(+) create mode 100644 crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md new file mode 100644 index 00000000000000..303f72f29c054d --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md @@ -0,0 +1,121 @@ +# Calling a union of function types + + + +```toml +[environment] +python-version = "3.12" +``` + +## A smaller scale example + +```py +def f1() -> int: + return 0 + +def f2(name: str) -> int: + return 0 + +def _(flag: bool): + if flag: + f = f1 + else: + f = f2 + # error: [too-many-positional-arguments] + x = f(3) +``` + +## Multiple variants but only one is invalid + +This test in particular demonstrates some of the smarts of this diagnostic. Namely, since only one +variant is invalid, additional context specific to that variant is added to the diagnostic output. +(If more than one variant is invalid, then this additional context is elided to avoid overwhelming +the end user.) + +```py +def f1(a: int) -> int: + return 0 + +def f2(name: str) -> int: + return 0 + +def _(flag: bool): + if flag: + f = f1 + else: + f = f2 + # error: [invalid-argument-type] + x = f(3) +``` + +## Try to cover all possible reasons + +These tests is likely to become stale over time, but this was added when the union-specific +diagnostic was initially created. In each test, we try to cover as much as we can. This is mostly +just ensuring that we get test coverage for each of the possible diagnostic messages. + +### Cover non-keyword related reasons + +```py +from inspect import getattr_static + +def f1() -> int: + return 0 + +def f2(name: str) -> int: + return 0 + +def f3(a: int, b: int) -> int: + return 0 + +def f4[T: str](x: T) -> int: + return 0 + +class OverloadExample: + def f(self, x: str) -> int: + return 0 + +f5 = getattr_static(OverloadExample, "f").__get__ + +def _(n: int): + class PossiblyNotCallable: + if n == 0: + def __call__(self) -> int: + return 0 + + if n == 0: + f = f1 + elif n == 1: + f = f2 + elif n == 2: + f = f3 + elif n == 3: + f = f4 + elif n == 4: + f = 5 + elif n == 5: + f = f5 + else: + f = PossiblyNotCallable() + # error: [too-many-positional-arguments] + x = f(3) +``` + +### Cover keyword argument related reasons + +```py +def any(*args, **kwargs) -> int: + return 0 + +def f1(name: str) -> int: + return 0 + +def _(n: int): + if n == 0: + f = f1 + else: + f = any + # error: [parameter-already-assigned] + # error: [unknown-argument] + y = f("foo", name="bar", unknown="quux") +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap new file mode 100644 index 00000000000000..77cd2624e5590d --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap @@ -0,0 +1,43 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: union_call.md - Calling a union of function types - A smaller scale example +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def f1() -> int: + 2 | return 0 + 3 | + 4 | def f2(name: str) -> int: + 5 | return 0 + 6 | + 7 | def _(flag: bool): + 8 | if flag: + 9 | f = f1 +10 | else: +11 | f = f2 +12 | # error: [too-many-positional-arguments] +13 | x = f(3) +``` + +# Diagnostics + +``` +error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 + --> src/mdtest_snippet.py:13:11 + | +11 | f = f2 +12 | # error: [too-many-positional-arguments] +13 | x = f(3) + | ^ + | +info: `lint:too-many-positional-arguments` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap new file mode 100644 index 00000000000000..3d27c3172ab9b6 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap @@ -0,0 +1,52 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: union_call.md - Calling a union of function types - Multiple variants but only one is invalid +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def f1(a: int) -> int: + 2 | return 0 + 3 | + 4 | def f2(name: str) -> int: + 5 | return 0 + 6 | + 7 | def _(flag: bool): + 8 | if flag: + 9 | f = f1 +10 | else: +11 | f = f2 +12 | # error: [invalid-argument-type] +13 | x = f(3) +``` + +# Diagnostics + +``` +error: lint:invalid-argument-type: Argument to this function is incorrect + --> src/mdtest_snippet.py:13:11 + | +11 | f = f2 +12 | # error: [invalid-argument-type] +13 | x = f(3) + | ^ Expected `str`, found `Literal[3]` + | +info: Function defined here + --> src/mdtest_snippet.py:4:5 + | +2 | return 0 +3 | +4 | def f2(name: str) -> int: + | ^^ --------- Parameter declared here +5 | return 0 + | +info: `lint:invalid-argument-type` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap new file mode 100644 index 00000000000000..9e2b42242770c6 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap @@ -0,0 +1,57 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: union_call.md - Calling a union of function types - Try to cover all possible reasons - Cover keyword argument related reasons +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def any(*args, **kwargs) -> int: + 2 | return 0 + 3 | + 4 | def f1(name: str) -> int: + 5 | return 0 + 6 | + 7 | def _(n: int): + 8 | if n == 0: + 9 | f = f1 +10 | else: +11 | f = any +12 | # error: [parameter-already-assigned] +13 | # error: [unknown-argument] +14 | y = f("foo", name="bar", unknown="quux") +``` + +# Diagnostics + +``` +error: lint:parameter-already-assigned: Multiple values provided for parameter `name` of function `f1` + --> src/mdtest_snippet.py:14:18 + | +12 | # error: [parameter-already-assigned] +13 | # error: [unknown-argument] +14 | y = f("foo", name="bar", unknown="quux") + | ^^^^^^^^^^ + | +info: `lint:parameter-already-assigned` is enabled by default + +``` + +``` +error: lint:unknown-argument: Argument `unknown` does not match any known parameter of function `f1` + --> src/mdtest_snippet.py:14:30 + | +12 | # error: [parameter-already-assigned] +13 | # error: [unknown-argument] +14 | y = f("foo", name="bar", unknown="quux") + | ^^^^^^^^^^^^^^ + | +info: `lint:unknown-argument` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap new file mode 100644 index 00000000000000..93a36fd8419d14 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap @@ -0,0 +1,72 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: union_call.md - Calling a union of function types - Try to cover all possible reasons - Cover non-keyword related reasons +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from inspect import getattr_static + 2 | + 3 | def f1() -> int: + 4 | return 0 + 5 | + 6 | def f2(name: str) -> int: + 7 | return 0 + 8 | + 9 | def f3(a: int, b: int) -> int: +10 | return 0 +11 | +12 | def f4[T: str](x: T) -> int: +13 | return 0 +14 | +15 | class OverloadExample: +16 | def f(self, x: str) -> int: +17 | return 0 +18 | +19 | f5 = getattr_static(OverloadExample, "f").__get__ +20 | +21 | def _(n: int): +22 | class PossiblyNotCallable: +23 | if n == 0: +24 | def __call__(self) -> int: +25 | return 0 +26 | +27 | if n == 0: +28 | f = f1 +29 | elif n == 1: +30 | f = f2 +31 | elif n == 2: +32 | f = f3 +33 | elif n == 3: +34 | f = f4 +35 | elif n == 4: +36 | f = 5 +37 | elif n == 5: +38 | f = f5 +39 | else: +40 | f = PossiblyNotCallable() +41 | # error: [too-many-positional-arguments] +42 | x = f(3) +``` + +# Diagnostics + +``` +error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 + --> src/mdtest_snippet.py:42:11 + | +40 | f = PossiblyNotCallable() +41 | # error: [too-many-positional-arguments] +42 | x = f(3) + | ^ + | +info: `lint:too-many-positional-arguments` is enabled by default + +``` From 5ea3a52c8a069860d4d4460732673b23096ec4cf Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 09:44:00 -0400 Subject: [PATCH 011/487] ty_python_semantic: report all union diagnostic This makes one very simple change: we report all call binding errors from each union variant. This does result in duplicate-seeming diagnostics. For example, when two union variants are invalid for the same reason. --- .../resources/mdtest/call/constructor.md | 4 + .../resources/mdtest/call/union.md | 6 +- .../mdtest/diagnostics/union_call.md | 7 ++ ...ction_types_-_A_smaller_scale_example.snap | 31 ++++- ...s_-_Cover_non-keyword_related_reasons.snap | 112 +++++++++++++++++- .../ty_python_semantic/src/types/call/bind.rs | 7 +- 6 files changed, 150 insertions(+), 17 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index 5d2c93b6e6ec16..365da90b5e08dc 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -100,8 +100,10 @@ def _(flag: bool) -> None: reveal_type(Foo(1)) # revealed: Foo # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" reveal_type(Foo("1")) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" + # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo()) # revealed: Foo # error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" reveal_type(Foo(1, 2)) # revealed: Foo @@ -231,8 +233,10 @@ def _(flag: bool) -> None: reveal_type(Foo(1)) # revealed: Foo # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" reveal_type(Foo("1")) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" + # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo()) # revealed: Foo # error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" reveal_type(Foo(1, 2)) # revealed: Foo diff --git a/crates/ty_python_semantic/resources/mdtest/call/union.md b/crates/ty_python_semantic/resources/mdtest/call/union.md index 0b773fbb705400..3f891eaed1a83e 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/union.md +++ b/crates/ty_python_semantic/resources/mdtest/call/union.md @@ -56,8 +56,8 @@ def _(flag: bool, flag2: bool): else: def f() -> int: return 1 - # TODO we should mention all non-callable elements of the union # error: [call-non-callable] "Object of type `Literal[1]` is not callable" + # error: [call-non-callable] "Object of type `Literal["foo"]` is not callable" # revealed: Unknown | int reveal_type(f()) ``` @@ -125,8 +125,8 @@ def _(flag: bool): else: f = f2 - # TODO: we should show all errors from the union, not arbitrarily pick one union element # error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1" + # error: [too-many-positional-arguments] "Too many positional arguments to function `f2`: expected 0, got 1" x = f(3) reveal_type(x) # revealed: Unknown ``` @@ -143,8 +143,8 @@ def _(flag: bool): else: f = C() - # TODO: we should either show all union errors here, or prioritize the not-callable error # error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1" + # error: [call-non-callable] "Object of type `C` is not callable" x = f(3) reveal_type(x) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md index 303f72f29c054d..f8dd3b36902e0d 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md @@ -22,6 +22,7 @@ def _(flag: bool): else: f = f2 # error: [too-many-positional-arguments] + # error: [invalid-argument-type] x = f(3) ``` @@ -98,6 +99,12 @@ def _(n: int): else: f = PossiblyNotCallable() # error: [too-many-positional-arguments] + # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`" + # error: [missing-argument] + # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" + # error: [call-non-callable] "Object of type `Literal[5]` is not callable" + # error: [no-matching-overload] + # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" x = f(3) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap index 77cd2624e5590d..092bd1a77b0b2d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap @@ -24,18 +24,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m 10 | else: 11 | f = f2 12 | # error: [too-many-positional-arguments] -13 | x = f(3) +13 | # error: [invalid-argument-type] +14 | x = f(3) ``` # Diagnostics +``` +error: lint:invalid-argument-type: Argument to this function is incorrect + --> src/mdtest_snippet.py:14:11 + | +12 | # error: [too-many-positional-arguments] +13 | # error: [invalid-argument-type] +14 | x = f(3) + | ^ Expected `str`, found `Literal[3]` + | +info: Function defined here + --> src/mdtest_snippet.py:4:5 + | +2 | return 0 +3 | +4 | def f2(name: str) -> int: + | ^^ --------- Parameter declared here +5 | return 0 + | +info: `lint:invalid-argument-type` is enabled by default + +``` + ``` error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 - --> src/mdtest_snippet.py:13:11 + --> src/mdtest_snippet.py:14:11 | -11 | f = f2 12 | # error: [too-many-positional-arguments] -13 | x = f(3) +13 | # error: [invalid-argument-type] +14 | x = f(3) | ^ | info: `lint:too-many-positional-arguments` is enabled by default diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap index 93a36fd8419d14..e66a8226f7912e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap @@ -53,18 +53,120 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m 39 | else: 40 | f = PossiblyNotCallable() 41 | # error: [too-many-positional-arguments] -42 | x = f(3) +42 | # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`" +43 | # error: [missing-argument] +44 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" +45 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) ``` # Diagnostics +``` +error: lint:call-non-callable: Object of type `Literal[5]` is not callable + --> src/mdtest_snippet.py:48:9 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^^^^ + | +info: `lint:call-non-callable` is enabled by default + +``` + +``` +error: lint:call-non-callable: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method) + --> src/mdtest_snippet.py:48:9 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^^^^ + | +info: `lint:call-non-callable` is enabled by default + +``` + +``` +error: lint:missing-argument: No argument provided for required parameter `b` of function `f3` + --> src/mdtest_snippet.py:48:9 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^^^^ + | +info: `lint:missing-argument` is enabled by default + +``` + +``` +error: lint:no-matching-overload: No overload of method wrapper `__get__` of function `f` matches arguments + --> src/mdtest_snippet.py:48:9 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^^^^ + | +info: `lint:no-matching-overload` is enabled by default + +``` + +``` +error: lint:invalid-argument-type: Argument to this function is incorrect + --> src/mdtest_snippet.py:48:11 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^ Expected `str`, found `Literal[3]` + | +info: Function defined here + --> src/mdtest_snippet.py:6:5 + | +4 | return 0 +5 | +6 | def f2(name: str) -> int: + | ^^ --------- Parameter declared here +7 | return 0 + | +info: `lint:invalid-argument-type` is enabled by default + +``` + +``` +error: lint:invalid-argument-type: Argument to this function is incorrect + --> src/mdtest_snippet.py:48:11 + | +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) + | ^ Argument type `Literal[3]` does not satisfy upper bound of type variable `T` + | +info: Type variable defined here + --> src/mdtest_snippet.py:12:8 + | +10 | return 0 +11 | +12 | def f4[T: str](x: T) -> int: + | ^^^^^^ +13 | return 0 + | +info: `lint:invalid-argument-type` is enabled by default + +``` + ``` error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 - --> src/mdtest_snippet.py:42:11 + --> src/mdtest_snippet.py:48:11 | -40 | f = PossiblyNotCallable() -41 | # error: [too-many-positional-arguments] -42 | x = f(3) +46 | # error: [no-matching-overload] +47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +48 | x = f(3) | ^ | info: `lint:too-many-positional-arguments` is enabled by default diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 3e509e233357b4..28803bd90279ab 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -199,11 +199,8 @@ impl<'db> Bindings<'db> { } } - // TODO: We currently only report errors for the first union element. Ideally, we'd report - // an error saying that the union type can't be called, followed by subdiagnostics - // explaining why. - if let Some(first) = self.into_iter().find(|b| b.as_result().is_err()) { - first.report_diagnostics(context, node); + for binding in self { + binding.report_diagnostics(context, node); } } From 346e82b572f1a60effa28c506e80722a50d8e3fe Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 9 May 2025 09:58:18 -0400 Subject: [PATCH 012/487] ty_python_semantic: add union type context to function call type errors This context gets added only when calling a function through a union type. --- crates/ruff_benchmark/benches/ty.rs | 17 ++- .../resources/mdtest/annotations/new_types.md | 2 +- .../resources/mdtest/call/annotation.md | 4 +- .../mdtest/call/callable_instance.md | 4 +- .../resources/mdtest/call/constructor.md | 8 +- .../resources/mdtest/call/function.md | 16 +-- .../resources/mdtest/call/getattr_static.md | 2 +- .../resources/mdtest/call/invalid_syntax.md | 4 +- .../resources/mdtest/call/methods.md | 2 +- .../resources/mdtest/call/subclass_of.md | 2 +- .../resources/mdtest/call/union.md | 2 +- .../resources/mdtest/decorators.md | 2 +- .../mdtest/diagnostics/union_call.md | 4 +- .../doc/public_type_undeclared_symbols.md | 2 +- .../mdtest/generics/legacy/classes.md | 6 +- .../mdtest/generics/pep695/classes.md | 6 +- .../resources/mdtest/generics/scoping.md | 2 +- .../resources/mdtest/properties.md | 4 +- ...cy_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...95_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...lid_argument_type_diagnostics_-_Basic.snap | 2 +- ...t_type_diagnostics_-_Calls_to_methods.snap | 2 +- ...nt_type_diagnostics_-_Different_files.snap | 2 +- ..._diagnostics_-_Different_source_order.snap | 2 +- ...nt_type_diagnostics_-_Many_parameters.snap | 2 +- ...Many_parameters_across_multiple_lines.snap | 2 +- ...eters_with_multiple_invalid_arguments.snap | 6 +- ...hose_type_is_vendored_from_`typeshed`.snap | 2 +- ...gument_types_-_Keyword_only_arguments.snap | 2 +- ..._of_argument_types_-_Mix_of_arguments.snap | 2 +- ...argument_types_-_One_keyword_argument.snap | 2 +- ...y_of_argument_types_-_Only_positional.snap | 2 +- ..._argument_types_-_Synthetic_arguments.snap | 2 +- ...f_argument_types_-_Variadic_arguments.snap | 2 +- ...nt_types_-_Variadic_keyword_arguments.snap | 2 +- ...ction_types_-_A_smaller_scale_example.snap | 12 +- ...iple_variants_but_only_one_is_invalid.snap | 6 +- ...over_keyword_argument_related_reasons.snap | 12 +- ...s_-_Cover_non-keyword_related_reasons.snap | 46 ++++--- .../ty_python_semantic/src/types/call/bind.rs | 129 ++++++++++++++++-- 42 files changed, 235 insertions(+), 101 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index c67a457d640fb1..7a0fbbf8a1694e 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -59,26 +59,37 @@ type KeyDiagnosticFields = ( Severity, ); +// left: [ +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to function `skip_until` is incorrect", Error), +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to function `skip_until` is incorrect", Error), +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to function `skip_until` is incorrect", Error), +// ] +//right: [ +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to this function is incorrect", Error), +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to this function is incorrect", Error), +// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to this function is incorrect", Error), +// ] + static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ ( DiagnosticId::lint("invalid-argument-type"), Some("/src/tomllib/_parser.py"), Some(8224..8254), - "Argument to this function is incorrect", + "Argument to function `skip_until` is incorrect", Severity::Error, ), ( DiagnosticId::lint("invalid-argument-type"), Some("/src/tomllib/_parser.py"), Some(16914..16948), - "Argument to this function is incorrect", + "Argument to function `skip_until` is incorrect", Severity::Error, ), ( DiagnosticId::lint("invalid-argument-type"), Some("/src/tomllib/_parser.py"), Some(17319..17363), - "Argument to this function is incorrect", + "Argument to function `skip_until` is incorrect", Severity::Error, ), ]; diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/new_types.md b/crates/ty_python_semantic/resources/mdtest/annotations/new_types.md index 2fb342aeb4ba7d..5dc14964ccb734 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/new_types.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/new_types.md @@ -12,7 +12,7 @@ X = GenericAlias(type, ()) A = NewType("A", int) # TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased # to be compatible with `type` -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `type`, found `NewType`" +# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `NewType`" B = GenericAlias(A, ()) def _( diff --git a/crates/ty_python_semantic/resources/mdtest/call/annotation.md b/crates/ty_python_semantic/resources/mdtest/call/annotation.md index 709ad8e1a6d7a1..937d4226fc4a93 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/annotation.md +++ b/crates/ty_python_semantic/resources/mdtest/call/annotation.md @@ -9,8 +9,8 @@ def _(c: Callable[[], int]): def _(c: Callable[[int, str], int]): reveal_type(c(1, "a")) # revealed: int - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["a"]`" - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[1]`" + # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["a"]`" + # error: [invalid-argument-type] "Argument is incorrect: Expected `str`, found `Literal[1]`" reveal_type(c("a", 1)) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md b/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md index 9f43e7b621983b..d6e36e061f9087 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md @@ -85,7 +85,7 @@ class C: c = C() -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(c("foo")) # revealed: int ``` @@ -99,7 +99,7 @@ class C: c = C() -# error: 13 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `C`" +# error: 13 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `C`" reveal_type(c()) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index 365da90b5e08dc..e56fb09320104f 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -99,8 +99,8 @@ def _(flag: bool) -> None: def __new__(cls, x: int, y: int = 1): ... reveal_type(Foo(1)) # revealed: Foo - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `int`, found `Literal["1"]`" reveal_type(Foo("1")) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" @@ -232,8 +232,8 @@ def _(flag: bool) -> None: def __init__(self, x: int, y: int = 1): ... reveal_type(Foo(1)) # revealed: Foo - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `int`, found `Literal["1"]`" + # error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `int`, found `Literal["1"]`" reveal_type(Foo("1")) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" diff --git a/crates/ty_python_semantic/resources/mdtest/call/function.md b/crates/ty_python_semantic/resources/mdtest/call/function.md index 5a56f6c58d401a..e6e47710501402 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/function.md +++ b/crates/ty_python_semantic/resources/mdtest/call/function.md @@ -77,7 +77,7 @@ def _(flag: bool): def f(x: int) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f("foo")) # revealed: int ``` @@ -87,7 +87,7 @@ reveal_type(f("foo")) # revealed: int def f(x: int, /) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f("foo")) # revealed: int ``` @@ -97,7 +97,7 @@ reveal_type(f("foo")) # revealed: int def f(*args: int) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f("foo")) # revealed: int ``` @@ -107,7 +107,7 @@ reveal_type(f("foo")) # revealed: int def f(x: int) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f(x="foo")) # revealed: int ``` @@ -117,7 +117,7 @@ reveal_type(f(x="foo")) # revealed: int def f(*, x: int) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f(x="foo")) # revealed: int ``` @@ -127,7 +127,7 @@ reveal_type(f(x="foo")) # revealed: int def f(**kwargs: int) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" reveal_type(f(x="foo")) # revealed: int ``` @@ -137,8 +137,8 @@ reveal_type(f(x="foo")) # revealed: int def f(x: int = 1, y: str = "foo") -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[2]`" -# error: 20 [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["bar"]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[2]`" +# error: 20 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["bar"]`" reveal_type(f(y=2, x="bar")) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/getattr_static.md b/crates/ty_python_semantic/resources/mdtest/call/getattr_static.md index 79b0c821d6403b..fc54aefd00c754 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/getattr_static.md +++ b/crates/ty_python_semantic/resources/mdtest/call/getattr_static.md @@ -115,7 +115,7 @@ inspect.getattr_static() # error: [missing-argument] "No argument provided for required parameter `attr`" inspect.getattr_static(C()) -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[1]`" +# error: [invalid-argument-type] "Argument to function `getattr_static` is incorrect: Expected `str`, found `Literal[1]`" inspect.getattr_static(C(), 1) # error: [too-many-positional-arguments] "Too many positional arguments to function `getattr_static`: expected 3, got 4" diff --git a/crates/ty_python_semantic/resources/mdtest/call/invalid_syntax.md b/crates/ty_python_semantic/resources/mdtest/call/invalid_syntax.md index 81b933d7f517a4..3d88ff2ebf1a16 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/invalid_syntax.md +++ b/crates/ty_python_semantic/resources/mdtest/call/invalid_syntax.md @@ -24,7 +24,7 @@ to the valid order: def f(**kw: int, x: str) -> int: return 1 -# error: 15 [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[1]`" +# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[1]`" reveal_type(f(1)) # revealed: int ``` @@ -38,7 +38,7 @@ def f(x: int = 1, y: str) -> int: return 1 reveal_type(f(y="foo")) # revealed: int -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["foo"]`" +# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`" # error: [missing-argument] "No argument provided for required parameter `y` of function `f`" reveal_type(f("foo")) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index c1b366749befa0..e6fde2e2617e09 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -350,7 +350,7 @@ class D: # This function is wrongly annotated, it should be `type[D]` instead of `D` pass -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `D`, found ``" +# error: [invalid-argument-type] "Argument to bound method `f` is incorrect: Expected `D`, found ``" D.f() ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md b/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md index 696d578d845678..49b09670983238 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md +++ b/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md @@ -20,7 +20,7 @@ class C: def _(subclass_of_c: type[C]): reveal_type(subclass_of_c(1)) # revealed: C - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["a"]`" + # error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `int`, found `Literal["a"]`" reveal_type(subclass_of_c("a")) # revealed: C # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(subclass_of_c()) # revealed: C diff --git a/crates/ty_python_semantic/resources/mdtest/call/union.md b/crates/ty_python_semantic/resources/mdtest/call/union.md index 3f891eaed1a83e..5edbdb29819b61 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/union.md +++ b/crates/ty_python_semantic/resources/mdtest/call/union.md @@ -94,7 +94,7 @@ def _(flag: bool): else: f = f2 - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`" + # error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`" x = f(3) reveal_type(x) # revealed: int | str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/decorators.md b/crates/ty_python_semantic/resources/mdtest/decorators.md index 2c947bd734be17..f92eca800360e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/decorators.md +++ b/crates/ty_python_semantic/resources/mdtest/decorators.md @@ -207,7 +207,7 @@ first argument: def wrong_signature(f: int) -> str: return "a" -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `def f(x) -> Unknown`" +# error: [invalid-argument-type] "Argument to function `wrong_signature` is incorrect: Expected `int`, found `def f(x) -> Unknown`" @wrong_signature def f(x): ... diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md index f8dd3b36902e0d..e009c137a3fc68 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md @@ -99,9 +99,9 @@ def _(n: int): else: f = PossiblyNotCallable() # error: [too-many-positional-arguments] - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`" + # error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`" # error: [missing-argument] - # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" + # error: [invalid-argument-type] "Argument to function `f4` is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" # error: [call-non-callable] "Object of type `Literal[5]` is not callable" # error: [no-matching-overload] # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" diff --git a/crates/ty_python_semantic/resources/mdtest/doc/public_type_undeclared_symbols.md b/crates/ty_python_semantic/resources/mdtest/doc/public_type_undeclared_symbols.md index 72676dfbebf63e..50e73bef0f9287 100644 --- a/crates/ty_python_semantic/resources/mdtest/doc/public_type_undeclared_symbols.md +++ b/crates/ty_python_semantic/resources/mdtest/doc/public_type_undeclared_symbols.md @@ -42,7 +42,7 @@ def f(w: Wrapper) -> None: v: int | None = w.value # This function call is incorrect, because `w.value` could be `None`. We therefore emit the following - # error: "Argument to this function is incorrect: Expected `int`, found `Unknown | None`" + # error: "Argument to function `accepts_int` is incorrect: Expected `int`, found `Unknown | None`" c = accepts_int(w.value) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 2ddf11d0d17da3..e101384ce6a4f1 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -123,11 +123,11 @@ reveal_type(Bounded[int]()) # revealed: Bounded[int] reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass] # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `str`" +# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `str`" reveal_type(Bounded[str]()) # revealed: Unknown # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `int | str`" +# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `int | str`" reveal_type(Bounded[int | str]()) # revealed: Unknown reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int] @@ -156,7 +156,7 @@ reveal_type(Constrained[str]()) # revealed: Constrained[str] reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str] # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int | str`, found `object`" +# error: [invalid-argument-type] "Argument to class `Constrained` is incorrect: Expected `int | str`, found `object`" reveal_type(Constrained[object]()) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index b85d0f8960c9d1..b342137e7ffa89 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -98,11 +98,11 @@ reveal_type(Bounded[int]()) # revealed: Bounded[int] reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass] # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `str`" +# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `str`" reveal_type(Bounded[str]()) # revealed: Unknown # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `int | str`" +# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `int | str`" reveal_type(Bounded[int | str]()) # revealed: Unknown reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int] @@ -129,7 +129,7 @@ reveal_type(Constrained[str]()) # revealed: Constrained[str] reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str] # TODO: update this diagnostic to talk about type parameters and specializations -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int | str`, found `object`" +# error: [invalid-argument-type] "Argument to class `Constrained` is incorrect: Expected `int | str`, found `object`" reveal_type(Constrained[object]()) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index 0300cadc87d9af..4376f1db0d6977 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -84,7 +84,7 @@ class C[T]: c: C[int] = C[int]() c.m1(1) c.m2(1) -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal["string"]`" +# error: [invalid-argument-type] "Argument to bound method `m2` is incorrect: Expected `int`, found `Literal["string"]`" c.m2("string") ``` diff --git a/crates/ty_python_semantic/resources/mdtest/properties.md b/crates/ty_python_semantic/resources/mdtest/properties.md index 2a24f2cd5f0f6a..aff91ead1bc5a0 100644 --- a/crates/ty_python_semantic/resources/mdtest/properties.md +++ b/crates/ty_python_semantic/resources/mdtest/properties.md @@ -146,7 +146,7 @@ class C: @property def attr(self) -> int: return 1 - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `(Any, Any, /) -> None`, found `def attr(self) -> None`" + # error: [invalid-argument-type] "Argument to bound method `setter` is incorrect: Expected `(Any, Any, /) -> None`, found `def attr(self) -> None`" @attr.setter def attr(self) -> None: pass @@ -156,7 +156,7 @@ class C: ```py class C: - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `((Any, /) -> Any) | None`, found `def attr(self, x: int) -> int`" + # error: [invalid-argument-type] "Argument to class `property` is incorrect: Expected `((Any, /) -> Any) | None`, found `def attr(self, x: int) -> int`" @property def attr(self, x: int) -> int: return 1 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap index cac47eee3e7539..c5db7e159d2121 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap @@ -68,7 +68,7 @@ info[revealed-type]: Revealed type ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f` is incorrect --> src/mdtest_snippet.py:12:15 | 10 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap index a934f8206d57a1..c205f38f3f1c79 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap @@ -83,7 +83,7 @@ info[revealed-type]: Revealed type ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f` is incorrect --> src/mdtest_snippet.py:13:15 | 11 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap index 2900a6671dc927..87178f460d5ee9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap @@ -65,7 +65,7 @@ info[revealed-type]: Revealed type ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f` is incorrect --> src/mdtest_snippet.py:9:15 | 7 | reveal_type(f(True)) # revealed: Literal[True] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap index 57eb90a95f7fb8..a5f3b93c2d9ea7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap @@ -80,7 +80,7 @@ info[revealed-type]: Revealed type ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f` is incorrect --> src/mdtest_snippet.py:10:15 | 8 | reveal_type(f(None)) # revealed: None diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap index b6d8d86d2574e3..8cebbee51ecc38 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:5 | 2 | return x * x diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap index 1be0e562f17746..8665bb6c745501 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to bound method `square` is incorrect --> src/mdtest_snippet.py:6:10 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap index 9293cf184e5740..e5b7be00b9d2fb 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -27,7 +27,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:3:13 | 1 | import package diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap index 29c466d3c31d06..a673f81a1296ad 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:2:9 | 1 | def bar(): diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap index 7de8796083c325..f0b5a683cbdaad 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap index f558144b07b61a..e55a5c9dcfe0ca 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -25,7 +25,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:8:8 | 6 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap index 023fc78df1ec90..22fa78f1731a71 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:7:5 | 5 | # error: [invalid-argument-type] @@ -44,7 +44,7 @@ info: `invalid-argument-type` is enabled by default ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:7:10 | 5 | # error: [invalid-argument-type] @@ -64,7 +64,7 @@ info: `invalid-argument-type` is enabled by default ``` ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:7:15 | 5 | # error: [invalid-argument-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap index 1e1bdd1f173f96..bb31cfe7e92172 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `loads` is incorrect --> src/mdtest_snippet.py:3:12 | 1 | import json diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap index 2b8fba2de42556..60f4937c29e011 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap index de4b2e16c282a4..e7779170d0990b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap index fb6d69d999c1cd..7f93af33e2733b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:11 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap index e222277fa9d6e9..2839c5399bee1b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:8 | 2 | return x * y * z diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap index e9d144d63014fa..125d0b09d773b3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to bound method `__call__` is incorrect --> src/mdtest_snippet.py:6:3 | 5 | c = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap index 7e18db62f5a54c..dc4b6b73a50d56 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:14 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap index 2b36d6436368f7..da318f03e652ba 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -21,7 +21,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argu # Diagnostics ``` -error[invalid-argument-type]: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `foo` is incorrect --> src/mdtest_snippet.py:4:20 | 2 | return len(numbers) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap index 092bd1a77b0b2d..130dca3db651c0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f2` is incorrect --> src/mdtest_snippet.py:14:11 | 12 | # error: [too-many-positional-arguments] @@ -48,12 +48,14 @@ info: Function defined here | ^^ --------- Parameter declared here 5 | return 0 | -info: `lint:invalid-argument-type` is enabled by default +info: Union variant `def f2(name: str) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int)` +info: `invalid-argument-type` is enabled by default ``` ``` -error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 +error[too-many-positional-arguments]: Too many positional arguments to function `f1`: expected 0, got 1 --> src/mdtest_snippet.py:14:11 | 12 | # error: [too-many-positional-arguments] @@ -61,6 +63,8 @@ error: lint:too-many-positional-arguments: Too many positional arguments to func 14 | x = f(3) | ^ | -info: `lint:too-many-positional-arguments` is enabled by default +info: Union variant `def f1() -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int)` +info: `too-many-positional-arguments` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap index 3d27c3172ab9b6..61f3d7c0c585e5 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m # Diagnostics ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f2` is incorrect --> src/mdtest_snippet.py:13:11 | 11 | f = f2 @@ -47,6 +47,8 @@ info: Function defined here | ^^ --------- Parameter declared here 5 | return 0 | -info: `lint:invalid-argument-type` is enabled by default +info: Union variant `def f2(name: str) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1(a: int) -> int) | (def f2(name: str) -> int)` +info: `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap index 9e2b42242770c6..82c3a86de9c8c4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap @@ -31,7 +31,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m # Diagnostics ``` -error: lint:parameter-already-assigned: Multiple values provided for parameter `name` of function `f1` +error[parameter-already-assigned]: Multiple values provided for parameter `name` of function `f1` --> src/mdtest_snippet.py:14:18 | 12 | # error: [parameter-already-assigned] @@ -39,12 +39,14 @@ error: lint:parameter-already-assigned: Multiple values provided for parameter ` 14 | y = f("foo", name="bar", unknown="quux") | ^^^^^^^^^^ | -info: `lint:parameter-already-assigned` is enabled by default +info: Union variant `def f1(name: str) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1(name: str) -> int) | (def any(*args, **kwargs) -> int)` +info: `parameter-already-assigned` is enabled by default ``` ``` -error: lint:unknown-argument: Argument `unknown` does not match any known parameter of function `f1` +error[unknown-argument]: Argument `unknown` does not match any known parameter of function `f1` --> src/mdtest_snippet.py:14:30 | 12 | # error: [parameter-already-assigned] @@ -52,6 +54,8 @@ error: lint:unknown-argument: Argument `unknown` does not match any known parame 14 | y = f("foo", name="bar", unknown="quux") | ^^^^^^^^^^^^^^ | -info: `lint:unknown-argument` is enabled by default +info: Union variant `def f1(name: str) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1(name: str) -> int) | (def any(*args, **kwargs) -> int)` +info: `unknown-argument` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap index e66a8226f7912e..80b8aa20191813 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap @@ -53,9 +53,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m 39 | else: 40 | f = PossiblyNotCallable() 41 | # error: [too-many-positional-arguments] -42 | # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[3]`" +42 | # error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`" 43 | # error: [missing-argument] -44 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" +44 | # error: [invalid-argument-type] "Argument to function `f4` is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`" 45 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 46 | # error: [no-matching-overload] 47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" @@ -65,7 +65,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m # Diagnostics ``` -error: lint:call-non-callable: Object of type `Literal[5]` is not callable +error[call-non-callable]: Object of type `Literal[5]` is not callable --> src/mdtest_snippet.py:48:9 | 46 | # error: [no-matching-overload] @@ -73,12 +73,14 @@ error: lint:call-non-callable: Object of type `Literal[5]` is not callable 48 | x = f(3) | ^^^^ | -info: `lint:call-non-callable` is enabled by default +info: Union variant `Literal[5]` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `call-non-callable` is enabled by default ``` ``` -error: lint:call-non-callable: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method) +error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method) --> src/mdtest_snippet.py:48:9 | 46 | # error: [no-matching-overload] @@ -86,12 +88,14 @@ error: lint:call-non-callable: Object of type `PossiblyNotCallable` is not calla 48 | x = f(3) | ^^^^ | -info: `lint:call-non-callable` is enabled by default +info: Union variant `PossiblyNotCallable` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `call-non-callable` is enabled by default ``` ``` -error: lint:missing-argument: No argument provided for required parameter `b` of function `f3` +error[missing-argument]: No argument provided for required parameter `b` of function `f3` --> src/mdtest_snippet.py:48:9 | 46 | # error: [no-matching-overload] @@ -99,12 +103,14 @@ error: lint:missing-argument: No argument provided for required parameter `b` of 48 | x = f(3) | ^^^^ | -info: `lint:missing-argument` is enabled by default +info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `missing-argument` is enabled by default ``` ``` -error: lint:no-matching-overload: No overload of method wrapper `__get__` of function `f` matches arguments +error[no-matching-overload]: No overload of method wrapper `__get__` of function `f` matches arguments --> src/mdtest_snippet.py:48:9 | 46 | # error: [no-matching-overload] @@ -112,12 +118,14 @@ error: lint:no-matching-overload: No overload of method wrapper `__get__` of fun 48 | x = f(3) | ^^^^ | -info: `lint:no-matching-overload` is enabled by default +info: Union variant `` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `no-matching-overload` is enabled by default ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f2` is incorrect --> src/mdtest_snippet.py:48:11 | 46 | # error: [no-matching-overload] @@ -134,12 +142,14 @@ info: Function defined here | ^^ --------- Parameter declared here 7 | return 0 | -info: `lint:invalid-argument-type` is enabled by default +info: Union variant `def f2(name: str) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `invalid-argument-type` is enabled by default ``` ``` -error: lint:invalid-argument-type: Argument to this function is incorrect +error[invalid-argument-type]: Argument to function `f4` is incorrect --> src/mdtest_snippet.py:48:11 | 46 | # error: [no-matching-overload] @@ -156,12 +166,14 @@ info: Type variable defined here | ^^^^^^ 13 | return 0 | -info: `lint:invalid-argument-type` is enabled by default +info: Union variant `def f4(x: T) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `invalid-argument-type` is enabled by default ``` ``` -error: lint:too-many-positional-arguments: Too many positional arguments to function `f1`: expected 0, got 1 +error[too-many-positional-arguments]: Too many positional arguments to function `f1`: expected 0, got 1 --> src/mdtest_snippet.py:48:11 | 46 | # error: [no-matching-overload] @@ -169,6 +181,8 @@ error: lint:too-many-positional-arguments: Too many positional arguments to func 48 | x = f(3) | ^ | -info: `lint:too-many-positional-arguments` is enabled by default +info: Union variant `def f1() -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` +info: `too-many-positional-arguments` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 28803bd90279ab..d20c5e2faaf2da 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -24,7 +24,7 @@ use crate::types::{ FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, TupleType, UnionType, WrapperDescriptorKind, }; -use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; /// Binding information for a possible union of callables. At a call site, the arguments must be @@ -199,8 +199,19 @@ impl<'db> Bindings<'db> { } } + // If this is not a union, then report a diagnostic for any + // errors as normal. + if let Some(binding) = self.single_element() { + binding.report_diagnostics(context, node, None); + return; + } + for binding in self { - binding.report_diagnostics(context, node); + let union_diag = UnionDiagnostic { + callable_type: self.callable_type(), + binding, + }; + binding.report_diagnostics(context, node, Some(&union_diag)); } } @@ -1043,23 +1054,34 @@ impl<'db> CallableBinding<'db> { Type::unknown() } - fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { + fn report_diagnostics( + &self, + context: &InferContext<'db>, + node: ast::AnyNodeRef, + union_diag: Option<&UnionDiagnostic<'_, '_>>, + ) { if !self.is_callable() { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Object of type `{}` is not callable", self.callable_type.display(context.db()), )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } return; } if self.dunder_call_is_possibly_unbound { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Object of type `{}` is not callable (possibly unbound `__call__` method)", self.callable_type.display(context.db()), )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } return; } @@ -1067,7 +1089,7 @@ impl<'db> CallableBinding<'db> { let callable_description = CallableDescription::new(context.db(), self.callable_type); if self.overloads.len() > 1 { if let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "No overload{} matches arguments", if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") @@ -1075,6 +1097,9 @@ impl<'db> CallableBinding<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } return; } @@ -1086,6 +1111,7 @@ impl<'db> CallableBinding<'db> { node, self.signature_type, callable_description.as_ref(), + union_diag, ); } } @@ -1385,9 +1411,10 @@ impl<'db> Binding<'db> { node: ast::AnyNodeRef, callable_ty: Type<'db>, callable_description: Option<&CallableDescription>, + union_diag: Option<&UnionDiagnostic<'_, '_>>, ) { for error in &self.errors { - error.report_diagnostic(context, node, callable_ty, callable_description); + error.report_diagnostic(context, node, callable_ty, callable_description, union_diag); } } @@ -1539,12 +1566,13 @@ pub(crate) enum BindingError<'db> { } impl<'db> BindingError<'db> { - pub(super) fn report_diagnostic( + fn report_diagnostic( &self, context: &InferContext<'db>, node: ast::AnyNodeRef, callable_ty: Type<'db>, callable_description: Option<&CallableDescription>, + union_diag: Option<&UnionDiagnostic<'_, '_>>, ) { match self { Self::InvalidArgumentType { @@ -1561,7 +1589,14 @@ impl<'db> BindingError<'db> { let provided_ty_display = provided_ty.display(context.db()); let expected_ty_display = expected_ty.display(context.db()); - let mut diag = builder.into_diagnostic("Argument to this function is incorrect"); + let mut diag = builder.into_diagnostic(format_args!( + "Argument{} is incorrect", + if let Some(CallableDescription { kind, name }) = callable_description { + format!(" to {kind} `{name}`") + } else { + String::new() + } + )); diag.set_primary_message(format_args!( "Expected `{expected_ty_display}`, found `{provided_ty_display}`" )); @@ -1575,6 +1610,9 @@ impl<'db> BindingError<'db> { ); diag.sub(sub); } + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } Self::TooManyPositionalArguments { @@ -1584,7 +1622,7 @@ impl<'db> BindingError<'db> { } => { let node = Self::get_node(node, *first_excess_argument_index); if let Some(builder) = context.report_lint(&TOO_MANY_POSITIONAL_ARGUMENTS, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Too many positional arguments{}: expected \ {expected_positional_count}, got {provided_positional_count}", if let Some(CallableDescription { kind, name }) = callable_description { @@ -1593,13 +1631,16 @@ impl<'db> BindingError<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } } Self::MissingArguments { parameters } => { if let Some(builder) = context.report_lint(&MISSING_ARGUMENT, node) { let s = if parameters.0.len() == 1 { "" } else { "s" }; - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "No argument{s} provided for required parameter{s} {parameters}{}", if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") @@ -1607,6 +1648,9 @@ impl<'db> BindingError<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } } @@ -1616,7 +1660,7 @@ impl<'db> BindingError<'db> { } => { let node = Self::get_node(node, *argument_index); if let Some(builder) = context.report_lint(&UNKNOWN_ARGUMENT, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Argument `{argument_name}` does not match any known parameter{}", if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") @@ -1624,6 +1668,9 @@ impl<'db> BindingError<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } } @@ -1633,7 +1680,7 @@ impl<'db> BindingError<'db> { } => { let node = Self::get_node(node, *argument_index); if let Some(builder) = context.report_lint(&PARAMETER_ALREADY_ASSIGNED, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Multiple values provided for parameter {parameter}{}", if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") @@ -1641,6 +1688,9 @@ impl<'db> BindingError<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } } @@ -1657,7 +1707,14 @@ impl<'db> BindingError<'db> { let argument_type = error.argument_type(); let argument_ty_display = argument_type.display(context.db()); - let mut diag = builder.into_diagnostic("Argument to this function is incorrect"); + let mut diag = builder.into_diagnostic(format_args!( + "Argument{} is incorrect", + if let Some(CallableDescription { kind, name }) = callable_description { + format!(" to {kind} `{name}`") + } else { + String::new() + } + )); diag.set_primary_message(format_args!( "Argument type `{argument_ty_display}` does not satisfy {} of type variable `{}`", match error { @@ -1671,12 +1728,15 @@ impl<'db> BindingError<'db> { let mut sub = SubDiagnostic::new(Severity::Info, "Type variable defined here"); sub.annotate(Annotation::primary(typevar_range.into())); diag.sub(sub); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } Self::InternalCallError(reason) => { let node = Self::get_node(node, None); if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) { - builder.into_diagnostic(format_args!( + let mut diag = builder.into_diagnostic(format_args!( "Call{} failed: {reason}", if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") @@ -1684,6 +1744,9 @@ impl<'db> BindingError<'db> { String::new() } )); + if let Some(union_diag) = union_diag { + union_diag.add_union_context(context.db(), &mut diag); + } } } } @@ -1708,3 +1771,39 @@ impl<'db> BindingError<'db> { } } } + +/// Contains additional context for union specific diagnostics. +/// +/// This is used when a function call is inconsistent with one or more variants +/// of a union. This can be used to attach sub-diagnostics that clarify that +/// the error is part of a union. +struct UnionDiagnostic<'b, 'db> { + /// The type of the union. + callable_type: Type<'db>, + /// The specific binding that failed. + binding: &'b CallableBinding<'db>, +} + +impl UnionDiagnostic<'_, '_> { + /// Adds context about any relevant union function types to the given + /// diagnostic. + fn add_union_context(&self, db: &'_ dyn Db, diag: &mut Diagnostic) { + let sub = SubDiagnostic::new( + Severity::Info, + format_args!( + "Union variant `{callable_ty}` is incompatible with this call site", + callable_ty = self.binding.callable_type.display(db), + ), + ); + diag.sub(sub); + + let sub = SubDiagnostic::new( + Severity::Info, + format_args!( + "Attempted to call union type `{}`", + self.callable_type.display(db) + ), + ); + diag.sub(sub); + } +} From 7a48477c6797ae817c33994e5cddb7f8375118bb Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 9 May 2025 13:42:36 -0400 Subject: [PATCH 013/487] [ty] Add a warning about pre-release status to the CLI (#17983) Summary -- This was suggested on Discord, I hope this is roughly what we had in mind. I took the message from the ty README, but I'm more than happy to update it. Otherwise I just tried to mimic the appearance of the `ruff analyze graph` warning (although I'm realizing now the whole text is bold for ruff). Test Plan -- New warnings in the CLI tests. I thought this might be undesirable but it looks like uv did the same thing (https://github.com/astral-sh/uv/pull/6166). ![image](https://github.com/user-attachments/assets/e5e56a49-02ab-4c5f-9c38-716e4008d6e6) --- crates/ty/src/lib.rs | 5 +++++ crates/ty/tests/cli.rs | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 87fd6178620eee..584f9acb1b0708 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -62,6 +62,11 @@ fn run_check(args: CheckCommand) -> anyhow::Result { countme::enable(verbosity.is_trace()); let _guard = setup_tracing(verbosity)?; + tracing::warn!( + "ty is pre-release software and not ready for production use. \ + Expect to encounter bugs, missing features, and fatal errors.", + ); + tracing::debug!("Version: {}", version::version()); // The base path to which all CLI arguments are relative to. diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 3ea03222287373..1bebb41eba5240 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -24,6 +24,7 @@ fn test_run_in_sub_directory() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } @@ -45,6 +46,7 @@ fn test_include_hidden_files_by_default() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } @@ -60,6 +62,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { All checks passed! ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. WARN No python files found under the given path(s) "); @@ -78,6 +81,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Test that we can set to false via config file @@ -96,6 +100,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Ensure CLI takes precedence @@ -114,6 +119,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) } @@ -156,6 +162,7 @@ fn config_override_python_version() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r" @@ -165,6 +172,7 @@ fn config_override_python_version() -> anyhow::Result<()> { All checks passed! ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -208,6 +216,7 @@ fn config_override_python_platform() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); assert_cmd_snapshot!(case.command().arg("--python-platform").arg("all"), @r" @@ -226,6 +235,7 @@ fn config_override_python_platform() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -291,6 +301,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")).arg("--extra-search-path").arg("../libs"), @r" @@ -300,6 +311,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { All checks passed! ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -353,6 +365,7 @@ fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Re All checks passed! ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -402,6 +415,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); case.write_file( @@ -430,6 +444,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -493,6 +508,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { Found 3 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); assert_cmd_snapshot!( @@ -533,6 +549,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -584,6 +601,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); assert_cmd_snapshot!( @@ -613,6 +631,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -648,6 +667,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) @@ -667,6 +687,7 @@ fn cli_unknown_rules() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -691,6 +712,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -721,6 +743,7 @@ fn exit_code_only_info() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -751,6 +774,7 @@ fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -775,6 +799,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -808,6 +833,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -848,6 +874,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -888,6 +915,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -928,6 +956,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -992,6 +1021,7 @@ fn user_configuration() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -1036,6 +1066,7 @@ fn user_configuration() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -1102,6 +1133,7 @@ fn check_specific_paths() -> anyhow::Result<()> { Found 3 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -1134,6 +1166,7 @@ fn check_specific_paths() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. " ); @@ -1164,6 +1197,7 @@ fn check_non_existing_path() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. WARN No python files found under the given path(s) " ); @@ -1190,6 +1224,7 @@ fn concise_diagnostics() -> anyhow::Result<()> { Found 2 diagnostics ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -1223,6 +1258,7 @@ fn concise_revealed_type() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "#); Ok(()) @@ -1259,6 +1295,7 @@ fn can_handle_large_binop_expressions() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -1305,6 +1342,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Use default (which should be latest supported) @@ -1333,6 +1371,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { All checks passed! ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -1358,6 +1397,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); // Short flag @@ -1376,6 +1416,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -1408,6 +1449,7 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) @@ -1431,6 +1473,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { Found 1 diagnostic ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); Ok(()) From b4a1ebdfe390d3cd170645c3516fa3fdc7445994 Mon Sep 17 00:00:00 2001 From: Max Mynter <32773644+maxmynter@users.noreply.github.com> Date: Fri, 9 May 2025 20:54:05 +0200 Subject: [PATCH 014/487] [semantic-syntax-tests] `IrrefutableCasePattern`, `SingleStarredAssignment`, `WriteToDebug`, `InvalidExpression` (#17748) Re: #17526 ## Summary Add integration test for semantic syntax for `IrrefutableCasePattern`, `SingleStarredAssignment`, `WriteToDebug`, and `InvalidExpression`. ## Notes - Following @ntBre's suggestion, I will keep the test coming in batches like this over the next few days in separate PRs to keep the review load per PR manageable while also not spamming too many. - I did not add a test for `del __debug__` which is one of the examples in `crates/ruff_python_parser/src/semantic_errors.rs:1051`. For python version `<= 3.8` there is no error and for `>=3.9` the error is not `WriteToDebug` but `SyntaxError: cannot delete __debug__ on Python 3.9 (syntax was removed in 3.9)`. - The `blacken-docs` bypass is necessary because otherwise the test does not pass pre-commit checks; but we want to check for this faulty syntax. ## Test Plan This is a test. --- crates/ruff_linter/src/linter.rs | 89 ++++++++++++++++++ ...sion_walrus_in_return_annotation_3.12.snap | 8 ++ ...ression_yield_from_in_base_class_3.12.snap | 9 ++ ...d_expression_yield_in_type_alias_3.12.snap | 8 ++ ...d_expression_yield_in_type_param_3.12.snap | 8 ++ ...irrefutable_case_pattern_capture_3.10.snap | 11 +++ ...rrefutable_case_pattern_wildcard_3.10.snap | 11 +++ ...gnment_single_starred_assignment_3.10.snap | 8 ++ ...rror_WriteToDebug_write_to_debug_3.10.snap | 8 ++ ..._write_to_debug_class_type_param_3.12.snap | 9 ++ ...write_to_debug_in_function_param_3.10.snap | 9 ++ .../diagnostics/semantic_syntax_errors.md | 94 ++++++++++++++++++- 12 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index dc8fccdf9b2f9f..7427b75d9c5cb7 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -1116,6 +1116,95 @@ mod tests { PythonVersion::PY310, "InvalidStarExpression" )] + #[test_case( + "irrefutable_case_pattern_wildcard", + " + match value: + case _: + pass + case 1: + pass + ", + PythonVersion::PY310, + "IrrefutableCasePattern" + )] + #[test_case( + "irrefutable_case_pattern_capture", + " + match value: + case irrefutable: + pass + case 1: + pass + ", + PythonVersion::PY310, + "IrrefutableCasePattern" + )] + #[test_case( + "single_starred_assignment", + "*a = [1, 2, 3, 4]", + PythonVersion::PY310, + "SingleStarredAssignment" + )] + #[test_case( + "write_to_debug", + " + __debug__ = False + ", + PythonVersion::PY310, + "WriteToDebug" + )] + #[test_case( + "write_to_debug_in_function_param", + " + def process(__debug__): + pass + ", + PythonVersion::PY310, + "WriteToDebug" + )] + #[test_case( + "write_to_debug_class_type_param", + " + class Generic[__debug__]: + pass + ", + PythonVersion::PY312, + "WriteToDebug" + )] + #[test_case( + "invalid_expression_yield_in_type_param", + " + type X[T: (yield 1)] = int + ", + PythonVersion::PY312, + "InvalidExpression" + )] + #[test_case( + "invalid_expression_yield_in_type_alias", + " + type Y = (yield 1) + ", + PythonVersion::PY312, + "InvalidExpression" + )] + #[test_case( + "invalid_expression_walrus_in_return_annotation", + " + def f[T](x: int) -> (y := 3): return x + ", + PythonVersion::PY312, + "InvalidExpression" + )] + #[test_case( + "invalid_expression_yield_from_in_base_class", + " + class C[T]((yield from [object])): + pass + ", + PythonVersion::PY312, + "InvalidExpression" + )] fn test_semantic_errors( name: &str, contents: &str, diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap new file mode 100644 index 00000000000000..a09dda153fa57a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:22: SyntaxError: named expression cannot be used within a generic definition + | +2 | def f[T](x: int) -> (y := 3): return x + | ^^^^^^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap new file mode 100644 index 00000000000000..6f71b926a1c69d --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:13: SyntaxError: yield expression cannot be used within a generic definition + | +2 | class C[T]((yield from [object])): + | ^^^^^^^^^^^^^^^^^^^ +3 | pass + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap new file mode 100644 index 00000000000000..39c220e6a15343 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:11: SyntaxError: yield expression cannot be used within a type alias + | +2 | type Y = (yield 1) + | ^^^^^^^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap new file mode 100644 index 00000000000000..9f0c12aaacc428 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:12: SyntaxError: yield expression cannot be used within a TypeVar bound + | +2 | type X[T: (yield 1)] = int + | ^^^^^^^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap new file mode 100644 index 00000000000000..502f151558ad4d --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap @@ -0,0 +1,11 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:3:10: SyntaxError: name capture `irrefutable` makes remaining patterns unreachable + | +2 | match value: +3 | case irrefutable: + | ^^^^^^^^^^^ +4 | pass +5 | case 1: + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap new file mode 100644 index 00000000000000..cc54b4776b6bb3 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap @@ -0,0 +1,11 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:3:10: SyntaxError: wildcard makes remaining patterns unreachable + | +2 | match value: +3 | case _: + | ^ +4 | pass +5 | case 1: + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap new file mode 100644 index 00000000000000..ecc8f955d8fffe --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:1:1: SyntaxError: starred assignment target must be in a list or tuple + | +1 | *a = [1, 2, 3, 4] + | ^^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap new file mode 100644 index 00000000000000..baebbdf41f1231 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:1: SyntaxError: cannot assign to `__debug__` + | +2 | __debug__ = False + | ^^^^^^^^^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap new file mode 100644 index 00000000000000..44139dc6cf0cb0 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:15: SyntaxError: cannot assign to `__debug__` + | +2 | class Generic[__debug__]: + | ^^^^^^^^^ +3 | pass + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap new file mode 100644 index 00000000000000..935ad560543ce2 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:2:13: SyntaxError: cannot assign to `__debug__` + | +2 | def process(__debug__): + | ^^^^^^^^^ +3 | pass + | diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md index 25fadb9905300c..08813572f8989c 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md @@ -228,11 +228,102 @@ for x in *range(10): pass ``` +## Irrefutable case pattern + +Irrefutable patterns, i.e. wildcard or capture patterns, must be the last case in a match statement. +Following case statements are unreachable. + +```toml +[environment] +python-version = "3.12" +``` + +```py +value = 5 + +match value: + # error: [invalid-syntax] "wildcard makes remaining patterns unreachable" + case _: # Irrefutable wildcard pattern + pass + case 5: + pass + +match value: + # error: [invalid-syntax] "name capture `variable` makes remaining patterns unreachable" + case variable: # Irrefutable capture pattern + pass + case 10: + pass +``` + +## Single starred assignment + +Starred assignment targets cannot appear by themselves. They must be in the context of a list or +tuple. + +```py +# error: [invalid-syntax] "starred assignment target must be in a list or tuple" +*a = [1, 2, 3, 4] +``` + +## Write to debug + +The special Python builtin `__debug__` should not be modified. + +```toml +[environment] +python-version = "3.12" +``` + +```py +# error: [invalid-syntax] "cannot assign to `__debug__`" +__debug__ = False + +# error: [invalid-syntax] "cannot assign to `__debug__`" +def process(__debug__): + pass + +# error: [invalid-syntax] "cannot assign to `__debug__`" +class Generic[__debug__]: + pass +``` + +## Invalid expression + +Certain expressions like `yield` or inlined walrus assignments are not valid in specific contexts. + +```toml +[environment] +python-version = "3.12" +``` + +```py +# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions" +# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound" +# error: [invalid-syntax] "`yield` statement outside of a function" +type X[T: (yield 1)] = int + +# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions" +# error: [invalid-syntax] "yield expression cannot be used within a type alias" +# error: [invalid-syntax] "`yield` statement outside of a function" +type Y = (yield 1) + +# error: [invalid-type-form] "Named expressions are not allowed in type expressions" +# error: [invalid-syntax] "named expression cannot be used within a generic definition" +def f[T](x: int) -> (y := 3): + return x + +# error: [invalid-syntax] "`yield from` statement outside of a function" +# error: [invalid-syntax] "yield expression cannot be used within a generic definition" +class C[T]((yield from [object])): + pass +``` + ## `await` outside async function This error includes `await`, `async for`, `async with`, and `async` comprehensions. -```python +```py async def elements(n): yield n @@ -245,7 +336,6 @@ def _(): # error: [invalid-syntax] "`async with` outside of an asynchronous function" async with elements(1) as x: ... - # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)" # error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function" [x async for x in elements(1)] ``` From 882a1a702e8b1432ade2aa30d889dc5eccb7d770 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Fri, 9 May 2025 20:57:14 +0200 Subject: [PATCH 015/487] Fix typos (#17988) Fix typos --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Co-authored-by: Brent Westbrook --- crates/ruff_formatter/src/lib.rs | 4 +- .../test/fixtures/perflint/PERF401.py | 4 +- ...__perflint__tests__PERF401_PERF401.py.snap | 4 +- ...t__tests__preview__PERF401_PERF401.py.snap | 12 +- crates/ruff_python_ast/src/nodes.rs | 2 +- crates/ruff_python_ast/src/visitor.rs | 2 +- .../src/visitor/transformer.rs | 2 +- .../parenthesized/tuple_starred_expr.py | 2 +- ..._parenthesized__tuple_starred_expr.py.snap | 336 +++++++++--------- .../resources/mdtest/import/star.md | 2 +- .../mdtest/statically_known_branches.md | 2 +- .../src/semantic_index/builder.rs | 2 +- .../src/types/call/arguments.rs | 4 +- .../ty_python_semantic/src/types/context.rs | 2 +- crates/ty_python_semantic/src/types/infer.rs | 2 +- 15 files changed, 191 insertions(+), 191 deletions(-) diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index b675813a579b25..5b25630fc00a58 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -92,7 +92,7 @@ impl std::fmt::Display for IndentStyle { } } -/// The visual width of a indentation. +/// The visual width of an indentation. /// /// Determines the visual width of a tab character (`\t`) and the number of /// spaces per indent when using [`IndentStyle::Space`]. @@ -207,7 +207,7 @@ pub trait FormatOptions { /// What's the max width of a line. Defaults to 80. fn line_width(&self) -> LineWidth; - /// Derives the print options from the these format options + /// Derives the print options from these format options fn as_print_options(&self) -> PrinterOptions; } diff --git a/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py b/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py index b0e6ff0759ef69..263f0ff6c6eb97 100644 --- a/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py +++ b/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py @@ -144,14 +144,14 @@ def f(): def f(): # make sure that `tmp` is not deleted - tmp = 1; result = [] # commment should be protected + tmp = 1; result = [] # comment should be protected for i in range(10): result.append(i + 1) # PERF401 def f(): # make sure that `tmp` is not deleted - result = []; tmp = 1 # commment should be protected + result = []; tmp = 1 # comment should be protected for i in range(10): result.append(i + 1) # PERF401 diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap index c2de08bb3fd309..92d7d6df85ba40 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap @@ -93,7 +93,7 @@ PERF401.py:142:9: PERF401 Use a list comprehension to create a transformed list PERF401.py:149:9: PERF401 Use a list comprehension to create a transformed list | -147 | tmp = 1; result = [] # commment should be protected +147 | tmp = 1; result = [] # comment should be protected 148 | for i in range(10): 149 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 @@ -102,7 +102,7 @@ PERF401.py:149:9: PERF401 Use a list comprehension to create a transformed list PERF401.py:156:9: PERF401 Use a list comprehension to create a transformed list | -154 | result = []; tmp = 1 # commment should be protected +154 | result = []; tmp = 1 # comment should be protected 155 | for i in range(10): 156 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap index 47100714a67ee4..35f6a6bd927d47 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap @@ -223,7 +223,7 @@ PERF401.py:142:9: PERF401 [*] Use a list comprehension to create a transformed l PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed list | -147 | tmp = 1; result = [] # commment should be protected +147 | tmp = 1; result = [] # comment should be protected 148 | for i in range(10): 149 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 @@ -234,10 +234,10 @@ PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed l 144 144 | 145 145 | def f(): 146 146 | # make sure that `tmp` is not deleted -147 |- tmp = 1; result = [] # commment should be protected +147 |- tmp = 1; result = [] # comment should be protected 148 |- for i in range(10): 149 |- result.append(i + 1) # PERF401 - 147 |+ tmp = 1 # commment should be protected + 147 |+ tmp = 1 # comment should be protected 148 |+ result = [i + 1 for i in range(10)] # PERF401 150 149 | 151 150 | @@ -245,7 +245,7 @@ PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed l PERF401.py:156:9: PERF401 [*] Use a list comprehension to create a transformed list | -154 | result = []; tmp = 1 # commment should be protected +154 | result = []; tmp = 1 # comment should be protected 155 | for i in range(10): 156 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 @@ -256,10 +256,10 @@ PERF401.py:156:9: PERF401 [*] Use a list comprehension to create a transformed l 151 151 | 152 152 | def f(): 153 153 | # make sure that `tmp` is not deleted -154 |- result = []; tmp = 1 # commment should be protected +154 |- result = []; tmp = 1 # comment should be protected 155 |- for i in range(10): 156 |- result.append(i + 1) # PERF401 - 154 |+ tmp = 1 # commment should be protected + 154 |+ tmp = 1 # comment should be protected 155 |+ result = [i + 1 for i in range(10)] # PERF401 157 156 | 158 157 | diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 282fa2a78f062e..5284736f687b12 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -2844,7 +2844,7 @@ impl Arguments { self.find_argument(name, position).map(ArgOrKeyword::value) } - /// Return the the argument with the given name or at the given position, or `None` if no such + /// Return the argument with the given name or at the given position, or `None` if no such /// argument exists. Used to retrieve arguments that can be provided _either_ as keyword or /// positional arguments. pub fn find_argument(&self, name: &str, position: usize) -> Option { diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index 6d9bb92cef9686..8bb2013efc07b2 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -590,7 +590,7 @@ pub fn walk_except_handler<'a, V: Visitor<'a> + ?Sized>( } pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) { - // Note that the there might be keywords before the last arg, e.g. in + // Note that there might be keywords before the last arg, e.g. in // f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then // `keywords`. See also [Arguments::arguments_source_order`]. for arg in &*arguments.args { diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index dad507c53e34f9..68962941d61d98 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -576,7 +576,7 @@ pub fn walk_except_handler( } pub fn walk_arguments(visitor: &V, arguments: &mut Arguments) { - // Note that the there might be keywords before the last arg, e.g. in + // Note that there might be keywords before the last arg, e.g. in // f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then // `keywords`. See also [Arguments::arguments_source_order`]. for arg in &mut *arguments.args { diff --git a/crates/ruff_python_parser/resources/invalid/expressions/parenthesized/tuple_starred_expr.py b/crates/ruff_python_parser/resources/invalid/expressions/parenthesized/tuple_starred_expr.py index 1a87159f0aaae4..e3998d7ccec050 100644 --- a/crates/ruff_python_parser/resources/invalid/expressions/parenthesized/tuple_starred_expr.py +++ b/crates/ruff_python_parser/resources/invalid/expressions/parenthesized/tuple_starred_expr.py @@ -1,5 +1,5 @@ # For tuple expression, the minimum binding power of star expression is bitwise or. -# Test the first and any other element as the there are two separate calls. +# Test the first and any other element as there are two separate calls. (*x in y, z, *x in y) (*not x, z, *not x) diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap index 4b75ea06edf185..a81a6db84be53a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap @@ -7,24 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { - range: 0..536, + range: 0..532, body: [ Expr( StmtExpr { - range: 161..182, + range: 157..178, value: Tuple( ExprTuple { - range: 161..182, + range: 157..178, elts: [ Starred( ExprStarred { - range: 162..169, + range: 158..165, value: Compare( ExprCompare { - range: 163..169, + range: 159..165, left: Name( ExprName { - range: 163..164, + range: 159..160, id: Name("x"), ctx: Load, }, @@ -35,7 +35,7 @@ Module( comparators: [ Name( ExprName { - range: 168..169, + range: 164..165, id: Name("y"), ctx: Load, }, @@ -48,20 +48,20 @@ Module( ), Name( ExprName { - range: 171..172, + range: 167..168, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 174..181, + range: 170..177, value: Compare( ExprCompare { - range: 175..181, + range: 171..177, left: Name( ExprName { - range: 175..176, + range: 171..172, id: Name("x"), ctx: Load, }, @@ -72,7 +72,7 @@ Module( comparators: [ Name( ExprName { - range: 180..181, + range: 176..177, id: Name("y"), ctx: Load, }, @@ -92,21 +92,21 @@ Module( ), Expr( StmtExpr { - range: 183..202, + range: 179..198, value: Tuple( ExprTuple { - range: 183..202, + range: 179..198, elts: [ Starred( ExprStarred { - range: 184..190, + range: 180..186, value: UnaryOp( ExprUnaryOp { - range: 185..190, + range: 181..186, op: Not, operand: Name( ExprName { - range: 189..190, + range: 185..186, id: Name("x"), ctx: Load, }, @@ -118,21 +118,21 @@ Module( ), Name( ExprName { - range: 192..193, + range: 188..189, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 195..201, + range: 191..197, value: UnaryOp( ExprUnaryOp { - range: 196..201, + range: 192..197, op: Not, operand: Name( ExprName { - range: 200..201, + range: 196..197, id: Name("x"), ctx: Load, }, @@ -151,29 +151,29 @@ Module( ), Expr( StmtExpr { - range: 203..226, + range: 199..222, value: Tuple( ExprTuple { - range: 203..226, + range: 199..222, elts: [ Starred( ExprStarred { - range: 204..212, + range: 200..208, value: BoolOp( ExprBoolOp { - range: 205..212, + range: 201..208, op: And, values: [ Name( ExprName { - range: 205..206, + range: 201..202, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 211..212, + range: 207..208, id: Name("y"), ctx: Load, }, @@ -186,29 +186,29 @@ Module( ), Name( ExprName { - range: 214..215, + range: 210..211, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 217..225, + range: 213..221, value: BoolOp( ExprBoolOp { - range: 218..225, + range: 214..221, op: And, values: [ Name( ExprName { - range: 218..219, + range: 214..215, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 224..225, + range: 220..221, id: Name("y"), ctx: Load, }, @@ -228,29 +228,29 @@ Module( ), Expr( StmtExpr { - range: 227..248, + range: 223..244, value: Tuple( ExprTuple { - range: 227..248, + range: 223..244, elts: [ Starred( ExprStarred { - range: 228..235, + range: 224..231, value: BoolOp( ExprBoolOp { - range: 229..235, + range: 225..231, op: Or, values: [ Name( ExprName { - range: 229..230, + range: 225..226, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 234..235, + range: 230..231, id: Name("y"), ctx: Load, }, @@ -263,29 +263,29 @@ Module( ), Name( ExprName { - range: 237..238, + range: 233..234, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 240..247, + range: 236..243, value: BoolOp( ExprBoolOp { - range: 241..247, + range: 237..243, op: Or, values: [ Name( ExprName { - range: 241..242, + range: 237..238, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 246..247, + range: 242..243, id: Name("y"), ctx: Load, }, @@ -305,33 +305,33 @@ Module( ), Expr( StmtExpr { - range: 249..290, + range: 245..286, value: Tuple( ExprTuple { - range: 249..290, + range: 245..286, elts: [ Starred( ExprStarred { - range: 250..267, + range: 246..263, value: If( ExprIf { - range: 251..267, + range: 247..263, test: BooleanLiteral( ExprBooleanLiteral { - range: 256..260, + range: 252..256, value: true, }, ), body: Name( ExprName { - range: 251..252, + range: 247..248, id: Name("x"), ctx: Load, }, ), orelse: Name( ExprName { - range: 266..267, + range: 262..263, id: Name("y"), ctx: Load, }, @@ -343,33 +343,33 @@ Module( ), Name( ExprName { - range: 269..270, + range: 265..266, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 272..289, + range: 268..285, value: If( ExprIf { - range: 273..289, + range: 269..285, test: BooleanLiteral( ExprBooleanLiteral { - range: 278..282, + range: 274..278, value: true, }, ), body: Name( ExprName { - range: 273..274, + range: 269..270, id: Name("x"), ctx: Load, }, ), orelse: Name( ExprName { - range: 288..289, + range: 284..285, id: Name("y"), ctx: Load, }, @@ -388,29 +388,29 @@ Module( ), Expr( StmtExpr { - range: 291..322, + range: 287..318, value: Tuple( ExprTuple { - range: 291..322, + range: 287..318, elts: [ Starred( ExprStarred { - range: 292..304, + range: 288..300, value: Lambda( ExprLambda { - range: 293..304, + range: 289..300, parameters: Some( Parameters { - range: 300..301, + range: 296..297, posonlyargs: [], args: [ ParameterWithDefault { - range: 300..301, + range: 296..297, parameter: Parameter { - range: 300..301, + range: 296..297, name: Identifier { id: Name("x"), - range: 300..301, + range: 296..297, }, annotation: None, }, @@ -424,7 +424,7 @@ Module( ), body: Name( ExprName { - range: 303..304, + range: 299..300, id: Name("x"), ctx: Load, }, @@ -436,29 +436,29 @@ Module( ), Name( ExprName { - range: 306..307, + range: 302..303, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 309..321, + range: 305..317, value: Lambda( ExprLambda { - range: 310..321, + range: 306..317, parameters: Some( Parameters { - range: 317..318, + range: 313..314, posonlyargs: [], args: [ ParameterWithDefault { - range: 317..318, + range: 313..314, parameter: Parameter { - range: 317..318, + range: 313..314, name: Identifier { id: Name("x"), - range: 317..318, + range: 313..314, }, annotation: None, }, @@ -472,7 +472,7 @@ Module( ), body: Name( ExprName { - range: 320..321, + range: 316..317, id: Name("x"), ctx: Load, }, @@ -491,20 +491,20 @@ Module( ), Expr( StmtExpr { - range: 323..344, + range: 319..340, value: Tuple( ExprTuple { - range: 323..344, + range: 319..340, elts: [ Named( ExprNamed { - range: 324..331, + range: 320..327, target: Starred( ExprStarred { - range: 324..326, + range: 320..322, value: Name( ExprName { - range: 325..326, + range: 321..322, id: Name("x"), ctx: Store, }, @@ -514,7 +514,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { - range: 330..331, + range: 326..327, value: Int( 2, ), @@ -524,20 +524,20 @@ Module( ), Name( ExprName { - range: 333..334, + range: 329..330, id: Name("z"), ctx: Load, }, ), Named( ExprNamed { - range: 336..343, + range: 332..339, target: Starred( ExprStarred { - range: 336..338, + range: 332..334, value: Name( ExprName { - range: 337..338, + range: 333..334, id: Name("x"), ctx: Store, }, @@ -547,7 +547,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { - range: 342..343, + range: 338..339, value: Int( 2, ), @@ -564,20 +564,20 @@ Module( ), Expr( StmtExpr { - range: 367..386, + range: 363..382, value: Tuple( ExprTuple { - range: 367..386, + range: 363..382, elts: [ Starred( ExprStarred { - range: 367..374, + range: 363..370, value: Compare( ExprCompare { - range: 368..374, + range: 364..370, left: Name( ExprName { - range: 368..369, + range: 364..365, id: Name("x"), ctx: Load, }, @@ -588,7 +588,7 @@ Module( comparators: [ Name( ExprName { - range: 373..374, + range: 369..370, id: Name("y"), ctx: Load, }, @@ -601,20 +601,20 @@ Module( ), Name( ExprName { - range: 376..377, + range: 372..373, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 379..386, + range: 375..382, value: Compare( ExprCompare { - range: 380..386, + range: 376..382, left: Name( ExprName { - range: 380..381, + range: 376..377, id: Name("x"), ctx: Load, }, @@ -625,7 +625,7 @@ Module( comparators: [ Name( ExprName { - range: 385..386, + range: 381..382, id: Name("y"), ctx: Load, }, @@ -645,21 +645,21 @@ Module( ), Expr( StmtExpr { - range: 387..404, + range: 383..400, value: Tuple( ExprTuple { - range: 387..404, + range: 383..400, elts: [ Starred( ExprStarred { - range: 387..393, + range: 383..389, value: UnaryOp( ExprUnaryOp { - range: 388..393, + range: 384..389, op: Not, operand: Name( ExprName { - range: 392..393, + range: 388..389, id: Name("x"), ctx: Load, }, @@ -671,21 +671,21 @@ Module( ), Name( ExprName { - range: 395..396, + range: 391..392, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 398..404, + range: 394..400, value: UnaryOp( ExprUnaryOp { - range: 399..404, + range: 395..400, op: Not, operand: Name( ExprName { - range: 403..404, + range: 399..400, id: Name("x"), ctx: Load, }, @@ -704,29 +704,29 @@ Module( ), Expr( StmtExpr { - range: 405..426, + range: 401..422, value: Tuple( ExprTuple { - range: 405..426, + range: 401..422, elts: [ Starred( ExprStarred { - range: 405..413, + range: 401..409, value: BoolOp( ExprBoolOp { - range: 406..413, + range: 402..409, op: And, values: [ Name( ExprName { - range: 406..407, + range: 402..403, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 412..413, + range: 408..409, id: Name("y"), ctx: Load, }, @@ -739,29 +739,29 @@ Module( ), Name( ExprName { - range: 415..416, + range: 411..412, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 418..426, + range: 414..422, value: BoolOp( ExprBoolOp { - range: 419..426, + range: 415..422, op: And, values: [ Name( ExprName { - range: 419..420, + range: 415..416, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 425..426, + range: 421..422, id: Name("y"), ctx: Load, }, @@ -781,29 +781,29 @@ Module( ), Expr( StmtExpr { - range: 427..446, + range: 423..442, value: Tuple( ExprTuple { - range: 427..446, + range: 423..442, elts: [ Starred( ExprStarred { - range: 427..434, + range: 423..430, value: BoolOp( ExprBoolOp { - range: 428..434, + range: 424..430, op: Or, values: [ Name( ExprName { - range: 428..429, + range: 424..425, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 433..434, + range: 429..430, id: Name("y"), ctx: Load, }, @@ -816,29 +816,29 @@ Module( ), Name( ExprName { - range: 436..437, + range: 432..433, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 439..446, + range: 435..442, value: BoolOp( ExprBoolOp { - range: 440..446, + range: 436..442, op: Or, values: [ Name( ExprName { - range: 440..441, + range: 436..437, id: Name("x"), ctx: Load, }, ), Name( ExprName { - range: 445..446, + range: 441..442, id: Name("y"), ctx: Load, }, @@ -858,33 +858,33 @@ Module( ), Expr( StmtExpr { - range: 447..486, + range: 443..482, value: Tuple( ExprTuple { - range: 447..486, + range: 443..482, elts: [ Starred( ExprStarred { - range: 447..464, + range: 443..460, value: If( ExprIf { - range: 448..464, + range: 444..460, test: BooleanLiteral( ExprBooleanLiteral { - range: 453..457, + range: 449..453, value: true, }, ), body: Name( ExprName { - range: 448..449, + range: 444..445, id: Name("x"), ctx: Load, }, ), orelse: Name( ExprName { - range: 463..464, + range: 459..460, id: Name("y"), ctx: Load, }, @@ -896,33 +896,33 @@ Module( ), Name( ExprName { - range: 466..467, + range: 462..463, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 469..486, + range: 465..482, value: If( ExprIf { - range: 470..486, + range: 466..482, test: BooleanLiteral( ExprBooleanLiteral { - range: 475..479, + range: 471..475, value: true, }, ), body: Name( ExprName { - range: 470..471, + range: 466..467, id: Name("x"), ctx: Load, }, ), orelse: Name( ExprName { - range: 485..486, + range: 481..482, id: Name("y"), ctx: Load, }, @@ -941,29 +941,29 @@ Module( ), Expr( StmtExpr { - range: 487..516, + range: 483..512, value: Tuple( ExprTuple { - range: 487..516, + range: 483..512, elts: [ Starred( ExprStarred { - range: 487..499, + range: 483..495, value: Lambda( ExprLambda { - range: 488..499, + range: 484..495, parameters: Some( Parameters { - range: 495..496, + range: 491..492, posonlyargs: [], args: [ ParameterWithDefault { - range: 495..496, + range: 491..492, parameter: Parameter { - range: 495..496, + range: 491..492, name: Identifier { id: Name("x"), - range: 495..496, + range: 491..492, }, annotation: None, }, @@ -977,7 +977,7 @@ Module( ), body: Name( ExprName { - range: 498..499, + range: 494..495, id: Name("x"), ctx: Load, }, @@ -989,29 +989,29 @@ Module( ), Name( ExprName { - range: 501..502, + range: 497..498, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 504..516, + range: 500..512, value: Lambda( ExprLambda { - range: 505..516, + range: 501..512, parameters: Some( Parameters { - range: 512..513, + range: 508..509, posonlyargs: [], args: [ ParameterWithDefault { - range: 512..513, + range: 508..509, parameter: Parameter { - range: 512..513, + range: 508..509, name: Identifier { id: Name("x"), - range: 512..513, + range: 508..509, }, annotation: None, }, @@ -1025,7 +1025,7 @@ Module( ), body: Name( ExprName { - range: 515..516, + range: 511..512, id: Name("x"), ctx: Load, }, @@ -1044,13 +1044,13 @@ Module( ), Expr( StmtExpr { - range: 517..519, + range: 513..515, value: Starred( ExprStarred { - range: 517..519, + range: 513..515, value: Name( ExprName { - range: 518..519, + range: 514..515, id: Name("x"), ctx: Load, }, @@ -1062,14 +1062,14 @@ Module( ), Expr( StmtExpr { - range: 523..536, + range: 519..532, value: Tuple( ExprTuple { - range: 523..536, + range: 519..532, elts: [ NumberLiteral( ExprNumberLiteral { - range: 523..524, + range: 519..520, value: Int( 2, ), @@ -1077,17 +1077,17 @@ Module( ), Name( ExprName { - range: 526..527, + range: 522..523, id: Name("z"), ctx: Load, }, ), Starred( ExprStarred { - range: 529..531, + range: 525..527, value: Name( ExprName { - range: 530..531, + range: 526..527, id: Name("x"), ctx: Load, }, @@ -1097,7 +1097,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { - range: 535..536, + range: 531..532, value: Int( 2, ), @@ -1117,7 +1117,7 @@ Module( ## Errors | -2 | # Test the first and any other element as the there are two separate calls. +2 | # Test the first and any other element as there are two separate calls. 3 | 4 | (*x in y, z, *x in y) | ^^^^^^ Syntax Error: Comparison expression cannot be used here @@ -1127,7 +1127,7 @@ Module( | -2 | # Test the first and any other element as the there are two separate calls. +2 | # Test the first and any other element as there are two separate calls. 3 | 4 | (*x in y, z, *x in y) | ^^^^^^ Syntax Error: Comparison expression cannot be used here diff --git a/crates/ty_python_semantic/resources/mdtest/import/star.md b/crates/ty_python_semantic/resources/mdtest/import/star.md index 4894074f2ddbe4..a20f7259ef3fff 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/star.md +++ b/crates/ty_python_semantic/resources/mdtest/import/star.md @@ -1389,7 +1389,7 @@ X: bool = True ```py def f(): - # TODO: we should emit a syntax errror here (tracked by https://github.com/astral-sh/ruff/issues/11934) + # TODO: we should emit a syntax error here (tracked by https://github.com/astral-sh/ruff/issues/17412) from exporter import * # error: [unresolved-reference] diff --git a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md index c1d979d955553c..63db73d206eb84 100644 --- a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md @@ -163,7 +163,7 @@ other ## Based on type inference -For the the rest of this test suite, we will mostly use `True` and `False` literals to indicate +For the rest of this test suite, we will mostly use `True` and `False` literals to indicate statically known conditions, but here, we show that the results are truly based on type inference, not some special handling of specific conditions in semantic index building. We use two modules to demonstrate this, since semantic index building is inherently single-module: diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index 40fd38b5668f05..45b9fa01b6384a 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -1824,7 +1824,7 @@ where // Save the state immediately *after* visiting the `try` block // but *before* we prepare for visiting the `except` block(s). // - // We will revert to this state prior to visiting the the `else` block, + // We will revert to this state prior to visiting the `else` block, // as there necessarily must have been 0 `except` blocks executed // if we hit the `else` block. let post_try_block_state = self.flow_snapshot(); diff --git a/crates/ty_python_semantic/src/types/call/arguments.rs b/crates/ty_python_semantic/src/types/call/arguments.rs index 23b80528249af8..392c4ee5142683 100644 --- a/crates/ty_python_semantic/src/types/call/arguments.rs +++ b/crates/ty_python_semantic/src/types/call/arguments.rs @@ -9,7 +9,7 @@ pub(crate) struct CallArguments<'a>(Vec>); impl<'a> CallArguments<'a> { /// Prepend an optional extra synthetic argument (for a `self` or `cls` parameter) to the front - /// of this argument list. (If `bound_self` is none, we return the the argument list + /// of this argument list. (If `bound_self` is none, we return the argument list /// unmodified.) pub(crate) fn with_self(&self, bound_self: Option>) -> Cow { if bound_self.is_some() { @@ -87,7 +87,7 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> { } /// Prepend an optional extra synthetic argument (for a `self` or `cls` parameter) to the front - /// of this argument list. (If `bound_self` is none, we return the the argument list + /// of this argument list. (If `bound_self` is none, we return the argument list /// unmodified.) pub(crate) fn with_self(&self, bound_self: Option>) -> Cow { if let Some(bound_self) = bound_self { diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index 454f8ce7d700a2..dd287fa8eaf271 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -492,7 +492,7 @@ impl std::ops::DerefMut for DiagnosticGuard<'_, '_> { /// /// # Panics /// -/// This panics when the the underlying diagnostic lacks a primary +/// This panics when the underlying diagnostic lacks a primary /// annotation, or if it has one and its file doesn't match the file /// being type checked. impl Drop for DiagnosticGuard<'_, '_> { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index e071c60306f4e9..2e3fa5b7211e66 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -8277,7 +8277,7 @@ impl<'db> TypeInferenceBuilder<'db> { let callable_type = Type::Callable(callable_type); // `Signature` / `Parameters` are not a `Type` variant, so we're storing - // the outer callable type on the these expressions instead. + // the outer callable type on these expressions instead. self.store_expression_type(arguments_slice, callable_type); if let Some(first_argument) = first_argument { self.store_expression_type(first_argument, callable_type); From fd1eb3d801e0bcde005c342609b99fd9337ecbbe Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 9 May 2025 13:29:13 -0700 Subject: [PATCH 016/487] add test for typing_extensions.Self (#17995) Using `typing_extensions.Self` already worked, but we were lacking a test for it. --- .../resources/mdtest/annotations/self.md | 17 +++++++++++++++++ .../src/types/known_instance.rs | 3 +-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 87c73f57177777..5ffbe1ae8b7ffb 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -50,6 +50,23 @@ class Outer: return self ``` +## typing_extensions + +```toml +[environment] +python-version = "3.10" +``` + +```py +from typing_extensions import Self + +class C: + def method(self: Self) -> Self: + return self + +reveal_type(C().method()) # revealed: C +``` + ## Class Methods ```toml diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index a9966fc2a4ca34..801e03cae2afbb 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -86,8 +86,7 @@ pub enum KnownInstanceType<'db> { /// The symbol `typing.Callable` /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) Callable, - - /// The symbol `typing.Self` + /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self`) TypingSelf, // Various special forms, special aliases and type qualifiers that we don't yet understand From 40fd52dde0ddf0b95a3433163f51560b4827ab75 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 9 May 2025 16:59:00 -0400 Subject: [PATCH 017/487] Exclude broken symlinks from `meson-python` ecosystem check (#17993) Summary -- This should resolve the formatter ecosystem errors we've been seeing lately. https://github.com/mesonbuild/meson-python/pull/728 added the links, which I think are intentionally broken for testing purposes. Test Plan -- Ecosystem check on this PR --- python/ruff-ecosystem/ruff_ecosystem/defaults.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/ruff-ecosystem/ruff_ecosystem/defaults.py b/python/ruff-ecosystem/ruff_ecosystem/defaults.py index 1fd66f4afd3f74..3c07c571ee0d33 100644 --- a/python/ruff-ecosystem/ruff_ecosystem/defaults.py +++ b/python/ruff-ecosystem/ruff_ecosystem/defaults.py @@ -4,6 +4,7 @@ from ruff_ecosystem.projects import ( CheckOptions, + FormatOptions, Project, Repository, ) @@ -134,7 +135,12 @@ Project(repo=Repository(owner="wntrblm", name="nox", ref="main")), Project(repo=Repository(owner="pytest-dev", name="pytest", ref="main")), Project(repo=Repository(owner="encode", name="httpx", ref="master")), - Project(repo=Repository(owner="mesonbuild", name="meson-python", ref="main")), + Project( + repo=Repository(owner="mesonbuild", name="meson-python", ref="main"), + format_options=FormatOptions( + exclude="tests/packages/symlinks/baz.py,tests/packages/symlinks/qux.py" + ), + ), Project(repo=Repository(owner="pdm-project", name="pdm", ref="main")), Project(repo=Repository(owner="astropy", name="astropy", ref="main")), ] From 235b74a3105b2732086340857113c565d96d2c96 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Bodas <55339528+abhijeetbodas2001@users.noreply.github.com> Date: Sat, 10 May 2025 14:16:08 +0530 Subject: [PATCH 018/487] [ty] Add more tests for `NamedTuple`s (#17975) ## Summary Add more tests and TODOs for `NamedTuple` support, based on the typing spec: https://typing.python.org/en/latest/spec/namedtuples.html ## Test Plan This PR adds new tests. --- .../resources/mdtest/named_tuple.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 845ef71cdf40e0..d31ba81b3b529d 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -37,6 +37,10 @@ Person(3, "Eve", 99, "extra") # error: [invalid-argument-type] Person(id="3", name="Eve") + +# TODO: over-writing NamedTuple fields should be an error +alice.id = 42 +bob.age = None ``` Alternative functional syntax: @@ -52,6 +56,19 @@ reveal_type(alice2.id) # revealed: @Todo(functional `NamedTuple` syntax) reveal_type(alice2.name) # revealed: @Todo(functional `NamedTuple` syntax) ``` +### Definition + +TODO: Fields without default values should come before fields with. + +```py +from typing import NamedTuple + +class Location(NamedTuple): + altitude: float = 0.0 + latitude: float # this should be an error + longitude: float +``` + ### Multiple Inheritance Multiple inheritance is not supported for `NamedTuple` classes: @@ -89,6 +106,20 @@ reveal_type(alice.level) # revealed: int alice = SuperUser(1, "Alice", 3) ``` +TODO: If any fields added by the subclass conflict with those in the base class, that should be +flagged. + +```py +from typing import NamedTuple + +class User(NamedTuple): + id: int + name: str + +class SuperUser(User): + id: int # this should be an error +``` + ### Generic named tuples ```toml From cd1d906ffa93f127f9c99e6078a045cd925b43a9 Mon Sep 17 00:00:00 2001 From: David Peter Date: Sat, 10 May 2025 11:59:25 +0200 Subject: [PATCH 019/487] [ty] Silence false positives for PEP-695 ParamSpec annotations (#18001) ## Summary Suppress false positives for uses of PEP-695 `ParamSpec` in `Callable` annotations: ```py from typing_extensions import Callable def f[**P](c: Callable[P, int]): pass ``` addresses a comment here: https://github.com/astral-sh/ty/issues/157#issuecomment-2859284721 ## Test Plan Adapted Markdown tests --- .../resources/mdtest/annotations/callable.md | 8 ++++--- crates/ty_python_semantic/src/types.rs | 16 ++++++++++++-- .../src/types/class_base.rs | 2 +- crates/ty_python_semantic/src/types/infer.rs | 21 +++++++++++++++---- .../src/types/type_ordering.rs | 3 +++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 1fe2aa65d5b91f..dc27fdb7d4f742 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -249,10 +249,12 @@ Using a `ParamSpec` in a `Callable` annotation: ```py from typing_extensions import Callable -# TODO: Not an error; remove once `ParamSpec` is supported -# error: [invalid-type-form] def _[**P1](c: Callable[P1, int]): - reveal_type(c) # revealed: (...) -> Unknown + reveal_type(P1.args) # revealed: @Todo(ParamSpec) + reveal_type(P1.kwargs) # revealed: @Todo(ParamSpec) + + # TODO: Signature should be (**P1) -> int + reveal_type(c) # revealed: (...) -> int ``` And, using the legacy syntax: diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dcc0932e9aed8e..ed62c97ca2ddd3 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -662,7 +662,7 @@ impl<'db> Type<'db> { pub fn contains_todo(&self, db: &'db dyn Db) -> bool { match self { - Self::Dynamic(DynamicType::Todo(_)) => true, + Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => true, Self::AlwaysFalsy | Self::AlwaysTruthy @@ -703,7 +703,9 @@ impl<'db> Type<'db> { } Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() { - SubclassOfInner::Dynamic(DynamicType::Todo(_)) => true, + SubclassOfInner::Dynamic( + DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec, + ) => true, SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false, SubclassOfInner::Class(_) => false, }, @@ -5502,6 +5504,9 @@ pub enum DynamicType { /// /// This variant should be created with the `todo_type!` macro. Todo(TodoType), + /// A special Todo-variant for PEP-695 `ParamSpec` types. A temporary variant to detect and special- + /// case the handling of these types in `Callable` annotations. + TodoPEP695ParamSpec, } impl std::fmt::Display for DynamicType { @@ -5512,6 +5517,13 @@ impl std::fmt::Display for DynamicType { // `DynamicType::Todo`'s display should be explicit that is not a valid display of // any other type DynamicType::Todo(todo) => write!(f, "@Todo{todo}"), + DynamicType::TodoPEP695ParamSpec => { + if cfg!(debug_assertions) { + f.write_str("@Todo(ParamSpec)") + } else { + f.write_str("@Todo") + } + } } } } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 551f47dafb3ad2..d5b4e440d74cc3 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -76,7 +76,7 @@ impl<'db> ClassBase<'db> { ClassBase::Class(class) => class.name(db), ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", - ClassBase::Dynamic(DynamicType::Todo(_)) => "@Todo", + ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo", ClassBase::Protocol(_) => "Protocol", ClassBase::Generic(_) => "Generic", } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 2e3fa5b7211e66..e13aa08605a17f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2612,7 +2612,7 @@ impl<'db> TypeInferenceBuilder<'db> { default, } = node; self.infer_optional_expression(default.as_deref()); - let pep_695_todo = todo_type!("PEP-695 ParamSpec definition types"); + let pep_695_todo = Type::Dynamic(DynamicType::TodoPEP695ParamSpec); self.add_declaration_with_binding( node.into(), definition, @@ -5797,8 +5797,16 @@ impl<'db> TypeInferenceBuilder<'db> { | (_, any @ Type::Dynamic(DynamicType::Any), _) => Some(any), (unknown @ Type::Dynamic(DynamicType::Unknown), _, _) | (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown), - (todo @ Type::Dynamic(DynamicType::Todo(_)), _, _) - | (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo), + ( + todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec), + _, + _, + ) + | ( + _, + todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec), + _, + ) => Some(todo), (Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never), (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some( @@ -8651,9 +8659,14 @@ impl<'db> TypeInferenceBuilder<'db> { // `Callable[]`. return None; } + ast::Expr::Name(name) + if self.infer_name_load(name) + == Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => + { + return Some(Parameters::todo()); + } _ => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameters) { - // TODO: Check whether `Expr::Name` is a ParamSpec builder.into_diagnostic(format_args!( "The first argument to `Callable` \ must be either a list of types, \ diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 2099badb0d519a..4849c36aa98a38 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -386,5 +386,8 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering #[cfg(not(debug_assertions))] (DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal, + + (DynamicType::TodoPEP695ParamSpec, _) => Ordering::Less, + (_, DynamicType::TodoPEP695ParamSpec) => Ordering::Greater, } } From b765dc48e93c2cb973673da3fa30377886e26ef5 Mon Sep 17 00:00:00 2001 From: Max Mynter <32773644+maxmynter@users.noreply.github.com> Date: Sat, 10 May 2025 12:37:58 +0200 Subject: [PATCH 020/487] Skip S608 for expressionless f-strings (#17999) --- .../resources/test/fixtures/flake8_bandit/S608.py | 3 +++ .../flake8_bandit/rules/hardcoded_sql_expression.rs | 10 +++++++++- ...ter__rules__flake8_bandit__tests__S608_S608.py.snap | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py index 2e96462c847789..447e46dcf25c01 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py @@ -166,3 +166,6 @@ def query54(): foo FROM ({user_input}) raw """ + +# https://github.com/astral-sh/ruff/issues/17967 +query61 = f"SELECT * FROM table" # skip expressionless f-strings diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index f29cd41d30c024..df0effe5e8847d 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -100,7 +100,15 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) { } // f"select * from table where val = {val}" - Expr::FString(f_string) => concatenated_f_string(f_string, checker.locator()), + Expr::FString(f_string) + if f_string + .value + .f_strings() + .any(|fs| fs.elements.iter().any(ast::FStringElement::is_expression)) => + { + concatenated_f_string(f_string, checker.locator()) + } + _ => return, }; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap index bfd23cbc18eb83..2170539c1747ff 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap @@ -601,4 +601,6 @@ S608.py:164:11: S608 Possible SQL injection vector through string-based query co 167 | | FROM ({user_input}) raw 168 | | """ | |___^ S608 +169 | +170 | # https://github.com/astral-sh/ruff/issues/17967 | From 5ecd560c6f70b7f5c854daa9833853b2cd50a779 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 10 May 2025 12:40:40 +0200 Subject: [PATCH 021/487] Link to the rules.md in the ty repository (#17979) --- crates/ty/docs/configuration.md | 2 +- crates/ty_project/src/metadata/options.rs | 2 +- ty.schema.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 896751f8948176..0c135ee02d2555 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -22,7 +22,7 @@ respect-ignore-files = false Configures the enabled rules and their severity. -See [the rules documentation](https://github.com/astral-sh/ruff/blob/main/crates/ty/docs/rules.md) for a list of all available rules. +See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules. Valid severities are: diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index aa3f24dc1ec037..730a7283ef00bf 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -31,7 +31,7 @@ pub struct Options { /// Configures the enabled rules and their severity. /// - /// See [the rules documentation](https://github.com/astral-sh/ruff/blob/main/crates/ty/docs/rules.md) for a list of all available rules. + /// See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules. /// /// Valid severities are: /// diff --git a/ty.schema.json b/ty.schema.json index ac715b050383cd..783d34fb5ac66b 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -22,7 +22,7 @@ ] }, "rules": { - "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://github.com/astral-sh/ruff/blob/main/crates/ty/docs/rules.md) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", + "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", "anyOf": [ { "$ref": "#/definitions/Rules" From 316e406ca46704b308466a82630a10ddff8c9c35 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 10 May 2025 15:17:47 -0500 Subject: [PATCH 022/487] [ty] Add basic support for non-virtual Python environments (#17991) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds basic support for non-virtual Python environments by accepting a directory without a `pyvenv.cfg` which allows existing, subsequent site-packages discovery logic to succeed. We can do better here in the long-term, by adding more eager validation (for error messages) and parsing the Python version from the discovered site-packages directory (which isn't relevant yet, because we don't use the discovered Python version from virtual environments as the default `--python-version` yet either). Related - https://github.com/astral-sh/ty/issues/265 - https://github.com/astral-sh/ty/issues/193 You can review this commit by commit if it makes you happy. I tested this manually; I think refactoring the test setup is going to be a bit more invasive so I'll stack it on top (see https://github.com/astral-sh/ruff/pull/17996). ``` ❯ uv run ty check --python /Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/ -vv example 2025-05-09 12:06:33.685911 DEBUG Version: 0.0.0-alpha.7 (f9c4c8999 2025-05-08) 2025-05-09 12:06:33.685987 DEBUG Architecture: aarch64, OS: macos, case-sensitive: case-insensitive 2025-05-09 12:06:33.686002 DEBUG Searching for a project in '/Users/zb/workspace/ty' 2025-05-09 12:06:33.686123 DEBUG Resolving requires-python constraint: `>=3.8` 2025-05-09 12:06:33.686129 DEBUG Resolved requires-python constraint to: 3.8 2025-05-09 12:06:33.686142 DEBUG Project without `tool.ty` section: '/Users/zb/workspace/ty' 2025-05-09 12:06:33.686147 DEBUG Searching for a user-level configuration at `/Users/zb/.config/ty/ty.toml` 2025-05-09 12:06:33.686156 INFO Defaulting to python-platform `darwin` 2025-05-09 12:06:33.68636 INFO Python version: Python 3.8, platform: darwin 2025-05-09 12:06:33.686375 DEBUG Adding first-party search path '/Users/zb/workspace/ty' 2025-05-09 12:06:33.68638 DEBUG Using vendored stdlib 2025-05-09 12:06:33.686634 DEBUG Discovering site-packages paths from sys-prefix `/Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none` (`--python` argument') 2025-05-09 12:06:33.686667 DEBUG Attempting to parse virtual environment metadata at '/Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/pyvenv.cfg' 2025-05-09 12:06:33.686671 DEBUG Searching for site-packages directory in `sys.prefix` path `/Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none` 2025-05-09 12:06:33.686702 DEBUG Resolved site-packages directories for this environment are: ["/Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/lib/python3.10/site-packages"] 2025-05-09 12:06:33.686706 DEBUG Adding site-packages search path '/Users/zb/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/lib/python3.10/site-packages' ... ❯ uv run ty check --python /tmp -vv example 2025-05-09 15:36:10.819416 DEBUG Version: 0.0.0-alpha.7 (f9c4c8999 2025-05-08) 2025-05-09 15:36:10.819708 DEBUG Architecture: aarch64, OS: macos, case-sensitive: case-insensitive 2025-05-09 15:36:10.820118 DEBUG Searching for a project in '/Users/zb/workspace/ty' 2025-05-09 15:36:10.821652 DEBUG Resolving requires-python constraint: `>=3.8` 2025-05-09 15:36:10.821667 DEBUG Resolved requires-python constraint to: 3.8 2025-05-09 15:36:10.8217 DEBUG Project without `tool.ty` section: '/Users/zb/workspace/ty' 2025-05-09 15:36:10.821888 DEBUG Searching for a user-level configuration at `/Users/zb/.config/ty/ty.toml` 2025-05-09 15:36:10.822072 INFO Defaulting to python-platform `darwin` 2025-05-09 15:36:10.822439 INFO Python version: Python 3.8, platform: darwin 2025-05-09 15:36:10.822773 DEBUG Adding first-party search path '/Users/zb/workspace/ty' 2025-05-09 15:36:10.822929 DEBUG Using vendored stdlib 2025-05-09 15:36:10.829872 DEBUG Discovering site-packages paths from sys-prefix `/tmp` (`--python` argument') 2025-05-09 15:36:10.829911 DEBUG Attempting to parse virtual environment metadata at '/private/tmp/pyvenv.cfg' 2025-05-09 15:36:10.829917 DEBUG Searching for site-packages directory in `sys.prefix` path `/private/tmp` ty failed Cause: Invalid search path settings Cause: Failed to discover the site-packages directory: Failed to search the `lib` directory of the Python installation at `sys.prefix` path `/private/tmp` for `site-packages` ``` --- .../src/module_resolver/resolver.rs | 8 +- .../ty_python_semantic/src/site_packages.rs | 160 +++++++++++++----- 2 files changed, 123 insertions(+), 45 deletions(-) diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index d8d85ea57aa050..0be7d5107e1813 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -14,7 +14,7 @@ use ruff_python_ast::PythonVersion; use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{vendored_typeshed_versions, TypeshedVersions}; -use crate::site_packages::{SitePackagesDiscoveryError, SysPrefixPathOrigin, VirtualEnvironment}; +use crate::site_packages::{PythonEnvironment, SitePackagesDiscoveryError, SysPrefixPathOrigin}; use crate::{Program, PythonPath, SearchPathSettings}; use super::module::{Module, ModuleKind}; @@ -243,8 +243,8 @@ impl SearchPaths { // than the one resolved in the program settings because it indicates // that the `target-version` is incorrectly configured or that the // venv is out of date. - VirtualEnvironment::new(sys_prefix, *origin, system) - .and_then(|venv| venv.site_packages_directories(system))? + PythonEnvironment::new(sys_prefix, *origin, system) + .and_then(|env| env.site_packages_directories(system))? } PythonPath::Discover(root) => { @@ -262,7 +262,7 @@ impl SearchPaths { vec![] }; - match VirtualEnvironment::new( + match PythonEnvironment::new( virtual_env_path.clone(), SysPrefixPathOrigin::LocalVenv, system, diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index a11274378b031e..abc81ef45a5621 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -19,6 +19,47 @@ use ruff_python_ast::PythonVersion; type SitePackagesDiscoveryResult = Result; +#[derive(Debug)] +pub(crate) enum PythonEnvironment { + Virtual(VirtualEnvironment), + System(SystemEnvironment), +} + +impl PythonEnvironment { + pub(crate) fn new( + path: impl AsRef, + origin: SysPrefixPathOrigin, + system: &dyn System, + ) -> SitePackagesDiscoveryResult { + let path = SysPrefixPath::new(path, origin, system)?; + + // Attempt to inspect as a virtual environment first + // TODO(zanieb): Consider avoiding the clone here by checking for `pyvenv.cfg` ahead-of-time + match VirtualEnvironment::new(path.clone(), origin, system) { + Ok(venv) => Ok(Self::Virtual(venv)), + // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment + // + Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(_, _)) => { + Ok(Self::System(SystemEnvironment::new(path))) + } + Err(err) => Err(err), + } + } + + /// Returns the `site-packages` directories for this Python environment. + /// + /// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details. + pub(crate) fn site_packages_directories( + &self, + system: &dyn System, + ) -> SitePackagesDiscoveryResult> { + match self { + Self::Virtual(env) => env.site_packages_directories(system), + Self::System(env) => env.site_packages_directories(system), + } + } +} + /// Abstraction for a Python virtual environment. /// /// Most of this information is derived from the virtual environment's `pyvenv.cfg` file. @@ -26,7 +67,7 @@ type SitePackagesDiscoveryResult = Result; /// depends on the tool that was used to create the virtual environment. #[derive(Debug)] pub(crate) struct VirtualEnvironment { - venv_path: SysPrefixPath, + root_path: SysPrefixPath, base_executable_home_path: PythonHomePath, include_system_site_packages: bool, @@ -43,15 +84,7 @@ pub(crate) struct VirtualEnvironment { impl VirtualEnvironment { pub(crate) fn new( - path: impl AsRef, - origin: SysPrefixPathOrigin, - system: &dyn System, - ) -> SitePackagesDiscoveryResult { - Self::new_impl(path.as_ref(), origin, system) - } - - fn new_impl( - path: &SystemPath, + path: SysPrefixPath, origin: SysPrefixPathOrigin, system: &dyn System, ) -> SitePackagesDiscoveryResult { @@ -59,8 +92,7 @@ impl VirtualEnvironment { index.checked_add(1).and_then(NonZeroUsize::new).unwrap() } - let venv_path = SysPrefixPath::new(path, origin, system)?; - let pyvenv_cfg_path = venv_path.join("pyvenv.cfg"); + let pyvenv_cfg_path = path.join("pyvenv.cfg"); tracing::debug!("Attempting to parse virtual environment metadata at '{pyvenv_cfg_path}'"); let pyvenv_cfg = system @@ -150,7 +182,7 @@ impl VirtualEnvironment { }); let metadata = Self { - venv_path, + root_path: path, base_executable_home_path, include_system_site_packages, version, @@ -162,20 +194,20 @@ impl VirtualEnvironment { /// Return a list of `site-packages` directories that are available from this virtual environment /// - /// See the documentation for `site_packages_dir_from_sys_prefix` for more details. + /// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details. pub(crate) fn site_packages_directories( &self, system: &dyn System, ) -> SitePackagesDiscoveryResult> { let VirtualEnvironment { - venv_path, + root_path, base_executable_home_path, include_system_site_packages, version, } = self; let mut site_packages_directories = vec![site_packages_directory_from_sys_prefix( - venv_path, *version, system, + root_path, *version, system, )?]; if *include_system_site_packages { @@ -199,7 +231,7 @@ impl VirtualEnvironment { "Failed to resolve `sys.prefix` of the system Python installation \ from the `home` value in the `pyvenv.cfg` file at `{}`. \ System site-packages will not be used for module resolution.", - venv_path.join("pyvenv.cfg") + root_path.join("pyvenv.cfg") ); } } @@ -209,12 +241,49 @@ System site-packages will not be used for module resolution.", } } +/// A Python environment that is _not_ a virtual environment. +/// +/// This environment may or may not be one that is managed by the operating system itself, e.g., +/// this captures both Homebrew-installed Python versions and the bundled macOS Python installation. +#[derive(Debug)] +pub(crate) struct SystemEnvironment { + root_path: SysPrefixPath, +} + +impl SystemEnvironment { + /// Create a new system environment from the given path. + /// + /// At this time, there is no eager validation and this is infallible. Instead, validation + /// will occur in [`site_packages_directory_from_sys_prefix`] — which will fail if there is not + /// a Python environment at the given path. + pub(crate) fn new(path: SysPrefixPath) -> Self { + Self { root_path: path } + } + + /// Return a list of `site-packages` directories that are available from this environment. + /// + /// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details. + pub(crate) fn site_packages_directories( + &self, + system: &dyn System, + ) -> SitePackagesDiscoveryResult> { + let SystemEnvironment { root_path } = self; + + let site_packages_directories = vec![site_packages_directory_from_sys_prefix( + root_path, None, system, + )?]; + + tracing::debug!("Resolved site-packages directories for this environment are: {site_packages_directories:?}"); + Ok(site_packages_directories) + } +} + #[derive(Debug, thiserror::Error)] pub(crate) enum SitePackagesDiscoveryError { #[error("Invalid {1}: `{0}` could not be canonicalized")] - VenvDirCanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), + EnvDirCanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), #[error("Invalid {1}: `{0}` does not point to a directory on disk")] - VenvDirIsNotADirectory(SystemPathBuf, SysPrefixPathOrigin), + EnvDirNotDirectory(SystemPathBuf, SysPrefixPathOrigin), #[error("{0} points to a broken venv with no pyvenv.cfg file")] NoPyvenvCfgFile(SysPrefixPathOrigin, #[source] io::Error), #[error("Failed to parse the pyvenv.cfg file at {0} because {1}")] @@ -401,7 +470,7 @@ impl SysPrefixPath { let canonicalized = system .canonicalize_path(unvalidated_path) .map_err(|io_err| { - SitePackagesDiscoveryError::VenvDirCanonicalizationError( + SitePackagesDiscoveryError::EnvDirCanonicalizationError( unvalidated_path.to_path_buf(), origin, io_err, @@ -414,7 +483,7 @@ impl SysPrefixPath { origin, }) .ok_or_else(|| { - SitePackagesDiscoveryError::VenvDirIsNotADirectory( + SitePackagesDiscoveryError::EnvDirNotDirectory( unvalidated_path.to_path_buf(), origin, ) @@ -628,15 +697,19 @@ mod tests { fn test(self) { let venv_path = self.build_mock_venv(); - let venv = VirtualEnvironment::new( + let env = PythonEnvironment::new( venv_path.clone(), SysPrefixPathOrigin::VirtualEnvVar, &self.system, ) .unwrap(); + let PythonEnvironment::Virtual(venv) = &env else { + panic!("Expected a virtual environment; got {env:?}"); + }; + assert_eq!( - venv.venv_path, + venv.root_path, SysPrefixPath { inner: self.system.canonicalize_path(&venv_path).unwrap(), origin: SysPrefixPathOrigin::VirtualEnvVar, @@ -663,7 +736,7 @@ mod tests { }; assert_eq!(venv.base_executable_home_path, expected_home); - let site_packages_directories = venv.site_packages_directories(&self.system).unwrap(); + let site_packages_directories = env.site_packages_directories(&self.system).unwrap(); let expected_venv_site_packages = if cfg!(target_os = "windows") { SystemPathBuf::from(r"\.venv\Lib\site-packages") } else if self.free_threaded { @@ -782,8 +855,8 @@ mod tests { fn reject_venv_that_does_not_exist() { let system = TestSystem::default(); assert!(matches!( - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), - Err(SitePackagesDiscoveryError::VenvDirCanonicalizationError(..)) + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), + Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..)) )); } @@ -795,25 +868,30 @@ mod tests { .write_file_all("/.venv", "") .unwrap(); assert!(matches!( - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), - Err(SitePackagesDiscoveryError::VenvDirIsNotADirectory(..)) + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), + Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..)) )); } #[test] - fn reject_venv_with_no_pyvenv_cfg_file() { + fn env_with_no_pyvenv_cfg_file() { let system = TestSystem::default(); system .memory_file_system() .create_directory_all("/.venv") .unwrap(); - assert!(matches!( - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), - Err(SitePackagesDiscoveryError::NoPyvenvCfgFile( - SysPrefixPathOrigin::VirtualEnvVar, - _ - )) - )); + let env = + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap(); + let PythonEnvironment::System(env) = env else { + panic!("Expected a system environment; got {env:?}"); + }; + assert!( + env.root_path + == SysPrefixPath { + inner: system.canonicalize_path(SystemPath::new("/.venv")).unwrap(), + origin: SysPrefixPathOrigin::PythonCliFlag, + } + ); } #[test] @@ -825,7 +903,7 @@ mod tests { .write_file_all(&pyvenv_cfg_path, "home = bar = /.venv/bin") .unwrap(); let venv_result = - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); assert!(matches!( venv_result, Err(SitePackagesDiscoveryError::PyvenvCfgParseError( @@ -845,7 +923,7 @@ mod tests { .write_file_all(&pyvenv_cfg_path, "home =") .unwrap(); let venv_result = - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); assert!(matches!( venv_result, Err(SitePackagesDiscoveryError::PyvenvCfgParseError( @@ -865,7 +943,7 @@ mod tests { .write_file_all(&pyvenv_cfg_path, "= whatever") .unwrap(); let venv_result = - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); assert!(matches!( venv_result, Err(SitePackagesDiscoveryError::PyvenvCfgParseError( @@ -883,7 +961,7 @@ mod tests { let pyvenv_cfg_path = SystemPathBuf::from("/.venv/pyvenv.cfg"); memory_fs.write_file_all(&pyvenv_cfg_path, "").unwrap(); let venv_result = - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); assert!(matches!( venv_result, Err(SitePackagesDiscoveryError::PyvenvCfgParseError( @@ -903,7 +981,7 @@ mod tests { .write_file_all(&pyvenv_cfg_path, "home = foo") .unwrap(); let venv_result = - VirtualEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); + PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system); assert!(matches!( venv_result, Err(SitePackagesDiscoveryError::PyvenvCfgParseError( From 2923c55698731ffb210af416b2663dc9be67784f Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 10 May 2025 15:28:15 -0500 Subject: [PATCH 023/487] [ty] Add test coverage for `PythonEnvironment::System` variants (#17996) Adds test coverage for https://github.com/astral-sh/ruff/pull/17991, which includes some minor refactoring of the virtual environment test infrastructure. I tried to minimize stylistic changes, but there are still a few because I was a little confused by the setup. I could see this evolving more in the future, as I don't think the existing model can capture all the test coverage I'm looking for. --- .../ty_python_semantic/src/site_packages.rs | 271 ++++++++++++++---- 1 file changed, 211 insertions(+), 60 deletions(-) diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index abc81ef45a5621..bb427ff3f1ebc9 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -617,23 +617,26 @@ mod tests { use super::*; - struct VirtualEnvironmentTester { + struct VirtualEnvironmentTestCase { + system_site_packages: bool, + pyvenv_cfg_version_field: Option<&'static str>, + } + + struct PythonEnvironmentTestCase { system: TestSystem, minor_version: u8, free_threaded: bool, - system_site_packages: bool, - pyvenv_cfg_version_field: Option<&'static str>, + virtual_env: Option, } - impl VirtualEnvironmentTester { - /// Builds a mock virtual environment, and returns the path to the venv - fn build_mock_venv(&self) -> SystemPathBuf { - let VirtualEnvironmentTester { + impl PythonEnvironmentTestCase { + /// Builds a mock environment, and returns the path to the environment root. + fn build(&self) -> SystemPathBuf { + let PythonEnvironmentTestCase { system, minor_version, - system_site_packages, free_threaded, - pyvenv_cfg_version_field, + virtual_env, } = self; let memory_fs = system.memory_file_system(); let unix_site_packages = if *free_threaded { @@ -663,6 +666,14 @@ mod tests { .create_directory_all(&system_site_packages_path) .unwrap(); + let Some(VirtualEnvironmentTestCase { + pyvenv_cfg_version_field, + system_site_packages, + }) = virtual_env + else { + return system_install_sys_prefix; + }; + let venv_sys_prefix = SystemPathBuf::from("/.venv"); let (venv_exe, site_packages_path) = if cfg!(target_os = "windows") { ( @@ -695,29 +706,56 @@ mod tests { venv_sys_prefix } - fn test(self) { - let venv_path = self.build_mock_venv(); + fn run(self) { + let env_path = self.build(); let env = PythonEnvironment::new( - venv_path.clone(), + env_path.clone(), SysPrefixPathOrigin::VirtualEnvVar, &self.system, ) .unwrap(); - let PythonEnvironment::Virtual(venv) = &env else { - panic!("Expected a virtual environment; got {env:?}"); - }; + let expect_virtual_env = self.virtual_env.is_some(); + match env { + PythonEnvironment::Virtual(venv) if expect_virtual_env => { + self.assert_virtual_environment(&venv, &env_path); + } + PythonEnvironment::Virtual(venv) => { + panic!( + "Expected a system environment, but got a virtual environment: {venv:?}" + ); + } + PythonEnvironment::System(env) if !expect_virtual_env => { + self.assert_system_environment(&env, &env_path); + } + PythonEnvironment::System(env) => { + panic!("Expected a virtual environment, but got a system environment: {env:?}"); + } + } + } + + fn assert_virtual_environment( + &self, + venv: &VirtualEnvironment, + expected_env_path: &SystemPathBuf, + ) { + let self_venv = self.virtual_env.as_ref().expect( + "`assert_virtual_environment` should only be used when `virtual_env` is populated", + ); assert_eq!( venv.root_path, SysPrefixPath { - inner: self.system.canonicalize_path(&venv_path).unwrap(), + inner: self.system.canonicalize_path(expected_env_path).unwrap(), origin: SysPrefixPathOrigin::VirtualEnvVar, } ); - assert_eq!(venv.include_system_site_packages, self.system_site_packages); + assert_eq!( + venv.include_system_site_packages, + self_venv.system_site_packages + ); - if self.pyvenv_cfg_version_field.is_some() { + if self_venv.pyvenv_cfg_version_field.is_some() { assert_eq!( venv.version, Some(PythonVersion { @@ -736,7 +774,7 @@ mod tests { }; assert_eq!(venv.base_executable_home_path, expected_home); - let site_packages_directories = env.site_packages_directories(&self.system).unwrap(); + let site_packages_directories = venv.site_packages_directories(&self.system).unwrap(); let expected_venv_site_packages = if cfg!(target_os = "windows") { SystemPathBuf::from(r"\.venv\Lib\site-packages") } else if self.free_threaded { @@ -768,7 +806,7 @@ mod tests { )) }; - if self.system_site_packages { + if self_venv.system_site_packages { assert_eq!( &site_packages_directories, &[expected_venv_site_packages, expected_system_site_packages] @@ -777,120 +815,233 @@ mod tests { assert_eq!(&site_packages_directories, &[expected_venv_site_packages]); } } + + fn assert_system_environment( + &self, + env: &SystemEnvironment, + expected_env_path: &SystemPathBuf, + ) { + assert!( + self.virtual_env.is_none(), + "`assert_system_environment` should only be used when `virtual_env` is not populated" + ); + + assert_eq!( + env.root_path, + SysPrefixPath { + inner: self.system.canonicalize_path(expected_env_path).unwrap(), + origin: SysPrefixPathOrigin::VirtualEnvVar, + } + ); + + let site_packages_directories = env.site_packages_directories(&self.system).unwrap(); + + let expected_site_packages = if cfg!(target_os = "windows") { + SystemPathBuf::from(&*format!( + r"\Python3.{}\Lib\site-packages", + self.minor_version + )) + } else if self.free_threaded { + SystemPathBuf::from(&*format!( + "/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages", + minor_version = self.minor_version + )) + } else { + SystemPathBuf::from(&*format!( + "/Python3.{minor_version}/lib/python3.{minor_version}/site-packages", + minor_version = self.minor_version + )) + }; + + assert_eq!(&site_packages_directories, &[expected_site_packages]); + } + } + + #[test] + fn can_find_site_packages_directory_no_virtual_env() { + let test = PythonEnvironmentTestCase { + system: TestSystem::default(), + minor_version: 12, + free_threaded: false, + virtual_env: None, + }; + test.run(); + } + + #[test] + fn can_find_site_packages_directory_no_virtual_env_freethreaded() { + let test = PythonEnvironmentTestCase { + system: TestSystem::default(), + minor_version: 13, + free_threaded: true, + virtual_env: None, + }; + test.run(); } #[test] fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, free_threaded: false, - system_site_packages: false, - pyvenv_cfg_version_field: None, + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: false, + pyvenv_cfg_version_field: None, + }), }; - tester.test(); + test.run(); } #[test] fn can_find_site_packages_directory_venv_style_version_field_in_pyvenv_cfg() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, free_threaded: false, - system_site_packages: false, - pyvenv_cfg_version_field: Some("version = 3.12"), + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: false, + pyvenv_cfg_version_field: Some("version = 3.12"), + }), }; - tester.test(); + test.run(); } #[test] fn can_find_site_packages_directory_uv_style_version_field_in_pyvenv_cfg() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, free_threaded: false, - system_site_packages: false, - pyvenv_cfg_version_field: Some("version_info = 3.12"), + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: false, + pyvenv_cfg_version_field: Some("version_info = 3.12"), + }), }; - tester.test(); + test.run(); } #[test] fn can_find_site_packages_directory_virtualenv_style_version_field_in_pyvenv_cfg() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, free_threaded: false, - system_site_packages: false, - pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: false, + pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), + }), }; - tester.test(); + test.run(); } #[test] fn can_find_site_packages_directory_freethreaded_build() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 13, free_threaded: true, - system_site_packages: false, - pyvenv_cfg_version_field: Some("version_info = 3.13"), + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: false, + pyvenv_cfg_version_field: Some("version_info = 3.13"), + }), }; - tester.test(); + test.run(); } #[test] fn finds_system_site_packages() { - let tester = VirtualEnvironmentTester { + let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 13, free_threaded: true, - system_site_packages: true, - pyvenv_cfg_version_field: Some("version_info = 3.13"), + virtual_env: Some(VirtualEnvironmentTestCase { + system_site_packages: true, + pyvenv_cfg_version_field: Some("version_info = 3.13"), + }), }; - tester.test(); + test.run(); } #[test] - fn reject_venv_that_does_not_exist() { + fn reject_env_that_does_not_exist() { let system = TestSystem::default(); assert!(matches!( - PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), + PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..)) )); } #[test] - fn reject_venv_that_is_not_a_directory() { + fn reject_env_that_is_not_a_directory() { let system = TestSystem::default(); system .memory_file_system() - .write_file_all("/.venv", "") + .write_file_all("/env", "") .unwrap(); assert!(matches!( - PythonEnvironment::new("/.venv", SysPrefixPathOrigin::VirtualEnvVar, &system), + PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..)) )); } #[test] - fn env_with_no_pyvenv_cfg_file() { + fn cannot_read_lib_directory() { let system = TestSystem::default(); system .memory_file_system() - .create_directory_all("/.venv") + .create_directory_all("/env") .unwrap(); + // Environment creation succeeds, but site-packages retrieval fails reading the `lib` + // directory let env = - PythonEnvironment::new("/.venv", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap(); - let PythonEnvironment::System(env) = env else { - panic!("Expected a system environment; got {env:?}"); - }; + PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap(); + let site_packages = env.site_packages_directories(&system); + if cfg!(unix) { + assert!( + matches!( + site_packages, + Err(SitePackagesDiscoveryError::CouldNotReadLibDirectory(..)), + ), + "Got {site_packages:?}", + ); + } else { + // On Windows, we look for `Lib/site-packages` directly instead of listing the entries + // of `lib/...` — so we don't see the intermediate failure + assert!( + matches!( + site_packages, + Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(..)), + ), + "Got {site_packages:?}", + ); + } + } + + #[test] + fn cannot_find_site_packages_directory() { + let system = TestSystem::default(); + if cfg!(unix) { + system + .memory_file_system() + .create_directory_all("/env/lib") + .unwrap(); + } else { + system + .memory_file_system() + .create_directory_all("/env/Lib") + .unwrap(); + } + // Environment creation succeeds, but site-packages retrieval fails + let env = + PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system).unwrap(); + let site_packages = env.site_packages_directories(&system); assert!( - env.root_path - == SysPrefixPath { - inner: system.canonicalize_path(SystemPath::new("/.venv")).unwrap(), - origin: SysPrefixPathOrigin::PythonCliFlag, - } + matches!( + site_packages, + Err(SitePackagesDiscoveryError::NoSitePackagesDirFound(..)), + ), + "Got {site_packages:?}", ); } From 0bb8cbdf079f824c0d6776a41d9aa336603bb06e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 10 May 2025 15:36:12 -0500 Subject: [PATCH 024/487] [ty] Do not allow invalid virtual environments from discovered `.venv` or `VIRTUAL_ENV` (#18003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to https://github.com/astral-sh/ruff/pull/17991 ensuring we do not allow detection of system environments when the origin is `VIRTUAL_ENV` or a discovered `.venv` directory — i.e., those always require a `pyvenv.cfg` file. --- .../ty_python_semantic/src/site_packages.rs | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index bb427ff3f1ebc9..53d8a037a21e95 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -39,7 +39,9 @@ impl PythonEnvironment { Ok(venv) => Ok(Self::Virtual(venv)), // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment // - Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(_, _)) => { + Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(_, _)) + if !origin.must_be_virtual_env() => + { Ok(Self::System(SystemEnvironment::new(path))) } Err(err) => Err(err), @@ -531,6 +533,17 @@ pub enum SysPrefixPathOrigin { LocalVenv, } +impl SysPrefixPathOrigin { + /// Whether the given `sys.prefix` path must be a virtual environment (rather than a system + /// Python environment). + pub(crate) fn must_be_virtual_env(self) -> bool { + match self { + Self::LocalVenv | Self::VirtualEnvVar => true, + Self::PythonCliFlag | Self::Derived => false, + } + } +} + impl Display for SysPrefixPathOrigin { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { @@ -626,6 +639,7 @@ mod tests { system: TestSystem, minor_version: u8, free_threaded: bool, + origin: SysPrefixPathOrigin, virtual_env: Option, } @@ -636,6 +650,7 @@ mod tests { system, minor_version, free_threaded, + origin: _, virtual_env, } = self; let memory_fs = system.memory_file_system(); @@ -706,14 +721,17 @@ mod tests { venv_sys_prefix } + #[track_caller] + fn err(self) -> SitePackagesDiscoveryError { + PythonEnvironment::new(self.build(), self.origin, &self.system) + .expect_err("Expected environment construction to fail") + } + + #[track_caller] fn run(self) { let env_path = self.build(); - let env = PythonEnvironment::new( - env_path.clone(), - SysPrefixPathOrigin::VirtualEnvVar, - &self.system, - ) - .unwrap(); + let env = PythonEnvironment::new(env_path.clone(), self.origin, &self.system) + .expect("Expected environment construction to succeed"); let expect_virtual_env = self.virtual_env.is_some(); match env { @@ -747,7 +765,7 @@ mod tests { venv.root_path, SysPrefixPath { inner: self.system.canonicalize_path(expected_env_path).unwrap(), - origin: SysPrefixPathOrigin::VirtualEnvVar, + origin: self.origin, } ); assert_eq!( @@ -830,7 +848,7 @@ mod tests { env.root_path, SysPrefixPath { inner: self.system.canonicalize_path(expected_env_path).unwrap(), - origin: SysPrefixPathOrigin::VirtualEnvVar, + origin: self.origin, } ); @@ -863,6 +881,7 @@ mod tests { system: TestSystem::default(), minor_version: 12, free_threaded: false, + origin: SysPrefixPathOrigin::PythonCliFlag, virtual_env: None, }; test.run(); @@ -874,17 +893,51 @@ mod tests { system: TestSystem::default(), minor_version: 13, free_threaded: true, + origin: SysPrefixPathOrigin::PythonCliFlag, virtual_env: None, }; test.run(); } + #[test] + fn cannot_find_site_packages_directory_no_virtual_env_at_origin_virtual_env_var() { + let test = PythonEnvironmentTestCase { + system: TestSystem::default(), + minor_version: 13, + free_threaded: false, + origin: SysPrefixPathOrigin::VirtualEnvVar, + virtual_env: None, + }; + let err = test.err(); + assert!( + matches!(err, SitePackagesDiscoveryError::NoPyvenvCfgFile(..)), + "Got {err:?}", + ); + } + + #[test] + fn cannot_find_site_packages_directory_no_virtual_env_at_origin_local_venv() { + let test = PythonEnvironmentTestCase { + system: TestSystem::default(), + minor_version: 13, + free_threaded: false, + origin: SysPrefixPathOrigin::LocalVenv, + virtual_env: None, + }; + let err = test.err(); + assert!( + matches!(err, SitePackagesDiscoveryError::NoPyvenvCfgFile(..)), + "Got {err:?}", + ); + } + #[test] fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() { let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, free_threaded: false, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: false, pyvenv_cfg_version_field: None, @@ -899,6 +952,7 @@ mod tests { system: TestSystem::default(), minor_version: 12, free_threaded: false, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: false, pyvenv_cfg_version_field: Some("version = 3.12"), @@ -913,6 +967,7 @@ mod tests { system: TestSystem::default(), minor_version: 12, free_threaded: false, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12"), @@ -927,6 +982,7 @@ mod tests { system: TestSystem::default(), minor_version: 12, free_threaded: false, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), @@ -941,6 +997,7 @@ mod tests { system: TestSystem::default(), minor_version: 13, free_threaded: true, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.13"), @@ -955,6 +1012,7 @@ mod tests { system: TestSystem::default(), minor_version: 13, free_threaded: true, + origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: true, pyvenv_cfg_version_field: Some("version_info = 3.13"), From 7e8ba2b68eeecd4f25424d0bdeb7dd48e1c36a49 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 10 May 2025 15:52:49 -0500 Subject: [PATCH 025/487] [ty] Remove vestigial `pyvenv.cfg` creation in mdtest (#18006) Following #17991, removes some of https://github.com/astral-sh/ruff/pull/17222 which is no longer strictly necessary. I don't actually think it's that ugly to have around? no strong feelings on retaining it or not. --- crates/ty_test/README.md | 18 ++++++------------ crates/ty_test/src/lib.rs | 16 ++-------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/crates/ty_test/README.md b/crates/ty_test/README.md index 6930a0952813ae..e16b56ec490169 100644 --- a/crates/ty_test/README.md +++ b/crates/ty_test/README.md @@ -314,9 +314,9 @@ typeshed = "/typeshed" For more details, take a look at the [custom-typeshed Markdown test]. -### Mocking a virtual environment +### Mocking a Python environment -Mdtest supports mocking a virtual environment for a specific test at an arbitrary location, again +Mdtest supports mocking a Python environment for a specific test at an arbitrary location, again using the `[environment]` configuration option: ````markdown @@ -326,16 +326,10 @@ python = ".venv" ``` ```` -ty will reject virtual environments that do not have valid `pyvenv.cfg` files at the -virtual-environment directory root (here, `.venv/pyvenv.cfg`). However, if a `pyvenv.cfg` file does -not have its contents specified by the test, mdtest will automatically generate one for you, to -make mocking a virtual environment more ergonomic. - -Mdtest also makes it easy to write Python packages to the mock virtual environment's -`site-packages` directory using the `` magic path segment. This would -otherwise be hard, due to the fact that the `site-packages` subdirectory in a virtual environment -is located at a different relative path depending on the platform the virtual environment was -created on. In the following test, mdtest will write the Python file to +Mdtest also makes it easy to write Python packages to the mock environment's `site-packages` +directory using the `` magic path segment. This would otherwise be hard, due +to the fact that the `site-packages` subdirectory in an environment is located at a different +relative path depending on the platform. In the following test, mdtest will write the Python file to `.venv/Lib/site-packages/foo.py` in its in-memory filesystem used for the test if the test is being executed on Windows, and `.venv/lib/python3.13/site-packages/foo.py` otherwise: diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 43e6b141083736..cc4e92130ab28d 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -170,7 +170,6 @@ fn run_test( let mut typeshed_files = vec![]; let mut has_custom_versions_file = false; - let mut has_custom_pyvenv_cfg_file = false; let test_files: Vec<_> = test .files() @@ -196,9 +195,8 @@ fn run_test( } } else if let Some(python_path) = python_path { if let Ok(relative_path) = full_path.strip_prefix(python_path) { - if relative_path.as_str() == "pyvenv.cfg" { - has_custom_pyvenv_cfg_file = true; - } else { + // Construct the path to the site-packages directory + if relative_path.as_str() != "pyvenv.cfg" { let mut new_path = SystemPathBuf::new(); for component in full_path.components() { let component = component.as_str(); @@ -256,16 +254,6 @@ fn run_test( } } - if let Some(python_path) = python_path { - if !has_custom_pyvenv_cfg_file { - let pyvenv_cfg_file = python_path.join("pyvenv.cfg"); - let home_directory = SystemPathBuf::from(format!("/Python{python_version}")); - db.create_directory_all(&home_directory).unwrap(); - db.write_file(&pyvenv_cfg_file, format!("home = {home_directory}")) - .unwrap(); - } - } - let configuration = test.configuration(); let settings = ProgramSettings { From ff7ebecf89930baf65b471dd1c2c8240b0d160de Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 11 May 2025 10:27:27 +0100 Subject: [PATCH 026/487] [ty] Remove generic types from the daily property test run (for now) (#18004) --- .../src/types/property_tests/type_generation.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 470f3e07789550..362d603e6b85de 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -220,8 +220,6 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty { Ty::KnownClassInstance(KnownClass::Str), Ty::KnownClassInstance(KnownClass::Int), Ty::KnownClassInstance(KnownClass::Bool), - Ty::KnownClassInstance(KnownClass::List), - Ty::KnownClassInstance(KnownClass::Tuple), Ty::KnownClassInstance(KnownClass::FunctionType), Ty::KnownClassInstance(KnownClass::SpecialForm), Ty::KnownClassInstance(KnownClass::TypeVar), From 669855d2b5a66a7304dce3ffd0031e9243afad5a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 11 May 2025 11:18:55 +0100 Subject: [PATCH 027/487] [ty] Remove unused variants from various `Known*` enums (#18015) ## Summary `KnownClass::Range`, `KnownInstanceType::Any` and `ClassBase::any()` are no longer used or useful: all our tests pass with them removed. `KnownModule::Abc` _is_ now used outside of tests, however, so I removed the `#[allow(dead_code)]` branch above that variant. ## Test Plan `cargo test -p ty_python_semantic` --- .../src/module_resolver/module.rs | 3 +- crates/ty_python_semantic/src/types.rs | 39 ------------------- crates/ty_python_semantic/src/types/class.rs | 9 ----- .../src/types/class_base.rs | 5 --- crates/ty_python_semantic/src/types/infer.rs | 4 -- .../src/types/known_instance.rs | 12 +----- .../src/types/type_ordering.rs | 3 -- 7 files changed, 2 insertions(+), 73 deletions(-) diff --git a/crates/ty_python_semantic/src/module_resolver/module.rs b/crates/ty_python_semantic/src/module_resolver/module.rs index ecd8959f2dd261..2b02d4daca5990 100644 --- a/crates/ty_python_semantic/src/module_resolver/module.rs +++ b/crates/ty_python_semantic/src/module_resolver/module.rs @@ -114,8 +114,7 @@ pub enum KnownModule { TypingExtensions, Typing, Sys, - #[allow(dead_code)] - Abc, // currently only used in tests + Abc, Dataclasses, Collections, Inspect, diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ed62c97ca2ddd3..9dc4b4cc6d3669 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2432,25 +2432,6 @@ impl<'db> Type<'db> { )) .into(), ), - // TODO: - // We currently hard-code the knowledge that the following known classes are not - // descriptors, i.e. that they have no `__get__` method. This is not wrong and - // potentially even beneficial for performance, but it's not very principled. - // This case can probably be removed eventually, but we include it at the moment - // because we make extensive use of these types in our test suite. Note that some - // builtin types are not included here, since they do not have generic bases and - // are correctly handled by the `find_name_in_mro` method. - ( - Some( - KnownClass::Int - | KnownClass::Str - | KnownClass::Bytes - | KnownClass::Tuple - | KnownClass::Slice - | KnownClass::Range, - ), - "__get__" | "__set__" | "__delete__", - ) => Some(Symbol::Unbound.into()), _ => Some(class.class_member(db, name, policy)), } @@ -2460,25 +2441,6 @@ impl<'db> Type<'db> { Some(ClassType::from(*alias).class_member(db, name, policy)) } - Type::SubclassOf(subclass_of) - if name == "__get__" - && matches!( - subclass_of - .subclass_of() - .into_class() - .and_then(|c| c.known(db)), - Some( - KnownClass::Int - | KnownClass::Str - | KnownClass::Bytes - | KnownClass::Tuple - | KnownClass::Slice - | KnownClass::Range, - ) - ) => - { - Some(Symbol::Unbound.into()) - } Type::SubclassOf(subclass_of_ty) => { subclass_of_ty.find_name_in_mro_with_policy(db, name, policy) } @@ -4771,7 +4733,6 @@ impl<'db> Type<'db> { KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)), KnownInstanceType::Never | KnownInstanceType::NoReturn => Ok(Type::Never), KnownInstanceType::LiteralString => Ok(Type::LiteralString), - KnownInstanceType::Any => Ok(Type::any()), KnownInstanceType::Unknown => Ok(Type::unknown()), KnownInstanceType::AlwaysTruthy => Ok(Type::AlwaysTruthy), KnownInstanceType::AlwaysFalsy => Ok(Type::AlwaysFalsy), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 422eea1ea95b9a..e99844a9d8a9ba 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1887,7 +1887,6 @@ pub enum KnownClass { FrozenSet, Dict, Slice, - Range, Property, BaseException, BaseExceptionGroup, @@ -1996,7 +1995,6 @@ impl<'db> KnownClass { | Self::Bytes | Self::Bytearray | Self::FrozenSet - | Self::Range | Self::Property | Self::SpecialForm | Self::Dict @@ -2051,7 +2049,6 @@ impl<'db> KnownClass { | Self::List | Self::Type | Self::Slice - | Self::Range | Self::Property | Self::BaseException | Self::BaseExceptionGroup @@ -2109,7 +2106,6 @@ impl<'db> KnownClass { Self::List => "list", Self::Type => "type", Self::Slice => "slice", - Self::Range => "range", Self::Property => "property", Self::BaseException => "BaseException", Self::BaseExceptionGroup => "BaseExceptionGroup", @@ -2331,7 +2327,6 @@ impl<'db> KnownClass { | Self::BaseExceptionGroup | Self::Classmethod | Self::Slice - | Self::Range | Self::Super | Self::Property => KnownModule::Builtins, Self::VersionInfo => KnownModule::Sys, @@ -2416,7 +2411,6 @@ impl<'db> KnownClass { | Self::FrozenSet | Self::Dict | Self::Slice - | Self::Range | Self::Property | Self::BaseException | Self::BaseExceptionGroup @@ -2478,7 +2472,6 @@ impl<'db> KnownClass { | Self::List | Self::Type | Self::Slice - | Self::Range | Self::Property | Self::GenericAlias | Self::ModuleType @@ -2537,7 +2530,6 @@ impl<'db> KnownClass { "dict" => Self::Dict, "list" => Self::List, "slice" => Self::Slice, - "range" => Self::Range, "property" => Self::Property, "BaseException" => Self::BaseException, "BaseExceptionGroup" => Self::BaseExceptionGroup, @@ -2607,7 +2599,6 @@ impl<'db> KnownClass { | Self::FrozenSet | Self::Dict | Self::Slice - | Self::Range | Self::Property | Self::GenericAlias | Self::ChainMap diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index d5b4e440d74cc3..db5d23932195e4 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -26,10 +26,6 @@ pub enum ClassBase<'db> { } impl<'db> ClassBase<'db> { - pub(crate) const fn any() -> Self { - Self::Dynamic(DynamicType::Any) - } - pub(crate) const fn unknown() -> Self { Self::Dynamic(DynamicType::Unknown) } @@ -164,7 +160,6 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::AlwaysTruthy | KnownInstanceType::AlwaysFalsy => None, KnownInstanceType::Unknown => Some(Self::unknown()), - KnownInstanceType::Any => Some(Self::any()), // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO KnownInstanceType::Dict => { Self::try_from_type(db, KnownClass::Dict.to_class_literal(db)) diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index e13aa08605a17f..43c384c84f43a8 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -8045,9 +8045,6 @@ impl<'db> TypeInferenceBuilder<'db> { ) } } - Type::KnownInstance(KnownInstanceType::Any) => { - SubclassOfType::subclass_of_any() - } Type::KnownInstance(KnownInstanceType::Unknown) => { SubclassOfType::subclass_of_unknown() } @@ -8486,7 +8483,6 @@ impl<'db> TypeInferenceBuilder<'db> { } KnownInstanceType::NoReturn | KnownInstanceType::Never - | KnownInstanceType::Any | KnownInstanceType::AlwaysTruthy | KnownInstanceType::AlwaysFalsy => { self.infer_type_expression(arguments_slice); diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index 801e03cae2afbb..4e0a132daad9d9 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -34,11 +34,6 @@ pub enum KnownInstanceType<'db> { NoReturn, /// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`) Never, - /// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`) - /// This is not used since typeshed switched to representing `Any` as a class; now we use - /// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at - /// least for now. TODO maybe remove? - Any, /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) Tuple, /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) @@ -121,7 +116,6 @@ impl<'db> KnownInstanceType<'db> { | Self::Union | Self::NoReturn | Self::Never - | Self::Any | Self::Tuple | Self::Type | Self::TypingSelf @@ -177,7 +171,6 @@ impl<'db> KnownInstanceType<'db> { Self::Union => KnownClass::SpecialForm, Self::NoReturn => KnownClass::SpecialForm, Self::Never => KnownClass::SpecialForm, - Self::Any => KnownClass::Object, Self::Tuple => KnownClass::SpecialForm, Self::Type => KnownClass::SpecialForm, Self::TypingSelf => KnownClass::SpecialForm, @@ -236,7 +229,6 @@ impl<'db> KnownInstanceType<'db> { symbol_name: &str, ) -> Option { let candidate = match symbol_name { - "Any" => Self::Any, "ClassVar" => Self::ClassVar, "Deque" => Self::Deque, "List" => Self::List, @@ -291,8 +283,7 @@ impl<'db> KnownInstanceType<'db> { /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. pub(super) fn check_module(self, module: KnownModule) -> bool { match self { - Self::Any - | Self::ClassVar + Self::ClassVar | Self::Deque | Self::List | Self::Dict @@ -359,7 +350,6 @@ impl Display for KnownInstanceRepr<'_> { KnownInstanceType::Union => f.write_str("typing.Union"), KnownInstanceType::NoReturn => f.write_str("typing.NoReturn"), KnownInstanceType::Never => f.write_str("typing.Never"), - KnownInstanceType::Any => f.write_str("typing.Any"), KnownInstanceType::Tuple => f.write_str("typing.Tuple"), KnownInstanceType::Type => f.write_str("typing.Type"), KnownInstanceType::TypingSelf => f.write_str("typing.Self"), diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 4849c36aa98a38..b264aba1ad47c5 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -185,9 +185,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::KnownInstance(left_instance), Type::KnownInstance(right_instance)) => { match (left_instance, right_instance) { - (KnownInstanceType::Any, _) => Ordering::Less, - (_, KnownInstanceType::Any) => Ordering::Greater, - (KnownInstanceType::Tuple, _) => Ordering::Less, (_, KnownInstanceType::Tuple) => Ordering::Greater, From 8845a13efb6b05da17f2f89d0ce8b55e1d05045a Mon Sep 17 00:00:00 2001 From: Yunchi Pang Date: Sun, 11 May 2025 09:01:26 -0700 Subject: [PATCH 028/487] [`pylint`] add fix safety section (`PLC0414`) (#17802) This PR adds a fix safety section in comment for rule `PLC0414`. parent: #15584 discussion: #6294 --- .../src/rules/pylint/rules/useless_import_alias.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs index 8b26ad96d37028..c7364b079ba1a4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs @@ -12,6 +12,11 @@ use crate::checkers::ast::Checker; /// ## Why is this bad? /// The import alias is redundant and should be removed to avoid confusion. /// +/// ## Fix safety +/// This fix is marked as always unsafe because the user may be intentionally +/// re-exporting the import. While statements like `import numpy as numpy` +/// appear redundant, they can have semantic meaning in certain contexts. +/// /// ## Example /// ```python /// import numpy as numpy From 5792ed15da6e37b9c37dc1960794139338d9916f Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Sun, 11 May 2025 18:15:15 +0200 Subject: [PATCH 029/487] [`ruff`] add fix safety section (`RUF033`) (#17760) This PR adds the fix safety section for rule `RUF033` (https://github.com/astral-sh/ruff/issues/15584 ). --- .../ruff_linter/src/rules/ruff/rules/post_init_default.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index c587a0f7a2e0a3..256e650e680798 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs @@ -61,6 +61,12 @@ use super::helpers::{dataclass_kind, DataclassKind}; /// foo = Foo() # Prints '1 2'. /// ``` /// +/// ## Fix safety +/// +/// This fix is always marked as unsafe because, although switching to `InitVar` is usually correct, +/// it is incorrect when the parameter is not intended to be part of the public API or when the value +/// is meant to be shared across all instances. +/// /// ## References /// - [Python documentation: Post-init processing](https://docs.python.org/3/library/dataclasses.html#post-init-processing) /// - [Python documentation: Init-only variables](https://docs.python.org/3/library/dataclasses.html#init-only-variables) From bc7b30364dcd196f9d660218b852870b1d978e76 Mon Sep 17 00:00:00 2001 From: Rogdham <3994389+Rogdham@users.noreply.github.com> Date: Sun, 11 May 2025 18:25:54 +0200 Subject: [PATCH 030/487] python_stdlib: update for 3.14 (#18014) ## Summary Added version 3.14 to the script generating the `known_stdlib.rs` file. Rebuilt the known stdlibs with latest version (2025.5.10) of [stdlibs Python lib](https://pypi.org/project/stdlibs/) (which added support for 3.14.0b1). _Note: Python 3.14 is now in [feature freeze](https://peps.python.org/pep-0745/) so the modules in stdlib should be stable._ _See also: #15506_ ## Test Plan The following command has been run. Using for tests the `compression` module which been introduced with Python 3.14. ```sh ruff check --no-cache --select I001 --target-version py314 --fix ``` With ruff 0.11.9: ```python import base64 import datetime import compression print(base64, compression, datetime) ``` With this PR: ```python import base64 import compression import datetime print(base64, compression, datetime) ``` --- .../src/sys/known_stdlib.rs | 54 ++++++++++++++++++- scripts/generate_known_standard_library.py | 1 + 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/crates/ruff_python_stdlib/src/sys/known_stdlib.rs b/crates/ruff_python_stdlib/src/sys/known_stdlib.rs index 7c08ebe800aac8..4ab64366fc4001 100644 --- a/crates/ruff_python_stdlib/src/sys/known_stdlib.rs +++ b/crates/ruff_python_stdlib/src/sys/known_stdlib.rs @@ -23,7 +23,6 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "_collections" | "_collections_abc" | "_compat_pickle" - | "_compression" | "_contextvars" | "_csv" | "_ctypes" @@ -285,6 +284,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { ) | ( 7, "_bootlocale" + | "_compression" | "_crypt" | "_dummy_thread" | "_msi" @@ -324,6 +324,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { ) | ( 8, "_bootlocale" + | "_compression" | "_crypt" | "_dummy_thread" | "_msi" @@ -368,6 +369,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { "_aix_support" | "_bootlocale" | "_bootsubprocess" + | "_compression" | "_crypt" | "_msi" | "_peg_parser" @@ -399,7 +401,6 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "nntplib" | "ossaudiodev" | "parser" - | "peg_parser" | "pipes" | "smtpd" | "sndhdr" @@ -414,6 +415,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { 10, "_aix_support" | "_bootsubprocess" + | "_compression" | "_crypt" | "_msi" | "_posixshmem" @@ -460,6 +462,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "__phello_alias__" | "_aix_support" | "_bootsubprocess" + | "_compression" | "_crypt" | "_msi" | "_posixshmem" @@ -507,6 +510,7 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "__hello_only__" | "__phello_alias__" | "_aix_support" + | "_compression" | "_crypt" | "_msi" | "_posixshmem" @@ -554,7 +558,9 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "__phello_alias__" | "_aix_support" | "_android_support" + | "_apple_support" | "_colorize" + | "_compression" | "_interpchannels" | "_interpqueues" | "_interpreters" @@ -583,6 +589,50 @@ pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool { | "tomllib" | "xxlimited_35" | "zoneinfo" + ) | ( + 14, + "__hello_alias__" + | "__hello_only__" + | "__phello_alias__" + | "_aix_support" + | "_android_support" + | "_apple_support" + | "_ast_unparse" + | "_colorize" + | "_hmac" + | "_interpchannels" + | "_interpqueues" + | "_interpreters" + | "_ios_support" + | "_opcode_metadata" + | "_posixshmem" + | "_py_warnings" + | "_pydatetime" + | "_pylong" + | "_pyrepl" + | "_remote_debugging" + | "_sha2" + | "_statistics" + | "_suggestions" + | "_sysconfig" + | "_testcapi_datetime" + | "_testclinic" + | "_testclinic_limited" + | "_testinternalcapi" + | "_testlimitedcapi" + | "_testsinglephase" + | "_tokenize" + | "_types" + | "_typing" + | "_wmi" + | "_zoneinfo" + | "_zstd" + | "annotationlib" + | "compression" + | "graphlib" + | "tomllib" + | "xxlimited_35" + | "zoneinfo" ) ) } diff --git a/scripts/generate_known_standard_library.py b/scripts/generate_known_standard_library.py index 22117b60d4f995..84355af82a4f7f 100644 --- a/scripts/generate_known_standard_library.py +++ b/scripts/generate_known_standard_library.py @@ -13,6 +13,7 @@ (3, 11), (3, 12), (3, 13), + (3, 14), ] with PATH.open("w") as f: From b398b8363104347fe80f1d5241718f90fb637f84 Mon Sep 17 00:00:00 2001 From: Yunchi Pang Date: Sun, 11 May 2025 10:25:07 -0700 Subject: [PATCH 031/487] [`pylint`] add fix safety section (`PLW1514`) (#17932) parent #15584 fix was made unsafe at #8928 --- .../src/rules/pylint/rules/unspecified_encoding.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 0ebee95e794e39..66a44e0fd702ed 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -28,6 +28,17 @@ use crate::fix::edits::add_argument; /// Python 3.10 and later, or `locale.getpreferredencoding()` on earlier versions, /// to make the encoding explicit. /// +/// ## Fix safety +/// This fix is always unsafe and may change the program's behavior. It forces +/// `encoding="utf-8"` as the default, regardless of the platform’s actual default +/// encoding, which may cause `UnicodeDecodeError` on non-UTF-8 systems. +/// ```python +/// with open("test.txt") as f: +/// print(f.read()) # before fix (on UTF-8 systems): 你好,世界! +/// with open("test.txt", encoding="utf-8") as f: +/// print(f.read()) # after fix (on Windows): UnicodeDecodeError +/// ``` +/// /// ## Example /// ```python /// open("file.txt") From 99555b775c4656545ad8aaaf24fd85e79b8bf950 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:03:55 -0400 Subject: [PATCH 032/487] Update dependency ruff to v0.11.9 (#18024) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index 569e0034bae13e..f7759fb20563a8 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.8 +ruff==0.11.9 mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 diff --git a/docs/requirements.txt b/docs/requirements.txt index 2e0c1ad9dac261..5d9b05411d837b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.8 +ruff==0.11.9 mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 From c1cfb43bf03a3a606bdb258d7bc0cd9d3c7ca14e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:23:49 +0200 Subject: [PATCH 033/487] Update Rust crate getrandom to v0.3.3 (#18028) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e38a58c9ebc889..0c0c1d6d7b7ffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1057,9 +1057,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -2458,7 +2458,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -3138,7 +3138,7 @@ version = "0.11.9" dependencies = [ "console_error_panic_hook", "console_log", - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "log", "ruff_formatter", @@ -3625,7 +3625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.2", "windows-sys 0.59.0", @@ -4168,7 +4168,7 @@ version = "0.0.0" dependencies = [ "console_error_panic_hook", "console_log", - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "log", "ruff_db", @@ -4353,7 +4353,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "rand 0.9.1", "uuid-macro-internal", From 2bfd7b1816271fd8fe6e3661c457763ffb325ba6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:24:16 +0200 Subject: [PATCH 034/487] Update Rust crate jiff to v0.2.13 (#18029) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c0c1d6d7b7ffb..824e792bda02a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -487,7 +487,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -904,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1485,7 +1485,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1539,9 +1539,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1549,14 +1549,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "jiff-static" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -3229,7 +3229,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3242,7 +3242,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3628,7 +3628,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.2", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4626,7 +4626,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] From c38d6e8045b4cd56795232dda845c6f782121f98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:24:40 +0200 Subject: [PATCH 035/487] Update Rust crate clap to v4.5.38 (#18026) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 824e792bda02a3..73caf5ed9edec7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,9 +334,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", "clap_derive", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", From d7c54ba8c488c1c4ea3a96a3710718060d1077b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:25:08 +0200 Subject: [PATCH 036/487] Update Rust crate ctrlc to v3.4.7 (#18027) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73caf5ed9edec7..59134f4e779ace 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,7 +409,7 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4" dependencies = [ - "nix", + "nix 0.29.0", "terminfo", "thiserror 2.0.12", "which", @@ -681,11 +681,11 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.6" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ - "nix", + "nix 0.30.1", "windows-sys 0.59.0", ] @@ -1880,6 +1880,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" From b86c7bbf7c5c571d35ad8e93b038ab8f89a14723 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:25:28 +0200 Subject: [PATCH 037/487] Update cargo-bins/cargo-binstall action to v1.12.4 (#18023) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index da384604f379ba..dc20f7a49d1cfd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -437,7 +437,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo-binstall" - uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3 + uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4 with: tool: cargo-fuzz@0.11.2 - name: "Install cargo-fuzz" @@ -692,7 +692,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3 + - uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4 - run: cargo binstall --no-confirm cargo-shear - run: cargo shear From a34240a3f04acbc500bea1e6cc4276b08132bf5b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:26:03 +0200 Subject: [PATCH 038/487] Update taiki-e/install-action digest to 83254c5 (#18022) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dc20f7a49d1cfd..484b3b35195cb9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -239,11 +239,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-insta - name: ty mdtests (GitHub annotations) @@ -297,11 +297,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-insta - name: "Run tests" @@ -324,7 +324,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-nextest - name: "Run tests" @@ -407,11 +407,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-insta - name: "Run tests" @@ -908,7 +908,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2 with: tool: cargo-codspeed From d6280c5aea8583acd8b3176abfd8186a8c676c56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:26:22 +0200 Subject: [PATCH 039/487] Update docker/login-action action to v3.4.0 (#18031) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-docker.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 98ceac751a73d5..9ee71d7754e5ec 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -40,7 +40,7 @@ jobs: - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -131,7 +131,7 @@ jobs: type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }} type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }} - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -169,7 +169,7 @@ jobs: steps: - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -276,7 +276,7 @@ jobs: type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }} type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }} - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From 38c00dfad5c10e6a22814ce3c44cff77a1e0f4b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:26:41 +0200 Subject: [PATCH 040/487] Update docker/build-push-action action to v6.16.0 (#18030) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9ee71d7754e5ec..53514200e9af85 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -79,7 +79,7 @@ jobs: # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ - name: Build and push by digest id: build - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 with: context: . platforms: ${{ matrix.platform }} @@ -231,7 +231,7 @@ jobs: ${{ env.TAG_PATTERNS }} - name: Build and push - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 with: context: . platforms: linux/amd64,linux/arm64 From d3f3d92df3fb891c5d8c121ea66eff151f72e5c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:31:23 +0200 Subject: [PATCH 041/487] Update pre-commit dependencies (#18025) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a119b10ce6bd9a..39bf437f235961 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,7 +80,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.8 + rev: v0.11.9 hooks: - id: ruff-format - id: ruff @@ -98,7 +98,7 @@ repos: # zizmor detects security vulnerabilities in GitHub Actions workflows. # Additional configuration for the tool is found in `.github/zizmor.yml` - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.6.0 + rev: v1.7.0 hooks: - id: zizmor From d944a1397e9a9c7809495b6ba29314c3933879f7 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 12 May 2025 13:16:03 +0200 Subject: [PATCH 042/487] [ty] Remove brackets around option names (#18037) --- crates/ruff_dev/src/generate_ty_options.rs | 6 +++--- crates/ty/docs/configuration.md | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/ruff_dev/src/generate_ty_options.rs b/crates/ruff_dev/src/generate_ty_options.rs index c323fc88d93e56..c8edd2eed1727c 100644 --- a/crates/ruff_dev/src/generate_ty_options.rs +++ b/crates/ruff_dev/src/generate_ty_options.rs @@ -134,13 +134,13 @@ impl Set { fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) { let header_level = if parents.is_empty() { "###" } else { "####" }; - let _ = writeln!(output, "{header_level} [`{name}`]"); + let _ = writeln!(output, "{header_level} `{name}`"); output.push('\n'); if let Some(deprecated) = &field.deprecated { - output.push_str("!!! warning \"Deprecated\"\n"); - output.push_str(" This option has been deprecated"); + output.push_str("> [!WARN] \"Deprecated\"\n"); + output.push_str("> This option has been deprecated"); if let Some(since) = deprecated.since { write!(output, " in {since}").unwrap(); diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 0c135ee02d2555..4f4365a7f69fd6 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -1,5 +1,5 @@ # Configuration -#### [`respect-ignore-files`] +#### `respect-ignore-files` Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. @@ -18,7 +18,7 @@ respect-ignore-files = false --- -#### [`rules`] +#### `rules` Configures the enabled rules and their severity. @@ -47,7 +47,7 @@ division-by-zero = "ignore" ## `environment` -#### [`extra-paths`] +#### `extra-paths` List of user-provided paths that should take first priority in the module resolution. Examples in other type checkers are mypy's `MYPYPATH` environment variable, @@ -66,7 +66,7 @@ extra-paths = ["~/shared/my-search-path"] --- -#### [`python`] +#### `python` Path to the Python installation from which ty resolves type information and third-party dependencies. @@ -88,7 +88,7 @@ python = "./.venv" --- -#### [`python-platform`] +#### `python-platform` Specifies the target platform that will be used to analyze the source code. If specified, ty will understand conditions based on comparisons with `sys.platform`, such @@ -115,7 +115,7 @@ python-platform = "win32" --- -#### [`python-version`] +#### `python-version` Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version @@ -139,7 +139,7 @@ python-version = "3.12" --- -#### [`typeshed`] +#### `typeshed` Optional path to a "typeshed" directory on disk for us to use for standard-library types. If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib, @@ -160,7 +160,7 @@ typeshed = "/path/to/custom/typeshed" ## `src` -#### [`root`] +#### `root` The root(s) of the project, used for finding first-party modules. @@ -179,7 +179,7 @@ root = ["./app"] ## `terminal` -#### [`error-on-warning`] +#### `error-on-warning` Use exit code 1 if there are any warning-level diagnostics. @@ -199,7 +199,7 @@ error-on-warning = true --- -#### [`output-format`] +#### `output-format` The format to use for printing diagnostic messages. From fcd858e0c82febef6c13ea7970a147546663b388 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 12 May 2025 13:31:42 +0200 Subject: [PATCH 043/487] [ty] Refine message for why a rule is enabled (#18038) --- crates/ty/tests/cli.rs | 72 +++++++++---------- ..._-_Invalid_`__set__`_method_signature.snap | 2 +- ...a_descriptors_-_Invalid_argument_type.snap | 2 +- ..._attributes_with_class-level_defaults.snap | 4 +- ...ignment_-_Possibly-unbound_attributes.snap | 4 +- ...assignment_-_Pure_instance_attributes.snap | 4 +- ...t_-_Setting_attributes_on_union_types.snap | 2 +- ...ibute_assignment_-_Unknown_attributes.snap | 4 +- ..._-_Attribute_assignment_-_`ClassVar`s.snap | 4 +- ...ts_imported_from_an_unresolved_module.snap | 2 +- ...ructures_-_Unresolvable_module_import.snap | 2 +- ...ures_-_Unresolvable_submodule_imports.snap | 4 +- ..._For_loops_-_Bad_`__getitem__`_method.snap | 2 +- ...for.md_-_For_loops_-_Invalid_iterable.snap | 2 +- ...New_over_old_style_iteration_protocol.snap | 2 +- ...hod_and_`__getitem__`_is_not_callable.snap | 2 +- ...bly-not-callable_`__getitem__`_method.snap | 4 +- ...ossibly_invalid_`__getitem__`_methods.snap | 4 +- ...-_Possibly_invalid_`__iter__`_methods.snap | 4 +- ..._-_Possibly_invalid_`__next__`_method.snap | 4 +- ..._iter__`_and_bad_`__getitem__`_method.snap | 2 +- ..._`_and_possibly_invalid_`__getitem__`.snap | 4 +- ..._`_and_possibly_unbound_`__getitem__`.snap | 2 +- ...element_has_invalid_`__iter__`_method.snap | 2 +- ...nion_element_has_no_`__iter__`_method.snap | 2 +- ...or_loops_-_With_non-callable_iterator.snap | 4 +- ...__iter__`_does_not_return_an_iterator.snap | 2 +- ...__iter__`_method_with_a_bad_signature.snap | 2 +- ...tor_with_an_invalid_`__next__`_method.snap | 4 +- ...cy_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...95_syntax_-_Inferring_a_bound_typevar.snap | 2 +- ...tax_-_Inferring_a_constrained_typevar.snap | 2 +- ...types_with_invalid_`__bool__`_methods.snap | 2 +- ...lid_argument_type_diagnostics_-_Basic.snap | 2 +- ...t_type_diagnostics_-_Calls_to_methods.snap | 2 +- ...nt_type_diagnostics_-_Different_files.snap | 2 +- ..._diagnostics_-_Different_source_order.snap | 2 +- ...nt_type_diagnostics_-_Many_parameters.snap | 2 +- ...Many_parameters_across_multiple_lines.snap | 2 +- ...eters_with_multiple_invalid_arguments.snap | 6 +- ...hose_type_is_vendored_from_`typeshed`.snap | 2 +- ...gument_types_-_Keyword_only_arguments.snap | 2 +- ..._of_argument_types_-_Mix_of_arguments.snap | 2 +- ...argument_types_-_One_keyword_argument.snap | 2 +- ...y_of_argument_types_-_Only_positional.snap | 2 +- ..._argument_types_-_Synthetic_arguments.snap | 2 +- ...f_argument_types_-_Variadic_arguments.snap | 2 +- ...nt_types_-_Variadic_keyword_arguments.snap | 2 +- ...oesn't_implement_`__bool__`_correctly.snap | 4 +- ...__bases__`_lists_with_duplicate_bases.snap | 14 ++-- ...stics_-_Calls_to_overloaded_functions.snap | 2 +- ...hat_implements_`__bool__`_incorrectly.snap | 2 +- ...ds_-_Invalid_-_At_least_two_overloads.snap | 4 +- ...onsistent_decorators_-_`@classmethod`.snap | 6 +- ..._-_Inconsistent_decorators_-_`@final`.snap | 6 +- ...Inconsistent_decorators_-_`@override`.snap | 6 +- ...t_an_implementation_-_Regular_modules.snap | 4 +- ...Protocols_-_Calls_to_protocol_classes.snap | 6 +- ...lid_calls_to_`get_protocol_members()`.snap | 4 +- ..._-_Protocols_-_Narrowing_of_protocols.snap | 4 +- ...ion_return_type_-_Generator_functions.snap | 4 +- ...ype_-_Invalid_conditional_return_type.snap | 6 +- ...n_type_-_Invalid_implicit_return_type.snap | 8 +-- ...ion_return_type_-_Invalid_return_type.snap | 8 +-- ...pe_-_Invalid_return_type_in_stub_file.snap | 6 +- ..._don't_implement_`__bool__`_correctly.snap | 4 +- ..._Shadowing_-_Implicit_class_shadowing.snap | 2 +- ...adowing_-_Implicit_function_shadowing.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...ction_types_-_A_smaller_scale_example.snap | 4 +- ...iple_variants_but_only_one_is_invalid.snap | 2 +- ...over_keyword_argument_related_reasons.snap | 4 +- ...s_-_Cover_non-keyword_related_reasons.snap | 14 ++-- ...ng_-_Exactly_too_few_values_to_unpack.snap | 2 +- ...g_-_Exactly_too_many_values_to_unpack.snap | 2 +- ...acking_-_Right_hand_side_not_iterable.snap | 2 +- ..._Unpacking_-_Too_few_values_to_unpack.snap | 2 +- ...vable_import_that_does_not_use_`from`.snap | 2 +- ...solvable_module_but_unresolvable_item.snap | 2 +- ...`from`_with_an_unknown_current_module.snap | 2 +- ..._`from`_with_an_unknown_nested_module.snap | 2 +- ...ng_`from`_with_an_unresolvable_module.snap | 2 +- ...ing_`from`_with_too_many_leading_dots.snap | 2 +- ...l__`_attribute,_but_it's_not_callable.snap | 2 +- ...hod,_but_has_an_incorrect_return_type.snap | 2 +- ..._method,_but_has_incorrect_parameters.snap | 2 +- ...ember_has_incorrect_`__bool__`_method.snap | 2 +- .../ty_python_semantic/src/types/context.rs | 9 ++- 90 files changed, 184 insertions(+), 181 deletions(-) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 1bebb41eba5240..8e240ddacb0db5 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -157,7 +157,7 @@ fn config_override_python_version() -> anyhow::Result<()> { 5 | print(sys.last_exc) | ^^^^^^^^^^^^ | - info: `unresolved-attribute` is enabled by default + info: rule `unresolved-attribute` is enabled by default Found 1 diagnostic @@ -296,7 +296,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { 3 | 4 | stat = add(10, 15) | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default Found 1 diagnostic @@ -400,7 +400,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` is enabled by default + info: rule `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 @@ -410,7 +410,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 7 | prin(x) # unresolved-reference | ^^^^ | - info: `unresolved-reference` is enabled by default + info: rule `unresolved-reference` is enabled by default Found 2 diagnostics @@ -439,7 +439,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` was selected in the configuration file + info: rule `division-by-zero` was selected in the configuration file Found 1 diagnostic @@ -481,7 +481,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 @@ -493,7 +493,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 5 | 6 | for a in range(0, int(y)): | - info: `division-by-zero` is enabled by default + info: rule `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:9:1 @@ -503,7 +503,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 9 | prin(x) # unresolved-reference | ^^^^ | - info: `unresolved-reference` is enabled by default + info: rule `unresolved-reference` is enabled by default Found 3 diagnostics @@ -532,7 +532,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | - info: `unresolved-import` was selected on the command line + info: rule `unresolved-import` was selected on the command line warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero --> test.py:4:5 @@ -544,7 +544,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { 5 | 6 | for a in range(0, int(y)): | - info: `division-by-zero` was selected on the command line + info: rule `division-by-zero` was selected on the command line Found 2 diagnostics @@ -586,7 +586,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` is enabled by default + info: rule `division-by-zero` is enabled by default error[unresolved-reference]: Name `prin` used when not defined --> test.py:7:1 @@ -596,7 +596,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 7 | prin(x) # unresolved-reference | ^^^^ | - info: `unresolved-reference` is enabled by default + info: rule `unresolved-reference` is enabled by default Found 2 diagnostics @@ -626,7 +626,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` was selected on the command line + info: rule `division-by-zero` was selected on the command line Found 1 diagnostic @@ -707,7 +707,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -794,7 +794,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -828,7 +828,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -860,7 +860,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -869,7 +869,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `non-subscriptable` is enabled by default + info: rule `non-subscriptable` is enabled by default Found 2 diagnostics @@ -901,7 +901,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -910,7 +910,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow:: 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `non-subscriptable` is enabled by default + info: rule `non-subscriptable` is enabled by default Found 2 diagnostics @@ -942,7 +942,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { | ^ 3 | print(4[1]) # [non-subscriptable] | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method --> test.py:3:7 @@ -951,7 +951,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { 3 | print(4[1]) # [non-subscriptable] | ^ | - info: `non-subscriptable` is enabled by default + info: rule `non-subscriptable` is enabled by default Found 2 diagnostics @@ -1006,7 +1006,7 @@ fn user_configuration() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` was selected in the configuration file + info: rule `division-by-zero` was selected in the configuration file error[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 @@ -1016,7 +1016,7 @@ fn user_configuration() -> anyhow::Result<()> { 7 | prin(x) | ^^^^ | - info: `unresolved-reference` is enabled by default + info: rule `unresolved-reference` is enabled by default Found 2 diagnostics @@ -1051,7 +1051,7 @@ fn user_configuration() -> anyhow::Result<()> { 3 | 4 | for a in range(0, int(y)): | - info: `division-by-zero` was selected in the configuration file + info: rule `division-by-zero` was selected in the configuration file warning[unresolved-reference]: Name `prin` used when not defined --> main.py:7:1 @@ -1061,7 +1061,7 @@ fn user_configuration() -> anyhow::Result<()> { 7 | prin(x) | ^^^^ | - info: `unresolved-reference` was selected in the configuration file + info: rule `unresolved-reference` was selected in the configuration file Found 2 diagnostics @@ -1110,7 +1110,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | y = 4 / 0 # error: division-by-zero | ^^^^^ | - info: `division-by-zero` is enabled by default + info: rule `division-by-zero` is enabled by default error[unresolved-import]: Cannot resolve imported module `main2` --> project/other.py:2:6 @@ -1120,7 +1120,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 @@ -1128,7 +1128,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default Found 3 diagnostics @@ -1153,7 +1153,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default error[unresolved-import]: Cannot resolve imported module `does_not_exist` --> project/tests/test_main.py:2:8 @@ -1161,7 +1161,7 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | - info: `unresolved-import` is enabled by default + info: rule `unresolved-import` is enabled by default Found 2 diagnostics @@ -1337,7 +1337,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { 4 | os.grantpt(1) # only available on unix, Python 3.13 or newer | ^^^^^^^^^^ | - info: `unresolved-attribute` is enabled by default + info: rule `unresolved-attribute` is enabled by default Found 1 diagnostic @@ -1392,7 +1392,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -1411,7 +1411,7 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` is enabled by default + info: rule `unresolved-reference` is enabled by default Found 1 diagnostic @@ -1444,7 +1444,7 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic @@ -1468,7 +1468,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { 1 | print(x) # [unresolved-reference] | ^ | - info: `unresolved-reference` was selected on the command line + info: rule `unresolved-reference` was selected on the command line Found 1 diagnostic diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap index 5f9493b6583144..3cdd4bf7acc64b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap @@ -35,6 +35,6 @@ error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr 11 | instance.attr = 1 # error: [invalid-assignment] | ^^^^^^^^^^^^^ | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap index 1d390cd628969a..850a5b3ceaf536 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap @@ -36,6 +36,6 @@ error[invalid-assignment]: Invalid assignment to data descriptor attribute `attr 12 | instance.attr = "wrong" # error: [invalid-assignment] | ^^^^^^^^^^^^^ | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap index d01615b5f92c94..9b62b2d7ffde53 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap @@ -36,7 +36,7 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 7 | 8 | C.attr = 1 # fine | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` @@ -48,6 +48,6 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 9 | C.attr = "wrong" # error: [invalid-assignment] | ^^^^^^ | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap index 8b89349a116a88..369a67a25667bc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap @@ -36,7 +36,7 @@ warning[possibly-unbound-attribute]: Attribute `attr` on type `` is p 7 | 8 | instance = C() | -info: `possibly-unbound-attribute` is enabled by default +info: rule `possibly-unbound-attribute` is enabled by default ``` @@ -48,6 +48,6 @@ warning[possibly-unbound-attribute]: Attribute `attr` on type `C` is possibly un 9 | instance.attr = 1 # error: [possibly-unbound-attribute] | ^^^^^^^^^^^^^ | -info: `possibly-unbound-attribute` is enabled by default +info: rule `possibly-unbound-attribute` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap index 74aa6f471b6160..27df8aba9674ed 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap @@ -36,7 +36,7 @@ error[invalid-assignment]: Object of type `Literal["wrong"]` is not assignable t 8 | 9 | C.attr = 1 # error: [invalid-attribute-access] | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` @@ -49,6 +49,6 @@ error[invalid-attribute-access]: Cannot assign to instance attribute `attr` from 9 | C.attr = 1 # error: [invalid-attribute-access] | ^^^^^^ | -info: `invalid-attribute-access` is enabled by default +info: rule `invalid-attribute-access` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap index 56edf828e9ece3..7491ce72aecb3a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap @@ -46,6 +46,6 @@ error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attr 12 | 13 | class C2: | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap index c17f11fdf57157..c7beb2d87fba43 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap @@ -33,7 +33,7 @@ error[unresolved-attribute]: Unresolved attribute `non_existent` on type ` int) | None`, which is not callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap index b8f211480c20a6..ba0f58ca09c5a3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap @@ -54,7 +54,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable | info: It has no `__iter__` method and its `__getitem__` attribute is invalid info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -83,7 +83,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable | info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap index fc7be2bc200550..ca18f1184a4766 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap @@ -58,7 +58,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable info: Its `__iter__` method may have an invalid signature info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)` info: Expected signature for `__iter__` is `def __iter__(self): ...` -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -87,7 +87,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable 30 | reveal_type(x) # revealed: int | Unknown | info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap index 32164a51f389f6..91c7d40dbd8c3a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap @@ -61,7 +61,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable | info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method info: Expected signature for `__next__` is `def __next__(self): ...`) -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -90,7 +90,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable 34 | reveal_type(y) # revealed: int | Unknown | info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap index ca1328ccaa52af..c7ec49090296da 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap @@ -46,7 +46,7 @@ error[not-iterable]: Object of type `Iterable` may not be iterable | info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 8cf36a396095e9..9d09acef590b3b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -64,7 +64,7 @@ error[not-iterable]: Object of type `Iterable1` may not be iterable 33 | reveal_type(x) # revealed: bytes | str | Unknown | info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -93,7 +93,7 @@ error[not-iterable]: Object of type `Iterable2` may not be iterable | info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap index 6441d320082c0c..48013f59483b09 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap @@ -44,7 +44,7 @@ error[not-iterable]: Object of type `Iterable` may not be iterable 18 | reveal_type(x) # revealed: int | bytes | info: It may not have an `__iter__` method or a `__getitem__` method -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap index 69f9a2bad7215b..e7ab06ed09bcc7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap @@ -46,7 +46,7 @@ error[not-iterable]: Object of type `Test | Test2` may not be iterable 19 | reveal_type(x) # revealed: int | info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap index 1b92f8f9902e3f..8eb966da9ddb4d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap @@ -41,7 +41,7 @@ error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable 14 | reveal_type(x) # revealed: int | info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap index e7b32c95d66751..3e5d3bf4ebf204 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap @@ -42,7 +42,7 @@ error[not-iterable]: Object of type `NotIterable` is not iterable 12 | pass | info: Its `__iter__` attribute has type `int | None`, which is not callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -55,7 +55,7 @@ info[possibly-unresolved-reference]: Name `x` used when possibly not defined 16 | reveal_type(x) | ^ | -info: `possibly-unresolved-reference` is enabled by default +info: rule `possibly-unresolved-reference` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap index 34820107ceecdd..25942db89e132d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap @@ -35,7 +35,7 @@ error[not-iterable]: Object of type `Bad` is not iterable 9 | reveal_type(x) # revealed: Unknown | info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap index 2a515bad22c97a..e37675bbc28061 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap @@ -40,7 +40,7 @@ error[not-iterable]: Object of type `Iterable` is not iterable | info: Its `__iter__` method has an invalid signature info: Expected signature `def __iter__(self): ...` -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap index 362ad58707288c..b5c24e5283a44d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap @@ -51,7 +51,7 @@ error[not-iterable]: Object of type `Iterable1` is not iterable | info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method info: Expected signature for `__next__` is `def __next__(self): ...` -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` @@ -79,7 +79,7 @@ error[not-iterable]: Object of type `Iterable2` is not iterable 24 | reveal_type(y) # revealed: Unknown | info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap index c5db7e159d2121..6225e86e0369f1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap @@ -86,6 +86,6 @@ info: Type variable defined here 5 | 6 | def f(x: T) -> T: | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap index c205f38f3f1c79..a965047627168f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap @@ -101,6 +101,6 @@ info: Type variable defined here 5 | 6 | def f(x: T) -> T: | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap index 87178f460d5ee9..fc5bb331f1b0e6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap @@ -82,6 +82,6 @@ info: Type variable defined here | ^^^^^^ 4 | return x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap index a5f3b93c2d9ea7..8a961a23fcf5ea 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap @@ -97,6 +97,6 @@ info: Type variable defined here | ^^^^^^^^^^^^^^ 4 | return x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap index 76cc458b19fe0c..ac85e00fa018c4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap @@ -32,6 +32,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^ | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap index 8cebbee51ecc38..2dbdf7c5250e4d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap index 8665bb6c745501..e0483e581db394 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap @@ -38,6 +38,6 @@ info: Function defined here | ^^^^^^ ------ Parameter declared here 3 | return x * x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap index e5b7be00b9d2fb..f192c9723a836d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap @@ -42,6 +42,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap index a673f81a1296ad..672717075387db 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap @@ -40,6 +40,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 5 | return x * x | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap index f0b5a683cbdaad..d26e1fe2f9c0af 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap index e55a5c9dcfe0ca..e9e1198983d2e8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap @@ -44,6 +44,6 @@ info: Function defined here 4 | z: int, 5 | ) -> int: | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap index 22fa78f1731a71..6aa878c8df62ee 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap @@ -39,7 +39,7 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -59,7 +59,7 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -79,6 +79,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap index bb31cfe7e92172..a608b1f7de9cb4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap @@ -40,6 +40,6 @@ info: Function defined here 41 | *, 42 | cls: type[JSONDecoder] | None = None, | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap index 60f4937c29e011..640a3f0d5c84a4 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap index e7779170d0990b..b2b1b00b55a809 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap index 7f93af33e2733b..a43e5fa7f870d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ---------- Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap index 2839c5399bee1b..c2647c7550542b 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------ Parameter declared here 2 | return x * y * z | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap index 125d0b09d773b3..a21d9085a59bb9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap @@ -38,6 +38,6 @@ info: Function defined here | ^^^^^^^^ ------ Parameter declared here 3 | return 1 | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap index dc4b6b73a50d56..bd74bf3b8db79a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ ------------- Parameter declared here 2 | return len(numbers) | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap index da318f03e652ba..fe922058743a84 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap @@ -36,6 +36,6 @@ info: Function defined here | ^^^ -------------- Parameter declared here 2 | return len(numbers) | -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap index 3ab4b39aae970c..d6698b03f4b34f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap @@ -38,7 +38,7 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 11 | 10 not in WithContains() | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` @@ -52,6 +52,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index 54f2deb8af6cac..fceb6462c89ecd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -122,7 +122,7 @@ info: The definition of class `Foo` will raise `TypeError` at runtime 4 | 5 | reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -174,7 +174,7 @@ info: The definition of class `Ham` will raise `TypeError` at runtime 22 | Eggs, 23 | ): ... | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -211,7 +211,7 @@ info: The definition of class `Ham` will raise `TypeError` at runtime | ^^^^ Class `Eggs` later repeated here 23 | ): ... | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -250,7 +250,7 @@ info: The definition of class `Omelette` will raise `TypeError` at runtime 31 | 32 | reveal_type(Omelette.__mro__) # revealed: tuple[, Unknown, ] | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -309,7 +309,7 @@ info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runti | ^^^^ Class `Eggs` later repeated here 47 | ): ... | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -340,7 +340,7 @@ info: The definition of class `D` will raise `TypeError` at runtime | ^ Class `A` later repeated here 73 | ): ... | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` @@ -383,7 +383,7 @@ info: The definition of class `E` will raise `TypeError` at runtime 79 | ): 80 | # error: [unused-ignore-comment] | -info: `duplicate-base` is enabled by default +info: rule `duplicate-base` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap index d438d5967fed8e..4f3f262bcb6d82 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap @@ -24,6 +24,6 @@ error[no-matching-overload]: No overload of class `type` matches arguments 1 | type("Foo", ()) # error: [no-matching-overload] | ^^^^^^^^^^^^^^^ | -info: `no-matching-overload` is enabled by default +info: rule `no-matching-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap index 547db0bcf087a6..defb8528ec745e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap @@ -30,6 +30,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap index 32b3b92d714dc9..e8d42bc20c3b8d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap @@ -47,7 +47,7 @@ error[invalid-overload]: Overloaded function `func` requires at least two overlo | ^^^^ 8 | return x | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -62,6 +62,6 @@ error[invalid-overload]: Overloaded function `func` requires at least two overlo | | | Only one overload defined here | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap index ab0e933c5817b4..a2e027157665f6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap @@ -86,7 +86,7 @@ error[invalid-overload]: Overloaded function `try_from1` does not use the `@clas 17 | if isinstance(x, int): 18 | return cls(x) | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -109,7 +109,7 @@ error[invalid-overload]: Overloaded function `try_from2` does not use the `@clas 23 | @overload 24 | @classmethod | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -126,6 +126,6 @@ error[invalid-overload]: Overloaded function `try_from3` does not use the `@clas 41 | if isinstance(x, int): 42 | return cls(x) | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap index 47a0c2744c0c75..6799a92a57f382 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap @@ -76,7 +76,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo | Implementation defined here 19 | return x | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -92,7 +92,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo | Implementation defined here 28 | return x | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -109,6 +109,6 @@ error[invalid-overload]: `@final` decorator should be applied only to the first 15 | def method2(self, x: str) -> str: ... | ^^^^^^^ | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap index 2577e2ddf0375a..80ff3214b4b02d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap @@ -93,7 +93,7 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove | Implementation defined here 28 | return x | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -109,7 +109,7 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove | Implementation defined here 38 | return x | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -127,6 +127,6 @@ error[invalid-overload]: `@override` decorator should be applied only to the fir 22 | def method(self, x: str) -> str: ... | ^^^^^^ | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap index 9ced86c4f4d96e..21e0603051ed7e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap @@ -41,7 +41,7 @@ error[invalid-overload]: Overloaded non-stub function `func` must have an implem 8 | 9 | class Foo: | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` @@ -54,6 +54,6 @@ error[invalid-overload]: Overloaded non-stub function `method` must have an impl 14 | def method(self, x: str) -> str: ... | ^^^^^^ | -info: `invalid-overload` is enabled by default +info: rule `invalid-overload` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap index 8a65752dc49b44..3553f119c22420 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap @@ -51,7 +51,7 @@ error[call-non-callable]: Object of type `typing.Protocol` is not callable 5 | 6 | class MyProtocol(Protocol): | -info: `call-non-callable` is enabled by default +info: rule `call-non-callable` is enabled by default ``` @@ -87,7 +87,7 @@ info: Protocol classes cannot be instantiated | ^^^^^^^^^^^^^^^^^^^^ `MyProtocol` declared as a protocol here 7 | x: int | -info: `call-non-callable` is enabled by default +info: rule `call-non-callable` is enabled by default ``` @@ -122,7 +122,7 @@ info: Protocol classes cannot be instantiated | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol` declared as a protocol here 13 | x: T | -info: `call-non-callable` is enabled by default +info: rule `call-non-callable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap index c22ff204fc0b3c..8d4d21634abc46 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap @@ -52,7 +52,7 @@ info: `NotAProtocol` is declared here, but it is not a protocol class: | info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol` info: See https://typing.python.org/en/latest/spec/protocol.html# -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -79,6 +79,6 @@ info: `AlsoNotAProtocol` is declared here, but it is not a protocol class: | info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol` info: See https://typing.python.org/en/latest/spec/protocol.html# -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap index dd7628a74d57c7..65263835c3846a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap @@ -77,7 +77,7 @@ info: `HasX` is declared as a protocol class, but it is not declared as runtime- | info: A protocol class can only be used in `isinstance` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable` info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -131,7 +131,7 @@ info: `HasX` is declared as a protocol class, but it is not declared as runtime- | info: A protocol class can only be used in `issubclass` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable` info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap index bcec9763b3c105..fc5b1bc06a36db 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap @@ -66,7 +66,7 @@ error[invalid-return-type]: Return type does not match returned value | info: Function is inferred as returning `types.GeneratorType` because it is a generator function info: See https://docs.python.org/3/glossary.html#term-generator for more details -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -82,6 +82,6 @@ error[invalid-return-type]: Return type does not match returned value | info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap index 817169603d8704..e7ff634eb16a0f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap @@ -45,7 +45,7 @@ error[invalid-return-type]: Return type does not match returned value 7 | 8 | def f(cond: bool) -> str: | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -64,7 +64,7 @@ error[invalid-return-type]: Return type does not match returned value 12 | else: 13 | # error: [invalid-return-type] | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -86,6 +86,6 @@ error[invalid-return-type]: Return type does not match returned value 9 | if cond: 10 | # error: [invalid-return-type] | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap index 3a97bc897bfdde..9e47f142004565 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap @@ -52,7 +52,7 @@ error[invalid-return-type]: Return type does not match returned value 5 | 6 | # error: [invalid-return-type] | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -66,7 +66,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 8 | if cond: 9 | return 1 | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -80,7 +80,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 13 | if cond: 14 | raise ValueError() | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -94,6 +94,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 18 | if cond: 19 | cond = False | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index ced45db21600e4..c9e8134c2bcbac 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -43,7 +43,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not | ^^^ 3 | 1 | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -61,7 +61,7 @@ error[invalid-return-type]: Return type does not match returned value 8 | 9 | def f() -> int: | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -79,7 +79,7 @@ error[invalid-return-type]: Return type does not match returned value 12 | 13 | from typing import TypeVar | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -91,6 +91,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 18 | def m(x: T) -> T: ... | ^ | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap index f67b82d1b44728..3cd2fc5fcff989 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap @@ -41,7 +41,7 @@ error[invalid-return-type]: Return type does not match returned value 4 | 5 | # error: [invalid-return-type] | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -55,7 +55,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 7 | print("...") 8 | ... | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` @@ -69,6 +69,6 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 12 | f"""{foo} is a function that ...""" 13 | ... | -info: `invalid-return-type` is enabled by default +info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap index 2d0ac7ef1eaf69..e40ecc83614ded 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap @@ -43,7 +43,7 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 14 | 10 < Comparable() < Comparable() | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` @@ -59,6 +59,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 16 | Comparable() < Comparable() # fine | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap index 8dbd678be7accb..5109c05d9a7736 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap @@ -29,6 +29,6 @@ error[invalid-assignment]: Implicit shadowing of class `C` | ^ | info: Annotate to make it explicit if this is intentional -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap index ed53f7409aa735..c9e3b2b326195f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap @@ -29,6 +29,6 @@ error[invalid-assignment]: Implicit shadowing of function `f` | ^ | info: Annotate to make it explicit if this is intentional -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap index ca136e3af056ad..e8fe6a5285d026 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -44,6 +44,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` 17 | a < b # fine | info: `__bool__` on `NotBoolable | Literal[False]` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap index a51d35051472f6..10cc6b703f850e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -34,6 +34,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^^^^^^^^^^^^^^^^ | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap index 130dca3db651c0..f332f243c39269 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap @@ -50,7 +50,7 @@ info: Function defined here | info: Union variant `def f2(name: str) -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int)` -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -65,6 +65,6 @@ error[too-many-positional-arguments]: Too many positional arguments to function | info: Union variant `def f1() -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int)` -info: `too-many-positional-arguments` is enabled by default +info: rule `too-many-positional-arguments` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap index 61f3d7c0c585e5..4c278cbf2d12bc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap @@ -49,6 +49,6 @@ info: Function defined here | info: Union variant `def f2(name: str) -> int` is incompatible with this call site info: Attempted to call union type `(def f1(a: int) -> int) | (def f2(name: str) -> int)` -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap index 82c3a86de9c8c4..1224cb6610f4b9 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap @@ -41,7 +41,7 @@ error[parameter-already-assigned]: Multiple values provided for parameter `name` | info: Union variant `def f1(name: str) -> int` is incompatible with this call site info: Attempted to call union type `(def f1(name: str) -> int) | (def any(*args, **kwargs) -> int)` -info: `parameter-already-assigned` is enabled by default +info: rule `parameter-already-assigned` is enabled by default ``` @@ -56,6 +56,6 @@ error[unknown-argument]: Argument `unknown` does not match any known parameter o | info: Union variant `def f1(name: str) -> int` is incompatible with this call site info: Attempted to call union type `(def f1(name: str) -> int) | (def any(*args, **kwargs) -> int)` -info: `unknown-argument` is enabled by default +info: rule `unknown-argument` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap index 80b8aa20191813..b8924b5134e6d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap @@ -75,7 +75,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable | info: Union variant `Literal[5]` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `call-non-callable` is enabled by default +info: rule `call-non-callable` is enabled by default ``` @@ -90,7 +90,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable ( | info: Union variant `PossiblyNotCallable` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `call-non-callable` is enabled by default +info: rule `call-non-callable` is enabled by default ``` @@ -105,7 +105,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func | info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `missing-argument` is enabled by default +info: rule `missing-argument` is enabled by default ``` @@ -120,7 +120,7 @@ error[no-matching-overload]: No overload of method wrapper `__get__` of function | info: Union variant `` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `no-matching-overload` is enabled by default +info: rule `no-matching-overload` is enabled by default ``` @@ -144,7 +144,7 @@ info: Function defined here | info: Union variant `def f2(name: str) -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -168,7 +168,7 @@ info: Type variable defined here | info: Union variant `def f4(x: T) -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `invalid-argument-type` is enabled by default +info: rule `invalid-argument-type` is enabled by default ``` @@ -183,6 +183,6 @@ error[too-many-positional-arguments]: Too many positional arguments to function | info: Union variant `def f1() -> int` is incompatible with this call site info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | () | PossiblyNotCallable` -info: `too-many-positional-arguments` is enabled by default +info: rule `too-many-positional-arguments` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap index f91f2bac36a982..55d6a1a7ea7e53 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Not enough values to unpack | | | Expected 2 | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap index 8ce8075bd8417a..8aad685ea46f98 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Too many values to unpack | | | Expected 2 | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap index 5b1ff5e7c4a932..e26eb8fae48bc1 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap @@ -25,6 +25,6 @@ error[not-iterable]: Object of type `Literal[1]` is not iterable | ^ | info: It doesn't have an `__iter__` method or a `__getitem__` method -info: `not-iterable` is enabled by default +info: rule `not-iterable` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap index df56e0790960a0..b20add36c4dc50 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap @@ -26,6 +26,6 @@ error[invalid-assignment]: Not enough values to unpack | | | Expected 3 or more | -info: `invalid-assignment` is enabled by default +info: rule `invalid-assignment` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap index c466235b277697..73ff1ba8ff986a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist` 2 | 3 | x = does_not_exist.foo | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap index 6ddf795bc3d2b7..2972b4a66cb71a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap @@ -31,6 +31,6 @@ error[unresolved-import]: Module `a` has no member `does_not_exist` 1 | from a import does_exist1, does_not_exist, does_exist2 # error: [unresolved-import] | ^^^^^^^^^^^^^^ | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap index 012ee03d727674..7fd8b0c2645523 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist` 2 | 3 | stat = add(10, 15) | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap index ce8f76e18b0e05..5288e0941c30fd 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist.foo.ba 2 | 3 | stat = add(10, 15) | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap index 1295e1cd6172ad..614db7bc41aa46 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap @@ -28,6 +28,6 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist` 2 | 3 | stat = add(10, 15) | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap index 194e613487ce58..ea6e6e88a1ec2e 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap @@ -40,6 +40,6 @@ error[unresolved-import]: Cannot resolve imported module `....foo` 2 | 3 | stat = add(10, 15) | -info: `unresolved-import` is enabled by default +info: rule `unresolved-import` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap index 133c9416f136c3..343aaccc77d287 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap @@ -32,6 +32,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for type ` | ^ | info: `__bool__` on `NotBoolable` must be callable -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap index c32d97b04c346e..09343ef5d62a24 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap @@ -42,6 +42,6 @@ info: `str` is not assignable to `bool` | Method defined here 3 | return "wat" | -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap index 3c8f6dd9189765..9957e4c64f38cf 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap @@ -42,6 +42,6 @@ info: `__bool__` methods must only have a `self` parameter | Method defined here 3 | return False | -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap index 790f9a7bfe957f..22d0cc6adade4f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap @@ -39,6 +39,6 @@ error[unsupported-bool-conversion]: Boolean conversion is unsupported for union 15 | 10 and get() and True | ^^^^^ | -info: `unsupported-bool-conversion` is enabled by default +info: rule `unsupported-bool-conversion` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index dd287fa8eaf271..c166f37aca512c 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -318,10 +318,13 @@ impl Drop for LintDiagnosticGuard<'_, '_> { diag.sub(SubDiagnostic::new( Severity::Info, match self.source { - LintSource::Default => format!("`{}` is enabled by default", diag.id()), - LintSource::Cli => format!("`{}` was selected on the command line", diag.id()), + LintSource::Default => format!("rule `{}` is enabled by default", diag.id()), + LintSource::Cli => format!("rule `{}` was selected on the command line", diag.id()), LintSource::File => { - format!("`{}` was selected in the configuration file", diag.id()) + format!( + "rule `{}` was selected in the configuration file", + diag.id() + ) } }, )); From be6ec613dbe7674eaae5714c3d734008ed5f34c5 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 12 May 2025 14:28:14 +0200 Subject: [PATCH 044/487] [ty] Fix incorrect type of `src.root` in documentation (#18040) --- crates/ty/docs/configuration.md | 6 +++--- crates/ty_project/src/metadata/options.rs | 6 +++--- ty.schema.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 4f4365a7f69fd6..150e2ea16ca81f 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -162,17 +162,17 @@ typeshed = "/path/to/custom/typeshed" #### `root` -The root(s) of the project, used for finding first-party modules. +The root of the project, used for finding first-party modules. **Default value**: `[".", "./src"]` -**Type**: `list[str]` +**Type**: `str` **Example usage** (`pyproject.toml`): ```toml [tool.ty.src] -root = ["./app"] +root = "./app" ``` --- diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 730a7283ef00bf..44428e4d1b66fb 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -352,13 +352,13 @@ pub struct EnvironmentOptions { #[serde(rename_all = "kebab-case", deny_unknown_fields)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct SrcOptions { - /// The root(s) of the project, used for finding first-party modules. + /// The root of the project, used for finding first-party modules. #[serde(skip_serializing_if = "Option::is_none")] #[option( default = r#"[".", "./src"]"#, - value_type = "list[str]", + value_type = "str", example = r#" - root = ["./app"] + root = "./app" "# )] pub root: Option, diff --git a/ty.schema.json b/ty.schema.json index 783d34fb5ac66b..5d54f5b2bcf062 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -839,7 +839,7 @@ "type": "object", "properties": { "root": { - "description": "The root(s) of the project, used for finding first-party modules.", + "description": "The root of the project, used for finding first-party modules.", "type": [ "string", "null" From 797eb709044d0a9edf02f2a974e2a2fb13f499d8 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 12 May 2025 14:41:00 +0200 Subject: [PATCH 045/487] disable jemalloc on android (#18033) --- crates/ruff/Cargo.toml | 2 +- crates/ruff/src/main.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index c740f5882a8239..727726b3d14ee7 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -84,7 +84,7 @@ dist = true [target.'cfg(target_os = "windows")'.dependencies] mimalloc = { workspace = true } -[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies] +[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies] tikv-jemallocator = { workspace = true } [lints] diff --git a/crates/ruff/src/main.rs b/crates/ruff/src/main.rs index b33260bf36042b..6957afb9d5c0c2 100644 --- a/crates/ruff/src/main.rs +++ b/crates/ruff/src/main.rs @@ -15,6 +15,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), + not(target_os = "android"), any( target_arch = "x86_64", target_arch = "aarch64", From 6f8f7506b47c928b396bd846be62dd707fa4d020 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama <45118249+mtshiba@users.noreply.github.com> Date: Mon, 12 May 2025 22:44:00 +0900 Subject: [PATCH 046/487] [ty] fix infinite recursion bug in `is_disjoint_from` (#18043) ## Summary I found this bug while working on #18041. The following code leads to infinite recursion. ```python from ty_extensions import is_disjoint_from, static_assert, TypeOf class C: @property def prop(self) -> int: return 1 static_assert(not is_disjoint_from(int, TypeOf[C.prop])) ``` The cause is a trivial missing binding in `is_disjoint_from`. This PR fixes the bug and adds a test case (this is a simple fix and may not require a new test case?). ## Test Plan A new test case is added to `mdtest/type_properties/is_disjoint_from.md`. --- .../type_properties/is_disjoint_from.md | 23 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 8 ++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index 2671887d954104..bc9d2b9c27d8ad 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -375,6 +375,29 @@ class UsesMeta2(metaclass=Meta2): ... static_assert(is_disjoint_from(type[UsesMeta1], type[UsesMeta2])) ``` +### `property` + +```py +from ty_extensions import is_disjoint_from, static_assert, TypeOf +from typing import final + +class C: + @property + def prop(self) -> int: + return 1 + +reveal_type(C.prop) # revealed: property + +@final +class D: + pass + +static_assert(not is_disjoint_from(int, TypeOf[C.prop])) +static_assert(not is_disjoint_from(TypeOf[C.prop], int)) +static_assert(is_disjoint_from(TypeOf[C.prop], D)) +static_assert(is_disjoint_from(D, TypeOf[C.prop])) +``` + ## Callables No two callable types are disjoint because there exists a non-empty callable type diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9dc4b4cc6d3669..fdbd76539d1b5e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2101,9 +2101,11 @@ impl<'db> Type<'db> { instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db)) } - (Type::PropertyInstance(_), _) | (_, Type::PropertyInstance(_)) => KnownClass::Property - .to_instance(db) - .is_disjoint_from(db, other), + (Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => { + KnownClass::Property + .to_instance(db) + .is_disjoint_from(db, other) + } (Type::BoundSuper(_), Type::BoundSuper(_)) => !self.is_equivalent_to(db, other), (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super From 6b3ff6f5b82fe2cc45144450886aec8288df64e7 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 12:16:59 -0300 Subject: [PATCH 047/487] [`flake8-pie`] Mark autofix for `PIE804` as unsafe if the dictionary contains comments (#18046) ## Summary Fixes #18036 --- .../test/fixtures/flake8_pie/PIE804.py | 11 ++++++ .../rules/unnecessary_dict_kwargs.rs | 36 +++++++++++++++++-- ...__flake8_pie__tests__PIE804_PIE804.py.snap | 35 ++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE804.py b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE804.py index a8233d2917f922..b6229ed24ca1f3 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE804.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE804.py @@ -28,3 +28,14 @@ # Some values need to be parenthesized. abc(foo=1, **{'bar': (bar := 1)}) # PIE804 abc(foo=1, **{'bar': (yield 1)}) # PIE804 + +# https://github.com/astral-sh/ruff/issues/18036 +# The autofix for this is unsafe due to the comments inside the dictionary. +foo( + **{ + # Comment 1 + "x": 1.0, + # Comment 2 + "y": 2.0, + } +) diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs index f0f9ba7c598c91..b06744c58eee26 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr}; @@ -19,6 +19,7 @@ use crate::fix::edits::{remove_argument, Parentheses}; /// arguments directly. /// /// ## Example +/// /// ```python /// def foo(bar): /// return bar + 1 @@ -28,6 +29,7 @@ use crate::fix::edits::{remove_argument, Parentheses}; /// ``` /// /// Use instead: +/// /// ```python /// def foo(bar): /// return bar + 1 @@ -36,6 +38,26 @@ use crate::fix::edits::{remove_argument, Parentheses}; /// print(foo(bar=2)) # prints 3 /// ``` /// +/// ## Fix safety +/// +/// This rule's fix is marked as unsafe for dictionaries with comments interleaved between +/// the items, as comments may be removed. +/// +/// For example, the fix would be marked as unsafe in the following case: +/// +/// ```python +/// foo( +/// **{ +/// # comment +/// "x": 1.0, +/// # comment +/// "y": 2.0, +/// } +/// ) +/// ``` +/// +/// as this is converted to `foo(x=1.0, y=2.0)` without any of the comments. +/// /// ## References /// - [Python documentation: Dictionary displays](https://docs.python.org/3/reference/expressions.html#dictionary-displays) /// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls) @@ -113,7 +135,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) { .iter() .all(|kwarg| !duplicate_keywords.contains(kwarg)) { - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + let edit = Edit::range_replacement( kwargs .iter() .zip(dict.iter_values()) @@ -134,7 +156,15 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) { }) .join(", "), keyword.range(), - ))); + ); + diagnostic.set_fix(Fix::applicable_edit( + edit, + if checker.comment_ranges().intersects(dict.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE804_PIE804.py.snap b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE804_PIE804.py.snap index b26502fe8ccf7a..68e4f648e62bf8 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE804_PIE804.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE804_PIE804.py.snap @@ -208,6 +208,8 @@ PIE804.py:29:12: PIE804 [*] Unnecessary `dict` kwargs 29 |-abc(foo=1, **{'bar': (bar := 1)}) # PIE804 29 |+abc(foo=1, bar=(bar := 1)) # PIE804 30 30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804 +31 31 | +32 32 | # https://github.com/astral-sh/ruff/issues/18036 PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs | @@ -215,6 +217,8 @@ PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs 29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804 30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804 | ^^^^^^^^^^^^^^^^^^^^ PIE804 +31 | +32 | # https://github.com/astral-sh/ruff/issues/18036 | = help: Remove unnecessary kwargs @@ -224,3 +228,34 @@ PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs 29 29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804 30 |-abc(foo=1, **{'bar': (yield 1)}) # PIE804 30 |+abc(foo=1, bar=(yield 1)) # PIE804 +31 31 | +32 32 | # https://github.com/astral-sh/ruff/issues/18036 +33 33 | # The autofix for this is unsafe due to the comments inside the dictionary. + +PIE804.py:35:5: PIE804 [*] Unnecessary `dict` kwargs + | +33 | # The autofix for this is unsafe due to the comments inside the dictionary. +34 | foo( +35 | / **{ +36 | | # Comment 1 +37 | | "x": 1.0, +38 | | # Comment 2 +39 | | "y": 2.0, +40 | | } + | |_____^ PIE804 +41 | ) + | + = help: Remove unnecessary kwargs + +ℹ Unsafe fix +32 32 | # https://github.com/astral-sh/ruff/issues/18036 +33 33 | # The autofix for this is unsafe due to the comments inside the dictionary. +34 34 | foo( +35 |- **{ +36 |- # Comment 1 +37 |- "x": 1.0, +38 |- # Comment 2 +39 |- "y": 2.0, +40 |- } + 35 |+ x=1.0, y=2.0 +41 36 | ) From 3ccc0edfe4623d9f558d8133d74939418957a2d3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 12 May 2025 11:52:55 -0400 Subject: [PATCH 048/487] Add comma to panic message (#18048) ## Summary Consistent with other variants of this, separate the conditional clause. --- crates/ty_project/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index 547f80bf354bae..8385004cf2f3c2 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -605,7 +605,7 @@ where "This indicates a bug in ty.", )); - let report_message = "If you could open an issue at https://github.com/astral-sh/ty/issues/new?title=%5Bpanic%5D we'd be very appreciative!"; + let report_message = "If you could open an issue at https://github.com/astral-sh/ty/issues/new?title=%5Bpanic%5D, we'd be very appreciative!"; diagnostic.sub(SubDiagnostic::new(Severity::Info, report_message)); diagnostic.sub(SubDiagnostic::new( Severity::Info, From bdccb37b4ae17606015d4c03e04f1dd6f1d6f4da Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 12 May 2025 13:48:54 -0400 Subject: [PATCH 049/487] [ty] Apply function specialization to all overloads (#18020) Function literals have an optional specialization, which is applied to the parameter/return type annotations lazily when the function's signature is requested. We were previously only applying this specialization to the final overload of an overloaded function. This manifested most visibly for `list.__add__`, which has an overloaded definition in the typeshed: https://github.com/astral-sh/ruff/blob/b398b8363104347fe80f1d5241718f90fb637f84/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi#L1069-L1072 Closes https://github.com/astral-sh/ty/issues/314 --- .../mdtest/generics/legacy/classes.md | 22 ++++ .../mdtest/generics/pep695/classes.md | 20 +++ crates/ty_python_semantic/src/types.rs | 115 +++++++----------- .../ty_python_semantic/src/types/display.rs | 18 +-- .../src/types/signatures.rs | 21 +++- 5 files changed, 110 insertions(+), 86 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index e101384ce6a4f1..380452f6c26f82 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -454,6 +454,28 @@ reveal_type(d.method3()) # revealed: SomeProtocol[int] reveal_type(d.method3().x) # revealed: int ``` +When a method is overloaded, the specialization is applied to all overloads. + +```py +from typing import overload, Generic, TypeVar + +S = TypeVar("S") + +class WithOverloadedMethod(Generic[T]): + @overload + def method(self, x: T) -> T: + return x + + @overload + def method(self, x: S) -> S | T: + return x + + def method(self, x: S | T) -> S | T: + return x + +reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] +``` + ## Cyclic class definitions ### F-bounded quantification diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index b342137e7ffa89..c5d4772d362f32 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -347,6 +347,26 @@ reveal_type(c.method2()) # revealed: str reveal_type(c.method3()) # revealed: LinkedList[int] ``` +When a method is overloaded, the specialization is applied to all overloads. + +```py +from typing import overload + +class WithOverloadedMethod[T]: + @overload + def method(self, x: T) -> T: + return x + + @overload + def method[S](self, x: S) -> S | T: + return x + + def method[S](self, x: S | T) -> S | T: + return x + +reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] +``` + ## Cyclic class definitions ### F-bounded quantification diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index fdbd76539d1b5e..754f607512c1b7 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3418,16 +3418,10 @@ impl<'db> Type<'db> { Type::BoundMethod(bound_method) => { let signature = bound_method.function(db).signature(db); - Signatures::single(match signature { - FunctionSignature::Single(signature) => { - CallableSignature::single(self, signature.clone()) - .with_bound_type(bound_method.self_instance(db)) - } - FunctionSignature::Overloaded(signatures, _) => { - CallableSignature::from_overloads(self, signatures.iter().cloned()) - .with_bound_type(bound_method.self_instance(db)) - } - }) + Signatures::single( + CallableSignature::from_overloads(self, signature.overloads.iter().cloned()) + .with_bound_type(bound_method.self_instance(db)), + ) } Type::MethodWrapper( @@ -3785,14 +3779,7 @@ impl<'db> Type<'db> { Signatures::single(signature) } - _ => Signatures::single(match function_type.signature(db) { - FunctionSignature::Single(signature) => { - CallableSignature::single(self, signature.clone()) - } - FunctionSignature::Overloaded(signatures, _) => { - CallableSignature::from_overloads(self, signatures.iter().cloned()) - } - }), + _ => Signatures::single(function_type.signature(db).overloads.clone()), }, Type::ClassLiteral(class) => match class.known(db) { @@ -6561,46 +6548,21 @@ bitflags! { } } -/// A function signature, which can be either a single signature or an overloaded signature. +/// A function signature, which optionally includes an implementation signature if the function is +/// overloaded. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) enum FunctionSignature<'db> { - /// A single function signature. - Single(Signature<'db>), - - /// An overloaded function signature containing the `@overload`-ed signatures and an optional - /// implementation signature. - Overloaded(Vec>, Option>), +pub(crate) struct FunctionSignature<'db> { + pub(crate) overloads: CallableSignature<'db>, + pub(crate) implementation: Option>, } impl<'db> FunctionSignature<'db> { - /// Returns a slice of all signatures. - /// - /// For an overloaded function, this only includes the `@overload`-ed signatures and not the - /// implementation signature. - pub(crate) fn as_slice(&self) -> &[Signature<'db>] { - match self { - Self::Single(signature) => std::slice::from_ref(signature), - Self::Overloaded(signatures, _) => signatures, - } - } - - /// Returns an iterator over the signatures. - pub(crate) fn iter(&self) -> Iter> { - self.as_slice().iter() - } - /// Returns the "bottom" signature (subtype of all fully-static signatures.) pub(crate) fn bottom(db: &'db dyn Db) -> Self { - Self::Single(Signature::bottom(db)) - } -} - -impl<'db> IntoIterator for &'db FunctionSignature<'db> { - type Item = &'db Signature<'db>; - type IntoIter = Iter<'db, Signature<'db>>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() + FunctionSignature { + overloads: CallableSignature::single(Type::any(), Signature::bottom(db)), + implementation: None, + } } } @@ -6671,7 +6633,7 @@ impl<'db> FunctionType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { Type::Callable(CallableType::from_overloads( db, - self.signature(db).iter().cloned(), + self.signature(db).overloads.iter().cloned(), )) } @@ -6739,20 +6701,32 @@ impl<'db> FunctionType<'db> { /// would depend on the function's AST and rerun for every change in that file. #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> { + let specialization = self.specialization(db); if let Some(overloaded) = self.to_overloaded(db) { - FunctionSignature::Overloaded( - overloaded - .overloads - .iter() - .copied() - .map(|overload| overload.internal_signature(db)) - .collect(), - overloaded - .implementation - .map(|implementation| implementation.internal_signature(db)), - ) + FunctionSignature { + overloads: CallableSignature::from_overloads( + Type::FunctionLiteral(self), + overloaded.overloads.iter().copied().map(|overload| { + overload + .internal_signature(db) + .apply_optional_specialization(db, specialization) + }), + ), + implementation: overloaded.implementation.map(|implementation| { + implementation + .internal_signature(db) + .apply_optional_specialization(db, specialization) + }), + } } else { - FunctionSignature::Single(self.internal_signature(db)) + FunctionSignature { + overloads: CallableSignature::single( + Type::FunctionLiteral(self), + self.internal_signature(db) + .apply_optional_specialization(db, specialization), + ), + implementation: None, + } } } @@ -6774,17 +6748,13 @@ impl<'db> FunctionType<'db> { let index = semantic_index(db, scope.file(db)); GenericContext::from_type_params(db, index, type_params) }); - let mut signature = Signature::from_function( + Signature::from_function( db, generic_context, self.inherited_generic_context(db), definition, function_stmt_node, - ); - if let Some(specialization) = self.specialization(db) { - signature = signature.apply_specialization(db, specialization); - } - signature + ) } pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool { @@ -6854,7 +6824,7 @@ impl<'db> FunctionType<'db> { typevars: &mut FxOrderSet>, ) { let signatures = self.signature(db); - for signature in signatures { + for signature in &signatures.overloads { signature.find_legacy_typevars(db, typevars); } } @@ -7114,6 +7084,7 @@ impl<'db> BoundMethodType<'db> { db, self.function(db) .signature(db) + .overloads .iter() .map(signatures::Signature::bind_self), )) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index b5bc35e0e2a2a5..d0716a6459b9f0 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -10,9 +10,9 @@ use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - CallableType, FunctionSignature, IntersectionType, KnownClass, MethodWrapperKind, Protocol, - StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, - UnionType, WrapperDescriptorKind, + CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType, + SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType, + WrapperDescriptorKind, }; use crate::{Db, FxOrderSet}; @@ -118,8 +118,8 @@ impl Display for DisplayRepresentation<'_> { // the generic type parameters to the signature, i.e. // show `def foo[T](x: T) -> T`. - match signature { - FunctionSignature::Single(signature) => { + match signature.overloads.as_slice() { + [signature] => { write!( f, // "def {name}{specialization}{signature}", @@ -128,7 +128,7 @@ impl Display for DisplayRepresentation<'_> { signature = signature.display(self.db) ) } - FunctionSignature::Overloaded(signatures, _) => { + signatures => { // TODO: How to display overloads? f.write_str("Overload[")?; let mut join = f.join(", "); @@ -146,8 +146,8 @@ impl Display for DisplayRepresentation<'_> { // TODO: use the specialization from the method. Similar to the comment above // about the function specialization, - match function.signature(self.db) { - FunctionSignature::Single(signature) => { + match function.signature(self.db).overloads.as_slice() { + [signature] => { write!( f, "bound method {instance}.{method}{signature}", @@ -156,7 +156,7 @@ impl Display for DisplayRepresentation<'_> { signature = signature.bind_self().display(self.db) ) } - FunctionSignature::Overloaded(signatures, _) => { + signatures => { // TODO: How to display overloads? f.write_str("Overload[")?; let mut join = f.join(", "); diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 7cbead0b751c73..25169d3f570513 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -195,6 +195,10 @@ impl<'db> CallableSignature<'db> { self.overloads.iter() } + pub(crate) fn as_slice(&self) -> &[Signature<'db>] { + self.overloads.as_slice() + } + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { if self.callable_type == before { self.callable_type = after; @@ -309,12 +313,16 @@ impl<'db> Signature<'db> { } } - pub(crate) fn apply_specialization( - &self, + pub(crate) fn apply_optional_specialization( + self, db: &'db dyn Db, - specialization: Specialization<'db>, + specialization: Option>, ) -> Self { - self.apply_type_mapping(db, specialization.type_mapping()) + if let Some(specialization) = specialization { + self.apply_type_mapping(db, specialization.type_mapping()) + } else { + self + } } pub(crate) fn apply_type_mapping<'a>( @@ -1743,7 +1751,10 @@ mod tests { // With no decorators, internal and external signature are the same assert_eq!( func.signature(&db), - &FunctionSignature::Single(expected_sig) + &FunctionSignature { + overloads: CallableSignature::single(Type::FunctionLiteral(func), expected_sig), + implementation: None + }, ); } } From 550b8be5520e9dd780ddd3d58f833f1379cdeee5 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Mon, 12 May 2025 15:07:55 -0400 Subject: [PATCH 050/487] Avoid initializing progress bars early (#18049) ## Summary Resolves https://github.com/astral-sh/ty/issues/324. --- crates/ruff_benchmark/benches/ty.rs | 10 ++++----- crates/ty/src/lib.rs | 34 ++++++++++++++++------------- crates/ty/tests/file_watching.rs | 12 +++------- crates/ty_project/src/db.rs | 17 ++++++++++++--- crates/ty_project/src/lib.rs | 15 ++++++++----- crates/ty_wasm/src/lib.rs | 4 ++-- 6 files changed, 53 insertions(+), 39 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index 7a0fbbf8a1694e..3bb28fe0ce24af 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -16,7 +16,7 @@ use ruff_python_ast::PythonVersion; use ty_project::metadata::options::{EnvironmentOptions, Options}; use ty_project::metadata::value::RangedValue; use ty_project::watch::{ChangeEvent, ChangedKind}; -use ty_project::{Db, DummyReporter, ProjectDatabase, ProjectMetadata}; +use ty_project::{Db, ProjectDatabase, ProjectMetadata}; struct Case { db: ProjectDatabase, @@ -164,7 +164,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { fn setup() -> Case { let case = setup_tomllib_case(); - let result: Vec<_> = case.db.check(&DummyReporter).unwrap(); + let result: Vec<_> = case.db.check().unwrap(); assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); @@ -192,7 +192,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { None, ); - let result = db.check(&DummyReporter).unwrap(); + let result = db.check().unwrap(); assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len()); } @@ -212,7 +212,7 @@ fn benchmark_cold(criterion: &mut Criterion) { setup_tomllib_case, |case| { let Case { db, .. } = case; - let result: Vec<_> = db.check(&DummyReporter).unwrap(); + let result: Vec<_> = db.check().unwrap(); assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); }, @@ -326,7 +326,7 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) { }, |case| { let Case { db, .. } = case; - let result = db.check(&DummyReporter).unwrap(); + let result = db.check().unwrap(); assert_eq!(result.len(), 0); }, BatchSize::SmallInput, diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 584f9acb1b0708..a0b2e1037e61f4 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -216,7 +216,10 @@ impl MainLoop { self.run_with_progress::(db) } - fn run_with_progress(mut self, db: &mut ProjectDatabase) -> Result { + fn run_with_progress(mut self, db: &mut ProjectDatabase) -> Result + where + R: Reporter + Default + 'static, + { self.sender.send(MainLoopMessage::CheckWorkspace).unwrap(); let result = self.main_loop::(db); @@ -226,7 +229,10 @@ impl MainLoop { result } - fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result { + fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result + where + R: Reporter + Default + 'static, + { // Schedule the first check. tracing::debug!("Starting main loop"); @@ -237,12 +243,12 @@ impl MainLoop { MainLoopMessage::CheckWorkspace => { let db = db.clone(); let sender = self.sender.clone(); - let reporter = R::default(); + let mut reporter = R::default(); // Spawn a new task that checks the project. This needs to be done in a separate thread // to prevent blocking the main loop here. rayon::spawn(move || { - match db.check(&reporter) { + match db.check_with_reporter(&mut reporter) { Ok(result) => { // Send the result back to the main loop for printing. sender @@ -353,11 +359,12 @@ impl MainLoop { } /// A progress reporter for `ty check`. -struct IndicatifReporter(indicatif::ProgressBar); +#[derive(Default)] +struct IndicatifReporter(Option); -impl Default for IndicatifReporter { - fn default() -> IndicatifReporter { - let progress = indicatif::ProgressBar::new(0); +impl ty_project::Reporter for IndicatifReporter { + fn set_files(&mut self, files: usize) { + let progress = indicatif::ProgressBar::new(files as u64); progress.set_style( indicatif::ProgressStyle::with_template( "{msg:8.dim} {bar:60.green/dim} {pos}/{len} files", @@ -366,17 +373,14 @@ impl Default for IndicatifReporter { .progress_chars("--"), ); progress.set_message("Checking"); - IndicatifReporter(progress) - } -} -impl ty_project::Reporter for IndicatifReporter { - fn set_files(&self, files: usize) { - self.0.set_length(files as u64); + self.0 = Some(progress); } fn report_file(&self, _file: &ruff_db::files::File) { - self.0.inc(1); + if let Some(ref progress_bar) = self.0 { + progress_bar.inc(1); + } } } diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index 56ff5e2bfc30aa..c8205c925195b3 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -14,7 +14,7 @@ use ty_project::metadata::options::{EnvironmentOptions, Options}; use ty_project::metadata::pyproject::{PyProject, Tool}; use ty_project::metadata::value::{RangedValue, RelativePathBuf}; use ty_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher}; -use ty_project::{Db, DummyReporter, ProjectDatabase, ProjectMetadata}; +use ty_project::{Db, ProjectDatabase, ProjectMetadata}; use ty_python_semantic::{resolve_module, ModuleName, PythonPlatform}; struct TestCase { @@ -1117,10 +1117,7 @@ print(sys.last_exc, os.getegid()) Ok(()) })?; - let diagnostics = case - .db - .check(&DummyReporter) - .context("Failed to check project.")?; + let diagnostics = case.db.check().context("Failed to check project.")?; assert_eq!(diagnostics.len(), 2); assert_eq!( @@ -1145,10 +1142,7 @@ print(sys.last_exc, os.getegid()) }) .expect("Search path settings to be valid"); - let diagnostics = case - .db - .check(&DummyReporter) - .context("Failed to check project.")?; + let diagnostics = case.db.check().context("Failed to check project.")?; assert!(diagnostics.is_empty()); Ok(()) diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index c15f0c99c45fbd..07ae404e22dc37 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -1,7 +1,7 @@ -use std::panic::RefUnwindSafe; +use std::panic::{AssertUnwindSafe, RefUnwindSafe}; use std::sync::Arc; -use crate::DEFAULT_LINT_REGISTRY; +use crate::{DummyReporter, DEFAULT_LINT_REGISTRY}; use crate::{Project, ProjectMetadata, Reporter}; use ruff_db::diagnostic::Diagnostic; use ruff_db::files::{File, Files}; @@ -68,7 +68,18 @@ impl ProjectDatabase { } /// Checks all open files in the project and its dependencies. - pub fn check(&self, reporter: &impl Reporter) -> Result, Cancelled> { + pub fn check(&self) -> Result, Cancelled> { + let mut reporter = DummyReporter; + let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter); + self.with_db(|db| db.project().check(db, reporter)) + } + + /// Checks all open files in the project and its dependencies, using the given reporter. + pub fn check_with_reporter( + &self, + reporter: &mut dyn Reporter, + ) -> Result, Cancelled> { + let reporter = AssertUnwindSafe(reporter); self.with_db(|db| db.project().check(db, reporter)) } diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index 8385004cf2f3c2..4e36f693d58a7e 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -18,7 +18,7 @@ use rustc_hash::FxHashSet; use salsa::Durability; use salsa::Setter; use std::backtrace::BacktraceStatus; -use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe}; +use std::panic::{AssertUnwindSafe, UnwindSafe}; use std::sync::Arc; use thiserror::Error; use tracing::error; @@ -107,9 +107,9 @@ pub struct Project { } /// A progress reporter. -pub trait Reporter: Default + Send + Sync + RefUnwindSafe + 'static { +pub trait Reporter: Send + Sync { /// Initialize the reporter with the number of files. - fn set_files(&self, files: usize); + fn set_files(&mut self, files: usize); /// Report the completion of a given file. fn report_file(&self, file: &File); @@ -120,7 +120,7 @@ pub trait Reporter: Default + Send + Sync + RefUnwindSafe + 'static { pub struct DummyReporter; impl Reporter for DummyReporter { - fn set_files(&self, _files: usize) {} + fn set_files(&mut self, _files: usize) {} fn report_file(&self, _file: &File) {} } @@ -186,7 +186,11 @@ impl Project { } /// Checks all open files in the project and its dependencies. - pub(crate) fn check(self, db: &ProjectDatabase, reporter: &impl Reporter) -> Vec { + pub(crate) fn check( + self, + db: &ProjectDatabase, + mut reporter: AssertUnwindSafe<&mut dyn Reporter>, + ) -> Vec { let project_span = tracing::debug_span!("Project::check"); let _span = project_span.enter(); @@ -215,6 +219,7 @@ impl Project { let db = db.clone(); let file_diagnostics = &file_diagnostics; let project_span = &project_span; + let reporter = &reporter; rayon::scope(move |scope| { for file in &files { diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 2a7cad6f8a267f..9ed86299e808c3 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -18,8 +18,8 @@ use ty_ide::{goto_type_definition, hover, inlay_hints, MarkupKind}; use ty_project::metadata::options::Options; use ty_project::metadata::value::ValueSource; use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind}; +use ty_project::ProjectMetadata; use ty_project::{Db, ProjectDatabase}; -use ty_project::{DummyReporter, ProjectMetadata}; use ty_python_semantic::Program; use wasm_bindgen::prelude::*; @@ -186,7 +186,7 @@ impl Workspace { /// Checks all open files pub fn check(&self) -> Result, Error> { - let result = self.db.check(&DummyReporter).map_err(into_error)?; + let result = self.db.check().map_err(into_error)?; Ok(result.into_iter().map(Diagnostic::wrap).collect()) } From 138ab91def9bdf03191947fa2704452c6528b2e1 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 16:53:08 -0300 Subject: [PATCH 051/487] [`flake8-simplify`] Fix `SIM905` autofix for `rsplit` creating a reversed list literal (#18045) ## Summary Fixes #18042 --- .../test/fixtures/flake8_simplify/SIM905.py | 8 ++- .../rules/split_static_string.rs | 12 +++- ...ke8_simplify__tests__SIM905_SIM905.py.snap | 56 ++++++++++++++++--- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py index 7d767d475385bf..86ef2171949ff7 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py @@ -31,7 +31,7 @@ " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] "".split() # [] -""" +""" """.split() # [] " ".split() # [] "/abc/".split() # ["/abc/"] @@ -73,7 +73,7 @@ # negatives -# invalid values should not cause panic +# invalid values should not cause panic "a,b,c,d".split(maxsplit="hello") "a,b,c,d".split(maxsplit=-"hello") @@ -106,3 +106,7 @@ '''itemC''' "'itemD'" """.split() + +# https://github.com/astral-sh/ruff/issues/18042 +print("a,b".rsplit(",")) +print("a,b,c".rsplit(",", 1)) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs index 4d670aa7f86c38..8c9c863b64d786 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs @@ -186,12 +186,20 @@ fn split_sep( let list_items: Vec<&str> = if let Ok(split_n) = usize::try_from(max_split) { match direction { Direction::Left => value.splitn(split_n + 1, sep_value).collect(), - Direction::Right => value.rsplitn(split_n + 1, sep_value).collect(), + Direction::Right => { + let mut items: Vec<&str> = value.rsplitn(split_n + 1, sep_value).collect(); + items.reverse(); + items + } } } else { match direction { Direction::Left => value.split(sep_value).collect(), - Direction::Right => value.rsplit(sep_value).collect(), + Direction::Right => { + let mut items: Vec<&str> = value.rsplit(sep_value).collect(); + items.reverse(); + items + } } }; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap index 62db77198bc15b..0a3684c5ce71e5 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap @@ -352,7 +352,7 @@ SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split` 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 33 | "".split() # [] -34 | """ +34 | """ | = help: Replace with list literal @@ -363,7 +363,7 @@ SIM905.py:32:1: SIM905 [*] Consider using a list literal instead of `str.split` 32 |-" a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] 32 |+[" a", "a a", "a a "] # [" a", "a a", "a a "] 33 33 | "".split() # [] -34 34 | """ +34 34 | """ 35 35 | """.split() # [] SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split` @@ -371,7 +371,7 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split` 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] 33 | "".split() # [] | ^^^^^^^^^^ SIM905 -34 | """ +34 | """ 35 | """.split() # [] | = help: Replace with list literal @@ -382,7 +382,7 @@ SIM905.py:33:1: SIM905 [*] Consider using a list literal instead of `str.split` 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] 33 |-"".split() # [] 33 |+[] # [] -34 34 | """ +34 34 | """ 35 35 | """.split() # [] 36 36 | " ".split() # [] @@ -390,7 +390,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split` | 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] 33 | "".split() # [] -34 | / """ +34 | / """ 35 | | """.split() # [] | |___________^ SIM905 36 | " ".split() # [] @@ -402,7 +402,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split` 31 31 | 32 32 | " a*a a*a a ".split("*", -1) # [" a", "a a", "a a "] 33 33 | "".split() # [] -34 |-""" +34 |-""" 35 |-""".split() # [] 34 |+[] # [] 36 35 | " ".split() # [] @@ -411,7 +411,7 @@ SIM905.py:34:1: SIM905 [*] Consider using a list literal instead of `str.split` SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split` | -34 | """ +34 | """ 35 | """.split() # [] 36 | " ".split() # [] | ^^^^^^^^^^^^^^^^^ SIM905 @@ -422,7 +422,7 @@ SIM905.py:36:1: SIM905 [*] Consider using a list literal instead of `str.split` ℹ Safe fix 33 33 | "".split() # [] -34 34 | """ +34 34 | """ 35 35 | """.split() # [] 36 |-" ".split() # [] 36 |+[] # [] @@ -442,7 +442,7 @@ SIM905.py:37:1: SIM905 [*] Consider using a list literal instead of `str.split` = help: Replace with list literal ℹ Safe fix -34 34 | """ +34 34 | """ 35 35 | """.split() # [] 36 36 | " ".split() # [] 37 |-"/abc/".split() # ["/abc/"] @@ -854,6 +854,8 @@ SIM905.py:103:1: SIM905 [*] Consider using a list literal instead of `str.split` 107 | | "'itemD'" 108 | | """.split() | |___________^ SIM905 +109 | +110 | # https://github.com/astral-sh/ruff/issues/18042 | = help: Replace with list literal @@ -868,3 +870,39 @@ SIM905.py:103:1: SIM905 [*] Consider using a list literal instead of `str.split` 107 |-"'itemD'" 108 |-""".split() 103 |+['"itemA"', "'itemB'", "'''itemC'''", "\"'itemD'\""] +109 104 | +110 105 | # https://github.com/astral-sh/ruff/issues/18042 +111 106 | print("a,b".rsplit(",")) + +SIM905.py:111:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +110 | # https://github.com/astral-sh/ruff/issues/18042 +111 | print("a,b".rsplit(",")) + | ^^^^^^^^^^^^^^^^^ SIM905 +112 | print("a,b,c".rsplit(",", 1)) + | + = help: Replace with list literal + +ℹ Safe fix +108 108 | """.split() +109 109 | +110 110 | # https://github.com/astral-sh/ruff/issues/18042 +111 |-print("a,b".rsplit(",")) + 111 |+print(["a", "b"]) +112 112 | print("a,b,c".rsplit(",", 1)) + +SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +110 | # https://github.com/astral-sh/ruff/issues/18042 +111 | print("a,b".rsplit(",")) +112 | print("a,b,c".rsplit(",", 1)) + | ^^^^^^^^^^^^^^^^^^^^^^ SIM905 + | + = help: Replace with list literal + +ℹ Safe fix +109 109 | +110 110 | # https://github.com/astral-sh/ruff/issues/18042 +111 111 | print("a,b".rsplit(",")) +112 |-print("a,b,c".rsplit(",", 1)) + 112 |+print(["a,b", "c"]) From c9031ce59f8c41334b90e8b35836722fba7b0ada Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 17:08:12 -0300 Subject: [PATCH 052/487] [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692) ## Summary We can only guarantee the safety of the autofix for number literals, all other cases may change the runtime behaviour of the program or introduce a syntax error. For the cases reported in the issue that would result in a syntax error, I disabled the autofix. Follow-up of #17661. Fixes #16472. ## Test Plan Snapshot tests. --- .../resources/test/fixtures/refurb/FURB116.py | 20 + crates/ruff_linter/src/rules/refurb/mod.rs | 11 + .../refurb/rules/fstring_number_format.rs | 82 +++- ...es__refurb__tests__FURB116_FURB116.py.snap | 368 ++++++++++++------ ...sts__fstring_number_format_python_311.snap | 256 ++++++++++++ 5 files changed, 608 insertions(+), 129 deletions(-) create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py index b95f9147e77acc..6ec754dadc09ce 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py @@ -1,3 +1,6 @@ +import datetime +import sys + num = 1337 def return_num() -> int: @@ -10,6 +13,7 @@ def return_num() -> int: print(oct(1337)[2:]) # FURB116 print(hex(1337)[2:]) # FURB116 print(bin(1337)[2:]) # FURB116 +print(bin(+1337)[2:]) # FURB116 print(bin(return_num())[2:]) # FURB116 (no autofix) print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) @@ -22,3 +26,19 @@ def return_num() -> int: # float and complex numbers should be ignored print(bin(1.0)[2:]) print(bin(3.14j)[2:]) + +d = datetime.datetime.now(tz=datetime.UTC) +# autofix is display-only +print(bin(d)[2:]) +# no autofix for Python 3.11 and earlier, as it introduces a syntax error +print(bin(len("xyz").numerator)[2:]) + +# autofix is display-only +print(bin({0: 1}[0].numerator)[2:]) +# no autofix for Python 3.11 and earlier, as it introduces a syntax error +print(bin(ord("\\").numerator)[2:]) +print(hex(sys +.maxunicode)[2:]) + +# for negatives numbers autofix is display-only +print(bin(-1)[2:]) diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index 1793e0e9649ebc..d841bcafe1cb79 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -89,4 +89,15 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test] + fn fstring_number_format_python_311() -> Result<()> { + let diagnostics = test_path( + Path::new("refurb/FURB116.py"), + &settings::LinterSettings::for_rule(Rule::FStringNumberFormat) + .with_target_version(PythonVersion::PY311), + )?; + assert_messages!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs index 0d18e8d06fb120..5bd2523599b4b7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, Expr, ExprCall, Number}; +use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp}; +use ruff_source_file::find_newline; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -24,6 +25,11 @@ use crate::fix::snippet::SourceCodeSnippet; /// ```python /// print(f"{1337:b}") /// ``` +/// +/// ## Fix safety +/// The fix is only marked as safe for integer literals, all other cases +/// are display-only, as they may change the runtime behaviour of the program +/// or introduce syntax errors. #[derive(ViolationMetadata)] pub(crate) struct FStringNumberFormat { replacement: Option, @@ -121,21 +127,24 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs return; } - // Generate a replacement, if possible. - let replacement = if matches!( - arg, - Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_) - ) { - let inner_source = checker.locator().slice(arg); - - let quote = checker.stylist().quote(); - let shorthand = base.shorthand(); + let maybe_number = if let Some(maybe_number) = arg + .as_unary_op_expr() + .filter(|unary_expr| unary_expr.op == UnaryOp::UAdd) + .map(|unary_expr| &unary_expr.operand) + { + maybe_number + } else { + arg + }; - Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}")) + let applicability = if matches!(maybe_number, Expr::NumberLiteral(_)) { + Applicability::Safe } else { - None + Applicability::DisplayOnly }; + let replacement = try_create_replacement(checker, arg, base); + let mut diagnostic = Diagnostic::new( FStringNumberFormat { replacement: replacement.as_deref().map(SourceCodeSnippet::from_str), @@ -145,15 +154,54 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs ); if let Some(replacement) = replacement { - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - replacement, - subscript.range(), - ))); + let edit = Edit::range_replacement(replacement, subscript.range()); + diagnostic.set_fix(Fix::applicable_edit(edit, applicability)); } checker.report_diagnostic(diagnostic); } +/// Generate a replacement, if possible. +fn try_create_replacement(checker: &Checker, arg: &Expr, base: Base) -> Option { + if !matches!( + arg, + Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_) | Expr::UnaryOp(_) + ) { + return None; + } + + let inner_source = checker.locator().slice(arg); + + // On Python 3.11 and earlier, trying to replace an `arg` that contains a backslash + // would create a `SyntaxError` in the f-string. + if checker.target_version() <= PythonVersion::PY311 && inner_source.contains('\\') { + return None; + } + + // On Python 3.11 and earlier, trying to replace an `arg` that spans multiple lines + // would create a `SyntaxError` in the f-string. + if checker.target_version() <= PythonVersion::PY311 && find_newline(inner_source).is_some() { + return None; + } + + let quote = checker.stylist().quote(); + let shorthand = base.shorthand(); + + // If the `arg` contains double quotes we need to create the f-string with single quotes + // to avoid a `SyntaxError` in Python 3.11 and earlier. + if checker.target_version() <= PythonVersion::PY311 && inner_source.contains(quote.as_str()) { + return None; + } + + // If the `arg` contains a brace add an space before it to avoid a `SyntaxError` + // in the f-string. + if inner_source.starts_with('{') { + Some(format!("f{quote}{{ {inner_source}:{shorthand}}}{quote}")) + } else { + Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}")) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Base { Hex, diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap index a19b474227e6ba..c39547d551468a 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap @@ -1,144 +1,288 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB116.py:6:7: FURB116 [*] Replace `oct` call with `f"{num:o}"` - | -4 | return num -5 | -6 | print(oct(num)[2:]) # FURB116 - | ^^^^^^^^^^^^ FURB116 -7 | print(hex(num)[2:]) # FURB116 -8 | print(bin(num)[2:]) # FURB116 - | - = help: Replace with `f"{num:o}"` +FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"` + | + 7 | return num + 8 | + 9 | print(oct(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:o}"` -ℹ Safe fix -3 3 | def return_num() -> int: -4 4 | return num -5 5 | -6 |-print(oct(num)[2:]) # FURB116 - 6 |+print(f"{num:o}") # FURB116 -7 7 | print(hex(num)[2:]) # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | - -FURB116.py:7:7: FURB116 [*] Replace `hex` call with `f"{num:x}"` - | -6 | print(oct(num)[2:]) # FURB116 -7 | print(hex(num)[2:]) # FURB116 - | ^^^^^^^^^^^^ FURB116 -8 | print(bin(num)[2:]) # FURB116 - | - = help: Replace with `f"{num:x}"` +ℹ Display-only fix +6 6 | def return_num() -> int: +7 7 | return num +8 8 | +9 |-print(oct(num)[2:]) # FURB116 + 9 |+print(f"{num:o}") # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | -ℹ Safe fix -4 4 | return num -5 5 | -6 6 | print(oct(num)[2:]) # FURB116 -7 |-print(hex(num)[2:]) # FURB116 - 7 |+print(f"{num:x}") # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 - -FURB116.py:8:7: FURB116 [*] Replace `bin` call with `f"{num:b}"` - | - 6 | print(oct(num)[2:]) # FURB116 - 7 | print(hex(num)[2:]) # FURB116 - 8 | print(bin(num)[2:]) # FURB116 +FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 | ^^^^^^^^^^^^ FURB116 - 9 | -10 | print(oct(1337)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:x}"` + +ℹ Display-only fix +7 7 | return num +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 |-print(hex(num)[2:]) # FURB116 + 10 |+print(f"{num:x}") # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 + +FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 | = help: Replace with `f"{num:b}"` -ℹ Safe fix -5 5 | -6 6 | print(oct(num)[2:]) # FURB116 -7 7 | print(hex(num)[2:]) # FURB116 -8 |-print(bin(num)[2:]) # FURB116 - 8 |+print(f"{num:b}") # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 - -FURB116.py:10:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` - | - 8 | print(bin(num)[2:]) # FURB116 - 9 | -10 | print(oct(1337)[2:]) # FURB116 +ℹ Display-only fix +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 |-print(bin(num)[2:]) # FURB116 + 11 |+print(f"{num:b}") # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 + +FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` + | +11 | print(bin(num)[2:]) # FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -11 | print(hex(1337)[2:]) # FURB116 -12 | print(bin(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 | = help: Replace with `f"{1337:o}"` ℹ Safe fix -7 7 | print(hex(num)[2:]) # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 |-print(oct(1337)[2:]) # FURB116 - 10 |+print(f"{1337:o}") # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 -12 12 | print(bin(1337)[2:]) # FURB116 -13 13 | - -FURB116.py:11:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` - | -10 | print(oct(1337)[2:]) # FURB116 -11 | print(hex(1337)[2:]) # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 |-print(oct(1337)[2:]) # FURB116 + 13 |+print(f"{1337:o}") # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 + +FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -12 | print(bin(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 | = help: Replace with `f"{1337:x}"` ℹ Safe fix -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 |-print(hex(1337)[2:]) # FURB116 - 11 |+print(f"{1337:x}") # FURB116 -12 12 | print(bin(1337)[2:]) # FURB116 -13 13 | -14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix) - -FURB116.py:12:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` - | -10 | print(oct(1337)[2:]) # FURB116 -11 | print(hex(1337)[2:]) # FURB116 -12 | print(bin(1337)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 |-print(hex(1337)[2:]) # FURB116 + 14 |+print(f"{1337:x}") # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | + +FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -13 | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) +16 | print(bin(+1337)[2:]) # FURB116 | = help: Replace with `f"{1337:b}"` ℹ Safe fix -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 -12 |-print(bin(1337)[2:]) # FURB116 - 12 |+print(f"{1337:b}") # FURB116 -13 13 | -14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix) -15 15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) - -FURB116.py:14:7: FURB116 Replace `bin` call with f-string - | -12 | print(bin(1337)[2:]) # FURB116 -13 | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 |-print(bin(1337)[2:]) # FURB116 + 15 |+print(f"{1337:b}") # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + +FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"` + | +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^^ FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | + = help: Replace with `f"{+1337:b}"` + +ℹ Safe fix +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 |-print(bin(+1337)[2:]) # FURB116 + 16 |+print(f"{+1337:b}") # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + +FURB116.py:18:7: FURB116 Replace `bin` call with f-string + | +16 | print(bin(+1337)[2:]) # FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) | ^^^^^^^^^^^^^^^^^^^^^ FURB116 -15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) | = help: Replace with f-string -FURB116.py:15:7: FURB116 Replace `bin` call with f-string +FURB116.py:19:7: FURB116 Replace `bin` call with f-string | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) -15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) | ^^^^^^^^^^^^^^^^^^^^^^ FURB116 -16 | -17 | ## invalid +20 | +21 | ## invalid | = help: Replace with f-string + +FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"` + | +30 | d = datetime.datetime.now(tz=datetime.UTC) +31 | # autofix is display-only +32 | print(bin(d)[2:]) + | ^^^^^^^^^^ FURB116 +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | + = help: Replace with `f"{d:b}"` + +ℹ Display-only fix +29 29 | +30 30 | d = datetime.datetime.now(tz=datetime.UTC) +31 31 | # autofix is display-only +32 |-print(bin(d)[2:]) + 32 |+print(f"{d:b}") +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | + +FURB116.py:34:7: FURB116 Replace `bin` call with `f"{len("xyz").numerator:b}"` + | +32 | print(bin(d)[2:]) +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +35 | +36 | # autofix is display-only + | + = help: Replace with `f"{len("xyz").numerator:b}"` + +ℹ Display-only fix +31 31 | # autofix is display-only +32 32 | print(bin(d)[2:]) +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 |-print(bin(len("xyz").numerator)[2:]) + 34 |+print(f"{len("xyz").numerator:b}") +35 35 | +36 36 | # autofix is display-only +37 37 | print(bin({0: 1}[0].numerator)[2:]) + +FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"` + | +36 | # autofix is display-only +37 | print(bin({0: 1}[0].numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | + = help: Replace with `f"{ {0: 1}[0].numerator:b}"` + +ℹ Display-only fix +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | +36 36 | # autofix is display-only +37 |-print(bin({0: 1}[0].numerator)[2:]) + 37 |+print(f"{ {0: 1}[0].numerator:b}") +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 40 | print(hex(sys + +FURB116.py:39:7: FURB116 Replace `bin` call with `f"{ord("\\").numerator:b}"` + | +37 | print(bin({0: 1}[0].numerator)[2:]) +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +40 | print(hex(sys +41 | .maxunicode)[2:]) + | + = help: Replace with `f"{ord("\\").numerator:b}"` + +ℹ Display-only fix +36 36 | # autofix is display-only +37 37 | print(bin({0: 1}[0].numerator)[2:]) +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 |-print(bin(ord("\\").numerator)[2:]) + 39 |+print(f"{ord("\\").numerator:b}") +40 40 | print(hex(sys +41 41 | .maxunicode)[2:]) +42 42 | + +FURB116.py:40:7: FURB116 Replace `hex` call with f-string + | +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) +40 | print(hex(sys + | _______^ +41 | | .maxunicode)[2:]) + | |________________^ FURB116 +42 | +43 | # for negatives numbers autofix is display-only + | + = help: Replace with f-string + +ℹ Display-only fix +37 37 | print(bin({0: 1}[0].numerator)[2:]) +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 |-print(hex(sys +41 |-.maxunicode)[2:]) + 40 |+print(f"{sys + 41 |+.maxunicode:x}") +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 44 | print(bin(-1)[2:]) + +FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"` + | +43 | # for negatives numbers autofix is display-only +44 | print(bin(-1)[2:]) + | ^^^^^^^^^^^ FURB116 + | + = help: Replace with `f"{-1:b}"` + +ℹ Display-only fix +41 41 | .maxunicode)[2:]) +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 |-print(bin(-1)[2:]) + 44 |+print(f"{-1:b}") diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap new file mode 100644 index 00000000000000..37b1e3aea601ad --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap @@ -0,0 +1,256 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"` + | + 7 | return num + 8 | + 9 | print(oct(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:o}"` + +ℹ Display-only fix +6 6 | def return_num() -> int: +7 7 | return num +8 8 | +9 |-print(oct(num)[2:]) # FURB116 + 9 |+print(f"{num:o}") # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | + +FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:x}"` + +ℹ Display-only fix +7 7 | return num +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 |-print(hex(num)[2:]) # FURB116 + 10 |+print(f"{num:x}") # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 + +FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 + | + = help: Replace with `f"{num:b}"` + +ℹ Display-only fix +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 |-print(bin(num)[2:]) # FURB116 + 11 |+print(f"{num:b}") # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 + +FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` + | +11 | print(bin(num)[2:]) # FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:o}"` + +ℹ Safe fix +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 |-print(oct(1337)[2:]) # FURB116 + 13 |+print(f"{1337:o}") # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 + +FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:x}"` + +ℹ Safe fix +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 |-print(hex(1337)[2:]) # FURB116 + 14 |+print(f"{1337:x}") # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | + +FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:b}"` + +ℹ Safe fix +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 |-print(bin(1337)[2:]) # FURB116 + 15 |+print(f"{1337:b}") # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + +FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"` + | +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^^ FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | + = help: Replace with `f"{+1337:b}"` + +ℹ Safe fix +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 |-print(bin(+1337)[2:]) # FURB116 + 16 |+print(f"{+1337:b}") # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + +FURB116.py:18:7: FURB116 Replace `bin` call with f-string + | +16 | print(bin(+1337)[2:]) # FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | ^^^^^^^^^^^^^^^^^^^^^ FURB116 +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + | + = help: Replace with f-string + +FURB116.py:19:7: FURB116 Replace `bin` call with f-string + | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + | ^^^^^^^^^^^^^^^^^^^^^^ FURB116 +20 | +21 | ## invalid + | + = help: Replace with f-string + +FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"` + | +30 | d = datetime.datetime.now(tz=datetime.UTC) +31 | # autofix is display-only +32 | print(bin(d)[2:]) + | ^^^^^^^^^^ FURB116 +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | + = help: Replace with `f"{d:b}"` + +ℹ Display-only fix +29 29 | +30 30 | d = datetime.datetime.now(tz=datetime.UTC) +31 31 | # autofix is display-only +32 |-print(bin(d)[2:]) + 32 |+print(f"{d:b}") +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | + +FURB116.py:34:7: FURB116 Replace `bin` call with f-string + | +32 | print(bin(d)[2:]) +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +35 | +36 | # autofix is display-only + | + = help: Replace with f-string + +FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"` + | +36 | # autofix is display-only +37 | print(bin({0: 1}[0].numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | + = help: Replace with `f"{ {0: 1}[0].numerator:b}"` + +ℹ Display-only fix +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | +36 36 | # autofix is display-only +37 |-print(bin({0: 1}[0].numerator)[2:]) + 37 |+print(f"{ {0: 1}[0].numerator:b}") +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 40 | print(hex(sys + +FURB116.py:39:7: FURB116 Replace `bin` call with f-string + | +37 | print(bin({0: 1}[0].numerator)[2:]) +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +40 | print(hex(sys +41 | .maxunicode)[2:]) + | + = help: Replace with f-string + +FURB116.py:40:7: FURB116 Replace `hex` call with f-string + | +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) +40 | print(hex(sys + | _______^ +41 | | .maxunicode)[2:]) + | |________________^ FURB116 +42 | +43 | # for negatives numbers autofix is display-only + | + = help: Replace with f-string + +FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"` + | +43 | # for negatives numbers autofix is display-only +44 | print(bin(-1)[2:]) + | ^^^^^^^^^^^ FURB116 + | + = help: Replace with `f"{-1:b}"` + +ℹ Display-only fix +41 41 | .maxunicode)[2:]) +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 |-print(bin(-1)[2:]) + 44 |+print(f"{-1:b}") From d7ef01401c4724c3f26ff94b309d979eb8ce4a55 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 17:11:56 -0300 Subject: [PATCH 053/487] [`flake8-use-pathlib`] `PTH*` suppress diagnostic for all `os.*` functions that have the `dir_fd` parameter (#17968) ## Summary Fixes #17776. This PR also handles all other `PTH*` rules that don't support file descriptors. ## Test Plan Update existing tests. --- .../fixtures/flake8_use_pathlib/PTH207.py | 4 + .../fixtures/flake8_use_pathlib/full_name.py | 17 ++ .../rules/replaceable_by_pathlib.rs | 171 +++++++++++++++--- ..._use_pathlib__tests__PTH207_PTH207.py.snap | 3 +- 4 files changed, 166 insertions(+), 29 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH207.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH207.py index 752e924b8bfd23..0005327d83c036 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH207.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH207.py @@ -9,3 +9,7 @@ glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")) list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))) search("*.png") + +# if `dir_fd` is set, suppress the diagnostic +glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"), dir_fd=1) +list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"), dir_fd=1)) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py index 0f99bc4b85d032..04bc90b801cbc7 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -87,3 +87,20 @@ def bar(x: int): os.rename("src", "dst", src_dir_fd=3, dst_dir_fd=4) os.rename("src", "dst", src_dir_fd=3) os.rename("src", "dst", dst_dir_fd=4) + +# if `dir_fd` is set, suppress the diagnostic +os.readlink(p, dir_fd=1) +os.stat(p, dir_fd=2) +os.unlink(p, dir_fd=3) +os.remove(p, dir_fd=4) +os.rmdir(p, dir_fd=5) +os.mkdir(p, dir_fd=6) +os.chmod(p, dir_fd=7) +# `chmod` can also receive a file descriptor in the first argument +os.chmod(8) +os.chmod(x) + +# if `src_dir_fd` or `dst_dir_fd` are set, suppress the diagnostic +os.replace("src", "dst", src_dir_fd=1, dst_dir_fd=2) +os.replace("src", "dst", src_dir_fd=1) +os.replace("src", "dst", dst_dir_fd=2) diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 14b724730ffb35..bf1b3c0661521e 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -26,41 +26,109 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // PTH100 ["os", "path", "abspath"] => OsPathAbspath.into(), // PTH101 - ["os", "chmod"] => OsChmod.into(), + ["os", "chmod"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.chmod) + // ```text + // 0 1 2 3 + // os.chmod(path, mode, *, dir_fd=None, follow_symlinks=True) + // ``` + if call + .arguments + .find_argument_value("path", 0) + .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + || is_argument_non_default(&call.arguments, "dir_fd", 2) + { + return; + } + OsChmod.into() + } // PTH102 ["os", "makedirs"] => OsMakedirs.into(), // PTH103 - ["os", "mkdir"] => OsMkdir.into(), + ["os", "mkdir"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir) + // ```text + // 0 1 2 + // os.mkdir(path, mode=0o777, *, dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 2) { + return; + } + OsMkdir.into() + } // PTH104 ["os", "rename"] => { // `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are - // are set to non-default values. + // set to non-default values. // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rename) // ```text // 0 1 2 3 // os.rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None) // ``` - if call - .arguments - .find_argument_value("src_dir_fd", 2) - .is_some_and(|expr| !expr.is_none_literal_expr()) - || call - .arguments - .find_argument_value("dst_dir_fd", 3) - .is_some_and(|expr| !expr.is_none_literal_expr()) + if is_argument_non_default(&call.arguments, "src_dir_fd", 2) + || is_argument_non_default(&call.arguments, "dst_dir_fd", 3) { return; } OsRename.into() } // PTH105 - ["os", "replace"] => OsReplace.into(), + ["os", "replace"] => { + // `src_dir_fd` and `dst_dir_fd` are not supported by pathlib, so check if they are + // set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.replace) + // ```text + // 0 1 2 3 + // os.replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "src_dir_fd", 2) + || is_argument_non_default(&call.arguments, "dst_dir_fd", 3) + { + return; + } + OsReplace.into() + } // PTH106 - ["os", "rmdir"] => OsRmdir.into(), + ["os", "rmdir"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir) + // ```text + // 0 1 + // os.rmdir(path, *, dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 1) { + return; + } + OsRmdir.into() + } // PTH107 - ["os", "remove"] => OsRemove.into(), + ["os", "remove"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove) + // ```text + // 0 1 + // os.remove(path, *, dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 1) { + return; + } + OsRemove.into() + } // PTH108 - ["os", "unlink"] => OsUnlink.into(), + ["os", "unlink"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink) + // ```text + // 0 1 + // os.unlink(path, *, dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 1) { + return; + } + OsUnlink.into() + } // PTH109 ["os", "getcwd"] => OsGetcwd.into(), ["os", "getcwdb"] => OsGetcwd.into(), @@ -76,10 +144,17 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { ["os", "path", "islink"] => OsPathIslink.into(), // PTH116 ["os", "stat"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.stat) + // ```text + // 0 1 2 + // os.stat(path, *, dir_fd=None, follow_symlinks=True) + // ``` if call .arguments - .find_positional(0) + .find_argument_value("path", 0) .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + || is_argument_non_default(&call.arguments, "dir_fd", 1) { return; } @@ -148,13 +223,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. }) ) }) + || is_argument_non_default(&call.arguments, "opener", 7) || call .arguments - .find_argument_value("opener", 7) - .is_some_and(|expr| !expr.is_none_literal_expr()) - || call - .arguments - .find_positional(0) + .find_argument_value("file", 0) .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) { return; @@ -164,17 +236,53 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // PTH124 ["py", "path", "local"] => PyPath.into(), // PTH207 - ["glob", "glob"] => Glob { - function: "glob".to_string(), + ["glob", "glob"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.glob) + // ```text + // 0 1 2 3 4 + // glob.glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 2) { + return; + } + + Glob { + function: "glob".to_string(), + } + .into() } - .into(), - ["glob", "iglob"] => Glob { - function: "iglob".to_string(), + + ["glob", "iglob"] => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/glob.html#glob.iglob) + // ```text + // 0 1 2 3 4 + // glob.iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 2) { + return; + } + + Glob { + function: "iglob".to_string(), + } + .into() } - .into(), // PTH115 // Python 3.9+ - ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => OsReadlink.into(), + ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => { + // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink) + // ```text + // 0 1 + // os.readlink(path, *, dir_fd=None) + // ``` + if is_argument_non_default(&call.arguments, "dir_fd", 1) { + return; + } + OsReadlink.into() + } // PTH208 ["os", "listdir"] => { if call @@ -224,3 +332,10 @@ fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> { _ => None, } } + +/// Returns `true` if argument `name` is set to a non-default `None` value. +fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usize) -> bool { + arguments + .find_argument_value(name, position) + .is_some_and(|expr| !expr.is_none_literal_expr()) +} diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH207_PTH207.py.snap b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH207_PTH207.py.snap index 6d30e0fb5e5556..ddcba2ba597ee3 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH207_PTH207.py.snap +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH207_PTH207.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs -snapshot_kind: text --- PTH207.py:9:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob` | @@ -26,4 +25,6 @@ PTH207.py:11:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob` 10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))) 11 | search("*.png") | ^^^^^^ PTH207 +12 | +13 | # if `dir_fd` is set, suppress the diagnostic | From b2d9f599376c83171ab0945d74f49c06394cefe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20N=C3=A4slund?= Date: Mon, 12 May 2025 22:17:13 +0200 Subject: [PATCH 054/487] [`ruff`] Implement a recursive check for `RUF060` (#17976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The existing implementation of RUF060 (InEmptyCollection) is not recursive, meaning that although set([]) results in an empty collection, the existing code fails it because set is taking an argument. The updated implementation allows set and frozenset to take empty collection as positional argument (which results in empty set/frozenset). ## Test Plan Added test cases for recursive cases + updated snapshot (see RUF060.py). --------- Co-authored-by: Marcus Näslund --- .../resources/test/fixtures/ruff/RUF060.py | 7 ++++ .../rules/ruff/rules/in_empty_collection.rs | 27 +++++++++++---- ..._rules__ruff__tests__RUF060_RUF060.py.snap | 34 +++++++++++++++++-- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py index 52ea18de39a913..c3b96c6b7be52f 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF060.py @@ -19,6 +19,9 @@ b"a" in bytearray() b"a" in bytes() 1 in frozenset() +1 in set(set()) +2 in frozenset([]) +'' in set("") # OK 1 in [2] @@ -35,3 +38,7 @@ b"a" in bytearray([2]) b"a" in bytes("a", "utf-8") 1 in frozenset("c") +1 in set(set((1,2))) +1 in set(set([1])) +'' in {""} +frozenset() in {frozenset()} diff --git a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs index 142b968b1b8eea..9132508e8cf687 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, CmpOp, Expr}; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -48,6 +49,14 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare) }; let semantic = checker.semantic(); + + if is_empty(right, semantic) { + checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range())); + } +} + +fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool { + let set_methods = ["set", "frozenset"]; let collection_methods = [ "list", "tuple", @@ -59,7 +68,7 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare) "str", ]; - let is_empty_collection = match right { + match expr { Expr::List(ast::ExprList { elts, .. }) => elts.is_empty(), Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.is_empty(), Expr::Set(ast::ExprSet { elts, .. }) => elts.is_empty(), @@ -75,15 +84,19 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare) arguments, range: _, }) => { - arguments.is_empty() - && collection_methods + if arguments.is_empty() { + collection_methods .iter() .any(|s| semantic.match_builtin_expr(func, s)) + } else if let Some(arg) = arguments.find_positional(0) { + set_methods + .iter() + .any(|s| semantic.match_builtin_expr(func, s)) + && is_empty(arg, semantic) + } else { + false + } } _ => false, - }; - - if is_empty_collection { - checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range())); } } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF060_RUF060.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF060_RUF060.py.snap index a269459cd7c0c1..ac92e967a97926 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF060_RUF060.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF060_RUF060.py.snap @@ -187,6 +187,7 @@ RUF060.py:20:1: RUF060 Unnecessary membership test on empty collection 20 | b"a" in bytes() | ^^^^^^^^^^^^^^^ RUF060 21 | 1 in frozenset() +22 | 1 in set(set()) | RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection @@ -195,6 +196,35 @@ RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection 20 | b"a" in bytes() 21 | 1 in frozenset() | ^^^^^^^^^^^^^^^^ RUF060 -22 | -23 | # OK +22 | 1 in set(set()) +23 | 2 in frozenset([]) + | + +RUF060.py:22:1: RUF060 Unnecessary membership test on empty collection + | +20 | b"a" in bytes() +21 | 1 in frozenset() +22 | 1 in set(set()) + | ^^^^^^^^^^^^^^^ RUF060 +23 | 2 in frozenset([]) +24 | '' in set("") + | + +RUF060.py:23:1: RUF060 Unnecessary membership test on empty collection + | +21 | 1 in frozenset() +22 | 1 in set(set()) +23 | 2 in frozenset([]) + | ^^^^^^^^^^^^^^^^^^ RUF060 +24 | '' in set("") + | + +RUF060.py:24:1: RUF060 Unnecessary membership test on empty collection + | +22 | 1 in set(set()) +23 | 2 in frozenset([]) +24 | '' in set("") + | ^^^^^^^^^^^^^ RUF060 +25 | +26 | # OK | From d545b5bfd2eaf67fbbdae8e3af7855276160c79c Mon Sep 17 00:00:00 2001 From: Yunchi Pang Date: Mon, 12 May 2025 13:27:54 -0700 Subject: [PATCH 055/487] [`pylint`] add fix safety section (`PLE4703`) (#17824) This PR adds a fix safety section in comment for rule PLE4703. parent: #15584 impl was introduced at #970 (couldn't find newer PRs sorry!) --- .../src/rules/pylint/rules/modified_iterating_set.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs index 84bb50f0734a19..f9e5f17a674c03 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs @@ -38,6 +38,11 @@ use crate::checkers::ast::Checker; /// nums.add(num + 5) /// ``` /// +/// ## Fix safety +/// This fix is always unsafe because it changes the program’s behavior. Replacing the +/// original set with a copy during iteration allows code that would previously raise a +/// `RuntimeError` to run without error. +/// /// ## References /// - [Python documentation: `set`](https://docs.python.org/3/library/stdtypes.html#set) #[derive(ViolationMetadata)] From 6b64630635688429bb16a1fad5ca5fb8eebd375c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 12 May 2025 15:39:04 -0500 Subject: [PATCH 056/487] Update `--python` to accept paths to executables in virtual environments (#17954) ## Summary Updates the `--python` flag to accept Python executables in virtual environments. Notably, we do not query the executable and it _must_ be in a canonical location in a virtual environment. This is pretty naive, but solves for the trivial case of `ty check --python .venv/bin/python3` which will be a common mistake (and `ty check --python $(which python)`) I explored this while trying to understand Python discovery in ty in service of https://github.com/astral-sh/ty/issues/272, I'm not attached to it, but figure it's worth sharing. As an alternative, we can add more variants to the `SearchPathValidationError` and just improve the _error_ message, i.e., by hinting that this looks like a virtual environment and suggesting the concrete alternative path they should provide. We'll probably want to do that for some other cases anyway (e.g., `3.13` as described in the linked issue) This functionality is also briefly mentioned in https://github.com/astral-sh/ty/issues/193 Closes https://github.com/astral-sh/ty/issues/318 ## Test Plan e.g., ``` uv run ty check --python .venv/bin/python3 ``` needs test coverage still --- crates/ty/docs/cli.md | 9 ++++--- crates/ty/src/args.rs | 16 +++++++---- .../src/module_resolver/resolver.rs | 27 +++++++++++++++++++ crates/ty_python_semantic/src/program.rs | 5 +++- crates/ty_test/src/config.rs | 13 ++++++--- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index e83f0ccc954867..4df73846cbdb14 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -56,10 +56,11 @@ ty check [OPTIONS] [PATH]...
--project project

Run the command within the given project directory.

All pyproject.toml files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (.venv) unless the venv-path option is set.

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

-
--python path

Path to the Python installation from which ty resolves type information and third-party dependencies.

-

If not specified, ty will look at the VIRTUAL_ENV environment variable.

-

ty will search in the path's site-packages directories for type information and third-party imports.

-

This option is commonly used to specify the path to a virtual environment.

+
--python path

Path to the Python environment.

+

ty uses the Python environment to resolve type information and third-party dependencies.

+

If not specified, ty will attempt to infer it from the VIRTUAL_ENV environment variable or discover a .venv directory in the project root or working directory.

+

If a path to a Python interpreter is provided, e.g., .venv/bin/python3, ty will attempt to find an environment two directories up from the interpreter's path, e.g., .venv. At this time, ty does not invoke the interpreter to determine the location of the environment. This means that ty will not resolve dynamic executables such as a shim.

+

ty will search in the resolved environments's site-packages directories for type information and third-party imports.

--python-platform, --platform platform

Target platform to assume when resolving types.

This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

--python-version, --target-version version

Python version to assume when resolving types

diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index c4e8988e80014f..be65581580bf7d 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -51,14 +51,20 @@ pub(crate) struct CheckCommand { #[arg(long, value_name = "PROJECT")] pub(crate) project: Option, - /// Path to the Python installation from which ty resolves type information and third-party dependencies. + /// Path to the Python environment. /// - /// If not specified, ty will look at the `VIRTUAL_ENV` environment variable. + /// ty uses the Python environment to resolve type information and third-party dependencies. /// - /// ty will search in the path's `site-packages` directories for type information and - /// third-party imports. + /// If not specified, ty will attempt to infer it from the `VIRTUAL_ENV` environment variable or + /// discover a `.venv` directory in the project root or working directory. /// - /// This option is commonly used to specify the path to a virtual environment. + /// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to + /// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this + /// time, ty does not invoke the interpreter to determine the location of the environment. This + /// means that ty will not resolve dynamic executables such as a shim. + /// + /// ty will search in the resolved environments's `site-packages` directories for type + /// information and third-party imports. #[arg(long, value_name = "PATH")] pub(crate) python: Option, diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 0be7d5107e1813..6b2c76e23af5d4 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -247,6 +247,33 @@ impl SearchPaths { .and_then(|env| env.site_packages_directories(system))? } + PythonPath::Resolve(target, origin) => { + tracing::debug!("Resolving {origin}: {target}"); + + let root = system + // If given a file, assume it's a Python executable, e.g., `.venv/bin/python3`, + // and search for a virtual environment in the root directory. Ideally, we'd + // invoke the target to determine `sys.prefix` here, but that's more complicated + // and may be deferred to uv. + .is_file(target) + .then(|| target.as_path()) + .take_if(|target| { + // Avoid using the target if it doesn't look like a Python executable, e.g., + // to deny cases like `.venv/bin/foo` + target + .file_name() + .is_some_and(|name| name.starts_with("python")) + }) + .and_then(SystemPath::parent) + .and_then(SystemPath::parent) + // If not a file, use the path as given and allow let `PythonEnvironment::new` + // handle the error. + .unwrap_or(target); + + PythonEnvironment::new(root, *origin, system) + .and_then(|venv| venv.site_packages_directories(system))? + } + PythonPath::Discover(root) => { tracing::debug!("Discovering virtual environment in `{root}`"); let virtual_env_path = discover_venv_in(db.system(), root); diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 63bc35be57d85a..5683b62073b275 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -145,6 +145,9 @@ pub enum PythonPath { /// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix SysPrefix(SystemPathBuf, SysPrefixPathOrigin), + /// Resolve a path to an executable (or environment directory) into a usable environment. + Resolve(SystemPathBuf, SysPrefixPathOrigin), + /// Tries to discover a virtual environment in the given path. Discover(SystemPathBuf), @@ -161,6 +164,6 @@ impl PythonPath { } pub fn from_cli_flag(path: SystemPathBuf) -> Self { - Self::SysPrefix(path, SysPrefixPathOrigin::PythonCliFlag) + Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag) } } diff --git a/crates/ty_test/src/config.rs b/crates/ty_test/src/config.rs index ba279007192c44..8bcaef829b3a65 100644 --- a/crates/ty_test/src/config.rs +++ b/crates/ty_test/src/config.rs @@ -68,12 +68,17 @@ pub(crate) struct Environment { /// Additional search paths to consider when resolving modules. pub(crate) extra_paths: Option>, - /// Path to the Python installation from which ty resolves type information and third-party dependencies. + /// Path to the Python environment. /// - /// ty will search in the path's `site-packages` directories for type information and - /// third-party imports. + /// ty uses the Python environment to resolve type information and third-party dependencies. /// - /// This option is commonly used to specify the path to a virtual environment. + /// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to + /// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this + /// time, ty does not invoke the interpreter to determine the location of the environment. This + /// means that ty will not resolve dynamic executables such as a shim. + /// + /// ty will search in the resolved environment's `site-packages` directories for type + /// information and third-party imports. #[serde(skip_serializing_if = "Option::is_none")] pub python: Option, } From f549dfe39d5248a65fa5d1c6a0c2dfec9667869a Mon Sep 17 00:00:00 2001 From: Yunchi Pang Date: Mon, 12 May 2025 13:51:05 -0700 Subject: [PATCH 057/487] [`pylint`] add fix safety section (`PLW3301`) (#17878) parent: #15584 issue: #16163 --------- Co-authored-by: Brent Westbrook --- .../src/rules/pylint/rules/nested_min_max.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index 5c7290b932159a..51d34b97e3d6df 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs @@ -21,6 +21,7 @@ pub(crate) enum MinMax { /// readability. /// /// ## Example +/// /// ```python /// minimum = min(1, 2, min(3, 4, 5)) /// maximum = max(1, 2, max(3, 4, 5)) @@ -28,12 +29,26 @@ pub(crate) enum MinMax { /// ``` /// /// Use instead: +/// /// ```python /// minimum = min(1, 2, 3, 4, 5) /// maximum = max(1, 2, 3, 4, 5) /// diff = maximum - minimum /// ``` /// +/// ## Fix safety +/// +/// This fix is always unsafe and may change the program's behavior for types without full +/// equivalence relations, such as float comparisons involving `NaN`. +/// +/// ```python +/// print(min(2.0, min(float("nan"), 1.0))) # before fix: 2.0 +/// print(min(2.0, float("nan"), 1.0)) # after fix: 1.0 +/// +/// print(max(1.0, max(float("nan"), 2.0))) # before fix: 1.0 +/// print(max(1.0, float("nan"), 2.0)) # after fix: 2.0 +/// ``` +/// /// ## References /// - [Python documentation: `min`](https://docs.python.org/3/library/functions.html#min) /// - [Python documentation: `max`](https://docs.python.org/3/library/functions.html#max) From 2eb2d5359b4bad21d987e15cc776ac710c2af5a5 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 13 May 2025 05:13:41 +0800 Subject: [PATCH 058/487] [`airflow`] Apply try-catch guard to all AIR3 rules (`AIR3`) (#17887) ## Summary If a try-catch block guards the names, we don't raise warnings. During this change, I discovered that some of the replacement types were missed. Thus, I extend the fix to types other than AutoImport as well ## Test Plan Test fixtures are added and updated. --- .../test/fixtures/airflow/AIR301_names_try.py | 15 ++----- .../test/fixtures/airflow/AIR302_try.py | 8 ++++ .../test/fixtures/airflow/AIR311_try.py | 8 ++++ .../test/fixtures/airflow/AIR312_try.py | 8 ++++ .../ruff_linter/src/rules/airflow/helpers.rs | 18 ++++----- crates/ruff_linter/src/rules/airflow/mod.rs | 3 ++ .../airflow/rules/moved_to_provider_in_3.rs | 21 +++++----- .../src/rules/airflow/rules/removal_in_3.rs | 15 ++++--- .../suggested_to_move_to_provider_in_3.rs | 20 ++++++---- .../airflow/rules/suggested_to_update_3_0.rs | 14 ++++--- ..._airflow__tests__AIR302_AIR302_try.py.snap | 4 ++ ...irflow__tests__AIR311_AIR311_names.py.snap | 40 ++++++++++++++++++- ..._airflow__tests__AIR311_AIR311_try.py.snap | 4 ++ ..._airflow__tests__AIR312_AIR312_try.py.snap | 4 ++ 14 files changed, 129 insertions(+), 53 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py create mode 100644 crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py create mode 100644 crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py create mode 100644 crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_try.py.snap create mode 100644 crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_try.py.snap create mode 100644 crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312_try.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_try.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_try.py index 113fe88831f74a..e4202741eddbf3 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_try.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_try.py @@ -1,17 +1,8 @@ from __future__ import annotations try: - from airflow.sdk import Asset + from airflow.assets.manager import AssetManager except ModuleNotFoundError: - from airflow.datasets import Dataset as Asset + from airflow.datasets.manager import DatasetManager as AssetManager -Asset - -try: - from airflow.sdk import Asset -except ModuleNotFoundError: - from airflow import datasets - - Asset = datasets.Dataset - -asset = Asset() +AssetManager() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py new file mode 100644 index 00000000000000..c062aeb61a17e6 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_try.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +try: + from airflow.providers.http.operators.http import HttpOperator +except ModuleNotFoundError: + from airflow.operators.http_operator import SimpleHttpOperator as HttpOperator + +HttpOperator() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py new file mode 100644 index 00000000000000..5f6a1df0328559 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_try.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +try: + from airflow.sdk import Asset +except ModuleNotFoundError: + from airflow.datasets import Dataset as Asset + +Asset() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py new file mode 100644 index 00000000000000..f8cf9402b609a2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR312_try.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +try: + from airflow.providers.standard.triggers.file import FileTrigger +except ModuleNotFoundError: + from airflow.triggers.file import FileTrigger + +FileTrigger() diff --git a/crates/ruff_linter/src/rules/airflow/helpers.rs b/crates/ruff_linter/src/rules/airflow/helpers.rs index c5468943be2657..fb6f3b9ff5eb19 100644 --- a/crates/ruff_linter/src/rules/airflow/helpers.rs +++ b/crates/ruff_linter/src/rules/airflow/helpers.rs @@ -45,7 +45,8 @@ pub(crate) enum ProviderReplacement { pub(crate) fn is_guarded_by_try_except( expr: &Expr, - replacement: &Replacement, + module: &str, + name: &str, semantic: &SemanticModel, ) -> bool { match expr { @@ -63,7 +64,7 @@ pub(crate) fn is_guarded_by_try_except( if !suspended_exceptions.contains(Exceptions::ATTRIBUTE_ERROR) { return false; } - try_block_contains_undeprecated_attribute(try_node, replacement, semantic) + try_block_contains_undeprecated_attribute(try_node, module, name, semantic) } Expr::Name(ExprName { id, .. }) => { let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else { @@ -89,7 +90,7 @@ pub(crate) fn is_guarded_by_try_except( { return false; } - try_block_contains_undeprecated_import(try_node, replacement) + try_block_contains_undeprecated_import(try_node, module, name) } _ => false, } @@ -100,12 +101,10 @@ pub(crate) fn is_guarded_by_try_except( /// member is being accessed from the non-deprecated location? fn try_block_contains_undeprecated_attribute( try_node: &StmtTry, - replacement: &Replacement, + module: &str, + name: &str, semantic: &SemanticModel, ) -> bool { - let Replacement::AutoImport { module, name } = replacement else { - return false; - }; let undeprecated_qualified_name = { let mut builder = QualifiedNameBuilder::default(); for part in module.split('.') { @@ -122,10 +121,7 @@ fn try_block_contains_undeprecated_attribute( /// Given an [`ast::StmtTry`] node, does the `try` branch of that node /// contain any [`ast::StmtImportFrom`] nodes that indicate the airflow /// member is being imported from the non-deprecated location? -fn try_block_contains_undeprecated_import(try_node: &StmtTry, replacement: &Replacement) -> bool { - let Replacement::AutoImport { module, name } = replacement else { - return false; - }; +fn try_block_contains_undeprecated_import(try_node: &StmtTry, module: &str, name: &str) -> bool { let mut import_searcher = ImportSearcher::new(module, name); import_searcher.visit_body(&try_node.body); import_searcher.found_import diff --git a/crates/ruff_linter/src/rules/airflow/mod.rs b/crates/ruff_linter/src/rules/airflow/mod.rs index 4b4864a97d27c4..60eb9f5a096162 100644 --- a/crates/ruff_linter/src/rules/airflow/mod.rs +++ b/crates/ruff_linter/src/rules/airflow/mod.rs @@ -46,9 +46,12 @@ mod tests { #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_sqlite.py"))] #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_zendesk.py"))] #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_standard.py"))] + #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_try.py"))] #[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))] #[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))] + #[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_try.py"))] #[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312.py"))] + #[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312_try.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index 44a7fda7d2035c..b0b7e36f57f9da 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -1,5 +1,5 @@ use crate::importer::ImportRequest; -use crate::rules::airflow::helpers::ProviderReplacement; +use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{Expr, ExprAttribute}; @@ -937,13 +937,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan ranged.range(), ); - if let ProviderReplacement::AutoImport { - module, - name, - provider: _, - version: _, - } = replacement - { + let semantic = checker.semantic(); + if let Some((module, name)) = match &replacement { + ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)), + ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { + Some((module, name.as_str())) + } + _ => None, + } { + if is_guarded_by_try_except(expr, module, name, semantic) { + return; + } diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), @@ -954,6 +958,5 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index ecc30466f3f36d..66b88c0f407578 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -865,10 +865,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { _ => return, }; - if is_guarded_by_try_except(expr, &replacement, semantic) { - return; - } - let mut diagnostic = Diagnostic::new( Airflow3Removal { deprecated: qualified_name.to_string(), @@ -876,8 +872,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { }, range, ); - - if let Replacement::AutoImport { module, name } = replacement { + let semantic = checker.semantic(); + if let Some((module, name)) = match &replacement { + Replacement::AutoImport { module, name } => Some((module, *name)), + Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())), + _ => None, + } { + if is_guarded_by_try_except(expr, module, name, semantic) { + return; + } diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index b0c2389583c358..a95bdeef844775 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -1,6 +1,6 @@ use crate::importer::ImportRequest; -use crate::rules::airflow::helpers::ProviderReplacement; +use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{Expr, ExprAttribute}; @@ -279,13 +279,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan ranged.range(), ); - if let ProviderReplacement::AutoImport { - module, - name, - provider: _, - version: _, - } = replacement - { + let semantic = checker.semantic(); + if let Some((module, name)) = match &replacement { + ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)), + ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { + Some((module, name.as_str())) + } + _ => None, + } { + if is_guarded_by_try_except(expr, module, name, semantic) { + return; + } diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index f732ac0ea20969..05b68911b1ac3d 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -283,10 +283,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { _ => return, }; - if is_guarded_by_try_except(expr, &replacement, semantic) { - return; - } - let mut diagnostic = Diagnostic::new( Airflow3SuggestedUpdate { deprecated: qualified_name.to_string(), @@ -295,7 +291,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { range, ); - if let Replacement::AutoImport { module, name } = replacement { + let semantic = checker.semantic(); + if let Some((module, name)) = match &replacement { + Replacement::AutoImport { module, name } => Some((module, *name)), + Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())), + _ => None, + } { + if is_guarded_by_try_except(expr, module, name, semantic) { + return; + } diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_try.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_try.py.snap new file mode 100644 index 00000000000000..6d6d3986fe9dd4 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_try.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap index 326f8b37fbb371..8828f7b1164ac6 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap @@ -258,7 +258,7 @@ AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3 | = help: Use `airflow.sdk.Connection` instead -AIR311_names.py:50:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | 48 | # airflow.models 49 | Connection() @@ -268,6 +268,24 @@ AIR311_names.py:50:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It | = help: Use `airflow.sdk.DAG` instead +ℹ Safe fix +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import DAG +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() +-------------------------------------------------------------------------------- +47 48 | +48 49 | # airflow.models +49 50 | Connection() +50 |-DAGFromModel() + 51 |+DAG() +51 52 | Variable() +52 53 | +53 54 | # airflow.models.baseoperator + AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | 49 | Connection() @@ -310,7 +328,7 @@ AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is r | = help: Use `airflow.sdk.cross_downstream` instead -AIR311_names.py:62:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | 61 | # airflow.models.dag 62 | DAGFromDag() @@ -320,6 +338,24 @@ AIR311_names.py:62:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; | = help: Use `airflow.sdk.DAG` instead +ℹ Safe fix +22 22 | from airflow.models.dag import DAG as DAGFromDag +23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule +24 24 | from airflow.utils.dag_parsing_context import get_parsing_context + 25 |+from airflow.sdk import DAG +25 26 | +26 27 | # airflow +27 28 | DatasetFromRoot() +-------------------------------------------------------------------------------- +59 60 | BaseOperatorLink() +60 61 | +61 62 | # airflow.models.dag +62 |-DAGFromDag() + 63 |+DAG() +63 64 | # airflow.timetables.datasets +64 65 | DatasetOrTimeSchedule() +65 66 | + AIR311_names.py:64:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | 62 | DAGFromDag() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_try.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_try.py.snap new file mode 100644 index 00000000000000..6d6d3986fe9dd4 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_try.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312_try.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312_try.py.snap new file mode 100644 index 00000000000000..6d6d3986fe9dd4 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312_try.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +--- + From 0d6fafd0f9a626402ff23aa0e4ee9cd2f97138e7 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 19:06:51 -0300 Subject: [PATCH 059/487] [`flake8-bugbear`] Ignore `B028` if `skip_file_prefixes` is present (#18047) ## Summary Fixes #18011 --- .../test/fixtures/flake8_bugbear/B028.py | 8 +++++++ .../rules/no_explicit_stacklevel.rs | 21 ++++++++++++++++- ...__flake8_bugbear__tests__B028_B028.py.snap | 23 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py index 943f5c4d930903..54362a5070eb60 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py @@ -25,3 +25,11 @@ # some comments here source = None # no trailing comma ) + +# https://github.com/astral-sh/ruff/issues/18011 +warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +# trigger diagnostic if `skip_file_prefixes` is present and set to the default value +warnings.warn("test", skip_file_prefixes=()) + +_my_prefixes = ("this","that") +warnings.warn("test", skip_file_prefixes = _my_prefixes) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index 73c41f47097670..0e4ffd91f7e8c6 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast}; +use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::{checkers::ast::Checker, fix::edits::add_argument}; @@ -60,10 +60,18 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { return; } + // When prefixes are supplied, stacklevel is implicitly overridden to be `max(2, stacklevel)`. + // + // Signature as of Python 3.13 (https://docs.python.org/3/library/warnings.html#warnings.warn) + // ```text + // 0 1 2 3 4 + // warnings.warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=()) + // ``` if call .arguments .find_argument_value("stacklevel", 2) .is_some() + || is_skip_file_prefixes_param_set(&call.arguments) || call .arguments .args @@ -90,3 +98,14 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { checker.report_diagnostic(diagnostic); } + +/// Returns `true` if `skip_file_prefixes` is set to its non-default value. +/// The default value of `skip_file_prefixes` is an empty tuple. +fn is_skip_file_prefixes_param_set(arguments: &ast::Arguments) -> bool { + arguments + .find_keyword("skip_file_prefixes") + .is_some_and(|keyword| match &keyword.value { + Expr::Tuple(tuple) => !tuple.elts.is_empty(), + _ => true, + }) +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap index cf6ec733783d75..db323efe66afbd 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap @@ -61,3 +61,26 @@ B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found 26 |- source = None # no trailing comma 26 |+ source = None, stacklevel=2 # no trailing comma 27 27 | ) +28 28 | +29 29 | # https://github.com/astral-sh/ruff/issues/18011 + +B028.py:32:1: B028 [*] No explicit `stacklevel` keyword argument found + | +30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value +32 | warnings.warn("test", skip_file_prefixes=()) + | ^^^^^^^^^^^^^ B028 +33 | +34 | _my_prefixes = ("this","that") + | + = help: Set `stacklevel=2` + +ℹ Unsafe fix +29 29 | # https://github.com/astral-sh/ruff/issues/18011 +30 30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +31 31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value +32 |-warnings.warn("test", skip_file_prefixes=()) + 32 |+warnings.warn("test", skip_file_prefixes=(), stacklevel=2) +33 33 | +34 34 | _my_prefixes = ("this","that") +35 35 | warnings.warn("test", skip_file_prefixes = _my_prefixes) From a97e72fb5e34885aa553a6c6c34aba3343393798 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 12 May 2025 17:31:04 -0500 Subject: [PATCH 060/487] Update reference documentation for `--python-version` (#18056) Adding more detail here --- crates/ty/docs/cli.md | 5 ++++- crates/ty/src/args.rs | 9 +++++++++ crates/ty_test/src/config.rs | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index 4df73846cbdb14..ac26903b0005f9 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -63,7 +63,10 @@ ty check [OPTIONS] [PATH]...

ty will search in the resolved environments's site-packages directories for type information and third-party imports.

--python-platform, --platform platform

Target platform to assume when resolving types.

This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

-
--python-version, --target-version version

Python version to assume when resolving types

+
--python-version, --target-version version

Python version to assume when resolving types.

+

The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.

+

By default, the Python version is inferred as the lower bound of the project's requires-python field from the pyproject.toml, if available. Otherwise, the latest stable version supported by ty is used, which is currently 3.13.

+

ty will not infer the Python version from the Python environment at this time.

Possible values:

  • 3.7
  • diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index be65581580bf7d..9321319fad4cea 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -77,6 +77,15 @@ pub(crate) struct CheckCommand { pub(crate) extra_search_path: Option>, /// Python version to assume when resolving types. + /// + /// The Python version affects allowed syntax, type definitions of the standard library, and + /// type definitions of first- and third-party modules that are conditional on the Python version. + /// + /// By default, the Python version is inferred as the lower bound of the project's + /// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest + /// stable version supported by ty is used, which is currently 3.13. + /// + /// ty will not infer the Python version from the Python environment at this time. #[arg(long, value_name = "VERSION", alias = "target-version")] pub(crate) python_version: Option, diff --git a/crates/ty_test/src/config.rs b/crates/ty_test/src/config.rs index 8bcaef829b3a65..316a3e502692e2 100644 --- a/crates/ty_test/src/config.rs +++ b/crates/ty_test/src/config.rs @@ -57,6 +57,15 @@ impl MarkdownTestConfig { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct Environment { /// Target Python version to assume when resolving types. + /// + /// The Python version affects allowed syntax, type definitions of the standard library, and + /// type definitions of first- and third-party modules that are conditional on the Python version. + /// + /// By default, the Python version is inferred as the lower bound of the project's + /// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest + /// stable version supported by ty is used, which is currently 3.13. + /// + /// ty will not infer the Python version from the Python environment at this time. pub(crate) python_version: Option, /// Target platform to assume when resolving types. From c7b6108cb8b811d0a7c9f0ce8a417dc737e3ac9e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 May 2025 18:58:14 -0400 Subject: [PATCH 061/487] [ty] Narrowing for `hasattr()` (#18053) --- .../resources/mdtest/narrow/hasattr.md | 26 +++++++++ crates/ty_python_semantic/src/types.rs | 16 ++++-- .../ty_python_semantic/src/types/instance.rs | 9 +++ crates/ty_python_semantic/src/types/narrow.rs | 55 ++++++++++++++----- .../src/types/protocol_class.rs | 19 +++++++ 5 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md new file mode 100644 index 00000000000000..2a231802da2133 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md @@ -0,0 +1,26 @@ +# Narrowing using `hasattr()` + +The builtin function `hasattr()` can be used to narrow nominal and structural types. This is +accomplished using an intersection with a synthesized protocol: + +```py +from typing import final + +class Foo: ... + +@final +class Bar: ... + +def f(x: Foo): + if hasattr(x, "spam"): + reveal_type(x) # revealed: Foo & + reveal_type(x.spam) # revealed: object + + if hasattr(x, "not-an-identifier"): + reveal_type(x) # revealed: Foo + +def y(x: Bar): + if hasattr(x, "spam"): + reveal_type(x) # revealed: Never + reveal_type(x.spam) # revealed: Never +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 754f607512c1b7..cd48fa9877f6ae 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -28,7 +28,7 @@ pub(crate) use self::infer::{ infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types, infer_scope_types, }; -pub(crate) use self::narrow::KnownConstraintFunction; +pub(crate) use self::narrow::ClassInfoConstraintFunction; pub(crate) use self::signatures::{CallableSignature, Signature, Signatures}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; @@ -6939,6 +6939,9 @@ pub enum KnownFunction { /// `builtins.issubclass` #[strum(serialize = "issubclass")] IsSubclass, + /// `builtins.hasattr` + #[strum(serialize = "hasattr")] + HasAttr, /// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type` RevealType, /// `builtins.len` @@ -7005,10 +7008,10 @@ pub enum KnownFunction { } impl KnownFunction { - pub fn into_constraint_function(self) -> Option { + pub fn into_classinfo_constraint_function(self) -> Option { match self { - Self::IsInstance => Some(KnownConstraintFunction::IsInstance), - Self::IsSubclass => Some(KnownConstraintFunction::IsSubclass), + Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance), + Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass), _ => None, } } @@ -7027,7 +7030,9 @@ impl KnownFunction { /// Return `true` if `self` is defined in `module` at runtime. const fn check_module(self, module: KnownModule) -> bool { match self { - Self::IsInstance | Self::IsSubclass | Self::Len | Self::Repr => module.is_builtins(), + Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => { + module.is_builtins() + } Self::AssertType | Self::AssertNever | Self::Cast @@ -8423,6 +8428,7 @@ pub(crate) mod tests { KnownFunction::Len | KnownFunction::Repr | KnownFunction::IsInstance + | KnownFunction::HasAttr | KnownFunction::IsSubclass => KnownModule::Builtins, KnownFunction::AbstractMethod => KnownModule::Abc, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 59de7274672dc0..b492a55952a988 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -25,6 +25,15 @@ impl<'db> Type<'db> { } } + pub(super) fn synthesized_protocol<'a, M>(db: &'db dyn Db, members: M) -> Self + where + M: IntoIterator)>, + { + Self::ProtocolInstance(ProtocolInstanceType(Protocol::Synthesized( + SynthesizedProtocolType::new(db, ProtocolInterface::with_members(db, members)), + ))) + } + /// Return `true` if `self` conforms to the interface described by `protocol`. /// /// TODO: we may need to split this into two methods in the future, once we start diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index e42dcfa32f4673..175e787cbf6809 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -11,13 +11,16 @@ use crate::types::{ UnionBuilder, }; use crate::Db; + +use ruff_python_stdlib::identifiers::is_identifier; + use itertools::Itertools; use ruff_python_ast as ast; use ruff_python_ast::{BoolOp, ExprBoolOp}; use rustc_hash::FxHashMap; use std::collections::hash_map::Entry; -use super::UnionType; +use super::{KnownFunction, UnionType}; /// Return the type constraint that `test` (if true) would place on `symbol`, if any. /// @@ -138,23 +141,27 @@ fn negative_constraints_for_expression_cycle_initial<'db>( None } +/// Functions that can be used to narrow the type of a first argument using a "classinfo" second argument. +/// +/// A "classinfo" argument is either a class or a tuple of classes, or a tuple of tuples of classes +/// (etc. for arbitrary levels of recursion) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum KnownConstraintFunction { +pub enum ClassInfoConstraintFunction { /// `builtins.isinstance` IsInstance, /// `builtins.issubclass` IsSubclass, } -impl KnownConstraintFunction { +impl ClassInfoConstraintFunction { /// Generate a constraint from the type of a `classinfo` argument to `isinstance` or `issubclass`. /// /// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604 /// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type. fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option> { let constraint_fn = |class| match self { - KnownConstraintFunction::IsInstance => Type::instance(db, class), - KnownConstraintFunction::IsSubclass => SubclassOfType::from(db, class), + ClassInfoConstraintFunction::IsInstance => Type::instance(db, class), + ClassInfoConstraintFunction::IsSubclass => SubclassOfType::from(db, class), }; match classinfo { @@ -704,20 +711,38 @@ impl<'db> NarrowingConstraintsBuilder<'db> { // and `issubclass`, for example `isinstance(x, str | (int | float))`. match callable_ty { Type::FunctionLiteral(function_type) if expr_call.arguments.keywords.is_empty() => { - let function = function_type.known(self.db)?.into_constraint_function()?; - - let (id, class_info) = match &*expr_call.arguments.args { - [first, class_info] => match expr_name(first) { - Some(id) => (id, class_info), - None => return None, - }, - _ => return None, + let [first_arg, second_arg] = &*expr_call.arguments.args else { + return None; }; + let first_arg = expr_name(first_arg)?; + let function = function_type.known(self.db)?; + let symbol = self.expect_expr_name_symbol(first_arg); + + if function == KnownFunction::HasAttr { + let attr = inference + .expression_type(second_arg.scoped_expression_id(self.db, scope)) + .into_string_literal()? + .value(self.db); + + if !is_identifier(attr) { + return None; + } + + let constraint = Type::synthesized_protocol( + self.db, + [(attr, KnownClass::Object.to_instance(self.db))], + ); + + return Some(NarrowingConstraints::from_iter([( + symbol, + constraint.negate_if(self.db, !is_positive), + )])); + } - let symbol = self.expect_expr_name_symbol(id); + let function = function.into_classinfo_constraint_function()?; let class_info_ty = - inference.expression_type(class_info.scoped_expression_id(self.db, scope)); + inference.expression_type(second_arg.scoped_expression_id(self.db, scope)); function .generate_constraint(self.db, class_info_ty) diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 871b6d83516f92..cf7a05ebf9310f 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -70,6 +70,25 @@ pub(super) enum ProtocolInterface<'db> { } impl<'db> ProtocolInterface<'db> { + pub(super) fn with_members<'a, M>(db: &'db dyn Db, members: M) -> Self + where + M: IntoIterator)>, + { + let members: BTreeMap<_, _> = members + .into_iter() + .map(|(name, ty)| { + ( + Name::new(name), + ProtocolMemberData { + ty: ty.normalized(db), + qualifiers: TypeQualifiers::default(), + }, + ) + }) + .collect(); + Self::Members(ProtocolInterfaceMembers::new(db, members)) + } + fn empty(db: &'db dyn Db) -> Self { Self::Members(ProtocolInterfaceMembers::new(db, BTreeMap::default())) } From 41fa082414c7277082d7e19b307baea0a4771d4c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 May 2025 19:07:11 -0400 Subject: [PATCH 062/487] [ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` (#18055) --- .../resources/mdtest/mro.md | 23 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 7 ++++++ .../src/types/class_base.rs | 13 ++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 0b54d3e2bb02bb..70369b5b051fbb 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -154,6 +154,29 @@ reveal_type(E.__mro__) # revealed: tuple[, , , reveal_type(F.__mro__) ``` +## Inheritance with intersections that include `Unknown` + +An intersection that includes `Unknown` or `Any` is permitted as long as the intersection is not +disjoint from `type`. + +```py +from does_not_exist import DoesNotExist # error: [unresolved-import] + +reveal_type(DoesNotExist) # revealed: Unknown + +if hasattr(DoesNotExist, "__mro__"): + reveal_type(DoesNotExist) # revealed: Unknown & + + class Foo(DoesNotExist): ... # no error! + reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] + +if not isinstance(DoesNotExist, type): + reveal_type(DoesNotExist) # revealed: Unknown & ~type + + class Foo(DoesNotExist): ... # error: [invalid-base] + reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] +``` + ## `__bases__` lists that cause errors at runtime If the class's `__bases__` cause an exception to be raised at runtime and therefore the class diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index cd48fa9877f6ae..9212781eab003d 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -543,6 +543,13 @@ impl<'db> Type<'db> { Self::Dynamic(DynamicType::Unknown) } + pub(crate) fn into_dynamic(self) -> Option { + match self { + Type::Dynamic(dynamic_type) => Some(dynamic_type), + _ => None, + } + } + pub fn object(db: &'db dyn Db) -> Self { KnownClass::Object.to_instance(db) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index db5d23932195e4..2069ec257ea1f5 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -107,8 +107,19 @@ impl<'db> ClassBase<'db> { { Self::try_from_type(db, todo_type!("GenericAlias instance")) } + Type::Intersection(inter) => { + let dynamic_element = inter + .positive(db) + .iter() + .find_map(|elem| elem.into_dynamic())?; + + if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) { + None + } else { + Some(ClassBase::Dynamic(dynamic_element)) + } + } Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? - Type::Intersection(_) => None, // TODO -- probably incorrect? Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`? Type::PropertyInstance(_) => None, Type::Never From 7e9b0df18a79da1e3f24d54ab26f41c5540917f5 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 May 2025 20:30:21 -0400 Subject: [PATCH 063/487] [ty] Allow classes to inherit from `type[Any]` or `type[Unknown]` (#18060) --- .../ty_python_semantic/resources/mdtest/mro.md | 17 +++++++++++++++++ ...`__bases__`_lists_with_duplicate_bases.snap | 18 +++++++++--------- crates/ty_python_semantic/src/types.rs | 7 ------- .../ty_python_semantic/src/types/class_base.rs | 11 +++++++---- .../src/types/subclass_of.rs | 7 +++++++ 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 70369b5b051fbb..3252178cf9017a 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -177,6 +177,23 @@ if not isinstance(DoesNotExist, type): reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] ``` +## Inheritance from `type[Any]` and `type[Unknown]` + +Inheritance from `type[Any]` and `type[Unknown]` is also permitted, in keeping with the gradual +guarantee: + +```py +from typing import Any +from ty_extensions import Unknown, Intersection + +def f(x: type[Any], y: Intersection[Unknown, type[Any]]): + class Foo(x): ... + reveal_type(Foo.__mro__) # revealed: tuple[, Any, ] + + class Bar(y): ... + reveal_type(Bar.__mro__) # revealed: tuple[, Unknown, ] +``` + ## `__bases__` lists that cause errors at runtime If the class's `__bases__` cause an exception to be raised at runtime and therefore the class diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index fceb6462c89ecd..ec876301783ea0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -141,7 +141,7 @@ info[revealed-type]: Revealed type ``` ``` -error[duplicate-base]: Duplicate base class `Spam` +error[duplicate-base]: Duplicate base class `Eggs` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -160,18 +160,17 @@ error[duplicate-base]: Duplicate base class `Spam` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:17:5 + --> src/mdtest_snippet.py:18:5 | -15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, - | ---- Class `Spam` first included in bases list here 18 | Eggs, + | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, - | ^^^^ Class `Spam` later repeated here 22 | Eggs, + | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default @@ -179,7 +178,7 @@ info: rule `duplicate-base` is enabled by default ``` ``` -error[duplicate-base]: Duplicate base class `Eggs` +error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -198,17 +197,18 @@ error[duplicate-base]: Duplicate base class `Eggs` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:18:5 + --> src/mdtest_snippet.py:17:5 | +15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, + | ---- Class `Spam` first included in bases list here 18 | Eggs, - | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, + | ^^^^ Class `Spam` later repeated here 22 | Eggs, - | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9212781eab003d..cd48fa9877f6ae 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -543,13 +543,6 @@ impl<'db> Type<'db> { Self::Dynamic(DynamicType::Unknown) } - pub(crate) fn into_dynamic(self) -> Option { - match self { - Type::Dynamic(dynamic_type) => Some(dynamic_type), - _ => None, - } - } - pub fn object(db: &'db dyn Db) -> Self { KnownClass::Object.to_instance(db) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 2069ec257ea1f5..d11b1245714cf3 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -107,16 +107,20 @@ impl<'db> ClassBase<'db> { { Self::try_from_type(db, todo_type!("GenericAlias instance")) } + Type::SubclassOf(subclass_of) => subclass_of + .subclass_of() + .into_dynamic() + .map(ClassBase::Dynamic), Type::Intersection(inter) => { - let dynamic_element = inter + let valid_element = inter .positive(db) .iter() - .find_map(|elem| elem.into_dynamic())?; + .find_map(|elem| ClassBase::try_from_type(db, *elem))?; if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) { None } else { - Some(ClassBase::Dynamic(dynamic_element)) + Some(valid_element) } } Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? @@ -137,7 +141,6 @@ impl<'db> ClassBase<'db> { | Type::LiteralString | Type::Tuple(_) | Type::ModuleLiteral(_) - | Type::SubclassOf(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::ProtocolInstance(_) diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 56bccc8b68806f..9eccbdb2c74b66 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -138,6 +138,13 @@ impl<'db> SubclassOfInner<'db> { } } + pub(crate) const fn into_dynamic(self) -> Option { + match self { + Self::Class(_) => None, + Self::Dynamic(dynamic) => Some(dynamic), + } + } + pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option { match ty { Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)), From f30193115944dabf66d18da3a2398096103d2e66 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 12 May 2025 21:53:11 -0400 Subject: [PATCH 064/487] [ty] Induct into instances and subclasses when finding and applying generics (#18052) We were not inducting into instance types and subclass-of types when looking for legacy typevars, nor when apply specializations. This addresses https://github.com/astral-sh/ruff/pull/17832#discussion_r2081502056 ```py from __future__ import annotations from typing import TypeVar, Any, reveal_type S = TypeVar("S") class Foo[T]: def method(self, other: Foo[S]) -> Foo[T | S]: ... # type: ignore[invalid-return-type] def f(x: Foo[Any], y: Foo[Any]): reveal_type(x.method(y)) # revealed: `Foo[Any | S]`, but should be `Foo[Any]` ``` We were not detecting that `S` made `method` generic, since we were not finding it when searching the function signature for legacy typevars. --- crates/ruff_benchmark/benches/ty.rs | 35 +--------- .../resources/mdtest/annotations/self.md | 6 +- .../mdtest/generics/legacy/functions.md | 68 ++++++++++++++++-- .../mdtest/generics/pep695/functions.md | 70 +++++++++++++++++-- crates/ty_python_semantic/src/types.rs | 31 ++++---- crates/ty_python_semantic/src/types/class.rs | 28 +++++++- .../ty_python_semantic/src/types/instance.rs | 40 +++++++++-- .../src/types/protocol_class.rs | 29 +++++++- .../src/types/subclass_of.rs | 30 +++++++- 9 files changed, 269 insertions(+), 68 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index 3bb28fe0ce24af..83c58e5e823bd9 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -59,40 +59,7 @@ type KeyDiagnosticFields = ( Severity, ); -// left: [ -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to function `skip_until` is incorrect", Error), -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to function `skip_until` is incorrect", Error), -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to function `skip_until` is incorrect", Error), -// ] -//right: [ -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to this function is incorrect", Error), -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to this function is incorrect", Error), -// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to this function is incorrect", Error), -// ] - -static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(8224..8254), - "Argument to function `skip_until` is incorrect", - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(16914..16948), - "Argument to function `skip_until` is incorrect", - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(17319..17363), - "Argument to function `skip_until` is incorrect", - Severity::Error, - ), -]; +static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[]; fn tomllib_path(file: &TestFile) -> SystemPathBuf { SystemPathBuf::from("src").join(file.name()) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 5ffbe1ae8b7ffb..8a99fac64c8607 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -19,7 +19,7 @@ class Shape: reveal_type(self) # revealed: Self return self - def nested_type(self) -> list[Self]: + def nested_type(self: Self) -> list[Self]: return [self] def nested_func(self: Self) -> Self: @@ -33,9 +33,7 @@ class Shape: reveal_type(self) # revealed: Unknown return self -# TODO: should be `list[Shape]` -reveal_type(Shape().nested_type()) # revealed: list[Self] - +reveal_type(Shape().nested_type()) # revealed: list[Shape] reveal_type(Shape().nested_func()) # revealed: Shape class Circle(Shape): diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 9f20d754cb9d83..62ffaf561eaad5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -66,18 +66,76 @@ reveal_type(f("string")) # revealed: Literal["string"] ## Inferring “deep” generic parameter types The matching up of call arguments and discovery of constraints on typevars can be a recursive -process for arbitrarily-nested generic types in parameters. +process for arbitrarily-nested generic classes and protocols in parameters. + +TODO: Note that we can currently only infer a specialization for a generic protocol when the +argument _explicitly_ implements the protocol by listing it as a base class. ```py -from typing import TypeVar +from typing import Protocol, TypeVar T = TypeVar("T") -def f(x: list[T]) -> T: +class CanIndex(Protocol[T]): + def __getitem__(self, index: int) -> T: ... + +class ExplicitlyImplements(CanIndex[T]): ... + +def takes_in_list(x: list[T]) -> list[T]: + return x + +def takes_in_protocol(x: CanIndex[T]) -> T: return x[0] -# TODO: revealed: float -reveal_type(f([1.0, 2.0])) # revealed: Unknown +def deep_list(x: list[str]) -> None: + # TODO: revealed: list[str] + reveal_type(takes_in_list(x)) # revealed: list[Unknown] + # TODO: revealed: str + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deeper_list(x: list[set[str]]) -> None: + # TODO: revealed: list[set[str]] + reveal_type(takes_in_list(x)) # revealed: list[Unknown] + # TODO: revealed: set[str] + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deep_explicit(x: ExplicitlyImplements[str]) -> None: + # TODO: revealed: str + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None: + # TODO: revealed: set[str] + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def takes_in_type(x: type[T]) -> type[T]: + return x + +reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form) +``` + +This also works when passing in arguments that are subclasses of the parameter type. + +```py +class Sub(list[int]): ... +class GenericSub(list[T]): ... + +# TODO: revealed: list[int] +reveal_type(takes_in_list(Sub())) # revealed: list[Unknown] +# TODO: revealed: int +reveal_type(takes_in_protocol(Sub())) # revealed: Unknown + +# TODO: revealed: list[str] +reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown] +# TODO: revealed: str +reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown + +class ExplicitSub(ExplicitlyImplements[int]): ... +class ExplicitGenericSub(ExplicitlyImplements[T]): ... + +# TODO: revealed: int +reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown +# TODO: revealed: str +reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown ``` ## Inferring a bound typevar diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index ffd208920c8fc2..31f8569e5c1001 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -61,14 +61,76 @@ reveal_type(f("string")) # revealed: Literal["string"] ## Inferring “deep” generic parameter types The matching up of call arguments and discovery of constraints on typevars can be a recursive -process for arbitrarily-nested generic types in parameters. +process for arbitrarily-nested generic classes and protocols in parameters. + +TODO: Note that we can currently only infer a specialization for a generic protocol when the +argument _explicitly_ implements the protocol by listing it as a base class. ```py -def f[T](x: list[T]) -> T: +from typing import Protocol, TypeVar + +S = TypeVar("S") + +class CanIndex(Protocol[S]): + def __getitem__(self, index: int) -> S: ... + +class ExplicitlyImplements[T](CanIndex[T]): ... + +def takes_in_list[T](x: list[T]) -> list[T]: + return x + +def takes_in_protocol[T](x: CanIndex[T]) -> T: return x[0] -# TODO: revealed: float -reveal_type(f([1.0, 2.0])) # revealed: Unknown +def deep_list(x: list[str]) -> None: + # TODO: revealed: list[str] + reveal_type(takes_in_list(x)) # revealed: list[Unknown] + # TODO: revealed: str + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deeper_list(x: list[set[str]]) -> None: + # TODO: revealed: list[set[str]] + reveal_type(takes_in_list(x)) # revealed: list[Unknown] + # TODO: revealed: set[str] + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deep_explicit(x: ExplicitlyImplements[str]) -> None: + # TODO: revealed: str + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None: + # TODO: revealed: set[str] + reveal_type(takes_in_protocol(x)) # revealed: Unknown + +def takes_in_type[T](x: type[T]) -> type[T]: + return x + +reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form) +``` + +This also works when passing in arguments that are subclasses of the parameter type. + +```py +class Sub(list[int]): ... +class GenericSub[T](list[T]): ... + +# TODO: revealed: list[int] +reveal_type(takes_in_list(Sub())) # revealed: list[Unknown] +# TODO: revealed: int +reveal_type(takes_in_protocol(Sub())) # revealed: Unknown + +# TODO: revealed: list[str] +reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown] +# TODO: revealed: str +reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown + +class ExplicitSub(ExplicitlyImplements[int]): ... +class ExplicitGenericSub[T](ExplicitlyImplements[T]): ... + +# TODO: revealed: int +reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown +# TODO: revealed: str +reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown ``` ## Inferring a bound typevar diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index cd48fa9877f6ae..bf91743bb2a901 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5048,7 +5048,7 @@ impl<'db> Type<'db> { ), Type::ProtocolInstance(instance) => { - Type::ProtocolInstance(instance.apply_specialization(db, type_mapping)) + Type::ProtocolInstance(instance.apply_type_mapping(db, type_mapping)) } Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { @@ -5080,12 +5080,13 @@ impl<'db> Type<'db> { } Type::GenericAlias(generic) => { - let specialization = generic - .specialization(db) - .apply_type_mapping(db, type_mapping); - Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization)) + Type::GenericAlias(generic.apply_type_mapping(db, type_mapping)) } + Type::SubclassOf(subclass_of) => Type::SubclassOf( + subclass_of.apply_type_mapping(db, type_mapping), + ), + Type::PropertyInstance(property) => { Type::PropertyInstance(property.apply_type_mapping(db, type_mapping)) } @@ -5125,9 +5126,6 @@ impl<'db> Type<'db> { // explicitly (via a subscript expression) or implicitly (via a call), and not because // some other generic context's specialization is applied to it. | Type::ClassLiteral(_) - // SubclassOf contains a ClassType, which has already been specialized if needed, like - // above with BoundMethod's self_instance. - | Type::SubclassOf(_) | Type::IntLiteral(_) | Type::BooleanLiteral(_) | Type::LiteralString @@ -5202,7 +5200,19 @@ impl<'db> Type<'db> { } Type::GenericAlias(alias) => { - alias.specialization(db).find_legacy_typevars(db, typevars); + alias.find_legacy_typevars(db, typevars); + } + + Type::NominalInstance(instance) => { + instance.find_legacy_typevars(db, typevars); + } + + Type::ProtocolInstance(instance) => { + instance.find_legacy_typevars(db, typevars); + } + + Type::SubclassOf(subclass_of) => { + subclass_of.find_legacy_typevars(db, typevars); } Type::Dynamic(_) @@ -5215,15 +5225,12 @@ impl<'db> Type<'db> { | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) | Type::ClassLiteral(_) - | Type::SubclassOf(_) | Type::IntLiteral(_) | Type::BooleanLiteral(_) | Type::LiteralString | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::BoundSuper(_) - | Type::NominalInstance(_) - | Type::ProtocolInstance(_) | Type::KnownInstance(_) => {} } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index e99844a9d8a9ba..150915f9ebc12c 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -12,6 +12,7 @@ use crate::types::generics::{GenericContext, Specialization, TypeMapping}; use crate::types::signatures::{Parameter, Parameters}; use crate::types::{ CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature, + TypeVarInstance, }; use crate::{ module_resolver::file_to_module, @@ -31,7 +32,7 @@ use crate::{ definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType, MetaclassCandidate, TupleType, UnionBuilder, UnionType, }, - Db, KnownModule, Program, + Db, FxOrderSet, KnownModule, Program, }; use indexmap::IndexSet; use itertools::Itertools as _; @@ -167,13 +168,25 @@ impl<'db> GenericAlias<'db> { self.origin(db).definition(db) } - fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + pub(super) fn apply_type_mapping<'a>( + self, + db: &'db dyn Db, + type_mapping: TypeMapping<'a, 'db>, + ) -> Self { Self::new( db, self.origin(db), self.specialization(db).apply_type_mapping(db, type_mapping), ) } + + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + self.specialization(db).find_legacy_typevars(db, typevars); + } } impl<'db> From> for Type<'db> { @@ -262,6 +275,17 @@ impl<'db> ClassType<'db> { } } + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + match self { + Self::NonGeneric(_) => {} + Self::Generic(generic) => generic.find_legacy_typevars(db, typevars), + } + } + /// Iterate over the [method resolution order] ("MRO") of the class. /// /// If the MRO could not be accurately resolved, this method falls back to iterating diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index b492a55952a988..a232400670c2b0 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -4,8 +4,8 @@ use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; use crate::symbol::{Symbol, SymbolAndQualifiers}; use crate::types::generics::TypeMapping; -use crate::types::ClassLiteral; -use crate::Db; +use crate::types::{ClassLiteral, TypeVarInstance}; +use crate::{Db, FxOrderSet}; pub(super) use synthesized_protocol::SynthesizedProtocolType; @@ -132,6 +132,14 @@ impl<'db> NominalInstanceType<'db> { class: self.class.apply_type_mapping(db, type_mapping), } } + + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + self.class.find_legacy_typevars(db, typevars); + } } impl<'db> From> for Type<'db> { @@ -270,7 +278,7 @@ impl<'db> ProtocolInstanceType<'db> { } } - pub(super) fn apply_specialization<'a>( + pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>, @@ -284,6 +292,21 @@ impl<'db> ProtocolInstanceType<'db> { )), } } + + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + match self.0 { + Protocol::FromClass(class) => { + class.find_legacy_typevars(db, typevars); + } + Protocol::Synthesized(synthesized) => { + synthesized.find_legacy_typevars(db, typevars); + } + } + } } /// An enumeration of the two kinds of protocol types: those that originate from a class @@ -310,9 +333,10 @@ impl<'db> Protocol<'db> { } mod synthesized_protocol { - use crate::db::Db; use crate::types::generics::TypeMapping; use crate::types::protocol_class::ProtocolInterface; + use crate::types::TypeVarInstance; + use crate::{Db, FxOrderSet}; /// A "synthesized" protocol type that is dissociated from a class definition in source code. /// @@ -339,6 +363,14 @@ mod synthesized_protocol { Self(self.0.specialized_and_normalized(db, type_mapping)) } + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + self.0.find_legacy_typevars(db, typevars); + } + pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> { self.0 } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index cf7a05ebf9310f..c29be0bd3ec563 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -5,10 +5,12 @@ use itertools::{Either, Itertools}; use ruff_python_ast::name::Name; use crate::{ - db::Db, semantic_index::{symbol_table, use_def_map}, symbol::{symbol_from_bindings, symbol_from_declarations}, - types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers}, + types::{ + ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance, + }, + {Db, FxOrderSet}, }; impl<'db> ClassLiteral<'db> { @@ -188,6 +190,21 @@ impl<'db> ProtocolInterface<'db> { Self::SelfReference => Self::SelfReference, } } + + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + match self { + Self::Members(members) => { + for data in members.inner(db).values() { + data.find_legacy_typevars(db, typevars); + } + } + Self::SelfReference => {} + } + } } #[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update)] @@ -210,6 +227,14 @@ impl<'db> ProtocolMemberData<'db> { qualifiers: self.qualifiers, } } + + fn find_legacy_typevars( + &self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + self.ty.find_legacy_typevars(db, typevars); + } } /// A single member of a protocol interface. diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 9eccbdb2c74b66..127540abca175b 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,6 +1,8 @@ use crate::symbol::SymbolAndQualifiers; +use crate::types::generics::TypeMapping; +use crate::{Db, FxOrderSet}; -use super::{ClassType, Db, DynamicType, KnownClass, MemberLookupPolicy, Type}; +use super::{ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeVarInstance}; /// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] @@ -66,6 +68,32 @@ impl<'db> SubclassOfType<'db> { !self.is_dynamic() } + pub(super) fn apply_type_mapping<'a>( + self, + db: &'db dyn Db, + type_mapping: TypeMapping<'a, 'db>, + ) -> Self { + match self.subclass_of { + SubclassOfInner::Class(class) => Self { + subclass_of: SubclassOfInner::Class(class.apply_type_mapping(db, type_mapping)), + }, + SubclassOfInner::Dynamic(_) => self, + } + } + + pub(super) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + match self.subclass_of { + SubclassOfInner::Class(class) => { + class.find_legacy_typevars(db, typevars); + } + SubclassOfInner::Dynamic(_) => {} + } + } + pub(crate) fn find_name_in_mro_with_policy( self, db: &'db dyn Db, From 55df9271ba08312370aeef4833a5209fee052b0e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 May 2025 22:02:25 -0400 Subject: [PATCH 065/487] [ty] Understand homogeneous tuple annotations (#17998) --- .../resources/mdtest/annotations/starred.md | 5 +- .../annotations/unsupported_special_forms.md | 7 +- .../mdtest/assignment/annotations.md | 9 +- .../resources/mdtest/attributes.md | 4 +- .../resources/mdtest/binary/instances.md | 3 +- .../resources/mdtest/binary/tuples.md | 5 +- .../resources/mdtest/call/builtins.md | 2 +- .../resources/mdtest/exception/basic.md | 13 ++- .../resources/mdtest/function/parameters.md | 3 +- .../resources/mdtest/pep695_type_aliases.md | 5 +- .../resources/mdtest/subscript/tuple.md | 4 +- .../type_properties/is_assignable_to.md | 31 ++++++- .../type_properties/is_disjoint_from.md | 4 + .../mdtest/type_properties/is_fully_static.md | 4 +- .../mdtest/type_properties/is_subtype_of.md | 33 ++++++- crates/ty_python_semantic/src/types.rs | 77 ++++++++-------- crates/ty_python_semantic/src/types/infer.rs | 91 +++++++++++++------ 17 files changed, 196 insertions(+), 104 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md index e102a79a2d5296..3bb3de68bf7dba 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md @@ -13,11 +13,10 @@ from typing_extensions import TypeVarTuple Ts = TypeVarTuple("Ts") def append_int(*args: *Ts) -> tuple[*Ts, int]: - # TODO: tuple[*Ts] - reveal_type(args) # revealed: tuple[Unknown, ...] + reveal_type(args) # revealed: @Todo(PEP 646) return (*args, 1) # TODO should be tuple[Literal[True], Literal["a"], int] -reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support) +reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index c225220e010a13..3887d9bb8455ed 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -15,16 +15,13 @@ R_co = TypeVar("R_co", covariant=True) Alias: TypeAlias = int def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: - # TODO: should understand the annotation - reveal_type(args) # revealed: tuple[Unknown, ...] - + reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...] reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`) def g() -> TypeGuard[int]: ... def h() -> TypeIs[int]: ... def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co: - # TODO: should understand the annotation - reveal_type(args) # revealed: tuple[Unknown, ...] + reveal_type(args) # revealed: tuple[@Todo(Support for `typing.ParamSpec`), ...] reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)] return callback(42, *args, **kwargs) diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index 3045460ec46d99..996dc31245908b 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -56,13 +56,12 @@ reveal_type(a) # revealed: tuple[()] reveal_type(b) # revealed: tuple[int] reveal_type(c) # revealed: tuple[str, int] reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]] +reveal_type(e) # revealed: tuple[str, ...] -# TODO: homogeneous tuples, PEP-646 tuples, generics -reveal_type(e) # revealed: @Todo(full tuple[...] support) -reveal_type(f) # revealed: @Todo(full tuple[...] support) -reveal_type(g) # revealed: @Todo(full tuple[...] support) -reveal_type(h) # revealed: tuple[list[int], list[int]] +reveal_type(f) # revealed: @Todo(PEP 646) +reveal_type(g) # revealed: @Todo(PEP 646) +reveal_type(h) # revealed: tuple[list[int], list[int]] reveal_type(i) # revealed: tuple[str | int, str | int] reveal_type(j) # revealed: tuple[str | int] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 3c655db8e3868a..acd0799c287951 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1676,7 +1676,7 @@ functions are instances of that class: ```py def f(): ... -reveal_type(f.__defaults__) # revealed: @Todo(full tuple[...] support) | None +reveal_type(f.__defaults__) # revealed: tuple[Any, ...] | None reveal_type(f.__kwdefaults__) # revealed: dict[str, Any] | None ``` @@ -1730,7 +1730,7 @@ All attribute access on literal `bytes` types is currently delegated to `builtin ```py # revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes reveal_type(b"foo".join) -# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool +# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool reveal_type(b"foo".endswith) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/binary/instances.md b/crates/ty_python_semantic/resources/mdtest/binary/instances.md index 465527a4c4cd48..c27e70ed76e0ff 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/instances.md @@ -317,8 +317,7 @@ reveal_type(A() + b"foo") # revealed: A reveal_type(b"foo" + A()) # revealed: bytes reveal_type(A() + ()) # revealed: A -# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances -reveal_type(() + A()) # revealed: @Todo(full tuple[...] support) +reveal_type(() + A()) # revealed: A literal_string_instance = "foo" * 1_000_000_000 # the test is not testing what it's meant to be testing if this isn't a `LiteralString`: diff --git a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md index 49fb5b7333a2be..ae23e4090a46fa 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md @@ -17,6 +17,7 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]): ```py def _(x: tuple[int, ...], y: tuple[str, ...]): - reveal_type(x + y) # revealed: @Todo(full tuple[...] support) - reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support) + # TODO: should be `tuple[int | str, ...]` + reveal_type(x + y) # revealed: tuple[int | Unknown, ...] + reveal_type(x + (1, 2)) # revealed: tuple[int, ...] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/builtins.md b/crates/ty_python_semantic/resources/mdtest/call/builtins.md index c93afe9baa2304..a2bee55560036c 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/call/builtins.md @@ -54,7 +54,7 @@ type(b"Foo", (), {}) # error: [no-matching-overload] "No overload of class `type` matches arguments" type("Foo", Base, {}) -# TODO: this should be an error +# error: [no-matching-overload] "No overload of class `type` matches arguments" type("Foo", (1, 2), {}) # TODO: this should be an error diff --git a/crates/ty_python_semantic/resources/mdtest/exception/basic.md b/crates/ty_python_semantic/resources/mdtest/exception/basic.md index f313532303f7dd..e8befe07d8771c 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/basic.md @@ -45,6 +45,8 @@ def foo( x: type[AttributeError], y: tuple[type[OSError], type[RuntimeError]], z: tuple[type[BaseException], ...], + zz: tuple[type[TypeError | RuntimeError], ...], + zzz: type[BaseException] | tuple[type[BaseException], ...], ): try: help() @@ -53,8 +55,11 @@ def foo( except y as f: reveal_type(f) # revealed: OSError | RuntimeError except z as g: - # TODO: should be `BaseException` - reveal_type(g) # revealed: @Todo(full tuple[...] support) + reveal_type(g) # revealed: BaseException + except zz as h: + reveal_type(h) # revealed: TypeError | RuntimeError + except zzz as i: + reveal_type(i) # revealed: BaseException ``` ## Invalid exception handlers @@ -86,9 +91,9 @@ def foo( # error: [invalid-exception-caught] except y as f: reveal_type(f) # revealed: OSError | RuntimeError | Unknown + # error: [invalid-exception-caught] except z as g: - # TODO: should emit a diagnostic here: - reveal_type(g) # revealed: @Todo(full tuple[...] support) + reveal_type(g) # revealed: Unknown ``` ## Object raised is not an exception diff --git a/crates/ty_python_semantic/resources/mdtest/function/parameters.md b/crates/ty_python_semantic/resources/mdtest/function/parameters.md index d61a320c6204b0..eb7316fe91a2d4 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/parameters.md +++ b/crates/ty_python_semantic/resources/mdtest/function/parameters.md @@ -25,8 +25,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5, reveal_type(f) # revealed: Literal[4] reveal_type(g) # revealed: Unknown | Literal[5] reveal_type(h) # revealed: Literal[6] - # TODO: should be `tuple[object, ...]` - reveal_type(args) # revealed: tuple[Unknown, ...] + reveal_type(args) # revealed: tuple[object, ...] reveal_type(kwargs) # revealed: dict[str, str] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md index 6f84dc5481f233..c7b4820b292e4d 100644 --- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md @@ -68,10 +68,7 @@ y: MyIntOrStr = None ```py type ListOrSet[T] = list[T] | set[T] - -# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`, -# as specified in the `typeshed` stubs. -reveal_type(ListOrSet.__type_params__) # revealed: @Todo(full tuple[...] support) +reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] ``` ## `TypeAliasType` properties diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index 20d01d4fb8257d..fbd4434d025bf8 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -70,7 +70,7 @@ def _(m: int, n: int): tuple_slice = t[m:n] # TODO: Should be `tuple[Literal[1, 'a', b"b"] | None, ...]` - reveal_type(tuple_slice) # revealed: @Todo(full tuple[...] support) + reveal_type(tuple_slice) # revealed: tuple[Unknown, ...] ``` ## Inheritance @@ -101,7 +101,7 @@ class A: ... def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]): reveal_type(c) # revealed: tuple[Unknown, ...] reveal_type(d) # revealed: tuple[int, A] - reveal_type(e) # revealed: @Todo(full tuple[...] support) + reveal_type(e) # revealed: tuple[Any, ...] ``` ### Inheritance diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 7f800567db975e..d70151109dbc59 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -198,7 +198,7 @@ static_assert(is_assignable_to(Meta, type[Any])) static_assert(is_assignable_to(Meta, type[Unknown])) ``` -## Tuple types +## Heterogeneous tuple types ```py from ty_extensions import static_assert, is_assignable_to, AlwaysTruthy, AlwaysFalsy @@ -232,6 +232,35 @@ static_assert(not is_assignable_to(tuple[int, int], tuple[Literal[1], int])) static_assert(not is_assignable_to(tuple[Any, Literal[2]], tuple[int, str])) ``` +## Assignability of heterogeneous tuple types to homogeneous tuple types + +While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous +tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be +assignable to `Sequence`: + +```py +from typing import Literal, Any, Sequence +from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy + +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...])) +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...])) +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...])) +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...])) +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...])) +static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], Sequence[int])) +static_assert(is_assignable_to(tuple[int, ...], Sequence[int])) +static_assert(is_assignable_to(tuple[int, ...], Sequence[Any])) +static_assert(is_assignable_to(tuple[Any, ...], Sequence[int])) + +static_assert(is_assignable_to(tuple[()], tuple[Literal[1, 2], ...])) +static_assert(is_assignable_to(tuple[()], tuple[int, ...])) +static_assert(is_assignable_to(tuple[()], tuple[int | str, ...])) +static_assert(is_assignable_to(tuple[()], tuple[Not[AlwaysFalsy], ...])) +static_assert(is_assignable_to(tuple[()], Sequence[int])) + +static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...])) +``` + ## Union types ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index bc9d2b9c27d8ad..a5935725b6ae38 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -91,6 +91,10 @@ static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1]]) static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[3]])) static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], int])) +static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[int, ...])) + +# TODO: should pass +static_assert(is_disjoint_from(tuple[int, int], tuple[None, ...])) # error: [static-assert-error] ``` ## Unions diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md index 3551742960f78c..ef9f677c55eed0 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md @@ -48,7 +48,9 @@ static_assert(not is_fully_static(Any | str)) static_assert(not is_fully_static(str | Unknown)) static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]])) -static_assert(not is_fully_static(tuple[Any, ...])) +# TODO: should pass +static_assert(not is_fully_static(tuple[Any, ...])) # error: [static-assert-error] + static_assert(not is_fully_static(tuple[int, Any])) static_assert(not is_fully_static(type[Any])) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 22fba74fe54967..f9a97c8c51ef0a 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -118,7 +118,7 @@ static_assert(is_subtype_of(Literal[b"foo"], bytes)) static_assert(is_subtype_of(Literal[b"foo"], object)) ``` -## Tuple types +## Heterogeneous tuple types ```py from ty_extensions import is_subtype_of, static_assert @@ -150,9 +150,36 @@ static_assert(not is_subtype_of(tuple[B1, B2], tuple[Unrelated, Unrelated])) static_assert(not is_subtype_of(tuple[B1, B2], tuple[()])) static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1])) static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1, A2, Unrelated])) +static_assert(is_subtype_of(tuple[int], tuple[object, ...])) +``` + +## Subtyping of heterogeneous tuple types and homogeneous tuple types + +While a homogeneous tuple type is not a subtype of any heterogeneous tuple types, a heterogeneous +tuple type can be a subtype of a homogeneous tuple type, and homogeneous tuple types can be subtypes +of `Sequence`: + +```py +from typing import Literal, Any, Sequence +from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy + +static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...])) +static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...])) +static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...])) +static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...])) +static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], Sequence[int])) +static_assert(is_subtype_of(tuple[int, ...], Sequence[int])) + +static_assert(is_subtype_of(tuple[()], tuple[Literal[1, 2], ...])) +static_assert(is_subtype_of(tuple[()], tuple[int, ...])) +static_assert(is_subtype_of(tuple[()], tuple[int | str, ...])) +static_assert(is_subtype_of(tuple[()], tuple[Not[AlwaysFalsy], ...])) +static_assert(is_subtype_of(tuple[()], Sequence[int])) -# TODO: should pass -static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error] +static_assert(not is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Any, ...])) +static_assert(not is_subtype_of(tuple[int, int], tuple[str, ...])) +static_assert(not is_subtype_of(tuple[int, ...], Sequence[Any])) +static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int])) ``` ## Union types diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index bf91743bb2a901..60418aac628957 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1273,17 +1273,8 @@ impl<'db> Type<'db> { ) } - // Other than the special tuple-to-tuple case handled, above, - // tuple subtyping delegates to `Instance(tuple)` in the same way as the literal types. - // - // All heterogeneous tuple types are subtypes of `Instance()`: - // `Instance()` expresses "the set of all possible instances of the class `T`"; - // consequently, `Instance()` expresses "the set of all possible instances of the class `tuple`". - // This type can be spelled in type annotations as `tuple[object, ...]` (since `tuple` is covariant). - // - // Note that this is not the same type as the type spelled in type annotations as `tuple`; - // as that type is equivalent to `type[Any, ...]` (and therefore not a fully static type). - (Type::Tuple(_), _) => KnownClass::Tuple.to_instance(db).is_subtype_of(db, target), + // `tuple[A, B, C]` is a subtype of `tuple[A | B | C, ...]` + (Type::Tuple(tuple), _) => tuple.homogeneous_supertype(db).is_subtype_of(db, target), (Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target), (Type::BoundSuper(_), _) => KnownClass::Super.to_instance(db).is_subtype_of(db, target), @@ -1494,10 +1485,10 @@ impl<'db> Type<'db> { // This special case is required because the left-hand side tuple might be a // gradual type, so we can not rely on subtyping. This allows us to assign e.g. // `tuple[Any, int]` to `tuple`. - (Type::Tuple(_), _) - if KnownClass::Tuple - .to_instance(db) - .is_assignable_to(db, target) => + // + // `tuple[A, B, C]` is assignable to `tuple[A | B | C, ...]` + (Type::Tuple(tuple), _) + if tuple.homogeneous_supertype(db).is_assignable_to(db, target) => { true } @@ -1960,9 +1951,9 @@ impl<'db> Type<'db> { !known_instance.is_instance_of(db, instance.class()) } - (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(_)) - | (Type::Tuple(_), known_instance_ty @ Type::KnownInstance(_)) => { - known_instance_ty.is_disjoint_from(db, KnownClass::Tuple.to_instance(db)) + (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple)) + | (Type::Tuple(tuple), known_instance_ty @ Type::KnownInstance(_)) => { + known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)) } (Type::BooleanLiteral(..), Type::NominalInstance(instance)) @@ -2088,17 +2079,9 @@ impl<'db> Type<'db> { .any(|(e1, e2)| e1.is_disjoint_from(db, *e2)) } - (Type::Tuple(..), instance @ Type::NominalInstance(_)) - | (instance @ Type::NominalInstance(_), Type::Tuple(..)) => { - // We cannot be sure if the tuple is disjoint from the instance because: - // - 'other' might be the homogeneous arbitrary-length tuple type - // tuple[T, ...] (which we don't have support for yet); if all of - // our element types are not disjoint with T, this is not disjoint - // - 'other' might be a user subtype of tuple, which, if generic - // over the same or compatible *Ts, would overlap with tuple. - // - // TODO: add checks for the above cases once we support them - instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db)) + (Type::Tuple(tuple), instance @ Type::NominalInstance(_)) + | (instance @ Type::NominalInstance(_), Type::Tuple(tuple)) => { + instance.is_disjoint_from(db, tuple.homogeneous_supertype(db)) } (Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => { @@ -2588,7 +2571,7 @@ impl<'db> Type<'db> { KnownClass::Str.to_instance(db).instance_member(db, name) } Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).instance_member(db, name), - Type::Tuple(_) => KnownClass::Tuple.to_instance(db).instance_member(db, name), + Type::Tuple(tuple) => tuple.homogeneous_supertype(db).instance_member(db, name), Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name), Type::ModuleLiteral(_) => KnownClass::ModuleType @@ -3573,8 +3556,10 @@ impl<'db> Type<'db> { db, [ KnownClass::Str.to_instance(db), - // TODO: tuple[str, ...] - KnownClass::Tuple.to_instance(db), + KnownClass::Tuple.to_specialized_instance( + db, + [KnownClass::Str.to_instance(db)], + ), ], )), Parameter::positional_only(Some(Name::new_static("start"))) @@ -3854,6 +3839,9 @@ impl<'db> Type<'db> { } Some(KnownClass::Type) => { + let str_instance = KnownClass::Str.to_instance(db); + let type_instance = KnownClass::Type.to_instance(db); + // ```py // class type: // @overload @@ -3869,20 +3857,26 @@ impl<'db> Type<'db> { Name::new_static("o"), )) .with_annotated_type(Type::any())]), - Some(KnownClass::Type.to_instance(db)), + Some(type_instance), ), Signature::new( Parameters::new([ Parameter::positional_only(Some(Name::new_static("name"))) - .with_annotated_type(KnownClass::Str.to_instance(db)), + .with_annotated_type(str_instance), Parameter::positional_only(Some(Name::new_static("bases"))) - // TODO: Should be tuple[type, ...] once we have support for homogenous tuples - .with_annotated_type(KnownClass::Tuple.to_instance(db)), + .with_annotated_type( + KnownClass::Tuple + .to_specialized_instance(db, [type_instance]), + ), Parameter::positional_only(Some(Name::new_static("dict"))) - // TODO: Should be `dict[str, Any]` once we have support for generics - .with_annotated_type(KnownClass::Dict.to_instance(db)), + .with_annotated_type( + KnownClass::Dict.to_specialized_instance( + db, + [str_instance, Type::any()], + ), + ), ]), - Some(KnownClass::Type.to_instance(db)), + Some(type_instance), ), ], ); @@ -7924,6 +7918,11 @@ pub struct TupleType<'db> { } impl<'db> TupleType<'db> { + fn homogeneous_supertype(self, db: &'db dyn Db) -> Type<'db> { + KnownClass::Tuple + .to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))]) + } + pub(crate) fn from_elements>>( db: &'db dyn Db, types: impl IntoIterator, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 43c384c84f43a8..8f3878a01d390e 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2107,9 +2107,13 @@ impl<'db> TypeInferenceBuilder<'db> { definition: Definition<'db>, ) { if let Some(annotation) = parameter.annotation() { - let _annotated_ty = self.file_expression_type(annotation); - // TODO `tuple[annotated_type, ...]` - let ty = KnownClass::Tuple.to_instance(self.db()); + let ty = if annotation.is_starred_expr() { + todo_type!("PEP 646") + } else { + let annotated_type = self.file_expression_type(annotation); + KnownClass::Tuple.to_specialized_instance(self.db(), [annotated_type]) + }; + self.add_declaration_with_binding( parameter.into(), definition, @@ -2119,8 +2123,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.add_binding( parameter.into(), definition, - // TODO `tuple[Unknown, ...]` - KnownClass::Tuple.to_instance(self.db()), + KnownClass::Tuple.to_specialized_instance(self.db(), [Type::unknown()]), ); } } @@ -2473,16 +2476,32 @@ impl<'db> TypeInferenceBuilder<'db> { except_handler_definition: &ExceptHandlerDefinitionKind, definition: Definition<'db>, ) { + fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option> { + let class = ty.into_nominal_instance()?.class(); + if !class.is_known(db, KnownClass::Tuple) { + return None; + } + let ClassType::Generic(class) = class else { + return None; + }; + let specialization = class.specialization(db).types(db)[0]; + let specialization_instance = specialization.to_instance(db)?; + + specialization_instance + .is_assignable_to(db, KnownClass::BaseException.to_instance(db)) + .then_some(specialization_instance) + } + let node = except_handler_definition.handled_exceptions(); // If there is no handled exception, it's invalid syntax; // a diagnostic will have already been emitted let node_ty = node.map_or(Type::unknown(), |ty| self.infer_expression(ty)); + let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db()); // If it's an `except*` handler, this won't actually be the type of the bound symbol; // it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`. let symbol_ty = if let Type::Tuple(tuple) = node_ty { - let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db()); let mut builder = UnionBuilder::new(self.db()); for element in tuple.elements(self.db()).iter().copied() { builder = builder.add( @@ -2500,27 +2519,37 @@ impl<'db> TypeInferenceBuilder<'db> { ); } builder.build() - } else if node_ty.is_subtype_of(self.db(), KnownClass::Tuple.to_instance(self.db())) { - todo_type!("Homogeneous tuple in exception handler") + } else if node_ty.is_assignable_to(self.db(), type_base_exception) { + node_ty.to_instance(self.db()).expect( + "`Type::to_instance()` should always return `Some()` \ + if called on a type assignable to `type[BaseException]`", + ) + } else if node_ty.is_assignable_to( + self.db(), + KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]), + ) { + extract_tuple_specialization(self.db(), node_ty) + .unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db())) + } else if node_ty.is_assignable_to( + self.db(), + UnionType::from_elements( + self.db(), + [ + type_base_exception, + KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]), + ], + ), + ) { + KnownClass::BaseException.to_instance(self.db()) } else { - let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db()); - if node_ty.is_assignable_to(self.db(), type_base_exception) { - node_ty.to_instance(self.db()).expect( - "`Type::to_instance()` should always return `Some()` \ - if called on a type assignable to `type[BaseException]`", - ) - } else { - if let Some(node) = node { - report_invalid_exception_caught(&self.context, node, node_ty); - } - Type::unknown() + if let Some(node) = node { + report_invalid_exception_caught(&self.context, node, node_ty); } + Type::unknown() }; let symbol_ty = if except_handler_definition.is_star() { // TODO: we should infer `ExceptionGroup` if `node_ty` is a subtype of `tuple[type[Exception], ...]` - // (needs support for homogeneous tuples). - // // TODO: should be generic with `symbol_ty` as the generic parameter KnownClass::BaseExceptionGroup.to_instance(self.db()) } else { @@ -7970,7 +7999,7 @@ impl<'db> TypeInferenceBuilder<'db> { } match element { - ast::Expr::EllipsisLiteral(_) | ast::Expr::Starred(_) => true, + ast::Expr::Starred(_) => true, ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => { let value_ty = if builder.deferred_state.in_string_annotation() { // Using `.expression_type` does not work in string annotations, because @@ -7980,17 +8009,23 @@ impl<'db> TypeInferenceBuilder<'db> { builder.expression_type(value) }; - matches!(value_ty, Type::KnownInstance(KnownInstanceType::Unpack)) + value_ty == Type::KnownInstance(KnownInstanceType::Unpack) } _ => false, } } - // TODO: - // - homogeneous tuples - // - PEP 646 + // TODO: PEP 646 match tuple_slice { ast::Expr::Tuple(elements) => { + if let [element, ellipsis @ ast::Expr::EllipsisLiteral(_)] = &*elements.elts { + self.infer_expression(ellipsis); + let result = KnownClass::Tuple + .to_specialized_instance(self.db(), [self.infer_type_expression(element)]); + self.store_expression_type(tuple_slice, result); + return result; + } + let mut element_types = Vec::with_capacity(elements.len()); // Whether to infer `Todo` for the whole tuple @@ -8005,7 +8040,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let ty = if return_todo { - todo_type!("full tuple[...] support") + todo_type!("PEP 646") } else { TupleType::from_elements(self.db(), element_types) }; @@ -8021,7 +8056,7 @@ impl<'db> TypeInferenceBuilder<'db> { let single_element_ty = self.infer_type_expression(single_element); if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self) { - todo_type!("full tuple[...] support") + todo_type!("PEP 646") } else { TupleType::from_elements(self.db(), std::iter::once(single_element_ty)) } From 0fb94c052e8e5e48743bf70931c4810bcc1fe53e Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 12 May 2025 22:12:44 -0400 Subject: [PATCH 066/487] [ty] Infer parameter specializations of generic aliases (#18021) This updates our function specialization inference to infer type mappings from parameters that are generic aliases, e.g.: ```py def f[T](x: list[T]) -> T: ... reveal_type(f(["a", "b"])) # revealed: str ``` Though note that we're still inferring the type of list literals as `list[Unknown]`, so for now we actually need something like the following in our tests: ```py def _(x: list[str]): reveal_type(f(x)) # revealed: str ``` --- .../resources/mdtest/binary/tuples.md | 3 +- .../mdtest/generics/legacy/functions.md | 12 ++-- .../mdtest/generics/pep695/functions.md | 12 ++-- crates/ty_python_semantic/src/types.rs | 57 +++++++++---------- .../ty_python_semantic/src/types/builder.rs | 8 +-- crates/ty_python_semantic/src/types/class.rs | 4 +- .../src/types/class_base.rs | 2 +- .../ty_python_semantic/src/types/display.rs | 2 +- .../ty_python_semantic/src/types/generics.rs | 32 +++++++++++ crates/ty_python_semantic/src/types/infer.rs | 25 +++----- .../ty_python_semantic/src/types/instance.rs | 16 ++++-- crates/ty_python_semantic/src/types/narrow.rs | 4 +- .../src/types/type_ordering.rs | 6 +- 13 files changed, 98 insertions(+), 85 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md index ae23e4090a46fa..78279831fd4ca4 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md @@ -17,7 +17,6 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]): ```py def _(x: tuple[int, ...], y: tuple[str, ...]): - # TODO: should be `tuple[int | str, ...]` - reveal_type(x + y) # revealed: tuple[int | Unknown, ...] + reveal_type(x + y) # revealed: tuple[int | str, ...] reveal_type(x + (1, 2)) # revealed: tuple[int, ...] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 62ffaf561eaad5..d188f269a81ce5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -88,14 +88,12 @@ def takes_in_protocol(x: CanIndex[T]) -> T: return x[0] def deep_list(x: list[str]) -> None: - # TODO: revealed: list[str] - reveal_type(takes_in_list(x)) # revealed: list[Unknown] + reveal_type(takes_in_list(x)) # revealed: list[str] # TODO: revealed: str reveal_type(takes_in_protocol(x)) # revealed: Unknown def deeper_list(x: list[set[str]]) -> None: - # TODO: revealed: list[set[str]] - reveal_type(takes_in_list(x)) # revealed: list[Unknown] + reveal_type(takes_in_list(x)) # revealed: list[set[str]] # TODO: revealed: set[str] reveal_type(takes_in_protocol(x)) # revealed: Unknown @@ -119,13 +117,11 @@ This also works when passing in arguments that are subclasses of the parameter t class Sub(list[int]): ... class GenericSub(list[T]): ... -# TODO: revealed: list[int] -reveal_type(takes_in_list(Sub())) # revealed: list[Unknown] +reveal_type(takes_in_list(Sub())) # revealed: list[int] # TODO: revealed: int reveal_type(takes_in_protocol(Sub())) # revealed: Unknown -# TODO: revealed: list[str] -reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown] +reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str] # TODO: revealed: str reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index 31f8569e5c1001..11701f17e7f84b 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -83,14 +83,12 @@ def takes_in_protocol[T](x: CanIndex[T]) -> T: return x[0] def deep_list(x: list[str]) -> None: - # TODO: revealed: list[str] - reveal_type(takes_in_list(x)) # revealed: list[Unknown] + reveal_type(takes_in_list(x)) # revealed: list[str] # TODO: revealed: str reveal_type(takes_in_protocol(x)) # revealed: Unknown def deeper_list(x: list[set[str]]) -> None: - # TODO: revealed: list[set[str]] - reveal_type(takes_in_list(x)) # revealed: list[Unknown] + reveal_type(takes_in_list(x)) # revealed: list[set[str]] # TODO: revealed: set[str] reveal_type(takes_in_protocol(x)) # revealed: Unknown @@ -114,13 +112,11 @@ This also works when passing in arguments that are subclasses of the parameter t class Sub(list[int]): ... class GenericSub[T](list[T]): ... -# TODO: revealed: list[int] -reveal_type(takes_in_list(Sub())) # revealed: list[Unknown] +reveal_type(takes_in_list(Sub())) # revealed: list[int] # TODO: revealed: int reveal_type(takes_in_protocol(Sub())) # revealed: Unknown -# TODO: revealed: list[str] -reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown] +reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str] # TODO: revealed: str reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 60418aac628957..9e536cbf31f0b8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -562,25 +562,22 @@ impl<'db> Type<'db> { fn is_none(&self, db: &'db dyn Db) -> bool { self.into_nominal_instance() - .is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType)) + .is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType)) } fn is_bool(&self, db: &'db dyn Db) -> bool { self.into_nominal_instance() - .is_some_and(|instance| instance.class().is_known(db, KnownClass::Bool)) + .is_some_and(|instance| instance.class.is_known(db, KnownClass::Bool)) } pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool { - self.into_nominal_instance().is_some_and(|instance| { - instance - .class() - .is_known(db, KnownClass::NotImplementedType) - }) + self.into_nominal_instance() + .is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType)) } pub fn is_object(&self, db: &'db dyn Db) -> bool { self.into_nominal_instance() - .is_some_and(|instance| instance.class().is_object(db)) + .is_some_and(|instance| instance.class.is_object(db)) } pub const fn is_todo(&self) -> bool { @@ -1063,7 +1060,7 @@ impl<'db> Type<'db> { (_, Type::Never) => false, // Everything is a subtype of `object`. - (_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true, + (_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true, // In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied: // 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`. @@ -1373,7 +1370,7 @@ impl<'db> Type<'db> { // All types are assignable to `object`. // TODO this special case might be removable once the below cases are comprehensive - (_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true, + (_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true, // In general, a TypeVar `T` is not assignable to a type `S` unless one of the two conditions is satisfied: // 1. `T` is a bound TypeVar and `T`'s upper bound is assignable to `S`. @@ -1547,7 +1544,7 @@ impl<'db> Type<'db> { } (Type::NominalInstance(instance), Type::Callable(_)) - if instance.class().is_subclass_of_any_or_unknown(db) => + if instance.class.is_subclass_of_any_or_unknown(db) => { true } @@ -1616,7 +1613,7 @@ impl<'db> Type<'db> { } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - n.class().is_object(db) && protocol.normalized(db) == nominal + n.class.is_object(db) && protocol.normalized(db) == nominal } _ => self == other && self.is_fully_static(db) && other.is_fully_static(db), } @@ -1671,7 +1668,7 @@ impl<'db> Type<'db> { } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - n.class().is_object(db) && protocol.normalized(db) == nominal + n.class.is_object(db) && protocol.normalized(db) == nominal } _ => false, } @@ -1883,7 +1880,7 @@ impl<'db> Type<'db> { // member on `protocol`. (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - n.class().is_final(db) && !nominal.satisfies_protocol(db, protocol) + n.class.is_final(db) && !nominal.satisfies_protocol(db, protocol) } ( @@ -1948,7 +1945,7 @@ impl<'db> Type<'db> { (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { - !known_instance.is_instance_of(db, instance.class()) + !known_instance.is_instance_of(db, instance.class) } (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple)) @@ -1960,7 +1957,7 @@ impl<'db> Type<'db> { | (Type::NominalInstance(instance), Type::BooleanLiteral(..)) => { // A `Type::BooleanLiteral()` must be an instance of exactly `bool` // (it cannot be an instance of a `bool` subclass) - !KnownClass::Bool.is_subclass_of(db, instance.class()) + !KnownClass::Bool.is_subclass_of(db, instance.class) } (Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true, @@ -1969,7 +1966,7 @@ impl<'db> Type<'db> { | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { // A `Type::IntLiteral()` must be an instance of exactly `int` // (it cannot be an instance of an `int` subclass) - !KnownClass::Int.is_subclass_of(db, instance.class()) + !KnownClass::Int.is_subclass_of(db, instance.class) } (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true, @@ -1981,7 +1978,7 @@ impl<'db> Type<'db> { | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { // A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str` // (it cannot be an instance of a `str` subclass) - !KnownClass::Str.is_subclass_of(db, instance.class()) + !KnownClass::Str.is_subclass_of(db, instance.class) } (Type::LiteralString, Type::LiteralString) => false, @@ -1991,7 +1988,7 @@ impl<'db> Type<'db> { | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { // A `Type::BytesLiteral()` must be an instance of exactly `bytes` // (it cannot be an instance of a `bytes` subclass) - !KnownClass::Bytes.is_subclass_of(db, instance.class()) + !KnownClass::Bytes.is_subclass_of(db, instance.class) } // A class-literal type `X` is always disjoint from an instance type `Y`, @@ -2012,7 +2009,7 @@ impl<'db> Type<'db> { | (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => { // A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType` // (it cannot be an instance of a `types.FunctionType` subclass) - !KnownClass::FunctionType.is_subclass_of(db, instance.class()) + !KnownClass::FunctionType.is_subclass_of(db, instance.class) } (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType @@ -2440,7 +2437,7 @@ impl<'db> Type<'db> { // i.e. Type::NominalInstance(type). So looking up a name in the MRO of // `Type::NominalInstance(type)` is equivalent to looking up the name in the // MRO of the class `object`. - Type::NominalInstance(instance) if instance.class().is_known(db, KnownClass::Type) => { + Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => { KnownClass::Object .to_class_literal(db) .find_name_in_mro_with_policy(db, name, policy) @@ -2530,7 +2527,7 @@ impl<'db> Type<'db> { Type::Dynamic(_) | Type::Never => Symbol::bound(self).into(), - Type::NominalInstance(instance) => instance.class().instance_member(db, name), + Type::NominalInstance(instance) => instance.class.instance_member(db, name), Type::ProtocolInstance(protocol) => protocol.instance_member(db, name), @@ -2978,7 +2975,7 @@ impl<'db> Type<'db> { Type::NominalInstance(instance) if matches!(name.as_str(), "major" | "minor") - && instance.class().is_known(db, KnownClass::VersionInfo) => + && instance.class.is_known(db, KnownClass::VersionInfo) => { let python_version = Program::get(db).python_version(db); let segment = if name == "major" { @@ -3050,7 +3047,7 @@ impl<'db> Type<'db> { // resolve the attribute. if matches!( self.into_nominal_instance() - .and_then(|instance| instance.class().known(db)), + .and_then(|instance| instance.class.known(db)), Some(KnownClass::ModuleType | KnownClass::GenericAlias) ) { return Symbol::Unbound.into(); @@ -3309,7 +3306,7 @@ impl<'db> Type<'db> { } }, - Type::NominalInstance(instance) => match instance.class().known(db) { + Type::NominalInstance(instance) => match instance.class.known(db) { Some(known_class) => known_class.bool(), None => try_dunder_bool()?, }, @@ -4863,7 +4860,7 @@ impl<'db> Type<'db> { Type::Dynamic(_) => Ok(*self), - Type::NominalInstance(instance) => match instance.class().known(db) { + Type::NominalInstance(instance) => match instance.class.known(db) { Some(KnownClass::TypeVar) => Ok(todo_type!( "Support for `typing.TypeVar` instances in type expressions" )), @@ -5291,7 +5288,7 @@ impl<'db> Type<'db> { } Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))), Self::NominalInstance(instance) => { - Some(TypeDefinition::Class(instance.class().definition(db))) + Some(TypeDefinition::Class(instance.class.definition(db))) } Self::KnownInstance(instance) => match instance { KnownInstanceType::TypeVar(var) => { @@ -8046,7 +8043,7 @@ impl<'db> SuperOwnerKind<'db> { Either::Left(ClassBase::Dynamic(dynamic).mro(db, None)) } SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)), - SuperOwnerKind::Instance(instance) => Either::Right(instance.class().iter_mro(db)), + SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)), } } @@ -8062,7 +8059,7 @@ impl<'db> SuperOwnerKind<'db> { match self { SuperOwnerKind::Dynamic(_) => None, SuperOwnerKind::Class(class) => Some(class), - SuperOwnerKind::Instance(instance) => Some(instance.class()), + SuperOwnerKind::Instance(instance) => Some(instance.class), } } @@ -8240,7 +8237,7 @@ impl<'db> BoundSuperType<'db> { .expect("Calling `find_name_in_mro` on dynamic type should return `Some`") } SuperOwnerKind::Class(class) => class, - SuperOwnerKind::Instance(instance) => instance.class(), + SuperOwnerKind::Instance(instance) => instance.class, }; let (class_literal, _) = class.class_literal(db); diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 01b5fa8c462e81..5b55088a8b3365 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -614,7 +614,7 @@ impl<'db> InnerIntersectionBuilder<'db> { _ => { let known_instance = new_positive .into_nominal_instance() - .and_then(|instance| instance.class().known(db)); + .and_then(|instance| instance.class.known(db)); if known_instance == Some(KnownClass::Object) { // `object & T` -> `T`; it is always redundant to add `object` to an intersection @@ -634,7 +634,7 @@ impl<'db> InnerIntersectionBuilder<'db> { new_positive = Type::BooleanLiteral(false); } Type::NominalInstance(instance) - if instance.class().is_known(db, KnownClass::Bool) => + if instance.class.is_known(db, KnownClass::Bool) => { match new_positive { // `bool & AlwaysTruthy` -> `Literal[True]` @@ -728,7 +728,7 @@ impl<'db> InnerIntersectionBuilder<'db> { self.positive .iter() .filter_map(|ty| ty.into_nominal_instance()) - .filter_map(|instance| instance.class().known(db)) + .filter_map(|instance| instance.class.known(db)) .any(KnownClass::is_bool) }; @@ -744,7 +744,7 @@ impl<'db> InnerIntersectionBuilder<'db> { Type::Never => { // Adding ~Never to an intersection is a no-op. } - Type::NominalInstance(instance) if instance.class().is_object(db) => { + Type::NominalInstance(instance) if instance.class.is_object(db) => { // Adding ~object to an intersection results in Never. *self = Self::default(); self.positive.insert(Type::Never); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 150915f9ebc12c..96673012f1f498 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2733,7 +2733,7 @@ impl<'db> Type<'db> { /// The type must be a specialization of the `slice` builtin type, where the specialized /// typevars are statically known integers or `None`. pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option { - let ClassType::Generic(alias) = self.into_nominal_instance()?.class() else { + let ClassType::Generic(alias) = self.into_nominal_instance()?.class else { return None; }; if !alias.origin(db).is_known(db, KnownClass::Slice) { @@ -2747,7 +2747,7 @@ impl<'db> Type<'db> { Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(), Type::BooleanLiteral(b) => Some(Some(i32::from(*b))), Type::NominalInstance(instance) - if instance.class().is_known(db, KnownClass::NoneType) => + if instance.class.is_known(db, KnownClass::NoneType) => { Some(None) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index d11b1245714cf3..bcd2af97cbf6cc 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -103,7 +103,7 @@ impl<'db> ClassBase<'db> { } Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))), Type::NominalInstance(instance) - if instance.class().is_known(db, KnownClass::GenericAlias) => + if instance.class.is_known(db, KnownClass::GenericAlias) => { Self::try_from_type(db, todo_type!("GenericAlias instance")) } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index d0716a6459b9f0..467a5779054dc6 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -69,7 +69,7 @@ impl Display for DisplayRepresentation<'_> { Type::Dynamic(dynamic) => dynamic.fmt(f), Type::Never => f.write_str("Never"), Type::NominalInstance(instance) => { - match (instance.class(), instance.class().known(self.db)) { + match (instance.class, instance.class.known(self.db)) { (_, Some(KnownClass::NoneType)) => f.write_str("None"), (_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"), (ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)), diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index cacad480c82f4f..b5f2b3ef83e97d 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -2,6 +2,9 @@ use ruff_python_ast as ast; use rustc_hash::FxHashMap; use crate::semantic_index::SemanticIndex; +use crate::types::class::ClassType; +use crate::types::class_base::ClassBase; +use crate::types::instance::NominalInstanceType; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, @@ -671,6 +674,35 @@ impl<'db> SpecializationBuilder<'db> { } } + ( + Type::NominalInstance(NominalInstanceType { + class: ClassType::Generic(formal_alias), + .. + }), + Type::NominalInstance(NominalInstanceType { + class: actual_class, + .. + }), + ) => { + let formal_origin = formal_alias.origin(self.db); + for base in actual_class.iter_mro(self.db) { + let ClassBase::Class(ClassType::Generic(base_alias)) = base else { + continue; + }; + if formal_origin != base_alias.origin(self.db) { + continue; + } + let formal_specialization = formal_alias.specialization(self.db).types(self.db); + let base_specialization = base_alias.specialization(self.db).types(self.db); + for (formal_ty, base_ty) in + formal_specialization.iter().zip(base_specialization) + { + self.infer(*formal_ty, *base_ty)?; + } + return Ok(()); + } + } + (Type::Union(formal), _) => { // TODO: We haven't implemented a full unification solver yet. If typevars appear // in multiple union elements, we ideally want to express that _only one_ of them diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 8f3878a01d390e..8ac4ec9ba0cdc7 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1384,7 +1384,7 @@ impl<'db> TypeInferenceBuilder<'db> { Type::BooleanLiteral(_) | Type::IntLiteral(_) => {} Type::NominalInstance(instance) if matches!( - instance.class().known(self.db()), + instance.class.known(self.db()), Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool) ) => {} _ => return false, @@ -2477,7 +2477,7 @@ impl<'db> TypeInferenceBuilder<'db> { definition: Definition<'db>, ) { fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option> { - let class = ty.into_nominal_instance()?.class(); + let class = ty.into_nominal_instance()?.class; if !class.is_known(db, KnownClass::Tuple) { return None; } @@ -2933,7 +2933,7 @@ impl<'db> TypeInferenceBuilder<'db> { } // Super instances do not allow attribute assignment - Type::NominalInstance(instance) if instance.class().is_known(db, KnownClass::Super) => { + Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Super) => { if emit_diagnostics { if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) { builder.into_diagnostic(format_args!( @@ -3462,10 +3462,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Handle various singletons. if let Type::NominalInstance(instance) = declared_ty.inner_type() { - if instance - .class() - .is_known(self.db(), KnownClass::SpecialForm) - { + if instance.class.is_known(self.db(), KnownClass::SpecialForm) { if let Some(name_expr) = target.as_name_expr() { if let Some(known_instance) = KnownInstanceType::try_from_file_and_name( self.db(), @@ -6593,9 +6590,7 @@ impl<'db> TypeInferenceBuilder<'db> { range, ), (Type::Tuple(_), Type::NominalInstance(instance)) - if instance - .class() - .is_known(self.db(), KnownClass::VersionInfo) => + if instance.class.is_known(self.db(), KnownClass::VersionInfo) => { self.infer_binary_type_comparison( left, @@ -6605,9 +6600,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) } (Type::NominalInstance(instance), Type::Tuple(_)) - if instance - .class() - .is_known(self.db(), KnownClass::VersionInfo) => + if instance.class.is_known(self.db(), KnownClass::VersionInfo) => { self.infer_binary_type_comparison( Type::version_info_tuple(self.db()), @@ -6999,9 +6992,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) -> Type<'db> { match (value_ty, slice_ty, slice_ty.slice_literal(self.db())) { (Type::NominalInstance(instance), _, _) - if instance - .class() - .is_known(self.db(), KnownClass::VersionInfo) => + if instance.class.is_known(self.db(), KnownClass::VersionInfo) => { self.infer_subscript_expression_types( value_node, @@ -7362,7 +7353,7 @@ impl<'db> TypeInferenceBuilder<'db> { let type_to_slice_argument = |ty: Option>| match ty { Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty), Some(ty @ Type::NominalInstance(instance)) - if instance.class().is_known(self.db(), KnownClass::NoneType) => + if instance.class.is_known(self.db(), KnownClass::NoneType) => { SliceArg::Arg(ty) } diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index a232400670c2b0..b33d4306098722 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -1,5 +1,7 @@ //! Instance types: both nominal and structural. +use std::marker::PhantomData; + use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; use crate::symbol::{Symbol, SymbolAndQualifiers}; @@ -14,7 +16,10 @@ impl<'db> Type<'db> { if class.class_literal(db).0.is_protocol(db) { Self::ProtocolInstance(ProtocolInstanceType(Protocol::FromClass(class))) } else { - Self::NominalInstance(NominalInstanceType { class }) + Self::NominalInstance(NominalInstanceType { + class, + _phantom: PhantomData, + }) } } @@ -56,16 +61,14 @@ impl<'db> Type<'db> { /// A type representing the set of runtime objects which are instances of a certain nominal class. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub struct NominalInstanceType<'db> { + pub(super) class: ClassType<'db>, + // Keep this field private, so that the only way of constructing `NominalInstanceType` instances // is through the `Type::instance` constructor function. - class: ClassType<'db>, + _phantom: PhantomData<()>, } impl<'db> NominalInstanceType<'db> { - pub(super) fn class(self) -> ClassType<'db> { - self.class - } - pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { // N.B. The subclass relation is fully static self.class.is_subclass_of(db, other.class) @@ -130,6 +133,7 @@ impl<'db> NominalInstanceType<'db> { ) -> Self { Self { class: self.class.apply_type_mapping(db, type_mapping), + _phantom: PhantomData, } } diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 175e787cbf6809..473035fdf7b050 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -474,7 +474,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> { } // Treat `bool` as `Literal[True, False]`. Type::NominalInstance(instance) - if instance.class().is_known(db, KnownClass::Bool) => + if instance.class.is_known(db, KnownClass::Bool) => { UnionType::from_elements( db, @@ -505,7 +505,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> { fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option> { match (lhs_ty, rhs_ty) { (Type::NominalInstance(instance), Type::IntLiteral(i)) - if instance.class().is_known(self.db, KnownClass::Bool) => + if instance.class.is_known(self.db, KnownClass::Bool) => { if i == 0 { Some(Type::BooleanLiteral(false).negate(self.db)) diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index b264aba1ad47c5..52dd6ee28a6212 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -126,9 +126,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::SubclassOf(_), _) => Ordering::Less, (_, Type::SubclassOf(_)) => Ordering::Greater, - (Type::NominalInstance(left), Type::NominalInstance(right)) => { - left.class().cmp(&right.class()) - } + (Type::NominalInstance(left), Type::NominalInstance(right)) => left.class.cmp(&right.class), (Type::NominalInstance(_), _) => Ordering::Less, (_, Type::NominalInstance(_)) => Ordering::Greater, @@ -171,7 +169,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (SuperOwnerKind::Class(_), _) => Ordering::Less, (_, SuperOwnerKind::Class(_)) => Ordering::Greater, (SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => { - left.class().cmp(&right.class()) + left.class.cmp(&right.class) } (SuperOwnerKind::Instance(_), _) => Ordering::Less, (_, SuperOwnerKind::Instance(_)) => Ordering::Greater, From 0ae07cdd1fb018f8ea2d40391e55d70b3fb78388 Mon Sep 17 00:00:00 2001 From: ZGY <1786718956@qq.com> Date: Tue, 13 May 2025 15:10:23 +0800 Subject: [PATCH 067/487] [ruff_python_ast] Fix redundant visitation of test expressions in elif clause statements (#18064) --- crates/ruff_python_ast/src/visitor/transformer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index 68962941d61d98..f3e1c30bbc2ae3 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -234,9 +234,6 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { visitor.visit_expr(test); visitor.visit_body(body); for clause in elif_else_clauses { - if let Some(test) = &mut clause.test { - visitor.visit_expr(test); - } walk_elif_else_clause(visitor, clause); } } From 68b0386007660f98c57a949b765b9b71c0ce0f85 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Bodas <55339528+abhijeetbodas2001@users.noreply.github.com> Date: Tue, 13 May 2025 14:01:26 +0530 Subject: [PATCH 068/487] [ty] Implement `DataClassInstance` protocol for dataclasses. (#18018) Fixes: https://github.com/astral-sh/ty/issues/92 ## Summary We currently get a `invalid-argument-type` error when using `dataclass.fields` on a dataclass, because we do not synthesize the `__dataclass_fields__` member. This PR fixes this diagnostic. Note that we do not yet model the `Field` type correctly. After that is done, we can assign a more precise `tuple[Field, ...]` type to this new member. ## Test Plan New mdtest. --------- Co-authored-by: David Peter --- .../resources/mdtest/dataclasses.md | 19 ++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 13 ++++++++++++ crates/ty_python_semantic/src/types/class.rs | 20 ++++++++++++++----- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses.md index 54ac25998b3880..7b5377d0132a38 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses.md @@ -616,6 +616,25 @@ reveal_type(C.__init__) # revealed: (field: str | int = int) -> None To do +## `dataclass.fields` + +Dataclasses have `__dataclass_fields__` in them, which makes them a subtype of the +`DataclassInstance` protocol. + +Here, we verify that dataclasses can be passed to `dataclasses.fields` without any errors, and that +the return type of `dataclasses.fields` is correct. + +```py +from dataclasses import dataclass, fields + +@dataclass +class Foo: + x: int + +reveal_type(Foo.__dataclass_fields__) # revealed: dict[str, Field[Any]] +reveal_type(fields(Foo)) # revealed: tuple[Field[Any], ...] +``` + ## Other special cases ### `dataclasses.dataclass` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9e536cbf31f0b8..3eb740871c27e1 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2939,6 +2939,19 @@ impl<'db> Type<'db> { )) .into() } + Type::ClassLiteral(class) + if name == "__dataclass_fields__" && class.dataclass_params(db).is_some() => + { + // Make this class look like a subclass of the `DataClassInstance` protocol + Symbol::bound(KnownClass::Dict.to_specialized_instance( + db, + [ + KnownClass::Str.to_instance(db), + KnownClass::Field.to_specialized_instance(db, [Type::any()]), + ], + )) + .with_qualifiers(TypeQualifiers::CLASS_VAR) + } Type::BoundMethod(bound_method) => match name_str { "__self__" => Symbol::bound(bound_method.self_instance(db)).into(), "__func__" => { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 96673012f1f498..0f7936a18d5587 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1958,6 +1958,8 @@ pub enum KnownClass { // backported as `builtins.ellipsis` by typeshed on Python <=3.9 EllipsisType, NotImplementedType, + // dataclasses + Field, } impl<'db> KnownClass { @@ -2037,7 +2039,8 @@ impl<'db> KnownClass { // and raises a `TypeError` in Python >=3.14 // (see https://docs.python.org/3/library/constants.html#NotImplemented) | Self::NotImplementedType - | Self::Classmethod => Truthiness::Ambiguous, + | Self::Classmethod + | Self::Field => Truthiness::Ambiguous, } } @@ -2108,7 +2111,8 @@ impl<'db> KnownClass { | Self::VersionInfo | Self::EllipsisType | Self::NotImplementedType - | Self::UnionType => false, + | Self::UnionType + | Self::Field => false, } } @@ -2181,6 +2185,7 @@ impl<'db> KnownClass { } } Self::NotImplementedType => "_NotImplementedType", + Self::Field => "Field", } } @@ -2405,6 +2410,7 @@ impl<'db> KnownClass { | Self::DefaultDict | Self::Deque | Self::OrderedDict => KnownModule::Collections, + Self::Field => KnownModule::Dataclasses, } } @@ -2464,7 +2470,8 @@ impl<'db> KnownClass { | Self::ABCMeta | Self::Super | Self::NamedTuple - | Self::NewType => false, + | Self::NewType + | Self::Field => false, } } @@ -2526,7 +2533,8 @@ impl<'db> KnownClass { | Self::Super | Self::UnionType | Self::NamedTuple - | Self::NewType => false, + | Self::NewType + | Self::Field => false, } } @@ -2596,6 +2604,7 @@ impl<'db> KnownClass { Self::EllipsisType } "_NotImplementedType" => Self::NotImplementedType, + "Field" => Self::Field, _ => return None, }; @@ -2647,7 +2656,8 @@ impl<'db> KnownClass { | Self::UnionType | Self::GeneratorType | Self::AsyncGeneratorType - | Self::WrapperDescriptorType => module == self.canonical_module(db), + | Self::WrapperDescriptorType + | Self::Field => module == self.canonical_module(db), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::SpecialForm | Self::TypeVar From 00f672a83b8435aa7e981eb6a8b4c42774ef2fbb Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 13 May 2025 01:55:01 -0700 Subject: [PATCH 069/487] [ty] contribution guide (#18061) First take on a contributing guide for `ty`. Lots of it is copied from the existing Ruff contribution guide. I've put this in Ruff repo, since I think a contributing guide belongs where the code is. I also updated the Ruff contributing guide to link to the `ty` one. Once this is merged, we can also add a link from the `CONTRIBUTING.md` in ty repo (which focuses on making contributions to things that are actually in the ty repo), to this guide. I also updated the pull request template to mention that it might be a ty PR, and mention the `[ty]` PR title prefix. Feel free to update/modify/merge this PR before I'm awake tomorrow. --------- Co-authored-by: Dhruv Manilawala Co-authored-by: David Peter --- .github/PULL_REQUEST_TEMPLATE.md | 5 +- CONTRIBUTING.md | 5 ++ crates/ty/CONTRIBUTING.md | 141 +++++++++++++++++++++++++++++++ crates/ty/README.md | 24 +----- 4 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 crates/ty/CONTRIBUTING.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5b6455590f091b..3bc48f3cc6747f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,9 @@ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9281bd10867454..aab1ed1e72b677 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,11 @@ Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff. +> [!NOTE] +> +> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing +> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md). + ## The Basics Ruff welcomes contributions in the form of pull requests. diff --git a/crates/ty/CONTRIBUTING.md b/crates/ty/CONTRIBUTING.md new file mode 100644 index 00000000000000..de7bedb7052236 --- /dev/null +++ b/crates/ty/CONTRIBUTING.md @@ -0,0 +1,141 @@ +# Contributing to ty + +Welcome! We're happy to have you here. Thank you in advance for your contribution to ty. + +> [!NOTE] +> +> This guide is for ty. If you're looking to contribute to Ruff, please see +> [the Ruff contributing guide](../../CONTRIBUTING.md). + +## The Basics + +We welcome contributions in the form of pull requests. + +For small changes (e.g., bug fixes), feel free to submit a PR. + +For larger changes (e.g. new diagnostics, new functionality, new configuration options), consider +creating an [**issue**](https://github.com/astral-sh/ty/issues) outlining your proposed change. +You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the +community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) +in the issue tracker, along with [bugs](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3Abug) +that are ready for contributions. + +### Prerequisites + +ty is written in Rust. You'll need to install the +[Rust toolchain](https://www.rust-lang.org/tools/install) for development. + +You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to +run Python utility commands. + +You can optionally install pre-commit hooks to automatically run the validation checks +when making a commit: + +```shell +uv tool install pre-commit +pre-commit install +``` + +We recommend [nextest](https://nexte.st/) to run ty's test suite (via `cargo nextest run`), +though it's not strictly necessary: + +```shell +cargo install cargo-nextest --locked +``` + +Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`, +if you choose to install `nextest`. + +### Development + +After cloning the repository, run ty locally from the repository root with: + +```shell +cargo run --bin ty -- check --project /path/to/project/ +``` + +Prior to opening a pull request, ensure that your code has been auto-formatted, +and that it passes both the lint and test validation checks: + +```shell +cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting +cargo test # Rust testing +uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc. +``` + +These checks will run on GitHub Actions when you open your pull request, but running them locally +will save you time and expedite the merge process. + +If you're using VS Code, you can also install the recommended [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension to get these checks while editing. + +Include the text `[ty]` at the beginning of your pull request title, to distinguish ty pull requests +from Ruff ones. + +Your pull request will be reviewed by a maintainer, which may involve a few rounds of iteration +prior to merging. + +### Debugging ty + +ty can optionally emit extensive tracing output, which can be very useful in understanding its +operation and debugging issues; see [`crates/ty/docs/tracing.md`](./docs/tracing.md) for details. + +### Project Structure + +The codebase is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html), +such that all crates are contained in a flat `crates` directory. + +The vast majority of ty's code lives in the `ty_python_semantic` crate (located at +`crates/ty_python_semantic`). As a contributor, that's the crate that'll probably be most relevant +to you. + +At the time of writing, the repository includes the following ty-specific crates (in addition to +crates shared with Ruff, such as `ruff_db`, `ruff_python_ast`, and `ruff_python_parser`): + +- `ty_python_semantic`: The core type checker, which includes the type inference engine and + semantic analysis. +- `ty_test`: The Markdown-based test framework for ty, "mdtest". +- `ty`: The command-line interface. +- `ty_ide`: IDE features (hover, go-to-definition, autocomplete) for the language server. +- `ty_project`: Discovery and representation of a Python project to be checked by ty. +- `ty_server`: The ty language server. +- `ty_vendored`: A vendored copy of [typeshed](https://github.com/python/typeshed), which holds type + annotations for the Python standard library. +- `ty_wasm`: library crate for exposing ty as a WebAssembly module. Powers the + [ty Playground](https://play.ty.dev/). + +## Writing tests + +Core type checking tests are written as Markdown code blocks. +They can be found in [`crates/ty_python_semantic/resources/mdtest`][resources-mdtest]. +See [`crates/ty_test/README.md`][mdtest-readme] for more information +on the test framework itself. + +Any ty pull request to improve ty's type inference or type checking logic should include mdtests +demonstrating the effect of the change. + +We write mdtests in a "literate" style, with prose explaining the motivation of each test, and any +context necessary to understand the feature being demonstrated. + +### Property tests + +ty uses property-based testing to test the core type relations. These tests are located in +[`crates/ty_python_semantic/src/types/property_tests.rs`](../ty_python_semantic/src/types/property_tests.rs). + +The property tests do not run in CI on every PR, just once daily. It is advisable to run them +locally after modifying core type relation methods (`is_subtype_of`, `is_equivalent_to`, etc.) to +ensure that the changes do not break any of the properties. + +## Ecosystem CI (mypy-primer) + +GitHub Actions will run your changes against a number of real-world projects from GitHub and +report on any linter or formatter differences. See [`crates/ty/docs/mypy_primer.md`](./docs/mypy_primer.md) +for instructions on running these checks locally. + +## Coding guidelines + +We use the [Salsa](https:://github.com/salsa-rs/salsa) library for incremental computation. Many +methods take a Salsa database (usually `db: &'db dyn Db`) as an argument. This should always be the +first argument (or second after `self`). + +[mdtest-readme]: ../ty_test/README.md +[resources-mdtest]: ../ty_python_semantic/resources/mdtest diff --git a/crates/ty/README.md b/crates/ty/README.md index e0b644c3ea8aa7..fb98292a0f8c14 100644 --- a/crates/ty/README.md +++ b/crates/ty/README.md @@ -1,25 +1,9 @@ # ty ty is an extremely fast type checker. -Currently, it is a work-in-progress and not ready for user testing. +Currently, it is a work-in-progress and not ready for production use. -ty is designed to prioritize good type inference, even in unannotated code, -and aims to avoid false positives. +The Rust code for ty lives in this repository; see [CONTRIBUTING.md](CONTRIBUTING.md) for more +information on contributing to ty. -While ty will produce similar results to mypy and pyright on many codebases, -100% compatibility with these tools is a non-goal. -On some codebases, ty's design decisions lead to different outcomes -than you would get from running one of these more established tools. - -## Contributing - -Core type checking tests are written as Markdown code blocks. -They can be found in [`ty_python_semantic/resources/mdtest`][resources-mdtest]. -See [`ty_test/README.md`][mdtest-readme] for more information -on the test framework itself. - -The list of open issues can be found [here][open-issues]. - -[mdtest-readme]: ../ty_test/README.md -[open-issues]: https://github.com/astral-sh/ty/issues -[resources-mdtest]: ../ty_python_semantic/resources/mdtest +See [the ty repo](https://github.com/astral-sh/ty) for ty documentation and releases. From 5913997c7228a81872b093bad6dc0a95dae66e12 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 13 May 2025 09:00:20 -0400 Subject: [PATCH 070/487] [ty] Improve diagnostics for `assert_type` and `assert_never` (#18050) --- .../mdtest/directives/assert_never.md | 10 +- .../mdtest/directives/assert_type.md | 2 + ..._`assert_never`_-_Basic_functionality.snap | 189 ++++++++++++++++++ ...ssert_type.md_-_`assert_type`_-_Basic.snap | 38 ++++ .../src/types/diagnostic.rs | 11 +- crates/ty_python_semantic/src/types/infer.rs | 49 ++++- 6 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap diff --git a/crates/ty_python_semantic/resources/mdtest/directives/assert_never.md b/crates/ty_python_semantic/resources/mdtest/directives/assert_never.md index 1866515a7b753d..958f2c04f905b5 100644 --- a/crates/ty_python_semantic/resources/mdtest/directives/assert_never.md +++ b/crates/ty_python_semantic/resources/mdtest/directives/assert_never.md @@ -2,6 +2,8 @@ ## Basic functionality + + `assert_never` makes sure that the type of the argument is `Never`. If it is not, a `type-assertion-failure` diagnostic is emitted. @@ -58,7 +60,7 @@ def if_else_isinstance_error(obj: A | B): elif isinstance(obj, C): pass else: - # error: [type-assertion-failure] "Expected type `Never`, got `B & ~A & ~C` instead" + # error: [type-assertion-failure] "Argument does not have asserted type `Never`" assert_never(obj) def if_else_singletons_success(obj: Literal[1, "a"] | None): @@ -79,7 +81,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None): elif obj is None: pass else: - # error: [type-assertion-failure] "Expected type `Never`, got `Literal["a"]` instead" + # error: [type-assertion-failure] "Argument does not have asserted type `Never`" assert_never(obj) def match_singletons_success(obj: Literal[1, "a"] | None): @@ -92,7 +94,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None): pass case _ as obj: # TODO: Ideally, we would not emit an error here - # error: [type-assertion-failure] "Expected type `Never`, got `@Todo" + # error: [type-assertion-failure] "Argument does not have asserted type `Never`" assert_never(obj) def match_singletons_error(obj: Literal[1, "a"] | None): @@ -106,6 +108,6 @@ def match_singletons_error(obj: Literal[1, "a"] | None): case _ as obj: # TODO: We should emit an error here, but the message should # show the type `Literal["a"]` instead of `@Todo(…)`. - # error: [type-assertion-failure] "Expected type `Never`, got `@Todo" + # error: [type-assertion-failure] "Argument does not have asserted type `Never`" assert_never(obj) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/directives/assert_type.md b/crates/ty_python_semantic/resources/mdtest/directives/assert_type.md index 336e82e0eecbd9..07ad5d555b07e1 100644 --- a/crates/ty_python_semantic/resources/mdtest/directives/assert_type.md +++ b/crates/ty_python_semantic/resources/mdtest/directives/assert_type.md @@ -2,6 +2,8 @@ ## Basic + + ```py from typing_extensions import assert_type diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap new file mode 100644 index 00000000000000..6b4c6d36256800 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap @@ -0,0 +1,189 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: assert_never.md - `assert_never` - Basic functionality +mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing_extensions import assert_never, Never, Any + 2 | from ty_extensions import Unknown + 3 | + 4 | def _(never: Never, any_: Any, unknown: Unknown, flag: bool): + 5 | assert_never(never) # fine + 6 | + 7 | assert_never(0) # error: [type-assertion-failure] + 8 | assert_never("") # error: [type-assertion-failure] + 9 | assert_never(None) # error: [type-assertion-failure] +10 | assert_never([]) # error: [type-assertion-failure] +11 | assert_never({}) # error: [type-assertion-failure] +12 | assert_never(()) # error: [type-assertion-failure] +13 | assert_never(1 if flag else never) # error: [type-assertion-failure] +14 | +15 | assert_never(any_) # error: [type-assertion-failure] +16 | assert_never(unknown) # error: [type-assertion-failure] +``` + +# Diagnostics + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:7:5 + | +5 | assert_never(never) # fine +6 | +7 | assert_never(0) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^-^ + | | + | Inferred type of argument is `Literal[0]` +8 | assert_never("") # error: [type-assertion-failure] +9 | assert_never(None) # error: [type-assertion-failure] + | +info: `Never` and `Literal[0]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:8:5 + | + 7 | assert_never(0) # error: [type-assertion-failure] + 8 | assert_never("") # error: [type-assertion-failure] + | ^^^^^^^^^^^^^--^ + | | + | Inferred type of argument is `Literal[""]` + 9 | assert_never(None) # error: [type-assertion-failure] +10 | assert_never([]) # error: [type-assertion-failure] + | +info: `Never` and `Literal[""]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:9:5 + | + 7 | assert_never(0) # error: [type-assertion-failure] + 8 | assert_never("") # error: [type-assertion-failure] + 9 | assert_never(None) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^----^ + | | + | Inferred type of argument is `None` +10 | assert_never([]) # error: [type-assertion-failure] +11 | assert_never({}) # error: [type-assertion-failure] + | +info: `Never` and `None` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:10:5 + | + 8 | assert_never("") # error: [type-assertion-failure] + 9 | assert_never(None) # error: [type-assertion-failure] +10 | assert_never([]) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^--^ + | | + | Inferred type of argument is `list[Unknown]` +11 | assert_never({}) # error: [type-assertion-failure] +12 | assert_never(()) # error: [type-assertion-failure] + | +info: `Never` and `list[Unknown]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:11:5 + | + 9 | assert_never(None) # error: [type-assertion-failure] +10 | assert_never([]) # error: [type-assertion-failure] +11 | assert_never({}) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^--^ + | | + | Inferred type of argument is `dict[Unknown, Unknown]` +12 | assert_never(()) # error: [type-assertion-failure] +13 | assert_never(1 if flag else never) # error: [type-assertion-failure] + | +info: `Never` and `dict[Unknown, Unknown]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:12:5 + | +10 | assert_never([]) # error: [type-assertion-failure] +11 | assert_never({}) # error: [type-assertion-failure] +12 | assert_never(()) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^--^ + | | + | Inferred type of argument is `tuple[()]` +13 | assert_never(1 if flag else never) # error: [type-assertion-failure] + | +info: `Never` and `tuple[()]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:13:5 + | +11 | assert_never({}) # error: [type-assertion-failure] +12 | assert_never(()) # error: [type-assertion-failure] +13 | assert_never(1 if flag else never) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^--------------------^ + | | + | Inferred type of argument is `Literal[1]` +14 | +15 | assert_never(any_) # error: [type-assertion-failure] + | +info: `Never` and `Literal[1]` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:15:5 + | +13 | assert_never(1 if flag else never) # error: [type-assertion-failure] +14 | +15 | assert_never(any_) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^----^ + | | + | Inferred type of argument is `Any` +16 | assert_never(unknown) # error: [type-assertion-failure] + | +info: `Never` and `Any` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` + +``` +error[type-assertion-failure]: Argument does not have asserted type `Never` + --> src/mdtest_snippet.py:16:5 + | +15 | assert_never(any_) # error: [type-assertion-failure] +16 | assert_never(unknown) # error: [type-assertion-failure] + | ^^^^^^^^^^^^^-------^ + | | + | Inferred type of argument is `Unknown` + | +info: `Never` and `Unknown` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap new file mode 100644 index 00000000000000..4b9da52c7b2677 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap @@ -0,0 +1,38 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: assert_type.md - `assert_type` - Basic +mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_type.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from typing_extensions import assert_type +2 | +3 | def _(x: int): +4 | assert_type(x, int) # fine +5 | assert_type(x, str) # error: [type-assertion-failure] +``` + +# Diagnostics + +``` +error[type-assertion-failure]: Argument does not have asserted type `str` + --> src/mdtest_snippet.py:5:5 + | +3 | def _(x: int): +4 | assert_type(x, int) # fine +5 | assert_type(x, str) # error: [type-assertion-failure] + | ^^^^^^^^^^^^-^^^^^^ + | | + | Inferred type of argument is `int` + | +info: `str` and `int` are not equivalent types +info: rule `type-assertion-failure` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 69549051f0fb69..041abbd1969748 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -11,7 +11,7 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type}; -use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; @@ -1543,7 +1543,7 @@ pub(super) fn report_invalid_return_type( return; }; - let return_type_span = Span::from(context.file()).with_range(return_type_range.range()); + let return_type_span = context.span(return_type_range); let mut diag = builder.into_diagnostic("Return type does not match returned value"); diag.set_primary_message(format_args!( @@ -1849,16 +1849,13 @@ pub(crate) fn report_duplicate_bases( ), ); sub_diagnostic.annotate( - Annotation::secondary( - Span::from(context.file()).with_range(bases_list[*first_index].range()), - ) - .message(format_args!( + Annotation::secondary(context.span(&bases_list[*first_index])).message(format_args!( "Class `{duplicate_name}` first included in bases list here" )), ); for index in later_indices { sub_diagnostic.annotate( - Annotation::primary(Span::from(context.file()).with_range(bases_list[*index].range())) + Annotation::primary(context.span(&bases_list[*index])) .message(format_args!("Class `{duplicate_name}` later repeated here")), ); } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 8ac4ec9ba0cdc7..7d85d5dd52d48b 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -4834,12 +4834,27 @@ impl<'db> TypeInferenceBuilder<'db> { &TYPE_ASSERTION_FAILURE, call_expression, ) { - builder.into_diagnostic(format_args!( - "Actual type `{}` is not the same \ - as asserted type `{}`", - actual_ty.display(self.db()), - asserted_ty.display(self.db()), - )); + let mut diagnostic = + builder.into_diagnostic(format_args!( + "Argument does not have asserted type `{}`", + asserted_ty.display(self.db()), + )); + diagnostic.annotate( + Annotation::secondary(self.context.span( + &call_expression.arguments.args[0], + )) + .message(format_args!( + "Inferred type of argument is `{}`", + actual_ty.display(self.db()), + )), + ); + diagnostic.info( + format_args!( + "`{asserted_type}` and `{inferred_type}` are not equivalent types", + asserted_type = asserted_ty.display(self.db()), + inferred_type = actual_ty.display(self.db()), + ) + ); } } } @@ -4851,10 +4866,24 @@ impl<'db> TypeInferenceBuilder<'db> { &TYPE_ASSERTION_FAILURE, call_expression, ) { - builder.into_diagnostic(format_args!( - "Expected type `Never`, got `{}` instead", - actual_ty.display(self.db()), - )); + let mut diagnostic = builder.into_diagnostic( + "Argument does not have asserted type `Never`", + ); + diagnostic.annotate( + Annotation::secondary(self.context.span( + &call_expression.arguments.args[0], + )) + .message(format_args!( + "Inferred type of argument is `{}`", + actual_ty.display(self.db()) + )), + ); + diagnostic.info( + format_args!( + "`Never` and `{inferred_type}` are not equivalent types", + inferred_type = actual_ty.display(self.db()), + ) + ); } } } From 5bf5f3682a4c7a26d5cadace58dc7c94414bfe9e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 13 May 2025 09:57:53 -0400 Subject: [PATCH 071/487] [ty] Add tests for `else` branches of `hasattr()` narrowing (#18067) ## Summary This addresses @sharkdp's post-merge review in https://github.com/astral-sh/ruff/pull/18053#discussion_r2086190617 ## Test Plan `cargo test -p ty_python_semantic` --- .../resources/mdtest/narrow/hasattr.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md index 2a231802da2133..b15891216086fd 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md @@ -15,12 +15,24 @@ def f(x: Foo): if hasattr(x, "spam"): reveal_type(x) # revealed: Foo & reveal_type(x.spam) # revealed: object + else: + reveal_type(x) # revealed: Foo & ~ + + # TODO: should error and reveal `Unknown` + reveal_type(x.spam) # revealed: @Todo(map_with_boundness: intersections with negative contributions) if hasattr(x, "not-an-identifier"): reveal_type(x) # revealed: Foo + else: + reveal_type(x) # revealed: Foo def y(x: Bar): if hasattr(x, "spam"): reveal_type(x) # revealed: Never reveal_type(x.spam) # revealed: Never + else: + reveal_type(x) # revealed: Bar + + # error: [unresolved-attribute] + reveal_type(x.spam) # revealed: Unknown ``` From c0f22928bd370f965e8f75c503fafd95fdf8cd6b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 13 May 2025 10:08:04 -0400 Subject: [PATCH 072/487] [ty] Add a note to the diagnostic if a new builtin is used on an old Python version (#18068) ## Summary If the user tries to use a new builtin on an old Python version, tell them what Python version the builtin was added on, what our inferred Python version is for their project, and what configuration settings they can tweak to fix the error. ## Test Plan Snapshots and screenshots: ![image](https://github.com/user-attachments/assets/767d570e-7af1-4e1f-98cf-50e4311db511) --- crates/ty/docs/rules.md | 104 +++++++++--------- .../diagnostics/unresolved_reference.md | 14 +++ ...ew_builtin_used_on_old_Python_version.snap | 32 ++++++ .../src/types/diagnostic.rs | 22 +++- 4 files changed, 118 insertions(+), 54 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_reference.md create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 65376a9ffa87b7..bcdd6de53433fd 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -50,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L84) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L85) ## `conflicting-argument-forms` @@ -81,7 +81,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L115) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116) ## `conflicting-declarations` @@ -111,7 +111,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142) ## `conflicting-metaclass` @@ -142,7 +142,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L166) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L167) ## `cyclic-class-definition` @@ -173,7 +173,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L192) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193) ## `division-by-zero` @@ -196,7 +196,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L218) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219) ## `duplicate-base` @@ -222,7 +222,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237) ## `escape-character-in-forward-annotation` @@ -359,7 +359,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) ## `inconsistent-mro` @@ -388,7 +388,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L344) ## `index-out-of-bounds` @@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L367) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L368) ## `invalid-argument-type` @@ -439,7 +439,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) ## `invalid-assignment` @@ -466,7 +466,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) ## `invalid-attribute-access` @@ -499,7 +499,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) ## `invalid-base` @@ -513,7 +513,7 @@ TODO #14889 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) ## `invalid-context-manager` @@ -539,7 +539,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L458) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L459) ## `invalid-declaration` @@ -567,7 +567,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L479) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L480) ## `invalid-exception-caught` @@ -608,7 +608,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L502) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L503) ## `invalid-generic-class` @@ -639,7 +639,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L539) ## `invalid-legacy-type-variable` @@ -672,7 +672,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) ## `invalid-metaclass` @@ -704,7 +704,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L593) ## `invalid-overload` @@ -752,7 +752,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L619) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L620) ## `invalid-parameter-default` @@ -777,7 +777,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L662) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663) ## `invalid-protocol` @@ -810,7 +810,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L315) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316) ## `invalid-raise` @@ -858,7 +858,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L682) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683) ## `invalid-return-type` @@ -882,7 +882,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) ## `invalid-super-argument` @@ -926,7 +926,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L725) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726) ## `invalid-syntax-in-forward-annotation` @@ -969,7 +969,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) ## `invalid-type-form` @@ -997,7 +997,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L789) ## `invalid-type-variable-constraints` @@ -1031,7 +1031,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812) ## `missing-argument` @@ -1055,7 +1055,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L840) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841) ## `no-matching-overload` @@ -1083,7 +1083,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L859) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L860) ## `non-subscriptable` @@ -1106,7 +1106,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L882) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L883) ## `not-iterable` @@ -1131,7 +1131,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L900) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901) ## `parameter-already-assigned` @@ -1157,7 +1157,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L951) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L952) ## `raw-string-type-annotation` @@ -1216,7 +1216,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1287) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1288) ## `subclass-of-final-class` @@ -1244,7 +1244,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1042) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043) ## `too-many-positional-arguments` @@ -1270,7 +1270,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088) ## `type-assertion-failure` @@ -1297,7 +1297,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066) ## `unavailable-implicit-super-arguments` @@ -1341,7 +1341,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1108) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1109) ## `unknown-argument` @@ -1367,7 +1367,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1165) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166) ## `unresolved-attribute` @@ -1394,7 +1394,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1186) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187) ## `unresolved-import` @@ -1418,7 +1418,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1209) ## `unresolved-reference` @@ -1442,7 +1442,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1227) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1228) ## `unsupported-bool-conversion` @@ -1478,7 +1478,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L920) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L921) ## `unsupported-operator` @@ -1505,7 +1505,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1247) ## `zero-stepsize-in-slice` @@ -1529,7 +1529,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) ## `call-possibly-unbound-method` @@ -1547,7 +1547,7 @@ Calling an unbound method will raise an `AttributeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-possibly-unbound-method) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L102) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L103) ## `invalid-ignore-comment` @@ -1603,7 +1603,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L972) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L973) ## `possibly-unbound-import` @@ -1634,7 +1634,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L994) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995) ## `redundant-cast` @@ -1660,7 +1660,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1339) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1340) ## `undefined-reveal` @@ -1683,7 +1683,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1147) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148) ## `unknown-rule` @@ -1740,7 +1740,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1020) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_reference.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_reference.md new file mode 100644 index 00000000000000..6be987a4e2eb45 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_reference.md @@ -0,0 +1,14 @@ +# Diagnostics for unresolved references + +## New builtin used on old Python version + + + +```toml +[environment] +python-version = "3.9" +``` + +```py +aiter # error: [unresolved-reference] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap new file mode 100644 index 00000000000000..4669867bfa9175 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap @@ -0,0 +1,32 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: unresolved_reference.md - Diagnostics for unresolved references - New builtin used on old Python version +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unresolved_reference.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | aiter # error: [unresolved-reference] +``` + +# Diagnostics + +``` +error[unresolved-reference]: Name `aiter` used when not defined + --> src/mdtest_snippet.py:1:1 + | +1 | aiter # error: [unresolved-reference] + | ^^^^^ + | +info: `aiter` was added as a builtin in Python 3.10 +info: The inferred target version of your project is Python 3.9 +info: If using a pyproject.toml file, consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field +info: rule `unresolved-reference` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 041abbd1969748..b0ad0cd8c1beac 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -2,7 +2,6 @@ use super::context::InferContext; use super::mro::DuplicateBaseError; use super::{ClassLiteral, KnownClass}; use crate::db::Db; -use crate::declare_lint; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; use crate::types::string_annotation::{ @@ -11,8 +10,10 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type}; +use crate::{declare_lint, Program}; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; +use ruff_python_stdlib::builtins::version_builtin_was_added; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::Formatter; @@ -1650,7 +1651,24 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node }; let ast::ExprName { id, .. } = expr_name_node; - builder.into_diagnostic(format_args!("Name `{id}` used when not defined")); + let mut diagnostic = builder.into_diagnostic(format_args!("Name `{id}` used when not defined")); + if let Some(version_added_to_builtins) = version_builtin_was_added(id) { + diagnostic.info(format_args!( + "`{id}` was added as a builtin in Python 3.{version_added_to_builtins}" + )); + + // TODO: can we tell the user *why* we're inferring this target version? + // CLI flag? pyproject.toml? Python environment? + diagnostic.info(format_args!( + "The inferred target version of your project is Python {}", + Program::get(context.db()).python_version(context.db()) + )); + + diagnostic.info( + "If using a pyproject.toml file, \ + consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field" + ); + } } pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) { From 142c1bc7609b76dd651dcbbf8fa184261a910686 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 13 May 2025 16:59:11 +0200 Subject: [PATCH 073/487] [ty] Recognize submodules in self-referential imports (#18005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix the lookup of `submodule`s in cases where the `parent` module has a self-referential import like `from parent import submodule`. This allows us to infer proper types for many symbols where we previously inferred `Never`. This leads to many new false (and true) positives across the ecosystem because the fact that we previously inferred `Never` shadowed a lot of problems. For example, we inferred `Never` for `os.path`, which is why we now see a lot of new diagnostics related to `os.path.abspath` and similar. ```py import os reveal_type(os.path) # previously: Never, now: ``` closes https://github.com/astral-sh/ty/issues/261 closes https://github.com/astral-sh/ty/issues/307 ## Ecosystem analysis ``` ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┓ ┃ Diagnostic ID ┃ Severity ┃ Removed ┃ Added ┃ Net Change ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━┩ │ call-non-callable │ error │ 1 │ 5 │ +4 │ │ call-possibly-unbound-method │ warning │ 6 │ 26 │ +20 │ │ invalid-argument-type │ error │ 26 │ 94 │ +68 │ │ invalid-assignment │ error │ 18 │ 46 │ +28 │ │ invalid-context-manager │ error │ 9 │ 4 │ -5 │ │ invalid-raise │ error │ 1 │ 1 │ 0 │ │ invalid-return-type │ error │ 3 │ 20 │ +17 │ │ invalid-super-argument │ error │ 4 │ 0 │ -4 │ │ invalid-type-form │ error │ 573 │ 0 │ -573 │ │ missing-argument │ error │ 2 │ 10 │ +8 │ │ no-matching-overload │ error │ 0 │ 715 │ +715 │ │ non-subscriptable │ error │ 0 │ 35 │ +35 │ │ not-iterable │ error │ 6 │ 7 │ +1 │ │ possibly-unbound-attribute │ warning │ 14 │ 31 │ +17 │ │ possibly-unbound-import │ warning │ 13 │ 0 │ -13 │ │ possibly-unresolved-reference │ warning │ 0 │ 8 │ +8 │ │ redundant-cast │ warning │ 1 │ 0 │ -1 │ │ too-many-positional-arguments │ error │ 2 │ 0 │ -2 │ │ unknown-argument │ error │ 2 │ 0 │ -2 │ │ unresolved-attribute │ error │ 583 │ 304 │ -279 │ │ unresolved-import │ error │ 0 │ 96 │ +96 │ │ unsupported-operator │ error │ 0 │ 17 │ +17 │ │ unused-ignore-comment │ warning │ 29 │ 2 │ -27 │ ├───────────────────────────────┼──────────┼─────────┼───────┼────────────┤ │ TOTAL │ │ 1293 │ 1421 │ +128 │ └───────────────────────────────┴──────────┴─────────┴───────┴────────────┘ Analysis complete. Found 23 unique diagnostic IDs. Total diagnostics removed: 1293 Total diagnostics added: 1421 Net change: +128 ``` * We see a lot of new errors (`no-matching-overload`) related to `os.path.dirname` and other `os.path` operations because we infer `str | None` for `__file__`, but many projects use something like `os.path.dirname(__file__)`. * We also see many new `unresolved-attribute` errors related to the fact that we now infer proper module types for some imports (e.g. `import kornia.augmentation as K`), but we don't allow implicit imports (e.g. accessing `K.auto.operations` without also importing `K.auto`). See https://github.com/astral-sh/ty/issues/133. * Many false positive `invalid-type-form` are removed because we now infer the correct type for some type expression instead of `Never`, which is not valid in a type annotation/expression context. ## Test Plan Added new Markdown tests --- .../resources/mdtest/import/cyclic.md | 108 ++++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 42 ++++--- 2 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/import/cyclic.md diff --git a/crates/ty_python_semantic/resources/mdtest/import/cyclic.md b/crates/ty_python_semantic/resources/mdtest/import/cyclic.md new file mode 100644 index 00000000000000..4af714c7dea147 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/import/cyclic.md @@ -0,0 +1,108 @@ +## Cyclic imports + +### Regression tests + +#### Issue 261 + +See: + +`main.py`: + +```py +from foo import bar + +reveal_type(bar) # revealed: +``` + +`foo/__init__.py`: + +```py +from foo import bar + +__all__ = ["bar"] +``` + +`foo/bar/__init__.py`: + +```py +# empty +``` + +#### Issue 113 + +See: + +`main.py`: + +```py +from pkg.sub import A + +# TODO: This should be `` +reveal_type(A) # revealed: Never +``` + +`pkg/outer.py`: + +```py +class A: ... +``` + +`pkg/sub/__init__.py`: + +```py +from ..outer import * +from .inner import * +``` + +`pkg/sub/inner.py`: + +```py +from pkg.sub import A +``` + +### Actual cycle + +The following example fails at runtime. Ideally, we would emit a diagnostic here. For now, we only +make sure that this does not lead to a module resolution cycle. + +`main.py`: + +```py +from module import x + +reveal_type(x) # revealed: Unknown +``` + +`module.py`: + +```py +# error: [unresolved-import] +from module import x +``` + +### Normal self-referential import + +Some modules like `sys` in typeshed import themselves. Here, we make sure that this does not lead to +cycles or unresolved imports. + +`module/__init__.py`: + +```py +import module # self-referential import + +from module.sub import x +``` + +`module/sub.py`: + +```py +x: int = 1 +``` + +`main.py`: + +```py +from module import x + +reveal_type(x) # revealed: int +``` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 7d85d5dd52d48b..16ebb5b830f802 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -3946,26 +3946,34 @@ impl<'db> TypeInferenceBuilder<'db> { &alias.name.id }; + // Avoid looking up attributes on a module if a module imports from itself + // (e.g. `from parent import submodule` inside the `parent` module). + let import_is_self_referential = module_ty + .into_module_literal() + .is_some_and(|module| self.file() == module.module(self.db()).file()); + // First try loading the requested attribute from the module. - if let Symbol::Type(ty, boundness) = module_ty.member(self.db(), name).symbol { - if &alias.name != "*" && boundness == Boundness::PossiblyUnbound { - // TODO: Consider loading _both_ the attribute and any submodule and unioning them - // together if the attribute exists but is possibly-unbound. - if let Some(builder) = self - .context - .report_lint(&POSSIBLY_UNBOUND_IMPORT, AnyNodeRef::Alias(alias)) - { - builder.into_diagnostic(format_args!( - "Member `{name}` of module `{module_name}` is possibly unbound", - )); + if !import_is_self_referential { + if let Symbol::Type(ty, boundness) = module_ty.member(self.db(), name).symbol { + if &alias.name != "*" && boundness == Boundness::PossiblyUnbound { + // TODO: Consider loading _both_ the attribute and any submodule and unioning them + // together if the attribute exists but is possibly-unbound. + if let Some(builder) = self + .context + .report_lint(&POSSIBLY_UNBOUND_IMPORT, AnyNodeRef::Alias(alias)) + { + builder.into_diagnostic(format_args!( + "Member `{name}` of module `{module_name}` is possibly unbound", + )); + } } + self.add_declaration_with_binding( + alias.into(), + definition, + &DeclaredAndInferredType::AreTheSame(ty), + ); + return; } - self.add_declaration_with_binding( - alias.into(), - definition, - &DeclaredAndInferredType::AreTheSame(ty), - ); - return; } // If the module doesn't bind the symbol, check if it's a submodule. This won't get From f8890b70c35b2d05a8fa21958077de2aab5ba2ad Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 13 May 2025 08:20:43 -0700 Subject: [PATCH 074/487] [ty] __file__ is always a string inside a Python module (#18071) ## Summary Understand that `__file__` is always set and a `str` when looked up as an implicit global from a Python file we are type checking. ## Test Plan mdtests --- .../resources/mdtest/scopes/moduletype_attrs.md | 7 ++++++- crates/ty_python_semantic/src/symbol.rs | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 71c298b8d9b254..09fdefa94278e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -8,7 +8,8 @@ is unbound. ```py reveal_type(__name__) # revealed: str -reveal_type(__file__) # revealed: str | None +# Typeshed says this is str | None, but for a pure-Python on-disk module its always str +reveal_type(__file__) # revealed: str reveal_type(__loader__) # revealed: LoaderProtocol | None reveal_type(__package__) # revealed: str | None reveal_type(__doc__) # revealed: str | None @@ -52,6 +53,10 @@ import typing reveal_type(typing.__name__) # revealed: str reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ellipsis) -> None +# For a stub module, we don't know that `__file__` is a string (at runtime it may be entirely +# unset, but we follow typeshed here): +reveal_type(typing.__file__) # revealed: str | None + # These come from `builtins.object`, not `types.ModuleType`: reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: object, /) -> bool diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index 920c7ff9e65fa8..b18dba77332287 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -982,12 +982,18 @@ mod implicit_globals { db: &'db dyn Db, name: &str, ) -> SymbolAndQualifiers<'db> { + // We special-case `__file__` here because we know that for an internal implicit global + // lookup in a Python module, it is always a string, even though typeshed says `str | + // None`. + if name == "__file__" { + Symbol::bound(KnownClass::Str.to_instance(db)).into() + } // In general we wouldn't check to see whether a symbol exists on a class before doing the // `.member()` call on the instance type -- we'd just do the `.member`() call on the instance // type, since it has the same end result. The reason to only call `.member()` on `ModuleType` // when absolutely necessary is that this function is used in a very hot path (name resolution // in `infer.rs`). We use less idiomatic (and much more verbose) code here as a micro-optimisation. - if module_type_symbols(db) + else if module_type_symbols(db) .iter() .any(|module_type_member| &**module_type_member == name) { From a9f7521944d610df5e70af710c337e6ea9866703 Mon Sep 17 00:00:00 2001 From: InSync Date: Tue, 13 May 2025 23:43:19 +0700 Subject: [PATCH 075/487] [ty] Shorten snapshot names (#18039) Co-authored-by: Micha Reiser --- Cargo.lock | 25 +++-- Cargo.toml | 1 + ...sic_functionality_(78e7b52096b8d36c).snap} | 0 ...ert_type`_-_Basic_(c507788da2659ec9).snap} | 0 ..._`_me\342\200\246_(116c27bd98838df7).snap" | 0 ...t_typ\342\200\246_(a903c11fedbc5020).snap" | 0 ...utes_\342\200\246_(ebfb3de6d1b96b23).snap" | 0 ...d_att\342\200\246_(e5bdf78c427cb7fc).snap" | 0 ...ttrib\342\200\246_(d13d57d3cc36face).snap" | 0 ...tes_o\342\200\246_(467e26496f4c0c13).snap" | 0 ...nknown_attributes_(368ba83a71ef2120).snap" | 0 ...ent_-_`ClassVar`s_(8d7cca27987b099d).snap" | 0 ...s_imp\342\200\246_(cbfbf5ff94e6e104).snap" | 0 ...dule_\342\200\246_(846453deaca1071c).snap" | 0 ...bmodu\342\200\246_(4fad4be9778578b7).snap" | 0 ..._`_me\342\200\246_(3ffe352bb3a76715).snap" | 0 ..._Invalid_iterable_(3153247bb9a9b72a).snap} | 0 ...yle_i\342\200\246_(a90ba167a7c191eb).snap" | 0 ...ethod\342\200\246_(36425dbcbd793d2b).snap" | 0 ...llabl\342\200\246_(49a21e4b7fe6e97b).snap" | 0 ...d_`__\342\200\246_(6388761c90a0555c).snap" | 0 ...d_`__\342\200\246_(6805a6032e504b63).snap" | 0 ...d_`__\342\200\246_(c626bde8651b643a).snap" | 0 ...nd_`__\342\200\246_(3b75cc467e6e012).snap" | 0 ...d_`__\342\200\246_(8745233539d31200).snap" | 0 ...nd_`__\342\200\246_(b1ce0da35c06026).snap" | 0 ...terab\342\200\246_(6177bb6d13a22241).snap" | 0 ...terab\342\200\246_(ba36fbef63a14969).snap" | 0 ...le_it\342\200\246_(a1cdf01ad69ac37c).snap" | 0 ..._not_\342\200\246_(92e3fdd69edad63d).snap" | 0 ...od_wi\342\200\246_(1136c0e783d61ba4).snap" | 0 ...rns_a\342\200\246_(707bd02a22c4acc8).snap" | 0 ...nd_ty\342\200\246_(d50204b9d91b7bd1).snap" | 0 ...strai\342\200\246_(48ab83f977c109b4).snap" | 0 ...nd_ty\342\200\246_(5935d14c26afe407).snap" | 0 ...strai\342\200\246_(d2c475fccc70a8e2).snap" | 0 ...lving\342\200\246_(492b1163b8163c05).snap" | 0 ...42\200\246_-_Basic_(16be9d90a741761).snap" | 0 ...-_Calls_to_methods_(4b3b8695d519a02).snap" | 0 ...-_Different_files_(d02c38e2dd054b4c).snap" | 0 ...e_ord\342\200\246_(9b0bf549733d3f0a).snap" | 0 ...-_Many_parameters_(ee38fd34ceba3293).snap" | 0 ..._acro\342\200\246_(1d5d112808c49e9d).snap" | 0 ..._with\342\200\246_(4bc5c16cd568b8ec).snap" | 0 ..._funct\342\200\246_(3b18271a821a59b).snap" | 0 ...rgumen\342\200\246_(8d9f18c78137411).snap" | 0 ..._Mix_of_arguments_(cfc64b1136058112).snap" | 0 ..._keyword_argument_(cc34b2f7d19d427e).snap" | 0 ...-_Only_positional_(3dc93b1709eb3be9).snap" | 0 ...nthetic_arguments_(4c09844bbbf47741).snap" | 0 ...ariadic_arguments_(e26a3e7b2773a63b).snap" | 0 ...d_arg\342\200\246_(4c855e39ea6baeaf).snap" | 0 ...t_doe\342\200\246_(feccf6b9da1e7cd3).snap" | 0 ...ts_wi\342\200\246_(ea7ebc83ec359b54).snap" | 0 ...aded_\342\200\246_(3553d085684e16a0).snap" | 0 ...lemen\342\200\246_(ab3f546bf004e24d).snap" | 0 ...verloa\342\200\246_(84dadf8abd8f2f2).snap" | 0 ..._-_`@classmethod`_(aaa04d4cfa3adaba).snap" | 0 ...00\246_-_`@final`_(f8e529ec23a61665).snap" | 0 ...246_-_`@override`_(2df210735ca532f9).snap" | 0 ...-_Regular_modules_(5c8e81664d1c7470).snap" | 0 ...ol_cl\342\200\246_(288988036f34ddcf).snap" | 0 ...o_`ge\342\200\246_(3d0c4ee818c4d8d5).snap" | 0 ...rotoco\342\200\246_(98257e7c2300373).snap" | 0 ...nerator_functions_(d9ed06b61b14fd4c).snap} | 0 ...onal_\342\200\246_(94c036c5d3803ab2).snap" | 0 ...t_ret\342\200\246_(393cb38bf7119649).snap" | 0 ...valid_return_type_(a91e0c67519cd77f).snap} | 0 ...type_\342\200\246_(c3a523878447af6b).snap" | 0 ...sons_\342\200\246_(c391c13e2abc18a0).snap" | 0 ...246_-_Python_3.10_(96aa8ec77d46553d).snap" | 0 ...shado\342\200\246_(c8ff9e3a079e8bd5).snap" | 0 ...on_sh\342\200\246_(a1515328b775ebc1).snap" | 0 ...sons_\342\200\246_(f45f1da2f8ca693d).snap" | 0 ...lemen\342\200\246_(39b614d4707c0661).snap" | 0 ..._exam\342\200\246_(c24ecd8582e5eb2f).snap" | 0 ...ts_bu\342\200\246_(d840ac443ca8ec7f).snap" | 0 ...rgume\342\200\246_(ad1d489710ee2a34).snap" | 0 ...rd_re\342\200\246_(707b284610419a54).snap" | 0 ..._valu\342\200\246_(f920ea85eefe9cfe).snap" | 0 ...ny_val\342\200\246_(a53a2aec02bc999).snap" | 0 ..._not_\342\200\246_(fae6e2d526396252).snap" | 0 ...to_un\342\200\246_(cef19e6b2b58e6a3).snap" | 0 ..._impo\342\200\246_(72d090df51ea97b8).snap" | 0 ...th_a_\342\200\246_(12d4a70b7fc67cc6).snap" | 0 ...th_an\342\200\246_(6cff507dc64a1bff).snap" | 0 ...th_an\342\200\246_(9da56616d6332a83).snap" | 0 ...th_an\342\200\246_(9fa713dfa17cc404).snap" | 0 ...th_to\342\200\246_(4b8ba6ee48180cdd).snap" | 0 ...d_on_\342\200\246_(51edda0b1aebc2bf).snap" | 0 ...`_att\342\200\246_(2721d40bf12fe8b7).snap" | 0 ...`_met\342\200\246_(15636dc4074e5335).snap" | 0 ...`_met\342\200\246_(ce8b8da49eaf4cda).snap" | 0 ...n_wher\342\200\246_(7cca8063ea43c1a).snap" | 0 ...ent_-_Before_3.10_(2545eaa83b635b8b).snap" | 0 crates/ty_test/Cargo.toml | 1 + crates/ty_test/src/lib.rs | 7 +- crates/ty_test/src/parser.rs | 104 ++++++++++++++---- 98 files changed, 108 insertions(+), 30 deletions(-) rename crates/ty_python_semantic/resources/mdtest/snapshots/{assert_never.md_-_`assert_never`_-_Basic_functionality.snap => assert_never.md_-_`assert_never`_-_Basic_functionality_(78e7b52096b8d36c).snap} (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{assert_type.md_-_`assert_type`_-_Basic.snap => assert_type.md_-_`assert_type`_-_Basic_(c507788da2659ec9).snap} (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_me\342\200\246_(116c27bd98838df7).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_typ\342\200\246_(a903c11fedbc5020).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Instance_attributes_\342\200\246_(ebfb3de6d1b96b23).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Possibly-unbound_att\342\200\246_(e5bdf78c427cb7fc).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Pure_instance_attrib\342\200\246_(d13d57d3cc36face).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Setting_attributes_o\342\200\246_(467e26496f4c0c13).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_`ClassVar`s_(8d7cca27987b099d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp\342\200\246_(cbfbf5ff94e6e104).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_\342\200\246_(846453deaca1071c).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu\342\200\246_(4fad4be9778578b7).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_me\342\200\246_(3ffe352bb3a76715).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{for.md_-_For_loops_-_Invalid_iterable.snap => for.md_-_For_loops_-_Invalid_iterable_(3153247bb9a9b72a).snap} (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_i\342\200\246_(a90ba167a7c191eb).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method\342\200\246_(36425dbcbd793d2b).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(3b75cc467e6e012).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(8745233539d31200).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(b1ce0da35c06026).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(6177bb6d13a22241).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(ba36fbef63a14969).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_it\342\200\246_(a1cdf01ad69ac37c).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_\342\200\246_(92e3fdd69edad63d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_wi\342\200\246_(1136c0e783d61ba4).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_a\342\200\246_(707bd02a22c4acc8).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_bound_ty\342\200\246_(d50204b9d91b7bd1).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_constrai\342\200\246_(48ab83f977c109b4).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_bound_ty\342\200\246_(5935d14c26afe407).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_constrai\342\200\246_(d2c475fccc70a8e2).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on\342\200\246_-_Operations_involving\342\200\246_(492b1163b8163c05).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Basic_(16be9d90a741761).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Calls_to_methods_(4b3b8695d519a02).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_files_(d02c38e2dd054b4c).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_source_ord\342\200\246_(9b0bf549733d3f0a).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_(ee38fd34ceba3293).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_acro\342\200\246_(1d5d112808c49e9d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_with\342\200\246_(4bc5c16cd568b8ec).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Test_calling_a_funct\342\200\246_(3b18271a821a59b).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Keyword_only_argumen\342\200\246_(8d9f18c78137411).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Mix_of_arguments_(cfc64b1136058112).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_One_keyword_argument_(cc34b2f7d19d427e).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Only_positional_(3dc93b1709eb3be9).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Synthetic_arguments_(4c09844bbbf47741).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_arguments_(e26a3e7b2773a63b).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_keyword_arg\342\200\246_(4c855e39ea6baeaf).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membersh\342\200\246_-_Return_type_that_doe\342\200\246_(feccf6b9da1e7cd3).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implemen\342\200\246_(ab3f546bf004e24d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_\342\200\246_-_Regular_modules_(5c8e81664d1c7470).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_cl\342\200\246_(288988036f34ddcf).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`ge\342\200\246_(3d0c4ee818c4d8d5).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protoco\342\200\246_(98257e7c2300373).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{return_type.md_-_Function_return_type_-_Generator_functions.snap => return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap} (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{return_type.md_-_Function_return_type_-_Invalid_return_type.snap => return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap} (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Com\342\200\246_-_Chained_comparisons_\342\200\246_(c391c13e2abc18a0).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_erro\342\200\246_-_Semantic_syntax_erro\342\200\246_-_`async`_comprehensio\342\200\246_-_Python_3.10_(96aa8ec77d46553d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shado\342\200\246_(c8ff9e3a079e8bd5).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_sh\342\200\246_(a1515328b775ebc1).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_\342\200\246_(f45f1da2f8ca693d).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elemen\342\200\246_(39b614d4707c0661).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_A_smaller_scale_exam\342\200\246_(c24ecd8582e5eb2f).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Multiple_variants_bu\342\200\246_(d840ac443ca8ec7f).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_keyword_argume\342\200\246_(ad1d489710ee2a34).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_valu\342\200\246_(f920ea85eefe9cfe).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_val\342\200\246_(a53a2aec02bc999).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_\342\200\246_(fae6e2d526396252).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_un\342\200\246_(cef19e6b2b58e6a3).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_An_unresolvable_impo\342\200\246_(72d090df51ea97b8).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_a_\342\200\246_(12d4a70b7fc67cc6).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(6cff507dc64a1bff).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9da56616d6332a83).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9fa713dfa17cc404).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_to\342\200\246_(4b8ba6ee48180cdd).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference\342\200\246_-_Diagnostics_for_unre\342\200\246_-_New_builtin_used_on_\342\200\246_(51edda0b1aebc2bf).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_att\342\200\246_(2721d40bf12fe8b7).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(15636dc4074e5335).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(ce8b8da49eaf4cda).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Part_of_a_union_wher\342\200\246_(7cca8063ea43c1a).snap" (100%) rename crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap => "crates/ty_python_semantic/resources/mdtest/snapshots/version_related_synt\342\200\246_-_Version-related_synt\342\200\246_-_`match`_statement_-_Before_3.10_(2545eaa83b635b8b).snap" (100%) diff --git a/Cargo.lock b/Cargo.lock index 59134f4e779ace..a967621aae2974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -487,7 +487,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -904,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1485,7 +1485,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1549,7 +1549,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3231,6 +3231,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc-stable-hash" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08" + [[package]] name = "rustix" version = "0.38.44" @@ -3241,7 +3247,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3254,7 +3260,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3640,7 +3646,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4153,6 +4159,7 @@ dependencies = [ "ruff_source_file", "ruff_text_size", "rustc-hash 2.1.1", + "rustc-stable-hash", "salsa", "serde", "smallvec", @@ -4638,7 +4645,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3ac7fe0da5ed5e..d28dd238480225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ rand = { version = "0.9.0" } rayon = { version = "1.10.0" } regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } +rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" } schemars = { version = "0.8.16" } diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality_(78e7b52096b8d36c).snap similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality.snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/assert_never.md_-_`assert_never`_-_Basic_functionality_(78e7b52096b8d36c).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic_(c507788da2659ec9).snap similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic.snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/assert_type.md_-_`assert_type`_-_Basic_(c507788da2659ec9).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_me\342\200\246_(116c27bd98838df7).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_method_signature.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_`__set__`_me\342\200\246_(116c27bd98838df7).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_typ\342\200\246_(a903c11fedbc5020).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_type.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Data_descriptors_-_Invalid_argument_typ\342\200\246_(a903c11fedbc5020).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Instance_attributes_\342\200\246_(ebfb3de6d1b96b23).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Instance_attributes_with_class-level_defaults.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Instance_attributes_\342\200\246_(ebfb3de6d1b96b23).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Possibly-unbound_att\342\200\246_(e5bdf78c427cb7fc).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Possibly-unbound_attributes.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Possibly-unbound_att\342\200\246_(e5bdf78c427cb7fc).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Pure_instance_attrib\342\200\246_(d13d57d3cc36face).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Pure_instance_attributes.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Pure_instance_attrib\342\200\246_(d13d57d3cc36face).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Setting_attributes_o\342\200\246_(467e26496f4c0c13).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Setting_attributes_on_union_types.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Setting_attributes_o\342\200\246_(467e26496f4c0c13).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_Unknown_attributes.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_`ClassVar`s_(8d7cca27987b099d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment.md_-_Attribute_assignment_-_`ClassVar`s.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment\342\200\246_-_Attribute_assignment_-_`ClassVar`s_(8d7cca27987b099d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp\342\200\246_(cbfbf5ff94e6e104).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imported_from_an_unresolved_module.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp\342\200\246_(cbfbf5ff94e6e104).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_\342\200\246_(846453deaca1071c).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_import.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_\342\200\246_(846453deaca1071c).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu\342\200\246_(4fad4be9778578b7).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodule_imports.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu\342\200\246_(4fad4be9778578b7).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_me\342\200\246_(3ffe352bb3a76715).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Bad_`__getitem__`_me\342\200\246_(3ffe352bb3a76715).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable_(3153247bb9a9b72a).snap similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable.snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Invalid_iterable_(3153247bb9a9b72a).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_i\342\200\246_(a90ba167a7c191eb).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_iteration_protocol.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_New_over_old_style_i\342\200\246_(a90ba167a7c191eb).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method\342\200\246_(36425dbcbd793d2b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method_and_`__getitem__`_is_not_callable.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_No_`__iter__`_method\342\200\246_(36425dbcbd793d2b).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__next__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(3b75cc467e6e012).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_bad_`__getitem__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(3b75cc467e6e012).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(8745233539d31200).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_unbound_`__getitem__`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(8745233539d31200).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(b1ce0da35c06026).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__\342\200\246_(b1ce0da35c06026).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(6177bb6d13a22241).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_invalid_`__iter__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(6177bb6d13a22241).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(ba36fbef63a14969).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterable_where_one_union_element_has_no_`__iter__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Union_type_as_iterab\342\200\246_(ba36fbef63a14969).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_it\342\200\246_(a1cdf01ad69ac37c).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_iterator.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_With_non-callable_it\342\200\246_(a1cdf01ad69ac37c).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_\342\200\246_(92e3fdd69edad63d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_return_an_iterator.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_does_not_\342\200\246_(92e3fdd69edad63d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_wi\342\200\246_(1136c0e783d61ba4).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_with_a_bad_signature.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_method_wi\342\200\246_(1136c0e783d61ba4).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_a\342\200\246_(707bd02a22c4acc8).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_an_iterator_with_an_invalid_`__next__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_`__iter__`_returns_a\342\200\246_(707bd02a22c4acc8).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_bound_ty\342\200\246_(d50204b9d91b7bd1).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_bound_typevar.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_bound_ty\342\200\246_(d50204b9d91b7bd1).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_constrai\342\200\246_(48ab83f977c109b4).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___Legacy_syntax_-_Inferring_a_constrained_typevar.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___L\342\200\246_-_Inferring_a_constrai\342\200\246_(48ab83f977c109b4).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_bound_ty\342\200\246_(5935d14c26afe407).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_bound_typevar.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_bound_ty\342\200\246_(5935d14c26afe407).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_constrai\342\200\246_(d2c475fccc70a8e2).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___PEP_695_syntax_-_Inferring_a_constrained_typevar.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/functions.md_-_Generic_functions___P\342\200\246_-_Inferring_a_constrai\342\200\246_(d2c475fccc70a8e2).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on\342\200\246_-_Operations_involving\342\200\246_(492b1163b8163c05).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on\342\200\246_-_Operations_involving\342\200\246_(492b1163b8163c05).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Basic_(16be9d90a741761).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Basic.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Basic_(16be9d90a741761).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Calls_to_methods_(4b3b8695d519a02).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Calls_to_methods.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Calls_to_methods_(4b3b8695d519a02).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_files_(d02c38e2dd054b4c).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_files.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_files_(d02c38e2dd054b4c).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_source_ord\342\200\246_(9b0bf549733d3f0a).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Different_source_order.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Different_source_ord\342\200\246_(9b0bf549733d3f0a).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_(ee38fd34ceba3293).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_(ee38fd34ceba3293).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_acro\342\200\246_(1d5d112808c49e9d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_across_multiple_lines.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_acro\342\200\246_(1d5d112808c49e9d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_with\342\200\246_(4bc5c16cd568b8ec).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Many_parameters_with_multiple_invalid_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Many_parameters_with\342\200\246_(4bc5c16cd568b8ec).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Test_calling_a_funct\342\200\246_(3b18271a821a59b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Test_calling_a_function_whose_type_is_vendored_from_`typeshed`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Test_calling_a_funct\342\200\246_(3b18271a821a59b).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Keyword_only_argumen\342\200\246_(8d9f18c78137411).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Keyword_only_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Keyword_only_argumen\342\200\246_(8d9f18c78137411).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Mix_of_arguments_(cfc64b1136058112).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Mix_of_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Mix_of_arguments_(cfc64b1136058112).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_One_keyword_argument_(cc34b2f7d19d427e).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_One_keyword_argument.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_One_keyword_argument_(cc34b2f7d19d427e).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Only_positional_(3dc93b1709eb3be9).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Only_positional.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Only_positional_(3dc93b1709eb3be9).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Synthetic_arguments_(4c09844bbbf47741).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Synthetic_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Synthetic_arguments_(4c09844bbbf47741).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_arguments_(e26a3e7b2773a63b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_arguments_(e26a3e7b2773a63b).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_keyword_arg\342\200\246_(4c855e39ea6baeaf).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_type.md_-_Invalid_argument_type_diagnostics_-_Tests_for_a_variety_of_argument_types_-_Variadic_keyword_arguments.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ\342\200\246_-_Invalid_argument_typ\342\200\246_-_Tests_for_a_variety_\342\200\246_-_Variadic_keyword_arg\342\200\246_(4c855e39ea6baeaf).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membersh\342\200\246_-_Return_type_that_doe\342\200\246_(feccf6b9da1e7cd3).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membersh\342\200\246_-_Return_type_that_doe\342\200\246_(feccf6b9da1e7cd3).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload.md_-_No_matching_overload_diagnostics_-_Calls_to_overloaded_functions.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implemen\342\200\246_(ab3f546bf004e24d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implemen\342\200\246_(ab3f546bf004e24d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloads.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@classmethod`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@final`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorators_-_`@override`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_\342\200\246_-_Regular_modules_(5c8e81664d1c7470).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_implementation_-_Regular_modules.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Overload_without_an_\342\200\246_-_Regular_modules_(5c8e81664d1c7470).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_cl\342\200\246_(288988036f34ddcf).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_classes.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Calls_to_protocol_cl\342\200\246_(288988036f34ddcf).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`ge\342\200\246_(3d0c4ee818c4d8d5).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`get_protocol_members()`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Invalid_calls_to_`ge\342\200\246_(3d0c4ee818c4d8d5).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protoco\342\200\246_(98257e7c2300373).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protocols.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Narrowing_of_protoco\342\200\246_(98257e7c2300373).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions.snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Com\342\200\246_-_Chained_comparisons_\342\200\246_(c391c13e2abc18a0).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Com\342\200\246_-_Chained_comparisons_\342\200\246_(c391c13e2abc18a0).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_erro\342\200\246_-_Semantic_syntax_erro\342\200\246_-_`async`_comprehensio\342\200\246_-_Python_3.10_(96aa8ec77d46553d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/semantic_syntax_erro\342\200\246_-_Semantic_syntax_erro\342\200\246_-_`async`_comprehensio\342\200\246_-_Python_3.10_(96aa8ec77d46553d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shado\342\200\246_(c8ff9e3a079e8bd5).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shadowing.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_class_shado\342\200\246_(c8ff9e3a079e8bd5).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_sh\342\200\246_(a1515328b775ebc1).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_shadowing.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/shadowing.md_-_Shadowing_-_Implicit_function_sh\342\200\246_(a1515328b775ebc1).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_\342\200\246_(f45f1da2f8ca693d).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_\342\200\246_(f45f1da2f8ca693d).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elemen\342\200\246_(39b614d4707c0661).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elemen\342\200\246_(39b614d4707c0661).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_A_smaller_scale_exam\342\200\246_(c24ecd8582e5eb2f).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_A_smaller_scale_example.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_A_smaller_scale_exam\342\200\246_(c24ecd8582e5eb2f).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Multiple_variants_bu\342\200\246_(d840ac443ca8ec7f).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Multiple_variants_but_only_one_is_invalid.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Multiple_variants_bu\342\200\246_(d840ac443ca8ec7f).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_keyword_argume\342\200\246_(ad1d489710ee2a34).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_keyword_argument_related_reasons.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_keyword_argume\342\200\246_(ad1d489710ee2a34).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_function_types_-_Try_to_cover_all_possible_reasons_-_Cover_non-keyword_related_reasons.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_valu\342\200\246_(f920ea85eefe9cfe).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_values_to_unpack.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_few_valu\342\200\246_(f920ea85eefe9cfe).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_val\342\200\246_(a53a2aec02bc999).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_values_to_unpack.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Exactly_too_many_val\342\200\246_(a53a2aec02bc999).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_\342\200\246_(fae6e2d526396252).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_iterable.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Right_hand_side_not_\342\200\246_(fae6e2d526396252).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_un\342\200\246_(cef19e6b2b58e6a3).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_unpack.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unpacking.md_-_Unpacking_-_Too_few_values_to_un\342\200\246_(cef19e6b2b58e6a3).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_An_unresolvable_impo\342\200\246_(72d090df51ea97b8).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_An_unresolvable_import_that_does_not_use_`from`.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_An_unresolvable_impo\342\200\246_(72d090df51ea97b8).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_a_\342\200\246_(12d4a70b7fc67cc6).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_a_resolvable_module_but_unresolvable_item.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_a_\342\200\246_(12d4a70b7fc67cc6).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(6cff507dc64a1bff).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_nested_module.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(6cff507dc64a1bff).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9da56616d6332a83).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unknown_current_module.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9da56616d6332a83).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9fa713dfa17cc404).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_an_unresolvable_module.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_an\342\200\246_(9fa713dfa17cc404).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_to\342\200\246_(4b8ba6ee48180cdd).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_diagnostics_-_Using_`from`_with_too_many_leading_dots.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di\342\200\246_-_Using_`from`_with_to\342\200\246_(4b8ba6ee48180cdd).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference\342\200\246_-_Diagnostics_for_unre\342\200\246_-_New_builtin_used_on_\342\200\246_(51edda0b1aebc2bf).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference.md_-_Diagnostics_for_unresolved_references_-_New_builtin_used_on_old_Python_version.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_reference\342\200\246_-_Diagnostics_for_unre\342\200\246_-_New_builtin_used_on_\342\200\246_(51edda0b1aebc2bf).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_att\342\200\246_(2721d40bf12fe8b7).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_attribute,_but_it's_not_callable.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_att\342\200\246_(2721d40bf12fe8b7).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(15636dc4074e5335).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_an_incorrect_return_type.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(15636dc4074e5335).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(ce8b8da49eaf4cda).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Has_a_`__bool__`_method,_but_has_incorrect_parameters.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Has_a_`__bool__`_met\342\200\246_(ce8b8da49eaf4cda).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Part_of_a_union_wher\342\200\246_(7cca8063ea43c1a).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_conversion.md_-_Different_ways_that_`unsupported-bool-conversion`_can_occur_-_Part_of_a_union_where_at_least_one_member_has_incorrect_`__bool__`_method.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/unsupported_bool_con\342\200\246_-_Different_ways_that_\342\200\246_-_Part_of_a_union_wher\342\200\246_(7cca8063ea43c1a).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap "b/crates/ty_python_semantic/resources/mdtest/snapshots/version_related_synt\342\200\246_-_Version-related_synt\342\200\246_-_`match`_statement_-_Before_3.10_(2545eaa83b635b8b).snap" similarity index 100% rename from crates/ty_python_semantic/resources/mdtest/snapshots/version_related_syntax_errors.md_-_Version-related_syntax_error_diagnostics_-_`match`_statement_-_Before_3.10.snap rename to "crates/ty_python_semantic/resources/mdtest/snapshots/version_related_synt\342\200\246_-_Version-related_synt\342\200\246_-_`match`_statement_-_Before_3.10_(2545eaa83b635b8b).snap" diff --git a/crates/ty_test/Cargo.toml b/crates/ty_test/Cargo.toml index 06b83c5ad43e0a..c80bf76011e9db 100644 --- a/crates/ty_test/Cargo.toml +++ b/crates/ty_test/Cargo.toml @@ -28,6 +28,7 @@ insta = { workspace = true, features = ["filters"] } memchr = { workspace = true } regex = { workspace = true } rustc-hash = { workspace = true } +rustc-stable-hash = { workspace = true } salsa = { workspace = true } smallvec = { workspace = true } serde = { workspace = true } diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index cc4e92130ab28d..112105b870143c 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -56,7 +56,10 @@ pub fn run( let filter = std::env::var(MDTEST_TEST_FILTER).ok(); let mut any_failures = false; for test in suite.tests() { - if filter.as_ref().is_some_and(|f| !test.name().contains(f)) { + if filter + .as_ref() + .is_some_and(|f| !test.uncontracted_name().contains(f)) + { continue; } @@ -420,7 +423,7 @@ fn create_diagnostic_snapshot( let mut snapshot = String::new(); writeln!(snapshot).unwrap(); writeln!(snapshot, "---").unwrap(); - writeln!(snapshot, "mdtest name: {}", test.name()).unwrap(); + writeln!(snapshot, "mdtest name: {}", test.uncontracted_name()).unwrap(); writeln!(snapshot, "mdtest path: {relative_fixture_path}").unwrap(); writeln!(snapshot, "---").unwrap(); writeln!(snapshot).unwrap(); diff --git a/crates/ty_test/src/parser.rs b/crates/ty_test/src/parser.rs index 5eb3b003a120bb..0f0c825203f08c 100644 --- a/crates/ty_test/src/parser.rs +++ b/crates/ty_test/src/parser.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, collections::hash_map::Entry}; +use std::{ + borrow::Cow, + collections::hash_map::Entry, + fmt::{Formatter, LowerHex, Write}, + hash::Hash, +}; use anyhow::bail; use ruff_db::system::{SystemPath, SystemPathBuf}; @@ -9,6 +14,7 @@ use ruff_python_ast::PySourceType; use ruff_python_trivia::Cursor; use ruff_source_file::{LineIndex, LineRanges, OneIndexed}; use ruff_text_size::{TextLen, TextRange, TextSize}; +use rustc_stable_hash::{FromStableHash, SipHasher128Hash, StableSipHasher128}; use crate::config::MarkdownTestConfig; @@ -39,6 +45,25 @@ impl<'s> MarkdownTestSuite<'s> { } } +struct Hash128([u64; 2]); + +impl FromStableHash for Hash128 { + type Hash = SipHasher128Hash; + + fn from(SipHasher128Hash(hash): SipHasher128Hash) -> Hash128 { + Hash128(hash) + } +} + +impl LowerHex for Hash128 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self(hash) = self; + + // Only write the first half for concision + write!(f, "{:x}", hash[0]) + } +} + /// A single test inside a [`MarkdownTestSuite`]. /// /// A test is a single header section (or the implicit root section, if there are no Markdown @@ -52,22 +77,61 @@ pub(crate) struct MarkdownTest<'m, 's> { } impl<'m, 's> MarkdownTest<'m, 's> { - pub(crate) fn name(&self) -> String { - let mut name = String::new(); + const MAX_TITLE_LENGTH: usize = 20; + const ELLIPSIS: char = '\u{2026}'; + + fn contracted_title(title: &str) -> String { + if title.len() <= Self::MAX_TITLE_LENGTH { + return (*title).to_string(); + } + + format!( + "{}{}", + title + .chars() + .take(Self::MAX_TITLE_LENGTH) + .collect::(), + Self::ELLIPSIS + ) + } + + fn joined_name(&self, contracted: bool) -> String { + let mut name_fragments = vec![]; let mut parent_id = self.section.parent_id; + while let Some(next_id) = parent_id { let parent = &self.suite.sections[next_id]; + name_fragments.insert(0, parent.title); parent_id = parent.parent_id; - if !name.is_empty() { - name.insert_str(0, " - "); - } - name.insert_str(0, parent.title); } - if !name.is_empty() { - name.push_str(" - "); + + name_fragments.push(self.section.title); + + let full_name = name_fragments.join(" - "); + + if !contracted { + return full_name; } - name.push_str(self.section.title); - name + + let mut contracted_name = name_fragments + .iter() + .map(|fragment| Self::contracted_title(fragment)) + .collect::>() + .join(" - "); + + let mut hasher = StableSipHasher128::new(); + full_name.hash(&mut hasher); + let _ = write!(contracted_name, " ({:x})", hasher.finish::()); + + contracted_name + } + + pub(crate) fn uncontracted_name(&self) -> String { + self.joined_name(false) + } + + pub(crate) fn name(&self) -> String { + self.joined_name(true) } pub(crate) fn files(&self) -> impl Iterator> { @@ -762,6 +826,8 @@ mod tests { use ruff_python_ast::PySourceType; use ruff_python_trivia::textwrap::dedent; + use insta::assert_snapshot; + use crate::parser::EmbeddedFilePath; #[test] @@ -786,7 +852,7 @@ mod tests { panic!("expected one test"); }; - assert_eq!(test.name(), "file.md"); + assert_snapshot!(test.name(), @"file.md (a8decfe8bd23e259)"); let [file] = test.files().collect::>()[..] else { panic!("expected one file"); @@ -814,7 +880,7 @@ mod tests { panic!("expected one test"); }; - assert_eq!(test.name(), "file.md"); + assert_snapshot!(test.name(), @"file.md (a8decfe8bd23e259)"); let [file] = test.files().collect::>()[..] else { panic!("expected one file"); @@ -865,9 +931,9 @@ mod tests { panic!("expected three tests"); }; - assert_eq!(test1.name(), "file.md - One"); - assert_eq!(test2.name(), "file.md - Two"); - assert_eq!(test3.name(), "file.md - Three"); + assert_snapshot!(test1.name(), @"file.md - One (9f620a533a21278)"); + assert_snapshot!(test2.name(), @"file.md - Two (1b4d4ef5a2cebbdc)"); + assert_snapshot!(test3.name(), @"file.md - Three (26479e23633dda57)"); let [file] = test1.files().collect::>()[..] else { panic!("expected one file"); @@ -935,8 +1001,8 @@ mod tests { panic!("expected two tests"); }; - assert_eq!(test1.name(), "file.md - One"); - assert_eq!(test2.name(), "file.md - Two"); + assert_snapshot!(test1.name(), @"file.md - One (9f620a533a21278)"); + assert_snapshot!(test2.name(), @"file.md - Two (1b4d4ef5a2cebbdc)"); let [main, foo] = test1.files().collect::>()[..] else { panic!("expected two files"); @@ -1331,7 +1397,7 @@ mod tests { panic!("expected one test"); }; - assert_eq!(test.name(), "file.md - A test"); + assert_snapshot!(test.name(), @"file.md - A test (1b4e27e6123dc8e7)"); } #[test] From fe653de3dd5fe98292c3ac832b964dc969dc157f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 13 May 2025 13:13:00 -0400 Subject: [PATCH 076/487] [ty] Infer parameter specializations of explicitly implemented generic protocols (#18054) Follows on from (and depends on) https://github.com/astral-sh/ruff/pull/18021. This updates our function specialization inference to infer type mappings from parameters that are generic protocols. For now, this only works when the argument _explicitly_ implements the protocol by listing it as a base class. (We end up using exactly the same logic as for generic classes in #18021.) For this to work with classes that _implicitly_ implement the protocol, we will have to check the types of the protocol members (which we are not currently doing), so that we can infer the specialization of the protocol that the class implements. --------- Co-authored-by: Alex Waygood --- .../mdtest/generics/legacy/functions.md | 12 +-- .../mdtest/generics/pep695/functions.md | 12 +-- crates/ty_python_semantic/src/types.rs | 2 +- .../ty_python_semantic/src/types/display.rs | 2 +- .../ty_python_semantic/src/types/generics.rs | 15 ++- .../ty_python_semantic/src/types/instance.rs | 95 +++++++++++-------- 6 files changed, 80 insertions(+), 58 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index d188f269a81ce5..ba4b45f9dffaa3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -98,12 +98,10 @@ def deeper_list(x: list[set[str]]) -> None: reveal_type(takes_in_protocol(x)) # revealed: Unknown def deep_explicit(x: ExplicitlyImplements[str]) -> None: - # TODO: revealed: str - reveal_type(takes_in_protocol(x)) # revealed: Unknown + reveal_type(takes_in_protocol(x)) # revealed: str def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None: - # TODO: revealed: set[str] - reveal_type(takes_in_protocol(x)) # revealed: Unknown + reveal_type(takes_in_protocol(x)) # revealed: set[str] def takes_in_type(x: type[T]) -> type[T]: return x @@ -128,10 +126,8 @@ reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown class ExplicitSub(ExplicitlyImplements[int]): ... class ExplicitGenericSub(ExplicitlyImplements[T]): ... -# TODO: revealed: int -reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown -# TODO: revealed: str -reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown +reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int +reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str ``` ## Inferring a bound typevar diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index 11701f17e7f84b..ffcd04a78ef565 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -93,12 +93,10 @@ def deeper_list(x: list[set[str]]) -> None: reveal_type(takes_in_protocol(x)) # revealed: Unknown def deep_explicit(x: ExplicitlyImplements[str]) -> None: - # TODO: revealed: str - reveal_type(takes_in_protocol(x)) # revealed: Unknown + reveal_type(takes_in_protocol(x)) # revealed: str def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None: - # TODO: revealed: set[str] - reveal_type(takes_in_protocol(x)) # revealed: Unknown + reveal_type(takes_in_protocol(x)) # revealed: set[str] def takes_in_type[T](x: type[T]) -> type[T]: return x @@ -123,10 +121,8 @@ reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown class ExplicitSub(ExplicitlyImplements[int]): ... class ExplicitGenericSub[T](ExplicitlyImplements[T]): ... -# TODO: revealed: int -reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown -# TODO: revealed: str -reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown +reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int +reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str ``` ## Inferring a bound typevar diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 3eb740871c27e1..94c3bfd9d75c50 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5333,7 +5333,7 @@ impl<'db> Type<'db> { Self::TypeVar(var) => Some(TypeDefinition::TypeVar(var.definition(db))), - Self::ProtocolInstance(protocol) => match protocol.inner() { + Self::ProtocolInstance(protocol) => match protocol.inner { Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))), Protocol::Synthesized(_) => None, }, diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 467a5779054dc6..b2a150fe85cca1 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -76,7 +76,7 @@ impl Display for DisplayRepresentation<'_> { (ClassType::Generic(alias), _) => alias.display(self.db).fmt(f), } } - Type::ProtocolInstance(protocol) => match protocol.inner() { + Type::ProtocolInstance(protocol) => match protocol.inner { Protocol::FromClass(ClassType::NonGeneric(class)) => { f.write_str(class.name(self.db)) } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index b5f2b3ef83e97d..a6360e57320e5d 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap; use crate::semantic_index::SemanticIndex; use crate::types::class::ClassType; use crate::types::class_base::ClassBase; -use crate::types::instance::NominalInstanceType; +use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, @@ -630,7 +630,10 @@ impl<'db> SpecializationBuilder<'db> { // ``` // // without specializing `T` to `None`. - if !actual.is_never() && actual.is_subtype_of(self.db, formal) { + if !matches!(formal, Type::ProtocolInstance(_)) + && !actual.is_never() + && actual.is_subtype_of(self.db, formal) + { return Ok(()); } @@ -678,6 +681,14 @@ impl<'db> SpecializationBuilder<'db> { Type::NominalInstance(NominalInstanceType { class: ClassType::Generic(formal_alias), .. + }) + // TODO: This will only handle classes that explicit implement a generic protocol + // by listing it as a base class. To handle classes that implicitly implement a + // generic protocol, we will need to check the types of the protocol members to be + // able to infer the specialization of the protocol that the class implements. + | Type::ProtocolInstance(ProtocolInstanceType { + inner: Protocol::FromClass(ClassType::Generic(formal_alias)), + .. }), Type::NominalInstance(NominalInstanceType { class: actual_class, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index b33d4306098722..99312849da3156 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -14,12 +14,9 @@ pub(super) use synthesized_protocol::SynthesizedProtocolType; impl<'db> Type<'db> { pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self { if class.class_literal(db).0.is_protocol(db) { - Self::ProtocolInstance(ProtocolInstanceType(Protocol::FromClass(class))) + Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) } else { - Self::NominalInstance(NominalInstanceType { - class, - _phantom: PhantomData, - }) + Self::NominalInstance(NominalInstanceType::from_class(class)) } } @@ -34,9 +31,9 @@ impl<'db> Type<'db> { where M: IntoIterator)>, { - Self::ProtocolInstance(ProtocolInstanceType(Protocol::Synthesized( + Self::ProtocolInstance(ProtocolInstanceType::synthesized( SynthesizedProtocolType::new(db, ProtocolInterface::with_members(db, members)), - ))) + )) } /// Return `true` if `self` conforms to the interface described by `protocol`. @@ -51,7 +48,7 @@ impl<'db> Type<'db> { // TODO: this should consider the types of the protocol members // as well as whether each member *exists* on `self`. protocol - .0 + .inner .interface(db) .members(db) .all(|member| !self.member(db, member.name()).symbol.is_unbound()) @@ -69,6 +66,15 @@ pub struct NominalInstanceType<'db> { } impl<'db> NominalInstanceType<'db> { + // Keep this method private, so that the only way of constructing `NominalInstanceType` + // instances is through the `Type::instance` constructor function. + fn from_class(class: ClassType<'db>) -> Self { + Self { + class, + _phantom: PhantomData, + } + } + pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { // N.B. The subclass relation is fully static self.class.is_subclass_of(db, other.class) @@ -131,10 +137,7 @@ impl<'db> NominalInstanceType<'db> { db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>, ) -> Self { - Self { - class: self.class.apply_type_mapping(db, type_mapping), - _phantom: PhantomData, - } + Self::from_class(self.class.apply_type_mapping(db, type_mapping)) } pub(super) fn find_legacy_typevars( @@ -155,21 +158,37 @@ impl<'db> From> for Type<'db> { /// A `ProtocolInstanceType` represents the set of all possible runtime objects /// that conform to the interface described by a certain protocol. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, salsa::Update)] -pub struct ProtocolInstanceType<'db>( +pub struct ProtocolInstanceType<'db> { + pub(super) inner: Protocol<'db>, + // Keep the inner field here private, // so that the only way of constructing `ProtocolInstanceType` instances // is through the `Type::instance` constructor function. - Protocol<'db>, -); + _phantom: PhantomData<()>, +} impl<'db> ProtocolInstanceType<'db> { - pub(super) fn inner(self) -> Protocol<'db> { - self.0 + // Keep this method private, so that the only way of constructing `ProtocolInstanceType` + // instances is through the `Type::instance` constructor function. + fn from_class(class: ClassType<'db>) -> Self { + Self { + inner: Protocol::FromClass(class), + _phantom: PhantomData, + } + } + + // Keep this method private, so that the only way of constructing `ProtocolInstanceType` + // instances is through the `Type::instance` constructor function. + fn synthesized(synthesized: SynthesizedProtocolType<'db>) -> Self { + Self { + inner: Protocol::Synthesized(synthesized), + _phantom: PhantomData, + } } /// Return the meta-type of this protocol-instance type. pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { - match self.0 { + match self.inner { Protocol::FromClass(class) => SubclassOfType::from(db, class), // TODO: we can and should do better here. @@ -197,22 +216,22 @@ impl<'db> ProtocolInstanceType<'db> { if object.satisfies_protocol(db, self) { return object; } - match self.0 { - Protocol::FromClass(_) => Type::ProtocolInstance(Self(Protocol::Synthesized( - SynthesizedProtocolType::new(db, self.0.interface(db)), - ))), + match self.inner { + Protocol::FromClass(_) => Type::ProtocolInstance(Self::synthesized( + SynthesizedProtocolType::new(db, self.inner.interface(db)), + )), Protocol::Synthesized(_) => Type::ProtocolInstance(self), } } /// Replace references to `class` with a self-reference marker pub(super) fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - match self.0 { + match self.inner { Protocol::FromClass(class_type) if class_type.class_literal(db).0 == class => { - ProtocolInstanceType(Protocol::Synthesized(SynthesizedProtocolType::new( + ProtocolInstanceType::synthesized(SynthesizedProtocolType::new( db, ProtocolInterface::SelfReference, - ))) + )) } _ => self, } @@ -220,12 +239,12 @@ impl<'db> ProtocolInstanceType<'db> { /// Return `true` if any of the members of this protocol type contain any `Todo` types. pub(super) fn contains_todo(self, db: &'db dyn Db) -> bool { - self.0.interface(db).contains_todo(db) + self.inner.interface(db).contains_todo(db) } /// Return `true` if this protocol type is fully static. pub(super) fn is_fully_static(self, db: &'db dyn Db) -> bool { - self.0.interface(db).is_fully_static(db) + self.inner.interface(db).is_fully_static(db) } /// Return `true` if this protocol type is a subtype of the protocol `other`. @@ -238,9 +257,9 @@ impl<'db> ProtocolInstanceType<'db> { /// TODO: consider the types of the members as well as their existence pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { other - .0 + .inner .interface(db) - .is_sub_interface_of(db, self.0.interface(db)) + .is_sub_interface_of(db, self.inner.interface(db)) } /// Return `true` if this protocol type is equivalent to the protocol `other`. @@ -269,7 +288,7 @@ impl<'db> ProtocolInstanceType<'db> { } pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { - match self.inner() { + match self.inner { Protocol::FromClass(class) => class.instance_member(db, name), Protocol::Synthesized(synthesized) => synthesized .interface() @@ -287,13 +306,13 @@ impl<'db> ProtocolInstanceType<'db> { db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>, ) -> Self { - match self.0 { - Protocol::FromClass(class) => Self(Protocol::FromClass( - class.apply_type_mapping(db, type_mapping), - )), - Protocol::Synthesized(synthesized) => Self(Protocol::Synthesized( - synthesized.apply_type_mapping(db, type_mapping), - )), + match self.inner { + Protocol::FromClass(class) => { + Self::from_class(class.apply_type_mapping(db, type_mapping)) + } + Protocol::Synthesized(synthesized) => { + Self::synthesized(synthesized.apply_type_mapping(db, type_mapping)) + } } } @@ -302,7 +321,7 @@ impl<'db> ProtocolInstanceType<'db> { db: &'db dyn Db, typevars: &mut FxOrderSet>, ) { - match self.0 { + match self.inner { Protocol::FromClass(class) => { class.find_legacy_typevars(db, typevars); } From cfbb9141002b2ed82aa420e354fa6dc3b971f239 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 13 May 2025 19:21:06 +0200 Subject: [PATCH 077/487] Use `https://ty.dev/rules` when linking to the rules table (#18072) --- crates/ty/docs/configuration.md | 2 +- crates/ty_project/src/metadata/options.rs | 2 +- ty.schema.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 150e2ea16ca81f..35aa8c48537363 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -22,7 +22,7 @@ respect-ignore-files = false Configures the enabled rules and their severity. -See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules. +See [the rules documentation](https://ty.dev/rules) for a list of all available rules. Valid severities are: diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 44428e4d1b66fb..0d465237ce886c 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -31,7 +31,7 @@ pub struct Options { /// Configures the enabled rules and their severity. /// - /// See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules. + /// See [the rules documentation](https://ty.dev/rules) for a list of all available rules. /// /// Valid severities are: /// diff --git a/ty.schema.json b/ty.schema.json index 5d54f5b2bcf062..5734db20a3e0c5 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -22,7 +22,7 @@ ] }, "rules": { - "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", + "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://ty.dev/rules) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", "anyOf": [ { "$ref": "#/definitions/Rules" From 301d9985d80c012aafc52c3b708fb634b9d1e56a Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 13 May 2025 22:14:30 +0200 Subject: [PATCH 078/487] [ty] Add benchmark for union of tuples (#18076) ## Summary Add a micro-benchmark for the code pattern observed in https://github.com/astral-sh/ty/issues/362. This currently takes around 1 second on my machine. ## Test Plan ```bash cargo bench -p ruff_benchmark -- 'ty_micro\[many_tuple' --sample-size 10 ``` --- crates/ruff_benchmark/benches/ty.rs | 58 ++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index 83c58e5e823bd9..c20f35e8c9c906 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -301,6 +301,62 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) { }); } +fn benchmark_many_tuple_assignments(criterion: &mut Criterion) { + setup_rayon(); + + criterion.bench_function("ty_micro[many_tuple_assignments]", |b| { + b.iter_batched_ref( + || { + // This is a micro benchmark, but it is effectively identical to a code sample + // observed in https://github.com/astral-sh/ty/issues/362 + setup_micro_case( + r#" + def flag() -> bool: + return True + + t = () + if flag(): + t += (1,) + if flag(): + t += (2,) + if flag(): + t += (3,) + if flag(): + t += (4,) + if flag(): + t += (5,) + if flag(): + t += (6,) + if flag(): + t += (7,) + if flag(): + t += (8,) + if flag(): + t += (9,) + if flag(): + t += (10,) + if flag(): + t += (11,) + + # Perform some kind of operation on the union type + print(1 in t) + "#, + ) + }, + |case| { + let Case { db, .. } = case; + let result = db.check().unwrap(); + assert_eq!(result.len(), 0); + }, + BatchSize::SmallInput, + ); + }); +} + criterion_group!(check_file, benchmark_cold, benchmark_incremental); -criterion_group!(micro, benchmark_many_string_assignments); +criterion_group!( + micro, + benchmark_many_string_assignments, + benchmark_many_tuple_assignments +); criterion_main!(check_file, micro); From 65e48cb4399e2e2768b4aa6a6051fb26f0649b41 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 13 May 2025 16:37:20 -0400 Subject: [PATCH 079/487] [ty] Check assignments to implicit global symbols are assignable to the types declared on `types.ModuleType` (#18077) --- .../mdtest/scopes/moduletype_attrs.md | 43 ++++++++++- crates/ty_python_semantic/src/symbol.rs | 36 +++++++-- crates/ty_python_semantic/src/types/infer.rs | 77 ++++++++++++++++--- 3 files changed, 137 insertions(+), 19 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 09fdefa94278e2..152ab6edb3dc3d 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -40,6 +40,39 @@ reveal_type(__dict__) reveal_type(__init__) ``` +## `ModuleType` globals combined with explicit assignments and declarations + +A `ModuleType` attribute can be overridden in the global scope with a different type, but it must be +a type assignable to the declaration on `ModuleType` unless it is accompanied by an explicit +redeclaration: + +`module.py`: + +```py +__file__ = None +__path__: list[str] = [] +__doc__: int # error: [invalid-declaration] "Cannot declare type `int` for inferred type `str | None`" +# error: [invalid-declaration] "Cannot shadow implicit global attribute `__package__` with declaration of type `int`" +__package__: int = 42 +__spec__ = 42 # error: [invalid-assignment] "Object of type `Literal[42]` is not assignable to `ModuleSpec | None`" +``` + +`main.py`: + +```py +import module + +reveal_type(module.__file__) # revealed: Unknown | None +reveal_type(module.__path__) # revealed: list[str] +reveal_type(module.__doc__) # revealed: Unknown +reveal_type(module.__spec__) # revealed: Unknown | ModuleSpec | None + +def nested_scope(): + global __loader__ + reveal_type(__loader__) # revealed: LoaderProtocol | None + __loader__ = 56 # error: [invalid-assignment] "Object of type `Literal[56]` is not assignable to `LoaderProtocol | None`" +``` + ## Accessed as attributes `ModuleType` attributes can also be accessed as attributes on module-literal types. The special @@ -105,16 +138,16 @@ defined as a global, however, a name lookup should union the `ModuleType` type w conditionally defined type: ```py -__file__ = 42 +__file__ = "foo" def returns_bool() -> bool: return True if returns_bool(): - __name__ = 1 + __name__ = 1 # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`" -reveal_type(__file__) # revealed: Literal[42] -reveal_type(__name__) # revealed: Literal[1] | str +reveal_type(__file__) # revealed: Literal["foo"] +reveal_type(__name__) # revealed: str ``` ## Conditionally global or `ModuleType` attribute, with annotation @@ -122,12 +155,14 @@ reveal_type(__name__) # revealed: Literal[1] | str The same is true if the name is annotated: ```py +# error: [invalid-declaration] "Cannot shadow implicit global attribute `__file__` with declaration of type `int`" __file__: int = 42 def returns_bool() -> bool: return True if returns_bool(): + # error: [invalid-declaration] "Cannot shadow implicit global attribute `__name__` with declaration of type `int`" __name__: int = 1 reveal_type(__file__) # revealed: Literal[42] diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index b18dba77332287..1d1156767985c1 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -14,7 +14,9 @@ use crate::types::{ }; use crate::{resolve_module, Db, KnownModule, Program}; -pub(crate) use implicit_globals::module_type_implicit_global_symbol; +pub(crate) use implicit_globals::{ + module_type_implicit_global_declaration, module_type_implicit_global_symbol, +}; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) enum Boundness { @@ -275,7 +277,6 @@ pub(crate) fn explicit_global_symbol<'db>( /// rather than being looked up as symbols explicitly defined/declared in the global scope. /// /// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports). -#[cfg(test)] pub(crate) fn global_symbol<'db>( db: &'db dyn Db, file: File, @@ -958,11 +959,36 @@ mod implicit_globals { use ruff_python_ast as ast; use crate::db::Db; - use crate::semantic_index::{self, symbol_table}; + use crate::semantic_index::{self, symbol_table, use_def_map}; use crate::symbol::SymbolAndQualifiers; - use crate::types::KnownClass; + use crate::types::{KnownClass, Type}; - use super::Symbol; + use super::{symbol_from_declarations, Symbol, SymbolFromDeclarationsResult}; + + pub(crate) fn module_type_implicit_global_declaration<'db>( + db: &'db dyn Db, + name: &str, + ) -> SymbolFromDeclarationsResult<'db> { + if !module_type_symbols(db) + .iter() + .any(|module_type_member| &**module_type_member == name) + { + return Ok(Symbol::Unbound.into()); + } + let Type::ClassLiteral(module_type_class) = KnownClass::ModuleType.to_class_literal(db) + else { + return Ok(Symbol::Unbound.into()); + }; + let module_type_scope = module_type_class.body_scope(db); + let symbol_table = symbol_table(db, module_type_scope); + let Some(symbol_id) = symbol_table.symbol_id_by_name(name) else { + return Ok(Symbol::Unbound.into()); + }; + symbol_from_declarations( + db, + use_def_map(db, module_type_scope).public_declarations(symbol_id), + ) + } /// Looks up the type of an "implicit global symbol". Returns [`Symbol::Unbound`] if /// `name` is not present as an implicit symbol in module-global namespaces. diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 16ebb5b830f802..5b7533d714e3ad 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -60,9 +60,10 @@ use crate::semantic_index::symbol::{ }; use crate::semantic_index::{semantic_index, EagerSnapshotResult, SemanticIndex}; use crate::symbol::{ - builtins_module_scope, builtins_symbol, explicit_global_symbol, - module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, - typing_extensions_symbol, Boundness, LookupError, + builtins_module_scope, builtins_symbol, explicit_global_symbol, global_symbol, + module_type_implicit_global_declaration, module_type_implicit_global_symbol, symbol, + symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, Boundness, + LookupError, }; use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; use crate::types::class::{MetaclassErrorKind, SliceLiteral}; @@ -1420,8 +1421,9 @@ impl<'db> TypeInferenceBuilder<'db> { let symbol_id = binding.symbol(self.db()); let global_use_def_map = self.index.use_def_map(FileScopeId::global()); - let declarations = if self.skip_non_global_scopes(file_scope_id, symbol_id) { - let symbol_name = symbol_table.symbol(symbol_id).name(); + let symbol_name = symbol_table.symbol(symbol_id).name(); + let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, symbol_id); + let declarations = if skip_non_global_scopes { match self .index .symbol_table(FileScopeId::global()) @@ -1436,6 +1438,20 @@ impl<'db> TypeInferenceBuilder<'db> { }; let declared_ty = symbol_from_declarations(self.db(), declarations) + .and_then(|symbol| { + let symbol = if matches!(symbol.symbol, Symbol::Type(_, Boundness::Bound)) { + symbol + } else if skip_non_global_scopes + || self.scope().file_scope_id(self.db()).is_global() + { + let module_type_declarations = + module_type_implicit_global_declaration(self.db(), symbol_name)?; + symbol.or_fall_back_to(self.db(), || module_type_declarations) + } else { + symbol + }; + Ok(symbol) + }) .map(|SymbolAndQualifiers { symbol, .. }| { symbol.ignore_possibly_unbound().unwrap_or(Type::unknown()) }) @@ -1487,6 +1503,23 @@ impl<'db> TypeInferenceBuilder<'db> { let prior_bindings = use_def.bindings_at_declaration(declaration); // unbound_ty is Never because for this check we don't care about unbound let inferred_ty = symbol_from_bindings(self.db(), prior_bindings) + .with_qualifiers(TypeQualifiers::empty()) + .or_fall_back_to(self.db(), || { + // Fallback to bindings declared on `types.ModuleType` if it's a global symbol + let scope = self.scope().file_scope_id(self.db()); + if scope.is_global() { + module_type_implicit_global_symbol( + self.db(), + self.index + .symbol_table(scope) + .symbol(declaration.symbol(self.db())) + .name(), + ) + } else { + Symbol::Unbound.into() + } + }) + .symbol .ignore_possibly_unbound() .unwrap_or(Type::Never); let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_type()) { @@ -1525,6 +1558,34 @@ impl<'db> TypeInferenceBuilder<'db> { declared_ty, inferred_ty, } => { + let file_scope_id = self.scope().file_scope_id(self.db()); + if file_scope_id.is_global() { + let symbol_table = self.index.symbol_table(file_scope_id); + let symbol_name = symbol_table.symbol(definition.symbol(self.db())).name(); + if let Some(module_type_implicit_declaration) = + module_type_implicit_global_declaration(self.db(), symbol_name) + .ok() + .and_then(|sym| sym.symbol.ignore_possibly_unbound()) + { + let declared_type = declared_ty.inner_type(); + if !declared_type + .is_assignable_to(self.db(), module_type_implicit_declaration) + { + if let Some(builder) = + self.context.report_lint(&INVALID_DECLARATION, node) + { + let mut diagnostic = builder.into_diagnostic(format_args!( + "Cannot shadow implicit global attribute `{symbol_name}` with declaration of type `{}`", + declared_type.display(self.db()) + )); + diagnostic.info(format_args!("The global symbol `{}` must always have a type assignable to `{}`", + symbol_name, + module_type_implicit_declaration.display(self.db()) + )); + } + } + } + } if inferred_ty.is_assignable_to(self.db(), declared_ty.inner_type()) { (declared_ty, inferred_ty) } else { @@ -5386,11 +5447,7 @@ impl<'db> TypeInferenceBuilder<'db> { .is_some_and(|symbol_id| self.skip_non_global_scopes(file_scope_id, symbol_id)); if skip_non_global_scopes { - return symbol( - db, - FileScopeId::global().to_scope_id(db, current_file), - symbol_name, - ); + return global_symbol(self.db(), self.file(), symbol_name); } // If it's a function-like scope and there is one or more binding in this scope (but From 8cbd433a3130c692ea8f78894fdfacd6e4a50c27 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 14 May 2025 02:57:48 +0530 Subject: [PATCH 080/487] [ty] Add cycle handling for unpacking targets (#18078) ## Summary This PR adds cycle handling for `infer_unpack_types` based on the analysis in astral-sh/ty#364. Fixes: astral-sh/ty#364 ## Test Plan Add a cycle handling test for unpacking in `cycle.md` --- .../resources/mdtest/cycle.md | 18 +++++++++++ crates/ty_python_semantic/src/types/infer.rs | 15 +++++++++- .../ty_python_semantic/src/types/unpacker.rs | 30 +++++++++++++++++-- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/cycle.md b/crates/ty_python_semantic/resources/mdtest/cycle.md index dea0a124e5bb3a..0bd3b5b2a6df4a 100644 --- a/crates/ty_python_semantic/resources/mdtest/cycle.md +++ b/crates/ty_python_semantic/resources/mdtest/cycle.md @@ -13,3 +13,21 @@ def f(x: f): reveal_type(f) # revealed: def f(x: Unknown) -> Unknown ``` + +## Unpacking + +See: + +```py +class Point: + def __init__(self, x: int = 0, y: int = 0) -> None: + self.x = x + self.y = y + + def replace_with(self, other: "Point") -> None: + self.x, self.y = other.x, other.y + +p = Point() +reveal_type(p.x) # revealed: Unknown | int +reveal_type(p.y) # revealed: Unknown | int +``` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 5b7533d714e3ad..be64625dc24e3a 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -307,7 +307,7 @@ fn single_expression_cycle_initial<'db>( /// involved in an unpacking operation. It returns a result-like object that can be used to get the /// type of the variables involved in this unpacking along with any violations that are detected /// during this unpacking. -#[salsa::tracked(returns(ref))] +#[salsa::tracked(returns(ref), cycle_fn=unpack_cycle_recover, cycle_initial=unpack_cycle_initial)] pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> { let file = unpack.file(db); let _span = @@ -318,6 +318,19 @@ pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> U unpacker.finish() } +fn unpack_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &UnpackResult<'db>, + _count: u32, + _unpack: Unpack<'db>, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn unpack_cycle_initial<'db>(_db: &'db dyn Db, _unpack: Unpack<'db>) -> UnpackResult<'db> { + UnpackResult::cycle_fallback(Type::Never) +} + /// Returns the type of the nearest enclosing class for the given scope. /// /// This function walks up the ancestor scopes starting from the given scope, diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index d3c3026f4ac8d7..5a3ffbc65e4a90 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -287,6 +287,7 @@ impl<'db> Unpacker<'db> { UnpackResult { diagnostics: self.context.finish(), targets: self.targets, + cycle_fallback_type: None, } } } @@ -295,21 +296,46 @@ impl<'db> Unpacker<'db> { pub(crate) struct UnpackResult<'db> { targets: FxHashMap>, diagnostics: TypeCheckDiagnostics, + + /// The fallback type for missing expressions. + /// + /// This is used only when constructing a cycle-recovery `UnpackResult`. + cycle_fallback_type: Option>, } impl<'db> UnpackResult<'db> { /// Returns the inferred type for a given sub-expression of the left-hand side target /// of an unpacking assignment. /// - /// Panics if a scoped expression ID is passed in that does not correspond to a sub- + /// # Panics + /// + /// May panic if a scoped expression ID is passed in that does not correspond to a sub- /// expression of the target. #[track_caller] pub(crate) fn expression_type(&self, expr_id: ScopedExpressionId) -> Type<'db> { - self.targets[&expr_id] + self.try_expression_type(expr_id).expect( + "expression should belong to this `UnpackResult` and \ + `Unpacker` should have inferred a type for it", + ) + } + + pub(crate) fn try_expression_type(&self, expr_id: ScopedExpressionId) -> Option> { + self.targets + .get(&expr_id) + .copied() + .or(self.cycle_fallback_type) } /// Returns the diagnostics in this unpacking assignment. pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics { &self.diagnostics } + + pub(crate) fn cycle_fallback(cycle_fallback_type: Type<'db>) -> Self { + Self { + targets: FxHashMap::default(), + diagnostics: TypeCheckDiagnostics::default(), + cycle_fallback_type: Some(cycle_fallback_type), + } + } } From d17557f0aef4c6af9892d1f9a411c03727cb857b Mon Sep 17 00:00:00 2001 From: Chandra Kiran G <121796315+kiran-4444@users.noreply.github.com> Date: Wed, 14 May 2025 11:56:48 +0530 Subject: [PATCH 081/487] [ty] Fix Inconsistent casing in diagnostic (#18084) --- .../resources/mdtest/function/return_type.md | 2 +- .../resources/mdtest/generics/legacy/functions.md | 4 ++-- .../resources/mdtest/generics/pep695/functions.md | 4 ++-- ...eturn_type_-_Generator_functions_(d9ed06b61b14fd4c).snap | 4 ++-- ...nvalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" | 6 +++--- ...nvalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" | 2 +- ...eturn_type_-_Invalid_return_type_(a91e0c67519cd77f).snap | 4 ++-- ...nvalid_return_type_\342\200\246_(c3a523878447af6b).snap" | 2 +- crates/ty_python_semantic/src/types/diagnostic.rs | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/function/return_type.md b/crates/ty_python_semantic/resources/mdtest/function/return_type.md index c3589b2c626d86..918491020082b3 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/ty_python_semantic/resources/mdtest/function/return_type.md @@ -318,7 +318,7 @@ def f(cond: bool) -> str: return "hello" if cond else NotImplemented def f(cond: bool) -> int: - # error: [invalid-return-type] "Return type does not match returned value: Expected `int`, found `Literal["hello"]`" + # error: [invalid-return-type] "Return type does not match returned value: expected `int`, found `Literal["hello"]`" return "hello" if cond else NotImplemented ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index ba4b45f9dffaa3..651de8c08368a1 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -194,7 +194,7 @@ def good_return(x: T) -> T: return x def bad_return(x: T) -> T: - # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" return x + 1 ``` @@ -212,7 +212,7 @@ def different_types(cond: bool, t: T, s: S) -> T: if cond: return t else: - # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" return s def same_types(cond: bool, t1: T, t2: T) -> T: diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index ffcd04a78ef565..c9357ecc266846 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -179,7 +179,7 @@ def good_return[T: int](x: T) -> T: return x def bad_return[T: int](x: T) -> T: - # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" return x + 1 ``` @@ -192,7 +192,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T: if cond: return t else: - # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" return s def same_types[T](cond: bool, t1: T, t2: T) -> T: diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap index fc5b1bc06a36db..fe49d4d2be8853 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Generator_functions_(d9ed06b61b14fd4c).snap @@ -60,7 +60,7 @@ error[invalid-return-type]: Return type does not match returned value 17 | yield from i() 18 | 19 | def j() -> str: # error: [invalid-return-type] - | ^^^ Expected `str`, found `types.GeneratorType` + | ^^^ expected `str`, found `types.GeneratorType` 20 | yield 42 21 | import types | @@ -77,7 +77,7 @@ error[invalid-return-type]: Return type does not match returned value 34 | yield 42 35 | 36 | async def j() -> str: # error: [invalid-return-type] - | ^^^ Expected `str`, found `types.AsyncGeneratorType` + | ^^^ expected `str`, found `types.AsyncGeneratorType` 37 | yield 42 | info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" index e7ff634eb16a0f..516ed04ec67bf3 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_\342\200\246_(94c036c5d3803ab2).snap" @@ -41,7 +41,7 @@ error[invalid-return-type]: Return type does not match returned value 4 | else: 5 | # error: [invalid-return-type] 6 | return 1 - | ^ Expected `str`, found `Literal[1]` + | ^ expected `str`, found `Literal[1]` 7 | 8 | def f(cond: bool) -> str: | @@ -60,7 +60,7 @@ error[invalid-return-type]: Return type does not match returned value 9 | if cond: 10 | # error: [invalid-return-type] 11 | return 1 - | ^ Expected `str`, found `Literal[1]` + | ^ expected `str`, found `Literal[1]` 12 | else: 13 | # error: [invalid-return-type] | @@ -75,7 +75,7 @@ error[invalid-return-type]: Return type does not match returned value 12 | else: 13 | # error: [invalid-return-type] 14 | return 2 - | ^ Expected `str`, found `Literal[2]` + | ^ expected `str`, found `Literal[2]` | ::: src/mdtest_snippet.py:8:22 | diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" index 9e47f142004565..e77bd91b65ba51 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" @@ -48,7 +48,7 @@ error[invalid-return-type]: Return type does not match returned value 2 | if False: 3 | # error: [invalid-return-type] 4 | return 1 - | ^ Expected `None`, found `Literal[1]` + | ^ expected `None`, found `Literal[1]` 5 | 6 | # error: [invalid-return-type] | diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap index c9e8134c2bcbac..d452f178202ba8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap @@ -57,7 +57,7 @@ error[invalid-return-type]: Return type does not match returned value | --- Expected `str` because of return type 6 | # error: [invalid-return-type] 7 | return 1 - | ^ Expected `str`, found `Literal[1]` + | ^ expected `str`, found `Literal[1]` 8 | 9 | def f() -> int: | @@ -75,7 +75,7 @@ error[invalid-return-type]: Return type does not match returned value | --- Expected `int` because of return type 10 | # error: [invalid-return-type] 11 | return - | ^^^^^^ Expected `int`, found `None` + | ^^^^^^ expected `int`, found `None` 12 | 13 | from typing import TypeVar | diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" index 3cd2fc5fcff989..bf469ce066d669 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" @@ -37,7 +37,7 @@ error[invalid-return-type]: Return type does not match returned value | --- Expected `int` because of return type 2 | # error: [invalid-return-type] 3 | return ... - | ^^^ Expected `int`, found `ellipsis` + | ^^^ expected `int`, found `ellipsis` 4 | 5 | # error: [invalid-return-type] | diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index b0ad0cd8c1beac..c6910b32df9e43 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1548,7 +1548,7 @@ pub(super) fn report_invalid_return_type( let mut diag = builder.into_diagnostic("Return type does not match returned value"); diag.set_primary_message(format_args!( - "Expected `{expected_ty}`, found `{actual_ty}`", + "expected `{expected_ty}`, found `{actual_ty}`", expected_ty = expected_ty.display(context.db()), actual_ty = actual_ty.display(context.db()), )); @@ -1573,7 +1573,7 @@ pub(super) fn report_invalid_generator_function_return_type( let mut diag = builder.into_diagnostic("Return type does not match returned value"); let inferred_ty = inferred_return.display(context.db()); diag.set_primary_message(format_args!( - "Expected `{expected_ty}`, found `{inferred_ty}`", + "expected `{expected_ty}`, found `{inferred_ty}`", expected_ty = expected_ty.display(context.db()), )); From e7f97a3e4bf06c9d5539d71ff0a35eb399a41fee Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 14 May 2025 09:20:23 +0200 Subject: [PATCH 082/487] [ty] Reduce log level of 'symbol .. (via star import) not found' log message (#18087) --- .../src/semantic_index/visibility_constraints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs b/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs index 4c656963a116bc..f1294287e4d994 100644 --- a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs @@ -663,7 +663,7 @@ impl VisibilityConstraints { if all_names.contains(symbol_name) { Some(RequiresExplicitReExport::No) } else { - tracing::debug!( + tracing::trace!( "Symbol `{}` (via star import) not found in `__all__` of `{}`", symbol_name, referenced_file.path(db) From 1eab59e68185d6d7afb68d0a2fd4b8bad19e9f8f Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 14 May 2025 10:20:19 +0100 Subject: [PATCH 083/487] Remove double whitespace (#18090) --- README.md | 2 +- docs/configuration.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c7c98b7cf65ce1..37c5d02cbb073f 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ indent-width = 4 target-version = "py39" [lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. select = ["E4", "E7", "E9", "F"] ignore = [] diff --git a/docs/configuration.md b/docs/configuration.md index ade0b0ced3c5d2..533c04c71dcd8e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -51,7 +51,7 @@ If left unspecified, Ruff's default configuration is equivalent to: target-version = "py39" [tool.ruff.lint] - # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. + # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. select = ["E4", "E7", "E9", "F"] @@ -133,7 +133,7 @@ If left unspecified, Ruff's default configuration is equivalent to: target-version = "py39" [lint] - # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. + # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. select = ["E4", "E7", "E9", "F"] From 97d7b4693684753139b15f86bc796df9ee25e76e Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 14 May 2025 15:33:42 +0200 Subject: [PATCH 084/487] [ty] Do not look up `__init__` on instances (#18092) ## Summary Dunder methods are never looked up on instances. We do this implicitly in `try_call_dunder`, but the corresponding flag was missing in the instance-construction code where we use `member_lookup_with_policy` directly. fixes https://github.com/astral-sh/ty/issues/322 ## Test Plan Added regression test. --- .../resources/mdtest/attributes.md | 8 ++++++++ .../resources/mdtest/named_tuple.md | 17 +++++++++++++++++ crates/ty_python_semantic/src/types.rs | 3 ++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index acd0799c287951..40a1841c678f3c 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1462,6 +1462,14 @@ Instance attributes also take precedence over the `__getattr__` method: reveal_type(c.instance_attr) # revealed: str ``` +Importantly, `__getattr__` is only called if attributes are accessed on instances, not if they are +accessed on the class itself: + +```py +# error: [unresolved-attribute] +CustomGetAttr.whatever +``` + ### Type of the `name` parameter If the `name` parameter of the `__getattr__` method is annotated with a (union of) literal type(s), diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index d31ba81b3b529d..b7cf17ea46b953 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -149,3 +149,20 @@ Person = namedtuple("Person", ["id", "name", "age"], defaults=[None]) alice = Person(1, "Alice", 42) bob = Person(2, "Bob") ``` + +## NamedTuple with custom `__getattr__` + +This is a regression test for . Make sure that the +`__getattr__` method does not interfere with the `NamedTuple` behavior. + +```py +from typing import NamedTuple + +class Vec2(NamedTuple): + x: float = 0.0 + y: float = 0.0 + + def __getattr__(self, attrs: str): ... + +Vec2(0.0, 0.0) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 94c3bfd9d75c50..1ffe70873a2fca 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4516,7 +4516,8 @@ impl<'db> Type<'db> { .member_lookup_with_policy( db, "__init__".into(), - MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, + MemberLookupPolicy::NO_INSTANCE_FALLBACK + | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) .symbol .is_unbound() From 9b52ae8991b5ad67cd3624f12ed839002e3ff438 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Wed, 14 May 2025 11:31:42 -0300 Subject: [PATCH 085/487] [`flake8-pytest-style`] Don't recommend `usefixtures` for parametrize values in `PT019` (#17650) ## Summary Fixes #17599. ## Test Plan Snapshot tests. --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> --- .../fixtures/flake8_pytest_style/PT019.py | 35 +++++++++++++ .../flake8_pytest_style/rules/fixture.rs | 49 +++++++++++++++++-- ...es__flake8_pytest_style__tests__PT019.snap | 17 ++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT019.py b/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT019.py index 1df67fd7f89582..2bdbe93116ca74 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT019.py @@ -12,3 +12,38 @@ def test_xxx(_fixture): # Error arg def test_xxx(*, _fixture): # Error kwonly pass + +# https://github.com/astral-sh/ruff/issues/17599 + +@pytest.mark.parametrize("_foo", [1, 2, 3]) +def test_thingy(_foo): # Ok defined in parametrize + pass + +@pytest.mark.parametrize( + "_test_input,_expected", + [("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)], +) +def test_eval(_test_input, _expected): # OK defined in parametrize + pass + + +@pytest.mark.parametrize("_foo", [1, 2, 3]) +def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize + pass + +@pytest.mark.parametrize(["_foo", "_bar"], [1, 2, 3]) +def test_thingy3(_foo, _bar): # OK defined in parametrize + pass + +@pytest.mark.parametrize(("_foo"), [1, 2, 3]) +def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize + pass + +@pytest.mark.parametrize([" _foo", " _bar "], [1, 2, 3]) +def test_thingy5(_foo, _bar): # OK defined in parametrize + pass + +x = "other" +@pytest.mark.parametrize(x, [1, 2, 3]) +def test_thingy5(_foo, _bar): # known false negative, we don't try to resolve variables + pass diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index ff34a8a6c00e06..7e0d08faedad70 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::helpers::map_callable; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; @@ -11,6 +12,7 @@ use ruff_python_semantic::SemanticModel; use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; +use rustc_hash::FxHashSet; use crate::checkers::ast::Checker; use crate::fix::edits; @@ -807,10 +809,51 @@ fn check_fixture_returns(checker: &Checker, name: &str, body: &[Stmt], returns: } /// PT019 -fn check_test_function_args(checker: &Checker, parameters: &Parameters) { +fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorators: &[Decorator]) { + let mut named_parametrize = FxHashSet::default(); + for decorator in decorators.iter().filter(|decorator| { + UnqualifiedName::from_expr(map_callable(&decorator.expression)) + .is_some_and(|name| matches!(name.segments(), ["pytest", "mark", "parametrize"])) + }) { + let Some(call_expr) = decorator.expression.as_call_expr() else { + continue; + }; + let Some(first_arg) = call_expr.arguments.find_argument_value("argnames", 0) else { + continue; + }; + + match first_arg { + Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { + named_parametrize.extend( + value + .to_str() + .split(',') + .map(str::trim) + .filter(|param| !param.is_empty() && param.starts_with('_')), + ); + } + + Expr::Name(_) => return, + Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) + if elts.iter().any(Expr::is_name_expr) => + { + return + } + Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => { + named_parametrize.extend( + elts.iter() + .filter_map(Expr::as_string_literal_expr) + .map(|param| param.value.to_str().trim()) + .filter(|param| !param.is_empty() && param.starts_with('_')), + ); + } + _ => {} + } + } + for parameter in parameters.iter_non_variadic_params() { let name = parameter.name(); - if name.starts_with('_') { + if name.starts_with('_') && !named_parametrize.contains(name.as_str()) { checker.report_diagnostic(Diagnostic::new( PytestFixtureParamWithoutValue { name: name.to_string(), @@ -915,6 +958,6 @@ pub(crate) fn fixture( } if checker.enabled(Rule::PytestFixtureParamWithoutValue) && name.starts_with("test_") { - check_test_function_args(checker, parameters); + check_test_function_args(checker, parameters, decorators); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT019.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT019.snap index 309e4a19ec0291..2c2abc05e472fe 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT019.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT019.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs -snapshot_kind: text --- PT019.py:9:14: PT019 Fixture `_fixture` without value is injected as parameter, use `@pytest.mark.usefixtures` instead | @@ -15,3 +14,19 @@ PT019.py:13:17: PT019 Fixture `_fixture` without value is injected as parameter, | ^^^^^^^^ PT019 14 | pass | + +PT019.py:31:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead + | +30 | @pytest.mark.parametrize("_foo", [1, 2, 3]) +31 | def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize + | ^^^^ PT019 +32 | pass + | + +PT019.py:39:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead + | +38 | @pytest.mark.parametrize(("_foo"), [1, 2, 3]) +39 | def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize + | ^^^^ PT019 +40 | pass + | From 1b4f7de840d86b6475b44abdba053b437601ad26 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Wed, 14 May 2025 16:37:25 +0200 Subject: [PATCH 086/487] [`pyupgrade`] Add `resource.error` as deprecated alias of `OSError` (`UP024`) (#17933) ## Summary Partially addresses #17935. [`resource.error`](https://docs.python.org/3/library/resource.html#resource.error) is a deprecated alias of [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError). > _Changed in version 3.3:_ Following [**PEP 3151**](https://peps.python.org/pep-3151/), this class was made an alias of [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError). Add it to the list of `OSError` aliases found by [os-error-alias (UP024)](https://docs.astral.sh/ruff/rules/os-error-alias/#os-error-alias-up024). ## Test Plan Sorry, I usually don't program in Rust. Could you at least point me to the test I would need to modify? --- .../test/fixtures/pyupgrade/UP024_2.py | 7 +- .../rules/pyupgrade/rules/os_error_alias.rs | 2 +- ...__rules__pyupgrade__tests__UP024_2.py.snap | 564 ++++++++++-------- 3 files changed, 320 insertions(+), 253 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP024_2.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP024_2.py index 25e846e0288cc7..e99b47da49f79e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP024_2.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP024_2.py @@ -6,14 +6,16 @@ raise error # Testing the modules -import socket, mmap, select +import socket, mmap, select, resource raise socket.error raise mmap.error raise select.error +raise resource.error raise socket.error() raise mmap.error(1) raise select.error(1, 2) +raise resource.error(1, "strerror", "filename") raise socket.error( 1, @@ -30,6 +32,9 @@ from select import error raise error(1, 2) +from resource import error +raise error(1, "strerror", "filename") + # Testing the names raise EnvironmentError raise IOError diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs index a9c65a92a06f65..920f10032894bd 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs @@ -64,7 +64,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool { [ "" | "builtins", "EnvironmentError" | "IOError" | "WindowsError" - ] | ["mmap" | "select" | "socket" | "os", "error"] + ] | ["mmap" | "resource" | "select" | "socket" | "os", "error"] ) }) } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP024_2.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP024_2.py.snap index b6e358f62f2268..7a05ae711be009 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP024_2.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP024_2.py.snap @@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError` | 8 | # Testing the modules - 9 | import socket, mmap, select + 9 | import socket, mmap, select, resource 10 | raise socket.error | ^^^^^^^^^^^^ UP024 11 | raise mmap.error @@ -15,32 +15,33 @@ UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError` ℹ Safe fix 7 7 | 8 8 | # Testing the modules -9 9 | import socket, mmap, select +9 9 | import socket, mmap, select, resource 10 |-raise socket.error 10 |+raise OSError 11 11 | raise mmap.error 12 12 | raise select.error -13 13 | +13 13 | raise resource.error UP024_2.py:11:7: UP024 [*] Replace aliased errors with `OSError` | - 9 | import socket, mmap, select + 9 | import socket, mmap, select, resource 10 | raise socket.error 11 | raise mmap.error | ^^^^^^^^^^ UP024 12 | raise select.error +13 | raise resource.error | = help: Replace `mmap.error` with builtin `OSError` ℹ Safe fix 8 8 | # Testing the modules -9 9 | import socket, mmap, select +9 9 | import socket, mmap, select, resource 10 10 | raise socket.error 11 |-raise mmap.error 11 |+raise OSError 12 12 | raise select.error -13 13 | -14 14 | raise socket.error() +13 13 | raise resource.error +14 14 | UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError` | @@ -48,355 +49,416 @@ UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError` 11 | raise mmap.error 12 | raise select.error | ^^^^^^^^^^^^ UP024 -13 | -14 | raise socket.error() +13 | raise resource.error | = help: Replace `select.error` with builtin `OSError` ℹ Safe fix -9 9 | import socket, mmap, select +9 9 | import socket, mmap, select, resource 10 10 | raise socket.error 11 11 | raise mmap.error 12 |-raise select.error 12 |+raise OSError -13 13 | -14 14 | raise socket.error() -15 15 | raise mmap.error(1) +13 13 | raise resource.error +14 14 | +15 15 | raise socket.error() -UP024_2.py:14:7: UP024 [*] Replace aliased errors with `OSError` +UP024_2.py:13:7: UP024 [*] Replace aliased errors with `OSError` | +11 | raise mmap.error 12 | raise select.error -13 | -14 | raise socket.error() - | ^^^^^^^^^^^^ UP024 -15 | raise mmap.error(1) -16 | raise select.error(1, 2) +13 | raise resource.error + | ^^^^^^^^^^^^^^ UP024 +14 | +15 | raise socket.error() | - = help: Replace `socket.error` with builtin `OSError` + = help: Replace `resource.error` with builtin `OSError` ℹ Safe fix +10 10 | raise socket.error 11 11 | raise mmap.error 12 12 | raise select.error -13 13 | -14 |-raise socket.error() - 14 |+raise OSError() -15 15 | raise mmap.error(1) -16 16 | raise select.error(1, 2) -17 17 | +13 |-raise resource.error + 13 |+raise OSError +14 14 | +15 15 | raise socket.error() +16 16 | raise mmap.error(1) UP024_2.py:15:7: UP024 [*] Replace aliased errors with `OSError` | -14 | raise socket.error() -15 | raise mmap.error(1) - | ^^^^^^^^^^ UP024 -16 | raise select.error(1, 2) +13 | raise resource.error +14 | +15 | raise socket.error() + | ^^^^^^^^^^^^ UP024 +16 | raise mmap.error(1) +17 | raise select.error(1, 2) | - = help: Replace `mmap.error` with builtin `OSError` + = help: Replace `socket.error` with builtin `OSError` ℹ Safe fix 12 12 | raise select.error -13 13 | -14 14 | raise socket.error() -15 |-raise mmap.error(1) - 15 |+raise OSError(1) -16 16 | raise select.error(1, 2) -17 17 | -18 18 | raise socket.error( +13 13 | raise resource.error +14 14 | +15 |-raise socket.error() + 15 |+raise OSError() +16 16 | raise mmap.error(1) +17 17 | raise select.error(1, 2) +18 18 | raise resource.error(1, "strerror", "filename") UP024_2.py:16:7: UP024 [*] Replace aliased errors with `OSError` | -14 | raise socket.error() -15 | raise mmap.error(1) -16 | raise select.error(1, 2) +15 | raise socket.error() +16 | raise mmap.error(1) + | ^^^^^^^^^^ UP024 +17 | raise select.error(1, 2) +18 | raise resource.error(1, "strerror", "filename") + | + = help: Replace `mmap.error` with builtin `OSError` + +ℹ Safe fix +13 13 | raise resource.error +14 14 | +15 15 | raise socket.error() +16 |-raise mmap.error(1) + 16 |+raise OSError(1) +17 17 | raise select.error(1, 2) +18 18 | raise resource.error(1, "strerror", "filename") +19 19 | + +UP024_2.py:17:7: UP024 [*] Replace aliased errors with `OSError` + | +15 | raise socket.error() +16 | raise mmap.error(1) +17 | raise select.error(1, 2) | ^^^^^^^^^^^^ UP024 -17 | -18 | raise socket.error( +18 | raise resource.error(1, "strerror", "filename") | = help: Replace `select.error` with builtin `OSError` ℹ Safe fix -13 13 | -14 14 | raise socket.error() -15 15 | raise mmap.error(1) -16 |-raise select.error(1, 2) - 16 |+raise OSError(1, 2) -17 17 | -18 18 | raise socket.error( -19 19 | 1, +14 14 | +15 15 | raise socket.error() +16 16 | raise mmap.error(1) +17 |-raise select.error(1, 2) + 17 |+raise OSError(1, 2) +18 18 | raise resource.error(1, "strerror", "filename") +19 19 | +20 20 | raise socket.error( UP024_2.py:18:7: UP024 [*] Replace aliased errors with `OSError` | -16 | raise select.error(1, 2) -17 | -18 | raise socket.error( +16 | raise mmap.error(1) +17 | raise select.error(1, 2) +18 | raise resource.error(1, "strerror", "filename") + | ^^^^^^^^^^^^^^ UP024 +19 | +20 | raise socket.error( + | + = help: Replace `resource.error` with builtin `OSError` + +ℹ Safe fix +15 15 | raise socket.error() +16 16 | raise mmap.error(1) +17 17 | raise select.error(1, 2) +18 |-raise resource.error(1, "strerror", "filename") + 18 |+raise OSError(1, "strerror", "filename") +19 19 | +20 20 | raise socket.error( +21 21 | 1, + +UP024_2.py:20:7: UP024 [*] Replace aliased errors with `OSError` + | +18 | raise resource.error(1, "strerror", "filename") +19 | +20 | raise socket.error( | ^^^^^^^^^^^^ UP024 -19 | 1, -20 | 2, +21 | 1, +22 | 2, | = help: Replace `socket.error` with builtin `OSError` ℹ Safe fix -15 15 | raise mmap.error(1) -16 16 | raise select.error(1, 2) -17 17 | -18 |-raise socket.error( - 18 |+raise OSError( -19 19 | 1, -20 20 | 2, -21 21 | 3, - -UP024_2.py:25:7: UP024 [*] Replace aliased errors with `OSError` - | -24 | from mmap import error -25 | raise error +17 17 | raise select.error(1, 2) +18 18 | raise resource.error(1, "strerror", "filename") +19 19 | +20 |-raise socket.error( + 20 |+raise OSError( +21 21 | 1, +22 22 | 2, +23 23 | 3, + +UP024_2.py:27:7: UP024 [*] Replace aliased errors with `OSError` + | +26 | from mmap import error +27 | raise error | ^^^^^ UP024 -26 | -27 | from socket import error +28 | +29 | from socket import error | = help: Replace `error` with builtin `OSError` ℹ Safe fix -22 22 | ) -23 23 | -24 24 | from mmap import error -25 |-raise error - 25 |+raise OSError -26 26 | -27 27 | from socket import error -28 28 | raise error(1) - -UP024_2.py:28:7: UP024 [*] Replace aliased errors with `OSError` - | -27 | from socket import error -28 | raise error(1) +24 24 | ) +25 25 | +26 26 | from mmap import error +27 |-raise error + 27 |+raise OSError +28 28 | +29 29 | from socket import error +30 30 | raise error(1) + +UP024_2.py:30:7: UP024 [*] Replace aliased errors with `OSError` + | +29 | from socket import error +30 | raise error(1) | ^^^^^ UP024 -29 | -30 | from select import error +31 | +32 | from select import error | = help: Replace `error` with builtin `OSError` ℹ Safe fix -25 25 | raise error -26 26 | -27 27 | from socket import error -28 |-raise error(1) - 28 |+raise OSError(1) -29 29 | -30 30 | from select import error -31 31 | raise error(1, 2) - -UP024_2.py:31:7: UP024 [*] Replace aliased errors with `OSError` - | -30 | from select import error -31 | raise error(1, 2) +27 27 | raise error +28 28 | +29 29 | from socket import error +30 |-raise error(1) + 30 |+raise OSError(1) +31 31 | +32 32 | from select import error +33 33 | raise error(1, 2) + +UP024_2.py:33:7: UP024 [*] Replace aliased errors with `OSError` + | +32 | from select import error +33 | raise error(1, 2) | ^^^^^ UP024 -32 | -33 | # Testing the names +34 | +35 | from resource import error | = help: Replace `error` with builtin `OSError` ℹ Safe fix -28 28 | raise error(1) -29 29 | -30 30 | from select import error -31 |-raise error(1, 2) - 31 |+raise OSError(1, 2) -32 32 | -33 33 | # Testing the names -34 34 | raise EnvironmentError - -UP024_2.py:34:7: UP024 [*] Replace aliased errors with `OSError` - | -33 | # Testing the names -34 | raise EnvironmentError +30 30 | raise error(1) +31 31 | +32 32 | from select import error +33 |-raise error(1, 2) + 33 |+raise OSError(1, 2) +34 34 | +35 35 | from resource import error +36 36 | raise error(1, "strerror", "filename") + +UP024_2.py:36:7: UP024 [*] Replace aliased errors with `OSError` + | +35 | from resource import error +36 | raise error(1, "strerror", "filename") + | ^^^^^ UP024 +37 | +38 | # Testing the names + | + = help: Replace `error` with builtin `OSError` + +ℹ Safe fix +33 33 | raise error(1, 2) +34 34 | +35 35 | from resource import error +36 |-raise error(1, "strerror", "filename") + 36 |+raise OSError(1, "strerror", "filename") +37 37 | +38 38 | # Testing the names +39 39 | raise EnvironmentError + +UP024_2.py:39:7: UP024 [*] Replace aliased errors with `OSError` + | +38 | # Testing the names +39 | raise EnvironmentError | ^^^^^^^^^^^^^^^^ UP024 -35 | raise IOError -36 | raise WindowsError +40 | raise IOError +41 | raise WindowsError | = help: Replace `EnvironmentError` with builtin `OSError` ℹ Safe fix -31 31 | raise error(1, 2) -32 32 | -33 33 | # Testing the names -34 |-raise EnvironmentError - 34 |+raise OSError -35 35 | raise IOError -36 36 | raise WindowsError +36 36 | raise error(1, "strerror", "filename") 37 37 | +38 38 | # Testing the names +39 |-raise EnvironmentError + 39 |+raise OSError +40 40 | raise IOError +41 41 | raise WindowsError +42 42 | -UP024_2.py:35:7: UP024 [*] Replace aliased errors with `OSError` +UP024_2.py:40:7: UP024 [*] Replace aliased errors with `OSError` | -33 | # Testing the names -34 | raise EnvironmentError -35 | raise IOError +38 | # Testing the names +39 | raise EnvironmentError +40 | raise IOError | ^^^^^^^ UP024 -36 | raise WindowsError +41 | raise WindowsError | = help: Replace `IOError` with builtin `OSError` ℹ Safe fix -32 32 | -33 33 | # Testing the names -34 34 | raise EnvironmentError -35 |-raise IOError - 35 |+raise OSError -36 36 | raise WindowsError 37 37 | -38 38 | raise EnvironmentError() - -UP024_2.py:36:7: UP024 [*] Replace aliased errors with `OSError` - | -34 | raise EnvironmentError -35 | raise IOError -36 | raise WindowsError +38 38 | # Testing the names +39 39 | raise EnvironmentError +40 |-raise IOError + 40 |+raise OSError +41 41 | raise WindowsError +42 42 | +43 43 | raise EnvironmentError() + +UP024_2.py:41:7: UP024 [*] Replace aliased errors with `OSError` + | +39 | raise EnvironmentError +40 | raise IOError +41 | raise WindowsError | ^^^^^^^^^^^^ UP024 -37 | -38 | raise EnvironmentError() +42 | +43 | raise EnvironmentError() | = help: Replace `WindowsError` with builtin `OSError` ℹ Safe fix -33 33 | # Testing the names -34 34 | raise EnvironmentError -35 35 | raise IOError -36 |-raise WindowsError - 36 |+raise OSError -37 37 | -38 38 | raise EnvironmentError() -39 39 | raise IOError(1) - -UP024_2.py:38:7: UP024 [*] Replace aliased errors with `OSError` - | -36 | raise WindowsError -37 | -38 | raise EnvironmentError() +38 38 | # Testing the names +39 39 | raise EnvironmentError +40 40 | raise IOError +41 |-raise WindowsError + 41 |+raise OSError +42 42 | +43 43 | raise EnvironmentError() +44 44 | raise IOError(1) + +UP024_2.py:43:7: UP024 [*] Replace aliased errors with `OSError` + | +41 | raise WindowsError +42 | +43 | raise EnvironmentError() | ^^^^^^^^^^^^^^^^ UP024 -39 | raise IOError(1) -40 | raise WindowsError(1, 2) +44 | raise IOError(1) +45 | raise WindowsError(1, 2) | = help: Replace `EnvironmentError` with builtin `OSError` ℹ Safe fix -35 35 | raise IOError -36 36 | raise WindowsError -37 37 | -38 |-raise EnvironmentError() - 38 |+raise OSError() -39 39 | raise IOError(1) -40 40 | raise WindowsError(1, 2) -41 41 | - -UP024_2.py:39:7: UP024 [*] Replace aliased errors with `OSError` - | -38 | raise EnvironmentError() -39 | raise IOError(1) +40 40 | raise IOError +41 41 | raise WindowsError +42 42 | +43 |-raise EnvironmentError() + 43 |+raise OSError() +44 44 | raise IOError(1) +45 45 | raise WindowsError(1, 2) +46 46 | + +UP024_2.py:44:7: UP024 [*] Replace aliased errors with `OSError` + | +43 | raise EnvironmentError() +44 | raise IOError(1) | ^^^^^^^ UP024 -40 | raise WindowsError(1, 2) +45 | raise WindowsError(1, 2) | = help: Replace `IOError` with builtin `OSError` ℹ Safe fix -36 36 | raise WindowsError -37 37 | -38 38 | raise EnvironmentError() -39 |-raise IOError(1) - 39 |+raise OSError(1) -40 40 | raise WindowsError(1, 2) -41 41 | -42 42 | raise EnvironmentError( - -UP024_2.py:40:7: UP024 [*] Replace aliased errors with `OSError` - | -38 | raise EnvironmentError() -39 | raise IOError(1) -40 | raise WindowsError(1, 2) +41 41 | raise WindowsError +42 42 | +43 43 | raise EnvironmentError() +44 |-raise IOError(1) + 44 |+raise OSError(1) +45 45 | raise WindowsError(1, 2) +46 46 | +47 47 | raise EnvironmentError( + +UP024_2.py:45:7: UP024 [*] Replace aliased errors with `OSError` + | +43 | raise EnvironmentError() +44 | raise IOError(1) +45 | raise WindowsError(1, 2) | ^^^^^^^^^^^^ UP024 -41 | -42 | raise EnvironmentError( +46 | +47 | raise EnvironmentError( | = help: Replace `WindowsError` with builtin `OSError` ℹ Safe fix -37 37 | -38 38 | raise EnvironmentError() -39 39 | raise IOError(1) -40 |-raise WindowsError(1, 2) - 40 |+raise OSError(1, 2) -41 41 | -42 42 | raise EnvironmentError( -43 43 | 1, - -UP024_2.py:42:7: UP024 [*] Replace aliased errors with `OSError` - | -40 | raise WindowsError(1, 2) -41 | -42 | raise EnvironmentError( +42 42 | +43 43 | raise EnvironmentError() +44 44 | raise IOError(1) +45 |-raise WindowsError(1, 2) + 45 |+raise OSError(1, 2) +46 46 | +47 47 | raise EnvironmentError( +48 48 | 1, + +UP024_2.py:47:7: UP024 [*] Replace aliased errors with `OSError` + | +45 | raise WindowsError(1, 2) +46 | +47 | raise EnvironmentError( | ^^^^^^^^^^^^^^^^ UP024 -43 | 1, -44 | 2, +48 | 1, +49 | 2, | = help: Replace `EnvironmentError` with builtin `OSError` ℹ Safe fix -39 39 | raise IOError(1) -40 40 | raise WindowsError(1, 2) -41 41 | -42 |-raise EnvironmentError( - 42 |+raise OSError( -43 43 | 1, -44 44 | 2, -45 45 | 3, - -UP024_2.py:48:7: UP024 [*] Replace aliased errors with `OSError` - | -46 | ) -47 | -48 | raise WindowsError +44 44 | raise IOError(1) +45 45 | raise WindowsError(1, 2) +46 46 | +47 |-raise EnvironmentError( + 47 |+raise OSError( +48 48 | 1, +49 49 | 2, +50 50 | 3, + +UP024_2.py:53:7: UP024 [*] Replace aliased errors with `OSError` + | +51 | ) +52 | +53 | raise WindowsError | ^^^^^^^^^^^^ UP024 -49 | raise EnvironmentError(1) -50 | raise IOError(1, 2) +54 | raise EnvironmentError(1) +55 | raise IOError(1, 2) | = help: Replace `WindowsError` with builtin `OSError` ℹ Safe fix -45 45 | 3, -46 46 | ) -47 47 | -48 |-raise WindowsError - 48 |+raise OSError -49 49 | raise EnvironmentError(1) -50 50 | raise IOError(1, 2) - -UP024_2.py:49:7: UP024 [*] Replace aliased errors with `OSError` - | -48 | raise WindowsError -49 | raise EnvironmentError(1) +50 50 | 3, +51 51 | ) +52 52 | +53 |-raise WindowsError + 53 |+raise OSError +54 54 | raise EnvironmentError(1) +55 55 | raise IOError(1, 2) + +UP024_2.py:54:7: UP024 [*] Replace aliased errors with `OSError` + | +53 | raise WindowsError +54 | raise EnvironmentError(1) | ^^^^^^^^^^^^^^^^ UP024 -50 | raise IOError(1, 2) +55 | raise IOError(1, 2) | = help: Replace `EnvironmentError` with builtin `OSError` ℹ Safe fix -46 46 | ) -47 47 | -48 48 | raise WindowsError -49 |-raise EnvironmentError(1) - 49 |+raise OSError(1) -50 50 | raise IOError(1, 2) - -UP024_2.py:50:7: UP024 [*] Replace aliased errors with `OSError` - | -48 | raise WindowsError -49 | raise EnvironmentError(1) -50 | raise IOError(1, 2) +51 51 | ) +52 52 | +53 53 | raise WindowsError +54 |-raise EnvironmentError(1) + 54 |+raise OSError(1) +55 55 | raise IOError(1, 2) + +UP024_2.py:55:7: UP024 [*] Replace aliased errors with `OSError` + | +53 | raise WindowsError +54 | raise EnvironmentError(1) +55 | raise IOError(1, 2) | ^^^^^^^ UP024 | = help: Replace `IOError` with builtin `OSError` ℹ Safe fix -47 47 | -48 48 | raise WindowsError -49 49 | raise EnvironmentError(1) -50 |-raise IOError(1, 2) - 50 |+raise OSError(1, 2) +52 52 | +53 53 | raise WindowsError +54 54 | raise EnvironmentError(1) +55 |-raise IOError(1, 2) + 55 |+raise OSError(1, 2) From 1e4377c9c63c1fc52f9fd1c7240c607e430c8784 Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Wed, 14 May 2025 17:07:11 +0200 Subject: [PATCH 087/487] [`ruff`] add fix safety section (`RUF007`) (#17755) The PR add the `fix safety` section for rule `RUF007` (#15584 ) It seems that the fix was always marked as unsafe #14401 ## Unsafety example This first example is a little extreme. In fact, the class `Foo` overrides the `__getitem__` method but in a very special, way. The difference lies in the fact that `zip(letters, letters[1:])` call the slice `letters[1:]` which is behaving weird in this case, while `itertools.pairwise(letters)` call just `__getitem__(0), __getitem__(1), ...` and so on. Note that the diagnostic is emitted: [playground](https://play.ruff.rs) I don't know if we want to mention this problem, as there is a subtile bug in the python implementation of `Foo` which make the rule unsafe. ```python from dataclasses import dataclass import itertools @dataclass class Foo: letters: str def __getitem__(self, index): return self.letters[index] + "_foo" letters = Foo("ABCD") zip_ = zip(letters, letters[1:]) for a, b in zip_: print(a, b) # A_foo B, B_foo C, C_foo D, D_foo _ pair = itertools.pairwise(letters) for a, b in pair: print(a, b) # A_foo B_foo, B_foo C_foo, C_foo D_foo ``` This other example is much probable. here, `itertools.pairwise` was shadowed by a costume function [(playground)](https://play.ruff.rs) ```python from dataclasses import dataclass from itertools import pairwise def pairwise(a): return [] letters = "ABCD" zip_ = zip(letters, letters[1:]) print([(a, b) for a, b in zip_]) # [('A', 'B'), ('B', 'C'), ('C', 'D')] pair = pairwise(letters) print(pair) # [] ``` --- .../src/rules/ruff/rules/zip_instead_of_pairwise.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs index 9a473eb32b7c68..286560187a7807 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs @@ -29,6 +29,14 @@ use crate::{checkers::ast::Checker, importer::ImportRequest}; /// pairwise(letters) # ("A", "B"), ("B", "C"), ("C", "D") /// ``` /// +/// ## Fix safety +/// +/// The fix is always marked unsafe because it assumes that slicing an object +/// (e.g., `obj[1:]`) produces a value with the same type and iteration behavior +/// as the original object, which is not guaranteed for user-defined types that +/// override `__getitem__` without properly handling slices. Moreover, the fix +/// could delete comments. +/// /// ## References /// - [Python documentation: `itertools.pairwise`](https://docs.python.org/3/library/itertools.html#itertools.pairwise) #[derive(ViolationMetadata)] From 2e94d372759379289905ad985439484f28eff074 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Wed, 14 May 2025 23:10:15 +0800 Subject: [PATCH 088/487] [`airflow`] Get rid of `Replacement::Name` and replace them with `Replacement::AutoImport` for enabling auto fixing (`AIR301`, `AIR311`) (#17941) ## Summary Similiar to https://github.com/astral-sh/ruff/pull/17941. `Replacement::Name` was designed for linting only. Now, we also want to fix the user code. It would be easier to replace it with a better AutoImport struct whenever possible. On the other hand, `AIR301` and `AIR311` contain attribute changes that can still use a struct like `Replacement::Name`. To reduce the confusion, I also updated it as `Replacement::AttrName` Some of the original `Replacement::Name` has been replaced as `Replacement::Message` as they're not directly mapping and the message has now been moved to `help` ## Test Plan The test fixtures have been updated --- .../test/fixtures/airflow/AIR301_names.py | 26 +- .../ruff_linter/src/rules/airflow/helpers.rs | 9 +- .../src/rules/airflow/rules/removal_in_3.rs | 164 +++--- .../airflow/rules/suggested_to_update_3_0.rs | 17 +- ...ests__AIR301_AIR301_airflow_plugin.py.snap | 12 +- ...airflow__tests__AIR301_AIR301_args.py.snap | 3 +- ...irflow__tests__AIR301_AIR301_names.py.snap | 542 ++++++++++-------- 7 files changed, 428 insertions(+), 345 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py index 3d5018719cc998..73884a5ae6888a 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py @@ -24,9 +24,6 @@ from airflow.datasets import DatasetAliasEvent from airflow.hooks.base_hook import BaseHook from airflow.operators.subdag import SubDagOperator -from airflow.providers.mysql.datasets import mysql -from airflow.providers.postgres.datasets import postgres -from airflow.providers.trino.datasets import trino from airflow.secrets.local_filesystem import LocalFilesystemBackend from airflow.sensors.base_sensor_operator import BaseSensorOperator from airflow.triggers.external_task import TaskStateTrigger @@ -78,14 +75,6 @@ # airflow.operators.subdag.* SubDagOperator() -# airflow.providers.mysql -mysql.sanitize_uri - -# airflow.providers.postgres -postgres.sanitize_uri - -# airflow.providers.trino -trino.sanitize_uri # airflow.secrets # get_connection @@ -155,3 +144,18 @@ from airflow.operators.python import get_current_context get_current_context() + +# airflow.providers.mysql +from airflow.providers.mysql.datasets.mysql import sanitize_uri + +sanitize_uri + +# airflow.providers.postgres +from airflow.providers.postgres.datasets.postgres import sanitize_uri + +sanitize_uri + +# airflow.providers.trino +from airflow.providers.trino.datasets.trino import sanitize_uri + +sanitize_uri diff --git a/crates/ruff_linter/src/rules/airflow/helpers.rs b/crates/ruff_linter/src/rules/airflow/helpers.rs index fb6f3b9ff5eb19..54a4d2bda574b6 100644 --- a/crates/ruff_linter/src/rules/airflow/helpers.rs +++ b/crates/ruff_linter/src/rules/airflow/helpers.rs @@ -8,13 +8,20 @@ use ruff_python_semantic::SemanticModel; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum Replacement { + // There's no replacement or suggestion other than removal None, - Name(&'static str), + // The attribute name of a class has been changed. + AttrName(&'static str), + // Additional information. Used when there's replacement but they're not direct mapping. Message(&'static str), + // Symbols updated in Airflow 3 with replacement + // e.g., `airflow.datasets.Dataset` to `airflow.sdk.Asset` AutoImport { module: &'static str, name: &'static str, }, + // Symbols updated in Airflow 3 with only module changed. Used when we want to match multiple names. + // e.g., `airflow.configuration.as_dict | get` to `airflow.configuration.conf.as_dict | get` SourceModuleMoved { module: &'static str, name: String, diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index 66b88c0f407578..b6cc71f5a233e0 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -57,28 +57,27 @@ impl Violation for Airflow3Removal { } = self; match replacement { Replacement::None - | Replacement::Name(_) + | Replacement::AttrName(_) + | Replacement::Message(_) | Replacement::AutoImport { module: _, name: _ } | Replacement::SourceModuleMoved { module: _, name: _ } => { format!("`{deprecated}` is removed in Airflow 3.0") } - Replacement::Message(message) => { - format!("`{deprecated}` is removed in Airflow 3.0; {message}") - } } } fn fix_title(&self) -> Option { let Airflow3Removal { replacement, .. } = self; match replacement { - Replacement::Name(name) => Some(format!("Use `{name}` instead")), + Replacement::None => None, + Replacement::AttrName(name) => Some(format!("Use `{name}` instead")), + Replacement::Message(message) => Some((*message).to_string()), Replacement::AutoImport { module, name } => { Some(format!("Use `{module}.{name}` instead")) } Replacement::SourceModuleMoved { module, name } => { Some(format!("Use `{module}.{name}` instead")) } - _ => None, } } } @@ -278,22 +277,22 @@ fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) { let replacement = match *qualname.segments() { ["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() { - "dataset_factories" => Replacement::Name("asset_factories"), - "dataset_uri_handlers" => Replacement::Name("asset_uri_handlers"), + "dataset_factories" => Replacement::AttrName("asset_factories"), + "dataset_uri_handlers" => Replacement::AttrName("asset_uri_handlers"), "dataset_to_openlineage_converters" => { - Replacement::Name("asset_to_openlineage_converters") + Replacement::AttrName("asset_to_openlineage_converters") } _ => return, }, ["airflow", "lineage", "hook", "DatasetLineageInfo"] => match attr.as_str() { - "dataset" => Replacement::Name("asset"), + "dataset" => Replacement::AttrName("asset"), _ => return, }, _ => return, }; // Create the `Fix` first to avoid cloning `Replacement`. - let fix = if let Replacement::Name(name) = replacement { + let fix = if let Replacement::AttrName(name) = replacement { Some(Fix::safe_edit(Edit::range_replacement( name.to_string(), attr.range(), @@ -466,52 +465,52 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) { let replacement = match qualname.segments() { ["airflow", "datasets", "manager", "DatasetManager"] => match attr.as_str() { - "register_dataset_change" => Replacement::Name("register_asset_change"), - "create_datasets" => Replacement::Name("create_assets"), - "notify_dataset_created" => Replacement::Name("notify_asset_created"), - "notify_dataset_changed" => Replacement::Name("notify_asset_changed"), - "notify_dataset_alias_created" => Replacement::Name("notify_asset_alias_created"), + "register_dataset_change" => Replacement::AttrName("register_asset_change"), + "create_datasets" => Replacement::AttrName("create_assets"), + "notify_dataset_created" => Replacement::AttrName("notify_asset_created"), + "notify_dataset_changed" => Replacement::AttrName("notify_asset_changed"), + "notify_dataset_alias_created" => Replacement::AttrName("notify_asset_alias_created"), _ => return, }, ["airflow", "lineage", "hook", "HookLineageCollector"] => match attr.as_str() { - "create_dataset" => Replacement::Name("create_asset"), - "add_input_dataset" => Replacement::Name("add_input_asset"), - "add_output_dataset" => Replacement::Name("add_output_asset"), - "collected_datasets" => Replacement::Name("collected_assets"), + "create_dataset" => Replacement::AttrName("create_asset"), + "add_input_dataset" => Replacement::AttrName("add_input_asset"), + "add_output_dataset" => Replacement::AttrName("add_output_asset"), + "collected_datasets" => Replacement::AttrName("collected_assets"), _ => return, }, ["airflow", "providers", "amazon", "auth_manager", "aws_auth_manager", "AwsAuthManager"] => { match attr.as_str() { - "is_authorized_dataset" => Replacement::Name("is_authorized_asset"), + "is_authorized_dataset" => Replacement::AttrName("is_authorized_asset"), _ => return, } } ["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() { "initialize_providers_dataset_uri_resources" => { - Replacement::Name("initialize_providers_asset_uri_resources") + Replacement::AttrName("initialize_providers_asset_uri_resources") } _ => return, }, ["airflow", "secrets", "local_filesystem", "LocalFilesystemBackend"] => match attr.as_str() { - "get_connections" => Replacement::Name("get_connection"), + "get_connections" => Replacement::AttrName("get_connection"), _ => return, }, ["airflow", "datasets", ..] | ["airflow", "Dataset"] => match attr.as_str() { - "iter_datasets" => Replacement::Name("iter_assets"), - "iter_dataset_aliases" => Replacement::Name("iter_asset_aliases"), + "iter_datasets" => Replacement::AttrName("iter_assets"), + "iter_dataset_aliases" => Replacement::AttrName("iter_asset_aliases"), _ => return, }, segments => { if is_airflow_secret_backend(segments) { match attr.as_str() { - "get_conn_uri" => Replacement::Name("get_conn_value"), - "get_connections" => Replacement::Name("get_connection"), + "get_conn_uri" => Replacement::AttrName("get_conn_value"), + "get_connections" => Replacement::AttrName("get_connection"), _ => return, } } else if is_airflow_hook(segments) { match attr.as_str() { - "get_connections" => Replacement::Name("get_connection"), + "get_connections" => Replacement::AttrName("get_connection"), _ => return, } } else { @@ -520,7 +519,7 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) { } }; // Create the `Fix` first to avoid cloning `Replacement`. - let fix = if let Replacement::Name(name) = replacement { + let fix = if let Replacement::AttrName(name) = replacement { Some(Fix::safe_edit(Edit::range_replacement( name.to_string(), attr.range(), @@ -566,12 +565,12 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { let replacement = match qualified_name.segments() { // airflow.PY\d{1,2} ["airflow", "PY36" | "PY37" | "PY38" | "PY39" | "PY310" | "PY311" | "PY312"] => { - Replacement::Name("sys.version_info") + Replacement::Message("Use `sys.version_info` instead") } // airflow.api_connexion.security ["airflow", "api_connexion", "security", "requires_access"] => { - Replacement::Name("airflow.api_connexion.security.requires_access_*") + Replacement::Message("Use `airflow.api_connexion.security.requires_access_*` instead") } ["airflow", "api_connexion", "security", "requires_access_dataset"] => { Replacement::AutoImport { @@ -626,9 +625,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { ["airflow", "datasets", "DatasetAliasEvent"] => Replacement::None, // airflow.hooks - ["airflow", "hooks", "base_hook", "BaseHook"] => { - Replacement::Name("airflow.hooks.base.BaseHook") - } + ["airflow", "hooks", "base_hook", "BaseHook"] => Replacement::AutoImport { + module: "airflow.hooks.base", + name: "BaseHook", + }, // airflow.lineage.hook ["airflow", "lineage", "hook", "DatasetLineageInfo"] => Replacement::AutoImport { @@ -664,9 +664,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { }, // airflow.notifications - ["airflow", "notifications", "basenotifier", "BaseNotifier"] => { - Replacement::Name("airflow.sdk.BaseNotifier") - } + ["airflow", "notifications", "basenotifier", "BaseNotifier"] => Replacement::AutoImport { + module: "airflow.sdk", + name: "BaseNotifier", + }, // airflow.operators ["airflow", "operators", "subdag", ..] => { @@ -691,7 +692,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { // airflow.sensors ["airflow", "sensors", "base_sensor_operator", "BaseSensorOperator"] => { - Replacement::Name("airflow.sdk.bases.sensor.BaseSensorOperator") + Replacement::AutoImport { + module: "airflow.sdk.bases.sensor", + name: "BaseSensorOperator", + } } // airflow.timetables @@ -720,23 +724,36 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { // airflow.utils.dates ["dates", "date_range"] => Replacement::None, - ["dates", "days_ago"] => Replacement::Name("pendulum.today('UTC').add(days=-N, ...)"), + ["dates", "days_ago"] => { + Replacement::Message("Use `pendulum.today('UTC').add(days=-N, ...)` instead") + } ["dates", "parse_execution_date" | "round_time" | "scale_time_units" | "infer_time_unit"] => { Replacement::None } // airflow.utils.file - ["file", "TemporaryDirectory"] => Replacement::Name("tempfile.TemporaryDirectory"), - ["file", "mkdirs"] => Replacement::Name("pathlib.Path({path}).mkdir"), + ["file", "TemporaryDirectory"] => Replacement::AutoImport { + module: "tempfile", + name: "TemporaryDirectory", + }, + ["file", "mkdirs"] => Replacement::Message("Use `pathlib.Path({path}).mkdir` instead"), // airflow.utils.helpers - ["helpers", "chain"] => Replacement::Name("airflow.sdk.chain"), - ["helpers", "cross_downstream"] => Replacement::Name("airflow.sdk.cross_downstream"), + ["helpers", "chain"] => Replacement::AutoImport { + module: "airflow.sdk", + name: "chain", + }, + ["helpers", "cross_downstream"] => Replacement::AutoImport { + module: "airflow.sdk", + name: "cross_downstream", + }, + // TODO: update it as SourceModuleMoved // airflow.utils.log.secrets_masker - ["log", "secrets_masker"] => { - Replacement::Name("airflow.sdk.execution_time.secrets_masker") - } + ["log", "secrets_masker"] => Replacement::AutoImport { + module: "airflow.sdk.execution_time", + name: "secrets_masker", + }, // airflow.utils.state ["state", "SHUTDOWN" | "terminating_states"] => Replacement::None, @@ -751,18 +768,20 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { // airflow.www // TODO: www has been removed ["airflow", "www", "auth", "has_access"] => { - Replacement::Name("airflow.www.auth.has_access_*") + Replacement::Message("Use `airflow.www.auth.has_access_*` instead") } ["airflow", "www", "auth", "has_access_dataset"] => Replacement::AutoImport { module: "airflow.www.auth", name: "has_access_asset", }, - ["airflow", "www", "utils", "get_sensitive_variables_fields"] => { - Replacement::Name("airflow.utils.log.secrets_masker.get_sensitive_variables_fields") - } - ["airflow", "www", "utils", "should_hide_value_for_key"] => { - Replacement::Name("airflow.utils.log.secrets_masker.should_hide_value_for_key") - } + ["airflow", "www", "utils", "get_sensitive_variables_fields"] => Replacement::AutoImport { + module: "airflow.utils.log.secrets_masker", + name: "get_sensitive_variables_fields", + }, + ["airflow", "www", "utils", "should_hide_value_for_key"] => Replacement::AutoImport { + module: "airflow.utils.log.secrets_masker", + name: "should_hide_value_for_key", + }, // airflow.providers.amazon ["airflow", "providers", "amazon", "aws", "datasets", "s3", rest] => match *rest { @@ -774,9 +793,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { module: "airflow.providers.amazon.aws.assets.s3", name: "convert_asset_to_openlineage", }, - "sanitize_uri" => { - Replacement::Name("airflow.providers.amazon.aws.assets.s3.sanitize_uri") - } + "sanitize_uri" => Replacement::AutoImport { + module: "airflow.providers.amazon.aws.assets.s3", + name: "sanitize_uri", + }, _ => return, }, ["airflow", "providers", "amazon", "aws", "auth_manager", "avp", "entities", "AvpEntities", "DATASET"] => { @@ -797,9 +817,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { module: "airflow.providers.common.io.assets.file", name: "convert_asset_to_openlineage", }, - "sanitize_uri" => { - Replacement::Name("airflow.providers.common.io.assets.file.sanitize_uri") - } + "sanitize_uri" => Replacement::AutoImport { + module: "airflow.providers.common.io.assets.file", + name: "sanitize_uri", + }, _ => return, }, @@ -826,20 +847,28 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { module: "airflow.providers.google.assets.gcs", name: "convert_asset_to_openlineage", }, - ["gcs", "sanitize_uri"] => { - Replacement::Name("airflow.providers.google.assets.gcs.sanitize_uri") - } + ["gcs", "sanitize_uri"] => Replacement::AutoImport { + module: "airflow.providers.google.assets.gcs", + name: "sanitize_uri", + }, + _ => return, }, // airflow.providers.mysql ["airflow", "providers", "mysql", "datasets", "mysql", "sanitize_uri"] => { - Replacement::Name("airflow.providers.mysql.assets.mysql.sanitize_uri") + Replacement::AutoImport { + module: "airflow.providers.mysql.assets.mysql", + name: "sanitize_uri", + } } // airflow.providers.postgres ["airflow", "providers", "postgres", "datasets", "postgres", "sanitize_uri"] => { - Replacement::Name("airflow.providers.postgres.assets.postgres.sanitize_uri") + Replacement::AutoImport { + module: "airflow.providers.postgres.assets.postgres", + name: "sanitize_uri", + } } // airflow.providers.openlineage @@ -859,7 +888,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { // airflow.providers.trino ["airflow", "providers", "trino", "datasets", "trino", "sanitize_uri"] => { - Replacement::Name("airflow.providers.trino.assets.trino.sanitize_uri") + Replacement::AutoImport { + module: "airflow.providers.trino.assets.trino", + name: "sanitize_uri", + } } _ => return, @@ -949,7 +981,7 @@ fn diagnostic_for_argument( Airflow3Removal { deprecated: deprecated.to_string(), replacement: match replacement { - Some(name) => Replacement::Name(name), + Some(name) => Replacement::AttrName(name), None => Replacement::None, }, }, diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index 05b68911b1ac3d..09cd9422cc4c90 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -53,7 +53,8 @@ impl Violation for Airflow3SuggestedUpdate { } = self; match replacement { Replacement::None - | Replacement::Name(_) + | Replacement::AttrName(_) + | Replacement::Message(_) | Replacement::AutoImport { module: _, name: _ } | Replacement::SourceModuleMoved { module: _, name: _ } => { format!( @@ -61,27 +62,21 @@ impl Violation for Airflow3SuggestedUpdate { It still works in Airflow 3.0 but is expected to be removed in a future version." ) } - Replacement::Message(message) => { - format!( - "`{deprecated}` is removed in Airflow 3.0; \ - It still works in Airflow 3.0 but is expected to be removed in a future version.; \ - {message}" - ) - } } } fn fix_title(&self) -> Option { let Airflow3SuggestedUpdate { replacement, .. } = self; match replacement { - Replacement::Name(name) => Some(format!("Use `{name}` instead")), + Replacement::None => None, + Replacement::AttrName(name) => Some(format!("Use `{name}` instead")), + Replacement::Message(message) => Some((*message).to_string()), Replacement::AutoImport { module, name } => { Some(format!("Use `{module}.{name}` instead")) } Replacement::SourceModuleMoved { module, name } => { Some(format!("Use `{module}.{name}` instead")) } - _ => None, } } } @@ -126,7 +121,7 @@ fn diagnostic_for_argument( Airflow3SuggestedUpdate { deprecated: deprecated.to_string(), replacement: match replacement { - Some(name) => Replacement::Name(name), + Some(name) => Replacement::AttrName(name), None => Replacement::None, }, }, diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_airflow_plugin.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_airflow_plugin.py.snap index dc16e1812bb4e3..173877e2d8aa2f 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_airflow_plugin.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_airflow_plugin.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This extension should just be imported as a regular python module. +AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0 | 5 | name = "test_plugin" 6 | # --- Invalid extensions start @@ -10,8 +10,9 @@ AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This 8 | sensors = [PluginSensorOperator] 9 | hooks = [PluginHook] | + = help: This extension should just be imported as a regular python module. -AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This extension should just be imported as a regular python module. +AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0 | 6 | # --- Invalid extensions start 7 | operators = [PluginOperator] @@ -20,8 +21,9 @@ AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This e 9 | hooks = [PluginHook] 10 | executors = [PluginExecutor] | + = help: This extension should just be imported as a regular python module. -AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This extension should just be imported as a regular python module. +AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0 | 7 | operators = [PluginOperator] 8 | sensors = [PluginSensorOperator] @@ -30,8 +32,9 @@ AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This ext 10 | executors = [PluginExecutor] 11 | # --- Invalid extensions end | + = help: This extension should just be imported as a regular python module. -AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; This extension should just be imported as a regular python module. +AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0 | 8 | sensors = [PluginSensorOperator] 9 | hooks = [PluginHook] @@ -40,3 +43,4 @@ AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; Thi 11 | # --- Invalid extensions end 12 | macros = [plugin_macro] | + = help: This extension should just be imported as a regular python module. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap index 47318937312372..17ca7e2196105e 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap @@ -258,10 +258,11 @@ AIR301_args.py:90:16: AIR301 `filename_template` is removed in Airflow 3.0 92 | FabAuthManager(None) | -AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now +AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0 | 90 | GCSTaskHandler(filename_template="/tmp/test") 91 | 92 | FabAuthManager(None) | ^^^^^^ AIR301 | + = help: The constructor takes no parameter now diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap index a45ba0bce2e168..f64e07f81555d1 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap @@ -1,448 +1,488 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_names.py:56:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 +AIR301_names.py:53:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 +AIR301_names.py:53:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 +AIR301_names.py:53:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 +AIR301_names.py:53:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 +AIR301_names.py:53:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 +AIR301_names.py:53:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 +AIR301_names.py:53:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 | -55 | # airflow root -56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +52 | # airflow root +53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -57 | -58 | # airflow.api_connexion.security +54 | +55 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:59:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 +AIR301_names.py:56:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 | -58 | # airflow.api_connexion.security -59 | requires_access +55 | # airflow.api_connexion.security +56 | requires_access | ^^^^^^^^^^^^^^^ AIR301 | = help: Use `airflow.api_connexion.security.requires_access_*` instead -AIR301_names.py:63:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0 +AIR301_names.py:60:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^ AIR301 | = help: Use `airflow.configuration.conf.get` instead -AIR301_names.py:63:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0 +AIR301_names.py:60:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.getboolean` instead -AIR301_names.py:63:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0 +AIR301_names.py:60:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.getfloat` instead -AIR301_names.py:63:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0 +AIR301_names.py:60:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.getint` instead -AIR301_names.py:63:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0 +AIR301_names.py:60:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.has_option` instead -AIR301_names.py:63:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0 +AIR301_names.py:60:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.remove_option` instead -AIR301_names.py:63:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0 +AIR301_names.py:60:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^ AIR301 | = help: Use `airflow.configuration.conf.as_dict` instead -AIR301_names.py:63:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0 +AIR301_names.py:60:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0 | -62 | # airflow.configuration -63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +59 | # airflow.configuration +60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^ AIR301 | = help: Use `airflow.configuration.conf.set` instead -AIR301_names.py:67:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0; The whole `airflow.contrib` module has been removed. +AIR301_names.py:64:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0 | -66 | # airflow.contrib.* -67 | AWSAthenaHook() +63 | # airflow.contrib.* +64 | AWSAthenaHook() | ^^^^^^^^^^^^^ AIR301 | + = help: The whole `airflow.contrib` module has been removed. -AIR301_names.py:71:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 +AIR301_names.py:68:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 | -70 | # airflow.datasets -71 | DatasetAliasEvent() +67 | # airflow.datasets +68 | DatasetAliasEvent() | ^^^^^^^^^^^^^^^^^ AIR301 | -AIR301_names.py:75:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 +AIR301_names.py:72:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 | -74 | # airflow.hooks -75 | BaseHook() +71 | # airflow.hooks +72 | BaseHook() | ^^^^^^^^ AIR301 | = help: Use `airflow.hooks.base.BaseHook` instead -AIR301_names.py:79:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0; The whole `airflow.subdag` module has been removed. +AIR301_names.py:76:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0 | -78 | # airflow.operators.subdag.* -79 | SubDagOperator() +75 | # airflow.operators.subdag.* +76 | SubDagOperator() | ^^^^^^^^^^^^^^ AIR301 -80 | -81 | # airflow.providers.mysql | + = help: The whole `airflow.subdag` module has been removed. -AIR301_names.py:82:7: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:85:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 | -81 | # airflow.providers.mysql -82 | mysql.sanitize_uri - | ^^^^^^^^^^^^ AIR301 -83 | -84 | # airflow.providers.postgres +84 | # airflow.sensors.base_sensor_operator +85 | BaseSensorOperator() + | ^^^^^^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead + = help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead -AIR301_names.py:85:10: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:89:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 | -84 | # airflow.providers.postgres -85 | postgres.sanitize_uri - | ^^^^^^^^^^^^ AIR301 -86 | -87 | # airflow.providers.trino +88 | # airflow.triggers.external_task +89 | TaskStateTrigger() + | ^^^^^^^^^^^^^^^^ AIR301 +90 | +91 | # airflow.utils.date | - = help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead -AIR301_names.py:88:7: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:92:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 | -87 | # airflow.providers.trino -88 | trino.sanitize_uri - | ^^^^^^^^^^^^ AIR301 -89 | -90 | # airflow.secrets +91 | # airflow.utils.date +92 | dates.date_range + | ^^^^^^^^^^ AIR301 +93 | dates.days_ago | - = help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead -AIR301_names.py:96:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 +AIR301_names.py:93:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 | -95 | # airflow.sensors.base_sensor_operator -96 | BaseSensorOperator() - | ^^^^^^^^^^^^^^^^^^ AIR301 +91 | # airflow.utils.date +92 | dates.date_range +93 | dates.days_ago + | ^^^^^^^^ AIR301 +94 | +95 | date_range | - = help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead - -AIR301_names.py:100:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 - | - 99 | # airflow.triggers.external_task -100 | TaskStateTrigger() - | ^^^^^^^^^^^^^^^^ AIR301 -101 | -102 | # airflow.utils.date - | + = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:103:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 - | -102 | # airflow.utils.date -103 | dates.date_range - | ^^^^^^^^^^ AIR301 -104 | dates.days_ago - | - -AIR301_names.py:104:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 - | -102 | # airflow.utils.date -103 | dates.date_range -104 | dates.days_ago - | ^^^^^^^^ AIR301 -105 | -106 | date_range - | - = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead - -AIR301_names.py:106:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 - | -104 | dates.days_ago -105 | -106 | date_range - | ^^^^^^^^^^ AIR301 -107 | days_ago -108 | infer_time_unit - | +AIR301_names.py:95:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 + | +93 | dates.days_ago +94 | +95 | date_range + | ^^^^^^^^^^ AIR301 +96 | days_ago +97 | infer_time_unit + | -AIR301_names.py:107:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 - | -106 | date_range -107 | days_ago - | ^^^^^^^^ AIR301 -108 | infer_time_unit -109 | parse_execution_date - | - = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead +AIR301_names.py:96:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 + | +95 | date_range +96 | days_ago + | ^^^^^^^^ AIR301 +97 | infer_time_unit +98 | parse_execution_date + | + = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:108:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 - | -106 | date_range -107 | days_ago -108 | infer_time_unit - | ^^^^^^^^^^^^^^^ AIR301 -109 | parse_execution_date -110 | round_time - | +AIR301_names.py:97:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 + | +95 | date_range +96 | days_ago +97 | infer_time_unit + | ^^^^^^^^^^^^^^^ AIR301 +98 | parse_execution_date +99 | round_time + | -AIR301_names.py:109:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 +AIR301_names.py:98:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 | -107 | days_ago -108 | infer_time_unit -109 | parse_execution_date + 96 | days_ago + 97 | infer_time_unit + 98 | parse_execution_date | ^^^^^^^^^^^^^^^^^^^^ AIR301 -110 | round_time -111 | scale_time_units + 99 | round_time +100 | scale_time_units | -AIR301_names.py:110:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 +AIR301_names.py:99:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 | -108 | infer_time_unit -109 | parse_execution_date -110 | round_time + 97 | infer_time_unit + 98 | parse_execution_date + 99 | round_time | ^^^^^^^^^^ AIR301 -111 | scale_time_units +100 | scale_time_units | -AIR301_names.py:111:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 +AIR301_names.py:100:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 | -109 | parse_execution_date -110 | round_time -111 | scale_time_units + 98 | parse_execution_date + 99 | round_time +100 | scale_time_units | ^^^^^^^^^^^^^^^^ AIR301 -112 | -113 | # This one was not deprecated. +101 | +102 | # This one was not deprecated. | -AIR301_names.py:118:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 +AIR301_names.py:107:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 | -117 | # airflow.utils.dag_cycle_tester -118 | test_cycle +106 | # airflow.utils.dag_cycle_tester +107 | test_cycle | ^^^^^^^^^^ AIR301 | -AIR301_names.py:122:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 +AIR301_names.py:111:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 | -121 | # airflow.utils.db -122 | create_session +110 | # airflow.utils.db +111 | create_session | ^^^^^^^^^^^^^^ AIR301 -123 | -124 | # airflow.utils.decorators +112 | +113 | # airflow.utils.decorators | -AIR301_names.py:125:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0; `apply_defaults` is now unconditionally done and can be safely removed. +AIR301_names.py:114:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0 | -124 | # airflow.utils.decorators -125 | apply_defaults +113 | # airflow.utils.decorators +114 | apply_defaults | ^^^^^^^^^^^^^^ AIR301 -126 | -127 | # airflow.utils.file +115 | +116 | # airflow.utils.file | + = help: `apply_defaults` is now unconditionally done and can be safely removed. -AIR301_names.py:128:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 +AIR301_names.py:117:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 | -127 | # airflow.utils.file -128 | TemporaryDirectory() +116 | # airflow.utils.file +117 | TemporaryDirectory() | ^^^^^^^^^^^^^^^^^^ AIR301 -129 | mkdirs +118 | mkdirs | = help: Use `tempfile.TemporaryDirectory` instead -AIR301_names.py:129:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 +AIR301_names.py:118:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 | -127 | # airflow.utils.file -128 | TemporaryDirectory() -129 | mkdirs +116 | # airflow.utils.file +117 | TemporaryDirectory() +118 | mkdirs | ^^^^^^ AIR301 -130 | -131 | # airflow.utils.helpers +119 | +120 | # airflow.utils.helpers | = help: Use `pathlib.Path({path}).mkdir` instead -AIR301_names.py:132:1: AIR301 `airflow.utils.helpers.chain` is removed in Airflow 3.0 +AIR301_names.py:121:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0 | -131 | # airflow.utils.helpers -132 | helper_chain +120 | # airflow.utils.helpers +121 | helper_chain | ^^^^^^^^^^^^ AIR301 -133 | helper_cross_downstream +122 | helper_cross_downstream | = help: Use `airflow.sdk.chain` instead -AIR301_names.py:133:1: AIR301 `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 - | -131 | # airflow.utils.helpers -132 | helper_chain -133 | helper_cross_downstream +ℹ Safe fix +48 48 | from airflow.utils.trigger_rule import TriggerRule +49 49 | from airflow.www.auth import has_access +50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key + 51 |+from airflow.sdk import chain +51 52 | +52 53 | # airflow root +53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +-------------------------------------------------------------------------------- +118 119 | mkdirs +119 120 | +120 121 | # airflow.utils.helpers +121 |-helper_chain + 122 |+chain +122 123 | helper_cross_downstream +123 124 | +124 125 | # airflow.utils.log + +AIR301_names.py:122:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 + | +120 | # airflow.utils.helpers +121 | helper_chain +122 | helper_cross_downstream | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -134 | -135 | # airflow.utils.log +123 | +124 | # airflow.utils.log | = help: Use `airflow.sdk.cross_downstream` instead -AIR301_names.py:136:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 - | -135 | # airflow.utils.log -136 | secrets_masker +ℹ Safe fix +48 48 | from airflow.utils.trigger_rule import TriggerRule +49 49 | from airflow.www.auth import has_access +50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key + 51 |+from airflow.sdk import cross_downstream +51 52 | +52 53 | # airflow root +53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +-------------------------------------------------------------------------------- +119 120 | +120 121 | # airflow.utils.helpers +121 122 | helper_chain +122 |-helper_cross_downstream + 123 |+cross_downstream +123 124 | +124 125 | # airflow.utils.log +125 126 | secrets_masker + +AIR301_names.py:125:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 + | +124 | # airflow.utils.log +125 | secrets_masker | ^^^^^^^^^^^^^^ AIR301 -137 | -138 | # airflow.utils.state +126 | +127 | # airflow.utils.state | = help: Use `airflow.sdk.execution_time.secrets_masker` instead -AIR301_names.py:139:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 +AIR301_names.py:128:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 | -138 | # airflow.utils.state -139 | SHUTDOWN +127 | # airflow.utils.state +128 | SHUTDOWN | ^^^^^^^^ AIR301 -140 | terminating_states +129 | terminating_states | -AIR301_names.py:140:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 +AIR301_names.py:129:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 | -138 | # airflow.utils.state -139 | SHUTDOWN -140 | terminating_states +127 | # airflow.utils.state +128 | SHUTDOWN +129 | terminating_states | ^^^^^^^^^^^^^^^^^^ AIR301 -141 | -142 | # airflow.utils.trigger_rule +130 | +131 | # airflow.utils.trigger_rule | -AIR301_names.py:143:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 +AIR301_names.py:132:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 | -142 | # airflow.utils.trigger_rule -143 | TriggerRule.DUMMY +131 | # airflow.utils.trigger_rule +132 | TriggerRule.DUMMY | ^^^^^ AIR301 -144 | TriggerRule.NONE_FAILED_OR_SKIPPED +133 | TriggerRule.NONE_FAILED_OR_SKIPPED | -AIR301_names.py:144:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 +AIR301_names.py:133:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 | -142 | # airflow.utils.trigger_rule -143 | TriggerRule.DUMMY -144 | TriggerRule.NONE_FAILED_OR_SKIPPED +131 | # airflow.utils.trigger_rule +132 | TriggerRule.DUMMY +133 | TriggerRule.NONE_FAILED_OR_SKIPPED | ^^^^^^^^^^^^^^^^^^^^^^ AIR301 | -AIR301_names.py:148:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 +AIR301_names.py:137:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 | -147 | # airflow.www.auth -148 | has_access +136 | # airflow.www.auth +137 | has_access | ^^^^^^^^^^ AIR301 -149 | -150 | # airflow.www.utils +138 | +139 | # airflow.www.utils | = help: Use `airflow.www.auth.has_access_*` instead -AIR301_names.py:151:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 +AIR301_names.py:140:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 | -150 | # airflow.www.utils -151 | get_sensitive_variables_fields +139 | # airflow.www.utils +140 | get_sensitive_variables_fields | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -152 | should_hide_value_for_key +141 | should_hide_value_for_key | = help: Use `airflow.utils.log.secrets_masker.get_sensitive_variables_fields` instead -AIR301_names.py:152:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 +AIR301_names.py:141:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 | -150 | # airflow.www.utils -151 | get_sensitive_variables_fields -152 | should_hide_value_for_key +139 | # airflow.www.utils +140 | get_sensitive_variables_fields +141 | should_hide_value_for_key | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -153 | -154 | # airflow.operators.python +142 | +143 | # airflow.operators.python | = help: Use `airflow.utils.log.secrets_masker.should_hide_value_for_key` instead -AIR301_names.py:157:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0 +AIR301_names.py:146:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0 | -155 | from airflow.operators.python import get_current_context -156 | -157 | get_current_context() +144 | from airflow.operators.python import get_current_context +145 | +146 | get_current_context() | ^^^^^^^^^^^^^^^^^^^ AIR301 +147 | +148 | # airflow.providers.mysql | = help: Use `airflow.sdk.get_current_context` instead + +AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 + | +149 | from airflow.providers.mysql.datasets.mysql import sanitize_uri +150 | +151 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 +152 | +153 | # airflow.providers.postgres + | + = help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead + +AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 + | +154 | from airflow.providers.postgres.datasets.postgres import sanitize_uri +155 | +156 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 +157 | +158 | # airflow.providers.trino + | + = help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead + +AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 + | +159 | from airflow.providers.trino.datasets.trino import sanitize_uri +160 | +161 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 + | + = help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead From 0230cbac2c2b135a7ef7160fac1220ad4be2c3c2 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 13 May 2025 11:23:19 -0400 Subject: [PATCH 089/487] ty_python_semantic: update "no matching overload" diagnostic test It looks like support for `@overload` has been added since this test was created, so we remove the TODO and add a snippet (from #274). --- .../diagnostics/no_matching_overload.md | 16 ++++++----- ...aded_\342\200\246_(3553d085684e16a0).snap" | 27 ++++++++++++++----- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md index d0d3c916df9d2b..9dc6265df2bd8f 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md @@ -4,11 +4,15 @@ ## Calls to overloaded functions -TODO: Note that we do not yet support the `@overload` decorator to define overloaded functions in -real Python code. We are instead testing a special-cased function where we create an overloaded -signature internally. Update this to an `@overload` function in the Python snippet itself once we -can. - ```py -type("Foo", ()) # error: [no-matching-overload] +from typing import overload + +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: int | str) -> int | str: + return x + +f(b"foo") # error: [no-matching-overload] ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" index 4f3f262bcb6d82..28389124c967c3 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" @@ -12,18 +12,31 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_ ## mdtest_snippet.py ``` -1 | type("Foo", ()) # error: [no-matching-overload] + 1 | from typing import overload + 2 | + 3 | @overload + 4 | def f(x: int) -> int: ... + 5 | + 6 | @overload + 7 | def f(x: str) -> str: ... + 8 | + 9 | def f(x: int | str) -> int | str: +10 | return x +11 | +12 | f(b"foo") # error: [no-matching-overload] ``` # Diagnostics ``` -error[no-matching-overload]: No overload of class `type` matches arguments - --> src/mdtest_snippet.py:1:1 - | -1 | type("Foo", ()) # error: [no-matching-overload] - | ^^^^^^^^^^^^^^^ - | +error[no-matching-overload]: No overload of function `f` matches arguments + --> src/mdtest_snippet.py:12:1 + | +10 | return x +11 | +12 | f(b"foo") # error: [no-matching-overload] + | ^^^^^^^^^ + | info: rule `no-matching-overload` is enabled by default ``` From bd5b7f415fe333215f5d86b45447c378b4bf5c91 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 13 May 2025 11:37:11 -0400 Subject: [PATCH 090/487] ty_python_semantic: rejigger handling of overload error conditions I found the previous code somewhat harder to read. Namely, a `for` loop was being used to encode "execute zero or one times, but not more." Which is sometimes okay, but it seemed clearer to me to use more explicit case analysis here. This should have no behavioral changes. --- .../ty_python_semantic/src/types/call/bind.rs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d20c5e2faaf2da..db9a95190acb88 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1086,9 +1086,25 @@ impl<'db> CallableBinding<'db> { return; } - let callable_description = CallableDescription::new(context.db(), self.callable_type); - if self.overloads.len() > 1 { - if let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) { + match self.overloads.as_slice() { + [] => {} + [overload] => { + let callable_description = + CallableDescription::new(context.db(), self.signature_type); + overload.report_diagnostics( + context, + node, + self.signature_type, + callable_description.as_ref(), + union_diag, + ); + } + _overloads => { + let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) else { + return; + }; + let callable_description = + CallableDescription::new(context.db(), self.callable_type); let mut diag = builder.into_diagnostic(format_args!( "No overload{} matches arguments", if let Some(CallableDescription { kind, name }) = callable_description { @@ -1101,18 +1117,6 @@ impl<'db> CallableBinding<'db> { union_diag.add_union_context(context.db(), &mut diag); } } - return; - } - - let callable_description = CallableDescription::new(context.db(), self.signature_type); - for overload in &self.overloads { - overload.report_diagnostics( - context, - node, - self.signature_type, - callable_description.as_ref(), - union_diag, - ); } } } From 451c5db7a3d064816b8c2727fd1af6f92c9dd5cb Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 13 May 2025 12:37:19 -0400 Subject: [PATCH 091/487] ty_python_semantic: move some routines to `FunctionType` These are, after all, specific to function types. The methods on `Type` are more like conveniences that return something when the type *happens* to be a function. But defining them on `FunctionType` itself makes it easy to call them when you have a `FunctionType` instead of a `Type`. --- crates/ty_python_semantic/src/types.rs | 107 ++++++++++++++++++------- 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 1ffe70873a2fca..7853e35d2d6116 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5372,16 +5372,7 @@ impl<'db> Type<'db> { /// a diagnostic. fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> { match *self { - Type::FunctionLiteral(function) => { - let function_scope = function.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let return_type_range = func_def.returns.as_ref()?.range(); - let name_span = span.clone().with_range(func_def.name.range); - let return_type_span = span.with_range(return_type_range); - Some((name_span, return_type_span)) - } + Type::FunctionLiteral(function) => function.return_type_span(db), Type::BoundMethod(bound_method) => { Type::FunctionLiteral(bound_method.function(db)).return_type_span(db) } @@ -5418,24 +5409,7 @@ impl<'db> Type<'db> { parameter_index: Option, ) -> Option<(Span, Span)> { match *self { - Type::FunctionLiteral(function) => { - let function_scope = function.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let range = parameter_index - .and_then(|parameter_index| { - func_def - .parameters - .iter() - .nth(parameter_index) - .map(|param| param.range()) - }) - .unwrap_or(func_def.parameters.range); - let name_span = span.clone().with_range(func_def.name.range); - let parameter_span = span.with_range(range); - Some((name_span, parameter_span)) - } + Type::FunctionLiteral(function) => function.parameter_span(db, parameter_index), Type::BoundMethod(bound_method) => { Type::FunctionLiteral(bound_method.function(db)).parameter_span(db, parameter_index) } @@ -6919,6 +6893,83 @@ impl<'db> FunctionType<'db> { }) } } + + /// Returns a tuple of two spans. The first is + /// the span for the identifier of the function + /// definition for `self`. The second is + /// the span for the return type in the function + /// definition for `self`. + /// + /// If there are no meaningful spans, then this + /// returns `None`. For example, when this type + /// isn't callable or if the function has no + /// declared return type. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + let return_type_range = func_def.returns.as_ref()?.range(); + let name_span = span.clone().with_range(func_def.name.range); + let return_type_span = span.with_range(return_type_range); + Some((name_span, return_type_span)) + } + + /// Returns a tuple of two spans. The first is + /// the span for the identifier of the function + /// definition for `self`. The second is + /// the span for the parameter in the function + /// definition for `self`. + /// + /// If there are no meaningful spans, then this + /// returns `None`. For example, when this type + /// isn't callable. + /// + /// When `parameter_index` is `None`, then the + /// second span returned covers the entire parameter + /// list. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + fn parameter_span( + &self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + let range = parameter_index + .and_then(|parameter_index| { + func_def + .parameters + .iter() + .nth(parameter_index) + .map(|param| param.range()) + }) + .unwrap_or(func_def.parameters.range); + let name_span = span.clone().with_range(func_def.name.range); + let parameter_span = span.with_range(range); + Some((name_span, parameter_span)) + } } fn signature_cycle_recover<'db>( From faf54c01811821cea5c349b4120a311fc6479af3 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 13 May 2025 12:37:14 -0400 Subject: [PATCH 092/487] ty_python_semantic: improve failed overloaded function call The diagnostic now includes a pointer to the implementation definition along with each possible overload. This doesn't include information about *why* each overload failed. But given the emphasis on concise output (since there can be *many* unmatched overloads), it's not totally clear how to include that additional information. Fixes #274 --- .../diagnostics/no_matching_overload.md | 262 ++++++++++++++++++ ...n_wit\342\200\246_(dd80c593d9136f35).snap" | 120 ++++++++ ...n_wit\342\200\246_(f66e3a8a3977c472).snap" | 230 +++++++++++++++ ...aded_\342\200\246_(3553d085684e16a0).snap" | 43 ++- ...aded_\342\200\246_(36814b28492c01d2).snap" | 148 ++++++++++ crates/ty_python_semantic/src/types.rs | 132 +++++---- .../ty_python_semantic/src/types/call/bind.rs | 49 ++++ .../src/types/signatures.rs | 2 +- 8 files changed, 914 insertions(+), 72 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md index 9dc6265df2bd8f..c71e608e6ee145 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md @@ -16,3 +16,265 @@ def f(x: int | str) -> int | str: f(b"foo") # error: [no-matching-overload] ``` + +## Call to function with many unmatched overloads + +Note that it would be fine to use `pow` here as an example of a routine with many overloads, but at +time of writing (2025-05-14), ty doesn't support some of the type signatures of those overloads. +Which in turn makes snapshotting a bit annoying, since the output can depend on how ty is compiled +(because of how `Todo` types are dealt with when `debug_assertions` is enabled versus disabled). + +```py +from typing import overload + +class Foo: ... + +@overload +def foo(a: int, b: int, c: int): ... +@overload +def foo(a: str, b: int, c: int): ... +@overload +def foo(a: int, b: str, c: int): ... +@overload +def foo(a: int, b: int, c: str): ... +@overload +def foo(a: str, b: str, c: int): ... +@overload +def foo(a: int, b: str, c: str): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: int, b: int, c: int): ... +@overload +def foo(a: float, b: int, c: int): ... +@overload +def foo(a: int, b: float, c: int): ... +@overload +def foo(a: int, b: int, c: float): ... +@overload +def foo(a: float, b: float, c: int): ... +@overload +def foo(a: int, b: float, c: float): ... +@overload +def foo(a: float, b: float, c: float): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: float, b: str, c: str): ... +@overload +def foo(a: str, b: float, c: str): ... +@overload +def foo(a: str, b: str, c: float): ... +@overload +def foo(a: float, b: float, c: str): ... +@overload +def foo(a: str, b: float, c: float): ... +@overload +def foo(a: float, b: float, c: float): ... +def foo(a, b, c): ... + +foo(Foo(), Foo()) # error: [no-matching-overload] +``` + +## Call to function with too many unmatched overloads + +This is like the above example, but has an excessive number of overloads to the point that ty will +cut off the list in the diagnostic and emit a message stating the number of omitted overloads. + +```py +from typing import overload + +class Foo: ... + +@overload +def foo(a: int, b: int, c: int): ... +@overload +def foo(a: str, b: int, c: int): ... +@overload +def foo(a: int, b: str, c: int): ... +@overload +def foo(a: int, b: int, c: str): ... +@overload +def foo(a: str, b: str, c: int): ... +@overload +def foo(a: int, b: str, c: str): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: int, b: int, c: int): ... +@overload +def foo(a: float, b: int, c: int): ... +@overload +def foo(a: int, b: float, c: int): ... +@overload +def foo(a: int, b: int, c: float): ... +@overload +def foo(a: float, b: float, c: int): ... +@overload +def foo(a: int, b: float, c: float): ... +@overload +def foo(a: float, b: float, c: float): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: float, b: str, c: str): ... +@overload +def foo(a: str, b: float, c: str): ... +@overload +def foo(a: str, b: str, c: float): ... +@overload +def foo(a: float, b: float, c: str): ... +@overload +def foo(a: str, b: float, c: float): ... +@overload +def foo(a: float, b: float, c: float): ... +@overload +def foo(a: list[int], b: list[int], c: list[int]): ... +@overload +def foo(a: list[str], b: list[int], c: list[int]): ... +@overload +def foo(a: list[int], b: list[str], c: list[int]): ... +@overload +def foo(a: list[int], b: list[int], c: list[str]): ... +@overload +def foo(a: list[str], b: list[str], c: list[int]): ... +@overload +def foo(a: list[int], b: list[str], c: list[str]): ... +@overload +def foo(a: list[str], b: list[str], c: list[str]): ... +@overload +def foo(a: list[int], b: list[int], c: list[int]): ... +@overload +def foo(a: list[float], b: list[int], c: list[int]): ... +@overload +def foo(a: list[int], b: list[float], c: list[int]): ... +@overload +def foo(a: list[int], b: list[int], c: list[float]): ... +@overload +def foo(a: list[float], b: list[float], c: list[int]): ... +@overload +def foo(a: list[int], b: list[float], c: list[float]): ... +@overload +def foo(a: list[float], b: list[float], c: list[float]): ... +@overload +def foo(a: list[str], b: list[str], c: list[str]): ... +@overload +def foo(a: list[float], b: list[str], c: list[str]): ... +@overload +def foo(a: list[str], b: list[float], c: list[str]): ... +@overload +def foo(a: list[str], b: list[str], c: list[float]): ... +@overload +def foo(a: list[float], b: list[float], c: list[str]): ... +@overload +def foo(a: list[str], b: list[float], c: list[float]): ... +@overload +def foo(a: list[float], b: list[float], c: list[float]): ... +@overload +def foo(a: bool, b: bool, c: bool): ... +@overload +def foo(a: str, b: bool, c: bool): ... +@overload +def foo(a: bool, b: str, c: bool): ... +@overload +def foo(a: bool, b: bool, c: str): ... +@overload +def foo(a: str, b: str, c: bool): ... +@overload +def foo(a: bool, b: str, c: str): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: int, b: int, c: int): ... +@overload +def foo(a: bool, b: int, c: int): ... +@overload +def foo(a: int, b: bool, c: int): ... +@overload +def foo(a: int, b: int, c: bool): ... +@overload +def foo(a: bool, b: bool, c: int): ... +@overload +def foo(a: int, b: bool, c: bool): ... +@overload +def foo(a: str, b: str, c: str): ... +@overload +def foo(a: float, b: bool, c: bool): ... +@overload +def foo(a: bool, b: float, c: bool): ... +@overload +def foo(a: bool, b: bool, c: float): ... +@overload +def foo(a: float, b: float, c: bool): ... +@overload +def foo(a: bool, b: float, c: float): ... +def foo(a, b, c): ... + +foo(Foo(), Foo()) # error: [no-matching-overload] +``` + +## Calls to overloaded functions with lots of parameters + +```py +from typing import overload + +@overload +def f( + lion: int, + turtle: int, + tortoise: int, + goat: int, + capybara: int, + chicken: int, + ostrich: int, + gorilla: int, + giraffe: int, + condor: int, + kangaroo: int, + anaconda: int, + tarantula: int, + millipede: int, + leopard: int, + hyena: int, +) -> int: ... +@overload +def f( + lion: str, + turtle: str, + tortoise: str, + goat: str, + capybara: str, + chicken: str, + ostrich: str, + gorilla: str, + giraffe: str, + condor: str, + kangaroo: str, + anaconda: str, + tarantula: str, + millipede: str, + leopard: str, + hyena: str, +) -> str: ... +def f( + lion: int | str, + turtle: int | str, + tortoise: int | str, + goat: int | str, + capybara: int | str, + chicken: int | str, + ostrict: int | str, + gorilla: int | str, + giraffe: int | str, + condor: int | str, + kangaroo: int | str, + anaconda: int | str, + tarantula: int | str, + millipede: int | str, + leopard: int | str, + hyena: int | str, +) -> int | str: + return 0 + +f(b"foo") # error: [no-matching-overload] +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" new file mode 100644 index 00000000000000..68563b01be43af --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" @@ -0,0 +1,120 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - Call to function with many unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import overload + 2 | + 3 | class Foo: ... + 4 | + 5 | @overload + 6 | def foo(a: int, b: int, c: int): ... + 7 | @overload + 8 | def foo(a: str, b: int, c: int): ... + 9 | @overload +10 | def foo(a: int, b: str, c: int): ... +11 | @overload +12 | def foo(a: int, b: int, c: str): ... +13 | @overload +14 | def foo(a: str, b: str, c: int): ... +15 | @overload +16 | def foo(a: int, b: str, c: str): ... +17 | @overload +18 | def foo(a: str, b: str, c: str): ... +19 | @overload +20 | def foo(a: int, b: int, c: int): ... +21 | @overload +22 | def foo(a: float, b: int, c: int): ... +23 | @overload +24 | def foo(a: int, b: float, c: int): ... +25 | @overload +26 | def foo(a: int, b: int, c: float): ... +27 | @overload +28 | def foo(a: float, b: float, c: int): ... +29 | @overload +30 | def foo(a: int, b: float, c: float): ... +31 | @overload +32 | def foo(a: float, b: float, c: float): ... +33 | @overload +34 | def foo(a: str, b: str, c: str): ... +35 | @overload +36 | def foo(a: float, b: str, c: str): ... +37 | @overload +38 | def foo(a: str, b: float, c: str): ... +39 | @overload +40 | def foo(a: str, b: str, c: float): ... +41 | @overload +42 | def foo(a: float, b: float, c: str): ... +43 | @overload +44 | def foo(a: str, b: float, c: float): ... +45 | @overload +46 | def foo(a: float, b: float, c: float): ... +47 | def foo(a, b, c): ... +48 | +49 | foo(Foo(), Foo()) # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of function `foo` matches arguments + --> src/mdtest_snippet.py:49:1 + | +47 | def foo(a, b, c): ... +48 | +49 | foo(Foo(), Foo()) # error: [no-matching-overload] + | ^^^^^^^^^^^^^^^^^ + | +info: First overload defined here + --> src/mdtest_snippet.py:6:5 + | +5 | @overload +6 | def foo(a: int, b: int, c: int): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +7 | @overload +8 | def foo(a: str, b: int, c: int): ... + | +info: Possible overloads for function `foo`: +info: (a: int, b: int, c: int) -> Unknown +info: (a: str, b: int, c: int) -> Unknown +info: (a: int, b: str, c: int) -> Unknown +info: (a: int, b: int, c: str) -> Unknown +info: (a: str, b: str, c: int) -> Unknown +info: (a: int, b: str, c: str) -> Unknown +info: (a: str, b: str, c: str) -> Unknown +info: (a: int, b: int, c: int) -> Unknown +info: (a: int | float, b: int, c: int) -> Unknown +info: (a: int, b: int | float, c: int) -> Unknown +info: (a: int, b: int, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int) -> Unknown +info: (a: int, b: int | float, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int | float) -> Unknown +info: (a: str, b: str, c: str) -> Unknown +info: (a: int | float, b: str, c: str) -> Unknown +info: (a: str, b: int | float, c: str) -> Unknown +info: (a: str, b: str, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: str) -> Unknown +info: (a: str, b: int | float, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int | float) -> Unknown +info: Overload implementation defined here + --> src/mdtest_snippet.py:47:5 + | +45 | @overload +46 | def foo(a: float, b: float, c: float): ... +47 | def foo(a, b, c): ... + | ^^^^^^^^^^^^ +48 | +49 | foo(Foo(), Foo()) # error: [no-matching-overload] + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" new file mode 100644 index 00000000000000..b14961eedf3661 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" @@ -0,0 +1,230 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - Call to function with too many unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import overload + 2 | + 3 | class Foo: ... + 4 | + 5 | @overload + 6 | def foo(a: int, b: int, c: int): ... + 7 | @overload + 8 | def foo(a: str, b: int, c: int): ... + 9 | @overload + 10 | def foo(a: int, b: str, c: int): ... + 11 | @overload + 12 | def foo(a: int, b: int, c: str): ... + 13 | @overload + 14 | def foo(a: str, b: str, c: int): ... + 15 | @overload + 16 | def foo(a: int, b: str, c: str): ... + 17 | @overload + 18 | def foo(a: str, b: str, c: str): ... + 19 | @overload + 20 | def foo(a: int, b: int, c: int): ... + 21 | @overload + 22 | def foo(a: float, b: int, c: int): ... + 23 | @overload + 24 | def foo(a: int, b: float, c: int): ... + 25 | @overload + 26 | def foo(a: int, b: int, c: float): ... + 27 | @overload + 28 | def foo(a: float, b: float, c: int): ... + 29 | @overload + 30 | def foo(a: int, b: float, c: float): ... + 31 | @overload + 32 | def foo(a: float, b: float, c: float): ... + 33 | @overload + 34 | def foo(a: str, b: str, c: str): ... + 35 | @overload + 36 | def foo(a: float, b: str, c: str): ... + 37 | @overload + 38 | def foo(a: str, b: float, c: str): ... + 39 | @overload + 40 | def foo(a: str, b: str, c: float): ... + 41 | @overload + 42 | def foo(a: float, b: float, c: str): ... + 43 | @overload + 44 | def foo(a: str, b: float, c: float): ... + 45 | @overload + 46 | def foo(a: float, b: float, c: float): ... + 47 | @overload + 48 | def foo(a: list[int], b: list[int], c: list[int]): ... + 49 | @overload + 50 | def foo(a: list[str], b: list[int], c: list[int]): ... + 51 | @overload + 52 | def foo(a: list[int], b: list[str], c: list[int]): ... + 53 | @overload + 54 | def foo(a: list[int], b: list[int], c: list[str]): ... + 55 | @overload + 56 | def foo(a: list[str], b: list[str], c: list[int]): ... + 57 | @overload + 58 | def foo(a: list[int], b: list[str], c: list[str]): ... + 59 | @overload + 60 | def foo(a: list[str], b: list[str], c: list[str]): ... + 61 | @overload + 62 | def foo(a: list[int], b: list[int], c: list[int]): ... + 63 | @overload + 64 | def foo(a: list[float], b: list[int], c: list[int]): ... + 65 | @overload + 66 | def foo(a: list[int], b: list[float], c: list[int]): ... + 67 | @overload + 68 | def foo(a: list[int], b: list[int], c: list[float]): ... + 69 | @overload + 70 | def foo(a: list[float], b: list[float], c: list[int]): ... + 71 | @overload + 72 | def foo(a: list[int], b: list[float], c: list[float]): ... + 73 | @overload + 74 | def foo(a: list[float], b: list[float], c: list[float]): ... + 75 | @overload + 76 | def foo(a: list[str], b: list[str], c: list[str]): ... + 77 | @overload + 78 | def foo(a: list[float], b: list[str], c: list[str]): ... + 79 | @overload + 80 | def foo(a: list[str], b: list[float], c: list[str]): ... + 81 | @overload + 82 | def foo(a: list[str], b: list[str], c: list[float]): ... + 83 | @overload + 84 | def foo(a: list[float], b: list[float], c: list[str]): ... + 85 | @overload + 86 | def foo(a: list[str], b: list[float], c: list[float]): ... + 87 | @overload + 88 | def foo(a: list[float], b: list[float], c: list[float]): ... + 89 | @overload + 90 | def foo(a: bool, b: bool, c: bool): ... + 91 | @overload + 92 | def foo(a: str, b: bool, c: bool): ... + 93 | @overload + 94 | def foo(a: bool, b: str, c: bool): ... + 95 | @overload + 96 | def foo(a: bool, b: bool, c: str): ... + 97 | @overload + 98 | def foo(a: str, b: str, c: bool): ... + 99 | @overload +100 | def foo(a: bool, b: str, c: str): ... +101 | @overload +102 | def foo(a: str, b: str, c: str): ... +103 | @overload +104 | def foo(a: int, b: int, c: int): ... +105 | @overload +106 | def foo(a: bool, b: int, c: int): ... +107 | @overload +108 | def foo(a: int, b: bool, c: int): ... +109 | @overload +110 | def foo(a: int, b: int, c: bool): ... +111 | @overload +112 | def foo(a: bool, b: bool, c: int): ... +113 | @overload +114 | def foo(a: int, b: bool, c: bool): ... +115 | @overload +116 | def foo(a: str, b: str, c: str): ... +117 | @overload +118 | def foo(a: float, b: bool, c: bool): ... +119 | @overload +120 | def foo(a: bool, b: float, c: bool): ... +121 | @overload +122 | def foo(a: bool, b: bool, c: float): ... +123 | @overload +124 | def foo(a: float, b: float, c: bool): ... +125 | @overload +126 | def foo(a: bool, b: float, c: float): ... +127 | def foo(a, b, c): ... +128 | +129 | foo(Foo(), Foo()) # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of function `foo` matches arguments + --> src/mdtest_snippet.py:129:1 + | +127 | def foo(a, b, c): ... +128 | +129 | foo(Foo(), Foo()) # error: [no-matching-overload] + | ^^^^^^^^^^^^^^^^^ + | +info: First overload defined here + --> src/mdtest_snippet.py:6:5 + | +5 | @overload +6 | def foo(a: int, b: int, c: int): ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +7 | @overload +8 | def foo(a: str, b: int, c: int): ... + | +info: Possible overloads for function `foo`: +info: (a: int, b: int, c: int) -> Unknown +info: (a: str, b: int, c: int) -> Unknown +info: (a: int, b: str, c: int) -> Unknown +info: (a: int, b: int, c: str) -> Unknown +info: (a: str, b: str, c: int) -> Unknown +info: (a: int, b: str, c: str) -> Unknown +info: (a: str, b: str, c: str) -> Unknown +info: (a: int, b: int, c: int) -> Unknown +info: (a: int | float, b: int, c: int) -> Unknown +info: (a: int, b: int | float, c: int) -> Unknown +info: (a: int, b: int, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int) -> Unknown +info: (a: int, b: int | float, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int | float) -> Unknown +info: (a: str, b: str, c: str) -> Unknown +info: (a: int | float, b: str, c: str) -> Unknown +info: (a: str, b: int | float, c: str) -> Unknown +info: (a: str, b: str, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: str) -> Unknown +info: (a: str, b: int | float, c: int | float) -> Unknown +info: (a: int | float, b: int | float, c: int | float) -> Unknown +info: (a: list[int], b: list[int], c: list[int]) -> Unknown +info: (a: list[str], b: list[int], c: list[int]) -> Unknown +info: (a: list[int], b: list[str], c: list[int]) -> Unknown +info: (a: list[int], b: list[int], c: list[str]) -> Unknown +info: (a: list[str], b: list[str], c: list[int]) -> Unknown +info: (a: list[int], b: list[str], c: list[str]) -> Unknown +info: (a: list[str], b: list[str], c: list[str]) -> Unknown +info: (a: list[int], b: list[int], c: list[int]) -> Unknown +info: (a: list[int | float], b: list[int], c: list[int]) -> Unknown +info: (a: list[int], b: list[int | float], c: list[int]) -> Unknown +info: (a: list[int], b: list[int], c: list[int | float]) -> Unknown +info: (a: list[int | float], b: list[int | float], c: list[int]) -> Unknown +info: (a: list[int], b: list[int | float], c: list[int | float]) -> Unknown +info: (a: list[int | float], b: list[int | float], c: list[int | float]) -> Unknown +info: (a: list[str], b: list[str], c: list[str]) -> Unknown +info: (a: list[int | float], b: list[str], c: list[str]) -> Unknown +info: (a: list[str], b: list[int | float], c: list[str]) -> Unknown +info: (a: list[str], b: list[str], c: list[int | float]) -> Unknown +info: (a: list[int | float], b: list[int | float], c: list[str]) -> Unknown +info: (a: list[str], b: list[int | float], c: list[int | float]) -> Unknown +info: (a: list[int | float], b: list[int | float], c: list[int | float]) -> Unknown +info: (a: bool, b: bool, c: bool) -> Unknown +info: (a: str, b: bool, c: bool) -> Unknown +info: (a: bool, b: str, c: bool) -> Unknown +info: (a: bool, b: bool, c: str) -> Unknown +info: (a: str, b: str, c: bool) -> Unknown +info: (a: bool, b: str, c: str) -> Unknown +info: (a: str, b: str, c: str) -> Unknown +info: (a: int, b: int, c: int) -> Unknown +info: ... omitted 11 overloads +info: Overload implementation defined here + --> src/mdtest_snippet.py:127:5 + | +125 | @overload +126 | def foo(a: bool, b: float, c: float): ... +127 | def foo(a, b, c): ... + | ^^^^^^^^^^^^ +128 | +129 | foo(Foo(), Foo()) # error: [no-matching-overload] + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" index 28389124c967c3..367bfd4dd8d03e 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" @@ -16,27 +16,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_ 2 | 3 | @overload 4 | def f(x: int) -> int: ... - 5 | - 6 | @overload - 7 | def f(x: str) -> str: ... - 8 | - 9 | def f(x: int | str) -> int | str: -10 | return x -11 | -12 | f(b"foo") # error: [no-matching-overload] + 5 | @overload + 6 | def f(x: str) -> str: ... + 7 | def f(x: int | str) -> int | str: + 8 | return x + 9 | +10 | f(b"foo") # error: [no-matching-overload] ``` # Diagnostics ``` error[no-matching-overload]: No overload of function `f` matches arguments - --> src/mdtest_snippet.py:12:1 + --> src/mdtest_snippet.py:10:1 | -10 | return x -11 | -12 | f(b"foo") # error: [no-matching-overload] + 8 | return x + 9 | +10 | f(b"foo") # error: [no-matching-overload] | ^^^^^^^^^ | +info: First overload defined here + --> src/mdtest_snippet.py:4:5 + | +3 | @overload +4 | def f(x: int) -> int: ... + | ^^^^^^^^^^^^^^^^ +5 | @overload +6 | def f(x: str) -> str: ... + | +info: Possible overloads for function `f`: +info: (x: int) -> int +info: (x: str) -> str +info: Overload implementation defined here + --> src/mdtest_snippet.py:7:5 + | +5 | @overload +6 | def f(x: str) -> str: ... +7 | def f(x: int | str) -> int | str: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +8 | return x + | info: rule `no-matching-overload` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" new file mode 100644 index 00000000000000..83837288e207ce --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" @@ -0,0 +1,148 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - Calls to overloaded functions with lots of parameters +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import overload + 2 | + 3 | @overload + 4 | def f( + 5 | lion: int, + 6 | turtle: int, + 7 | tortoise: int, + 8 | goat: int, + 9 | capybara: int, +10 | chicken: int, +11 | ostrich: int, +12 | gorilla: int, +13 | giraffe: int, +14 | condor: int, +15 | kangaroo: int, +16 | anaconda: int, +17 | tarantula: int, +18 | millipede: int, +19 | leopard: int, +20 | hyena: int, +21 | ) -> int: ... +22 | @overload +23 | def f( +24 | lion: str, +25 | turtle: str, +26 | tortoise: str, +27 | goat: str, +28 | capybara: str, +29 | chicken: str, +30 | ostrich: str, +31 | gorilla: str, +32 | giraffe: str, +33 | condor: str, +34 | kangaroo: str, +35 | anaconda: str, +36 | tarantula: str, +37 | millipede: str, +38 | leopard: str, +39 | hyena: str, +40 | ) -> str: ... +41 | def f( +42 | lion: int | str, +43 | turtle: int | str, +44 | tortoise: int | str, +45 | goat: int | str, +46 | capybara: int | str, +47 | chicken: int | str, +48 | ostrict: int | str, +49 | gorilla: int | str, +50 | giraffe: int | str, +51 | condor: int | str, +52 | kangaroo: int | str, +53 | anaconda: int | str, +54 | tarantula: int | str, +55 | millipede: int | str, +56 | leopard: int | str, +57 | hyena: int | str, +58 | ) -> int | str: +59 | return 0 +60 | +61 | f(b"foo") # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of function `f` matches arguments + --> src/mdtest_snippet.py:61:1 + | +59 | return 0 +60 | +61 | f(b"foo") # error: [no-matching-overload] + | ^^^^^^^^^ + | +info: First overload defined here + --> src/mdtest_snippet.py:4:5 + | + 3 | @overload + 4 | def f( + | _____^ + 5 | | lion: int, + 6 | | turtle: int, + 7 | | tortoise: int, + 8 | | goat: int, + 9 | | capybara: int, +10 | | chicken: int, +11 | | ostrich: int, +12 | | gorilla: int, +13 | | giraffe: int, +14 | | condor: int, +15 | | kangaroo: int, +16 | | anaconda: int, +17 | | tarantula: int, +18 | | millipede: int, +19 | | leopard: int, +20 | | hyena: int, +21 | | ) -> int: ... + | |________^ +22 | @overload +23 | def f( + | +info: Possible overloads for function `f`: +info: (lion: int, turtle: int, tortoise: int, goat: int, capybara: int, chicken: int, ostrich: int, gorilla: int, giraffe: int, condor: int, kangaroo: int, anaconda: int, tarantula: int, millipede: int, leopard: int, hyena: int) -> int +info: (lion: str, turtle: str, tortoise: str, goat: str, capybara: str, chicken: str, ostrich: str, gorilla: str, giraffe: str, condor: str, kangaroo: str, anaconda: str, tarantula: str, millipede: str, leopard: str, hyena: str) -> str +info: Overload implementation defined here + --> src/mdtest_snippet.py:41:5 + | +39 | hyena: str, +40 | ) -> str: ... +41 | def f( + | _____^ +42 | | lion: int | str, +43 | | turtle: int | str, +44 | | tortoise: int | str, +45 | | goat: int | str, +46 | | capybara: int | str, +47 | | chicken: int | str, +48 | | ostrict: int | str, +49 | | gorilla: int | str, +50 | | giraffe: int | str, +51 | | condor: int | str, +52 | | kangaroo: int | str, +53 | | anaconda: int | str, +54 | | tarantula: int | str, +55 | | millipede: int | str, +56 | | leopard: int | str, +57 | | hyena: int | str, +58 | | ) -> int | str: + | |______________^ +59 | return 0 + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 7853e35d2d6116..af54dae8d66022 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5353,13 +5353,16 @@ impl<'db> Type<'db> { /// Returns a tuple of two spans. The first is /// the span for the identifier of the function /// definition for `self`. The second is - /// the span for the return type in the function + /// the span for the parameter in the function /// definition for `self`. /// /// If there are no meaningful spans, then this /// returns `None`. For example, when this type - /// isn't callable or if the function has no - /// declared return type. + /// isn't callable. + /// + /// When `parameter_index` is `None`, then the + /// second span returned covers the entire parameter + /// list. /// /// # Performance /// @@ -5370,30 +5373,28 @@ impl<'db> Type<'db> { /// /// An example of a good use case is to improve /// a diagnostic. - fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> { + fn parameter_span( + &self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { match *self { - Type::FunctionLiteral(function) => function.return_type_span(db), - Type::BoundMethod(bound_method) => { - Type::FunctionLiteral(bound_method.function(db)).return_type_span(db) - } + Type::FunctionLiteral(function) => function.parameter_span(db, parameter_index), + Type::BoundMethod(bound_method) => bound_method + .function(db) + .parameter_span(db, parameter_index), _ => None, } } - /// Returns a tuple of two spans. The first is - /// the span for the identifier of the function - /// definition for `self`. The second is - /// the span for the parameter in the function - /// definition for `self`. + /// Returns a collection of useful spans for a + /// function signature. These are useful for + /// creating annotations on diagnostics. /// /// If there are no meaningful spans, then this /// returns `None`. For example, when this type /// isn't callable. /// - /// When `parameter_index` is `None`, then the - /// second span returned covers the entire parameter - /// list. - /// /// # Performance /// /// Note that this may introduce cross-module @@ -5403,16 +5404,10 @@ impl<'db> Type<'db> { /// /// An example of a good use case is to improve /// a diagnostic. - fn parameter_span( - &self, - db: &'db dyn Db, - parameter_index: Option, - ) -> Option<(Span, Span)> { + fn function_spans(&self, db: &'db dyn Db) -> Option { match *self { - Type::FunctionLiteral(function) => function.parameter_span(db, parameter_index), - Type::BoundMethod(bound_method) => { - Type::FunctionLiteral(bound_method.function(db)).parameter_span(db, parameter_index) - } + Type::FunctionLiteral(function) => function.spans(db), + Type::BoundMethod(bound_method) => bound_method.function(db).spans(db), _ => None, } } @@ -6297,7 +6292,8 @@ impl<'db> BoolError<'db> { .member(context.db(), "__bool__") .into_lookup_result() .ok() - .and_then(|quals| quals.inner_type().return_type_span(context.db())) + .and_then(|quals| quals.inner_type().function_spans(context.db())) + .and_then(|spans| Some((spans.name, spans.return_type?))) { sub.annotate( Annotation::primary(return_type_span).message("Incorrect return type"), @@ -6894,37 +6890,6 @@ impl<'db> FunctionType<'db> { } } - /// Returns a tuple of two spans. The first is - /// the span for the identifier of the function - /// definition for `self`. The second is - /// the span for the return type in the function - /// definition for `self`. - /// - /// If there are no meaningful spans, then this - /// returns `None`. For example, when this type - /// isn't callable or if the function has no - /// declared return type. - /// - /// # Performance - /// - /// Note that this may introduce cross-module - /// dependencies. This can have an impact on - /// the effectiveness of incremental caching - /// and should therefore be used judiciously. - /// - /// An example of a good use case is to improve - /// a diagnostic. - fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> { - let function_scope = self.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let return_type_range = func_def.returns.as_ref()?.range(); - let name_span = span.clone().with_range(func_def.name.range); - let return_type_span = span.with_range(return_type_range); - Some((name_span, return_type_span)) - } - /// Returns a tuple of two spans. The first is /// the span for the identifier of the function /// definition for `self`. The second is @@ -6949,7 +6914,7 @@ impl<'db> FunctionType<'db> { /// An example of a good use case is to improve /// a diagnostic. fn parameter_span( - &self, + self, db: &'db dyn Db, parameter_index: Option, ) -> Option<(Span, Span)> { @@ -6970,6 +6935,55 @@ impl<'db> FunctionType<'db> { let parameter_span = span.with_range(range); Some((name_span, parameter_span)) } + + /// Returns a collection of useful spans for a + /// function signature. These are useful for + /// creating annotations on diagnostics. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + fn spans(self, db: &'db dyn Db) -> Option { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + 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); + if let Some(return_type_range) = return_type_range { + signature = signature.cover(return_type_range); + } + Some(FunctionSpans { + signature: span.clone().with_range(signature), + name: span.clone().with_range(func_def.name.range), + parameters: span.clone().with_range(func_def.parameters.range), + return_type: return_type_range.map(|range| span.clone().with_range(range)), + }) + } +} + +/// A collection of useful spans for annotating functions. +/// +/// This can be retrieved via `FunctionType::spans` or +/// `Type::function_spans`. +struct FunctionSpans { + /// The span of the entire function "signature." This includes + /// the name, parameter list and return type (if present). + signature: Span, + /// The span of the function name. i.e., `foo` in `def foo(): ...`. + name: Span, + /// The span of the parameter list, including the opening and + /// closing parentheses. + #[expect(dead_code)] + parameters: Span, + /// The span of the annotated return type, if present. + return_type: Option, } fn signature_cycle_recover<'db>( diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index db9a95190acb88..de8cc4168ea08f 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1100,6 +1100,13 @@ impl<'db> CallableBinding<'db> { ); } _overloads => { + // When the number of unmatched overloads exceeds this number, we stop + // printing them to avoid excessive output. + // + // An example of a routine with many many overloads: + // https://github.com/henribru/google-api-python-client-stubs/blob/master/googleapiclient-stubs/discovery.pyi + const MAXIMUM_OVERLOADS: usize = 50; + let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) else { return; }; @@ -1113,6 +1120,48 @@ impl<'db> CallableBinding<'db> { String::new() } )); + if let Some(function) = self.signature_type.into_function_literal() { + if let Some(overloaded_function) = function.to_overloaded(context.db()) { + if let Some(spans) = overloaded_function + .overloads + .first() + .and_then(|overload| overload.spans(context.db())) + { + let mut sub = + SubDiagnostic::new(Severity::Info, "First overload defined here"); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); + } + + diag.info(format_args!( + "Possible overloads for function `{}`:", + function.name(context.db()) + )); + + let overloads = &function.signature(context.db()).overloads.overloads; + for overload in overloads.iter().take(MAXIMUM_OVERLOADS) { + diag.info(format_args!(" {}", overload.display(context.db()))); + } + if overloads.len() > MAXIMUM_OVERLOADS { + diag.info(format_args!( + "... omitted {remaining} overloads", + remaining = overloads.len() - MAXIMUM_OVERLOADS + )); + } + + if let Some(spans) = overloaded_function + .implementation + .and_then(|function| function.spans(context.db())) + { + let mut sub = SubDiagnostic::new( + Severity::Info, + "Overload implementation defined here", + ); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); + } + } + } if let Some(union_diag) = union_diag { union_diag.add_union_context(context.db(), &mut diag); } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 25169d3f570513..182cce3eb4b953 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -123,7 +123,7 @@ pub(crate) struct CallableSignature<'db> { /// /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a /// non-overloaded callable. - overloads: SmallVec<[Signature<'db>; 1]>, + pub(crate) overloads: SmallVec<[Signature<'db>; 1]>, } impl<'db> CallableSignature<'db> { From cf70c7863c2c011367d5fd673332f7fa56ef81ec Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 14 May 2025 21:05:52 +0530 Subject: [PATCH 093/487] Remove symlinks from the fuzz directory (#18095) ## Summary This PR does the following: 1. Remove the symlinks from the `fuzz/` directory 2. Update `init-fuzzer.sh` script to create those symlinks 3. Update `fuzz/.gitignore` to ignore those corpus directories ## Test Plan Initialize the fuzzer: ```sh ./fuzz/init-fuzzer.sh ``` And, run a fuzz target: ```sh cargo +nightly fuzz run ruff_parse_simple -- -timeout=1 -only_ascii=1 ``` --- fuzz/.gitignore | 2 +- fuzz/README.md | 25 +++++++++++++++++------ fuzz/corpus/red_knot_check_invalid_syntax | 1 - fuzz/corpus/ruff_formatter_idempotency | 1 - fuzz/corpus/ruff_formatter_validity | 1 - fuzz/corpus/ruff_new_parser_equiv | 1 - fuzz/corpus/ruff_parse_idempotency | 1 - fuzz/corpus/ruff_parse_simple | 1 - fuzz/init-fuzzer.sh | 22 ++++++++++++++------ fuzz/reinit-fuzzer.sh | 14 ------------- 10 files changed, 36 insertions(+), 33 deletions(-) delete mode 120000 fuzz/corpus/red_knot_check_invalid_syntax delete mode 120000 fuzz/corpus/ruff_formatter_idempotency delete mode 120000 fuzz/corpus/ruff_formatter_validity delete mode 120000 fuzz/corpus/ruff_new_parser_equiv delete mode 120000 fuzz/corpus/ruff_parse_idempotency delete mode 120000 fuzz/corpus/ruff_parse_simple delete mode 100755 fuzz/reinit-fuzzer.sh diff --git a/fuzz/.gitignore b/fuzz/.gitignore index 0aae9a3e343154..41cc193fba36a6 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -1,3 +1,3 @@ artifacts/ -corpus/ruff_fix_validity +corpus/ Cargo.lock diff --git a/fuzz/README.md b/fuzz/README.md index 14f7692a89c68c..8d249780d0a510 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -12,8 +12,11 @@ To use the fuzzers provided in this directory, start by invoking: This will install [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) and optionally download a [dataset](https://zenodo.org/record/3628784) which improves the efficacy of the testing. -**This step is necessary for initialising the corpus directory, as all fuzzers share a common -corpus.** + +> [!NOTE] +> +> This step is necessary for initialising the corpus directory, as all fuzzers share a common corpus. + The dataset may take several hours to download and clean, so if you're just looking to try out the fuzzers, skip the dataset download, though be warned that some features simply cannot be tested without it (very unlikely for the fuzzer to generate valid python code from "thin air"). @@ -24,13 +27,23 @@ Once you have initialised the fuzzers, you can then execute any fuzzer with: cargo fuzz run -s none name_of_fuzzer -- -timeout=1 ``` -**Users using Apple M1 devices must use a nightly compiler and omit the `-s none` portion of this -command, as this architecture does not support fuzzing without a sanitizer.** +> [!NOTE] +> +> Users using Apple M1 devices must use a nightly compiler and omit the `-s none` portion of this +> command, as this architecture does not support fuzzing without a sanitizer. +> +> ```shell +> cargo +nightly fuzz run name_of_fuzzer -- -timeout=1 +> ``` + You can view the names of the available fuzzers with `cargo fuzz list`. For specific details about how each fuzzer works, please read this document in its entirety. -**IMPORTANT: You should run `./reinit-fuzzer.sh` after adding more file-based testcases.** This will -allow the testing of new features that you've added unit tests for. +> [!NOTE] +> +> Re-run `./init-fuzzer.sh` (say no to the dataset download) after adding more file-based test cases +> to the repository. This will make sure that the corpus is up to date with any new Python code +> added to the repository. ### Debugging a crash diff --git a/fuzz/corpus/red_knot_check_invalid_syntax b/fuzz/corpus/red_knot_check_invalid_syntax deleted file mode 120000 index 38dc5bc1ea3103..00000000000000 --- a/fuzz/corpus/red_knot_check_invalid_syntax +++ /dev/null @@ -1 +0,0 @@ -ruff_fix_validity \ No newline at end of file diff --git a/fuzz/corpus/ruff_formatter_idempotency b/fuzz/corpus/ruff_formatter_idempotency deleted file mode 120000 index 38dc5bc1ea3103..00000000000000 --- a/fuzz/corpus/ruff_formatter_idempotency +++ /dev/null @@ -1 +0,0 @@ -ruff_fix_validity \ No newline at end of file diff --git a/fuzz/corpus/ruff_formatter_validity b/fuzz/corpus/ruff_formatter_validity deleted file mode 120000 index 38dc5bc1ea3103..00000000000000 --- a/fuzz/corpus/ruff_formatter_validity +++ /dev/null @@ -1 +0,0 @@ -ruff_fix_validity \ No newline at end of file diff --git a/fuzz/corpus/ruff_new_parser_equiv b/fuzz/corpus/ruff_new_parser_equiv deleted file mode 120000 index 38dc5bc1ea3103..00000000000000 --- a/fuzz/corpus/ruff_new_parser_equiv +++ /dev/null @@ -1 +0,0 @@ -ruff_fix_validity \ No newline at end of file diff --git a/fuzz/corpus/ruff_parse_idempotency b/fuzz/corpus/ruff_parse_idempotency deleted file mode 120000 index 61e7ad4b4cd6fa..00000000000000 --- a/fuzz/corpus/ruff_parse_idempotency +++ /dev/null @@ -1 +0,0 @@ -ruff_parse_simple \ No newline at end of file diff --git a/fuzz/corpus/ruff_parse_simple b/fuzz/corpus/ruff_parse_simple deleted file mode 120000 index 018c02efec25c4..00000000000000 --- a/fuzz/corpus/ruff_parse_simple +++ /dev/null @@ -1 +0,0 @@ -ruff_fix_validity/ \ No newline at end of file diff --git a/fuzz/init-fuzzer.sh b/fuzz/init-fuzzer.sh index 7de7087dc5e5a2..2419ae24662f9d 100755 --- a/fuzz/init-fuzzer.sh +++ b/fuzz/init-fuzzer.sh @@ -6,22 +6,31 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "$SCRIPT_DIR" if ! cargo fuzz --help >&/dev/null; then + echo "Installing cargo-fuzz..." cargo install --git https://github.com/rust-fuzz/cargo-fuzz.git fi -if [ ! -d corpus/ruff_fix_validity ]; then - mkdir -p corpus/ruff_fix_validity +if [ ! -d corpus/common ]; then + mkdir -p corpus/common + + echo "Creating symlinks for fuzz targets to the common corpus directory..." + for target in fuzz_targets/*; do + corpus_dir="$(basename "$target" .rs)" + ln -vs "./common" "corpus/$corpus_dir" + done ( - cd corpus/ruff_fix_validity + cd corpus/common read -p "Would you like to build a corpus from a python source code dataset? (this will take a long time!) [Y/n] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Downloading the Python source code dataset..." curl -L 'https://zenodo.org/record/3628784/files/python-corpus.tar.gz?download=1' | tar xz fi # Build a smaller corpus in addition to the (optional) larger corpus + echo "Building a smaller corpus dataset..." curl -L 'https://github.com/python/cpython/archive/refs/tags/v3.13.0.tar.gz' | tar xz cp -r "../../../crates/ty_project/resources/test/corpus" "ty_project" cp -r "../../../crates/ruff_linter/resources/test/fixtures" "ruff_linter" @@ -32,11 +41,12 @@ if [ ! -d corpus/ruff_fix_validity ]; then find . -type f -not -name "*.py" -delete ) + echo "Minifying the corpus dataset..." if [[ "$OSTYPE" == "darwin"* ]]; then - cargo +nightly fuzz cmin ruff_fix_validity -- -timeout=5 + cargo +nightly fuzz cmin ruff_fix_validity corpus/common -- -timeout=5 else - cargo fuzz cmin -s none ruff_fix_validity -- -timeout=5 + cargo fuzz cmin -s none ruff_fix_validity corpus/common -- -timeout=5 fi fi -echo "Done! You are ready to fuzz." +echo "Done! You are ready to fuzz" diff --git a/fuzz/reinit-fuzzer.sh b/fuzz/reinit-fuzzer.sh deleted file mode 100755 index f1e2a698fb1926..00000000000000 --- a/fuzz/reinit-fuzzer.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# https://stackoverflow.com/a/246128/3549270 -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -cd "$SCRIPT_DIR" - -cd corpus/ruff_fix_validity -curl -L 'https://github.com/python/cpython/archive/refs/tags/v3.12.0b2.tar.gz' | tar xz -cp -r "../../../crates/ruff_linter/resources/test" . -cd - -cargo fuzz cmin -s none ruff_fix_validity -- -timeout=5 - -echo "Done! You are ready to fuzz." From 8104b1e83b3aa4a91c51ce89c2b347794378e32c Mon Sep 17 00:00:00 2001 From: Usul-Dev <65585004+Usul-Dev@users.noreply.github.com> Date: Thu, 15 May 2025 00:50:35 +0900 Subject: [PATCH 094/487] [ty] fix missing '>' in HTML anchor tags in CLI reference (#18096) Co-authored-by: Usul --- crates/ruff_dev/src/generate_ty_cli_reference.rs | 2 +- crates/ty/docs/cli.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_dev/src/generate_ty_cli_reference.rs b/crates/ruff_dev/src/generate_ty_cli_reference.rs index 8a8c6e4bb83bb7..349c27894c4eb5 100644 --- a/crates/ruff_dev/src/generate_ty_cli_reference.rs +++ b/crates/ruff_dev/src/generate_ty_cli_reference.rs @@ -179,7 +179,7 @@ fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut let id = format!("{name_key}--{}", arg.get_id()); output.push_str(&format!("
    ")); output.push_str(&format!( - "{}", + "{}", arg.get_id().to_string().to_uppercase(), )); output.push_str("
    "); diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index ac26903b0005f9..61d2e392f769ce 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -30,7 +30,7 @@ ty check [OPTIONS] [PATH]...

    Arguments

    -
    PATHS

    List of files or directories to check [default: the project root]

    +
    PATHS

    List of files or directories to check [default: the project root]

    Options

    @@ -125,7 +125,7 @@ ty generate-shell-completion

    Arguments

    -
    SHELL
    +
    SHELL

    Options

    From 0590b3821413c405b46ef315b755b76d58a991ec Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 May 2025 12:26:52 -0400 Subject: [PATCH 095/487] [ty] Fix more generics-related TODOs (#18062) --- .../resources/mdtest/exception/except_star.md | 15 ++++------ crates/ty_python_semantic/src/types/class.rs | 28 ++++++++++++++++--- crates/ty_python_semantic/src/types/infer.rs | 17 ++++++----- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/exception/except_star.md b/crates/ty_python_semantic/resources/mdtest/exception/except_star.md index ef75db64822fd1..8f862e747ee841 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/except_star.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/except_star.md @@ -22,9 +22,7 @@ except* BaseException as e: try: help() except* OSError as e: - # TODO: more precise would be `ExceptionGroup[OSError]` --Alex - # (needs homogeneous tuples + generics) - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: ExceptionGroup[OSError] ``` ## `except*` with multiple exceptions @@ -33,9 +31,7 @@ except* OSError as e: try: help() except* (TypeError, AttributeError) as e: - # TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex - # (needs homogeneous tuples + generics) - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: ExceptionGroup[TypeError | AttributeError] ``` ## `except*` with mix of `Exception`s and `BaseException`s @@ -44,8 +40,7 @@ except* (TypeError, AttributeError) as e: try: help() except* (KeyboardInterrupt, AttributeError) as e: - # TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[KeyboardInterrupt | AttributeError] ``` ## Invalid `except*` handlers @@ -54,10 +49,10 @@ except* (KeyboardInterrupt, AttributeError) as e: try: help() except* 3 as e: # error: [invalid-exception-caught] - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[Unknown] try: help() except* (AttributeError, 42) as e: # error: [invalid-exception-caught] - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[AttributeError | Unknown] ``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 0f7936a18d5587..8a6836253be1d0 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -896,8 +896,8 @@ impl<'db> ClassLiteral<'db> { } else { let name = Type::string_literal(db, self.name(db)); let bases = TupleType::from_elements(db, self.explicit_bases(db)); - // TODO: Should be `dict[str, Any]` - let namespace = KnownClass::Dict.to_instance(db); + let namespace = KnownClass::Dict + .to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()]); // TODO: Other keyword arguments? let arguments = CallArgumentTypes::positional([name, bases, namespace]); @@ -1913,7 +1913,9 @@ pub enum KnownClass { Slice, Property, BaseException, + Exception, BaseExceptionGroup, + ExceptionGroup, Classmethod, Super, // enum @@ -2004,6 +2006,8 @@ impl<'db> KnownClass { Self::Any | Self::BaseException + | Self::Exception + | Self::ExceptionGroup | Self::Object | Self::OrderedDict | Self::BaseExceptionGroup @@ -2079,6 +2083,8 @@ impl<'db> KnownClass { | Self::Property | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::GenericAlias | Self::GeneratorType @@ -2137,6 +2143,8 @@ impl<'db> KnownClass { Self::Property => "property", Self::BaseException => "BaseException", Self::BaseExceptionGroup => "BaseExceptionGroup", + Self::Exception => "Exception", + Self::ExceptionGroup => "ExceptionGroup", Self::Classmethod => "classmethod", Self::GenericAlias => "GenericAlias", Self::ModuleType => "ModuleType", @@ -2234,7 +2242,9 @@ impl<'db> KnownClass { db: &'db dyn Db, specialization: impl IntoIterator>, ) -> Type<'db> { - let class_literal = self.to_class_literal(db).expect_class_literal(); + let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else { + return Type::unknown(); + }; let Some(generic_context) = class_literal.generic_context(db) else { return Type::unknown(); }; @@ -2354,6 +2364,8 @@ impl<'db> KnownClass { | Self::Dict | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::Slice | Self::Super @@ -2444,6 +2456,8 @@ impl<'db> KnownClass { | Self::Property | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::GenericAlias | Self::ModuleType @@ -2522,6 +2536,8 @@ impl<'db> KnownClass { | Self::SupportsIndex | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::TypeVar | Self::ParamSpec @@ -2565,6 +2581,8 @@ impl<'db> KnownClass { "property" => Self::Property, "BaseException" => Self::BaseException, "BaseExceptionGroup" => Self::BaseExceptionGroup, + "Exception" => Self::Exception, + "ExceptionGroup" => Self::ExceptionGroup, "classmethod" => Self::Classmethod, "GenericAlias" => Self::GenericAlias, "NoneType" => Self::NoneType, @@ -2643,6 +2661,8 @@ impl<'db> KnownClass { | Self::ModuleType | Self::VersionInfo | Self::BaseException + | Self::Exception + | Self::ExceptionGroup | Self::EllipsisType | Self::BaseExceptionGroup | Self::Classmethod @@ -2856,7 +2876,7 @@ mod tests { for class in KnownClass::iter() { let version_added = match class { KnownClass::UnionType => PythonVersion::PY310, - KnownClass::BaseExceptionGroup => PythonVersion::PY311, + KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => PythonVersion::PY311, KnownClass::GenericAlias => PythonVersion::PY39, _ => PythonVersion::PY37, }; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index be64625dc24e3a..adfe5c48e9ca42 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1843,9 +1843,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let use_def = self.index.use_def_map(scope_id); if use_def.can_implicit_return(self.db()) - && !KnownClass::NoneType - .to_instance(self.db()) - .is_assignable_to(self.db(), declared_ty) + && !Type::none(self.db()).is_assignable_to(self.db(), declared_ty) { report_implicit_return_type(&self.context, returns.range(), declared_ty); } @@ -2623,9 +2621,14 @@ impl<'db> TypeInferenceBuilder<'db> { }; let symbol_ty = if except_handler_definition.is_star() { - // TODO: we should infer `ExceptionGroup` if `node_ty` is a subtype of `tuple[type[Exception], ...]` - // TODO: should be generic with `symbol_ty` as the generic parameter - KnownClass::BaseExceptionGroup.to_instance(self.db()) + let class = if symbol_ty + .is_subtype_of(self.db(), KnownClass::Exception.to_instance(self.db())) + { + KnownClass::ExceptionGroup + } else { + KnownClass::BaseExceptionGroup + }; + class.to_specialized_instance(self.db(), [symbol_ty]) } else { symbol_ty }; @@ -4104,7 +4107,7 @@ impl<'db> TypeInferenceBuilder<'db> { .map_or(ret.range(), |value| value.range()); self.record_return_type(ty, range); } else { - self.record_return_type(KnownClass::NoneType.to_instance(self.db()), ret.range()); + self.record_return_type(Type::none(self.db()), ret.range()); } } From 030a16cb5febcfb37e32594a91f71b49361c1863 Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Wed, 14 May 2025 14:20:18 -0400 Subject: [PATCH 096/487] [`flake8-simplify`] Correct behavior for `str.split`/`rsplit` with `maxsplit=0` (`SIM905`) (#18075) Fixes #18069 ## Summary This PR addresses a bug in the `flake8-simplify` rule `SIM905` (split-static-string) where `str.split(maxsplit=0)` and `str.rsplit(maxsplit=0)` produced incorrect results for empty strings or strings starting/ending with whitespace. The fix ensures that the linting rule's suggested replacements now align with Python's native behavior for these specific `maxsplit=0` scenarios. ## Test Plan 1. Added new test cases to the existing `crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py` fixture to cover the scenarios described in issue #18069. 2. Ran `cargo test -p ruff_linter`. 3. Verified and accepted the updated snapshots for `SIM905.py` using `cargo insta review`. The new snapshots confirm the corrected behavior for `maxsplit=0`. --- .../test/fixtures/flake8_simplify/SIM905.py | 20 + .../rules/split_static_string.rs | 33 +- ...ke8_simplify__tests__SIM905_SIM905.py.snap | 354 ++++++++++++++++++ 3 files changed, 401 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py index 86ef2171949ff7..049b60dfaffc60 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM905.py @@ -110,3 +110,23 @@ # https://github.com/astral-sh/ruff/issues/18042 print("a,b".rsplit(",")) print("a,b,c".rsplit(",", 1)) + +# https://github.com/astral-sh/ruff/issues/18069 + +print("".split(maxsplit=0)) +print("".split(sep=None, maxsplit=0)) +print(" ".split(maxsplit=0)) +print(" ".split(sep=None, maxsplit=0)) +print(" x ".split(maxsplit=0)) +print(" x ".split(sep=None, maxsplit=0)) +print(" x ".split(maxsplit=0)) +print(" x ".split(sep=None, maxsplit=0)) +print("".rsplit(maxsplit=0)) +print("".rsplit(sep=None, maxsplit=0)) +print(" ".rsplit(maxsplit=0)) +print(" ".rsplit(sep=None, maxsplit=0)) +print(" x ".rsplit(maxsplit=0)) +print(" x ".rsplit(maxsplit=0)) +print(" x ".rsplit(sep=None, maxsplit=0)) +print(" x ".rsplit(maxsplit=0)) +print(" x ".rsplit(sep=None, maxsplit=0)) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs index 8c9c863b64d786..af2b4797d518a5 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs @@ -83,7 +83,7 @@ pub(crate) fn split_static_string( let sep_arg = arguments.find_argument_value("sep", 0); let split_replacement = if let Some(sep) = sep_arg { match sep { - Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value), + Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction), Expr::StringLiteral(sep_value) => { let sep_value_str = sep_value.value.to_str(); Some(split_sep( @@ -99,7 +99,7 @@ pub(crate) fn split_static_string( } } } else { - split_default(str_value, maxsplit_value) + split_default(str_value, maxsplit_value, direction) }; let mut diagnostic = Diagnostic::new(SplitStaticString, call.range()); @@ -144,7 +144,11 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr { }) } -fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option { +fn split_default( + str_value: &StringLiteralValue, + max_split: i32, + direction: Direction, +) -> Option { // From the Python documentation: // > If sep is not specified or is None, a different splitting algorithm is applied: runs of // > consecutive whitespace are regarded as a single separator, and the result will contain @@ -152,6 +156,7 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option // > Consequently, splitting an empty string or a string consisting of just whitespace with // > a None separator returns []. // https://docs.python.org/3/library/stdtypes.html#str.split + let string_val = str_value.to_str(); match max_split.cmp(&0) { Ordering::Greater => { // Autofix for `maxsplit` without separator not yet implemented, as @@ -160,14 +165,30 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option None } Ordering::Equal => { - let list_items: Vec<&str> = vec![str_value.to_str()]; + // Behavior for maxsplit = 0 when sep is None: + // - If the string is empty or all whitespace, result is []. + // - Otherwise: + // - " x ".split(maxsplit=0) -> ['x '] + // - " x ".rsplit(maxsplit=0) -> [' x'] + // - "".split(maxsplit=0) -> [] + // - " ".split(maxsplit=0) -> [] + let processed_str = if direction == Direction::Left { + string_val.trim_start() + } else { + string_val.trim_end() + }; + let list_items: &[_] = if processed_str.is_empty() { + &[] + } else { + &[processed_str] + }; Some(construct_replacement( - &list_items, + list_items, str_value.first_literal_flags(), )) } Ordering::Less => { - let list_items: Vec<&str> = str_value.to_str().split_whitespace().collect(); + let list_items: Vec<&str> = string_val.split_whitespace().collect(); Some(construct_replacement( &list_items, str_value.first_literal_flags(), diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap index 0a3684c5ce71e5..ae93115007f43f 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM905_SIM905.py.snap @@ -890,6 +890,8 @@ SIM905.py:111:7: SIM905 [*] Consider using a list literal instead of `str.split` 111 |-print("a,b".rsplit(",")) 111 |+print(["a", "b"]) 112 112 | print("a,b,c".rsplit(",", 1)) +113 113 | +114 114 | # https://github.com/astral-sh/ruff/issues/18069 SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split` | @@ -897,6 +899,8 @@ SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split` 111 | print("a,b".rsplit(",")) 112 | print("a,b,c".rsplit(",", 1)) | ^^^^^^^^^^^^^^^^^^^^^^ SIM905 +113 | +114 | # https://github.com/astral-sh/ruff/issues/18069 | = help: Replace with list literal @@ -906,3 +910,353 @@ SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split` 111 111 | print("a,b".rsplit(",")) 112 |-print("a,b,c".rsplit(",", 1)) 112 |+print(["a,b", "c"]) +113 113 | +114 114 | # https://github.com/astral-sh/ruff/issues/18069 +115 115 | + +SIM905.py:116:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +114 | # https://github.com/astral-sh/ruff/issues/18069 +115 | +116 | print("".split(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^ SIM905 +117 | print("".split(sep=None, maxsplit=0)) +118 | print(" ".split(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +113 113 | +114 114 | # https://github.com/astral-sh/ruff/issues/18069 +115 115 | +116 |-print("".split(maxsplit=0)) + 116 |+print([]) +117 117 | print("".split(sep=None, maxsplit=0)) +118 118 | print(" ".split(maxsplit=0)) +119 119 | print(" ".split(sep=None, maxsplit=0)) + +SIM905.py:117:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +116 | print("".split(maxsplit=0)) +117 | print("".split(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +118 | print(" ".split(maxsplit=0)) +119 | print(" ".split(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +114 114 | # https://github.com/astral-sh/ruff/issues/18069 +115 115 | +116 116 | print("".split(maxsplit=0)) +117 |-print("".split(sep=None, maxsplit=0)) + 117 |+print([]) +118 118 | print(" ".split(maxsplit=0)) +119 119 | print(" ".split(sep=None, maxsplit=0)) +120 120 | print(" x ".split(maxsplit=0)) + +SIM905.py:118:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +116 | print("".split(maxsplit=0)) +117 | print("".split(sep=None, maxsplit=0)) +118 | print(" ".split(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^ SIM905 +119 | print(" ".split(sep=None, maxsplit=0)) +120 | print(" x ".split(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +115 115 | +116 116 | print("".split(maxsplit=0)) +117 117 | print("".split(sep=None, maxsplit=0)) +118 |-print(" ".split(maxsplit=0)) + 118 |+print([]) +119 119 | print(" ".split(sep=None, maxsplit=0)) +120 120 | print(" x ".split(maxsplit=0)) +121 121 | print(" x ".split(sep=None, maxsplit=0)) + +SIM905.py:119:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +117 | print("".split(sep=None, maxsplit=0)) +118 | print(" ".split(maxsplit=0)) +119 | print(" ".split(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +120 | print(" x ".split(maxsplit=0)) +121 | print(" x ".split(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +116 116 | print("".split(maxsplit=0)) +117 117 | print("".split(sep=None, maxsplit=0)) +118 118 | print(" ".split(maxsplit=0)) +119 |-print(" ".split(sep=None, maxsplit=0)) + 119 |+print([]) +120 120 | print(" x ".split(maxsplit=0)) +121 121 | print(" x ".split(sep=None, maxsplit=0)) +122 122 | print(" x ".split(maxsplit=0)) + +SIM905.py:120:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +118 | print(" ".split(maxsplit=0)) +119 | print(" ".split(sep=None, maxsplit=0)) +120 | print(" x ".split(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +121 | print(" x ".split(sep=None, maxsplit=0)) +122 | print(" x ".split(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +117 117 | print("".split(sep=None, maxsplit=0)) +118 118 | print(" ".split(maxsplit=0)) +119 119 | print(" ".split(sep=None, maxsplit=0)) +120 |-print(" x ".split(maxsplit=0)) + 120 |+print(["x "]) +121 121 | print(" x ".split(sep=None, maxsplit=0)) +122 122 | print(" x ".split(maxsplit=0)) +123 123 | print(" x ".split(sep=None, maxsplit=0)) + +SIM905.py:121:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +119 | print(" ".split(sep=None, maxsplit=0)) +120 | print(" x ".split(maxsplit=0)) +121 | print(" x ".split(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +122 | print(" x ".split(maxsplit=0)) +123 | print(" x ".split(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +118 118 | print(" ".split(maxsplit=0)) +119 119 | print(" ".split(sep=None, maxsplit=0)) +120 120 | print(" x ".split(maxsplit=0)) +121 |-print(" x ".split(sep=None, maxsplit=0)) + 121 |+print(["x "]) +122 122 | print(" x ".split(maxsplit=0)) +123 123 | print(" x ".split(sep=None, maxsplit=0)) +124 124 | print("".rsplit(maxsplit=0)) + +SIM905.py:122:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +120 | print(" x ".split(maxsplit=0)) +121 | print(" x ".split(sep=None, maxsplit=0)) +122 | print(" x ".split(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +123 | print(" x ".split(sep=None, maxsplit=0)) +124 | print("".rsplit(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +119 119 | print(" ".split(sep=None, maxsplit=0)) +120 120 | print(" x ".split(maxsplit=0)) +121 121 | print(" x ".split(sep=None, maxsplit=0)) +122 |-print(" x ".split(maxsplit=0)) + 122 |+print(["x "]) +123 123 | print(" x ".split(sep=None, maxsplit=0)) +124 124 | print("".rsplit(maxsplit=0)) +125 125 | print("".rsplit(sep=None, maxsplit=0)) + +SIM905.py:123:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +121 | print(" x ".split(sep=None, maxsplit=0)) +122 | print(" x ".split(maxsplit=0)) +123 | print(" x ".split(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +124 | print("".rsplit(maxsplit=0)) +125 | print("".rsplit(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +120 120 | print(" x ".split(maxsplit=0)) +121 121 | print(" x ".split(sep=None, maxsplit=0)) +122 122 | print(" x ".split(maxsplit=0)) +123 |-print(" x ".split(sep=None, maxsplit=0)) + 123 |+print(["x "]) +124 124 | print("".rsplit(maxsplit=0)) +125 125 | print("".rsplit(sep=None, maxsplit=0)) +126 126 | print(" ".rsplit(maxsplit=0)) + +SIM905.py:124:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +122 | print(" x ".split(maxsplit=0)) +123 | print(" x ".split(sep=None, maxsplit=0)) +124 | print("".rsplit(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^ SIM905 +125 | print("".rsplit(sep=None, maxsplit=0)) +126 | print(" ".rsplit(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +121 121 | print(" x ".split(sep=None, maxsplit=0)) +122 122 | print(" x ".split(maxsplit=0)) +123 123 | print(" x ".split(sep=None, maxsplit=0)) +124 |-print("".rsplit(maxsplit=0)) + 124 |+print([]) +125 125 | print("".rsplit(sep=None, maxsplit=0)) +126 126 | print(" ".rsplit(maxsplit=0)) +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) + +SIM905.py:125:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +123 | print(" x ".split(sep=None, maxsplit=0)) +124 | print("".rsplit(maxsplit=0)) +125 | print("".rsplit(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +126 | print(" ".rsplit(maxsplit=0)) +127 | print(" ".rsplit(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +122 122 | print(" x ".split(maxsplit=0)) +123 123 | print(" x ".split(sep=None, maxsplit=0)) +124 124 | print("".rsplit(maxsplit=0)) +125 |-print("".rsplit(sep=None, maxsplit=0)) + 125 |+print([]) +126 126 | print(" ".rsplit(maxsplit=0)) +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 128 | print(" x ".rsplit(maxsplit=0)) + +SIM905.py:126:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +124 | print("".rsplit(maxsplit=0)) +125 | print("".rsplit(sep=None, maxsplit=0)) +126 | print(" ".rsplit(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^ SIM905 +127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 | print(" x ".rsplit(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +123 123 | print(" x ".split(sep=None, maxsplit=0)) +124 124 | print("".rsplit(maxsplit=0)) +125 125 | print("".rsplit(sep=None, maxsplit=0)) +126 |-print(" ".rsplit(maxsplit=0)) + 126 |+print([]) +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 128 | print(" x ".rsplit(maxsplit=0)) +129 129 | print(" x ".rsplit(maxsplit=0)) + +SIM905.py:127:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +125 | print("".rsplit(sep=None, maxsplit=0)) +126 | print(" ".rsplit(maxsplit=0)) +127 | print(" ".rsplit(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +128 | print(" x ".rsplit(maxsplit=0)) +129 | print(" x ".rsplit(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +124 124 | print("".rsplit(maxsplit=0)) +125 125 | print("".rsplit(sep=None, maxsplit=0)) +126 126 | print(" ".rsplit(maxsplit=0)) +127 |-print(" ".rsplit(sep=None, maxsplit=0)) + 127 |+print([]) +128 128 | print(" x ".rsplit(maxsplit=0)) +129 129 | print(" x ".rsplit(maxsplit=0)) +130 130 | print(" x ".rsplit(sep=None, maxsplit=0)) + +SIM905.py:128:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +126 | print(" ".rsplit(maxsplit=0)) +127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 | print(" x ".rsplit(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +129 | print(" x ".rsplit(maxsplit=0)) +130 | print(" x ".rsplit(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +125 125 | print("".rsplit(sep=None, maxsplit=0)) +126 126 | print(" ".rsplit(maxsplit=0)) +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 |-print(" x ".rsplit(maxsplit=0)) + 128 |+print([" x"]) +129 129 | print(" x ".rsplit(maxsplit=0)) +130 130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 131 | print(" x ".rsplit(maxsplit=0)) + +SIM905.py:129:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 | print(" x ".rsplit(maxsplit=0)) +129 | print(" x ".rsplit(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 | print(" x ".rsplit(maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +126 126 | print(" ".rsplit(maxsplit=0)) +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 128 | print(" x ".rsplit(maxsplit=0)) +129 |-print(" x ".rsplit(maxsplit=0)) + 129 |+print([" x"]) +130 130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 131 | print(" x ".rsplit(maxsplit=0)) +132 132 | print(" x ".rsplit(sep=None, maxsplit=0)) + +SIM905.py:130:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +128 | print(" x ".rsplit(maxsplit=0)) +129 | print(" x ".rsplit(maxsplit=0)) +130 | print(" x ".rsplit(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +131 | print(" x ".rsplit(maxsplit=0)) +132 | print(" x ".rsplit(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +127 127 | print(" ".rsplit(sep=None, maxsplit=0)) +128 128 | print(" x ".rsplit(maxsplit=0)) +129 129 | print(" x ".rsplit(maxsplit=0)) +130 |-print(" x ".rsplit(sep=None, maxsplit=0)) + 130 |+print([" x"]) +131 131 | print(" x ".rsplit(maxsplit=0)) +132 132 | print(" x ".rsplit(sep=None, maxsplit=0)) + +SIM905.py:131:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +129 | print(" x ".rsplit(maxsplit=0)) +130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 | print(" x ".rsplit(maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 +132 | print(" x ".rsplit(sep=None, maxsplit=0)) + | + = help: Replace with list literal + +ℹ Safe fix +128 128 | print(" x ".rsplit(maxsplit=0)) +129 129 | print(" x ".rsplit(maxsplit=0)) +130 130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 |-print(" x ".rsplit(maxsplit=0)) + 131 |+print([" x"]) +132 132 | print(" x ".rsplit(sep=None, maxsplit=0)) + +SIM905.py:132:7: SIM905 [*] Consider using a list literal instead of `str.split` + | +130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 | print(" x ".rsplit(maxsplit=0)) +132 | print(" x ".rsplit(sep=None, maxsplit=0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905 + | + = help: Replace with list literal + +ℹ Safe fix +129 129 | print(" x ".rsplit(maxsplit=0)) +130 130 | print(" x ".rsplit(sep=None, maxsplit=0)) +131 131 | print(" x ".rsplit(maxsplit=0)) +132 |-print(" x ".rsplit(sep=None, maxsplit=0)) + 132 |+print([" x"]) From 2a217e80ca02518cc2df009ef3a140d4a6d33e8d Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 14 May 2025 20:23:53 +0200 Subject: [PATCH 097/487] [ty] mypy_primer: fix static-frame setup (#18103) ## Summary Pull in https://github.com/hauntsaninja/mypy_primer/pull/169 --- .github/workflows/mypy_primer.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mypy_primer.yaml b/.github/workflows/mypy_primer.yaml index 4bb37f0fb82be8..bdefa575ab3ce0 100644 --- a/.github/workflows/mypy_primer.yaml +++ b/.github/workflows/mypy_primer.yaml @@ -69,7 +69,7 @@ jobs: echo "Project selector: $PRIMER_SELECTOR" # Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs uvx \ - --from="git+https://github.com/hauntsaninja/mypy_primer@4b15cf3b07db69db67bbfaebfffb2a8a28040933" \ + --from="git+https://github.com/hauntsaninja/mypy_primer@968b2b61c05f84462d6fcc78d2f5205bbb8b98c2" \ mypy_primer \ --repo ruff \ --type-checker ty \ From 68559fc17d492f335dbab93214f6862b6b8c5515 Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Wed, 14 May 2025 20:24:15 +0200 Subject: [PATCH 098/487] [`flake8-simplify`] add fix safety section (`SIM103`) (#18086) The PR add the `fix safety` section for rule `SIM103` (#15584 ) ### Unsafe Fix Example ```python class Foo: def __eq__(self, other): return 1 def foo(): if Foo() == 1: return True return False def foo_fix(): return Foo() == 1 print(foo()) # True print(foo_fix()) # 1 ``` ### Note I updated the code snippet example, because I thought it was cool to have a correct example, i.e., that I can paste inside the playground and it works :-) --- .../flake8_simplify/rules/needless_bool.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 9b73053450e2c0..38d79fa0d4b24b 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -19,29 +19,40 @@ use crate::fix::snippet::SourceCodeSnippet; /// ## Example /// Given: /// ```python -/// if x > 0: -/// return True -/// else: -/// return False +/// def foo(x: int) -> bool: +/// if x > 0: +/// return True +/// else: +/// return False /// ``` /// /// Use instead: /// ```python -/// return x > 0 +/// def foo(x: int) -> bool: +/// return x > 0 /// ``` /// /// Or, given: /// ```python -/// if x > 0: -/// return True -/// return False +/// def foo(x: int) -> bool: +/// if x > 0: +/// return True +/// return False /// ``` /// /// Use instead: /// ```python -/// return x > 0 +/// def foo(x: int) -> bool: +/// return x > 0 /// ``` /// +/// ## Fix safety +/// +/// This fix is marked as unsafe because it may change the program’s behavior if the condition does not +/// return a proper Boolean. While the fix will try to wrap non-boolean values in a call to bool, +/// custom implementations of comparison functions like `__eq__` can avoid the bool call and still +/// lead to altered behavior. +/// /// ## References /// - [Python documentation: Truth Value Testing](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) #[derive(ViolationMetadata)] From 6800a9f6f3e57fc0395c14b10f12bf7c9017b46d Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 14 May 2025 20:56:44 +0200 Subject: [PATCH 099/487] [ty] Add type-expression syntax link to invalid-type-expression (#18104) ## Summary Add a link to [this page](https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions) when emitting `invalid-type-expression` diagnostics. --- crates/ty/docs/rules.md | 107 +++++++++--------- .../src/types/diagnostic.rs | 11 +- crates/ty_python_semantic/src/types/infer.rs | 11 +- ty.schema.json | 2 +- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index bcdd6de53433fd..b8dba78d7c9711 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -50,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L85) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L86) ## `conflicting-argument-forms` @@ -81,7 +81,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) ## `conflicting-declarations` @@ -111,7 +111,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) ## `conflicting-metaclass` @@ -142,7 +142,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L167) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L168) ## `cyclic-class-definition` @@ -173,7 +173,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) ## `division-by-zero` @@ -196,7 +196,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) ## `duplicate-base` @@ -222,7 +222,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238) ## `escape-character-in-forward-annotation` @@ -359,7 +359,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259) ## `inconsistent-mro` @@ -388,7 +388,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L344) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L345) ## `index-out-of-bounds` @@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L368) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L369) ## `invalid-argument-type` @@ -439,7 +439,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) ## `invalid-assignment` @@ -466,7 +466,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) ## `invalid-attribute-access` @@ -499,7 +499,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1314) ## `invalid-base` @@ -513,7 +513,7 @@ TODO #14889 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L451) ## `invalid-context-manager` @@ -539,7 +539,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L459) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L460) ## `invalid-declaration` @@ -567,7 +567,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L480) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L481) ## `invalid-exception-caught` @@ -608,7 +608,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L503) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L504) ## `invalid-generic-class` @@ -639,7 +639,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L539) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L540) ## `invalid-legacy-type-variable` @@ -672,7 +672,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) ## `invalid-metaclass` @@ -704,7 +704,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L593) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L594) ## `invalid-overload` @@ -752,7 +752,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L620) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L621) ## `invalid-parameter-default` @@ -777,7 +777,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L664) ## `invalid-protocol` @@ -810,7 +810,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L317) ## `invalid-raise` @@ -858,7 +858,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684) ## `invalid-return-type` @@ -882,7 +882,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L410) ## `invalid-super-argument` @@ -926,7 +926,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L727) ## `invalid-syntax-in-forward-annotation` @@ -969,7 +969,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) ## `invalid-type-form` @@ -980,7 +980,7 @@ TYPE_CHECKING = '' detects invalid type forms ### What it does -Checks for expressions that are used as type expressions +Checks for expressions that are used as [type expressions] but cannot validly be interpreted as such. ### Why is this bad? @@ -994,10 +994,11 @@ from typing import Annotated a: type[1] # `1` is not a type b: Annotated[int] # `Annotated` expects at least two arguments ``` +[type expressions]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L789) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L790) ## `invalid-type-variable-constraints` @@ -1031,7 +1032,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L814) ## `missing-argument` @@ -1055,7 +1056,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L843) ## `no-matching-overload` @@ -1083,7 +1084,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L860) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L862) ## `non-subscriptable` @@ -1106,7 +1107,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L883) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L885) ## `not-iterable` @@ -1131,7 +1132,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903) ## `parameter-already-assigned` @@ -1157,7 +1158,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L952) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954) ## `raw-string-type-annotation` @@ -1216,7 +1217,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1288) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) ## `subclass-of-final-class` @@ -1244,7 +1245,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1045) ## `too-many-positional-arguments` @@ -1270,7 +1271,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1090) ## `type-assertion-failure` @@ -1297,7 +1298,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1068) ## `unavailable-implicit-super-arguments` @@ -1341,7 +1342,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1109) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1111) ## `unknown-argument` @@ -1367,7 +1368,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1168) ## `unresolved-attribute` @@ -1394,7 +1395,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1189) ## `unresolved-import` @@ -1418,7 +1419,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1209) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1211) ## `unresolved-reference` @@ -1442,7 +1443,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1228) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) ## `unsupported-bool-conversion` @@ -1478,7 +1479,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L921) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923) ## `unsupported-operator` @@ -1505,7 +1506,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1247) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249) ## `zero-stepsize-in-slice` @@ -1529,7 +1530,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) ## `call-possibly-unbound-method` @@ -1547,7 +1548,7 @@ Calling an unbound method will raise an `AttributeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-possibly-unbound-method) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L103) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L104) ## `invalid-ignore-comment` @@ -1603,7 +1604,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L973) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L975) ## `possibly-unbound-import` @@ -1634,7 +1635,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L997) ## `redundant-cast` @@ -1660,7 +1661,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1340) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1342) ## `undefined-reveal` @@ -1683,7 +1684,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) ## `unknown-rule` @@ -1740,7 +1741,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1023) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index c6910b32df9e43..10cc4097160e09 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -9,6 +9,7 @@ use crate::types::string_annotation::{ IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; +use crate::types::LintDiagnosticGuard; use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type}; use crate::{declare_lint, Program}; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; @@ -788,7 +789,7 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for expressions that are used as type expressions + /// Checks for expressions that are used as [type expressions] /// but cannot validly be interpreted as such. /// /// ## Why is this bad? @@ -802,6 +803,7 @@ declare_lint! { /// a: type[1] # `1` is not a type /// b: Annotated[int] # `Annotated` expects at least two arguments /// ``` + /// [type expressions]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions pub(crate) static INVALID_TYPE_FORM = { summary: "detects invalid type forms", status: LintStatus::preview("1.0.0"), @@ -1774,6 +1776,13 @@ pub(crate) fn report_invalid_arguments_to_callable( )); } +pub(crate) fn add_type_expression_reference_link(mut diag: LintDiagnosticGuard) { + diag.info("See the following page for a reference on valid type expressions:"); + diag.info( + "https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions", + ); +} + pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol( context: &InferContext, call: &ast::ExprCall, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index adfe5c48e9ca42..d818187c023bc7 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -68,7 +68,7 @@ use crate::symbol::{ use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ - report_implicit_return_type, report_invalid_arguments_to_annotated, + self, report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, report_invalid_attribute_assignment, report_invalid_generator_function_return_type, report_invalid_return_type, report_possibly_unbound_attribute, TypeCheckDiagnostics, @@ -7743,7 +7743,8 @@ impl<'db> TypeInferenceBuilder<'db> { message: std::fmt::Arguments, ) -> Type<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, expression) { - builder.into_diagnostic(message); + let diag = builder.into_diagnostic(message); + diagnostic::add_type_expression_reference_link(diag); } Type::unknown() } @@ -8571,11 +8572,12 @@ impl<'db> TypeInferenceBuilder<'db> { } KnownInstanceType::ClassVar | KnownInstanceType::Final => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( + let diag = builder.into_diagnostic(format_args!( "Type qualifier `{}` is not allowed in type expressions \ (only in annotation expressions)", known_instance.repr(self.db()) )); + diagnostic::add_type_expression_reference_link(diag); } self.infer_type_expression(arguments_slice) } @@ -8799,11 +8801,12 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameters) { - builder.into_diagnostic(format_args!( + let diag = builder.into_diagnostic(format_args!( "The first argument to `Callable` \ must be either a list of types, \ ParamSpec, Concatenate, or `...`", )); + diagnostic::add_type_expression_reference_link(diag); } return None; } diff --git a/ty.schema.json b/ty.schema.json index 5734db20a3e0c5..85fd63614fcd48 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -562,7 +562,7 @@ }, "invalid-type-form": { "title": "detects invalid type forms", - "description": "## What it does\nChecks for expressions that are used as type expressions\nbut cannot validly be interpreted as such.\n\n## Why is this bad?\nSuch expressions cannot be understood by ty.\nIn some cases, they might raise errors at runtime.\n\n## Examples\n```python\nfrom typing import Annotated\n\na: type[1] # `1` is not a type\nb: Annotated[int] # `Annotated` expects at least two arguments\n```", + "description": "## What it does\nChecks for expressions that are used as [type expressions]\nbut cannot validly be interpreted as such.\n\n## Why is this bad?\nSuch expressions cannot be understood by ty.\nIn some cases, they might raise errors at runtime.\n\n## Examples\n```python\nfrom typing import Annotated\n\na: type[1] # `1` is not a type\nb: Annotated[int] # `Annotated` expects at least two arguments\n```\n[type expressions]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions", "default": "error", "oneOf": [ { From 33e14c5963d620550b45e99c3653b9d4e7a28378 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 14 May 2025 22:54:24 +0200 Subject: [PATCH 100/487] Update Neovim setup docs (#18108) ## Summary Nvim 0.11+ uses the builtin `vim.lsp.enable` and `vim.lsp.config` to enable and configure LSP clients. This adds the new non legacy way of configuring Nvim with `nvim-lspconfig` according to the upstream documentation. Update documentation for Nvim LSP configuration according to `nvim-lspconfig` and Nvim 0.11+ ## Test Plan Tested locally on macOS with Nvim 0.11.1 and `nvim-lspconfig` master/[ac1dfbe](https://github.com/neovim/nvim-lspconfig/tree/ac1dfbe3b60e5e23a2cff90e3bd6a3bc88031a57). --- docs/editors/setup.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/editors/setup.md b/docs/editors/setup.md index 0603a6b029b5d4..507e9d450c4618 100644 --- a/docs/editors/setup.md +++ b/docs/editors/setup.md @@ -36,15 +36,31 @@ Ruff Language Server in Neovim. To set it up, install [configuration](https://github.com/neovim/nvim-lspconfig#configuration) documentation, and add the following to your `init.lua`: -```lua -require('lspconfig').ruff.setup({ - init_options = { - settings = { - -- Ruff language server settings go here - } - } -}) -``` +=== "Neovim 0.10 (with [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig))" + + ```lua + require('lspconfig').ruff.setup({ + init_options = { + settings = { + -- Ruff language server settings go here + } + } + }) + ``` + +=== "Neovim 0.11+ (with [`vim.lsp.config`](https://neovim.io/doc/user/lsp.html#vim.lsp.config()))" + + ```lua + vim.lsp.config('ruff', { + init_options = { + settings = { + -- Ruff language server settings go here + } + } + }) + + vim.lsp.enable('ruff') + ``` !!! note From 466021d5e1793ca30e0d860cb1cd27e32f5233aa Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Wed, 14 May 2025 23:16:20 +0200 Subject: [PATCH 101/487] [`flake8-simplify`] add fix safety section (`SIM112`) (#18099) The PR add the `fix safety` section for rule `SIM112` (#15584 ). --- .../ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index 5131b6ad0fd55e..6a3f24fe6a89d7 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -32,6 +32,12 @@ use crate::checkers::ast::Checker; /// os.environ["FOO"] /// ``` /// +/// ## Fix safety +/// +/// This fix is always marked as unsafe because automatically capitalizing environment variable names +/// can change program behavior in environments where the variable names are case-sensitive, such as most +/// Unix-like systems. +/// /// ## References /// - [Python documentation: `os.environ`](https://docs.python.org/3/library/os.html#os.environ) #[derive(ViolationMetadata)] From b600ff106abf235ab701847e66f406b623b9881f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 22:14:52 -0400 Subject: [PATCH 102/487] Sync vendored typeshed stubs (#18110) Close and reopen this PR to trigger CI --------- Co-authored-by: typeshedbot <> Co-authored-by: Carl Meyer --- crates/ty_ide/src/goto.rs | 138 +++++----- .../resources/mdtest/pep695_type_aliases.md | 2 +- crates/ty_vendored/vendor/typeshed/README.md | 2 +- .../vendor/typeshed/source_commit.txt | 2 +- .../vendor/typeshed/stdlib/VERSIONS | 8 +- .../vendor/typeshed/stdlib/__main__.pyi | 4 +- .../vendor/typeshed/stdlib/_ast.pyi | 9 +- .../vendor/typeshed/stdlib/_compression.pyi | 8 +- .../vendor/typeshed/stdlib/_contextvars.pyi | 8 +- .../vendor/typeshed/stdlib/_csv.pyi | 9 +- .../vendor/typeshed/stdlib/_ctypes.pyi | 17 +- .../vendor/typeshed/stdlib/_decimal.pyi | 5 + .../vendor/typeshed/stdlib/_io.pyi | 40 ++- .../vendor/typeshed/stdlib/_pydecimal.pyi | 4 + .../vendor/typeshed/stdlib/_socket.pyi | 29 ++- .../vendor/typeshed/stdlib/_ssl.pyi | 2 + .../typeshed/stdlib/_typeshed/__init__.pyi | 16 +- .../_typeshed/_type_checker_internals.pyi | 89 +++++++ .../vendor/typeshed/stdlib/annotationlib.pyi | 132 ++++++++++ .../vendor/typeshed/stdlib/argparse.pyi | 135 ++++++++-- .../vendor/typeshed/stdlib/ast.pyi | 245 +++++++++--------- .../typeshed/stdlib/asyncio/__init__.pyi | 18 ++ .../vendor/typeshed/stdlib/asyncio/events.pyi | 4 +- .../typeshed/stdlib/asyncio/futures.pyi | 10 +- .../vendor/typeshed/stdlib/asyncio/graph.pyi | 26 ++ .../vendor/typeshed/stdlib/bdb.pyi | 17 +- .../vendor/typeshed/stdlib/builtins.pyi | 156 ++++++++--- .../vendor/typeshed/stdlib/bz2.pyi | 10 +- .../vendor/typeshed/stdlib/code.pyi | 4 +- .../vendor/typeshed/stdlib/codeop.pyi | 6 +- .../typeshed/stdlib/compression/__init__.pyi | 0 .../stdlib/compression/_common/__init__.pyi | 0 .../stdlib/compression/_common/_streams.pyi | 25 ++ .../stdlib/compression/bz2/__init__.pyi | 1 + .../stdlib/compression/gzip/__init__.pyi | 1 + .../stdlib/compression/lzma/__init__.pyi | 1 + .../stdlib/compression/zlib/__init__.pyi | 1 + .../stdlib/concurrent/futures/__init__.pyi | 22 +- .../stdlib/concurrent/futures/_base.pyi | 17 +- .../stdlib/concurrent/futures/interpreter.pyi | 102 ++++++++ .../stdlib/concurrent/futures/process.pyi | 4 + .../stdlib/concurrent/futures/thread.pyi | 94 +++++-- .../vendor/typeshed/stdlib/configparser.pyi | 35 ++- .../typeshed/stdlib/ctypes/__init__.pyi | 21 +- .../vendor/typeshed/stdlib/dataclasses.pyi | 106 +++++++- .../vendor/typeshed/stdlib/datetime.pyi | 10 + .../vendor/typeshed/stdlib/decimal.pyi | 8 + .../vendor/typeshed/stdlib/dis.pyi | 85 +++++- .../vendor/typeshed/stdlib/email/__init__.pyi | 3 +- .../typeshed/stdlib/email/_policybase.pyi | 25 +- .../typeshed/stdlib/email/feedparser.pyi | 5 +- .../typeshed/stdlib/email/generator.pyi | 2 +- .../vendor/typeshed/stdlib/email/message.pyi | 38 +-- .../typeshed/stdlib/email/mime/message.pyi | 3 +- .../typeshed/stdlib/email/mime/multipart.pyi | 3 +- .../typeshed/stdlib/email/mime/text.pyi | 2 +- .../vendor/typeshed/stdlib/email/parser.pyi | 17 +- .../vendor/typeshed/stdlib/email/policy.pyi | 8 +- .../typeshed/stdlib/encodings/__init__.pyi | 3 +- .../vendor/typeshed/stdlib/fnmatch.pyi | 6 + .../vendor/typeshed/stdlib/functools.pyi | 71 +++-- .../vendor/typeshed/stdlib/getpass.pyi | 8 +- .../vendor/typeshed/stdlib/gzip.pyi | 10 +- .../vendor/typeshed/stdlib/http/client.pyi | 2 +- .../vendor/typeshed/stdlib/http/server.pyi | 56 +++- .../vendor/typeshed/stdlib/imaplib.pyi | 41 ++- .../vendor/typeshed/stdlib/inspect.pyi | 60 ++++- .../ty_vendored/vendor/typeshed/stdlib/io.pyi | 15 +- .../vendor/typeshed/stdlib/ipaddress.pyi | 29 ++- .../typeshed/stdlib/logging/handlers.pyi | 24 +- .../vendor/typeshed/stdlib/lzma.pyi | 7 +- .../vendor/typeshed/stdlib/mailbox.pyi | 2 +- .../vendor/typeshed/stdlib/marshal.pyi | 20 +- .../vendor/typeshed/stdlib/nturl2path.pyi | 14 +- .../vendor/typeshed/stdlib/os/__init__.pyi | 11 + .../{pathlib.pyi => pathlib/__init__.pyi} | 51 ++-- .../vendor/typeshed/stdlib/pathlib/types.pyi | 8 + .../vendor/typeshed/stdlib/pdb.pyi | 74 ++++-- .../vendor/typeshed/stdlib/pkgutil.pyi | 14 +- .../vendor/typeshed/stdlib/platform.pyi | 3 + .../vendor/typeshed/stdlib/posix.pyi | 6 + .../vendor/typeshed/stdlib/socket.pyi | 33 +++ .../typeshed/stdlib/sqlite3/__init__.pyi | 4 +- .../{string.pyi => string/__init__.pyi} | 0 .../typeshed/stdlib/string/templatelib.pyi | 28 ++ .../vendor/typeshed/stdlib/tempfile.pyi | 2 +- .../vendor/typeshed/stdlib/threading.pyi | 38 ++- .../typeshed/stdlib/tkinter/__init__.pyi | 5 +- .../vendor/typeshed/stdlib/tkinter/ttk.pyi | 2 +- .../vendor/typeshed/stdlib/token.pyi | 8 + .../vendor/typeshed/stdlib/tokenize.pyi | 3 + .../vendor/typeshed/stdlib/tomllib.pyi | 20 +- .../vendor/typeshed/stdlib/traceback.pyi | 5 +- .../vendor/typeshed/stdlib/types.pyi | 13 +- .../vendor/typeshed/stdlib/typing.pyi | 244 +++++++++++------ .../typeshed/stdlib/typing_extensions.pyi | 153 +++++++---- .../vendor/typeshed/stdlib/unittest/case.pyi | 11 + .../vendor/typeshed/stdlib/urllib/request.pyi | 194 +++++++------- .../vendor/typeshed/stdlib/uuid.pyi | 28 +- 99 files changed, 2370 insertions(+), 750 deletions(-) create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/asyncio/graph.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/bz2/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/gzip/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/lzma/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/zlib/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi rename crates/ty_vendored/vendor/typeshed/stdlib/{pathlib.pyi => pathlib/__init__.pyi} (87%) create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi rename crates/ty_vendored/vendor/typeshed/stdlib/{string.pyi => string/__init__.pyi} (100%) create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index 862a371aa1a7c4..eea630bcf157f2 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -421,16 +421,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r#" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:13 @@ -440,7 +440,7 @@ mod tests { 4 | a | ^ | - "#); + "###); } #[test] fn goto_type_of_expression_with_literal_node() { @@ -450,16 +450,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r#" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:2:22 @@ -467,7 +467,7 @@ mod tests { 2 | a: str = "test" | ^^^^^^ | - "#); + "###); } #[test] @@ -532,16 +532,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r#" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:18 @@ -551,7 +551,7 @@ mod tests { 4 | test(a= "123") | ^ | - "#); + "###); } #[test] @@ -567,16 +567,16 @@ mod tests { // TODO: This should jump to `str` and not `int` because // the keyword is typed as a string. It's only the passed argument that // is an int. Navigating to `str` would match pyright's behavior. - assert_snapshot!(test.goto_type_definition(), @r" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:231:7 + --> stdlib/builtins.pyi:238:7 | - 229 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed - 230 | - 231 | class int: + 236 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed + 237 | + 238 | class int: | ^^^ - 232 | @overload - 233 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ... + 239 | @overload + 240 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ... | info: Source --> main.py:4:18 @@ -586,7 +586,7 @@ mod tests { 4 | test(a= 123) | ^ | - "); + "###); } #[test] @@ -601,16 +601,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r#" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:1086:7 + --> stdlib/builtins.pyi:1096:7 | - 1084 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - 1085 | - 1086 | class dict(MutableMapping[_KT, _VT]): + 1094 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + 1095 | + 1096 | class dict(MutableMapping[_KT, _VT]): | ^^^^ - 1087 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics - 1088 | # Also multiprocessing.managers.SyncManager.dict() + 1097 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics + 1098 | # Also multiprocessing.managers.SyncManager.dict() | info: Source --> main.py:6:5 @@ -620,7 +620,7 @@ f(**kwargs) 6 | f(**kwargs) | ^^^^^^ | - "#); + "###); } #[test] @@ -632,16 +632,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:3:17 @@ -650,7 +650,7 @@ f(**kwargs) 3 | a | ^ | - "); + "###); } #[test] @@ -725,16 +725,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:27 @@ -744,7 +744,7 @@ f(**kwargs) 4 | print(a) | ^ | - "); + "###); } #[test] @@ -756,15 +756,15 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r" + assert_snapshot!(test.goto_type_definition(), @r###" info[goto-type-definition]: Type definition - --> stdlib/types.pyi:671:11 + --> stdlib/types.pyi:680:11 | - 669 | if sys.version_info >= (3, 10): - 670 | @final - 671 | class NoneType: + 678 | if sys.version_info >= (3, 10): + 679 | @final + 680 | class NoneType: | ^^^^^^^^ - 672 | def __bool__(self) -> Literal[False]: ... + 681 | def __bool__(self) -> Literal[False]: ... | info: Source --> main.py:3:17 @@ -775,14 +775,14 @@ f(**kwargs) | info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:438:7 + --> stdlib/builtins.pyi:445:7 | - 436 | def __getitem__(self, key: int, /) -> str | int | None: ... - 437 | - 438 | class str(Sequence[str]): + 443 | def __getitem__(self, key: int, /) -> str | int | None: ... + 444 | + 445 | class str(Sequence[str]): | ^^^ - 439 | @overload - 440 | def __new__(cls, object: object = ...) -> Self: ... + 446 | @overload + 447 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:3:17 @@ -791,7 +791,7 @@ f(**kwargs) 3 | a | ^ | - "); + "###); } impl CursorTest { diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md index c7b4820b292e4d..efe494babed322 100644 --- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md @@ -28,7 +28,7 @@ def f() -> None: ```py type IntOrStr = int | str -reveal_type(IntOrStr.__value__) # revealed: Any +reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`) ``` ## Invalid assignment diff --git a/crates/ty_vendored/vendor/typeshed/README.md b/crates/ty_vendored/vendor/typeshed/README.md index b52ecf3a5de983..ee09529b967ac1 100644 --- a/crates/ty_vendored/vendor/typeshed/README.md +++ b/crates/ty_vendored/vendor/typeshed/README.md @@ -21,7 +21,7 @@ the project the stubs are for, but instead report them here to typeshed.** Further documentation on stub files, typeshed, and Python's typing system in general, can also be found at https://typing.readthedocs.io/en/latest/. -Typeshed supports Python versions 3.9 to 3.13. +Typeshed supports Python versions 3.9 to 3.14. ## Using diff --git a/crates/ty_vendored/vendor/typeshed/source_commit.txt b/crates/ty_vendored/vendor/typeshed/source_commit.txt index b3112953f24d5c..fdd24bb31d9100 100644 --- a/crates/ty_vendored/vendor/typeshed/source_commit.txt +++ b/crates/ty_vendored/vendor/typeshed/source_commit.txt @@ -1 +1 @@ -eec809d049d10a5ae9b88780eab15fe36a9768d7 +1063db7c15135c172f1f6a81d3aff6d1cb00a980 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS b/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS index fec56ce59e36b2..1ecd8af6455953 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS +++ b/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS @@ -28,7 +28,7 @@ _bz2: 3.3- _codecs: 3.0- _collections_abc: 3.3- _compat_pickle: 3.1- -_compression: 3.5- +_compression: 3.5-3.13 _contextvars: 3.7- _csv: 3.0- _ctypes: 3.0- @@ -78,6 +78,7 @@ _weakrefset: 3.0- _winapi: 3.3- abc: 3.0- aifc: 3.0-3.12 +annotationlib: 3.14- antigravity: 3.0- argparse: 3.0- array: 3.0- @@ -86,6 +87,7 @@ asynchat: 3.0-3.11 asyncio: 3.4- asyncio.exceptions: 3.8- asyncio.format_helpers: 3.7- +asyncio.graph: 3.14- asyncio.mixins: 3.10- asyncio.runners: 3.7- asyncio.staggered: 3.8- @@ -117,7 +119,9 @@ collections: 3.0- collections.abc: 3.3- colorsys: 3.0- compileall: 3.0- +compression: 3.14- concurrent: 3.2- +concurrent.futures.interpreter: 3.14- configparser: 3.0- contextlib: 3.0- contextvars: 3.7- @@ -226,6 +230,7 @@ os: 3.0- ossaudiodev: 3.0-3.12 parser: 3.0-3.9 pathlib: 3.4- +pathlib.types: 3.14- pdb: 3.0- pickle: 3.0- pickletools: 3.0- @@ -278,6 +283,7 @@ ssl: 3.0- stat: 3.0- statistics: 3.4- string: 3.0- +string.templatelib: 3.14- stringprep: 3.0- struct: 3.0- subprocess: 3.0- diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/__main__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/__main__.pyi index e27843e5338213..5b0f74feb261ba 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/__main__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/__main__.pyi @@ -1,3 +1 @@ -from typing import Any - -def __getattr__(name: str) -> Any: ... +def __getattr__(name: str): ... # incomplete module diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_ast.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_ast.pyi index bc0ebd9d8a0f8d..00c6b357f7d807 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_ast.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_ast.pyi @@ -111,13 +111,20 @@ from ast import ( from typing import Literal if sys.version_info >= (3, 12): - from ast import ParamSpec as ParamSpec, TypeVar as TypeVar, TypeVarTuple as TypeVarTuple, type_param as type_param + from ast import ( + ParamSpec as ParamSpec, + TypeAlias as TypeAlias, + TypeVar as TypeVar, + TypeVarTuple as TypeVarTuple, + type_param as type_param, + ) if sys.version_info >= (3, 11): from ast import TryStar as TryStar if sys.version_info >= (3, 10): from ast import ( + Match as Match, MatchAs as MatchAs, MatchClass as MatchClass, MatchMapping as MatchMapping, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_compression.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_compression.pyi index a41a8142cc3ac6..80d38b4db824b4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_compression.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_compression.pyi @@ -1,4 +1,6 @@ -from _typeshed import WriteableBuffer +# _compression is replaced by compression._common._streams on Python 3.14+ (PEP-784) + +from _typeshed import Incomplete, WriteableBuffer from collections.abc import Callable from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase from typing import Any, Protocol @@ -16,9 +18,9 @@ class DecompressReader(RawIOBase): def __init__( self, fp: _Reader, - decomp_factory: Callable[..., object], + decomp_factory: Callable[..., Incomplete], trailing_error: type[Exception] | tuple[type[Exception], ...] = (), - **decomp_args: Any, + **decomp_args: Any, # These are passed to decomp_factory. ) -> None: ... def readinto(self, b: WriteableBuffer) -> int: ... def read(self, size: int = -1) -> bytes: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_contextvars.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_contextvars.pyi index 33df799a768c34..e2e2e4df9d0867 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_contextvars.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_contextvars.pyi @@ -1,5 +1,6 @@ +import sys from collections.abc import Callable, Iterator, Mapping -from types import GenericAlias +from types import GenericAlias, TracebackType from typing import Any, ClassVar, Generic, TypeVar, final, overload from typing_extensions import ParamSpec, Self @@ -35,6 +36,11 @@ class Token(Generic[_T]): MISSING: ClassVar[object] __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + if sys.version_info >= (3, 14): + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None + ) -> None: ... def copy_context() -> Context: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_csv.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_csv.pyi index aa9fc538417e3c..ecea4878907c49 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_csv.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_csv.pyi @@ -2,7 +2,7 @@ import csv import sys from _typeshed import SupportsWrite from collections.abc import Iterable -from typing import Any, Final, type_check_only +from typing import Any, Final, Literal, type_check_only from typing_extensions import Self, TypeAlias __version__: Final[str] @@ -15,9 +15,10 @@ if sys.version_info >= (3, 12): QUOTE_STRINGS: Final = 4 QUOTE_NOTNULL: Final = 5 -# Ideally this would be `QUOTE_ALL | QUOTE_MINIMAL | QUOTE_NONE | QUOTE_NONNUMERIC` -# However, using literals in situations like these can cause false-positives (see #7258) -_QuotingType: TypeAlias = int +if sys.version_info >= (3, 12): + _QuotingType: TypeAlias = Literal[0, 1, 2, 3, 4, 5] +else: + _QuotingType: TypeAlias = Literal[0, 1, 2, 3] class Error(Exception): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi index 4cbb030bb13624..944685646c36dc 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi @@ -131,18 +131,23 @@ class _Pointer(_PointerLike, _CData, Generic[_CT], metaclass=_PyCPointerType): def __getitem__(self, key: slice, /) -> list[Any]: ... def __setitem__(self, key: int, value: Any, /) -> None: ... -@overload -def POINTER(type: None, /) -> type[c_void_p]: ... -@overload -def POINTER(type: type[_CT], /) -> type[_Pointer[_CT]]: ... -def pointer(obj: _CT, /) -> _Pointer[_CT]: ... +if sys.version_info < (3, 14): + @overload + def POINTER(type: None, /) -> type[c_void_p]: ... + @overload + def POINTER(type: type[_CT], /) -> type[_Pointer[_CT]]: ... + def pointer(obj: _CT, /) -> _Pointer[_CT]: ... # This class is not exposed. It calls itself _ctypes.CArgObject. @final @type_check_only class _CArgObject: ... -def byref(obj: _CData | _CDataType, offset: int = ...) -> _CArgObject: ... +if sys.version_info >= (3, 14): + def byref(obj: _CData | _CDataType, offset: int = 0, /) -> _CArgObject: ... + +else: + def byref(obj: _CData | _CDataType, offset: int = 0) -> _CArgObject: ... _ECT: TypeAlias = Callable[[_CData | _CDataType | None, CFuncPtr, tuple[_CData | _CDataType, ...]], _CDataType] _PF: TypeAlias = tuple[int] | tuple[int, str | None] | tuple[int, str | None, Any] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_decimal.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_decimal.pyi index 06c0197dcf07c8..fd0e6e6ac0914a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_decimal.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_decimal.pyi @@ -41,6 +41,8 @@ MAX_EMAX: Final[int] MAX_PREC: Final[int] MIN_EMIN: Final[int] MIN_ETINY: Final[int] +if sys.version_info >= (3, 14): + IEEE_CONTEXT_MAX_BITS: Final[int] def setcontext(context: Context, /) -> None: ... def getcontext() -> Context: ... @@ -62,6 +64,9 @@ if sys.version_info >= (3, 11): else: def localcontext(ctx: Context | None = None) -> _ContextManager: ... +if sys.version_info >= (3, 14): + def IEEEContext(bits: int, /) -> Context: ... + DefaultContext: Context BasicContext: Context ExtendedContext: Context diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_io.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_io.pyi index 54efd319976034..c77d75287c25ad 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_io.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_io.pyi @@ -88,9 +88,36 @@ class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] def readlines(self, size: int | None = None, /) -> list[bytes]: ... def seek(self, pos: int, whence: int = 0, /) -> int: ... -class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes - raw: RawIOBase - def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ... +class _BufferedReaderStream(Protocol): + def read(self, n: int = ..., /) -> bytes: ... + # Optional: def readall(self) -> bytes: ... + def readinto(self, b: memoryview, /) -> int | None: ... + def seek(self, pos: int, whence: int, /) -> int: ... + def tell(self) -> int: ... + def truncate(self, size: int, /) -> int: ... + def flush(self) -> object: ... + def close(self) -> object: ... + @property + def closed(self) -> bool: ... + def readable(self) -> bool: ... + def seekable(self) -> bool: ... + + # The following methods just pass through to the underlying stream. Since + # not all streams support them, they are marked as optional here, and will + # raise an AttributeError if called on a stream that does not support them. + + # @property + # def name(self) -> Any: ... # Type is inconsistent between the various I/O types. + # @property + # def mode(self) -> str: ... + # def fileno(self) -> int: ... + # def isatty(self) -> bool: ... + +_BufferedReaderStreamT = TypeVar("_BufferedReaderStreamT", bound=_BufferedReaderStream, default=_BufferedReaderStream) + +class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO, Generic[_BufferedReaderStreamT]): # type: ignore[misc] # incompatible definitions of methods in the base classes + raw: _BufferedReaderStreamT + def __init__(self, raw: _BufferedReaderStreamT, buffer_size: int = 8192) -> None: ... def peek(self, size: int = 0, /) -> bytes: ... def seek(self, target: int, whence: int = 0, /) -> int: ... def truncate(self, pos: int | None = None, /) -> int: ... @@ -111,8 +138,8 @@ class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore def peek(self, size: int = 0, /) -> bytes: ... def truncate(self, pos: int | None = None, /) -> int: ... -class BufferedRWPair(BufferedIOBase, _BufferedIOBase): - def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = 8192, /) -> None: ... +class BufferedRWPair(BufferedIOBase, _BufferedIOBase, Generic[_BufferedReaderStreamT]): + def __init__(self, reader: _BufferedReaderStreamT, writer: RawIOBase, buffer_size: int = 8192, /) -> None: ... def peek(self, size: int = 0, /) -> bytes: ... class _TextIOBase(_IOBase): @@ -131,8 +158,7 @@ class _TextIOBase(_IOBase): @type_check_only class _WrappedBuffer(Protocol): # "name" is wrapped by TextIOWrapper. Its type is inconsistent between - # the various I/O types, see the comments on TextIOWrapper.name and - # TextIO.name. + # the various I/O types. @property def name(self) -> Any: ... @property diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_pydecimal.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_pydecimal.pyi index faff626ac0baee..a6723f749da6d2 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_pydecimal.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_pydecimal.pyi @@ -1,5 +1,6 @@ # This is a slight lie, the implementations aren't exactly identical # However, in all likelihood, the differences are inconsequential +import sys from _decimal import * __all__ = [ @@ -41,3 +42,6 @@ __all__ = [ "HAVE_THREADS", "HAVE_CONTEXTVAR", ] + +if sys.version_info >= (3, 14): + __all__ += ["IEEEContext", "IEEE_CONTEXT_MAX_BITS"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi index 5399f4edf01068..06a8a2ba5fa060 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi @@ -229,6 +229,28 @@ if sys.platform != "win32": IP_RECVOPTS: int IP_RECVRETOPTS: int IP_RETOPTS: int +if sys.version_info >= (3, 14): + IP_RECVTTL: int + + if sys.platform == "win32" or sys.platform == "linux": + IPV6_RECVERR: int + IP_RECVERR: int + SO_ORIGINAL_DST: int + + if sys.platform == "win32": + SOL_RFCOMM: int + SO_BTH_ENCRYPT: int + SO_BTH_MTU: int + SO_BTH_MTU_MAX: int + SO_BTH_MTU_MIN: int + TCP_QUICKACK: int + + if sys.platform == "linux": + CAN_RAW_ERR_FILTER: int + IP_FREEBIND: int + IP_RECVORIGDSTADDR: int + VMADDR_CID_LOCAL: int + if sys.platform != "win32" and sys.platform != "darwin": IP_TRANSPARENT: int if sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 11): @@ -829,6 +851,11 @@ if sys.platform != "win32": def if_nameindex() -> list[tuple[int, str]]: ... def if_nametoindex(oname: str, /) -> int: ... -def if_indextoname(index: int, /) -> str: ... + +if sys.version_info >= (3, 14): + def if_indextoname(if_index: int, /) -> str: ... + +else: + def if_indextoname(index: int, /) -> str: ... CAPI: CapsuleType diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_ssl.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_ssl.pyi index e39ab5eb6de803..7ab880e4def742 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_ssl.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_ssl.pyi @@ -283,6 +283,8 @@ HAS_TLSv1: bool HAS_TLSv1_1: bool HAS_TLSv1_2: bool HAS_TLSv1_3: bool +if sys.version_info >= (3, 14): + HAS_PHA: bool # version info OPENSSL_VERSION_NUMBER: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi index a503637998d023..c37d55a7d9ec32 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi @@ -353,7 +353,10 @@ class DataclassInstance(Protocol): __dataclass_fields__: ClassVar[dict[str, Field[Any]]] # Anything that can be passed to the int/float constructors -ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc +if sys.version_info >= (3, 14): + ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex +else: + ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc ConvertibleToFloat: TypeAlias = str | ReadableBuffer | SupportsFloat | SupportsIndex # A few classes updated from Foo(str, Enum) to Foo(StrEnum). This is a convenience so these @@ -364,3 +367,14 @@ else: from enum import Enum class StrEnum(str, Enum): ... + +# Objects that appear in annotations or in type expressions. +# Similar to PEP 747's TypeForm but a little broader. +AnnotationForm: TypeAlias = Any + +if sys.version_info >= (3, 14): + from annotationlib import Format + + # These return annotations, which can be arbitrary objects + AnnotateFunc: TypeAlias = Callable[[Format], dict[str, AnnotationForm]] + EvaluateFunc: TypeAlias = Callable[[Format], AnnotationForm] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi new file mode 100644 index 00000000000000..feb22aae007320 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi @@ -0,0 +1,89 @@ +# Internals used by some type checkers. +# +# Don't use this module directly. It is only for type checkers to use. + +import sys +import typing_extensions +from _collections_abc import dict_items, dict_keys, dict_values +from abc import ABCMeta +from collections.abc import Awaitable, Generator, Iterable, Mapping +from typing import Any, ClassVar, Generic, TypeVar, overload +from typing_extensions import Never + +_T = TypeVar("_T") + +# Used for an undocumented mypy feature. Does not exist at runtime. +promote = object() + +# Fallback type providing methods and attributes that appear on all `TypedDict` types. +# N.B. Keep this mostly in sync with typing_extensions._TypedDict/mypy_extensions._TypedDict +class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta): + __total__: ClassVar[bool] + __required_keys__: ClassVar[frozenset[str]] + __optional_keys__: ClassVar[frozenset[str]] + # __orig_bases__ sometimes exists on <3.12, but not consistently, + # so we only add it to the stub on 3.12+ + if sys.version_info >= (3, 12): + __orig_bases__: ClassVar[tuple[Any, ...]] + if sys.version_info >= (3, 13): + __readonly_keys__: ClassVar[frozenset[str]] + __mutable_keys__: ClassVar[frozenset[str]] + + def copy(self) -> typing_extensions.Self: ... + # Using Never so that only calls using mypy plugin hook that specialize the signature + # can go through. + def setdefault(self, k: Never, default: object) -> object: ... + # Mypy plugin hook for 'pop' expects that 'default' has a type variable type. + def pop(self, k: Never, default: _T = ...) -> object: ... # pyright: ignore[reportInvalidTypeVarUse] + def update(self, m: typing_extensions.Self, /) -> None: ... + def __delitem__(self, k: Never) -> None: ... + def items(self) -> dict_items[str, object]: ... + def keys(self) -> dict_keys[str, object]: ... + def values(self) -> dict_values[str, object]: ... + @overload + def __or__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... + @overload + def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload + def __ror__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... + @overload + def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of __or__ and __ior__ + def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... # type: ignore[misc] + +# Fallback type providing methods and attributes that appear on all `NamedTuple` types. +class NamedTupleFallback(tuple[Any, ...]): + _field_defaults: ClassVar[dict[str, Any]] + _fields: ClassVar[tuple[str, ...]] + # __orig_bases__ sometimes exists on <3.12, but not consistently + # So we only add it to the stub on 3.12+. + if sys.version_info >= (3, 12): + __orig_bases__: ClassVar[tuple[Any, ...]] + + @overload + def __init__(self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None: ... + @overload + @typing_extensions.deprecated( + "Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15" + ) + def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ... + @classmethod + def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ... + def _asdict(self) -> dict[str, Any]: ... + def _replace(self, **kwargs: Any) -> typing_extensions.Self: ... + if sys.version_info >= (3, 13): + def __replace__(self, **kwargs: Any) -> typing_extensions.Self: ... + +# Non-default variations to accommodate couroutines, and `AwaitableGenerator` having a 4th type parameter. +_S = TypeVar("_S") +_YieldT_co = TypeVar("_YieldT_co", covariant=True) +_SendT_nd_contra = TypeVar("_SendT_nd_contra", contravariant=True) +_ReturnT_nd_co = TypeVar("_ReturnT_nd_co", covariant=True) + +# The parameters correspond to Generator, but the 4th is the original type. +class AwaitableGenerator( + Awaitable[_ReturnT_nd_co], + Generator[_YieldT_co, _SendT_nd_contra, _ReturnT_nd_co], + Generic[_YieldT_co, _SendT_nd_contra, _ReturnT_nd_co, _S], + metaclass=ABCMeta, +): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi new file mode 100644 index 00000000000000..7590c632d7856b --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi @@ -0,0 +1,132 @@ +import sys +from typing import Literal + +if sys.version_info >= (3, 14): + import enum + import types + from _typeshed import AnnotateFunc, AnnotationForm, EvaluateFunc, SupportsItems + from collections.abc import Mapping + from typing import Any, ParamSpec, TypeVar, TypeVarTuple, final, overload + from warnings import deprecated + + __all__ = [ + "Format", + "ForwardRef", + "call_annotate_function", + "call_evaluate_function", + "get_annotate_from_class_namespace", + "get_annotations", + "annotations_to_string", + "type_repr", + ] + + class Format(enum.IntEnum): + VALUE = 1 + VALUE_WITH_FAKE_GLOBALS = 2 + FORWARDREF = 3 + STRING = 4 + + @final + class ForwardRef: + __forward_is_argument__: bool + __forward_is_class__: bool + __forward_module__: str | None + def __init__( + self, arg: str, *, module: str | None = None, owner: object = None, is_argument: bool = True, is_class: bool = False + ) -> None: ... + @overload + def evaluate( + self, + *, + globals: dict[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] | None = None, + owner: object = None, + format: Literal[Format.STRING], + ) -> str: ... + @overload + def evaluate( + self, + *, + globals: dict[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] | None = None, + owner: object = None, + format: Literal[Format.FORWARDREF], + ) -> AnnotationForm | ForwardRef: ... + @overload + def evaluate( + self, + *, + globals: dict[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] | None = None, + owner: object = None, + format: Format = Format.VALUE, # noqa: Y011 + ) -> AnnotationForm: ... + @deprecated("Use ForwardRef.evaluate() or typing.evaluate_forward_ref() instead.") + def _evaluate( + self, + globalns: dict[str, Any] | None, + localns: Mapping[str, Any] | None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = ..., + *, + recursive_guard: frozenset[str], + ) -> AnnotationForm: ... + @property + def __forward_arg__(self) -> str: ... + @property + def __forward_code__(self) -> types.CodeType: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __or__(self, other: Any) -> types.UnionType: ... + def __ror__(self, other: Any) -> types.UnionType: ... + + @overload + def call_evaluate_function(evaluate: EvaluateFunc, format: Literal[Format.STRING], *, owner: object = None) -> str: ... + @overload + def call_evaluate_function( + evaluate: EvaluateFunc, format: Literal[Format.FORWARDREF], *, owner: object = None + ) -> AnnotationForm | ForwardRef: ... + @overload + def call_evaluate_function(evaluate: EvaluateFunc, format: Format, *, owner: object = None) -> AnnotationForm: ... + @overload + def call_annotate_function( + annotate: AnnotateFunc, format: Literal[Format.STRING], *, owner: object = None + ) -> dict[str, str]: ... + @overload + def call_annotate_function( + annotate: AnnotateFunc, format: Literal[Format.FORWARDREF], *, owner: object = None + ) -> dict[str, AnnotationForm | ForwardRef]: ... + @overload + def call_annotate_function(annotate: AnnotateFunc, format: Format, *, owner: object = None) -> dict[str, AnnotationForm]: ... + def get_annotate_from_class_namespace(obj: Mapping[str, object]) -> AnnotateFunc | None: ... + @overload + def get_annotations( + obj: Any, # any object with __annotations__ or __annotate__ + *, + globals: dict[str, object] | None = None, + locals: Mapping[str, object] | None = None, + eval_str: bool = False, + format: Literal[Format.STRING], + ) -> dict[str, str]: ... + @overload + def get_annotations( + obj: Any, + *, + globals: dict[str, object] | None = None, + locals: Mapping[str, object] | None = None, + eval_str: bool = False, + format: Literal[Format.FORWARDREF], + ) -> dict[str, AnnotationForm | ForwardRef]: ... + @overload + def get_annotations( + obj: Any, + *, + globals: dict[str, object] | None = None, + locals: Mapping[str, object] | None = None, + eval_str: bool = False, + format: Format = Format.VALUE, # noqa: Y011 + ) -> dict[str, AnnotationForm]: ... + def type_repr(value: object) -> str: ... + def annotations_to_string(annotations: SupportsItems[str, object]) -> dict[str, str]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi index 32beaff14696b3..95ad6c7da8ebff 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi @@ -123,6 +123,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): fromfile_prefix_chars: str | None add_help: bool allow_abbrev: bool + exit_on_error: bool + + if sys.version_info >= (3, 14): + suggest_on_error: bool + color: bool # undocumented _positionals: _ArgumentGroup @@ -130,22 +135,44 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): _subparsers: _ArgumentGroup | None # Note: the constructor arguments are also used in _SubParsersAction.add_parser. - def __init__( - self, - prog: str | None = None, - usage: str | None = None, - description: str | None = None, - epilog: str | None = None, - parents: Sequence[ArgumentParser] = [], - formatter_class: _FormatterClass = ..., - prefix_chars: str = "-", - fromfile_prefix_chars: str | None = None, - argument_default: Any = None, - conflict_handler: str = "error", - add_help: bool = True, - allow_abbrev: bool = True, - exit_on_error: bool = True, - ) -> None: ... + if sys.version_info >= (3, 14): + def __init__( + self, + prog: str | None = None, + usage: str | None = None, + description: str | None = None, + epilog: str | None = None, + parents: Sequence[ArgumentParser] = [], + formatter_class: _FormatterClass = ..., + prefix_chars: str = "-", + fromfile_prefix_chars: str | None = None, + argument_default: Any = None, + conflict_handler: str = "error", + add_help: bool = True, + allow_abbrev: bool = True, + exit_on_error: bool = True, + *, + suggest_on_error: bool = False, + color: bool = False, + ) -> None: ... + else: + def __init__( + self, + prog: str | None = None, + usage: str | None = None, + description: str | None = None, + epilog: str | None = None, + parents: Sequence[ArgumentParser] = [], + formatter_class: _FormatterClass = ..., + prefix_chars: str = "-", + fromfile_prefix_chars: str | None = None, + argument_default: Any = None, + conflict_handler: str = "error", + add_help: bool = True, + allow_abbrev: bool = True, + exit_on_error: bool = True, + ) -> None: ... + @overload def parse_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... @overload @@ -252,7 +279,21 @@ class HelpFormatter: def __init__(self, formatter: HelpFormatter, parent: Self | None, heading: str | None = None) -> None: ... def format_help(self) -> str: ... - def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None) -> None: ... + if sys.version_info >= (3, 14): + def __init__( + self, + prog: str, + indent_increment: int = 2, + max_help_position: int = 24, + width: int | None = None, + prefix_chars: str = "-", + color: bool = False, + ) -> None: ... + else: + def __init__( + self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None + ) -> None: ... + def _indent(self) -> None: ... def _dedent(self) -> None: ... def _add_item(self, func: Callable[..., str], args: Iterable[Any]) -> None: ... @@ -431,14 +472,30 @@ class Namespace(_AttributeHolder): def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] -class FileType: - # undocumented - _mode: str - _bufsize: int - _encoding: str | None - _errors: str | None - def __init__(self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None) -> None: ... - def __call__(self, string: str) -> IO[Any]: ... +if sys.version_info >= (3, 14): + @deprecated("Deprecated in Python 3.14; Simply open files after parsing arguments") + class FileType: + # undocumented + _mode: str + _bufsize: int + _encoding: str | None + _errors: str | None + def __init__( + self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None + ) -> None: ... + def __call__(self, string: str) -> IO[Any]: ... + +else: + class FileType: + # undocumented + _mode: str + _bufsize: int + _encoding: str | None + _errors: str | None + def __init__( + self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None + ) -> None: ... + def __call__(self, string: str) -> IO[Any]: ... # undocumented class _ArgumentGroup(_ActionsContainer): @@ -668,7 +725,33 @@ class _SubParsersAction(Action, Generic[_ArgumentParserT]): # Note: `add_parser` accepts all kwargs of `ArgumentParser.__init__`. It also # accepts its own `help` and `aliases` kwargs. - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + def add_parser( + self, + name: str, + *, + deprecated: bool = False, + help: str | None = ..., + aliases: Sequence[str] = ..., + # Kwargs from ArgumentParser constructor + prog: str | None = ..., + usage: str | None = ..., + description: str | None = ..., + epilog: str | None = ..., + parents: Sequence[_ArgumentParserT] = ..., + formatter_class: _FormatterClass = ..., + prefix_chars: str = ..., + fromfile_prefix_chars: str | None = ..., + argument_default: Any = ..., + conflict_handler: str = ..., + add_help: bool = ..., + allow_abbrev: bool = ..., + exit_on_error: bool = ..., + suggest_on_error: bool = False, + color: bool = False, + **kwargs: Any, # Accepting any additional kwargs for custom parser classes + ) -> _ArgumentParserT: ... + elif sys.version_info >= (3, 13): def add_parser( self, name: str, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi index 1a3d3e97d11e2d..f26ec4d1a08be6 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi @@ -1,3 +1,4 @@ +import builtins import os import sys import typing_extensions @@ -7,19 +8,13 @@ from _ast import ( PyCF_TYPE_COMMENTS as PyCF_TYPE_COMMENTS, ) from _typeshed import ReadableBuffer, Unused -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator, Sequence from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar as _TypeVar, overload from typing_extensions import Self, Unpack, deprecated if sys.version_info >= (3, 13): from _ast import PyCF_OPTIMIZED_AST as PyCF_OPTIMIZED_AST -# Alias used for fields that must always be valid identifiers -# A string `x` counts as a valid identifier if both the following are True -# (1) `x.isidentifier()` evaluates to `True` -# (2) `keyword.iskeyword(x)` evaluates to `False` -_Identifier: typing_extensions.TypeAlias = str - # Used for node end positions in constructor keyword arguments _EndPositionT = typing_extensions.TypeVar("_EndPositionT", int, int | None, default=int | None) @@ -111,7 +106,7 @@ class FunctionDef(stmt): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") elif sys.version_info >= (3, 10): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") - name: _Identifier + name: str args: arguments body: list[stmt] decorator_list: list[expr] @@ -122,7 +117,7 @@ class FunctionDef(stmt): if sys.version_info >= (3, 13): def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt] = ..., decorator_list: list[expr] = ..., @@ -135,7 +130,7 @@ class FunctionDef(stmt): @overload def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -147,7 +142,7 @@ class FunctionDef(stmt): @overload def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -160,7 +155,7 @@ class FunctionDef(stmt): else: def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -173,13 +168,14 @@ class FunctionDef(stmt): def __replace__( self, *, - name: _Identifier = ..., + name: str = ..., args: arguments = ..., body: list[stmt] = ..., decorator_list: list[expr] = ..., returns: expr | None = ..., type_comment: str | None = ..., type_params: list[type_param] = ..., + **kwargs: Unpack[_Attributes], ) -> Self: ... class AsyncFunctionDef(stmt): @@ -187,7 +183,7 @@ class AsyncFunctionDef(stmt): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") elif sys.version_info >= (3, 10): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") - name: _Identifier + name: str args: arguments body: list[stmt] decorator_list: list[expr] @@ -198,7 +194,7 @@ class AsyncFunctionDef(stmt): if sys.version_info >= (3, 13): def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt] = ..., decorator_list: list[expr] = ..., @@ -211,7 +207,7 @@ class AsyncFunctionDef(stmt): @overload def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -223,7 +219,7 @@ class AsyncFunctionDef(stmt): @overload def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -236,7 +232,7 @@ class AsyncFunctionDef(stmt): else: def __init__( self, - name: _Identifier, + name: str, args: arguments, body: list[stmt], decorator_list: list[expr], @@ -249,13 +245,14 @@ class AsyncFunctionDef(stmt): def __replace__( self, *, - name: _Identifier = ..., + name: str = ..., args: arguments = ..., - body: list[stmt], - decorator_list: list[expr], - returns: expr | None, - type_comment: str | None, - type_params: list[type_param], + body: list[stmt] = ..., + decorator_list: list[expr] = ..., + returns: expr | None = ..., + type_comment: str | None = ..., + type_params: list[type_param] = ..., + **kwargs: Unpack[_Attributes], ) -> Self: ... class ClassDef(stmt): @@ -263,7 +260,7 @@ class ClassDef(stmt): __match_args__ = ("name", "bases", "keywords", "body", "decorator_list", "type_params") elif sys.version_info >= (3, 10): __match_args__ = ("name", "bases", "keywords", "body", "decorator_list") - name: _Identifier + name: str bases: list[expr] keywords: list[keyword] body: list[stmt] @@ -273,7 +270,7 @@ class ClassDef(stmt): if sys.version_info >= (3, 13): def __init__( self, - name: _Identifier, + name: str, bases: list[expr] = ..., keywords: list[keyword] = ..., body: list[stmt] = ..., @@ -284,7 +281,7 @@ class ClassDef(stmt): elif sys.version_info >= (3, 12): def __init__( self, - name: _Identifier, + name: str, bases: list[expr], keywords: list[keyword], body: list[stmt], @@ -295,7 +292,7 @@ class ClassDef(stmt): else: def __init__( self, - name: _Identifier, + name: str, bases: list[expr], keywords: list[keyword], body: list[stmt], @@ -307,12 +304,12 @@ class ClassDef(stmt): def __replace__( self, *, - name: _Identifier, - bases: list[expr], - keywords: list[keyword], - body: list[stmt], - decorator_list: list[expr], - type_params: list[type_param], + name: str = ..., + bases: list[expr] = ..., + keywords: list[keyword] = ..., + body: list[stmt] = ..., + decorator_list: list[expr] = ..., + type_params: list[type_param] = ..., **kwargs: Unpack[_Attributes], ) -> Self: ... @@ -383,7 +380,7 @@ if sys.version_info >= (3, 12): ) -> None: ... if sys.version_info >= (3, 14): - def __replace__( + def __replace__( # type: ignore[override] self, *, name: Name = ..., @@ -546,7 +543,9 @@ class While(stmt): def __init__(self, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__( + self, *, test: expr = ..., body: list[stmt] = ..., orelse: list[stmt] = ..., **kwargs: Unpack[_Attributes] + ) -> Self: ... class If(stmt): if sys.version_info >= (3, 10): @@ -731,7 +730,7 @@ class Assert(stmt): def __init__(self, test: expr, msg: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, test: expr, msg: expr | None, **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, test: expr = ..., msg: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Import(stmt): if sys.version_info >= (3, 10): @@ -774,26 +773,26 @@ class ImportFrom(stmt): class Global(stmt): if sys.version_info >= (3, 10): __match_args__ = ("names",) - names: list[_Identifier] + names: list[str] if sys.version_info >= (3, 13): - def __init__(self, names: list[_Identifier] = ..., **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> None: ... else: - def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, names: list[str], **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Nonlocal(stmt): if sys.version_info >= (3, 10): __match_args__ = ("names",) - names: list[_Identifier] + names: list[str] if sys.version_info >= (3, 13): - def __init__(self, names: list[_Identifier] = ..., **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> None: ... else: - def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, names: list[str], **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, names: list[_Identifier] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Expr(stmt): if sys.version_info >= (3, 10): @@ -1065,6 +1064,37 @@ class JoinedStr(expr): if sys.version_info >= (3, 14): def __replace__(self, *, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... +if sys.version_info >= (3, 14): + class TemplateStr(expr): + __match_args__ = ("values",) + values: list[expr] + def __init__(self, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> None: ... + def __replace__(self, *, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + + class Interpolation(expr): + __match_args__ = ("value", "str", "conversion", "format_spec") + value: expr + str: builtins.str + conversion: int + format_spec: builtins.str | None = None + def __init__( + self, + value: expr = ..., + str: builtins.str = ..., + conversion: int = ..., + format_spec: builtins.str | None = ..., + **kwargs: Unpack[_Attributes], + ) -> None: ... + def __replace__( + self, + *, + value: expr = ..., + str: builtins.str = ..., + conversion: int = ..., + format_spec: builtins.str | None = ..., + **kwargs: Unpack[_Attributes], + ) -> Self: ... + class Constant(expr): if sys.version_info >= (3, 10): __match_args__ = ("value", "kind") @@ -1084,13 +1114,13 @@ class Attribute(expr): if sys.version_info >= (3, 10): __match_args__ = ("value", "attr", "ctx") value: expr - attr: _Identifier + attr: str ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` - def __init__(self, value: expr, attr: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, value: expr, attr: str, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, *, value: expr = ..., attr: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes] + self, *, value: expr = ..., attr: str = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes] ) -> Self: ... class Subscript(expr): @@ -1119,12 +1149,12 @@ class Starred(expr): class Name(expr): if sys.version_info >= (3, 10): __match_args__ = ("id", "ctx") - id: _Identifier + id: str ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` - def __init__(self, id: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, id: str, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, id: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, id: str = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class List(expr): if sys.version_info >= (3, 10): @@ -1272,30 +1302,23 @@ class ExceptHandler(excepthandler): if sys.version_info >= (3, 10): __match_args__ = ("type", "name", "body") type: expr | None - name: _Identifier | None + name: str | None body: list[stmt] if sys.version_info >= (3, 13): def __init__( - self, type: expr | None = None, name: _Identifier | None = None, body: list[stmt] = ..., **kwargs: Unpack[_Attributes] + self, type: expr | None = None, name: str | None = None, body: list[stmt] = ..., **kwargs: Unpack[_Attributes] ) -> None: ... else: @overload - def __init__( - self, type: expr | None, name: _Identifier | None, body: list[stmt], **kwargs: Unpack[_Attributes] - ) -> None: ... + def __init__(self, type: expr | None, name: str | None, body: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ... @overload def __init__( - self, type: expr | None = None, name: _Identifier | None = None, *, body: list[stmt], **kwargs: Unpack[_Attributes] + self, type: expr | None = None, name: str | None = None, *, body: list[stmt], **kwargs: Unpack[_Attributes] ) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, - *, - type: expr | None = ..., - name: _Identifier | None = ..., - body: list[stmt] = ..., - **kwargs: Unpack[_Attributes], + self, *, type: expr | None = ..., name: str | None = ..., body: list[stmt] = ..., **kwargs: Unpack[_Attributes] ) -> Self: ... class arguments(AST): @@ -1376,21 +1399,16 @@ class arg(AST): end_col_offset: int | None if sys.version_info >= (3, 10): __match_args__ = ("arg", "annotation", "type_comment") - arg: _Identifier + arg: str annotation: expr | None type_comment: str | None def __init__( - self, arg: _Identifier, annotation: expr | None = None, type_comment: str | None = None, **kwargs: Unpack[_Attributes] + self, arg: str, annotation: expr | None = None, type_comment: str | None = None, **kwargs: Unpack[_Attributes] ) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, - *, - arg: _Identifier = ..., - annotation: expr | None = ..., - type_comment: str | None = ..., - **kwargs: Unpack[_Attributes], + self, *, arg: str = ..., annotation: expr | None = ..., type_comment: str | None = ..., **kwargs: Unpack[_Attributes] ) -> Self: ... class keyword(AST): @@ -1400,15 +1418,15 @@ class keyword(AST): end_col_offset: int | None if sys.version_info >= (3, 10): __match_args__ = ("arg", "value") - arg: _Identifier | None + arg: str | None value: expr @overload - def __init__(self, arg: _Identifier | None, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, arg: str | None, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @overload - def __init__(self, arg: _Identifier | None = None, *, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, arg: str | None = None, *, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, arg: _Identifier | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, arg: str | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class alias(AST): lineno: int @@ -1418,11 +1436,11 @@ class alias(AST): if sys.version_info >= (3, 10): __match_args__ = ("name", "asname") name: str - asname: _Identifier | None - def __init__(self, name: str, asname: _Identifier | None = None, **kwargs: Unpack[_Attributes]) -> None: ... + asname: str | None + def __init__(self, name: str, asname: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, name: str = ..., asname: _Identifier | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, name: str = ..., asname: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class withitem(AST): if sys.version_info >= (3, 10): @@ -1497,22 +1515,18 @@ if sys.version_info >= (3, 10): __match_args__ = ("keys", "patterns", "rest") keys: list[expr] patterns: list[pattern] - rest: _Identifier | None + rest: str | None if sys.version_info >= (3, 13): def __init__( self, keys: list[expr] = ..., patterns: list[pattern] = ..., - rest: _Identifier | None = None, + rest: str | None = None, **kwargs: Unpack[_Attributes[int]], ) -> None: ... else: def __init__( - self, - keys: list[expr], - patterns: list[pattern], - rest: _Identifier | None = None, - **kwargs: Unpack[_Attributes[int]], + self, keys: list[expr], patterns: list[pattern], rest: str | None = None, **kwargs: Unpack[_Attributes[int]] ) -> None: ... if sys.version_info >= (3, 14): @@ -1521,7 +1535,7 @@ if sys.version_info >= (3, 10): *, keys: list[expr] = ..., patterns: list[pattern] = ..., - rest: _Identifier | None = ..., + rest: str | None = ..., **kwargs: Unpack[_Attributes[int]], ) -> Self: ... @@ -1529,14 +1543,14 @@ if sys.version_info >= (3, 10): __match_args__ = ("cls", "patterns", "kwd_attrs", "kwd_patterns") cls: expr patterns: list[pattern] - kwd_attrs: list[_Identifier] + kwd_attrs: list[str] kwd_patterns: list[pattern] if sys.version_info >= (3, 13): def __init__( self, cls: expr, patterns: list[pattern] = ..., - kwd_attrs: list[_Identifier] = ..., + kwd_attrs: list[str] = ..., kwd_patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]], ) -> None: ... @@ -1545,7 +1559,7 @@ if sys.version_info >= (3, 10): self, cls: expr, patterns: list[pattern], - kwd_attrs: list[_Identifier], + kwd_attrs: list[str], kwd_patterns: list[pattern], **kwargs: Unpack[_Attributes[int]], ) -> None: ... @@ -1556,30 +1570,30 @@ if sys.version_info >= (3, 10): *, cls: expr = ..., patterns: list[pattern] = ..., - kwd_attrs: list[_Identifier] = ..., + kwd_attrs: list[str] = ..., kwd_patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]], ) -> Self: ... class MatchStar(pattern): __match_args__ = ("name",) - name: _Identifier | None - def __init__(self, name: _Identifier | None, **kwargs: Unpack[_Attributes[int]]) -> None: ... + name: str | None + def __init__(self, name: str | None, **kwargs: Unpack[_Attributes[int]]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + def __replace__(self, *, name: str | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... class MatchAs(pattern): __match_args__ = ("pattern", "name") pattern: _Pattern | None - name: _Identifier | None + name: str | None def __init__( - self, pattern: _Pattern | None = None, name: _Identifier | None = None, **kwargs: Unpack[_Attributes[int]] + self, pattern: _Pattern | None = None, name: str | None = None, **kwargs: Unpack[_Attributes[int]] ) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, *, pattern: _Pattern | None = ..., name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]] + self, *, pattern: _Pattern | None = ..., name: str | None = ..., **kwargs: Unpack[_Attributes[int]] ) -> Self: ... class MatchOr(pattern): @@ -1621,25 +1635,21 @@ if sys.version_info >= (3, 12): __match_args__ = ("name", "bound", "default_value") else: __match_args__ = ("name", "bound") - name: _Identifier + name: str bound: expr | None if sys.version_info >= (3, 13): default_value: expr | None def __init__( - self, - name: _Identifier, - bound: expr | None = None, - default_value: expr | None = None, - **kwargs: Unpack[_Attributes[int]], + self, name: str, bound: expr | None = None, default_value: expr | None = None, **kwargs: Unpack[_Attributes[int]] ) -> None: ... else: - def __init__(self, name: _Identifier, bound: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... + def __init__(self, name: str, bound: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... if sys.version_info >= (3, 14): def __replace__( self, *, - name: _Identifier = ..., + name: str = ..., bound: expr | None = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]], @@ -1650,18 +1660,16 @@ if sys.version_info >= (3, 12): __match_args__ = ("name", "default_value") else: __match_args__ = ("name",) - name: _Identifier + name: str if sys.version_info >= (3, 13): default_value: expr | None - def __init__( - self, name: _Identifier, default_value: expr | None = None, **kwargs: Unpack[_Attributes[int]] - ) -> None: ... + def __init__(self, name: str, default_value: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... else: - def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ... + def __init__(self, name: str, **kwargs: Unpack[_Attributes[int]]) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]] + self, *, name: str = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]] ) -> Self: ... class TypeVarTuple(type_param): @@ -1669,18 +1677,16 @@ if sys.version_info >= (3, 12): __match_args__ = ("name", "default_value") else: __match_args__ = ("name",) - name: _Identifier + name: str if sys.version_info >= (3, 13): default_value: expr | None - def __init__( - self, name: _Identifier, default_value: expr | None = None, **kwargs: Unpack[_Attributes[int]] - ) -> None: ... + def __init__(self, name: str, default_value: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... else: - def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ... + def __init__(self, name: str, **kwargs: Unpack[_Attributes[int]]) -> None: ... if sys.version_info >= (3, 14): def __replace__( - self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]] + self, *, name: str = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]] ) -> Self: ... class _ABC(type): @@ -2042,4 +2048,9 @@ class NodeTransformer(NodeVisitor): # is also allowed in some cases -- this needs to be mapped. def unparse(ast_obj: AST) -> str: ... -def main() -> None: ... + +if sys.version_info >= (3, 14): + def main(args: Sequence[str] | None = None) -> None: ... + +else: + def main() -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi index c314acbea1ca3e..f9118608060e5d 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi @@ -21,6 +21,9 @@ from .tasks import * from .threads import * from .transports import * +if sys.version_info >= (3, 14): + from .graph import * + if sys.version_info >= (3, 11): from .taskgroups import * from .timeouts import * @@ -32,6 +35,7 @@ else: if sys.platform == "win32": if sys.version_info >= (3, 14): + __all__ = ( "BaseEventLoop", # from base_events "Server", # from base_events @@ -60,6 +64,13 @@ if sys.platform == "win32": "Future", # from futures "wrap_future", # from futures "isfuture", # from futures + "future_discard_from_awaited_by", # from futures + "future_add_to_awaited_by", # from futures + "capture_call_graph", # from graph + "format_call_graph", # from graph + "print_call_graph", # from graph + "FrameCallGraphEntry", # from graph + "FutureCallGraph", # from graph "Lock", # from locks "Event", # from locks "Condition", # from locks @@ -527,6 +538,13 @@ else: "Future", # from futures "wrap_future", # from futures "isfuture", # from futures + "future_discard_from_awaited_by", # from futures + "future_add_to_awaited_by", # from futures + "capture_call_graph", # from graph + "format_call_graph", # from graph + "print_call_graph", # from graph + "FrameCallGraphEntry", # from graph + "FutureCallGraph", # from graph "Lock", # from locks "Event", # from locks "Condition", # from locks diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi index afe912d01fe1f1..af43d2f5937dc6 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi @@ -21,7 +21,9 @@ from .futures import Future from .protocols import BaseProtocol from .tasks import Task from .transports import BaseTransport, DatagramTransport, ReadTransport, SubprocessTransport, Transport, WriteTransport -from .unix_events import AbstractChildWatcher + +if sys.version_info < (3, 14): + from .unix_events import AbstractChildWatcher # Keep asyncio.__all__ updated with any changes to __all__ here if sys.version_info >= (3, 14): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi index cb2785012fb274..a63de66f02e67a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi @@ -1,3 +1,4 @@ +import sys from _asyncio import Future as Future from concurrent.futures._base import Future as _ConcurrentFuture from typing import Any, TypeVar @@ -6,7 +7,10 @@ from typing_extensions import TypeIs from .events import AbstractEventLoop # Keep asyncio.__all__ updated with any changes to __all__ here -__all__ = ("Future", "wrap_future", "isfuture") +if sys.version_info >= (3, 14): + __all__ = ("Future", "wrap_future", "isfuture", "future_discard_from_awaited_by", "future_add_to_awaited_by") +else: + __all__ = ("Future", "wrap_future", "isfuture") _T = TypeVar("_T") @@ -15,3 +19,7 @@ _T = TypeVar("_T") # That's why the import order is reversed. def isfuture(obj: object) -> TypeIs[Future[Any]]: ... def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]: ... + +if sys.version_info >= (3, 14): + def future_discard_from_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... + def future_add_to_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/graph.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/graph.pyi new file mode 100644 index 00000000000000..cb2cf01749955b --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/graph.pyi @@ -0,0 +1,26 @@ +from _typeshed import SupportsWrite +from asyncio import Future +from dataclasses import dataclass +from types import FrameType +from typing import Any, overload + +__all__ = ("capture_call_graph", "format_call_graph", "print_call_graph", "FrameCallGraphEntry", "FutureCallGraph") + +@dataclass(frozen=True) +class FrameCallGraphEntry: + frame: FrameType + +@dataclass(frozen=True) +class FutureCallGraph: + future: Future[Any] + call_stack: tuple[FrameCallGraphEntry, ...] + awaited_by: tuple[FutureCallGraph, ...] + +@overload +def capture_call_graph(future: None = None, /, *, depth: int = 1, limit: int | None = None) -> FutureCallGraph | None: ... +@overload +def capture_call_graph(future: Future[Any], /, *, depth: int = 1, limit: int | None = None) -> FutureCallGraph | None: ... +def format_call_graph(future: Future[Any] | None = None, /, *, depth: int = 1, limit: int | None = None) -> str: ... +def print_call_graph( + future: Future[Any] | None = None, /, *, file: SupportsWrite[str] | None = None, depth: int = 1, limit: int | None = None +) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/bdb.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/bdb.pyi index 2004874a52b264..b73f894093ce5f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/bdb.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/bdb.pyi @@ -3,13 +3,14 @@ from _typeshed import ExcInfo, TraceFunction, Unused from collections.abc import Callable, Iterable, Iterator, Mapping from contextlib import contextmanager from types import CodeType, FrameType, TracebackType -from typing import IO, Any, Final, SupportsInt, TypeVar -from typing_extensions import ParamSpec +from typing import IO, Any, Final, Literal, SupportsInt, TypeVar +from typing_extensions import ParamSpec, TypeAlias __all__ = ["BdbQuit", "Bdb", "Breakpoint"] _T = TypeVar("_T") _P = ParamSpec("_P") +_Backend: TypeAlias = Literal["settrace", "monitoring"] # A union of code-object flags at runtime. # The exact values of code-object flags are implementation details, @@ -28,7 +29,12 @@ class Bdb: stopframe: FrameType | None returnframe: FrameType | None stoplineno: int - def __init__(self, skip: Iterable[str] | None = None) -> None: ... + if sys.version_info >= (3, 14): + backend: _Backend + def __init__(self, skip: Iterable[str] | None = None, backend: _Backend = "settrace") -> None: ... + else: + def __init__(self, skip: Iterable[str] | None = None) -> None: ... + def canonic(self, filename: str) -> str: ... def reset(self) -> None: ... if sys.version_info >= (3, 12): @@ -85,6 +91,11 @@ class Bdb: def runeval(self, expr: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None: ... def runctx(self, cmd: str | CodeType, globals: dict[str, Any] | None, locals: Mapping[str, Any] | None) -> None: ... def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... + if sys.version_info >= (3, 14): + def start_trace(self) -> None: ... + def stop_trace(self) -> None: ... + def disable_current_event(self) -> None: ... + def restart_events(self) -> None: ... class Breakpoint: next: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi index b75250aad3de13..ad6994cf605b3f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi @@ -5,6 +5,7 @@ import sys import types from _collections_abc import dict_items, dict_keys, dict_values from _typeshed import ( + AnnotationForm, AnyStr_co, ConvertibleToFloat, ConvertibleToInt, @@ -72,6 +73,9 @@ from typing_extensions import ( # noqa: Y023 deprecated, ) +if sys.version_info >= (3, 14): + from _typeshed import AnnotateFunc + _T = TypeVar("_T") _I = TypeVar("_I", default=int) _T_co = TypeVar("_T_co", covariant=True) @@ -215,6 +219,9 @@ class type: def __ror__(self, value: Any, /) -> types.UnionType: ... if sys.version_info >= (3, 12): __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] + __annotations__: dict[str, AnnotationForm] + if sys.version_info >= (3, 14): + __annotate__: AnnotateFunc | None class super: @overload @@ -1004,6 +1011,7 @@ class tuple(Sequence[_T_co]): # Doesn't exist at runtime, but deleting this breaks mypy and pyright. See: # https://github.com/python/typeshed/issues/7580 # https://github.com/python/mypy/issues/8240 +# Obsolete, use types.FunctionType instead. @final @type_check_only class function: @@ -1017,7 +1025,9 @@ class function: def __globals__(self) -> dict[str, Any]: ... __name__: str __qualname__: str - __annotations__: dict[str, Any] + __annotations__: dict[str, AnnotationForm] + if sys.version_info >= (3, 14): + __annotate__: AnnotateFunc | None __kwdefaults__: dict[str, Any] if sys.version_info >= (3, 10): @property @@ -1494,48 +1504,108 @@ license: _sitebuiltins._Printer def locals() -> dict[str, Any]: ... class map(Generic[_S]): - @overload - def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: ... - @overload - def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... - @overload - def __new__( - cls, func: Callable[[_T1, _T2, _T3], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], / - ) -> Self: ... - @overload - def __new__( - cls, - func: Callable[[_T1, _T2, _T3, _T4], _S], - iterable: Iterable[_T1], - iter2: Iterable[_T2], - iter3: Iterable[_T3], - iter4: Iterable[_T4], - /, - ) -> Self: ... - @overload - def __new__( - cls, - func: Callable[[_T1, _T2, _T3, _T4, _T5], _S], - iterable: Iterable[_T1], - iter2: Iterable[_T2], - iter3: Iterable[_T3], - iter4: Iterable[_T4], - iter5: Iterable[_T5], - /, - ) -> Self: ... - @overload - def __new__( - cls, - func: Callable[..., _S], - iterable: Iterable[Any], - iter2: Iterable[Any], - iter3: Iterable[Any], - iter4: Iterable[Any], - iter5: Iterable[Any], - iter6: Iterable[Any], - /, - *iterables: Iterable[Any], - ) -> Self: ... + # 3.14 adds `strict` argument. + if sys.version_info >= (3, 14): + @overload + def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /, *, strict: bool = False) -> Self: ... + @overload + def __new__( + cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /, *, strict: bool = False + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[[_T1, _T2, _T3], _S], + iterable: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + /, + *, + strict: bool = False, + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4], _S], + iterable: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + /, + *, + strict: bool = False, + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4, _T5], _S], + iterable: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, + *, + strict: bool = False, + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[..., _S], + iterable: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, + *iterables: Iterable[Any], + strict: bool = False, + ) -> Self: ... + else: + @overload + def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: ... + @overload + def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... + @overload + def __new__( + cls, func: Callable[[_T1, _T2, _T3], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], / + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4], _S], + iterable: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + /, + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4, _T5], _S], + iterable: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, + ) -> Self: ... + @overload + def __new__( + cls, + func: Callable[..., _S], + iterable: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, + *iterables: Iterable[Any], + ) -> Self: ... + def __iter__(self) -> Self: ... def __next__(self) -> _S: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi index 3b21fbcf711769..0f9d00fbc633ed 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi @@ -1,17 +1,21 @@ -import _compression +import sys from _bz2 import BZ2Compressor as BZ2Compressor, BZ2Decompressor as BZ2Decompressor -from _compression import BaseStream from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Iterable from typing import IO, Literal, Protocol, SupportsIndex, TextIO, overload from typing_extensions import Self, TypeAlias +if sys.version_info >= (3, 14): + from compression._common._streams import BaseStream, _Reader +else: + from _compression import BaseStream, _Reader + __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "decompress"] # The following attributes and methods are optional: # def fileno(self) -> int: ... # def close(self) -> object: ... -class _ReadableFileobj(_compression._Reader, Protocol): ... +class _ReadableFileobj(_Reader, Protocol): ... class _WritableFileobj(Protocol): def write(self, b: bytes, /) -> object: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/code.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/code.pyi index 16721927c23667..0b13c8a5016d48 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/code.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/code.pyi @@ -1,5 +1,5 @@ import sys -from codeop import CommandCompiler +from codeop import CommandCompiler, compile_command as compile_command from collections.abc import Callable from types import CodeType from typing import Any @@ -52,5 +52,3 @@ else: local: dict[str, Any] | None = None, exitmsg: str | None = None, ) -> None: ... - -def compile_command(source: str, filename: str = "", symbol: str = "single") -> CodeType | None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/codeop.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/codeop.pyi index cfe52e9b35de7a..8e311343eb89db 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/codeop.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/codeop.pyi @@ -3,7 +3,11 @@ from types import CodeType __all__ = ["compile_command", "Compile", "CommandCompiler"] -def compile_command(source: str, filename: str = "", symbol: str = "single") -> CodeType | None: ... +if sys.version_info >= (3, 14): + def compile_command(source: str, filename: str = "", symbol: str = "single", flags: int = 0) -> CodeType | None: ... + +else: + def compile_command(source: str, filename: str = "", symbol: str = "single") -> CodeType | None: ... class Compile: flags: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/__init__.pyi new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/__init__.pyi new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi new file mode 100644 index 00000000000000..6303a9b1d460c8 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete, WriteableBuffer +from collections.abc import Callable +from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase +from typing import Any, Protocol + +BUFFER_SIZE = DEFAULT_BUFFER_SIZE + +class _Reader(Protocol): + def read(self, n: int, /) -> bytes: ... + def seekable(self) -> bool: ... + def seek(self, n: int, /) -> Any: ... + +class BaseStream(BufferedIOBase): ... + +class DecompressReader(RawIOBase): + def __init__( + self, + fp: _Reader, + decomp_factory: Callable[..., Incomplete], # Consider backporting changes to _compression + trailing_error: type[Exception] | tuple[type[Exception], ...] = (), + **decomp_args: Any, # These are passed to decomp_factory. + ) -> None: ... + def readinto(self, b: WriteableBuffer) -> int: ... + def read(self, size: int = -1) -> bytes: ... + def seek(self, offset: int, whence: int = 0) -> int: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/bz2/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/bz2/__init__.pyi new file mode 100644 index 00000000000000..9ddc39f27c2863 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/bz2/__init__.pyi @@ -0,0 +1 @@ +from bz2 import * diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/gzip/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/gzip/__init__.pyi new file mode 100644 index 00000000000000..9422a735c590ef --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/gzip/__init__.pyi @@ -0,0 +1 @@ +from gzip import * diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/lzma/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/lzma/__init__.pyi new file mode 100644 index 00000000000000..936c3813db4f12 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/lzma/__init__.pyi @@ -0,0 +1 @@ +from lzma import * diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/zlib/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zlib/__init__.pyi new file mode 100644 index 00000000000000..78d176c03ee833 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zlib/__init__.pyi @@ -0,0 +1 @@ +from zlib import * diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/__init__.pyi index 68fd0bc5acb439..dd1f6da80c4d66 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/__init__.pyi @@ -16,7 +16,27 @@ from ._base import ( from .process import ProcessPoolExecutor as ProcessPoolExecutor from .thread import ThreadPoolExecutor as ThreadPoolExecutor -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 14): + from .interpreter import InterpreterPoolExecutor as InterpreterPoolExecutor + + __all__ = ( + "FIRST_COMPLETED", + "FIRST_EXCEPTION", + "ALL_COMPLETED", + "CancelledError", + "TimeoutError", + "InvalidStateError", + "BrokenExecutor", + "Future", + "Executor", + "wait", + "as_completed", + "ProcessPoolExecutor", + "ThreadPoolExecutor", + "InterpreterPoolExecutor", + ) + +elif sys.version_info >= (3, 13): __all__ = ( "FIRST_COMPLETED", "FIRST_EXCEPTION", diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/_base.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/_base.pyi index 7294b69567d60f..fbf07a3fc78f9c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/_base.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/_base.pyi @@ -54,9 +54,20 @@ class Future(Generic[_T]): class Executor: def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ... - def map( - self, fn: Callable[..., _T], *iterables: Iterable[Any], timeout: float | None = None, chunksize: int = 1 - ) -> Iterator[_T]: ... + if sys.version_info >= (3, 14): + def map( + self, + fn: Callable[..., _T], + *iterables: Iterable[Any], + timeout: float | None = None, + chunksize: int = 1, + buffersize: int | None = None, + ) -> Iterator[_T]: ... + else: + def map( + self, fn: Callable[..., _T], *iterables: Iterable[Any], timeout: float | None = None, chunksize: int = 1 + ) -> Iterator[_T]: ... + def shutdown(self, wait: bool = True, *, cancel_futures: bool = False) -> None: ... def __enter__(self) -> Self: ... def __exit__( diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi new file mode 100644 index 00000000000000..c1a29e6b055213 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi @@ -0,0 +1,102 @@ +import sys +from collections.abc import Callable, Mapping +from concurrent.futures import ThreadPoolExecutor +from typing import Final, Literal, Protocol, overload, type_check_only +from typing_extensions import ParamSpec, Self, TypeAlias, TypeVar, TypeVarTuple, Unpack + +_Task: TypeAlias = tuple[bytes, Literal["function", "script"]] + +@type_check_only +class _TaskFunc(Protocol): + @overload + def __call__(self, fn: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> tuple[bytes, Literal["function"]]: ... + @overload + def __call__(self, fn: str) -> tuple[bytes, Literal["script"]]: ... + +_Ts = TypeVarTuple("_Ts") +_P = ParamSpec("_P") +_R = TypeVar("_R") + +# A `type.simplenamespace` with `__name__` attribute. +@type_check_only +class _HasName(Protocol): + __name__: str + +# `_interpreters.exec` technically gives us a simple namespace. +@type_check_only +class _ExcInfo(Protocol): + formatted: str + msg: str + type: _HasName + +if sys.version_info >= (3, 14): + from concurrent.futures.thread import BrokenThreadPool, WorkerContext as ThreadWorkerContext + + from _interpreters import InterpreterError + + class ExecutionFailed(InterpreterError): + def __init__(self, excinfo: _ExcInfo) -> None: ... # type: ignore[override] + + UNBOUND: Final = 2 + + class WorkerContext(ThreadWorkerContext): + # Parent class doesn't have `shared` argument, + @overload # type: ignore[override] + @classmethod + def prepare( + cls, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]], shared: Mapping[str, object] + ) -> tuple[Callable[[], Self], _TaskFunc]: ... + @overload # type: ignore[override] + @classmethod + def prepare( + cls, initializer: Callable[[], object], initargs: tuple[()], shared: Mapping[str, object] + ) -> tuple[Callable[[], Self], _TaskFunc]: ... + def __init__( + self, initdata: tuple[bytes, Literal["function", "script"]], shared: Mapping[str, object] | None = None + ) -> None: ... # type: ignore[override] + def __del__(self) -> None: ... + def run(self, task: _Task) -> None: ... # type: ignore[override] + + class BrokenInterpreterPool(BrokenThreadPool): ... + + class InterpreterPoolExecutor(ThreadPoolExecutor): + BROKEN: type[BrokenInterpreterPool] + + @overload # type: ignore[override] + @classmethod + def prepare_context( + cls, initializer: Callable[[], object], initargs: tuple[()], shared: Mapping[str, object] + ) -> tuple[Callable[[], WorkerContext], _TaskFunc]: ... + @overload # type: ignore[override] + @classmethod + def prepare_context( + cls, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]], shared: Mapping[str, object] + ) -> tuple[Callable[[], WorkerContext], _TaskFunc]: ... + @overload + def __init__( + self, + max_workers: int | None = None, + thread_name_prefix: str = "", + initializer: Callable[[], object] | None = None, + initargs: tuple[()] = (), + shared: Mapping[str, object] | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None = None, + thread_name_prefix: str = "", + *, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + shared: Mapping[str, object] | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None, + thread_name_prefix: str, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + shared: Mapping[str, object] | None = None, + ) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/process.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/process.pyi index 9c904f793fa9fd..607990100369ee 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/process.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/process.pyi @@ -236,3 +236,7 @@ class ProcessPoolExecutor(Executor): def _start_executor_manager_thread(self) -> None: ... def _adjust_process_count(self) -> None: ... + + if sys.version_info >= (3, 14): + def kill_workers(self) -> None: ... + def terminate_workers(self) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/thread.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/thread.pyi index da3e006b6f138f..22df0dca5a3fd8 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/thread.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/thread.pyi @@ -1,9 +1,10 @@ import queue +import sys from collections.abc import Callable, Iterable, Mapping, Set as AbstractSet from threading import Lock, Semaphore, Thread from types import GenericAlias -from typing import Any, Generic, TypeVar, overload -from typing_extensions import TypeVarTuple, Unpack +from typing import Any, Generic, Protocol, TypeVar, overload, type_check_only +from typing_extensions import Self, TypeAlias, TypeVarTuple, Unpack from weakref import ref from ._base import BrokenExecutor, Executor, Future @@ -18,25 +19,71 @@ def _python_exit() -> None: ... _S = TypeVar("_S") -class _WorkItem(Generic[_S]): - future: Future[_S] - fn: Callable[..., _S] - args: Iterable[Any] - kwargs: Mapping[str, Any] - def __init__(self, future: Future[_S], fn: Callable[..., _S], args: Iterable[Any], kwargs: Mapping[str, Any]) -> None: ... - def run(self) -> None: ... - def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - -def _worker( - executor_reference: ref[Any], - work_queue: queue.SimpleQueue[Any], - initializer: Callable[[Unpack[_Ts]], object], - initargs: tuple[Unpack[_Ts]], -) -> None: ... +_Task: TypeAlias = tuple[Callable[..., Any], tuple[Any, ...], dict[str, Any]] + +_C = TypeVar("_C", bound=Callable[..., object]) +_KT = TypeVar("_KT", bound=str) +_VT = TypeVar("_VT") + +@type_check_only +class _ResolveTaskFunc(Protocol): + def __call__( + self, func: _C, args: tuple[Unpack[_Ts]], kwargs: dict[_KT, _VT] + ) -> tuple[_C, tuple[Unpack[_Ts]], dict[_KT, _VT]]: ... + +if sys.version_info >= (3, 14): + class WorkerContext: + @overload + @classmethod + def prepare( + cls, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]] + ) -> tuple[Callable[[], Self], _ResolveTaskFunc]: ... + @overload + @classmethod + def prepare( + cls, initializer: Callable[[], object], initargs: tuple[()] + ) -> tuple[Callable[[], Self], _ResolveTaskFunc]: ... + @overload + def __init__(self, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]]) -> None: ... + @overload + def __init__(self, initializer: Callable[[], object], initargs: tuple[()]) -> None: ... + def initialize(self) -> None: ... + def finalize(self) -> None: ... + def run(self, task: _Task) -> None: ... + +if sys.version_info >= (3, 14): + class _WorkItem(Generic[_S]): + future: Future[Any] + task: _Task + def __init__(self, future: Future[Any], task: _Task) -> None: ... + def run(self, ctx: WorkerContext) -> None: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + + def _worker(executor_reference: ref[Any], ctx: WorkerContext, work_queue: queue.SimpleQueue[Any]) -> None: ... + +else: + class _WorkItem(Generic[_S]): + future: Future[_S] + fn: Callable[..., _S] + args: Iterable[Any] + kwargs: Mapping[str, Any] + def __init__(self, future: Future[_S], fn: Callable[..., _S], args: Iterable[Any], kwargs: Mapping[str, Any]) -> None: ... + def run(self) -> None: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + + def _worker( + executor_reference: ref[Any], + work_queue: queue.SimpleQueue[Any], + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + ) -> None: ... class BrokenThreadPool(BrokenExecutor): ... class ThreadPoolExecutor(Executor): + if sys.version_info >= (3, 14): + BROKEN: type[BrokenThreadPool] + _max_workers: int _idle_semaphore: Semaphore _threads: AbstractSet[Thread] @@ -47,6 +94,19 @@ class ThreadPoolExecutor(Executor): _initializer: Callable[..., None] | None _initargs: tuple[Any, ...] _work_queue: queue.SimpleQueue[_WorkItem[Any]] + + if sys.version_info >= (3, 14): + @overload + @classmethod + def prepare_context( + cls, initializer: Callable[[], object], initargs: tuple[()] + ) -> tuple[Callable[[], Self], _ResolveTaskFunc]: ... + @overload + @classmethod + def prepare_context( + cls, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]] + ) -> tuple[Callable[[], Self], _ResolveTaskFunc]: ... + @overload def __init__( self, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/configparser.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/configparser.pyi index 8996c85d9a53fe..15c564c0258974 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/configparser.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/configparser.pyi @@ -5,7 +5,33 @@ from re import Pattern from typing import Any, ClassVar, Final, Literal, TypeVar, overload from typing_extensions import TypeAlias -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 14): + __all__ = ( + "NoSectionError", + "DuplicateOptionError", + "DuplicateSectionError", + "NoOptionError", + "InterpolationError", + "InterpolationDepthError", + "InterpolationMissingOptionError", + "InterpolationSyntaxError", + "ParsingError", + "MissingSectionHeaderError", + "MultilineContinuationError", + "UnnamedSectionDisabledError", + "InvalidWriteError", + "ConfigParser", + "RawConfigParser", + "Interpolation", + "BasicInterpolation", + "ExtendedInterpolation", + "SectionProxy", + "ConverterMapping", + "DEFAULTSECT", + "MAX_INTERPOLATION_DEPTH", + "UNNAMED_SECTION", + ) +elif sys.version_info >= (3, 13): __all__ = ( "NoSectionError", "DuplicateOptionError", @@ -429,3 +455,10 @@ if sys.version_info >= (3, 13): lineno: int line: str def __init__(self, filename: str, lineno: int, line: str) -> None: ... + +if sys.version_info >= (3, 14): + class UnnamedSectionDisabledError(Error): + msg: Final = "Support for UNNAMED_SECTION is disabled." + def __init__(self) -> None: ... + + class InvalidWriteError(Error): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi index a7e19483301ce6..68b75b86def15c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi @@ -1,6 +1,5 @@ import sys from _ctypes import ( - POINTER as POINTER, RTLD_GLOBAL as RTLD_GLOBAL, RTLD_LOCAL as RTLD_LOCAL, Array as Array, @@ -19,7 +18,6 @@ from _ctypes import ( alignment as alignment, byref as byref, get_errno as get_errno, - pointer as pointer, resize as resize, set_errno as set_errno, sizeof as sizeof, @@ -27,7 +25,7 @@ from _ctypes import ( from _typeshed import StrPath from ctypes._endian import BigEndianStructure as BigEndianStructure, LittleEndianStructure as LittleEndianStructure from types import GenericAlias -from typing import Any, ClassVar, Generic, Literal, TypeVar, type_check_only +from typing import Any, ClassVar, Generic, Literal, TypeVar, overload, type_check_only from typing_extensions import Self, TypeAlias, deprecated if sys.platform == "win32": @@ -36,9 +34,22 @@ if sys.platform == "win32": if sys.version_info >= (3, 11): from ctypes._endian import BigEndianUnion as BigEndianUnion, LittleEndianUnion as LittleEndianUnion +_CT = TypeVar("_CT", bound=_CData) _T = TypeVar("_T", default=Any) _DLLT = TypeVar("_DLLT", bound=CDLL) -_CT = TypeVar("_CT", bound=_CData) + +if sys.version_info >= (3, 14): + @overload + @deprecated("ctypes.POINTER with string") + def POINTER(cls: str) -> type[Any]: ... + @overload + def POINTER(cls: None) -> type[c_void_p]: ... + @overload + def POINTER(cls: type[_CT]) -> type[_Pointer[_CT]]: ... + def pointer(obj: _CT) -> _Pointer[_CT]: ... + +else: + from _ctypes import POINTER as POINTER, pointer as pointer DEFAULT_MODE: int @@ -148,7 +159,7 @@ c_buffer = create_string_buffer def create_unicode_buffer(init: int | str, size: int | None = None) -> Array[c_wchar]: ... @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") -def SetPointerType(pointer: type[_Pointer[Any]], cls: Any) -> None: ... # noqa: F811 +def SetPointerType(pointer: type[_Pointer[Any]], cls: Any) -> None: ... def ARRAY(typ: _CT, len: int) -> Array[_CT]: ... # Soft Deprecated, no plans to remove if sys.platform == "win32": diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi index e08b1919d8e599..bba76c1af1b4b0 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi @@ -5,7 +5,7 @@ from _typeshed import DataclassInstance from builtins import type as Type # alias to avoid name clashes with fields named "type" from collections.abc import Callable, Iterable, Mapping from types import GenericAlias -from typing import Any, Generic, Literal, Protocol, TypeVar, overload +from typing import Any, Generic, Literal, Protocol, TypeVar, overload, type_check_only from typing_extensions import Never, TypeIs _T = TypeVar("_T") @@ -31,6 +31,25 @@ if sys.version_info >= (3, 10): _DataclassT = TypeVar("_DataclassT", bound=DataclassInstance) +@type_check_only +class _DataclassFactory(Protocol): + def __call__( + self, + cls: type[_T], + /, + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + match_args: bool = True, + kw_only: bool = False, + slots: bool = False, + weakref_slot: bool = False, + ) -> type[_T]: ... + # define _MISSING_TYPE as an enum within the type stubs, # even though that is not really its type at runtime # this allows us to use Literal[_MISSING_TYPE.MISSING] @@ -114,8 +133,27 @@ class Field(Generic[_T]): init: bool compare: bool metadata: types.MappingProxyType[Any, Any] + + if sys.version_info >= (3, 14): + doc: str | None + if sys.version_info >= (3, 10): kw_only: bool | Literal[_MISSING_TYPE.MISSING] + + if sys.version_info >= (3, 14): + def __init__( + self, + default: _T, + default_factory: Callable[[], _T], + init: bool, + repr: bool, + hash: bool | None, + compare: bool, + metadata: Mapping[Any, Any], + kw_only: bool, + doc: str | None, + ) -> None: ... + elif sys.version_info >= (3, 10): def __init__( self, default: _T, @@ -144,7 +182,48 @@ class Field(Generic[_T]): # NOTE: Actual return type is 'Field[_T]', but we want to help type checkers # to understand the magic that happens at runtime. -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 14): + @overload # `default` and `default_factory` are optional and mutually exclusive. + def field( + *, + default: _T, + default_factory: Literal[_MISSING_TYPE.MISSING] = ..., + init: bool = True, + repr: bool = True, + hash: bool | None = None, + compare: bool = True, + metadata: Mapping[Any, Any] | None = None, + kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ..., + doc: str | None = None, + ) -> _T: ... + @overload + def field( + *, + default: Literal[_MISSING_TYPE.MISSING] = ..., + default_factory: Callable[[], _T], + init: bool = True, + repr: bool = True, + hash: bool | None = None, + compare: bool = True, + metadata: Mapping[Any, Any] | None = None, + kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ..., + doc: str | None = None, + ) -> _T: ... + @overload + def field( + *, + default: Literal[_MISSING_TYPE.MISSING] = ..., + default_factory: Literal[_MISSING_TYPE.MISSING] = ..., + init: bool = True, + repr: bool = True, + hash: bool | None = None, + compare: bool = True, + metadata: Mapping[Any, Any] | None = None, + kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ..., + doc: str | None = None, + ) -> Any: ... + +elif sys.version_info >= (3, 10): @overload # `default` and `default_factory` are optional and mutually exclusive. def field( *, @@ -237,7 +316,28 @@ class InitVar(Generic[_T], metaclass=type): @overload def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore[reportInvalidTypeForm] -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 14): + def make_dataclass( + cls_name: str, + fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], + *, + bases: tuple[type, ...] = (), + namespace: dict[str, Any] | None = None, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + match_args: bool = True, + kw_only: bool = False, + slots: bool = False, + weakref_slot: bool = False, + module: str | None = None, + decorator: _DataclassFactory = ..., + ) -> type: ... + +elif sys.version_info >= (3, 12): def make_dataclass( cls_name: str, fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/datetime.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/datetime.pyi index 72fb5fceb1fb27..37d6a06dfff95e 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/datetime.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/datetime.pyi @@ -73,6 +73,11 @@ class date: @property def day(self) -> int: ... def ctime(self) -> str: ... + + if sys.version_info >= (3, 14): + @classmethod + def strptime(cls, date_string: str, format: str, /) -> Self: ... + # On <3.12, the name of the parameter in the pure-Python implementation # didn't match the name in the C implementation, # meaning it is only *safe* to pass it as a keyword argument on 3.12+ @@ -142,6 +147,11 @@ class time: def isoformat(self, timespec: str = ...) -> str: ... @classmethod def fromisoformat(cls, time_string: str, /) -> Self: ... + + if sys.version_info >= (3, 14): + @classmethod + def strptime(cls, date_string: str, format: str, /) -> Self: ... + # On <3.12, the name of the parameter in the pure-Python implementation # didn't match the name in the C implementation, # meaning it is only *safe* to pass it as a keyword argument on 3.12+ diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/decimal.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/decimal.pyi index 4ded21e0b017ee..b85c0008009208 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/decimal.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/decimal.pyi @@ -1,4 +1,5 @@ import numbers +import sys from _decimal import ( HAVE_CONTEXTVAR as HAVE_CONTEXTVAR, HAVE_THREADS as HAVE_THREADS, @@ -28,6 +29,9 @@ from types import TracebackType from typing import Any, ClassVar, Literal, NamedTuple, final, overload, type_check_only from typing_extensions import Self, TypeAlias +if sys.version_info >= (3, 14): + from _decimal import IEEE_CONTEXT_MAX_BITS as IEEE_CONTEXT_MAX_BITS, IEEEContext as IEEEContext + _Decimal: TypeAlias = Decimal | int _DecimalNew: TypeAlias = Decimal | float | str | tuple[int, Sequence[int], int] _ComparableNum: TypeAlias = Decimal | float | numbers.Rational @@ -66,6 +70,10 @@ class FloatOperation(DecimalException, TypeError): ... class Decimal: def __new__(cls, value: _DecimalNew = "0", context: Context | None = None) -> Self: ... + if sys.version_info >= (3, 14): + @classmethod + def from_number(cls, number: Decimal | float, /) -> Self: ... + @classmethod def from_float(cls, f: float, /) -> Self: ... def __bool__(self) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/dis.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/dis.pyi index cb69eac89c9205..86b6d01e3120d3 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/dis.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/dis.pyi @@ -106,11 +106,40 @@ class Instruction(_Instruction): def jump_target(self) -> int: ... @property def is_jump_target(self) -> bool: ... + if sys.version_info >= (3, 14): + @staticmethod + def make( + opname: str, + arg: int | None, + argval: Any, + argrepr: str, + offset: int, + start_offset: int, + starts_line: bool, + line_number: int | None, + label: int | None = None, + positions: Positions | None = None, + cache_info: list[tuple[str, int, Any]] | None = None, + ) -> Instruction: ... class Bytecode: codeobj: types.CodeType first_line: int - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + show_positions: bool + # 3.14 added `show_positions` + def __init__( + self, + x: _HaveCodeType | str, + *, + first_line: int | None = None, + current_offset: int | None = None, + show_caches: bool = False, + adaptive: bool = False, + show_offsets: bool = False, + show_positions: bool = False, + ) -> None: ... + elif sys.version_info >= (3, 13): show_offsets: bool # 3.13 added `show_offsets` def __init__( @@ -156,7 +185,39 @@ def findlinestarts(code: _HaveCodeType) -> Iterator[tuple[int, int]]: ... def pretty_flags(flags: int) -> str: ... def code_info(x: _HaveCodeType | str) -> str: ... -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 14): + # 3.14 added `show_positions` + def dis( + x: _HaveCodeType | str | bytes | bytearray | None = None, + *, + file: IO[str] | None = None, + depth: int | None = None, + show_caches: bool = False, + adaptive: bool = False, + show_offsets: bool = False, + show_positions: bool = False, + ) -> None: ... + def disassemble( + co: _HaveCodeType, + lasti: int = -1, + *, + file: IO[str] | None = None, + show_caches: bool = False, + adaptive: bool = False, + show_offsets: bool = False, + show_positions: bool = False, + ) -> None: ... + def distb( + tb: types.TracebackType | None = None, + *, + file: IO[str] | None = None, + show_caches: bool = False, + adaptive: bool = False, + show_offsets: bool = False, + show_positions: bool = False, + ) -> None: ... + +elif sys.version_info >= (3, 13): # 3.13 added `show_offsets` def dis( x: _HaveCodeType | str | bytes | bytearray | None = None, @@ -184,10 +245,6 @@ if sys.version_info >= (3, 13): adaptive: bool = False, show_offsets: bool = False, ) -> None: ... - # 3.13 made `show_cache` `None` by default - def get_instructions( - x: _HaveCodeType, *, first_line: int | None = None, show_caches: bool | None = None, adaptive: bool = False - ) -> Iterator[Instruction]: ... elif sys.version_info >= (3, 11): # 3.11 added `show_caches` and `adaptive` @@ -205,9 +262,6 @@ elif sys.version_info >= (3, 11): def distb( tb: types.TracebackType | None = None, *, file: IO[str] | None = None, show_caches: bool = False, adaptive: bool = False ) -> None: ... - def get_instructions( - x: _HaveCodeType, *, first_line: int | None = None, show_caches: bool = False, adaptive: bool = False - ) -> Iterator[Instruction]: ... else: def dis( @@ -215,6 +269,19 @@ else: ) -> None: ... def disassemble(co: _HaveCodeType, lasti: int = -1, *, file: IO[str] | None = None) -> None: ... def distb(tb: types.TracebackType | None = None, *, file: IO[str] | None = None) -> None: ... + +if sys.version_info >= (3, 13): + # 3.13 made `show_cache` `None` by default + def get_instructions( + x: _HaveCodeType, *, first_line: int | None = None, show_caches: bool | None = None, adaptive: bool = False + ) -> Iterator[Instruction]: ... + +elif sys.version_info >= (3, 11): + def get_instructions( + x: _HaveCodeType, *, first_line: int | None = None, show_caches: bool = False, adaptive: bool = False + ) -> Iterator[Instruction]: ... + +else: def get_instructions(x: _HaveCodeType, *, first_line: int | None = None) -> Iterator[Instruction]: ... def show_code(co: _HaveCodeType, *, file: IO[str] | None = None) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/__init__.pyi index 628ffb2b793a2d..53f8c350b01e35 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/__init__.pyi @@ -1,6 +1,7 @@ from collections.abc import Callable +from email._policybase import _MessageT from email.message import Message -from email.policy import Policy, _MessageT +from email.policy import Policy from typing import IO, overload from typing_extensions import TypeAlias diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/_policybase.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/_policybase.pyi index 5266609e597fb1..0fb890d424b10f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/_policybase.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/_policybase.pyi @@ -2,12 +2,13 @@ from abc import ABCMeta, abstractmethod from email.errors import MessageDefect from email.header import Header from email.message import Message -from typing import Generic, Protocol, TypeVar, type_check_only +from typing import Any, Generic, Protocol, TypeVar, type_check_only from typing_extensions import Self __all__ = ["Policy", "Compat32", "compat32"] -_MessageT = TypeVar("_MessageT", bound=Message, default=Message) +_MessageT = TypeVar("_MessageT", bound=Message[Any, Any], default=Message[str, str]) +_MessageT_co = TypeVar("_MessageT_co", covariant=True, bound=Message[Any, Any], default=Message[str, str]) @type_check_only class _MessageFactory(Protocol[_MessageT]): @@ -16,13 +17,13 @@ class _MessageFactory(Protocol[_MessageT]): # Policy below is the only known direct subclass of _PolicyBase. We therefore # assume that the __init__ arguments and attributes of _PolicyBase are # the same as those of Policy. -class _PolicyBase(Generic[_MessageT]): +class _PolicyBase(Generic[_MessageT_co]): max_line_length: int | None linesep: str cte_type: str raise_on_defect: bool mangle_from_: bool - message_factory: _MessageFactory[_MessageT] | None + message_factory: _MessageFactory[_MessageT_co] | None # Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 verify_generated_headers: bool @@ -34,7 +35,7 @@ class _PolicyBase(Generic[_MessageT]): cte_type: str = "8bit", raise_on_defect: bool = False, mangle_from_: bool = ..., # default depends on sub-class - message_factory: _MessageFactory[_MessageT] | None = None, + message_factory: _MessageFactory[_MessageT_co] | None = None, # Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 verify_generated_headers: bool = True, ) -> None: ... @@ -46,15 +47,17 @@ class _PolicyBase(Generic[_MessageT]): cte_type: str = ..., raise_on_defect: bool = ..., mangle_from_: bool = ..., - message_factory: _MessageFactory[_MessageT] | None = ..., + message_factory: _MessageFactory[_MessageT_co] | None = ..., # Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 verify_generated_headers: bool = ..., ) -> Self: ... def __add__(self, other: Policy) -> Self: ... -class Policy(_PolicyBase[_MessageT], metaclass=ABCMeta): - def handle_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ... - def register_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ... +class Policy(_PolicyBase[_MessageT_co], metaclass=ABCMeta): + # Every Message object has a `defects` attribute, so the following + # methods will work for any Message object. + def handle_defect(self, obj: Message[Any, Any], defect: MessageDefect) -> None: ... + def register_defect(self, obj: Message[Any, Any], defect: MessageDefect) -> None: ... def header_max_count(self, name: str) -> int | None: ... @abstractmethod def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... @@ -67,11 +70,11 @@ class Policy(_PolicyBase[_MessageT], metaclass=ABCMeta): @abstractmethod def fold_binary(self, name: str, value: str) -> bytes: ... -class Compat32(Policy[_MessageT]): +class Compat32(Policy[_MessageT_co]): def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... def header_fetch_parse(self, name: str, value: str) -> str | Header: ... # type: ignore[override] def fold(self, name: str, value: str) -> str: ... def fold_binary(self, name: str, value: str) -> bytes: ... -compat32: Compat32[Message] +compat32: Compat32[Message[str, str]] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/feedparser.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/feedparser.pyi index 8c268ca1ae18c5..d9279e9cd996d4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/feedparser.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/feedparser.pyi @@ -1,12 +1,11 @@ from collections.abc import Callable +from email._policybase import _MessageT from email.message import Message from email.policy import Policy -from typing import Generic, TypeVar, overload +from typing import Generic, overload __all__ = ["FeedParser", "BytesFeedParser"] -_MessageT = TypeVar("_MessageT", bound=Message, default=Message) - class FeedParser(Generic[_MessageT]): @overload def __init__(self: FeedParser[Message], _factory: None = None, *, policy: Policy[Message] = ...) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/generator.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/generator.pyi index dfa0604a20a98c..d30e686299fabb 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/generator.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/generator.pyi @@ -7,7 +7,7 @@ from typing_extensions import Self __all__ = ["Generator", "DecodedGenerator", "BytesGenerator"] # By default, generators do not have a message policy. -_MessageT = TypeVar("_MessageT", bound=Message, default=Any) +_MessageT = TypeVar("_MessageT", bound=Message[Any, Any], default=Any) class Generator(Generic[_MessageT]): maxheaderlen: int | None diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/message.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/message.pyi index ebad05a1cf7b6e..e4d14992168a17 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/message.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/message.pyi @@ -12,12 +12,12 @@ __all__ = ["Message", "EmailMessage"] _T = TypeVar("_T") # Type returned by Policy.header_fetch_parse, often str or Header. -_HeaderT = TypeVar("_HeaderT", default=str) -_HeaderParamT = TypeVar("_HeaderParamT", default=str) +_HeaderT_co = TypeVar("_HeaderT_co", covariant=True, default=str) +_HeaderParamT_contra = TypeVar("_HeaderParamT_contra", contravariant=True, default=str) # Represents headers constructed by HeaderRegistry. Those are sub-classes # of BaseHeader and another header type. -_HeaderRegistryT = TypeVar("_HeaderRegistryT", default=Any) -_HeaderRegistryParamT = TypeVar("_HeaderRegistryParamT", default=Any) +_HeaderRegistryT_co = TypeVar("_HeaderRegistryT_co", covariant=True, default=Any) +_HeaderRegistryParamT_contra = TypeVar("_HeaderRegistryParamT_contra", contravariant=True, default=Any) _PayloadType: TypeAlias = Message | str _EncodedPayloadType: TypeAlias = Message | bytes @@ -30,7 +30,7 @@ class _SupportsEncodeToPayload(Protocol): class _SupportsDecodeToPayload(Protocol): def decode(self, encoding: str, errors: str, /) -> _PayloadType | _MultipartPayloadType: ... -class Message(Generic[_HeaderT, _HeaderParamT]): +class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): # The policy attributes and arguments in this class and its subclasses # would ideally use Policy[Self], but this is not possible. policy: Policy[Any] # undocumented @@ -76,22 +76,22 @@ class Message(Generic[_HeaderT, _HeaderParamT]): # This is important for protocols using __getitem__, like SupportsKeysAndGetItem # Morally, the return type should be `AnyOf[_HeaderType, None]`, # so using "the Any trick" instead. - def __getitem__(self, name: str) -> _HeaderT | MaybeNone: ... - def __setitem__(self, name: str, val: _HeaderParamT) -> None: ... + def __getitem__(self, name: str) -> _HeaderT_co | MaybeNone: ... + def __setitem__(self, name: str, val: _HeaderParamT_contra) -> None: ... def __delitem__(self, name: str) -> None: ... def keys(self) -> list[str]: ... - def values(self) -> list[_HeaderT]: ... - def items(self) -> list[tuple[str, _HeaderT]]: ... + def values(self) -> list[_HeaderT_co]: ... + def items(self) -> list[tuple[str, _HeaderT_co]]: ... @overload - def get(self, name: str, failobj: None = None) -> _HeaderT | None: ... + def get(self, name: str, failobj: None = None) -> _HeaderT_co | None: ... @overload - def get(self, name: str, failobj: _T) -> _HeaderT | _T: ... + def get(self, name: str, failobj: _T) -> _HeaderT_co | _T: ... @overload - def get_all(self, name: str, failobj: None = None) -> list[_HeaderT] | None: ... + def get_all(self, name: str, failobj: None = None) -> list[_HeaderT_co] | None: ... @overload - def get_all(self, name: str, failobj: _T) -> list[_HeaderT] | _T: ... + def get_all(self, name: str, failobj: _T) -> list[_HeaderT_co] | _T: ... def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ... - def replace_header(self, _name: str, _value: _HeaderParamT) -> None: ... + def replace_header(self, _name: str, _value: _HeaderParamT_contra) -> None: ... def get_content_type(self) -> str: ... def get_content_maintype(self) -> str: ... def get_content_subtype(self) -> str: ... @@ -144,18 +144,18 @@ class Message(Generic[_HeaderT, _HeaderParamT]): replace: bool = False, ) -> None: ... # The following two methods are undocumented, but a source code comment states that they are public API - def set_raw(self, name: str, value: _HeaderParamT) -> None: ... - def raw_items(self) -> Iterator[tuple[str, _HeaderT]]: ... + def set_raw(self, name: str, value: _HeaderParamT_contra) -> None: ... + def raw_items(self) -> Iterator[tuple[str, _HeaderT_co]]: ... -class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]): +class MIMEPart(Message[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]): def __init__(self, policy: Policy[Any] | None = None) -> None: ... - def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ... + def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT_co] | None: ... def attach(self, payload: Self) -> None: ... # type: ignore[override] # The attachments are created via type(self) in the attach method. It's theoretically # possible to sneak other attachment types into a MIMEPart instance, but could cause # cause unforseen consequences. def iter_attachments(self) -> Iterator[Self]: ... - def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ... + def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT_co]]: ... def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ... def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ... def make_related(self, boundary: str | None = None) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/message.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/message.pyi index 2a5f46296150ba..a1e370e2eab51a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/message.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/message.pyi @@ -1,5 +1,6 @@ +from email._policybase import _MessageT from email.mime.nonmultipart import MIMENonMultipart -from email.policy import Policy, _MessageT +from email.policy import Policy __all__ = ["MIMEMessage"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/multipart.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/multipart.pyi index 1c229f7436a8ac..fb9599edbcb8f4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/multipart.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/multipart.pyi @@ -1,7 +1,8 @@ from collections.abc import Sequence from email import _ParamsType +from email._policybase import _MessageT from email.mime.base import MIMEBase -from email.policy import Policy, _MessageT +from email.policy import Policy __all__ = ["MIMEMultipart"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/text.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/text.pyi index 74d5ef4c5caea3..edfa67a092427f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/text.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/mime/text.pyi @@ -1,5 +1,5 @@ +from email._policybase import Policy from email.mime.nonmultipart import MIMENonMultipart -from email.policy import Policy __all__ = ["MIMEText"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/parser.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/parser.pyi index a1a57b4eef4b16..a4924a6cbd88f0 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/parser.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/parser.pyi @@ -1,20 +1,21 @@ from _typeshed import SupportsRead from collections.abc import Callable +from email._policybase import _MessageT from email.feedparser import BytesFeedParser as BytesFeedParser, FeedParser as FeedParser from email.message import Message from email.policy import Policy from io import _WrappedBuffer -from typing import Generic, TypeVar, overload +from typing import Generic, overload __all__ = ["Parser", "HeaderParser", "BytesParser", "BytesHeaderParser", "FeedParser", "BytesFeedParser"] -_MessageT = TypeVar("_MessageT", bound=Message, default=Message) - class Parser(Generic[_MessageT]): @overload - def __init__(self: Parser[Message[str, str]], _class: None = None, *, policy: Policy[Message[str, str]] = ...) -> None: ... + def __init__(self: Parser[Message[str, str]], _class: None = None) -> None: ... @overload - def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ... + def __init__(self, _class: None = None, *, policy: Policy[_MessageT]) -> None: ... + @overload + def __init__(self, _class: Callable[[], _MessageT] | None, *, policy: Policy[_MessageT] = ...) -> None: ... def parse(self, fp: SupportsRead[str], headersonly: bool = False) -> _MessageT: ... def parsestr(self, text: str, headersonly: bool = False) -> _MessageT: ... @@ -25,9 +26,9 @@ class HeaderParser(Parser[_MessageT]): class BytesParser(Generic[_MessageT]): parser: Parser[_MessageT] @overload - def __init__( - self: BytesParser[Message[str, str]], _class: None = None, *, policy: Policy[Message[str, str]] = ... - ) -> None: ... + def __init__(self: BytesParser[Message[str, str]], _class: None = None) -> None: ... + @overload + def __init__(self, _class: None = None, *, policy: Policy[_MessageT]) -> None: ... @overload def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ... def parse(self, fp: _WrappedBuffer, headersonly: bool = False) -> _MessageT: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/policy.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/policy.pyi index 5005483edf868b..35c999919eedee 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/policy.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/policy.pyi @@ -1,14 +1,12 @@ from collections.abc import Callable -from email._policybase import Compat32 as Compat32, Policy as Policy, _MessageFactory, compat32 as compat32 +from email._policybase import Compat32 as Compat32, Policy as Policy, _MessageFactory, _MessageT, compat32 as compat32 from email.contentmanager import ContentManager -from email.message import EmailMessage, Message -from typing import Any, TypeVar, overload +from email.message import EmailMessage +from typing import Any, overload from typing_extensions import Self __all__ = ["Compat32", "compat32", "Policy", "EmailPolicy", "default", "strict", "SMTP", "HTTP"] -_MessageT = TypeVar("_MessageT", bound=Message, default=Message) - class EmailPolicy(Policy[_MessageT]): utf8: bool refold_source: str diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/encodings/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/encodings/__init__.pyi index 2e83f0f65a71ad..12ec6792d49b50 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/encodings/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/encodings/__init__.pyi @@ -1,4 +1,3 @@ -from _typeshed import Incomplete from codecs import CodecInfo class CodecRegistryError(LookupError, SystemError): ... @@ -7,4 +6,4 @@ def normalize_encoding(encoding: str | bytes) -> str: ... def search_function(encoding: str) -> CodecInfo | None: ... # Needed for submodules -def __getattr__(name: str) -> Incomplete: ... +def __getattr__(name: str): ... # incomplete module diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/fnmatch.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/fnmatch.pyi index 7051c999c43052..345c4576497de6 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/fnmatch.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/fnmatch.pyi @@ -1,9 +1,15 @@ +import sys from collections.abc import Iterable from typing import AnyStr __all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] +if sys.version_info >= (3, 14): + __all__ += ["filterfalse"] def fnmatch(name: AnyStr, pat: AnyStr) -> bool: ... def fnmatchcase(name: AnyStr, pat: AnyStr) -> bool: ... def filter(names: Iterable[AnyStr], pat: AnyStr) -> list[AnyStr]: ... def translate(pat: str) -> str: ... + +if sys.version_info >= (3, 14): + def filterfalse(names: Iterable[AnyStr], pat: AnyStr) -> list[AnyStr]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/functools.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/functools.pyi index d35c295754e5e4..e31399fb870547 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/functools.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/functools.pyi @@ -3,7 +3,7 @@ import types from _typeshed import SupportsAllComparisons, SupportsItems from collections.abc import Callable, Hashable, Iterable, Sized from types import GenericAlias -from typing import Any, Generic, Literal, NamedTuple, TypedDict, TypeVar, final, overload +from typing import Any, Final, Generic, Literal, NamedTuple, TypedDict, TypeVar, final, overload from typing_extensions import ParamSpec, Self, TypeAlias __all__ = [ @@ -31,10 +31,16 @@ _RWrapped = TypeVar("_RWrapped") _PWrapper = ParamSpec("_PWrapper") _RWrapper = TypeVar("_RWrapper") +if sys.version_info >= (3, 14): + @overload + def reduce(function: Callable[[_T, _S], _T], iterable: Iterable[_S], /, initial: _T) -> _T: ... + +else: + @overload + def reduce(function: Callable[[_T, _S], _T], iterable: Iterable[_S], initial: _T, /) -> _T: ... + @overload -def reduce(function: Callable[[_T, _S], _T], sequence: Iterable[_S], initial: _T, /) -> _T: ... -@overload -def reduce(function: Callable[[_T, _T], _T], sequence: Iterable[_T], /) -> _T: ... +def reduce(function: Callable[[_T, _T], _T], iterable: Iterable[_T], /) -> _T: ... class _CacheInfo(NamedTuple): hits: int @@ -61,19 +67,33 @@ def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Calla @overload def lru_cache(maxsize: Callable[..., _T], typed: bool = False) -> _lru_cache_wrapper[_T]: ... -if sys.version_info >= (3, 12): - WRAPPER_ASSIGNMENTS: tuple[ - Literal["__module__"], - Literal["__name__"], - Literal["__qualname__"], - Literal["__doc__"], - Literal["__annotations__"], - Literal["__type_params__"], +if sys.version_info >= (3, 14): + WRAPPER_ASSIGNMENTS: Final[ + tuple[ + Literal["__module__"], + Literal["__name__"], + Literal["__qualname__"], + Literal["__doc__"], + Literal["__annotate__"], + Literal["__type_params__"], + ] + ] +elif sys.version_info >= (3, 12): + WRAPPER_ASSIGNMENTS: Final[ + tuple[ + Literal["__module__"], + Literal["__name__"], + Literal["__qualname__"], + Literal["__doc__"], + Literal["__annotations__"], + Literal["__type_params__"], + ] ] else: - WRAPPER_ASSIGNMENTS: tuple[ - Literal["__module__"], Literal["__name__"], Literal["__qualname__"], Literal["__doc__"], Literal["__annotations__"] + WRAPPER_ASSIGNMENTS: Final[ + tuple[Literal["__module__"], Literal["__name__"], Literal["__qualname__"], Literal["__doc__"], Literal["__annotations__"]] ] + WRAPPER_UPDATES: tuple[Literal["__dict__"]] class _Wrapped(Generic[_PWrapped, _RWrapped, _PWrapper, _RWrapper]): @@ -86,7 +106,20 @@ class _Wrapped(Generic[_PWrapped, _RWrapped, _PWrapper, _RWrapper]): class _Wrapper(Generic[_PWrapped, _RWrapped]): def __call__(self, f: Callable[_PWrapper, _RWrapper]) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ... -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 14): + def update_wrapper( + wrapper: Callable[_PWrapper, _RWrapper], + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Iterable[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotate__", "__type_params__"), + updated: Iterable[str] = ("__dict__",), + ) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ... + def wraps( + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Iterable[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotate__", "__type_params__"), + updated: Iterable[str] = ("__dict__",), + ) -> _Wrapper[_PWrapped, _RWrapped]: ... + +elif sys.version_info >= (3, 12): def update_wrapper( wrapper: Callable[_PWrapper, _RWrapper], wrapped: Callable[_PWrapped, _RWrapped], @@ -204,3 +237,11 @@ def _make_key( type: Any = ..., len: Callable[[Sized], int] = ..., ) -> Hashable: ... + +if sys.version_info >= (3, 14): + @final + class _PlaceholderType: ... + + Placeholder: Final[_PlaceholderType] + + __all__ += ["Placeholder"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/getpass.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/getpass.pyi index 6104e0dedfee4f..bb3013dfbf3931 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/getpass.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/getpass.pyi @@ -1,8 +1,14 @@ +import sys from typing import TextIO __all__ = ["getpass", "getuser", "GetPassWarning"] -def getpass(prompt: str = "Password: ", stream: TextIO | None = None) -> str: ... +if sys.version_info >= (3, 14): + def getpass(prompt: str = "Password: ", stream: TextIO | None = None, *, echo_char: str | None = None) -> str: ... + +else: + def getpass(prompt: str = "Password: ", stream: TextIO | None = None) -> str: ... + def getuser() -> str: ... class GetPassWarning(UserWarning): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi index b7fb40fbd82ee1..883456b1ddc3de 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi @@ -1,4 +1,3 @@ -import _compression import sys import zlib from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath @@ -6,6 +5,11 @@ from io import FileIO, TextIOWrapper from typing import Final, Literal, Protocol, overload from typing_extensions import TypeAlias +if sys.version_info >= (3, 14): + from compression._common._streams import BaseStream, DecompressReader +else: + from _compression import BaseStream, DecompressReader + __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] _ReadBinaryMode: TypeAlias = Literal["r", "rb"] @@ -84,7 +88,7 @@ class _PaddedFile: class BadGzipFile(OSError): ... -class GzipFile(_compression.BaseStream): +class GzipFile(BaseStream): myfileobj: FileIO | None mode: object name: str @@ -153,7 +157,7 @@ class GzipFile(_compression.BaseStream): def seek(self, offset: int, whence: int = 0) -> int: ... def readline(self, size: int | None = -1) -> bytes: ... -class _GzipReader(_compression.DecompressReader): +class _GzipReader(DecompressReader): def __init__(self, fp: _ReadableFileobj) -> None: ... def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/http/client.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/http/client.pyi index 9e0f61598cb812..5c35dff28d43a7 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/http/client.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/http/client.pyi @@ -5,6 +5,7 @@ import sys import types from _typeshed import MaybeNone, ReadableBuffer, SupportsRead, SupportsReadline, WriteableBuffer from collections.abc import Callable, Iterable, Iterator, Mapping +from email._policybase import _MessageT from socket import socket from typing import BinaryIO, Literal, TypeVar, overload from typing_extensions import Self, TypeAlias @@ -33,7 +34,6 @@ __all__ = [ _DataType: TypeAlias = SupportsRead[bytes] | Iterable[ReadableBuffer] | ReadableBuffer _T = TypeVar("_T") -_MessageT = TypeVar("_MessageT", bound=email.message.Message) _HeaderValue: TypeAlias = ReadableBuffer | str | int HTTP_PORT: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/http/server.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/http/server.pyi index 1a6fde6000d9fa..429bb65bb0efc3 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/http/server.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/http/server.pyi @@ -3,12 +3,25 @@ import email.message import io import socketserver import sys -from _typeshed import StrPath, SupportsRead, SupportsWrite -from collections.abc import Mapping, Sequence -from typing import Any, AnyStr, BinaryIO, ClassVar -from typing_extensions import deprecated +from _ssl import _PasswordType +from _typeshed import ReadableBuffer, StrOrBytesPath, StrPath, SupportsRead, SupportsWrite +from collections.abc import Callable, Iterable, Mapping, Sequence +from ssl import Purpose, SSLContext +from typing import Any, AnyStr, BinaryIO, ClassVar, Protocol, type_check_only +from typing_extensions import Self, deprecated -__all__ = ["HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler", "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler"] +if sys.version_info >= (3, 14): + __all__ = [ + "HTTPServer", + "ThreadingHTTPServer", + "HTTPSServer", + "ThreadingHTTPSServer", + "BaseHTTPRequestHandler", + "SimpleHTTPRequestHandler", + "CGIHTTPRequestHandler", + ] +else: + __all__ = ["HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler", "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler"] class HTTPServer(socketserver.TCPServer): server_name: str @@ -16,6 +29,39 @@ class HTTPServer(socketserver.TCPServer): class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): ... +if sys.version_info >= (3, 14): + @type_check_only + class _SSLModule(Protocol): + @staticmethod + def create_default_context( + purpose: Purpose = ..., + *, + cafile: StrOrBytesPath | None = None, + capath: StrOrBytesPath | None = None, + cadata: str | ReadableBuffer | None = None, + ) -> SSLContext: ... + + class HTTPSServer(HTTPServer): + ssl: _SSLModule + certfile: StrOrBytesPath + keyfile: StrOrBytesPath | None + password: _PasswordType | None + alpn_protocols: Iterable[str] + def __init__( + self, + server_address: socketserver._AfInetAddress, + RequestHandlerClass: Callable[[Any, _socket._RetAddress, Self], socketserver.BaseRequestHandler], + bind_and_activate: bool = True, + *, + certfile: StrOrBytesPath, + keyfile: StrOrBytesPath | None = None, + password: _PasswordType | None = None, + alpn_protocols: Iterable[str] | None = None, + ) -> None: ... + def server_activate(self) -> None: ... + + class ThreadingHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer): ... + class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): client_address: tuple[str, int] close_connection: bool diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/imaplib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/imaplib.pyi index ccee92bd5e8849..536985a592b7f0 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/imaplib.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/imaplib.pyi @@ -1,16 +1,16 @@ import subprocess import sys import time -from _typeshed import ReadableBuffer, SizedBuffer +from _typeshed import ReadableBuffer, SizedBuffer, Unused from builtins import list as _list # conflicts with a method named "list" -from collections.abc import Callable +from collections.abc import Callable, Generator from datetime import datetime from re import Pattern from socket import socket as _socket from ssl import SSLContext, SSLSocket from types import TracebackType from typing import IO, Any, Literal, SupportsAbs, SupportsInt -from typing_extensions import Self, TypeAlias +from typing_extensions import Self, TypeAlias, deprecated __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", "Int2AP", "ParseFlags", "Time2Internaldate", "IMAP4_SSL"] @@ -42,11 +42,17 @@ class IMAP4: PROTOCOL_VERSION: str def __init__(self, host: str = "", port: int = 143, timeout: float | None = None) -> None: ... def open(self, host: str = "", port: int = 143, timeout: float | None = None) -> None: ... + if sys.version_info >= (3, 14): + @property + @deprecated("IMAP4.file is unsupported, can cause errors, and may be removed.") + def file(self) -> IO[str] | IO[bytes]: ... + else: + file: IO[str] | IO[bytes] + def __getattr__(self, attr: str) -> Any: ... host: str port: int sock: _socket - file: IO[str] | IO[bytes] def read(self, size: int) -> bytes: ... def readline(self) -> bytes: ... def send(self, data: ReadableBuffer) -> None: ... @@ -72,6 +78,9 @@ class IMAP4: def getannotation(self, mailbox: str, entry: str, attribute: str) -> _CommandResults: ... def getquota(self, root: str) -> _CommandResults: ... def getquotaroot(self, mailbox: str) -> _CommandResults: ... + if sys.version_info >= (3, 14): + def idle(self, duration: float | None = None) -> Idler: ... + def list(self, directory: str = '""', pattern: str = "*") -> tuple[str, _AnyResponseData]: ... def login(self, user: str, password: str) -> tuple[Literal["OK"], _list[bytes]]: ... def login_cram_md5(self, user: str, password: str) -> _CommandResults: ... @@ -100,6 +109,15 @@ class IMAP4: def xatom(self, name: str, *args: str) -> _CommandResults: ... def print_log(self) -> None: ... +if sys.version_info >= (3, 14): + class Idler: + def __init__(self, imap: IMAP4, duration: float | None = None) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, exc_type: object, exc_val: Unused, exc_tb: Unused) -> Literal[False]: ... + def __iter__(self) -> Self: ... + def __next__(self) -> tuple[str, float | None]: ... + def burst(self, interval: float = 0.1) -> Generator[tuple[str, float | None]]: ... + class IMAP4_SSL(IMAP4): if sys.version_info < (3, 12): keyfile: str @@ -119,14 +137,25 @@ class IMAP4_SSL(IMAP4): timeout: float | None = None, ) -> None: ... sslobj: SSLSocket - file: IO[Any] + if sys.version_info >= (3, 14): + @property + @deprecated("IMAP4_SSL.file is unsupported, can cause errors, and may be removed.") + def file(self) -> IO[Any]: ... + else: + file: IO[Any] + def open(self, host: str = "", port: int | None = 993, timeout: float | None = None) -> None: ... def ssl(self) -> SSLSocket: ... class IMAP4_stream(IMAP4): command: str def __init__(self, command: str) -> None: ... - file: IO[Any] + if sys.version_info >= (3, 14): + @property + @deprecated("IMAP4_stream.file is unsupported, can cause errors, and may be removed.") + def file(self) -> IO[Any]: ... + else: + file: IO[Any] process: subprocess.Popen[bytes] writefile: IO[Any] readfile: IO[Any] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi index c525418c104b4b..e19c2a634aa093 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi @@ -2,7 +2,7 @@ import dis import enum import sys import types -from _typeshed import StrPath +from _typeshed import AnnotationForm, StrPath from collections import OrderedDict from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Generator, Mapping, Sequence, Set as AbstractSet from types import ( @@ -28,6 +28,9 @@ from types import ( from typing import Any, ClassVar, Final, Literal, NamedTuple, Protocol, TypeVar, overload from typing_extensions import ParamSpec, Self, TypeAlias, TypeGuard, TypeIs +if sys.version_info >= (3, 14): + from annotationlib import Format + if sys.version_info >= (3, 11): __all__ = [ "ArgInfo", @@ -139,6 +142,8 @@ if sys.version_info >= (3, 11): "getasyncgenstate", "BufferFlags", ] + if sys.version_info >= (3, 14): + __all__ += ["CO_HAS_DOCSTRING", "CO_METHOD", "ispackage"] _P = ParamSpec("_P") _T = TypeVar("_T") @@ -172,6 +177,9 @@ CO_COROUTINE: Final = 128 CO_ITERABLE_COROUTINE: Final = 256 CO_ASYNC_GENERATOR: Final = 512 TPFLAGS_IS_ABSTRACT: Final = 1048576 +if sys.version_info >= (3, 14): + CO_HAS_DOCSTRING: Final = 67108864 + CO_METHOD: Final = 134217728 modulesbyfile: dict[str, Any] @@ -199,6 +207,11 @@ def getmodulename(path: StrPath) -> str | None: ... def ismodule(object: object) -> TypeIs[ModuleType]: ... def isclass(object: object) -> TypeIs[type[Any]]: ... def ismethod(object: object) -> TypeIs[MethodType]: ... + +if sys.version_info >= (3, 14): + # Not TypeIs because it does not return True for all modules + def ispackage(object: object) -> TypeGuard[ModuleType]: ... + def isfunction(object: object) -> TypeIs[FunctionType]: ... if sys.version_info >= (3, 12): @@ -294,7 +307,18 @@ _IntrospectableCallable: TypeAlias = Callable[..., Any] # # Introspecting callables with the Signature object # -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 14): + def signature( + obj: _IntrospectableCallable, + *, + follow_wrapped: bool = True, + globals: Mapping[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + eval_str: bool = False, + annotation_format: Format = Format.VALUE, # noqa: Y011 + ) -> Signature: ... + +elif sys.version_info >= (3, 10): def signature( obj: _IntrospectableCallable, *, @@ -323,7 +347,19 @@ class Signature: def bind_partial(self, *args: Any, **kwargs: Any) -> BoundArguments: ... def replace(self, *, parameters: Sequence[Parameter] | type[_void] | None = ..., return_annotation: Any = ...) -> Self: ... __replace__ = replace - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 14): + @classmethod + def from_callable( + cls, + obj: _IntrospectableCallable, + *, + follow_wrapped: bool = True, + globals: Mapping[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + eval_str: bool = False, + annotation_format: Format = Format.VALUE, # noqa: Y011 + ) -> Self: ... + elif sys.version_info >= (3, 10): @classmethod def from_callable( cls, @@ -337,20 +373,24 @@ class Signature: else: @classmethod def from_callable(cls, obj: _IntrospectableCallable, *, follow_wrapped: bool = True) -> Self: ... - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + def format(self, *, max_width: int | None = None, quote_annotation_strings: bool = True) -> str: ... + elif sys.version_info >= (3, 13): def format(self, *, max_width: int | None = None) -> str: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 14): + from annotationlib import get_annotations as get_annotations +elif sys.version_info >= (3, 10): def get_annotations( obj: Callable[..., object] | type[object] | ModuleType, # any callable, class, or module *, globals: Mapping[str, Any] | None = None, # value types depend on the key locals: Mapping[str, Any] | None = None, # value types depend on the key eval_str: bool = False, - ) -> dict[str, Any]: ... # values are type expressions + ) -> dict[str, AnnotationForm]: ... # values are type expressions # The name is the same as the enum's name in CPython class _ParameterKind(enum.IntEnum): @@ -461,7 +501,13 @@ class ArgInfo(NamedTuple): locals: dict[str, Any] def getargvalues(frame: FrameType) -> ArgInfo: ... -def formatannotation(annotation: object, base_module: str | None = None) -> str: ... + +if sys.version_info >= (3, 14): + def formatannotation(annotation: object, base_module: str | None = None, *, quote_annotation_strings: bool = True) -> str: ... + +else: + def formatannotation(annotation: object, base_module: str | None = None) -> str: ... + def formatannotationrelativeto(object: object) -> Callable[[object], str]: ... if sys.version_info < (3, 11): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/io.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/io.pyi index 5c26cb245a2f1b..1313df183d36d2 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/io.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/io.pyi @@ -20,7 +20,7 @@ from _io import ( open as open, open_code as open_code, ) -from typing import Final +from typing import Final, Protocol, TypeVar __all__ = [ "BlockingIOError", @@ -44,11 +44,17 @@ __all__ = [ "SEEK_END", ] +if sys.version_info >= (3, 14): + __all__ += ["Reader", "Writer"] + if sys.version_info >= (3, 11): from _io import text_encoding as text_encoding __all__ += ["DEFAULT_BUFFER_SIZE", "IncrementalNewlineDecoder", "text_encoding"] +_T_co = TypeVar("_T_co", covariant=True) +_T_contra = TypeVar("_T_contra", contravariant=True) + SEEK_SET: Final = 0 SEEK_CUR: Final = 1 SEEK_END: Final = 2 @@ -58,3 +64,10 @@ class IOBase(_IOBase, metaclass=abc.ABCMeta): ... class RawIOBase(_RawIOBase, IOBase): ... class BufferedIOBase(_BufferedIOBase, IOBase): ... class TextIOBase(_TextIOBase, IOBase): ... + +if sys.version_info >= (3, 14): + class Reader(Protocol[_T_co]): + def read(self, size: int = ..., /) -> _T_co: ... + + class Writer(Protocol[_T_contra]): + def write(self, data: _T_contra, /) -> int: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ipaddress.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ipaddress.pyi index 6883895fd21933..9df6bab7c167ba 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ipaddress.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ipaddress.pyi @@ -28,8 +28,9 @@ class _IPAddressBase: def exploded(self) -> str: ... @property def reverse_pointer(self) -> str: ... - @property - def version(self) -> int: ... + if sys.version_info < (3, 14): + @property + def version(self) -> int: ... class _BaseAddress(_IPAddressBase): def __add__(self, other: int) -> Self: ... @@ -104,10 +105,14 @@ class _BaseNetwork(_IPAddressBase, Generic[_A]): def hostmask(self) -> _A: ... class _BaseV4: - @property - def version(self) -> Literal[4]: ... - @property - def max_prefixlen(self) -> Literal[32]: ... + if sys.version_info >= (3, 14): + version: Final = 4 + max_prefixlen: Final = 32 + else: + @property + def version(self) -> Literal[4]: ... + @property + def max_prefixlen(self) -> Literal[32]: ... class IPv4Address(_BaseV4, _BaseAddress): def __init__(self, address: object) -> None: ... @@ -151,10 +156,14 @@ class IPv4Interface(IPv4Address): def with_prefixlen(self) -> str: ... class _BaseV6: - @property - def version(self) -> Literal[6]: ... - @property - def max_prefixlen(self) -> Literal[128]: ... + if sys.version_info >= (3, 14): + version: Final = 6 + max_prefixlen: Final = 128 + else: + @property + def version(self) -> Literal[6]: ... + @property + def max_prefixlen(self) -> Literal[128]: ... class IPv6Address(_BaseV6, _BaseAddress): def __init__(self, address: object) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/logging/handlers.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/logging/handlers.pyi index 2c7ec05afe9a30..9636b81dc4f3c1 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/logging/handlers.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/logging/handlers.pyi @@ -8,7 +8,9 @@ from logging import FileHandler, Handler, LogRecord from re import Pattern from socket import SocketKind, socket from threading import Thread +from types import TracebackType from typing import Any, ClassVar, Final, Protocol, TypeVar +from typing_extensions import Self _T = TypeVar("_T") @@ -142,9 +144,19 @@ class SysLogHandler(Handler): priority_names: ClassVar[dict[str, int]] # undocumented facility_names: ClassVar[dict[str, int]] # undocumented priority_map: ClassVar[dict[str, str]] # undocumented - def __init__( - self, address: tuple[str, int] | str = ("localhost", 514), facility: str | int = 1, socktype: SocketKind | None = None - ) -> None: ... + if sys.version_info >= (3, 14): + timeout: float | None + def __init__( + self, + address: tuple[str, int] | str = ("localhost", 514), + facility: str | int = 1, + socktype: SocketKind | None = None, + timeout: float | None = None, + ) -> None: ... + else: + def __init__( + self, address: tuple[str, int] | str = ("localhost", 514), facility: str | int = 1, socktype: SocketKind | None = None + ) -> None: ... if sys.version_info >= (3, 11): def createSocket(self) -> None: ... @@ -237,3 +249,9 @@ class QueueListener: def stop(self) -> None: ... def enqueue_sentinel(self) -> None: ... def handle(self, record: LogRecord) -> None: ... + + if sys.version_info >= (3, 14): + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None + ) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi index 2f0279f5986bd0..b066d222466ba3 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi @@ -1,4 +1,4 @@ -from _compression import BaseStream +import sys from _lzma import ( CHECK_CRC32 as CHECK_CRC32, CHECK_CRC64 as CHECK_CRC64, @@ -38,6 +38,11 @@ from _typeshed import ReadableBuffer, StrOrBytesPath from typing import IO, Literal, TextIO, overload from typing_extensions import Self, TypeAlias +if sys.version_info >= (3, 14): + from compression._common._streams import BaseStream +else: + from _compression import BaseStream + __all__ = [ "CHECK_NONE", "CHECK_CRC32", diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/mailbox.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/mailbox.pyi index dc2fbd593d6742..ff605c0661fb12 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/mailbox.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/mailbox.pyi @@ -4,6 +4,7 @@ import sys from _typeshed import StrPath, SupportsNoArgReadline, SupportsRead from abc import ABCMeta, abstractmethod from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from email._policybase import _MessageT from types import GenericAlias, TracebackType from typing import IO, Any, AnyStr, Generic, Literal, Protocol, TypeVar, overload from typing_extensions import Self, TypeAlias @@ -29,7 +30,6 @@ __all__ = [ ] _T = TypeVar("_T") -_MessageT = TypeVar("_MessageT", bound=Message) class _SupportsReadAndReadline(SupportsRead[bytes], SupportsNoArgReadline[bytes], Protocol): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/marshal.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/marshal.pyi index 6ab202637ddaab..46c421e4ce307c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/marshal.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/marshal.pyi @@ -2,10 +2,10 @@ import builtins import sys import types from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite -from typing import Any +from typing import Any, Final from typing_extensions import TypeAlias -version: int +version: Final[int] _Marshallable: TypeAlias = ( # handled in w_object() in marshal.c @@ -28,14 +28,22 @@ _Marshallable: TypeAlias = ( | ReadableBuffer ) -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 14): + def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 5, /, *, allow_code: bool = True) -> None: ... + def dumps(value: _Marshallable, version: int = 5, /, *, allow_code: bool = True) -> bytes: ... + +elif sys.version_info >= (3, 13): def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 4, /, *, allow_code: bool = True) -> None: ... - def load(file: SupportsRead[bytes], /, *, allow_code: bool = True) -> Any: ... def dumps(value: _Marshallable, version: int = 4, /, *, allow_code: bool = True) -> bytes: ... - def loads(bytes: ReadableBuffer, /, *, allow_code: bool = True) -> Any: ... else: def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 4, /) -> None: ... - def load(file: SupportsRead[bytes], /) -> Any: ... def dumps(value: _Marshallable, version: int = 4, /) -> bytes: ... + +if sys.version_info >= (3, 13): + def load(file: SupportsRead[bytes], /, *, allow_code: bool = True) -> Any: ... + def loads(bytes: ReadableBuffer, /, *, allow_code: bool = True) -> Any: ... + +else: + def load(file: SupportsRead[bytes], /) -> Any: ... def loads(bytes: ReadableBuffer, /) -> Any: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/nturl2path.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/nturl2path.pyi index b8ad8d6821554a..c38a359469d2da 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/nturl2path.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/nturl2path.pyi @@ -1,2 +1,12 @@ -def url2pathname(url: str) -> str: ... -def pathname2url(p: str) -> str: ... +import sys +from typing_extensions import deprecated + +if sys.version_info >= (3, 14): + @deprecated("nturl2path module was deprecated since Python 3.14") + def url2pathname(url: str) -> str: ... + @deprecated("nturl2path module was deprecated since Python 3.14") + def pathname2url(p: str) -> str: ... + +else: + def url2pathname(url: str) -> str: ... + def pathname2url(p: str) -> str: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi index d0ef614abbcebf..5286c76d1b066c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi @@ -160,6 +160,8 @@ __all__ = [ "walk", "write", ] +if sys.version_info >= (3, 14): + __all__ += ["readinto"] if sys.platform == "darwin" and sys.version_info >= (3, 12): __all__ += ["PRIO_DARWIN_BG", "PRIO_DARWIN_NONUI", "PRIO_DARWIN_PROCESS", "PRIO_DARWIN_THREAD"] if sys.platform == "darwin" and sys.version_info >= (3, 10): @@ -208,6 +210,8 @@ if sys.platform == "linux": "removexattr", "setxattr", ] +if sys.platform == "linux" and sys.version_info >= (3, 14): + __all__ += ["SCHED_DEADLINE", "SCHED_NORMAL"] if sys.platform == "linux" and sys.version_info >= (3, 13): __all__ += [ "POSIX_SPAWN_CLOSEFROM", @@ -570,6 +574,10 @@ if sys.platform == "linux": SCHED_IDLE: int SCHED_RESET_ON_FORK: int +if sys.version_info >= (3, 14) and sys.platform == "linux": + SCHED_DEADLINE: int + SCHED_NORMAL: int + if sys.platform != "win32": RTLD_LAZY: int RTLD_NOW: int @@ -1149,6 +1157,9 @@ if sys.platform != "win32": def readv(fd: int, buffers: SupportsLenAndGetItem[WriteableBuffer], /) -> int: ... def writev(fd: int, buffers: SupportsLenAndGetItem[ReadableBuffer], /) -> int: ... +if sys.version_info >= (3, 14): + def readinto(fd: int, buffer: ReadableBuffer, /) -> int: ... + @final class terminal_size(structseq[int], tuple[int, int]): if sys.version_info >= (3, 10): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/pathlib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi similarity index 87% rename from crates/ty_vendored/vendor/typeshed/stdlib/pathlib.pyi rename to crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi index 1e4d97770b7bd0..b84fc69313a153 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/pathlib.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi @@ -15,11 +15,16 @@ from collections.abc import Callable, Generator, Iterator, Sequence from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper from os import PathLike, stat_result from types import GenericAlias, TracebackType -from typing import IO, Any, BinaryIO, ClassVar, Literal, overload +from typing import IO, Any, BinaryIO, ClassVar, Literal, TypeVar, overload from typing_extensions import Never, Self, deprecated +_PathT = TypeVar("_PathT", bound=PurePath) + __all__ = ["PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath"] +if sys.version_info >= (3, 14): + from pathlib.types import PathInfo + if sys.version_info >= (3, 13): __all__ += ["UnsupportedOperation"] @@ -63,7 +68,9 @@ class PurePath(PathLike[str]): def as_uri(self) -> str: ... def is_absolute(self) -> bool: ... def is_reserved(self) -> bool: ... - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 14): + def is_relative_to(self, other: StrPath) -> bool: ... + elif sys.version_info >= (3, 12): def is_relative_to(self, other: StrPath, /, *_deprecated: StrPath) -> bool: ... else: def is_relative_to(self, *other: StrPath) -> bool: ... @@ -73,7 +80,9 @@ class PurePath(PathLike[str]): else: def match(self, path_pattern: str) -> bool: ... - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 14): + def relative_to(self, other: StrPath, *, walk_up: bool = False) -> Self: ... + elif sys.version_info >= (3, 12): def relative_to(self, other: StrPath, /, *_deprecated: StrPath, walk_up: bool = False) -> Self: ... else: def relative_to(self, *other: StrPath) -> Self: ... @@ -154,17 +163,25 @@ class Path(PurePath): def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None: ... if sys.version_info >= (3, 14): - def copy(self, target: StrPath, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> None: ... - def copytree( - self, - target: StrPath, - *, - follow_symlinks: bool = True, - preserve_metadata: bool = False, - dirs_exist_ok: bool = False, - ignore: Callable[[Self], bool] | None = None, - on_error: Callable[[OSError], object] | None = None, - ) -> None: ... + + @property + def info(self) -> PathInfo: ... + @overload + def move_into(self, target_dir: _PathT) -> _PathT: ... # type: ignore[overload-overlap] + @overload + def move_into(self, target_dir: StrPath) -> Self: ... # type: ignore[overload-overlap] + @overload + def move(self, target: _PathT) -> _PathT: ... # type: ignore[overload-overlap] + @overload + def move(self, target: StrPath) -> Self: ... # type: ignore[overload-overlap] + @overload + def copy_into(self, target_dir: _PathT, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> _PathT: ... # type: ignore[overload-overlap] + @overload + def copy_into(self, target_dir: StrPath, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> Self: ... # type: ignore[overload-overlap] + @overload + def copy(self, target: _PathT, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> _PathT: ... # type: ignore[overload-overlap] + @overload + def copy(self, target: StrPath, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> Self: ... # type: ignore[overload-overlap] # Adapted from builtins.open # Text mode: always returns a TextIOWrapper @@ -253,9 +270,6 @@ class Path(PurePath): def resolve(self, strict: bool = False) -> Self: ... def rmdir(self) -> None: ... - if sys.version_info >= (3, 14): - def delete(self, ignore_errors: bool = False, on_error: Callable[[OSError], object] | None = None) -> None: ... - def symlink_to(self, target: StrOrBytesPath, target_is_directory: bool = False) -> None: ... if sys.version_info >= (3, 10): def hardlink_to(self, target: StrOrBytesPath) -> None: ... @@ -286,9 +300,6 @@ class Path(PurePath): self, top_down: bool = ..., on_error: Callable[[OSError], object] | None = ..., follow_symlinks: bool = ... ) -> Iterator[tuple[Self, list[str], list[str]]]: ... - if sys.version_info >= (3, 14): - def rmtree(self, ignore_errors: bool = False, on_error: Callable[[OSError], object] | None = None) -> None: ... - class PosixPath(Path, PurePosixPath): ... class WindowsPath(Path, PureWindowsPath): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi new file mode 100644 index 00000000000000..9f9a650846deb9 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi @@ -0,0 +1,8 @@ +from typing import Protocol, runtime_checkable + +@runtime_checkable +class PathInfo(Protocol): + def exists(self, *, follow_symlinks: bool = True) -> bool: ... + def is_dir(self, *, follow_symlinks: bool = True) -> bool: ... + def is_file(self, *, follow_symlinks: bool = True) -> bool: ... + def is_symlink(self) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/pdb.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/pdb.pyi index 61e8b7176e849d..ad69fcab16de05 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/pdb.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/pdb.pyi @@ -1,17 +1,21 @@ import signal import sys -from bdb import Bdb +from bdb import Bdb, _Backend from cmd import Cmd from collections.abc import Callable, Iterable, Mapping, Sequence from inspect import _SourceObjectType +from linecache import _ModuleGlobals from types import CodeType, FrameType, TracebackType -from typing import IO, Any, ClassVar, Final, TypeVar -from typing_extensions import ParamSpec, Self +from typing import IO, Any, ClassVar, Final, Literal, TypeVar +from typing_extensions import ParamSpec, Self, TypeAlias __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] +if sys.version_info >= (3, 14): + __all__ += ["set_default_backend", "get_default_backend"] _T = TypeVar("_T") _P = ParamSpec("_P") +_Mode: TypeAlias = Literal["inline", "cli"] line_prefix: str # undocumented @@ -21,7 +25,16 @@ def run(statement: str, globals: dict[str, Any] | None = None, locals: Mapping[s def runeval(expression: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> Any: ... def runctx(statement: str, globals: dict[str, Any], locals: Mapping[str, Any]) -> None: ... def runcall(func: Callable[_P, _T], *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... -def set_trace(*, header: str | None = None) -> None: ... + +if sys.version_info >= (3, 14): + def set_default_backend(backend: _Backend) -> None: ... + def get_default_backend() -> _Backend: ... + def set_trace(*, header: str | None = None, commands: Iterable[str] | None = None) -> None: ... + async def set_trace_async(*, header: str | None = None, commands: Iterable[str] | None = None) -> None: ... + +else: + def set_trace(*, header: str | None = None) -> None: ... + def post_mortem(t: TracebackType | None = None) -> None: ... def pm() -> None: ... @@ -47,15 +60,35 @@ class Pdb(Bdb, Cmd): curindex: int curframe: FrameType | None curframe_locals: Mapping[str, Any] - def __init__( - self, - completekey: str = "tab", - stdin: IO[str] | None = None, - stdout: IO[str] | None = None, - skip: Iterable[str] | None = None, - nosigint: bool = False, - readrc: bool = True, - ) -> None: ... + if sys.version_info >= (3, 14): + mode: _Mode | None + colorize: bool + def __init__( + self, + completekey: str = "tab", + stdin: IO[str] | None = None, + stdout: IO[str] | None = None, + skip: Iterable[str] | None = None, + nosigint: bool = False, + readrc: bool = True, + mode: _Mode | None = None, + backend: _Backend | None = None, + colorize: bool = False, + ) -> None: ... + else: + def __init__( + self, + completekey: str = "tab", + stdin: IO[str] | None = None, + stdout: IO[str] | None = None, + skip: Iterable[str] | None = None, + nosigint: bool = False, + readrc: bool = True, + ) -> None: ... + if sys.version_info >= (3, 14): + def set_trace(self, frame: FrameType | None = None, *, commands: Iterable[str] | None = None) -> None: ... + async def set_trace_async(self, frame: FrameType | None = None, *, commands: Iterable[str] | None = None) -> None: ... + def forget(self) -> None: ... def setup(self, f: FrameType | None, tb: TracebackType | None) -> None: ... if sys.version_info < (3, 11): @@ -75,14 +108,25 @@ class Pdb(Bdb, Cmd): def handle_command_def(self, line: str) -> bool: ... def defaultFile(self) -> str: ... def lineinfo(self, identifier: str) -> tuple[None, None, None] | tuple[str, str, int]: ... - def checkline(self, filename: str, lineno: int) -> int: ... + if sys.version_info >= (3, 14): + def checkline(self, filename: str, lineno: int, module_globals: _ModuleGlobals | None = None) -> int: ... + else: + def checkline(self, filename: str, lineno: int) -> int: ... + def _getval(self, arg: str) -> object: ... - def print_stack_trace(self) -> None: ... + if sys.version_info >= (3, 14): + def print_stack_trace(self, count: int | None = None) -> None: ... + else: + def print_stack_trace(self) -> None: ... + def print_stack_entry(self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> ") -> None: ... def lookupmodule(self, filename: str) -> str | None: ... if sys.version_info < (3, 11): def _runscript(self, filename: str) -> None: ... + if sys.version_info >= (3, 14): + def complete_multiline_names(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: ... + if sys.version_info >= (3, 13): def completedefault(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/pkgutil.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/pkgutil.pyi index d60e9bad53ae03..e764d08e79f806 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/pkgutil.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/pkgutil.pyi @@ -8,8 +8,6 @@ from typing_extensions import deprecated __all__ = [ "get_importer", "iter_importers", - "get_loader", - "find_loader", "walk_packages", "iter_modules", "get_data", @@ -17,6 +15,8 @@ __all__ = [ "extend_path", "ModuleInfo", ] +if sys.version_info < (3, 14): + __all__ += ["get_loader", "find_loader"] if sys.version_info < (3, 12): __all__ += ["ImpImporter", "ImpLoader"] @@ -36,11 +36,13 @@ if sys.version_info < (3, 12): class ImpLoader: def __init__(self, fullname: str, file: IO[str], filename: StrOrBytesPath, etc: tuple[str, str, int]) -> None: ... -@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") -def find_loader(fullname: str) -> LoaderProtocol | None: ... +if sys.version_info < (3, 14): + @deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") + def find_loader(fullname: str) -> LoaderProtocol | None: ... + @deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") + def get_loader(module_or_name: str) -> LoaderProtocol | None: ... + def get_importer(path_item: StrOrBytesPath) -> PathEntryFinderProtocol | None: ... -@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") -def get_loader(module_or_name: str) -> LoaderProtocol | None: ... def iter_importers(fullname: str = "") -> Iterator[MetaPathFinderProtocol | PathEntryFinderProtocol]: ... def iter_modules(path: Iterable[StrOrBytesPath] | None = None, prefix: str = "") -> Iterator[ModuleInfo]: ... def read_code(stream: SupportsRead[bytes]) -> Any: ... # undocumented diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/platform.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/platform.pyi index 19fac26134eb62..fbc73c6c917755 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/platform.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/platform.pyi @@ -82,3 +82,6 @@ if sys.version_info >= (3, 13): is_emulator: bool = False, ) -> AndroidVer: ... def ios_ver(system: str = "", release: str = "", model: str = "", is_simulator: bool = False) -> IOSVersionInfo: ... + +if sys.version_info >= (3, 14): + def invalidate_caches() -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/posix.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/posix.pyi index 88f4135af2a798..6d0d76ab82176e 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/posix.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/posix.pyi @@ -250,6 +250,12 @@ if sys.platform != "win32": timerfd_settime_ns as timerfd_settime_ns, ) + if sys.version_info >= (3, 14): + from os import readinto as readinto + + if sys.version_info >= (3, 14) and sys.platform == "linux": + from os import SCHED_DEADLINE as SCHED_DEADLINE, SCHED_NORMAL as SCHED_NORMAL + if sys.platform != "linux": from os import O_EXLOCK as O_EXLOCK, O_SHLOCK as O_SHLOCK, chflags as chflags, lchflags as lchflags, lchmod as lchmod diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi index ff89dcc72209fa..1ee006235ee6c1 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi @@ -1023,6 +1023,39 @@ if sys.platform != "linux": __all__ += ["IPPROTO_GGP", "IPPROTO_IPV4", "IPPROTO_MAX", "IPPROTO_ND", "IP_RECVDSTADDR", "SO_USELOOPBACK"] +if sys.version_info >= (3, 14): + from _socket import IP_RECVTTL as IP_RECVTTL + + __all__ += ["IP_RECVTTL"] + + if sys.platform == "win32" or sys.platform == "linux": + from _socket import IP_RECVERR as IP_RECVERR, IPV6_RECVERR as IPV6_RECVERR, SO_ORIGINAL_DST as SO_ORIGINAL_DST + + __all__ += ["IP_RECVERR", "IPV6_RECVERR", "SO_ORIGINAL_DST"] + + if sys.platform == "win32": + from _socket import ( + SO_BTH_ENCRYPT as SO_BTH_ENCRYPT, + SO_BTH_MTU as SO_BTH_MTU, + SO_BTH_MTU_MAX as SO_BTH_MTU_MAX, + SO_BTH_MTU_MIN as SO_BTH_MTU_MIN, + SOL_RFCOMM as SOL_RFCOMM, + TCP_QUICKACK as TCP_QUICKACK, + ) + + __all__ += ["SOL_RFCOMM", "SO_BTH_ENCRYPT", "SO_BTH_MTU", "SO_BTH_MTU_MAX", "SO_BTH_MTU_MIN", "TCP_QUICKACK"] + + if sys.platform == "linux": + from _socket import ( + CAN_RAW_ERR_FILTER as CAN_RAW_ERR_FILTER, + IP_FREEBIND as IP_FREEBIND, + IP_RECVORIGDSTADDR as IP_RECVORIGDSTADDR, + SO_ORIGINAL_DST as SO_ORIGINAL_DST, + VMADDR_CID_LOCAL as VMADDR_CID_LOCAL, + ) + + __all__ += ["CAN_RAW_ERR_FILTER", "IP_FREEBIND", "IP_RECVORIGDSTADDR", "VMADDR_CID_LOCAL"] + # Re-exported from errno EBADF: int EAGAIN: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi index b83516b4d4eb82..5d3c2330be5e8c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi @@ -60,12 +60,14 @@ from sqlite3.dbapi2 import ( sqlite_version as sqlite_version, sqlite_version_info as sqlite_version_info, threadsafety as threadsafety, - version_info as version_info, ) from types import TracebackType from typing import Any, Literal, Protocol, SupportsIndex, TypeVar, final, overload, type_check_only from typing_extensions import Self, TypeAlias +if sys.version_info < (3, 14): + from sqlite3.dbapi2 import version_info as version_info + if sys.version_info >= (3, 12): from sqlite3.dbapi2 import ( LEGACY_TRANSACTION_CONTROL as LEGACY_TRANSACTION_CONTROL, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/string.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi similarity index 100% rename from crates/ty_vendored/vendor/typeshed/stdlib/string.pyi rename to crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi new file mode 100644 index 00000000000000..01b95377a49c59 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi @@ -0,0 +1,28 @@ +from collections.abc import Iterator +from typing import Any, Literal, final + +__all__ = ["Interpolation", "Template"] + +@final +class Template: # TODO: consider making `Template` generic on `TypeVarTuple` + strings: tuple[str, ...] + interpolations: tuple[Interpolation, ...] + + def __new__(cls, *args: str | Interpolation) -> Template: ... + def __iter__(self) -> Iterator[str | Interpolation]: ... + def __add__(self, other: Template | str) -> Template: ... + @property + def values(self) -> tuple[Any, ...]: ... # Tuple of interpolation values, which can have any type + +@final +class Interpolation: + value: Any # TODO: consider making `Interpolation` generic in runtime + expression: str + conversion: Literal["a", "r", "s"] | None + format_spec: str + + __match_args__ = ("value", "expression", "conversion", "format_spec") + + def __new__( + cls, value: Any, expression: str, conversion: Literal["a", "r", "s"] | None = None, format_spec: str = "" + ) -> Interpolation: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi index c4861f7c6f39fa..ea6e057e410d40 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi @@ -384,7 +384,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def write(self: SpooledTemporaryFile[bytes], s: ReadableBuffer) -> int: ... @overload def write(self, s: AnyStr) -> int: ... - @overload + @overload # type: ignore[override] def writelines(self: SpooledTemporaryFile[str], iterable: Iterable[str]) -> None: ... @overload def writelines(self: SpooledTemporaryFile[bytes], iterable: Iterable[ReadableBuffer]) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/threading.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/threading.pyi index 99f5c8d2a516da..d31351754d056a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/threading.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/threading.pyi @@ -3,6 +3,7 @@ import sys from _thread import _excepthook, _ExceptHookArgs, get_native_id as get_native_id from _typeshed import ProfileFunction, TraceFunction from collections.abc import Callable, Iterable, Mapping +from contextvars import ContextVar from types import TracebackType from typing import Any, TypeVar, final from typing_extensions import deprecated @@ -76,16 +77,30 @@ class Thread: @property def ident(self) -> int | None: ... daemon: bool - def __init__( - self, - group: None = None, - target: Callable[..., object] | None = None, - name: str | None = None, - args: Iterable[Any] = (), - kwargs: Mapping[str, Any] | None = None, - *, - daemon: bool | None = None, - ) -> None: ... + if sys.version_info >= (3, 14): + def __init__( + self, + group: None = None, + target: Callable[..., object] | None = None, + name: str | None = None, + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] | None = None, + *, + daemon: bool | None = None, + context: ContextVar[Any] | None = None, + ) -> None: ... + else: + def __init__( + self, + group: None = None, + target: Callable[..., object] | None = None, + name: str | None = None, + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] | None = None, + *, + daemon: bool | None = None, + ) -> None: ... + def start(self) -> None: ... def run(self) -> None: ... def join(self, timeout: float | None = None) -> None: ... @@ -116,6 +131,9 @@ class _RLock: __enter__ = acquire def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ... + if sys.version_info >= (3, 14): + def locked(self) -> bool: ... + RLock = _thread.RLock # Actually a function at runtime. class Condition: diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi index dcac61d77e0a91..e23ab07f123d49 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi @@ -286,7 +286,7 @@ else: _W = TypeVar("_W", bound=Misc) # Events considered covariant because you should never assign to event.widget. -_W_co = TypeVar("_W_co", covariant=True, bound=Misc) +_W_co = TypeVar("_W_co", covariant=True, bound=Misc, default=Misc) class Event(Generic[_W_co]): serial: int @@ -3736,6 +3736,7 @@ class PhotoImage(Image, _PhotoImageLike): self, data: ( str + | bytes | list[str] | list[list[str]] | list[tuple[str, ...]] @@ -3743,7 +3744,7 @@ class PhotoImage(Image, _PhotoImageLike): | tuple[list[str], ...] | tuple[tuple[str, ...], ...] ), - to: tuple[int, int] | None = None, + to: tuple[int, int] | tuple[int, int, int, int] | None = None, ) -> None: ... if sys.version_info >= (3, 13): def read( diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi index 5328e461ebdc2d..ab3c010938bef0 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi @@ -562,7 +562,7 @@ class Notebook(Widget): compound: tkinter._Compound = ..., underline: int = ..., ) -> None: ... - def forget(self, tab_id) -> None: ... + def forget(self, tab_id) -> None: ... # type: ignore[override] def hide(self, tab_id) -> None: ... def identify(self, x: int, y: int) -> str: ... def index(self, tab_id): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/token.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/token.pyi index 741ce5b035b77e..7c13b15d95b790 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/token.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/token.pyi @@ -78,6 +78,9 @@ if sys.version_info >= (3, 10): if sys.version_info >= (3, 12): __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START", "EXACT_TOKEN_TYPES"] +if sys.version_info >= (3, 14): + __all__ += ["TSTRING_START", "TSTRING_MIDDLE", "TSTRING_END"] + ENDMARKER: int NAME: int NUMBER: int @@ -155,6 +158,11 @@ if sys.version_info >= (3, 12): FSTRING_MIDDLE: int FSTRING_START: int +if sys.version_info >= (3, 14): + TSTRING_START: int + TSTRING_MIDDLE: int + TSTRING_END: int + def ISTERMINAL(x: int) -> bool: ... def ISNONTERMINAL(x: int) -> bool: ... def ISEOF(x: int) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tokenize.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tokenize.pyi index 86e87704eb02d2..b658740a1ad7ac 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tokenize.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tokenize.pyi @@ -93,6 +93,9 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): __all__ += ["TokenError", "open"] +if sys.version_info >= (3, 14): + __all__ += ["TSTRING_START", "TSTRING_MIDDLE", "TSTRING_END"] + cookie_re: Pattern[str] blank_re: Pattern[bytes] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tomllib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tomllib.pyi index d559568b912b5d..c160ffc38bfddc 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tomllib.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tomllib.pyi @@ -1,10 +1,26 @@ +import sys from _typeshed import SupportsRead from collections.abc import Callable -from typing import Any +from typing import Any, overload +from typing_extensions import deprecated __all__ = ("loads", "load", "TOMLDecodeError") -class TOMLDecodeError(ValueError): ... +if sys.version_info >= (3, 14): + class TOMLDecodeError(ValueError): + msg: str + doc: str + pos: int + lineno: int + colno: int + @overload + def __init__(self, msg: str, doc: str, pos: int) -> None: ... + @overload + @deprecated("Deprecated in Python 3.14; Please set 'msg', 'doc' and 'pos' arguments only.") + def __init__(self, msg: str | type = ..., doc: str | type = ..., pos: int | type = ..., *args: Any) -> None: ... + +else: + class TOMLDecodeError(ValueError): ... def load(fp: SupportsRead[bytes], /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... def loads(s: str, /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/traceback.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/traceback.pyi index 4f132d51c617f6..4553dbd08384d3 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/traceback.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/traceback.pyi @@ -27,6 +27,9 @@ __all__ = [ "walk_tb", ] +if sys.version_info >= (3, 14): + __all__ += ["print_list"] + _FrameSummaryTuple: TypeAlias = tuple[str, int, str, str | None] def print_tb(tb: TracebackType | None, limit: int | None = None, file: SupportsWrite[str] | None = None) -> None: ... @@ -81,8 +84,6 @@ def print_stack(f: FrameType | None = None, limit: int | None = None, file: Supp def extract_tb(tb: TracebackType | None, limit: int | None = None) -> StackSummary: ... def extract_stack(f: FrameType | None = None, limit: int | None = None) -> StackSummary: ... def format_list(extracted_list: Iterable[FrameSummary | _FrameSummaryTuple]) -> list[str]: ... - -# undocumented def print_list(extracted_list: Iterable[FrameSummary | _FrameSummaryTuple], file: SupportsWrite[str] | None = None) -> None: ... if sys.version_info >= (3, 13): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi index fe443be2712158..1163d71d2c95af 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import MaybeNone, SupportsKeysAndGetItem +from _typeshed import AnnotationForm, MaybeNone, SupportsKeysAndGetItem from _typeshed.importlib import LoaderProtocol from collections.abc import ( AsyncGenerator, @@ -19,6 +19,9 @@ from importlib.machinery import ModuleSpec from typing import Any, ClassVar, Literal, TypeVar, final, overload from typing_extensions import ParamSpec, Self, TypeAliasType, TypeVarTuple, deprecated +if sys.version_info >= (3, 14): + from _typeshed import AnnotateFunc + __all__ = [ "FunctionType", "LambdaType", @@ -77,7 +80,9 @@ class FunctionType: def __globals__(self) -> dict[str, Any]: ... __name__: str __qualname__: str - __annotations__: dict[str, Any] + __annotations__: dict[str, AnnotationForm] + if sys.version_info >= (3, 14): + __annotate__: AnnotateFunc | None __kwdefaults__: dict[str, Any] | None if sys.version_info >= (3, 10): @property @@ -352,6 +357,10 @@ class ModuleType: # Redeclaring `__doc__` here helps some type checkers understand that `__doc__` is available # as an implicit global in all modules, similar to `__name__`, `__file__`, `__spec__`, etc. __doc__: str | None + __annotations__: dict[str, AnnotationForm] + if sys.version_info >= (3, 14): + __annotate__: AnnotateFunc | None + def __init__(self, name: str, doc: str | None = ...) -> None: ... # __getattr__ doesn't exist at runtime, # but having it here in typeshed makes dynamic imports diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi index df753cfd9bcaa1..5aa85543ed2cad 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi @@ -23,6 +23,11 @@ from types import ( ) from typing_extensions import Never as _Never, ParamSpec as _ParamSpec, deprecated +if sys.version_info >= (3, 14): + from _typeshed import EvaluateFunc + + from annotationlib import Format + if sys.version_info >= (3, 10): from types import UnionType @@ -37,7 +42,6 @@ __all__ = [ "AsyncIterator", "Awaitable", "BinaryIO", - "ByteString", "Callable", "ChainMap", "ClassVar", @@ -106,6 +110,12 @@ __all__ = [ "runtime_checkable", ] +if sys.version_info < (3, 14): + __all__ += ["ByteString"] + +if sys.version_info >= (3, 14): + __all__ += ["evaluate_forward_ref"] + if sys.version_info >= (3, 10): __all__ += ["Concatenate", "ParamSpec", "ParamSpecArgs", "ParamSpecKwargs", "TypeAlias", "TypeGuard", "is_typeddict"] @@ -132,6 +142,10 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): __all__ += ["get_protocol_members", "is_protocol", "NoDefault", "TypeIs", "ReadOnly"] +# We can't use this name here because it leads to issues with mypy, likely +# due to an import cycle. Below instead we use Any with a comment. +# from _typeshed import AnnotationForm + class Any: ... class _Final: ... @@ -141,9 +155,9 @@ class TypeVar: @property def __name__(self) -> str: ... @property - def __bound__(self) -> Any | None: ... + def __bound__(self) -> Any | None: ... # AnnotationForm @property - def __constraints__(self) -> tuple[Any, ...]: ... + def __constraints__(self) -> tuple[Any, ...]: ... # AnnotationForm @property def __covariant__(self) -> bool: ... @property @@ -153,46 +167,64 @@ class TypeVar: def __infer_variance__(self) -> bool: ... if sys.version_info >= (3, 13): @property - def __default__(self) -> Any: ... + def __default__(self) -> Any: ... # AnnotationForm if sys.version_info >= (3, 13): def __new__( cls, name: str, - *constraints: Any, - bound: Any | None = None, + *constraints: Any, # AnnotationForm + bound: Any | None = None, # AnnotationForm contravariant: bool = False, covariant: bool = False, infer_variance: bool = False, - default: Any = ..., + default: Any = ..., # AnnotationForm ) -> Self: ... elif sys.version_info >= (3, 12): def __new__( cls, name: str, - *constraints: Any, - bound: Any | None = None, + *constraints: Any, # AnnotationForm + bound: Any | None = None, # AnnotationForm covariant: bool = False, contravariant: bool = False, infer_variance: bool = False, ) -> Self: ... elif sys.version_info >= (3, 11): def __new__( - cls, name: str, *constraints: Any, bound: Any | None = None, covariant: bool = False, contravariant: bool = False + cls, + name: str, + *constraints: Any, # AnnotationForm + bound: Any | None = None, # AnnotationForm + covariant: bool = False, + contravariant: bool = False, ) -> Self: ... else: def __init__( - self, name: str, *constraints: Any, bound: Any | None = None, covariant: bool = False, contravariant: bool = False + self, + name: str, + *constraints: Any, # AnnotationForm + bound: Any | None = None, # AnnotationForm + covariant: bool = False, + contravariant: bool = False, ) -> None: ... if sys.version_info >= (3, 10): - def __or__(self, right: Any) -> _SpecialForm: ... - def __ror__(self, left: Any) -> _SpecialForm: ... + def __or__(self, right: Any) -> _SpecialForm: ... # AnnotationForm + def __ror__(self, left: Any) -> _SpecialForm: ... # AnnotationForm if sys.version_info >= (3, 11): def __typing_subst__(self, arg: Any) -> Any: ... if sys.version_info >= (3, 13): def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... def has_default(self) -> bool: ... + if sys.version_info >= (3, 14): + @property + def evaluate_bound(self) -> EvaluateFunc | None: ... + @property + def evaluate_constraints(self) -> EvaluateFunc | None: ... + @property + def evaluate_default(self) -> EvaluateFunc | None: ... # Used for an undocumented mypy feature. Does not exist at runtime. +# Obsolete, use _typeshed._type_checker_internals.promote instead. _promote = object() # N.B. Keep this definition in sync with typing_extensions._SpecialForm @@ -232,10 +264,10 @@ if sys.version_info >= (3, 11): def __name__(self) -> str: ... if sys.version_info >= (3, 13): @property - def __default__(self) -> Any: ... + def __default__(self) -> Any: ... # AnnotationForm def has_default(self) -> bool: ... if sys.version_info >= (3, 13): - def __new__(cls, name: str, *, default: Any = ...) -> Self: ... + def __new__(cls, name: str, *, default: Any = ...) -> Self: ... # AnnotationForm elif sys.version_info >= (3, 12): def __new__(cls, name: str) -> Self: ... else: @@ -244,6 +276,9 @@ if sys.version_info >= (3, 11): def __iter__(self) -> Any: ... def __typing_subst__(self, arg: Never) -> Never: ... def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... + if sys.version_info >= (3, 14): + @property + def evaluate_default(self) -> EvaluateFunc | None: ... if sys.version_info >= (3, 10): @final @@ -275,7 +310,7 @@ if sys.version_info >= (3, 10): @property def __name__(self) -> str: ... @property - def __bound__(self) -> Any | None: ... + def __bound__(self) -> Any | None: ... # AnnotationForm @property def __covariant__(self) -> bool: ... @property @@ -285,35 +320,45 @@ if sys.version_info >= (3, 10): def __infer_variance__(self) -> bool: ... if sys.version_info >= (3, 13): @property - def __default__(self) -> Any: ... + def __default__(self) -> Any: ... # AnnotationForm if sys.version_info >= (3, 13): def __new__( cls, name: str, *, - bound: Any | None = None, + bound: Any | None = None, # AnnotationForm contravariant: bool = False, covariant: bool = False, infer_variance: bool = False, - default: Any = ..., + default: Any = ..., # AnnotationForm ) -> Self: ... elif sys.version_info >= (3, 12): def __new__( cls, name: str, *, - bound: Any | None = None, + bound: Any | None = None, # AnnotationForm contravariant: bool = False, covariant: bool = False, infer_variance: bool = False, ) -> Self: ... elif sys.version_info >= (3, 11): def __new__( - cls, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False + cls, + name: str, + *, + bound: Any | None = None, # AnnotationForm + contravariant: bool = False, + covariant: bool = False, ) -> Self: ... else: def __init__( - self, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False + self, + name: str, + *, + bound: Any | None = None, # AnnotationForm + contravariant: bool = False, + covariant: bool = False, ) -> None: ... @property @@ -328,13 +373,16 @@ if sys.version_info >= (3, 10): def __ror__(self, left: Any) -> _SpecialForm: ... if sys.version_info >= (3, 13): def has_default(self) -> bool: ... + if sys.version_info >= (3, 14): + @property + def evaluate_default(self) -> EvaluateFunc | None: ... Concatenate: _SpecialForm TypeAlias: _SpecialForm TypeGuard: _SpecialForm class NewType: - def __init__(self, name: str, tp: Any) -> None: ... + def __init__(self, name: str, tp: Any) -> None: ... # AnnotationForm if sys.version_info >= (3, 11): @staticmethod def __call__(x: _T, /) -> _T: ... @@ -531,6 +579,7 @@ class Coroutine(Awaitable[_ReturnT_nd_co], Generic[_YieldT_co, _SendT_nd_contra, # NOTE: This type does not exist in typing.py or PEP 484 but mypy needs it to exist. # The parameters correspond to Generator, but the 4th is the original type. +# Obsolete, use _typeshed._type_checker_internals.AwaitableGenerator instead. @type_check_only class AwaitableGenerator( Awaitable[_ReturnT_nd_co], @@ -858,13 +907,25 @@ _get_type_hints_obj_allowed_types: typing_extensions.TypeAlias = ( # noqa: Y042 | MethodDescriptorType ) -def get_type_hints( - obj: _get_type_hints_obj_allowed_types, - globalns: dict[str, Any] | None = None, - localns: Mapping[str, Any] | None = None, - include_extras: bool = False, -) -> dict[str, Any]: ... -def get_args(tp: Any) -> tuple[Any, ...]: ... +if sys.version_info >= (3, 14): + def get_type_hints( + obj: _get_type_hints_obj_allowed_types, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, + *, + format: Format | None = None, + ) -> dict[str, Any]: ... # AnnotationForm + +else: + def get_type_hints( + obj: _get_type_hints_obj_allowed_types, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, + ) -> dict[str, Any]: ... # AnnotationForm + +def get_args(tp: Any) -> tuple[Any, ...]: ... # AnnotationForm if sys.version_info >= (3, 10): @overload @@ -875,7 +936,7 @@ if sys.version_info >= (3, 10): @overload def get_origin(tp: GenericAlias) -> type: ... @overload -def get_origin(tp: Any) -> Any | None: ... +def get_origin(tp: Any) -> Any | None: ... # AnnotationForm @overload def cast(typ: type[_T], val: Any) -> _T: ... @overload @@ -886,7 +947,7 @@ def cast(typ: object, val: Any) -> Any: ... if sys.version_info >= (3, 11): def reveal_type(obj: _T, /) -> _T: ... def assert_never(arg: Never, /) -> Never: ... - def assert_type(val: _T, typ: Any, /) -> _T: ... + def assert_type(val: _T, typ: Any, /) -> _T: ... # AnnotationForm def clear_overloads() -> None: ... def get_overloads(func: Callable[..., object]) -> Sequence[Callable[..., object]]: ... def dataclass_transform( @@ -901,6 +962,7 @@ if sys.version_info >= (3, 11): # Type constructors +# Obsolete, will be changed to a function. Use _typeshed._type_checker_internals.NamedTupleFallback instead. class NamedTuple(tuple[Any, ...]): _field_defaults: ClassVar[dict[str, Any]] _fields: ClassVar[tuple[str, ...]] @@ -925,6 +987,7 @@ class NamedTuple(tuple[Any, ...]): # Internal mypy fallback type for all typed dicts (does not exist at runtime) # N.B. Keep this mostly in sync with typing_extensions._TypedDict/mypy_extensions._TypedDict +# Obsolete, use _typeshed._type_checker_internals.TypedDictFallback instead. @type_check_only class _TypedDict(Mapping[str, object], metaclass=ABCMeta): __total__: ClassVar[bool] @@ -960,56 +1023,70 @@ class _TypedDict(Mapping[str, object], metaclass=ABCMeta): # supposedly incompatible definitions of __or__ and __ior__ def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... # type: ignore[misc] -@final -class ForwardRef(_Final): - __forward_arg__: str - __forward_code__: CodeType - __forward_evaluated__: bool - __forward_value__: Any | None - __forward_is_argument__: bool - __forward_is_class__: bool - __forward_module__: Any | None +if sys.version_info >= (3, 14): + from annotationlib import ForwardRef as ForwardRef - def __init__(self, arg: str, is_argument: bool = True, module: Any | None = None, *, is_class: bool = False) -> None: ... + def evaluate_forward_ref( + forward_ref: ForwardRef, + *, + owner: object = None, + globals: dict[str, Any] | None = None, + locals: Mapping[str, Any] | None = None, + type_params: tuple[TypeVar, ParamSpec, TypeVarTuple] | None = None, + format: Format | None = None, + ) -> Any: ... # AnnotationForm - if sys.version_info >= (3, 13): - @overload - @deprecated( - "Failing to pass a value to the 'type_params' parameter of ForwardRef._evaluate() is deprecated, " - "as it leads to incorrect behaviour when evaluating a stringified annotation " - "that references a PEP 695 type parameter. It will be disallowed in Python 3.15." - ) - def _evaluate( - self, globalns: dict[str, Any] | None, localns: Mapping[str, Any] | None, *, recursive_guard: frozenset[str] - ) -> Any | None: ... - @overload - def _evaluate( - self, - globalns: dict[str, Any] | None, - localns: Mapping[str, Any] | None, - type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...], - *, - recursive_guard: frozenset[str], - ) -> Any | None: ... - elif sys.version_info >= (3, 12): - def _evaluate( - self, - globalns: dict[str, Any] | None, - localns: Mapping[str, Any] | None, - type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] | None = None, - *, - recursive_guard: frozenset[str], - ) -> Any | None: ... - else: - def _evaluate( - self, globalns: dict[str, Any] | None, localns: Mapping[str, Any] | None, recursive_guard: frozenset[str] - ) -> Any | None: ... +else: + @final + class ForwardRef(_Final): + __forward_arg__: str + __forward_code__: CodeType + __forward_evaluated__: bool + __forward_value__: Any | None # AnnotationForm + __forward_is_argument__: bool + __forward_is_class__: bool + __forward_module__: Any | None - def __eq__(self, other: object) -> bool: ... - def __hash__(self) -> int: ... - if sys.version_info >= (3, 11): - def __or__(self, other: Any) -> _SpecialForm: ... - def __ror__(self, other: Any) -> _SpecialForm: ... + def __init__(self, arg: str, is_argument: bool = True, module: Any | None = None, *, is_class: bool = False) -> None: ... + + if sys.version_info >= (3, 13): + @overload + @deprecated( + "Failing to pass a value to the 'type_params' parameter of ForwardRef._evaluate() is deprecated, " + "as it leads to incorrect behaviour when evaluating a stringified annotation " + "that references a PEP 695 type parameter. It will be disallowed in Python 3.15." + ) + def _evaluate( + self, globalns: dict[str, Any] | None, localns: Mapping[str, Any] | None, *, recursive_guard: frozenset[str] + ) -> Any | None: ... # AnnotationForm + @overload + def _evaluate( + self, + globalns: dict[str, Any] | None, + localns: Mapping[str, Any] | None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...], + *, + recursive_guard: frozenset[str], + ) -> Any | None: ... # AnnotationForm + elif sys.version_info >= (3, 12): + def _evaluate( + self, + globalns: dict[str, Any] | None, + localns: Mapping[str, Any] | None, + type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] | None = None, + *, + recursive_guard: frozenset[str], + ) -> Any | None: ... # AnnotationForm + else: + def _evaluate( + self, globalns: dict[str, Any] | None, localns: Mapping[str, Any] | None, recursive_guard: frozenset[str] + ) -> Any | None: ... # AnnotationForm + + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + if sys.version_info >= (3, 11): + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... if sys.version_info >= (3, 10): def is_typeddict(tp: object) -> bool: ... @@ -1022,19 +1099,22 @@ if sys.version_info >= (3, 12): class TypeAliasType: def __new__(cls, name: str, value: Any, *, type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = ()) -> Self: ... @property - def __value__(self) -> Any: ... + def __value__(self) -> Any: ... # AnnotationForm @property def __type_params__(self) -> tuple[TypeVar | ParamSpec | TypeVarTuple, ...]: ... @property - def __parameters__(self) -> tuple[Any, ...]: ... + def __parameters__(self) -> tuple[Any, ...]: ... # AnnotationForm @property def __name__(self) -> str: ... # It's writable on types, but not on instances of TypeAliasType. @property def __module__(self) -> str | None: ... # type: ignore[override] - def __getitem__(self, parameters: Any) -> GenericAlias: ... + def __getitem__(self, parameters: Any) -> GenericAlias: ... # AnnotationForm def __or__(self, right: Any) -> _SpecialForm: ... def __ror__(self, left: Any) -> _SpecialForm: ... + if sys.version_info >= (3, 14): + @property + def evaluate_value(self) -> EvaluateFunc: ... if sys.version_info >= (3, 13): def is_protocol(tp: type, /) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi index bad5fae880c0fc..37f8e8ba6a4b48 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi @@ -2,7 +2,7 @@ import abc import enum import sys from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import IdentityFunction, Incomplete, Unused +from _typeshed import AnnotationForm, IdentityFunction, Incomplete, Unused from collections.abc import ( AsyncGenerator as AsyncGenerator, AsyncIterable as AsyncIterable, @@ -241,7 +241,7 @@ class _TypedDict(Mapping[str, object], metaclass=abc.ABCMeta): __mutable_keys__: ClassVar[frozenset[str]] # PEP 728 __closed__: ClassVar[bool] - __extra_items__: ClassVar[Any] + __extra_items__: ClassVar[AnnotationForm] def copy(self) -> Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature # can go through. @@ -267,13 +267,14 @@ class _TypedDict(Mapping[str, object], metaclass=abc.ABCMeta): OrderedDict = _Alias() -def get_type_hints( - obj: Callable[..., Any], - globalns: dict[str, Any] | None = None, - localns: Mapping[str, Any] | None = None, - include_extras: bool = False, -) -> dict[str, Any]: ... -def get_args(tp: Any) -> tuple[Any, ...]: ... +if sys.version_info >= (3, 13): + from typing import get_type_hints as get_type_hints +else: + def get_type_hints( + obj: Any, globalns: dict[str, Any] | None = None, localns: Mapping[str, Any] | None = None, include_extras: bool = False + ) -> dict[str, AnnotationForm]: ... + +def get_args(tp: AnnotationForm) -> tuple[AnnotationForm, ...]: ... if sys.version_info >= (3, 10): @overload @@ -284,7 +285,7 @@ def get_origin(tp: GenericAlias) -> type: ... @overload def get_origin(tp: ParamSpecArgs | ParamSpecKwargs) -> ParamSpec: ... @overload -def get_origin(tp: Any) -> Any | None: ... +def get_origin(tp: AnnotationForm) -> AnnotationForm | None: ... Annotated: _SpecialForm _AnnotatedAlias: Any # undocumented @@ -340,7 +341,7 @@ else: Never: _SpecialForm def reveal_type(obj: _T, /) -> _T: ... def assert_never(arg: Never, /) -> Never: ... - def assert_type(val: _T, typ: Any, /) -> _T: ... + def assert_type(val: _T, typ: AnnotationForm, /) -> _T: ... def clear_overloads() -> None: ... def get_overloads(func: Callable[..., object]) -> Sequence[Callable[..., object]]: ... @@ -373,7 +374,7 @@ else: def _replace(self, **kwargs: Any) -> Self: ... class NewType: - def __init__(self, name: str, tp: Any) -> None: ... + def __init__(self, name: str, tp: AnnotationForm) -> None: ... def __call__(self, obj: _T, /) -> _T: ... __supertype__: type | NewType if sys.version_info >= (3, 10): @@ -480,9 +481,9 @@ else: @property def __name__(self) -> str: ... @property - def __bound__(self) -> Any | None: ... + def __bound__(self) -> AnnotationForm | None: ... @property - def __constraints__(self) -> tuple[Any, ...]: ... + def __constraints__(self) -> tuple[AnnotationForm, ...]: ... @property def __covariant__(self) -> bool: ... @property @@ -490,15 +491,15 @@ else: @property def __infer_variance__(self) -> bool: ... @property - def __default__(self) -> Any: ... + def __default__(self) -> AnnotationForm: ... def __init__( self, name: str, - *constraints: Any, - bound: Any | None = None, + *constraints: AnnotationForm, + bound: AnnotationForm | None = None, covariant: bool = False, contravariant: bool = False, - default: Any = ..., + default: AnnotationForm = ..., infer_variance: bool = False, ) -> None: ... def has_default(self) -> bool: ... @@ -514,7 +515,7 @@ else: @property def __name__(self) -> str: ... @property - def __bound__(self) -> Any | None: ... + def __bound__(self) -> AnnotationForm | None: ... @property def __covariant__(self) -> bool: ... @property @@ -522,15 +523,15 @@ else: @property def __infer_variance__(self) -> bool: ... @property - def __default__(self) -> Any: ... + def __default__(self) -> AnnotationForm: ... def __init__( self, name: str, *, - bound: None | type[Any] | str = None, + bound: None | AnnotationForm | str = None, contravariant: bool = False, covariant: bool = False, - default: Any = ..., + default: AnnotationForm = ..., ) -> None: ... @property def args(self) -> ParamSpecArgs: ... @@ -547,8 +548,8 @@ else: @property def __name__(self) -> str: ... @property - def __default__(self) -> Any: ... - def __init__(self, name: str, *, default: Any = ...) -> None: ... + def __default__(self) -> AnnotationForm: ... + def __init__(self, name: str, *, default: AnnotationForm = ...) -> None: ... def __iter__(self) -> Any: ... # Unpack[Self] def has_default(self) -> bool: ... def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... @@ -563,23 +564,23 @@ else: @final class TypeAliasType: def __init__( - self, name: str, value: Any, *, type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () - ) -> None: ... # value is a type expression + self, name: str, value: AnnotationForm, *, type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () + ) -> None: ... @property - def __value__(self) -> Any: ... # a type expression + def __value__(self) -> AnnotationForm: ... @property def __type_params__(self) -> tuple[TypeVar | ParamSpec | TypeVarTuple, ...]: ... @property # `__parameters__` can include special forms if a `TypeVarTuple` was # passed as a `type_params` element to the constructor method. - def __parameters__(self) -> tuple[TypeVar | ParamSpec | Any, ...]: ... + def __parameters__(self) -> tuple[TypeVar | ParamSpec | AnnotationForm, ...]: ... @property def __name__(self) -> str: ... # It's writable on types, but not on instances of TypeAliasType. @property def __module__(self) -> str | None: ... # type: ignore[override] # Returns typing._GenericAlias, which isn't stubbed. - def __getitem__(self, parameters: Incomplete | tuple[Incomplete, ...]) -> Any: ... + def __getitem__(self, parameters: Incomplete | tuple[Incomplete, ...]) -> AnnotationForm: ... def __init_subclass__(cls, *args: Unused, **kwargs: Unused) -> NoReturn: ... if sys.version_info >= (3, 10): def __or__(self, right: Any) -> _SpecialForm: ... @@ -600,27 +601,75 @@ NoExtraItems: _NoExtraItemsType # PEP 747 TypeForm: _SpecialForm -class Format(enum.IntEnum): - VALUE = 1 - FORWARDREF = 2 - STRING = 3 - # PEP 649/749 -def get_annotations( - obj: Callable[..., object] | type[object] | ModuleType, # any callable, class, or module - *, - globals: Mapping[str, Any] | None = None, # value types depend on the key - locals: Mapping[str, Any] | None = None, # value types depend on the key - eval_str: bool = False, - format: Format = Format.VALUE, # noqa: Y011 -) -> dict[str, Any]: ... # values are type expressions -def evaluate_forward_ref( - forward_ref: ForwardRef, - *, - owner: Callable[..., object] | type[object] | ModuleType | None = None, # any callable, class, or module - globals: Mapping[str, Any] | None = None, # value types depend on the key - locals: Mapping[str, Any] | None = None, # value types depend on the key - type_params: Iterable[TypeVar | ParamSpec | TypeVarTuple] | None = None, - format: Format = Format.VALUE, # noqa: Y011 - _recursive_guard: Container[str] = ..., -) -> Any: ... # str if format is Format.STRING, otherwise a type expression +if sys.version_info >= (3, 14): + from typing import evaluate_forward_ref as evaluate_forward_ref + + from annotationlib import Format as Format, get_annotations as get_annotations +else: + class Format(enum.IntEnum): + VALUE = 1 + VALUE_WITH_FAKE_GLOBALS = 2 + FORWARDREF = 3 + STRING = 4 + + @overload + def get_annotations( + obj: Any, # any object with __annotations__ or __annotate__ + *, + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + eval_str: bool = False, + format: Literal[Format.STRING], + ) -> dict[str, str]: ... + @overload + def get_annotations( + obj: Any, # any object with __annotations__ or __annotate__ + *, + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + eval_str: bool = False, + format: Literal[Format.FORWARDREF], + ) -> dict[str, AnnotationForm | ForwardRef]: ... + @overload + def get_annotations( + obj: Any, # any object with __annotations__ or __annotate__ + *, + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + eval_str: bool = False, + format: Format = Format.VALUE, # noqa: Y011 + ) -> dict[str, AnnotationForm]: ... + @overload + def evaluate_forward_ref( + forward_ref: ForwardRef, + *, + owner: Callable[..., object] | type[object] | ModuleType | None = None, # any callable, class, or module + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + type_params: Iterable[TypeVar | ParamSpec | TypeVarTuple] | None = None, + format: Literal[Format.STRING], + _recursive_guard: Container[str] = ..., + ) -> str: ... + @overload + def evaluate_forward_ref( + forward_ref: ForwardRef, + *, + owner: Callable[..., object] | type[object] | ModuleType | None = None, # any callable, class, or module + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + type_params: Iterable[TypeVar | ParamSpec | TypeVarTuple] | None = None, + format: Literal[Format.FORWARDREF], + _recursive_guard: Container[str] = ..., + ) -> AnnotationForm | ForwardRef: ... + @overload + def evaluate_forward_ref( + forward_ref: ForwardRef, + *, + owner: Callable[..., object] | type[object] | ModuleType | None = None, # any callable, class, or module + globals: Mapping[str, Any] | None = None, # value types depend on the key + locals: Mapping[str, Any] | None = None, # value types depend on the key + type_params: Iterable[TypeVar | ParamSpec | TypeVarTuple] | None = None, + format: Format = Format.VALUE, # noqa: Y011 + _recursive_guard: Container[str] = ..., + ) -> AnnotationForm: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/unittest/case.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/unittest/case.pyi index 7d1a382a54a431..89bcabf104c25f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/unittest/case.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/unittest/case.pyi @@ -18,6 +18,7 @@ _T = TypeVar("_T") _S = TypeVar("_S", bound=SupportsSub[Any, Any]) _E = TypeVar("_E", bound=BaseException) _FT = TypeVar("_FT", bound=Callable[..., Any]) +_SB = TypeVar("_SB", str, bytes, bytearray) _P = ParamSpec("_P") DIFF_OMITTED: Final[str] @@ -289,6 +290,16 @@ class TestCase: # Runtime has *args, **kwargs, but will error if any are supplied def __init_subclass__(cls, *args: Never, **kwargs: Never) -> None: ... + if sys.version_info >= (3, 14): + def assertIsSubclass(self, cls: type, superclass: type | tuple[type, ...], msg: Any = None) -> None: ... + def assertNotIsSubclass(self, cls: type, superclass: type | tuple[type, ...], msg: Any = None) -> None: ... + def assertHasAttr(self, obj: object, name: str, msg: Any = None) -> None: ... + def assertNotHasAttr(self, obj: object, name: str, msg: Any = None) -> None: ... + def assertStartsWith(self, s: _SB, prefix: _SB | tuple[_SB, ...], msg: Any = None) -> None: ... + def assertNotStartsWith(self, s: _SB, prefix: _SB | tuple[_SB, ...], msg: Any = None) -> None: ... + def assertEndsWith(self, s: _SB, suffix: _SB | tuple[_SB, ...], msg: Any = None) -> None: ... + def assertNotEndsWith(self, s: _SB, suffix: _SB | tuple[_SB, ...], msg: Any = None) -> None: ... + class FunctionTestCase(TestCase): def __init__( self, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/urllib/request.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/urllib/request.pyi index 1f453fd1e1d604..d8fc5e0d8f48d8 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/urllib/request.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/urllib/request.pyi @@ -7,7 +7,7 @@ from http.client import HTTPConnection, HTTPMessage, HTTPResponse from http.cookiejar import CookieJar from re import Pattern from typing import IO, Any, ClassVar, NoReturn, Protocol, TypeVar, overload -from typing_extensions import TypeAlias +from typing_extensions import TypeAlias, deprecated from urllib.error import HTTPError as HTTPError from urllib.response import addclosehook, addinfourl @@ -43,10 +43,10 @@ __all__ = [ "getproxies", "urlretrieve", "urlcleanup", - "URLopener", - "FancyURLopener", "HTTPSHandler", ] +if sys.version_info < (3, 14): + __all__ += ["URLopener", "FancyURLopener"] _T = TypeVar("_T") _UrlopenRet: TypeAlias = Any @@ -72,11 +72,16 @@ else: def install_opener(opener: OpenerDirector) -> None: ... def build_opener(*handlers: BaseHandler | Callable[[], BaseHandler]) -> OpenerDirector: ... -if sys.platform == "win32": - from nturl2path import pathname2url as pathname2url, url2pathname as url2pathname +if sys.version_info >= (3, 14): + def url2pathname(url: str, *, require_scheme: bool = False, resolve_host: bool = False) -> str: ... + def pathname2url(pathname: str, *, add_scheme: bool = False) -> str: ... + else: - def url2pathname(pathname: str) -> str: ... - def pathname2url(pathname: str) -> str: ... + if sys.platform == "win32": + from nturl2path import pathname2url as pathname2url, url2pathname as url2pathname + else: + def url2pathname(pathname: str) -> str: ... + def pathname2url(pathname: str) -> str: ... def getproxies() -> dict[str, str]: ... def getproxies_environment() -> dict[str, str]: ... @@ -318,91 +323,94 @@ def urlretrieve( ) -> tuple[str, HTTPMessage]: ... def urlcleanup() -> None: ... -class URLopener: - version: ClassVar[str] - def __init__(self, proxies: dict[str, str] | None = None, **x509: str) -> None: ... - def open(self, fullurl: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... - def open_unknown(self, fullurl: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... - def retrieve( - self, - url: str, - filename: str | None = None, - reporthook: Callable[[int, int, int], object] | None = None, - data: ReadableBuffer | None = None, - ) -> tuple[str, Message | None]: ... - def addheader(self, *args: tuple[str, str]) -> None: ... # undocumented - def cleanup(self) -> None: ... # undocumented - def close(self) -> None: ... # undocumented - def http_error( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: bytes | None = None - ) -> _UrlopenRet: ... # undocumented - def http_error_default( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage - ) -> _UrlopenRet: ... # undocumented - def open_data(self, url: str, data: ReadableBuffer | None = None) -> addinfourl: ... # undocumented - def open_file(self, url: str) -> addinfourl: ... # undocumented - def open_ftp(self, url: str) -> addinfourl: ... # undocumented - def open_http(self, url: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... # undocumented - def open_https(self, url: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... # undocumented - def open_local_file(self, url: str) -> addinfourl: ... # undocumented - def open_unknown_proxy(self, proxy: str, fullurl: str, data: ReadableBuffer | None = None) -> None: ... # undocumented - def __del__(self) -> None: ... - -class FancyURLopener(URLopener): - def prompt_user_passwd(self, host: str, realm: str) -> tuple[str, str]: ... - def get_user_passwd(self, host: str, realm: str, clear_cache: int = 0) -> tuple[str, str]: ... # undocumented - def http_error_301( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None - ) -> _UrlopenRet | addinfourl | None: ... # undocumented - def http_error_302( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None - ) -> _UrlopenRet | addinfourl | None: ... # undocumented - def http_error_303( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None - ) -> _UrlopenRet | addinfourl | None: ... # undocumented - def http_error_307( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None - ) -> _UrlopenRet | addinfourl | None: ... # undocumented - if sys.version_info >= (3, 11): - def http_error_308( +if sys.version_info < (3, 14): + @deprecated("Deprecated since Python 3.3; Removed in 3.14; Use newer urlopen functions and methods.") + class URLopener: + version: ClassVar[str] + def __init__(self, proxies: dict[str, str] | None = None, **x509: str) -> None: ... + def open(self, fullurl: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... + def open_unknown(self, fullurl: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... + def retrieve( + self, + url: str, + filename: str | None = None, + reporthook: Callable[[int, int, int], object] | None = None, + data: ReadableBuffer | None = None, + ) -> tuple[str, Message | None]: ... + def addheader(self, *args: tuple[str, str]) -> None: ... # undocumented + def cleanup(self) -> None: ... # undocumented + def close(self) -> None: ... # undocumented + def http_error( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: bytes | None = None + ) -> _UrlopenRet: ... # undocumented + def http_error_default( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage + ) -> _UrlopenRet: ... # undocumented + def open_data(self, url: str, data: ReadableBuffer | None = None) -> addinfourl: ... # undocumented + def open_file(self, url: str) -> addinfourl: ... # undocumented + def open_ftp(self, url: str) -> addinfourl: ... # undocumented + def open_http(self, url: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... # undocumented + def open_https(self, url: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... # undocumented + def open_local_file(self, url: str) -> addinfourl: ... # undocumented + def open_unknown_proxy(self, proxy: str, fullurl: str, data: ReadableBuffer | None = None) -> None: ... # undocumented + def __del__(self) -> None: ... + + @deprecated("Deprecated since Python 3.3; Removed in 3.14; Use newer urlopen functions and methods.") + class FancyURLopener(URLopener): + def prompt_user_passwd(self, host: str, realm: str) -> tuple[str, str]: ... + def get_user_passwd(self, host: str, realm: str, clear_cache: int = 0) -> tuple[str, str]: ... # undocumented + def http_error_301( self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None ) -> _UrlopenRet | addinfourl | None: ... # undocumented - - def http_error_401( - self, - url: str, - fp: IO[bytes], - errcode: int, - errmsg: str, - headers: HTTPMessage, - data: ReadableBuffer | None = None, - retry: bool = False, - ) -> _UrlopenRet | None: ... # undocumented - def http_error_407( - self, - url: str, - fp: IO[bytes], - errcode: int, - errmsg: str, - headers: HTTPMessage, - data: ReadableBuffer | None = None, - retry: bool = False, - ) -> _UrlopenRet | None: ... # undocumented - def http_error_default( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage - ) -> addinfourl: ... # undocumented - def redirect_internal( - self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None - ) -> _UrlopenRet | None: ... # undocumented - def retry_http_basic_auth( - self, url: str, realm: str, data: ReadableBuffer | None = None - ) -> _UrlopenRet | None: ... # undocumented - def retry_https_basic_auth( - self, url: str, realm: str, data: ReadableBuffer | None = None - ) -> _UrlopenRet | None: ... # undocumented - def retry_proxy_http_basic_auth( - self, url: str, realm: str, data: ReadableBuffer | None = None - ) -> _UrlopenRet | None: ... # undocumented - def retry_proxy_https_basic_auth( - self, url: str, realm: str, data: ReadableBuffer | None = None - ) -> _UrlopenRet | None: ... # undocumented + def http_error_302( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None + ) -> _UrlopenRet | addinfourl | None: ... # undocumented + def http_error_303( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None + ) -> _UrlopenRet | addinfourl | None: ... # undocumented + def http_error_307( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None + ) -> _UrlopenRet | addinfourl | None: ... # undocumented + if sys.version_info >= (3, 11): + def http_error_308( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None = None + ) -> _UrlopenRet | addinfourl | None: ... # undocumented + + def http_error_401( + self, + url: str, + fp: IO[bytes], + errcode: int, + errmsg: str, + headers: HTTPMessage, + data: ReadableBuffer | None = None, + retry: bool = False, + ) -> _UrlopenRet | None: ... # undocumented + def http_error_407( + self, + url: str, + fp: IO[bytes], + errcode: int, + errmsg: str, + headers: HTTPMessage, + data: ReadableBuffer | None = None, + retry: bool = False, + ) -> _UrlopenRet | None: ... # undocumented + def http_error_default( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage + ) -> addinfourl: ... # undocumented + def redirect_internal( + self, url: str, fp: IO[bytes], errcode: int, errmsg: str, headers: HTTPMessage, data: ReadableBuffer | None + ) -> _UrlopenRet | None: ... # undocumented + def retry_http_basic_auth( + self, url: str, realm: str, data: ReadableBuffer | None = None + ) -> _UrlopenRet | None: ... # undocumented + def retry_https_basic_auth( + self, url: str, realm: str, data: ReadableBuffer | None = None + ) -> _UrlopenRet | None: ... # undocumented + def retry_proxy_http_basic_auth( + self, url: str, realm: str, data: ReadableBuffer | None = None + ) -> _UrlopenRet | None: ... # undocumented + def retry_proxy_https_basic_auth( + self, url: str, realm: str, data: ReadableBuffer | None = None + ) -> _UrlopenRet | None: ... # undocumented diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/uuid.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/uuid.pyi index 3202ae212cae6c..99ac6eb223ef36 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/uuid.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/uuid.pyi @@ -1,7 +1,8 @@ import builtins import sys from enum import Enum -from typing_extensions import TypeAlias +from typing import Final +from typing_extensions import LiteralString, TypeAlias _FieldsType: TypeAlias = tuple[int, int, int, int, int, int] @@ -67,6 +68,11 @@ class UUID: def getnode() -> int: ... def uuid1(node: int | None = None, clock_seq: int | None = None) -> UUID: ... +if sys.version_info >= (3, 14): + def uuid6(node: int | None = None, clock_seq: int | None = None) -> UUID: ... + def uuid7() -> UUID: ... + def uuid8(a: int | None = None, b: int | None = None, c: int | None = None) -> UUID: ... + if sys.version_info >= (3, 12): def uuid3(namespace: UUID, name: str | bytes) -> UUID: ... @@ -81,14 +87,18 @@ if sys.version_info >= (3, 12): else: def uuid5(namespace: UUID, name: str) -> UUID: ... -NAMESPACE_DNS: UUID -NAMESPACE_URL: UUID -NAMESPACE_OID: UUID -NAMESPACE_X500: UUID -RESERVED_NCS: str -RFC_4122: str -RESERVED_MICROSOFT: str -RESERVED_FUTURE: str +if sys.version_info >= (3, 14): + NIL: Final[UUID] + MAX: Final[UUID] + +NAMESPACE_DNS: Final[UUID] +NAMESPACE_URL: Final[UUID] +NAMESPACE_OID: Final[UUID] +NAMESPACE_X500: Final[UUID] +RESERVED_NCS: Final[LiteralString] +RFC_4122: Final[LiteralString] +RESERVED_MICROSOFT: Final[LiteralString] +RESERVED_FUTURE: Final[LiteralString] if sys.version_info >= (3, 12): def main() -> None: ... From 9aa6330bb1433e4d7cd12a07a975a906c3f5b001 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 May 2025 22:38:53 -0400 Subject: [PATCH 103/487] [ty] Fix `redundant-cast` false positives when casting to `Unknown` (#18111) --- .../resources/mdtest/directives/cast.md | 9 +- crates/ty_python_semantic/src/types.rs | 93 ++++++++++++------- crates/ty_python_semantic/src/types/infer.rs | 8 +- .../ty_python_semantic/src/types/instance.rs | 10 +- .../src/types/protocol_class.rs | 11 ++- 5 files changed, 92 insertions(+), 39 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/directives/cast.md b/crates/ty_python_semantic/resources/mdtest/directives/cast.md index 9731d4cb37fe03..66ba7938a5888d 100644 --- a/crates/ty_python_semantic/resources/mdtest/directives/cast.md +++ b/crates/ty_python_semantic/resources/mdtest/directives/cast.md @@ -56,6 +56,10 @@ readers of ty's output. For `Unknown` in particular, we may consider it differen of some opt-in diagnostics, as it indicates that the gradual type has come about due to an invalid annotation, missing annotation or missing type argument somewhere. +A cast from `Unknown` to `Todo` or `Any` is also not considered a "redundant cast", as this breaks +the gradual guarantee and leads to cascading errors when an object is inferred as having type +`Unknown` due to a missing import or similar. + ```py from ty_extensions import Unknown @@ -66,5 +70,8 @@ def f(x: Any, y: Unknown, z: Any | str | int): b = cast(Any, y) reveal_type(b) # revealed: Any - c = cast(str | int | Any, z) # error: [redundant-cast] + c = cast(Unknown, y) + reveal_type(c) # revealed: Unknown + + d = cast(str | int | Any, z) # error: [redundant-cast] ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index af54dae8d66022..cee9d1146b7314 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -657,25 +657,26 @@ impl<'db> Type<'db> { } } - pub fn contains_todo(&self, db: &'db dyn Db) -> bool { - match self { - Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => true, + /// Return `true` if `self`, or any of the types contained in `self`, match the closure passed in. + pub fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool { + if type_fn(self) { + return true; + } + match self { Self::AlwaysFalsy | Self::AlwaysTruthy | Self::Never | Self::BooleanLiteral(_) | Self::BytesLiteral(_) - | Self::FunctionLiteral(_) - | Self::NominalInstance(_) | Self::ModuleLiteral(_) + | Self::FunctionLiteral(_) | Self::ClassLiteral(_) | Self::KnownInstance(_) - | Self::PropertyInstance(_) | Self::StringLiteral(_) | Self::IntLiteral(_) | Self::LiteralString - | Self::Dynamic(DynamicType::Unknown | DynamicType::Any) + | Self::Dynamic(_) | Self::BoundMethod(_) | Self::WrapperDescriptor(_) | Self::MethodWrapper(_) @@ -686,7 +687,8 @@ impl<'db> Type<'db> { .specialization(db) .types(db) .iter() - .any(|ty| ty.contains_todo(db)), + .copied() + .any(|ty| ty.any_over_type(db, type_fn)), Self::Callable(callable) => { let signatures = callable.signatures(db); @@ -694,54 +696,73 @@ impl<'db> Type<'db> { signature.parameters().iter().any(|param| { param .annotated_type() - .is_some_and(|ty| ty.contains_todo(db)) - }) || signature.return_ty.is_some_and(|ty| ty.contains_todo(db)) + .is_some_and(|ty| ty.any_over_type(db, type_fn)) + }) || signature + .return_ty + .is_some_and(|ty| ty.any_over_type(db, type_fn)) }) } - Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() { - SubclassOfInner::Dynamic( - DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec, - ) => true, - SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false, - SubclassOfInner::Class(_) => false, - }, + Self::SubclassOf(subclass_of) => { + Type::from(subclass_of.subclass_of()).any_over_type(db, type_fn) + } Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) { None => false, - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.contains_todo(db), + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { + bound.any_over_type(db, type_fn) + } Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints .elements(db) .iter() - .any(|constraint| constraint.contains_todo(db)), + .any(|constraint| constraint.any_over_type(db, type_fn)), }, Self::BoundSuper(bound_super) => { - matches!( - bound_super.pivot_class(db), - ClassBase::Dynamic(DynamicType::Todo(_)) - ) || matches!( - bound_super.owner(db), - SuperOwnerKind::Dynamic(DynamicType::Todo(_)) - ) + Type::from(bound_super.pivot_class(db)).any_over_type(db, type_fn) + || Type::from(bound_super.owner(db)).any_over_type(db, type_fn) } - Self::Tuple(tuple) => tuple.elements(db).iter().any(|ty| ty.contains_todo(db)), + Self::Tuple(tuple) => tuple + .elements(db) + .iter() + .any(|ty| ty.any_over_type(db, type_fn)), - Self::Union(union) => union.elements(db).iter().any(|ty| ty.contains_todo(db)), + Self::Union(union) => union + .elements(db) + .iter() + .any(|ty| ty.any_over_type(db, type_fn)), Self::Intersection(intersection) => { intersection .positive(db) .iter() - .any(|ty| ty.contains_todo(db)) + .any(|ty| ty.any_over_type(db, type_fn)) || intersection .negative(db) .iter() - .any(|ty| ty.contains_todo(db)) + .any(|ty| ty.any_over_type(db, type_fn)) + } + + Self::ProtocolInstance(protocol) => protocol.any_over_type(db, type_fn), + + Self::PropertyInstance(property) => { + property + .getter(db) + .is_some_and(|ty| ty.any_over_type(db, type_fn)) + || property + .setter(db) + .is_some_and(|ty| ty.any_over_type(db, type_fn)) } - Self::ProtocolInstance(protocol) => protocol.contains_todo(db), + Self::NominalInstance(instance) => match instance.class { + ClassType::NonGeneric(_) => false, + ClassType::Generic(generic) => generic + .specialization(db) + .types(db) + .iter() + .any(|ty| ty.any_over_type(db, type_fn)), + }, } } @@ -8172,6 +8193,16 @@ impl<'db> SuperOwnerKind<'db> { } } +impl<'db> From> for Type<'db> { + fn from(owner: SuperOwnerKind<'db>) -> Self { + match owner { + SuperOwnerKind::Dynamic(dynamic) => Type::Dynamic(dynamic), + SuperOwnerKind::Class(class) => class.into(), + SuperOwnerKind::Instance(instance) => instance.into(), + } + } +} + /// Represent a bound super object like `super(PivotClass, owner)` #[salsa::interned(debug)] pub struct BoundSuperType<'db> { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index d818187c023bc7..72915862420641 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5051,7 +5051,13 @@ impl<'db> TypeInferenceBuilder<'db> { if (source_type.is_equivalent_to(db, *casted_type) || source_type.normalized(db) == casted_type.normalized(db)) - && !source_type.contains_todo(db) + && !source_type.any_over_type(db, &|ty| { + matches!( + ty, + Type::Dynamic(dynamic) + if dynamic != DynamicType::Any + ) + }) { if let Some(builder) = self .context diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 99312849da3156..193c0f626e077c 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -237,9 +237,13 @@ impl<'db> ProtocolInstanceType<'db> { } } - /// Return `true` if any of the members of this protocol type contain any `Todo` types. - pub(super) fn contains_todo(self, db: &'db dyn Db) -> bool { - self.inner.interface(db).contains_todo(db) + /// Return `true` if the types of any of the members match the closure passed in. + pub(super) fn any_over_type( + self, + db: &'db dyn Db, + type_fn: &dyn Fn(Type<'db>) -> bool, + ) -> bool { + self.inner.interface(db).any_over_type(db, type_fn) } /// Return `true` if this protocol type is fully static. diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index c29be0bd3ec563..6cf986485265b2 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -149,9 +149,14 @@ impl<'db> ProtocolInterface<'db> { } } - /// Return `true` if any of the members of this protocol type contain any `Todo` types. - pub(super) fn contains_todo(self, db: &'db dyn Db) -> bool { - self.members(db).any(|member| member.ty.contains_todo(db)) + /// Return `true` if the types of any of the members match the closure passed in. + pub(super) fn any_over_type( + self, + db: &'db dyn Db, + type_fn: &dyn Fn(Type<'db>) -> bool, + ) -> bool { + self.members(db) + .any(|member| member.ty.any_over_type(db, type_fn)) } pub(super) fn normalized(self, db: &'db dyn Db) -> Self { From c3a4992ae9792d65137a5f1d47db63a0eb154e1f Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 May 2025 22:48:33 -0400 Subject: [PATCH 104/487] [ty] Fix normalization of unions containing instances parameterized with unions (#18112) --- .../resources/mdtest/directives/cast.md | 5 ++++- .../type_properties/is_equivalent_to.md | 17 ++++++++++++++++ crates/ty_python_semantic/src/types.rs | 9 +++------ crates/ty_python_semantic/src/types/class.rs | 11 ++++++++++ crates/ty_python_semantic/src/types/infer.rs | 20 +++++++++---------- .../ty_python_semantic/src/types/instance.rs | 4 ++++ 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/directives/cast.md b/crates/ty_python_semantic/resources/mdtest/directives/cast.md index 66ba7938a5888d..6943eceee6c9b2 100644 --- a/crates/ty_python_semantic/resources/mdtest/directives/cast.md +++ b/crates/ty_python_semantic/resources/mdtest/directives/cast.md @@ -73,5 +73,8 @@ def f(x: Any, y: Unknown, z: Any | str | int): c = cast(Unknown, y) reveal_type(c) # revealed: Unknown - d = cast(str | int | Any, z) # error: [redundant-cast] + d = cast(Unknown, x) + reveal_type(d) # revealed: Unknown + + e = cast(str | int | Any, z) # error: [redundant-cast] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 885153d32b8b97..ae6631216c1218 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -118,6 +118,23 @@ class R: ... static_assert(is_equivalent_to(Intersection[tuple[P | Q], R], Intersection[tuple[Q | P], R])) ``` +## Unions containing generic instances parameterized by unions + +```toml +[environment] +python-version = "3.12" +``` + +```py +from ty_extensions import is_equivalent_to, static_assert + +class A: ... +class B: ... +class Foo[T]: ... + +static_assert(is_equivalent_to(A | Foo[A | B], Foo[B | A] | A)) +``` + ## Callable ### Equivalent diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index cee9d1146b7314..ccf6557e8f35ec 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -985,15 +985,15 @@ impl<'db> Type<'db> { Type::Tuple(tuple) => Type::Tuple(tuple.normalized(db)), Type::Callable(callable) => Type::Callable(callable.normalized(db)), Type::ProtocolInstance(protocol) => protocol.normalized(db), + Type::NominalInstance(instance) => Type::NominalInstance(instance.normalized(db)), + Type::Dynamic(_) => Type::any(), Type::LiteralString - | Type::NominalInstance(_) | Type::PropertyInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy | Type::BooleanLiteral(_) | Type::BytesLiteral(_) | Type::StringLiteral(_) - | Type::Dynamic(_) | Type::Never | Type::FunctionLiteral(_) | Type::MethodWrapper(_) @@ -1007,10 +1007,7 @@ impl<'db> Type<'db> { | Type::IntLiteral(_) | Type::BoundSuper(_) | Type::SubclassOf(_) => self, - Type::GenericAlias(generic) => { - let specialization = generic.specialization(db).normalized(db); - Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization)) - } + Type::GenericAlias(generic) => Type::GenericAlias(generic.normalized(db)), Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Type::TypeVar(TypeVarInstance::new( diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 8a6836253be1d0..59b6efcd048c73 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -164,6 +164,10 @@ pub struct GenericAlias<'db> { } impl<'db> GenericAlias<'db> { + pub(super) fn normalized(self, db: &'db dyn Db) -> Self { + Self::new(db, self.origin(db), self.specialization(db).normalized(db)) + } + pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { self.origin(db).definition(db) } @@ -207,6 +211,13 @@ pub enum ClassType<'db> { #[salsa::tracked] impl<'db> ClassType<'db> { + pub(super) fn normalized(self, db: &'db dyn Db) -> Self { + match self { + Self::NonGeneric(_) => self, + Self::Generic(generic) => Self::Generic(generic.normalized(db)), + } + } + /// Returns the class literal and specialization for this class. For a non-generic class, this /// is the class itself. For a generic alias, this is the alias's origin. pub(crate) fn class_literal( diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 72915862420641..3a40e660b2351f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5048,16 +5048,16 @@ impl<'db> TypeInferenceBuilder<'db> { overload.parameter_types() { let db = self.db(); - if (source_type.is_equivalent_to(db, *casted_type) - || source_type.normalized(db) - == casted_type.normalized(db)) - && !source_type.any_over_type(db, &|ty| { - matches!( - ty, - Type::Dynamic(dynamic) - if dynamic != DynamicType::Any - ) - }) + let contains_unknown_or_todo = |ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any); + if source_type.is_equivalent_to(db, *casted_type) + || (source_type.normalized(db) + == casted_type.normalized(db) + && !casted_type.any_over_type(db, &|ty| { + contains_unknown_or_todo(ty) + }) + && !source_type.any_over_type(db, &|ty| { + contains_unknown_or_todo(ty) + })) { if let Some(builder) = self .context diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 193c0f626e077c..3a5412f8665dcf 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -75,6 +75,10 @@ impl<'db> NominalInstanceType<'db> { } } + pub(super) fn normalized(self, db: &'db dyn Db) -> Self { + Self::from_class(self.class.normalized(db)) + } + pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { // N.B. The subclass relation is fully static self.class.is_subclass_of(db, other.class) From 46be305ad243a5286d4269b1f8e5fd67623d38c2 Mon Sep 17 00:00:00 2001 From: InSync Date: Thu, 15 May 2025 09:51:23 +0700 Subject: [PATCH 105/487] [ty] Include synthesized arguments in displayed counts for `too-many-positional-arguments` (#18098) ## Summary Resolves [#290](https://github.com/astral-sh/ty/issues/290). All arguments, synthesized or not, are now accounted for in `too-many-positional-arguments`'s error message. For example, consider this example: ```python class C: def foo(self): ... C().foo(1) # !!! ``` Previously, ty would say: > Too many positional arguments to bound method foo: expected 0, got 1 After this change, it will say: > Too many positional arguments to bound method foo: expected 1, got 2 This is what Python itself does too: ```text Traceback (most recent call last): File "", line 3, in C().foo() ~~~~~~~^^ TypeError: C.foo() takes 0 positional arguments but 1 was given ``` ## Test Plan Markdown tests. --- .../resources/mdtest/call/constructor.md | 24 +++++++++---------- .../resources/mdtest/call/subclass_of.md | 2 +- .../ty_python_semantic/src/types/call/bind.rs | 10 ++------ 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index e56fb09320104f..0e89cb5d32e571 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -47,7 +47,7 @@ class Foo: ... reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 0, got 1" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" reveal_type(Foo(1)) # revealed: Foo ``` @@ -62,7 +62,7 @@ reveal_type(Foo(1)) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" +# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -84,7 +84,7 @@ reveal_type(Foo(1)) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" +# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -105,7 +105,7 @@ def _(flag: bool) -> None: # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo()) # revealed: Foo - # error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" + # error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -198,7 +198,7 @@ reveal_type(Foo(1)) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -217,7 +217,7 @@ reveal_type(Foo(1)) # revealed: Foo # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -238,7 +238,7 @@ def _(flag: bool) -> None: # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo()) # revealed: Foo - # error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" + # error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -344,7 +344,7 @@ class Foo: reveal_type(Foo()) # revealed: Foo reveal_type(Foo(1)) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 2, got 3" reveal_type(Foo(1, 2)) # revealed: Foo ``` @@ -363,7 +363,7 @@ class Foo: # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo()) # revealed: Foo -# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 0, got 1" +# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" reveal_type(Foo(42)) # revealed: Foo class Foo2: @@ -376,7 +376,7 @@ class Foo2: # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo2()) # revealed: Foo2 -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 0, got 1" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" reveal_type(Foo2(42)) # revealed: Foo2 class Foo3(metaclass=abc.ABCMeta): @@ -389,7 +389,7 @@ class Foo3(metaclass=abc.ABCMeta): # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(Foo3()) # revealed: Foo3 -# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 0, got 1" +# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2" reveal_type(Foo3(42)) # revealed: Foo3 class Foo4(metaclass=abc.ABCMeta): @@ -402,7 +402,7 @@ class Foo4(metaclass=abc.ABCMeta): # error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`" reveal_type(Foo4()) # revealed: Foo4 -# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 0, got 1" +# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" reveal_type(Foo4(42)) # revealed: Foo4 ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md b/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md index 49b09670983238..544c4c7c90bb07 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md +++ b/crates/ty_python_semantic/resources/mdtest/call/subclass_of.md @@ -24,7 +24,7 @@ def _(subclass_of_c: type[C]): reveal_type(subclass_of_c("a")) # revealed: C # error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`" reveal_type(subclass_of_c()) # revealed: C - # error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2" + # error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 2, got 3" reveal_type(subclass_of_c(1, 2)) # revealed: C ``` diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index de8cc4168ea08f..52189b96026fc6 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1285,14 +1285,8 @@ impl<'db> Binding<'db> { first_excess_argument_index, num_synthetic_args, ), - expected_positional_count: parameters - .positional() - .count() - // using saturating_sub to avoid negative values due to invalid syntax in source code - .saturating_sub(num_synthetic_args), - provided_positional_count: next_positional - // using saturating_sub to avoid negative values due to invalid syntax in source code - .saturating_sub(num_synthetic_args), + expected_positional_count: parameters.positional().count(), + provided_positional_count: next_positional, }); } let mut missing = vec![]; From b6b7caa0238b2f8fc455a3a6769f6ca9ae65c2af Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 15 May 2025 09:57:59 +0200 Subject: [PATCH 106/487] [ty] Change layout of extra verbose output and respect `--color` for verbose output (#18089) --- Cargo.lock | 25 +-------------------- Cargo.toml | 1 - crates/ruff_db/Cargo.toml | 3 +-- crates/ruff_db/src/testing.rs | 41 +++++++---------------------------- crates/ty/Cargo.toml | 1 - crates/ty/src/lib.rs | 2 +- crates/ty/src/logging.rs | 28 ++++++++++++++++-------- 7 files changed, 30 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a967621aae2974..55cd4172335305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,15 +1943,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2706,7 +2697,6 @@ dependencies = [ "thiserror 2.0.12", "tracing", "tracing-subscriber", - "tracing-tree", "web-time", "zip", ] @@ -3955,7 +3945,7 @@ checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "chrono", "matchers", - "nu-ansi-term 0.46.0", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -3966,18 +3956,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "tracing-tree" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f459ca79f1b0d5f71c54ddfde6debfc59c8b6eeb46808ae492077f739dc7b49c" -dependencies = [ - "nu-ansi-term 0.50.1", - "tracing-core", - "tracing-log", - "tracing-subscriber", -] - [[package]] name = "tryfn" version = "0.2.3" @@ -4017,7 +3995,6 @@ dependencies = [ "tracing", "tracing-flame", "tracing-subscriber", - "tracing-tree", "ty_project", "ty_python_semantic", "ty_server", diff --git a/Cargo.toml b/Cargo.toml index d28dd238480225..79b8164e397dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,7 +163,6 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = "env-filter", "fmt", ] } -tracing-tree = { version = "0.4.0" } tryfn = { version = "0.2.1" } typed-arena = { version = "2.0.2" } unic-ucd-category = { version = "0.9" } diff --git a/crates/ruff_db/Cargo.toml b/crates/ruff_db/Cargo.toml index 3507efa545f3ab..f36a49810c9682 100644 --- a/crates/ruff_db/Cargo.toml +++ b/crates/ruff_db/Cargo.toml @@ -36,7 +36,6 @@ path-slash = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true, optional = true } -tracing-tree = { workspace = true, optional = true } rustc-hash = { workspace = true } zip = { workspace = true } @@ -55,4 +54,4 @@ cache = ["ruff_cache"] os = ["ignore", "dep:etcetera"] serde = ["dep:serde", "camino/serde1"] # Exposes testing utilities. -testing = ["tracing-subscriber", "tracing-tree"] +testing = ["tracing-subscriber"] diff --git a/crates/ruff_db/src/testing.rs b/crates/ruff_db/src/testing.rs index 57f264fa69ad08..5848c5d1944b18 100644 --- a/crates/ruff_db/src/testing.rs +++ b/crates/ruff_db/src/testing.rs @@ -141,7 +141,6 @@ pub fn setup_logging_with_filter(filter: &str) -> Option { #[derive(Debug)] pub struct LoggingBuilder { filter: EnvFilter, - hierarchical: bool, } impl LoggingBuilder { @@ -154,50 +153,26 @@ impl LoggingBuilder { .parse() .expect("Hardcoded directive to be valid"), ), - hierarchical: false, } } pub fn with_filter(filter: &str) -> Option { let filter = EnvFilter::builder().parse(filter).ok()?; - Some(Self { - filter, - hierarchical: false, - }) - } - - pub fn with_hierarchical(mut self, hierarchical: bool) -> Self { - self.hierarchical = hierarchical; - self + Some(Self { filter }) } pub fn build(self) -> LoggingGuard { let registry = tracing_subscriber::registry().with(self.filter); - let guard = if self.hierarchical { - let subscriber = registry.with( - tracing_tree::HierarchicalLayer::default() - .with_indent_lines(true) - .with_indent_amount(2) - .with_bracketed_fields(true) - .with_thread_ids(true) - .with_targets(true) - .with_writer(std::io::stderr) - .with_timer(tracing_tree::time::Uptime::default()), - ); - - tracing::subscriber::set_default(subscriber) - } else { - let subscriber = registry.with( - tracing_subscriber::fmt::layer() - .compact() - .with_writer(std::io::stderr) - .with_timer(tracing_subscriber::fmt::time()), - ); + let subscriber = registry.with( + tracing_subscriber::fmt::layer() + .compact() + .with_writer(std::io::stderr) + .with_timer(tracing_subscriber::fmt::time()), + ); - tracing::subscriber::set_default(subscriber) - }; + let guard = tracing::subscriber::set_default(subscriber); LoggingGuard { _guard: guard } } diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index 1a1222121d1419..f7fcae1212a746 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -35,7 +35,6 @@ salsa = { workspace = true } tracing = { workspace = true, features = ["release_max_level_debug"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } tracing-flame = { workspace = true } -tracing-tree = { workspace = true } wild = { workspace = true } [dev-dependencies] diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index a0b2e1037e61f4..f51030c9879828 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -60,7 +60,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result { let verbosity = args.verbosity.level(); countme::enable(verbosity.is_trace()); - let _guard = setup_tracing(verbosity)?; + let _guard = setup_tracing(verbosity, args.color.unwrap_or_default())?; tracing::warn!( "ty is pre-release software and not ready for production use. \ diff --git a/crates/ty/src/logging.rs b/crates/ty/src/logging.rs index 5119af652bd040..a90dfd85e77c63 100644 --- a/crates/ty/src/logging.rs +++ b/crates/ty/src/logging.rs @@ -1,10 +1,11 @@ //! Sets up logging for ty +use crate::args::TerminalColor; use anyhow::Context; use colored::Colorize; use std::fmt; use std::fs::File; -use std::io::BufWriter; +use std::io::{BufWriter, IsTerminal}; use tracing::{Event, Subscriber}; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::fmt::format::Writer; @@ -76,7 +77,10 @@ impl VerbosityLevel { } } -pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result { +pub(crate) fn setup_tracing( + level: VerbosityLevel, + color: TerminalColor, +) -> anyhow::Result { use tracing_subscriber::prelude::*; // The `TY_LOG` environment variable overrides the default log level. @@ -115,16 +119,21 @@ pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result { + colored::control::SHOULD_COLORIZE.should_colorize() && std::io::stderr().is_terminal() + } + TerminalColor::Always => true, + TerminalColor::Never => false, + }; + if level.is_trace() { let subscriber = registry.with( - tracing_tree::HierarchicalLayer::default() - .with_indent_lines(true) - .with_indent_amount(2) - .with_bracketed_fields(true) + tracing_subscriber::fmt::layer() + .event_format(tracing_subscriber::fmt::format().pretty()) .with_thread_ids(true) - .with_targets(true) - .with_writer(std::io::stderr) - .with_timer(tracing_tree::time::Uptime::default()), + .with_ansi(ansi) + .with_writer(std::io::stderr), ); subscriber.init(); @@ -136,6 +145,7 @@ pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result Date: Thu, 15 May 2025 11:43:15 +0200 Subject: [PATCH 107/487] [ty] Enable 'ansi' feature to fix compile error (#18116) --- Cargo.toml | 2 ++ crates/ty/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 79b8164e397dcb..08432d12f6274a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,6 +162,8 @@ tracing-log = { version = "0.2.0" } tracing-subscriber = { version = "0.3.18", default-features = false, features = [ "env-filter", "fmt", + "ansi", + "smallvec" ] } tryfn = { version = "0.2.1" } typed-arena = { version = "2.0.2" } diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index f7fcae1212a746..cd287501fbbaf7 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -33,7 +33,7 @@ jiff = { workspace = true } rayon = { workspace = true } salsa = { workspace = true } tracing = { workspace = true, features = ["release_max_level_debug"] } -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +tracing-subscriber = { workspace = true } tracing-flame = { workspace = true } wild = { workspace = true } From 57617031de659f0f080d0abbea466b0765f5bab8 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 15 May 2025 12:31:46 +0200 Subject: [PATCH 108/487] [ty] Enable optimizations for salsa in debug profile (#18117) --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 08432d12f6274a..0114b8002713fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -258,6 +258,9 @@ opt-level = 3 [profile.dev.package.similar] opt-level = 3 +[profile.dev.package.salsa] +opt-level = 3 + # Reduce complexity of a parser function that would trigger a locals limit in a wasm tool. # https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29 [profile.dev.package.ruff_python_parser] From 279dac1c0e3b4625c328ec60272a0b6605d8a3e3 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 15 May 2025 14:27:23 +0200 Subject: [PATCH 109/487] [ty] Make dataclass instances adhere to DataclassInstance (#18115) ## Summary Make dataclass instances adhere to the `DataclassInstance` protocol. fixes astral-sh/ty#400 ## Test Plan New Markdown tests --- .../resources/mdtest/dataclasses.md | 34 +++++++++++++++---- crates/ty_python_semantic/src/types.rs | 13 ------- crates/ty_python_semantic/src/types/class.rs | 12 +++++++ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses.md index 7b5377d0132a38..5db68ca81bfd1a 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses.md @@ -618,23 +618,45 @@ To do ## `dataclass.fields` -Dataclasses have `__dataclass_fields__` in them, which makes them a subtype of the -`DataclassInstance` protocol. - -Here, we verify that dataclasses can be passed to `dataclasses.fields` without any errors, and that -the return type of `dataclasses.fields` is correct. +Dataclasses have a special `__dataclass_fields__` class variable member. The `DataclassInstance` +protocol checks for the presence of this attribute. It is used in the `dataclasses.fields` and +`dataclasses.asdict` functions, for example: ```py -from dataclasses import dataclass, fields +from dataclasses import dataclass, fields, asdict @dataclass class Foo: x: int +foo = Foo(1) + +reveal_type(foo.__dataclass_fields__) # revealed: dict[str, Field[Any]] +reveal_type(fields(Foo)) # revealed: tuple[Field[Any], ...] +reveal_type(asdict(foo)) # revealed: dict[str, Any] +``` + +The class objects themselves also have a `__dataclass_fields__` attribute: + +```py reveal_type(Foo.__dataclass_fields__) # revealed: dict[str, Field[Any]] +``` + +They can be passed into `fields` as well, because it also accepts `type[DataclassInstance]` +arguments: + +```py reveal_type(fields(Foo)) # revealed: tuple[Field[Any], ...] ``` +But calling `asdict` on the class object is not allowed: + +```py +# TODO: this should be a invalid-argument-type error, but we don't properly check the +# types (and more importantly, the `ClassVar` type qualifier) of protocol members yet. +asdict(Foo) +``` + ## Other special cases ### `dataclasses.dataclass` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ccf6557e8f35ec..77da747c460118 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2957,19 +2957,6 @@ impl<'db> Type<'db> { )) .into() } - Type::ClassLiteral(class) - if name == "__dataclass_fields__" && class.dataclass_params(db).is_some() => - { - // Make this class look like a subclass of the `DataClassInstance` protocol - Symbol::bound(KnownClass::Dict.to_specialized_instance( - db, - [ - KnownClass::Str.to_instance(db), - KnownClass::Field.to_specialized_instance(db, [Type::any()]), - ], - )) - .with_qualifiers(TypeQualifiers::CLASS_VAR) - } Type::BoundMethod(bound_method) => match name_str { "__self__" => Symbol::bound(bound_method.self_instance(db)).into(), "__func__" => { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 59b6efcd048c73..d5763dfe62812c 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1132,6 +1132,18 @@ impl<'db> ClassLiteral<'db> { specialization: Option>, name: &str, ) -> SymbolAndQualifiers<'db> { + if name == "__dataclass_fields__" && self.dataclass_params(db).is_some() { + // Make this class look like a subclass of the `DataClassInstance` protocol + return Symbol::bound(KnownClass::Dict.to_specialized_instance( + db, + [ + KnownClass::Str.to_instance(db), + KnownClass::Field.to_specialized_instance(db, [Type::any()]), + ], + )) + .with_qualifiers(TypeQualifiers::CLASS_VAR); + } + let body_scope = self.body_scope(db); let symbol = class_symbol(db, body_scope, name).map_type(|ty| { // The `__new__` and `__init__` members of a non-specialized generic class are handled From b35bf8ae073a47e12a98eea3eb4818d3695ff302 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 15 May 2025 09:54:08 -0400 Subject: [PATCH 110/487] Bump 0.11.10 (#18120) --- CHANGELOG.md | 40 +++++++++++++++++++++++++++++++ Cargo.lock | 6 ++--- README.md | 6 ++--- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_wasm/Cargo.toml | 2 +- docs/integrations.md | 8 +++---- docs/tutorial.md | 2 +- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 10 files changed, 56 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e265cb93f12c4..9345ec6570742d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Changelog +## 0.11.10 + +### Preview features + +- \[`ruff`\] Implement a recursive check for `RUF060` ([#17976](https://github.com/astral-sh/ruff/pull/17976)) +- \[`airflow`\] Enable autofixes for `AIR301` and `AIR311` ([#17941](https://github.com/astral-sh/ruff/pull/17941)) +- \[`airflow`\] Apply try catch guard to all `AIR3` rules ([#17887](https://github.com/astral-sh/ruff/pull/17887)) +- \[`airflow`\] Extend `AIR311` rules ([#17913](https://github.com/astral-sh/ruff/pull/17913)) + +### Bug fixes + +- \[`flake8-bugbear`\] Ignore `B028` if `skip_file_prefixes` is present ([#18047](https://github.com/astral-sh/ruff/pull/18047)) +- \[`flake8-pie`\] Mark autofix for `PIE804` as unsafe if the dictionary contains comments ([#18046](https://github.com/astral-sh/ruff/pull/18046)) +- \[`flake8-simplify`\] Correct behavior for `str.split`/`rsplit` with `maxsplit=0` (`SIM905`) ([#18075](https://github.com/astral-sh/ruff/pull/18075)) +- \[`flake8-simplify`\] Fix `SIM905` autofix for `rsplit` creating a reversed list literal ([#18045](https://github.com/astral-sh/ruff/pull/18045)) +- \[`flake8-use-pathlib`\] Suppress diagnostics for all `os.*` functions that have the `dir_fd` parameter (`PTH`) ([#17968](https://github.com/astral-sh/ruff/pull/17968)) +- \[`refurb`\] Mark autofix as safe only for number literals (`FURB116`) ([#17692](https://github.com/astral-sh/ruff/pull/17692)) + +### Rule changes + +- \[`flake8-bandit`\] Skip `S608` for expressionless f-strings ([#17999](https://github.com/astral-sh/ruff/pull/17999)) +- \[`flake8-pytest-style`\] Don't recommend `usefixtures` for `parametrize` values (`PT019`) ([#17650](https://github.com/astral-sh/ruff/pull/17650)) +- \[`pyupgrade`\] Add `resource.error` as deprecated alias of `OSError` (`UP024`) ([#17933](https://github.com/astral-sh/ruff/pull/17933)) + +### CLI + +- Disable jemalloc on Android ([#18033](https://github.com/astral-sh/ruff/pull/18033)) + +### Documentation + +- Update Neovim setup docs ([#18108](https://github.com/astral-sh/ruff/pull/18108)) +- \[`flake8-simplify`\] Add fix safety section (`SIM103`) ([#18086](https://github.com/astral-sh/ruff/pull/18086)) +- \[`flake8-simplify`\] Add fix safety section (`SIM112`) ([#18099](https://github.com/astral-sh/ruff/pull/18099)) +- \[`pylint`\] Add fix safety section (`PLC0414`) ([#17802](https://github.com/astral-sh/ruff/pull/17802)) +- \[`pylint`\] Add fix safety section (`PLE4703`) ([#17824](https://github.com/astral-sh/ruff/pull/17824)) +- \[`pylint`\] Add fix safety section (`PLW1514`) ([#17932](https://github.com/astral-sh/ruff/pull/17932)) +- \[`pylint`\] Add fix safety section (`PLW3301`) ([#17878](https://github.com/astral-sh/ruff/pull/17878)) +- \[`ruff`\] Add fix safety section (`RUF007`) ([#17755](https://github.com/astral-sh/ruff/pull/17755)) +- \[`ruff`\] Add fix safety section (`RUF033`) ([#17760](https://github.com/astral-sh/ruff/pull/17760)) + ## 0.11.9 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index 55cd4172335305..649c18be807d64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2561,7 +2561,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.11.9" +version = "0.11.10" dependencies = [ "anyhow", "argfile", @@ -2801,7 +2801,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.11.9" +version = "0.11.10" dependencies = [ "aho-corasick", "anyhow", @@ -3136,7 +3136,7 @@ dependencies = [ [[package]] name = "ruff_wasm" -version = "0.11.9" +version = "0.11.10" dependencies = [ "console_error_panic_hook", "console_log", diff --git a/README.md b/README.md index 37c5d02cbb073f..99612af0d0fc6f 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. -curl -LsSf https://astral.sh/ruff/0.11.9/install.sh | sh -powershell -c "irm https://astral.sh/ruff/0.11.9/install.ps1 | iex" +curl -LsSf https://astral.sh/ruff/0.11.10/install.sh | sh +powershell -c "irm https://astral.sh/ruff/0.11.10/install.ps1 | iex" ``` You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), @@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.9 + rev: v0.11.10 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 727726b3d14ee7..25f3ee7a06647a 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.11.9" +version = "0.11.10" publish = true authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 4986673ae54099..af3c527efb88fa 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.11.9" +version = "0.11.10" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index b760ab1d93bd63..15623862c32619 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_wasm" -version = "0.11.9" +version = "0.11.10" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/docs/integrations.md b/docs/integrations.md index ee937ab85d427f..7be48ce36a001d 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma stage: build interruptible: true image: - name: ghcr.io/astral-sh/ruff:0.11.9-alpine + name: ghcr.io/astral-sh/ruff:0.11.10-alpine before_script: - cd $CI_PROJECT_DIR - ruff --version @@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.9 + rev: v0.11.10 hooks: # Run the linter. - id: ruff @@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.9 + rev: v0.11.10 hooks: # Run the linter. - id: ruff @@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.9 + rev: v0.11.10 hooks: # Run the linter. - id: ruff diff --git a/docs/tutorial.md b/docs/tutorial.md index 37542eed4adf11..1e249e10c8c51f 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.9 + rev: v0.11.10 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 6ed1a46b98e191..b034bca36b7193 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.11.9" +version = "0.11.10" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index b7c1b41cec65a0..db8d6ca413070d 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scripts" -version = "0.11.9" +version = "0.11.10" description = "" authors = ["Charles Marsh "] From e2c5b83fe1ff53bc144b60ee97e3eca21ea72fd1 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 15 May 2025 10:27:21 -0400 Subject: [PATCH 111/487] Inline `DiagnosticKind` into other diagnostic types (#18074) ## Summary This PR deletes the `DiagnosticKind` type by inlining its three fields (`name`, `body`, and `suggestion`) into three other diagnostic types: `Diagnostic`, `DiagnosticMessage`, and `CacheMessage`. Instead of deferring to an internal `DiagnosticKind`, both `Diagnostic` and `DiagnosticMessage` now have their own macro-generated `AsRule` implementations. This should make both https://github.com/astral-sh/ruff/pull/18051 and another follow-up PR changing the type of `name` on `CacheMessage` easier since its type will be able to change separately from `Diagnostic` and `DiagnosticMessage`. ## Test Plan Existing tests --- Cargo.lock | 1 + Cargo.toml | 1 + crates/ruff/src/cache.rs | 18 +- crates/ruff_diagnostics/src/diagnostic.rs | 24 +- crates/ruff_diagnostics/src/lib.rs | 2 +- crates/ruff_diagnostics/src/violation.rs | 14 -- .../ruff_linter/src/checkers/logical_lines.rs | 9 +- crates/ruff_linter/src/checkers/noqa.rs | 16 +- crates/ruff_linter/src/checkers/tokens.rs | 2 +- crates/ruff_linter/src/fix/edits.rs | 4 +- crates/ruff_linter/src/fix/mod.rs | 31 +-- crates/ruff_linter/src/linter.rs | 6 +- crates/ruff_linter/src/message/mod.rs | 212 +++++++++--------- crates/ruff_linter/src/noqa.rs | 6 +- .../rules/blocking_process_invocation.rs | 56 ++--- .../rules/suspicious_function_call.rs | 65 +++--- .../flake8_bandit/rules/suspicious_imports.rs | 194 +++++++--------- .../function_call_in_argument_default.rs | 15 +- .../rules/flake8_print/rules/print_call.rs | 2 +- .../src/rules/flake8_return/rules/function.rs | 8 +- .../rules/typing_only_runtime_import.rs | 30 +-- .../rules/unused_arguments.rs | 20 +- .../rules/replaceable_by_pathlib.rs | 125 ++++++----- .../src/rules/pandas_vet/rules/call.rs | 16 +- .../src/rules/pandas_vet/rules/subscript.rs | 18 +- .../rules/invalid_first_argument_name.rs | 26 ++- .../rules/logical_lines/indentation.rs | 40 ++-- .../missing_whitespace_around_operator.rs | 53 +++-- crates/ruff_linter/src/rules/pyflakes/mod.rs | 2 +- .../pylint/rules/invalid_string_characters.rs | 24 +- .../rules/pylint/rules/no_method_decorator.rs | 17 +- .../pyupgrade/rules/use_pep604_annotation.rs | 12 +- .../ruff/rules/ambiguous_unicode_character.rs | 35 +-- crates/ruff_linter/src/test.rs | 4 +- crates/ruff_macros/Cargo.toml | 1 + crates/ruff_macros/src/kebab_case.rs | 15 +- crates/ruff_macros/src/lib.rs | 2 +- crates/ruff_macros/src/map_codes.rs | 25 ++- crates/ruff_macros/src/violation_metadata.rs | 2 +- crates/ruff_server/src/lint.rs | 20 +- crates/ruff_wasm/src/lib.rs | 48 ++-- 41 files changed, 602 insertions(+), 619 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 649c18be807d64..3dd545560f8884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2866,6 +2866,7 @@ dependencies = [ name = "ruff_macros" version = "0.0.0" dependencies = [ + "heck", "itertools 0.14.0", "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0114b8002713fb..27c02132d43a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ hashbrown = { version = "0.15.0", default-features = false, features = [ "equivalent", "inline-more", ] } +heck = "0.5.0" ignore = { version = "0.4.22" } imara-diff = { version = "0.1.5" } imperative = { version = "1.0.4" } diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 127f6292b2ab3b..99eed7cbafca67 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -13,12 +13,13 @@ use itertools::Itertools; use log::{debug, error}; use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelIterator, ParallelBridge}; +use ruff_linter::{codes::Rule, registry::AsRule}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use tempfile::NamedTempFile; use ruff_cache::{CacheKey, CacheKeyHasher}; -use ruff_diagnostics::{DiagnosticKind, Fix}; +use ruff_diagnostics::Fix; use ruff_linter::message::{DiagnosticMessage, Message}; use ruff_linter::package::PackageRoot; use ruff_linter::{warn_user, VERSION}; @@ -348,7 +349,9 @@ impl FileCache { .iter() .map(|msg| { Message::Diagnostic(DiagnosticMessage { - kind: msg.kind.clone(), + name: msg.rule.into(), + body: msg.body.clone(), + suggestion: msg.suggestion.clone(), range: msg.range, fix: msg.fix.clone(), file: file.clone(), @@ -444,7 +447,9 @@ impl LintCacheData { "message uses a different source file" ); CacheMessage { - kind: msg.kind.clone(), + rule: msg.rule(), + body: msg.body.clone(), + suggestion: msg.suggestion.clone(), range: msg.range, parent: msg.parent, fix: msg.fix.clone(), @@ -464,7 +469,12 @@ impl LintCacheData { /// On disk representation of a diagnostic message. #[derive(Deserialize, Debug, Serialize, PartialEq)] pub(super) struct CacheMessage { - kind: DiagnosticKind, + /// The rule for the cached diagnostic. + rule: Rule, + /// The message body to display to the user, to explain the diagnostic. + body: String, + /// The message to display to the user, to explain the suggested fix. + suggestion: Option, /// Range into the message's [`FileCache::source`]. range: TextRange, parent: Option, diff --git a/crates/ruff_diagnostics/src/diagnostic.rs b/crates/ruff_diagnostics/src/diagnostic.rs index be54a8de0e2475..f9a93c78f140ae 100644 --- a/crates/ruff_diagnostics/src/diagnostic.rs +++ b/crates/ruff_diagnostics/src/diagnostic.rs @@ -1,35 +1,29 @@ use anyhow::Result; use log::debug; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::Fix; +use crate::{Fix, Violation}; #[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct DiagnosticKind { +pub struct Diagnostic { /// The identifier of the diagnostic, used to align the diagnostic with a rule. - pub name: String, + pub name: &'static str, /// The message body to display to the user, to explain the diagnostic. pub body: String, /// The message to display to the user, to explain the suggested fix. pub suggestion: Option, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Diagnostic { - pub kind: DiagnosticKind, pub range: TextRange, pub fix: Option, pub parent: Option, } impl Diagnostic { - pub fn new>(kind: T, range: TextRange) -> Self { + pub fn new(kind: T, range: TextRange) -> Self { Self { - kind: kind.into(), + name: T::rule_name(), + body: Violation::message(&kind), + suggestion: Violation::fix_title(&kind), range, fix: None, parent: None, @@ -56,7 +50,7 @@ impl Diagnostic { pub fn try_set_fix(&mut self, func: impl FnOnce() -> Result) { match func() { Ok(fix) => self.fix = Some(fix), - Err(err) => debug!("Failed to create fix for {}: {}", self.kind.name, err), + Err(err) => debug!("Failed to create fix for {}: {}", self.name, err), } } @@ -67,7 +61,7 @@ impl Diagnostic { match func() { Ok(None) => {} Ok(Some(fix)) => self.fix = Some(fix), - Err(err) => debug!("Failed to create fix for {}: {}", self.kind.name, err), + Err(err) => debug!("Failed to create fix for {}: {}", self.name, err), } } diff --git a/crates/ruff_diagnostics/src/lib.rs b/crates/ruff_diagnostics/src/lib.rs index 75a9bec240c3b9..fd97fc14084722 100644 --- a/crates/ruff_diagnostics/src/lib.rs +++ b/crates/ruff_diagnostics/src/lib.rs @@ -1,4 +1,4 @@ -pub use diagnostic::{Diagnostic, DiagnosticKind}; +pub use diagnostic::Diagnostic; pub use edit::Edit; pub use fix::{Applicability, Fix, IsolationLevel}; pub use source_map::{SourceMap, SourceMarker}; diff --git a/crates/ruff_diagnostics/src/violation.rs b/crates/ruff_diagnostics/src/violation.rs index bf6690c5b28e46..d0c7b9c763cd92 100644 --- a/crates/ruff_diagnostics/src/violation.rs +++ b/crates/ruff_diagnostics/src/violation.rs @@ -1,4 +1,3 @@ -use crate::DiagnosticKind; use std::fmt::{Debug, Display}; #[derive(Debug, Copy, Clone)] @@ -79,16 +78,3 @@ impl Violation for V { ::message_formats() } } - -impl From for DiagnosticKind -where - T: Violation, -{ - fn from(value: T) -> Self { - Self { - body: Violation::message(&value), - suggestion: Violation::fix_title(&value), - name: T::rule_name().to_string(), - } - } -} diff --git a/crates/ruff_linter/src/checkers/logical_lines.rs b/crates/ruff_linter/src/checkers/logical_lines.rs index 1933889387b92e..42ed63a40408c2 100644 --- a/crates/ruff_linter/src/checkers/logical_lines.rs +++ b/crates/ruff_linter/src/checkers/logical_lines.rs @@ -169,16 +169,17 @@ pub(crate) fn check_logical_lines( let indent_size = 4; if enforce_indentation { - for kind in indentation( + for diagnostic in indentation( &line, prev_line.as_ref(), indent_char, indent_level, prev_indent_level, indent_size, + range, ) { - if settings.rules.enabled(kind.rule()) { - context.push_diagnostic(Diagnostic::new(kind, range)); + if settings.rules.enabled(diagnostic.rule()) { + context.push_diagnostic(diagnostic); } } } @@ -206,7 +207,7 @@ impl<'a> LogicalLinesContext<'a> { } pub(crate) fn push_diagnostic(&mut self, diagnostic: Diagnostic) { - if self.settings.rules.enabled(diagnostic.kind.rule()) { + if self.settings.rules.enabled(diagnostic.rule()) { self.diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index f765da9c91111c..0a0a8da90f819a 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -47,7 +47,7 @@ pub(crate) fn check_noqa( // Remove any ignored diagnostics. 'outer: for (index, diagnostic) in diagnostics.iter().enumerate() { - if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) { + if matches!(diagnostic.rule(), Rule::BlanketNOQA) { continue; } @@ -59,7 +59,7 @@ pub(crate) fn check_noqa( } FileExemption::Codes(codes) => { // If the diagnostic is ignored by a global exemption, ignore it. - if codes.contains(&&diagnostic.kind.rule().noqa_code()) { + if codes.contains(&&diagnostic.rule().noqa_code()) { ignored_diagnostics.push(index); continue; } @@ -78,17 +78,13 @@ pub(crate) fn check_noqa( { let suppressed = match &directive_line.directive { Directive::All(_) => { - directive_line - .matches - .push(diagnostic.kind.rule().noqa_code()); + directive_line.matches.push(diagnostic.rule().noqa_code()); ignored_diagnostics.push(index); true } Directive::Codes(directive) => { - if directive.includes(diagnostic.kind.rule()) { - directive_line - .matches - .push(diagnostic.kind.rule().noqa_code()); + if directive.includes(diagnostic.rule()) { + directive_line.matches.push(diagnostic.rule().noqa_code()); ignored_diagnostics.push(index); true } else { @@ -161,7 +157,7 @@ pub(crate) fn check_noqa( let is_code_used = if is_file_level { diagnostics .iter() - .any(|diag| diag.kind.rule().noqa_code() == code) + .any(|diag| diag.rule().noqa_code() == code) } else { matches.iter().any(|match_| *match_ == code) } || settings diff --git a/crates/ruff_linter/src/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index 7564d498e287b4..7fa2f8b8ee4314 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -184,7 +184,7 @@ pub(crate) fn check_tokens( ); } - diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())); + diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.rule())); diagnostics } diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 3d9f08d35bd417..ae226d6a94d22a 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -746,7 +746,9 @@ x = 1 \ iter, )); DiagnosticMessage { - kind: diag.kind, + name: diag.name, + body: diag.body, + suggestion: diag.suggestion, range: diag.range, fix: diag.fix, parent: diag.parent, diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index c0441c58e4760f..6a29c5cb5c5423 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -64,12 +64,7 @@ fn apply_fixes<'a>( let mut source_map = SourceMap::default(); for (rule, fix) in diagnostics - .filter_map(|diagnostic| { - diagnostic - .fix - .as_ref() - .map(|fix| (diagnostic.kind.rule(), fix)) - }) + .filter_map(|diagnostic| diagnostic.fix.as_ref().map(|fix| (diagnostic.rule(), fix))) .sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2)) { let mut edits = fix @@ -163,7 +158,7 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi #[cfg(test)] mod tests { - use ruff_diagnostics::{Edit, Fix, SourceMarker}; + use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker}; use ruff_source_file::SourceFileBuilder; use ruff_text_size::{Ranged, TextSize}; @@ -177,15 +172,21 @@ mod tests { source: &str, edit: impl IntoIterator, ) -> Vec { + // The choice of rule here is arbitrary. edit.into_iter() - .map(|edit| DiagnosticMessage { - // The choice of rule here is arbitrary. - kind: MissingNewlineAtEndOfFile.into(), - range: edit.range(), - fix: Some(Fix::safe_edit(edit)), - parent: None, - file: SourceFileBuilder::new(filename, source).finish(), - noqa_offset: TextSize::default(), + .map(|edit| { + let range = edit.range(); + let diagnostic = Diagnostic::new(MissingNewlineAtEndOfFile, range); + DiagnosticMessage { + name: diagnostic.name, + body: diagnostic.body, + suggestion: diagnostic.suggestion, + range, + fix: Some(Fix::safe_edit(edit)), + parent: None, + file: SourceFileBuilder::new(filename, source).finish(), + noqa_offset: TextSize::default(), + } }) .collect() } diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 7427b75d9c5cb7..58c0a3b2ee1c80 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -308,7 +308,7 @@ pub fn check_path( RuleSet::empty() }; if !per_file_ignores.is_empty() { - diagnostics.retain(|diagnostic| !per_file_ignores.contains(diagnostic.kind.rule())); + diagnostics.retain(|diagnostic| !per_file_ignores.contains(diagnostic.rule())); } // Enforce `noqa` directives. @@ -338,7 +338,7 @@ pub fn check_path( if parsed.has_valid_syntax() { // Remove fixes for any rules marked as unfixable. for diagnostic in &mut diagnostics { - if !settings.rules.should_fix(diagnostic.kind.rule()) { + if !settings.rules.should_fix(diagnostic.rule()) { diagnostic.fix = None; } } @@ -349,7 +349,7 @@ pub fn check_path( if let Some(fix) = diagnostic.fix.take() { let fixed_applicability = settings .fix_safety - .resolve_applicability(diagnostic.kind.rule(), fix.applicability()); + .resolve_applicability(diagnostic.rule(), fix.applicability()); diagnostic.set_fix(fix.with_applicability(fixed_applicability)); } } diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 9ca16bc2bea72d..8e1961d0256517 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -17,7 +17,7 @@ pub use json_lines::JsonLinesEmitter; pub use junit::JunitEmitter; pub use pylint::PylintEmitter; pub use rdjson::RdjsonEmitter; -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix}; +use ruff_diagnostics::{Diagnostic, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::{ParseError, UnsupportedSyntaxError}; use ruff_source_file::{LineColumn, SourceFile}; @@ -53,7 +53,9 @@ pub enum Message { /// A diagnostic message corresponding to a rule violation. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DiagnosticMessage { - pub kind: DiagnosticKind, + pub name: &'static str, + pub body: String, + pub suggestion: Option, pub range: TextRange, pub fix: Option, pub parent: Option, @@ -96,7 +98,9 @@ impl Message { ) -> Message { Message::Diagnostic(DiagnosticMessage { range: diagnostic.range(), - kind: diagnostic.kind, + name: diagnostic.name, + body: diagnostic.body, + suggestion: diagnostic.suggestion, fix: diagnostic.fix, parent: diagnostic.parent, file, @@ -183,7 +187,7 @@ impl Message { /// Returns a message kind. pub fn kind(&self) -> MessageKind { match self { - Message::Diagnostic(m) => MessageKind::Diagnostic(m.kind.rule()), + Message::Diagnostic(m) => MessageKind::Diagnostic(m.rule()), Message::SyntaxError(_) => MessageKind::SyntaxError, } } @@ -191,7 +195,7 @@ impl Message { /// Returns the name used to represent the diagnostic. pub fn name(&self) -> &str { match self { - Message::Diagnostic(m) => &m.kind.name, + Message::Diagnostic(m) => m.name, Message::SyntaxError(_) => "SyntaxError", } } @@ -199,7 +203,7 @@ impl Message { /// Returns the message body to display to the user. pub fn body(&self) -> &str { match self { - Message::Diagnostic(m) => &m.kind.body, + Message::Diagnostic(m) => &m.body, Message::SyntaxError(m) => m .primary_annotation() .expect("Expected a primary annotation for a ruff diagnostic") @@ -211,7 +215,7 @@ impl Message { /// Returns the fix suggestion for the violation. pub fn suggestion(&self) -> Option<&str> { match self { - Message::Diagnostic(m) => m.kind.suggestion.as_deref(), + Message::Diagnostic(m) => m.suggestion.as_deref(), Message::SyntaxError(_) => None, } } @@ -240,7 +244,7 @@ impl Message { /// Returns the [`Rule`] corresponding to the diagnostic message. pub fn rule(&self) -> Option { match self { - Message::Diagnostic(m) => Some(m.kind.rule()), + Message::Diagnostic(m) => Some(m.rule()), Message::SyntaxError(_) => None, } } @@ -379,13 +383,13 @@ impl<'a> EmitterContext<'a> { mod tests { use rustc_hash::FxHashMap; - use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix}; + use ruff_diagnostics::{Edit, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::{parse_unchecked, Mode, ParseOptions}; use ruff_source_file::{OneIndexed, SourceFileBuilder}; - use ruff_text_size::{Ranged, TextRange, TextSize}; + use ruff_text_size::{TextRange, TextSize}; - use crate::message::{Emitter, EmitterContext, Message}; + use crate::message::{DiagnosticMessage, Emitter, EmitterContext, Message}; use crate::Locator; pub(super) fn create_syntax_error_messages() -> Vec { @@ -421,54 +425,56 @@ def fibonacci(n): return fibonacci(n - 1) + fibonacci(n - 2) "#; - let unused_import = Diagnostic::new( - DiagnosticKind { - name: "UnusedImport".to_string(), - body: "`os` imported but unused".to_string(), - suggestion: Some("Remove unused import: `os`".to_string()), - }, - TextRange::new(TextSize::from(7), TextSize::from(9)), - ) - .with_fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new( - TextSize::from(0), - TextSize::from(10), - )))); - let fib_source = SourceFileBuilder::new("fib.py", fib).finish(); - let unused_variable = Diagnostic::new( - DiagnosticKind { - name: "UnusedVariable".to_string(), - body: "Local variable `x` is assigned to but never used".to_string(), - suggestion: Some("Remove assignment to unused variable `x`".to_string()), - }, - TextRange::new(TextSize::from(94), TextSize::from(95)), - ) - .with_fix(Fix::unsafe_edit(Edit::deletion( - TextSize::from(94), - TextSize::from(99), - ))); + let unused_import_start = TextSize::from(7); + let unused_import = DiagnosticMessage { + name: "unused-import", + body: "`os` imported but unused".to_string(), + suggestion: Some("Remove unused import: `os`".to_string()), + range: TextRange::new(unused_import_start, TextSize::from(9)), + fix: Some(Fix::unsafe_edit(Edit::range_deletion(TextRange::new( + TextSize::from(0), + TextSize::from(10), + )))), + parent: None, + noqa_offset: unused_import_start, + file: fib_source.clone(), + }; + + let unused_variable_start = TextSize::from(94); + let unused_variable = DiagnosticMessage { + name: "unused-variable", + body: "Local variable `x` is assigned to but never used".to_string(), + suggestion: Some("Remove assignment to unused variable `x`".to_string()), + range: TextRange::new(unused_variable_start, TextSize::from(95)), + fix: Some(Fix::unsafe_edit(Edit::deletion( + TextSize::from(94), + TextSize::from(99), + ))), + parent: None, + noqa_offset: unused_variable_start, + file: fib_source, + }; let file_2 = r"if a == 1: pass"; - let undefined_name = Diagnostic::new( - DiagnosticKind { - name: "UndefinedName".to_string(), - body: "Undefined name `a`".to_string(), - suggestion: None, - }, - TextRange::new(TextSize::from(3), TextSize::from(4)), - ); - - let file_2_source = SourceFileBuilder::new("undef.py", file_2).finish(); + let undefined_name_start = TextSize::from(3); + let undefined_name = DiagnosticMessage { + name: "undefined-name", + body: "Undefined name `a`".to_string(), + suggestion: None, + range: TextRange::new(undefined_name_start, TextSize::from(4)), + fix: None, + parent: None, + noqa_offset: undefined_name_start, + file: SourceFileBuilder::new("undef.py", file_2).finish(), + }; - let unused_import_start = unused_import.start(); - let unused_variable_start = unused_variable.start(); - let undefined_name_start = undefined_name.start(); vec![ - Message::from_diagnostic(unused_import, fib_source.clone(), unused_import_start), - Message::from_diagnostic(unused_variable, fib_source, unused_variable_start), - Message::from_diagnostic(undefined_name, file_2_source, undefined_name_start), + Message::Diagnostic(unused_import), + Message::Diagnostic(unused_variable), + Message::Diagnostic(undefined_name), ] } @@ -485,47 +491,53 @@ def foo(): x = 1 "; - let unused_import_os = Diagnostic::new( - DiagnosticKind { - name: "UnusedImport".to_string(), - body: "`os` imported but unused".to_string(), - suggestion: Some("Remove unused import: `os`".to_string()), - }, - TextRange::new(TextSize::from(16), TextSize::from(18)), - ) - .with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new( - TextSize::from(9), - TextSize::from(19), - )))); - - let unused_import_math = Diagnostic::new( - DiagnosticKind { - name: "UnusedImport".to_string(), - body: "`math` imported but unused".to_string(), - suggestion: Some("Remove unused import: `math`".to_string()), - }, - TextRange::new(TextSize::from(35), TextSize::from(39)), - ) - .with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new( - TextSize::from(28), - TextSize::from(40), - )))); - - let unused_variable = Diagnostic::new( - DiagnosticKind { - name: "UnusedVariable".to_string(), - body: "Local variable `x` is assigned to but never used".to_string(), - suggestion: Some("Remove assignment to unused variable `x`".to_string()), - }, - TextRange::new(TextSize::from(98), TextSize::from(99)), - ) - .with_fix(Fix::unsafe_edit(Edit::deletion( - TextSize::from(94), - TextSize::from(104), - ))); - let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish(); + let unused_import_os_start = TextSize::from(16); + let unused_import_os = DiagnosticMessage { + name: "unused-import", + body: "`os` imported but unused".to_string(), + suggestion: Some("Remove unused import: `os`".to_string()), + range: TextRange::new(unused_import_os_start, TextSize::from(18)), + fix: Some(Fix::safe_edit(Edit::range_deletion(TextRange::new( + TextSize::from(9), + TextSize::from(19), + )))), + parent: None, + file: notebook_source.clone(), + noqa_offset: unused_import_os_start, + }; + + let unused_import_math_start = TextSize::from(35); + let unused_import_math = DiagnosticMessage { + name: "unused-import", + body: "`math` imported but unused".to_string(), + suggestion: Some("Remove unused import: `math`".to_string()), + range: TextRange::new(unused_import_math_start, TextSize::from(39)), + fix: Some(Fix::safe_edit(Edit::range_deletion(TextRange::new( + TextSize::from(28), + TextSize::from(40), + )))), + parent: None, + file: notebook_source.clone(), + noqa_offset: unused_import_math_start, + }; + + let unused_variable_start = TextSize::from(98); + let unused_variable = DiagnosticMessage { + name: "unused-variable", + body: "Local variable `x` is assigned to but never used".to_string(), + suggestion: Some("Remove assignment to unused variable `x`".to_string()), + range: TextRange::new(unused_variable_start, TextSize::from(99)), + fix: Some(Fix::unsafe_edit(Edit::deletion( + TextSize::from(94), + TextSize::from(104), + ))), + parent: None, + file: notebook_source, + noqa_offset: unused_variable_start, + }; + let mut notebook_indexes = FxHashMap::default(); notebook_indexes.insert( "notebook.ipynb".to_string(), @@ -557,23 +569,11 @@ def foo(): ), ); - let unused_import_os_start = unused_import_os.start(); - let unused_import_math_start = unused_import_math.start(); - let unused_variable_start = unused_variable.start(); - ( vec![ - Message::from_diagnostic( - unused_import_os, - notebook_source.clone(), - unused_import_os_start, - ), - Message::from_diagnostic( - unused_import_math, - notebook_source.clone(), - unused_import_math_start, - ), - Message::from_diagnostic(unused_variable, notebook_source, unused_variable_start), + Message::Diagnostic(unused_import_os), + Message::Diagnostic(unused_import_math), + Message::Diagnostic(unused_variable), ], notebook_indexes, ) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index e4875c0f87e6ae..bda945e3209315 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -855,7 +855,7 @@ fn find_noqa_comments<'a>( } FileExemption::Codes(codes) => { // If the diagnostic is ignored by a global exemption, don't add a noqa directive. - if codes.contains(&&diagnostic.kind.rule().noqa_code()) { + if codes.contains(&&diagnostic.rule().noqa_code()) { comments_by_line.push(None); continue; } @@ -873,7 +873,7 @@ fn find_noqa_comments<'a>( continue; } Directive::Codes(codes) => { - if codes.includes(diagnostic.kind.rule()) { + if codes.includes(diagnostic.rule()) { comments_by_line.push(None); continue; } @@ -884,7 +884,7 @@ fn find_noqa_comments<'a>( let noqa_offset = noqa_line_for.resolve(diagnostic.range.start()); - let rule = diagnostic.kind.rule(); + let rule = diagnostic.rule(); // Or ignored by the directive itself? if let Some(directive_line) = directives.find_line_with_directive(noqa_offset) { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index 693a345ba66ed7..e363f702f1f805 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; +use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::analyze::typing::find_assigned_value; @@ -117,35 +117,37 @@ pub(crate) fn blocking_process_invocation(checker: &Checker, call: &ast::ExprCal return; } - let Some(diagnostic_kind) = - checker - .semantic() - .resolve_qualified_name(call.func.as_ref()) - .and_then(|qualified_name| match qualified_name.segments() { - ["subprocess", "Popen"] | ["os", "popen"] => { - Some(CreateSubprocessInAsyncFunction.into()) - } - ["os", "system" | "posix_spawn" | "posix_spawnp"] - | ["subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput" - | "getstatusoutput"] => Some(RunProcessInAsyncFunction.into()), - ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] => { - Some(WaitForProcessInAsyncFunction.into()) - } - ["os", "spawnl" | "spawnle" | "spawnlp" | "spawnlpe" | "spawnv" | "spawnve" - | "spawnvp" | "spawnvpe"] => { - if is_p_wait(call, checker.semantic()) { - Some(RunProcessInAsyncFunction.into()) - } else { - Some(CreateSubprocessInAsyncFunction.into()) - } - } - _ => None, - }) + let Some(qualified_name) = checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) else { return; }; - let diagnostic = Diagnostic::new::(diagnostic_kind, call.func.range()); - if checker.enabled(diagnostic.kind.rule()) { + + let range = call.func.range(); + let diagnostic = match qualified_name.segments() { + ["subprocess", "Popen"] | ["os", "popen"] => { + Diagnostic::new(CreateSubprocessInAsyncFunction, range) + } + ["os", "system" | "posix_spawn" | "posix_spawnp"] + | ["subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput" | "getstatusoutput"] => { + Diagnostic::new(RunProcessInAsyncFunction, range) + } + ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] => { + Diagnostic::new(WaitForProcessInAsyncFunction, range) + } + ["os", "spawnl" | "spawnle" | "spawnlp" | "spawnlpe" | "spawnv" | "spawnve" | "spawnvp" + | "spawnvpe"] => { + if is_p_wait(call, checker.semantic()) { + Diagnostic::new(RunProcessInAsyncFunction, range) + } else { + Diagnostic::new(CreateSubprocessInAsyncFunction, range) + } + } + _ => return, + }; + + if checker.enabled(diagnostic.rule()) { checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index dc1765f56e33d5..d066a39d9fb52b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -2,7 +2,7 @@ //! //! See: use itertools::Either; -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; +use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator}; use ruff_text_size::{Ranged, TextRange}; @@ -1035,39 +1035,39 @@ fn suspicious_function( return; }; - let diagnostic_kind: DiagnosticKind = match qualified_name.segments() { + let diagnostic = match qualified_name.segments() { // Pickle ["pickle" | "dill", "load" | "loads" | "Unpickler"] | ["shelve", "open" | "DbfilenameShelf"] | ["jsonpickle", "decode"] | ["jsonpickle", "unpickler", "decode"] - | ["pandas", "read_pickle"] => SuspiciousPickleUsage.into(), + | ["pandas", "read_pickle"] => Diagnostic::new(SuspiciousPickleUsage, range), // Marshal - ["marshal", "load" | "loads"] => SuspiciousMarshalUsage.into(), + ["marshal", "load" | "loads"] => Diagnostic::new(SuspiciousMarshalUsage, range), // InsecureHash ["Crypto" | "Cryptodome", "Hash", "SHA" | "MD2" | "MD3" | "MD4" | "MD5", "new"] | ["cryptography", "hazmat", "primitives", "hashes", "SHA1" | "MD5"] => { - SuspiciousInsecureHashUsage.into() + Diagnostic::new(SuspiciousInsecureHashUsage, range) } // InsecureCipher ["Crypto" | "Cryptodome", "Cipher", "ARC2" | "Blowfish" | "DES" | "XOR", "new"] | ["cryptography", "hazmat", "primitives", "ciphers", "algorithms", "ARC4" | "Blowfish" | "IDEA"] => { - SuspiciousInsecureCipherUsage.into() + Diagnostic::new(SuspiciousInsecureCipherUsage, range) } // InsecureCipherMode ["cryptography", "hazmat", "primitives", "ciphers", "modes", "ECB"] => { - SuspiciousInsecureCipherModeUsage.into() + Diagnostic::new(SuspiciousInsecureCipherModeUsage, range) } // Mktemp - ["tempfile", "mktemp"] => SuspiciousMktempUsage.into(), + ["tempfile", "mktemp"] => Diagnostic::new(SuspiciousMktempUsage, range), // Eval - ["" | "builtins", "eval"] => SuspiciousEvalUsage.into(), + ["" | "builtins", "eval"] => Diagnostic::new(SuspiciousEvalUsage, range), // MarkSafe ["django", "utils", "safestring" | "html", "mark_safe"] => { @@ -1078,7 +1078,7 @@ fn suspicious_function( } } } - SuspiciousMarkSafeUsage.into() + Diagnostic::new(SuspiciousMarkSafeUsage, range) } // URLOpen (`Request`) @@ -1100,7 +1100,7 @@ fn suspicious_function( } } } - SuspiciousURLOpenUsage.into() + Diagnostic::new(SuspiciousURLOpenUsage, range) } // URLOpen (`urlopen`, `urlretrieve`) @@ -1146,64 +1146,75 @@ fn suspicious_function( } } } - SuspiciousURLOpenUsage.into() + Diagnostic::new(SuspiciousURLOpenUsage, range) } // URLOpen (`URLopener`, `FancyURLopener`) ["urllib", "request", "URLopener" | "FancyURLopener"] | ["six", "moves", "urllib", "request", "URLopener" | "FancyURLopener"] => { - SuspiciousURLOpenUsage.into() + Diagnostic::new(SuspiciousURLOpenUsage, range) } // NonCryptographicRandom ["random", "Random" | "random" | "randrange" | "randint" | "choice" | "choices" | "uniform" - | "triangular" | "randbytes"] => SuspiciousNonCryptographicRandomUsage.into(), + | "triangular" | "randbytes"] => { + Diagnostic::new(SuspiciousNonCryptographicRandomUsage, range) + } // UnverifiedContext - ["ssl", "_create_unverified_context"] => SuspiciousUnverifiedContextUsage.into(), + ["ssl", "_create_unverified_context"] => { + Diagnostic::new(SuspiciousUnverifiedContextUsage, range) + } // XMLCElementTree ["xml", "etree", "cElementTree", "parse" | "iterparse" | "fromstring" | "XMLParser"] => { - SuspiciousXMLCElementTreeUsage.into() + Diagnostic::new(SuspiciousXMLCElementTreeUsage, range) } // XMLElementTree ["xml", "etree", "ElementTree", "parse" | "iterparse" | "fromstring" | "XMLParser"] => { - SuspiciousXMLElementTreeUsage.into() + Diagnostic::new(SuspiciousXMLElementTreeUsage, range) } // XMLExpatReader - ["xml", "sax", "expatreader", "create_parser"] => SuspiciousXMLExpatReaderUsage.into(), + ["xml", "sax", "expatreader", "create_parser"] => { + Diagnostic::new(SuspiciousXMLExpatReaderUsage, range) + } // XMLExpatBuilder ["xml", "dom", "expatbuilder", "parse" | "parseString"] => { - SuspiciousXMLExpatBuilderUsage.into() + Diagnostic::new(SuspiciousXMLExpatBuilderUsage, range) } // XMLSax - ["xml", "sax", "parse" | "parseString" | "make_parser"] => SuspiciousXMLSaxUsage.into(), + ["xml", "sax", "parse" | "parseString" | "make_parser"] => { + Diagnostic::new(SuspiciousXMLSaxUsage, range) + } // XMLMiniDOM - ["xml", "dom", "minidom", "parse" | "parseString"] => SuspiciousXMLMiniDOMUsage.into(), + ["xml", "dom", "minidom", "parse" | "parseString"] => { + Diagnostic::new(SuspiciousXMLMiniDOMUsage, range) + } // XMLPullDOM - ["xml", "dom", "pulldom", "parse" | "parseString"] => SuspiciousXMLPullDOMUsage.into(), + ["xml", "dom", "pulldom", "parse" | "parseString"] => { + Diagnostic::new(SuspiciousXMLPullDOMUsage, range) + } // XMLETree ["lxml", "etree", "parse" | "fromstring" | "RestrictedElement" | "GlobalParserTLS" | "getDefaultParser" - | "check_docinfo"] => SuspiciousXMLETreeUsage.into(), + | "check_docinfo"] => Diagnostic::new(SuspiciousXMLETreeUsage, range), // Telnet - ["telnetlib", ..] => SuspiciousTelnetUsage.into(), + ["telnetlib", ..] => Diagnostic::new(SuspiciousTelnetUsage, range), // FTPLib - ["ftplib", ..] => SuspiciousFTPLibUsage.into(), + ["ftplib", ..] => Diagnostic::new(SuspiciousFTPLibUsage, range), _ => return, }; - let diagnostic = Diagnostic::new(diagnostic_kind, range); - if checker.enabled(diagnostic.kind.rule()) { + if checker.enabled(diagnostic.rule()) { checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs index deb208863d6a7e..f6dd1b638488ce 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs @@ -1,7 +1,7 @@ //! Check for imports of or from suspicious modules. //! //! See: -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; +use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::{Ranged, TextRange}; @@ -361,76 +361,44 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { Stmt::Import(ast::StmtImport { names, .. }) => { for name in names { match name.name.as_str() { - "telnetlib" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousTelnetlibImport), - name.range, - ), - "ftplib" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousFtplibImport), - name.range, - ), - "pickle" | "cPickle" | "dill" | "shelve" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousPickleImport), - name.range, - ), - "subprocess" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousSubprocessImport), - name.range, - ), + "telnetlib" => { + check_and_push_diagnostic(checker, SuspiciousTelnetlibImport, name.range); + } + "ftplib" => { + check_and_push_diagnostic(checker, SuspiciousFtplibImport, name.range); + } + "pickle" | "cPickle" | "dill" | "shelve" => { + check_and_push_diagnostic(checker, SuspiciousPickleImport, name.range); + } + "subprocess" => { + check_and_push_diagnostic(checker, SuspiciousSubprocessImport, name.range); + } "xml.etree.cElementTree" | "xml.etree.ElementTree" => { - check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlEtreeImport), - name.range, - ); + check_and_push_diagnostic(checker, SuspiciousXmlEtreeImport, name.range); + } + "xml.sax" => { + check_and_push_diagnostic(checker, SuspiciousXmlSaxImport, name.range); + } + "xml.dom.expatbuilder" => { + check_and_push_diagnostic(checker, SuspiciousXmlExpatImport, name.range); + } + "xml.dom.minidom" => { + check_and_push_diagnostic(checker, SuspiciousXmlMinidomImport, name.range); + } + "xml.dom.pulldom" => { + check_and_push_diagnostic(checker, SuspiciousXmlPulldomImport, name.range); + } + "lxml" => check_and_push_diagnostic(checker, SuspiciousLxmlImport, name.range), + "xmlrpc" => { + check_and_push_diagnostic(checker, SuspiciousXmlrpcImport, name.range); } - "xml.sax" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlSaxImport), - name.range, - ), - "xml.dom.expatbuilder" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlExpatImport), - name.range, - ), - "xml.dom.minidom" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlMinidomImport), - name.range, - ), - "xml.dom.pulldom" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlPulldomImport), - name.range, - ), - "lxml" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousLxmlImport), - name.range, - ), - "xmlrpc" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlrpcImport), - name.range, - ), "Crypto.Cipher" | "Crypto.Hash" | "Crypto.IO" | "Crypto.Protocol" | "Crypto.PublicKey" | "Crypto.Random" | "Crypto.Signature" | "Crypto.Util" => { - check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousPycryptoImport), - name.range, - ); + check_and_push_diagnostic(checker, SuspiciousPycryptoImport, name.range); + } + "pyghmi" => { + check_and_push_diagnostic(checker, SuspiciousPyghmiImport, name.range); } - "pyghmi" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousPyghmiImport), - name.range, - ), _ => {} } } @@ -440,22 +408,18 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { match identifier.as_str() { "telnetlib" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousTelnetlibImport), - identifier.range(), - ), - "ftplib" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousFtplibImport), - identifier.range(), - ), - "pickle" | "cPickle" | "dill" | "shelve" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousPickleImport), + SuspiciousTelnetlibImport, identifier.range(), ), + "ftplib" => { + check_and_push_diagnostic(checker, SuspiciousFtplibImport, identifier.range()); + } + "pickle" | "cPickle" | "dill" | "shelve" => { + check_and_push_diagnostic(checker, SuspiciousPickleImport, identifier.range()); + } "subprocess" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousSubprocessImport), + SuspiciousSubprocessImport, identifier.range(), ), "xml.etree" => { @@ -463,7 +427,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { if matches!(name.name.as_str(), "cElementTree" | "ElementTree") { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlEtreeImport), + SuspiciousXmlEtreeImport, identifier.range(), ); } @@ -472,7 +436,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { "xml.etree.cElementTree" | "xml.etree.ElementTree" => { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlEtreeImport), + SuspiciousXmlEtreeImport, identifier.range(), ); } @@ -481,70 +445,66 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { if name.name.as_str() == "sax" { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlSaxImport), + SuspiciousXmlSaxImport, identifier.range(), ); } } } - "xml.sax" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlSaxImport), - identifier.range(), - ), + "xml.sax" => { + check_and_push_diagnostic(checker, SuspiciousXmlSaxImport, identifier.range()); + } "xml.dom" => { for name in names { match name.name.as_str() { "expatbuilder" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlExpatImport), + SuspiciousXmlExpatImport, identifier.range(), ), "minidom" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlMinidomImport), + SuspiciousXmlMinidomImport, identifier.range(), ), "pulldom" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlPulldomImport), + SuspiciousXmlPulldomImport, identifier.range(), ), _ => (), } } } - "xml.dom.expatbuilder" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlExpatImport), - identifier.range(), - ), + "xml.dom.expatbuilder" => { + check_and_push_diagnostic( + checker, + SuspiciousXmlExpatImport, + identifier.range(), + ); + } "xml.dom.minidom" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlMinidomImport), + SuspiciousXmlMinidomImport, identifier.range(), ), "xml.dom.pulldom" => check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousXmlPulldomImport), - identifier.range(), - ), - "lxml" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousLxmlImport), - identifier.range(), - ), - "xmlrpc" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousXmlrpcImport), + SuspiciousXmlPulldomImport, identifier.range(), ), + "lxml" => { + check_and_push_diagnostic(checker, SuspiciousLxmlImport, identifier.range()); + } + "xmlrpc" => { + check_and_push_diagnostic(checker, SuspiciousXmlrpcImport, identifier.range()); + } "wsgiref.handlers" => { for name in names { if name.name.as_str() == "CGIHandler" { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousHttpoxyImport), + SuspiciousHttpoxyImport, identifier.range(), ); } @@ -555,7 +515,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { if name.name.as_str() == "CGIScript" { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousHttpoxyImport), + SuspiciousHttpoxyImport, identifier.range(), ); } @@ -576,7 +536,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { ) { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousPycryptoImport), + SuspiciousPycryptoImport, identifier.range(), ); } @@ -586,15 +546,13 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { | "Crypto.PublicKey" | "Crypto.Random" | "Crypto.Signature" | "Crypto.Util" => { check_and_push_diagnostic( checker, - DiagnosticKind::from(SuspiciousPycryptoImport), + SuspiciousPycryptoImport, identifier.range(), ); } - "pyghmi" => check_and_push_diagnostic( - checker, - DiagnosticKind::from(SuspiciousPyghmiImport), - identifier.range(), - ), + "pyghmi" => { + check_and_push_diagnostic(checker, SuspiciousPyghmiImport, identifier.range()); + } _ => {} } } @@ -602,9 +560,9 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { } } -fn check_and_push_diagnostic(checker: &Checker, diagnostic_kind: DiagnosticKind, range: TextRange) { - let diagnostic = Diagnostic::new::(diagnostic_kind, range); - if checker.enabled(diagnostic.kind.rule()) { +fn check_and_push_diagnostic(checker: &Checker, diagnostic: T, range: TextRange) { + let diagnostic = Diagnostic::new(diagnostic, range); + if checker.enabled(diagnostic.rule()) { checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs index 74816eba634a88..3cd9f00561777a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs @@ -1,8 +1,8 @@ use ruff_python_ast::{self as ast, Expr, Parameters}; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::Ranged; +use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; -use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_ast::visitor; @@ -83,7 +83,7 @@ impl Violation for FunctionCallInDefaultArgument { struct ArgumentDefaultVisitor<'a, 'b> { semantic: &'a SemanticModel<'b>, extend_immutable_calls: &'a [QualifiedName<'b>], - diagnostics: Vec<(DiagnosticKind, TextRange)>, + diagnostics: Vec, } impl<'a, 'b> ArgumentDefaultVisitor<'a, 'b> { @@ -109,11 +109,10 @@ impl Visitor<'_> for ArgumentDefaultVisitor<'_, '_> { is_immutable_newtype_call(name, self.semantic, self.extend_immutable_calls) }) { - self.diagnostics.push(( + self.diagnostics.push(Diagnostic::new( FunctionCallInDefaultArgument { name: UnqualifiedName::from_expr(func).map(|name| name.to_string()), - } - .into(), + }, expr.range(), )); } @@ -149,7 +148,5 @@ pub(crate) fn function_call_in_argument_default(checker: &Checker, parameters: & } } - for (check, range) in visitor.diagnostics { - checker.report_diagnostic(Diagnostic::new(check, range)); - } + checker.report_diagnostics(visitor.diagnostics); } diff --git a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs index ec895762912edd..b1521475383c88 100644 --- a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs @@ -124,7 +124,7 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { _ => return, }; - if !checker.enabled(diagnostic.kind.rule()) { + if !checker.enabled(diagnostic.rule()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index aa91da5e7409d0..d8d43d810a9838 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -674,7 +674,7 @@ fn superfluous_else_node( elif_else_range(elif_else, checker.locator().contents()) .unwrap_or_else(|| elif_else.range()), ); - if checker.enabled(diagnostic.kind.rule()) { + if checker.enabled(diagnostic.rule()) { diagnostic.try_set_fix(|| { remove_else( elif_else, @@ -692,7 +692,7 @@ fn superfluous_else_node( elif_else_range(elif_else, checker.locator().contents()) .unwrap_or_else(|| elif_else.range()), ); - if checker.enabled(diagnostic.kind.rule()) { + if checker.enabled(diagnostic.rule()) { diagnostic.try_set_fix(|| { remove_else( elif_else, @@ -711,7 +711,7 @@ fn superfluous_else_node( elif_else_range(elif_else, checker.locator().contents()) .unwrap_or_else(|| elif_else.range()), ); - if checker.enabled(diagnostic.kind.rule()) { + if checker.enabled(diagnostic.rule()) { diagnostic.try_set_fix(|| { remove_else( elif_else, @@ -730,7 +730,7 @@ fn superfluous_else_node( elif_else_range(elif_else, checker.locator().contents()) .unwrap_or_else(|| elif_else.range()), ); - if checker.enabled(diagnostic.kind.rule()) { + if checker.enabled(diagnostic.rule()) { diagnostic.try_set_fix(|| { remove_else( elif_else, diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index 2ddafcc71e6d79..957cd80fcaa4de 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -3,10 +3,10 @@ use std::borrow::Cow; use anyhow::Result; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_semantic::{Binding, Imported, NodeId, Scope}; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::codes::Rule; @@ -393,10 +393,8 @@ pub(crate) fn typing_only_runtime_import( .. } in imports { - let mut diagnostic = Diagnostic::new( - diagnostic_for(import_type, import.qualified_name().to_string()), - range, - ); + let mut diagnostic = + diagnostic_for(import_type, import.qualified_name().to_string(), range); if let Some(range) = parent_range { diagnostic.set_parent(range.start()); } @@ -417,10 +415,8 @@ pub(crate) fn typing_only_runtime_import( .. } in imports { - let mut diagnostic = Diagnostic::new( - diagnostic_for(import_type, import.qualified_name().to_string()), - range, - ); + let mut diagnostic = + diagnostic_for(import_type, import.qualified_name().to_string(), range); if let Some(range) = parent_range { diagnostic.set_parent(range.start()); } @@ -440,11 +436,17 @@ fn rule_for(import_type: ImportType) -> Rule { } /// Return the [`Diagnostic`] for the given import type. -fn diagnostic_for(import_type: ImportType, qualified_name: String) -> DiagnosticKind { +fn diagnostic_for(import_type: ImportType, qualified_name: String, range: TextRange) -> Diagnostic { match import_type { - ImportType::StandardLibrary => TypingOnlyStandardLibraryImport { qualified_name }.into(), - ImportType::ThirdParty => TypingOnlyThirdPartyImport { qualified_name }.into(), - ImportType::FirstParty => TypingOnlyFirstPartyImport { qualified_name }.into(), + ImportType::StandardLibrary => { + Diagnostic::new(TypingOnlyStandardLibraryImport { qualified_name }, range) + } + ImportType::ThirdParty => { + Diagnostic::new(TypingOnlyThirdPartyImport { qualified_name }, range) + } + ImportType::FirstParty => { + Diagnostic::new(TypingOnlyFirstPartyImport { qualified_name }, range) + } _ => unreachable!("Unexpected import type"), } } diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs index a8ceee48b88810..333cd73adfa5f6 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs @@ -1,12 +1,11 @@ use ruff_python_ast as ast; use ruff_python_ast::{Parameter, Parameters, Stmt, StmtExpr, StmtFunctionDef, StmtRaise}; -use ruff_diagnostics::DiagnosticKind; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_semantic::analyze::{function_type, visibility}; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::registry::Rule; @@ -223,13 +222,13 @@ enum Argumentable { } impl Argumentable { - fn check_for(self, name: String) -> DiagnosticKind { + fn check_for(self, name: String, range: TextRange) -> Diagnostic { match self { - Self::Function => UnusedFunctionArgument { name }.into(), - Self::Method => UnusedMethodArgument { name }.into(), - Self::ClassMethod => UnusedClassMethodArgument { name }.into(), - Self::StaticMethod => UnusedStaticMethodArgument { name }.into(), - Self::Lambda => UnusedLambdaArgument { name }.into(), + Self::Function => Diagnostic::new(UnusedFunctionArgument { name }, range), + Self::Method => Diagnostic::new(UnusedMethodArgument { name }, range), + Self::ClassMethod => Diagnostic::new(UnusedClassMethodArgument { name }, range), + Self::StaticMethod => Diagnostic::new(UnusedStaticMethodArgument { name }, range), + Self::Lambda => Diagnostic::new(UnusedLambdaArgument { name }, range), } } @@ -313,10 +312,7 @@ fn call<'a>( && binding.is_unused() && !dummy_variable_rgx.is_match(arg.name()) { - Some(Diagnostic::new( - argumentable.check_for(arg.name.to_string()), - binding.range(), - )) + Some(argumentable.check_for(arg.name.to_string(), binding.range())) } else { None } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index bf1b3c0661521e..1a2e97067ee7c7 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, DiagnosticKind}; +use ruff_diagnostics::Diagnostic; use ruff_python_ast::{self as ast, Expr, ExprBooleanLiteral, ExprCall}; use ruff_python_semantic::analyze::typing; use ruff_python_semantic::SemanticModel; @@ -22,9 +22,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { return; }; - let diagnostic_kind: DiagnosticKind = match qualified_name.segments() { + let range = call.func.range(); + let diagnostic = match qualified_name.segments() { // PTH100 - ["os", "path", "abspath"] => OsPathAbspath.into(), + ["os", "path", "abspath"] => Diagnostic::new(OsPathAbspath, range), // PTH101 ["os", "chmod"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -41,10 +42,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - OsChmod.into() + Diagnostic::new(OsChmod, range) } // PTH102 - ["os", "makedirs"] => OsMakedirs.into(), + ["os", "makedirs"] => Diagnostic::new(OsMakedirs, range), // PTH103 ["os", "mkdir"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -56,7 +57,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_argument_non_default(&call.arguments, "dir_fd", 2) { return; } - OsMkdir.into() + Diagnostic::new(OsMkdir, range) } // PTH104 ["os", "rename"] => { @@ -72,7 +73,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - OsRename.into() + Diagnostic::new(OsRename, range) } // PTH105 ["os", "replace"] => { @@ -88,7 +89,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - OsReplace.into() + Diagnostic::new(OsReplace, range) } // PTH106 ["os", "rmdir"] => { @@ -101,7 +102,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_argument_non_default(&call.arguments, "dir_fd", 1) { return; } - OsRmdir.into() + Diagnostic::new(OsRmdir, range) } // PTH107 ["os", "remove"] => { @@ -114,7 +115,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_argument_non_default(&call.arguments, "dir_fd", 1) { return; } - OsRemove.into() + Diagnostic::new(OsRemove, range) } // PTH108 ["os", "unlink"] => { @@ -127,21 +128,21 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_argument_non_default(&call.arguments, "dir_fd", 1) { return; } - OsUnlink.into() + Diagnostic::new(OsUnlink, range) } // PTH109 - ["os", "getcwd"] => OsGetcwd.into(), - ["os", "getcwdb"] => OsGetcwd.into(), + ["os", "getcwd"] => Diagnostic::new(OsGetcwd, range), + ["os", "getcwdb"] => Diagnostic::new(OsGetcwd, range), // PTH110 - ["os", "path", "exists"] => OsPathExists.into(), + ["os", "path", "exists"] => Diagnostic::new(OsPathExists, range), // PTH111 - ["os", "path", "expanduser"] => OsPathExpanduser.into(), + ["os", "path", "expanduser"] => Diagnostic::new(OsPathExpanduser, range), // PTH112 - ["os", "path", "isdir"] => OsPathIsdir.into(), + ["os", "path", "isdir"] => Diagnostic::new(OsPathIsdir, range), // PTH113 - ["os", "path", "isfile"] => OsPathIsfile.into(), + ["os", "path", "isfile"] => Diagnostic::new(OsPathIsfile, range), // PTH114 - ["os", "path", "islink"] => OsPathIslink.into(), + ["os", "path", "islink"] => Diagnostic::new(OsPathIslink, range), // PTH116 ["os", "stat"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -158,45 +159,49 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - OsStat.into() + Diagnostic::new(OsStat, range) } // PTH117 - ["os", "path", "isabs"] => OsPathIsabs.into(), + ["os", "path", "isabs"] => Diagnostic::new(OsPathIsabs, range), // PTH118 - ["os", "path", "join"] => OsPathJoin { - module: "path".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash + ["os", "path", "join"] => Diagnostic::new( + OsPathJoin { + module: "path".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, }, - } - .into(), - ["os", "sep", "join"] => OsPathJoin { - module: "sep".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash + range, + ), + ["os", "sep", "join"] => Diagnostic::new( + OsPathJoin { + module: "sep".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, }, - } - .into(), + range, + ), // PTH119 - ["os", "path", "basename"] => OsPathBasename.into(), + ["os", "path", "basename"] => Diagnostic::new(OsPathBasename, range), // PTH120 - ["os", "path", "dirname"] => OsPathDirname.into(), + ["os", "path", "dirname"] => Diagnostic::new(OsPathDirname, range), // PTH121 - ["os", "path", "samefile"] => OsPathSamefile.into(), + ["os", "path", "samefile"] => Diagnostic::new(OsPathSamefile, range), // PTH122 - ["os", "path", "splitext"] => OsPathSplitext.into(), + ["os", "path", "splitext"] => Diagnostic::new(OsPathSplitext, range), // PTH202 - ["os", "path", "getsize"] => OsPathGetsize.into(), + ["os", "path", "getsize"] => Diagnostic::new(OsPathGetsize, range), // PTH203 - ["os", "path", "getatime"] => OsPathGetatime.into(), + ["os", "path", "getatime"] => Diagnostic::new(OsPathGetatime, range), // PTH204 - ["os", "path", "getmtime"] => OsPathGetmtime.into(), + ["os", "path", "getmtime"] => Diagnostic::new(OsPathGetmtime, range), // PTH205 - ["os", "path", "getctime"] => OsPathGetctime.into(), + ["os", "path", "getctime"] => Diagnostic::new(OsPathGetctime, range), // PTH123 ["" | "builtins", "open"] => { // `closefd` and `opener` are not supported by pathlib, so check if they are @@ -231,10 +236,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - BuiltinOpen.into() + Diagnostic::new(BuiltinOpen, range) } // PTH124 - ["py", "path", "local"] => PyPath.into(), + ["py", "path", "local"] => Diagnostic::new(PyPath, range), // PTH207 ["glob", "glob"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -247,10 +252,12 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { return; } - Glob { - function: "glob".to_string(), - } - .into() + Diagnostic::new( + Glob { + function: "glob".to_string(), + }, + range, + ) } ["glob", "iglob"] => { @@ -264,10 +271,12 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { return; } - Glob { - function: "iglob".to_string(), - } - .into() + Diagnostic::new( + Glob { + function: "iglob".to_string(), + }, + range, + ) } // PTH115 // Python 3.9+ @@ -281,7 +290,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_argument_non_default(&call.arguments, "dir_fd", 1) { return; } - OsReadlink.into() + Diagnostic::new(OsReadlink, range) } // PTH208 ["os", "listdir"] => { @@ -292,13 +301,13 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - OsListdir.into() + Diagnostic::new(OsListdir, range) } _ => return, }; - if checker.enabled(diagnostic_kind.rule()) { - checker.report_diagnostic(Diagnostic::new(diagnostic_kind, call.func.range())); + if checker.enabled(diagnostic.rule()) { + checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs index ce69acc79067cd..7eb242bb10430c 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr}; +use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; -use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -171,12 +171,14 @@ pub(crate) fn call(checker: &Checker, func: &Expr) { let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func else { return; }; - let violation: DiagnosticKind = match attr.as_str() { + + let range = func.range(); + let diagnostic = match attr.as_str() { "isnull" if checker.settings.rules.enabled(Rule::PandasUseOfDotIsNull) => { - PandasUseOfDotIsNull.into() + Diagnostic::new(PandasUseOfDotIsNull, range) } "notnull" if checker.settings.rules.enabled(Rule::PandasUseOfDotNotNull) => { - PandasUseOfDotNotNull.into() + Diagnostic::new(PandasUseOfDotNotNull, range) } "pivot" | "unstack" if checker @@ -184,10 +186,10 @@ pub(crate) fn call(checker: &Checker, func: &Expr) { .rules .enabled(Rule::PandasUseOfDotPivotOrUnstack) => { - PandasUseOfDotPivotOrUnstack.into() + Diagnostic::new(PandasUseOfDotPivotOrUnstack, range) } "stack" if checker.settings.rules.enabled(Rule::PandasUseOfDotStack) => { - PandasUseOfDotStack.into() + Diagnostic::new(PandasUseOfDotStack, range) } _ => return, }; @@ -200,5 +202,5 @@ pub(crate) fn call(checker: &Checker, func: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new(violation, func.range())); + checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs index 4831569b1f172e..f6497b3cf9daaf 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr}; +use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; -use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -152,11 +152,17 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, expr: &Expr) { return; }; - let violation: DiagnosticKind = match attr.as_str() { - "ix" if checker.settings.rules.enabled(Rule::PandasUseOfDotIx) => PandasUseOfDotIx.into(), - "at" if checker.settings.rules.enabled(Rule::PandasUseOfDotAt) => PandasUseOfDotAt.into(), + let range = expr.range(); + + let diagnostic = match attr.as_str() { + "ix" if checker.settings.rules.enabled(Rule::PandasUseOfDotIx) => { + Diagnostic::new(PandasUseOfDotIx, range) + } + "at" if checker.settings.rules.enabled(Rule::PandasUseOfDotAt) => { + Diagnostic::new(PandasUseOfDotAt, range) + } "iat" if checker.settings.rules.enabled(Rule::PandasUseOfDotIat) => { - PandasUseOfDotIat.into() + Diagnostic::new(PandasUseOfDotIat, range) } _ => return, }; @@ -170,5 +176,5 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, expr: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new(violation, expr.range())); + checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index f09e0b476a6e50..7c9ac5c9fecc05 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix, Violation}; +use ruff_diagnostics::{Diagnostic, Fix, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; @@ -8,7 +8,7 @@ use ruff_python_codegen::Stylist; use ruff_python_semantic::analyze::class::{is_metaclass, IsMetaclass}; use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::registry::Rule; @@ -167,14 +167,18 @@ enum FunctionType { } impl FunctionType { - fn diagnostic_kind(self, argument_name: String) -> DiagnosticKind { + fn diagnostic_kind(self, argument_name: String, range: TextRange) -> Diagnostic { match self { - Self::Method => InvalidFirstArgumentNameForMethod { argument_name }.into(), - Self::ClassMethod => InvalidFirstArgumentNameForClassMethod { - argument_name, - is_new: false, + Self::Method => { + Diagnostic::new(InvalidFirstArgumentNameForMethod { argument_name }, range) } - .into(), + Self::ClassMethod => Diagnostic::new( + InvalidFirstArgumentNameForClassMethod { + argument_name, + is_new: false, + }, + range, + ), } } @@ -262,10 +266,8 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { return; } - let mut diagnostic = Diagnostic::new( - function_type.diagnostic_kind(self_or_cls.name.to_string()), - self_or_cls.range(), - ); + let mut diagnostic = + function_type.diagnostic_kind(self_or_cls.name.to_string(), self_or_cls.range()); diagnostic.try_set_optional_fix(|| { rename_parameter( scope, diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs index c5f89257842d21..dc54a67f3ee494 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs @@ -1,7 +1,8 @@ -use ruff_diagnostics::DiagnosticKind; +use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_parser::TokenKind; +use ruff_text_size::TextRange; use super::LogicalLine; @@ -261,18 +262,25 @@ pub(crate) fn indentation( indent_level: usize, prev_indent_level: Option, indent_size: usize, -) -> Vec { + range: TextRange, +) -> Vec { let mut diagnostics = vec![]; if indent_level % indent_size != 0 { diagnostics.push(if logical_line.is_comment_only() { - DiagnosticKind::from(IndentationWithInvalidMultipleComment { - indent_width: indent_size, - }) + Diagnostic::new( + IndentationWithInvalidMultipleComment { + indent_width: indent_size, + }, + range, + ) } else { - DiagnosticKind::from(IndentationWithInvalidMultiple { - indent_width: indent_size, - }) + Diagnostic::new( + IndentationWithInvalidMultiple { + indent_width: indent_size, + }, + range, + ) }); } let indent_expect = prev_logical_line @@ -281,29 +289,29 @@ pub(crate) fn indentation( if indent_expect && indent_level <= prev_indent_level.unwrap_or(0) { diagnostics.push(if logical_line.is_comment_only() { - DiagnosticKind::from(NoIndentedBlockComment) + Diagnostic::new(NoIndentedBlockComment, range) } else { - DiagnosticKind::from(NoIndentedBlock) + Diagnostic::new(NoIndentedBlock, range) }); } else if !indent_expect && prev_indent_level.is_some_and(|prev_indent_level| indent_level > prev_indent_level) { diagnostics.push(if logical_line.is_comment_only() { - DiagnosticKind::from(UnexpectedIndentationComment) + Diagnostic::new(UnexpectedIndentationComment, range) } else { - DiagnosticKind::from(UnexpectedIndentation) + Diagnostic::new(UnexpectedIndentation, range) }); } if indent_expect { let expected_indent_amount = if indent_char == '\t' { 8 } else { 4 }; let expected_indent_level = prev_indent_level.unwrap_or(0) + expected_indent_amount; if indent_level > expected_indent_level { - diagnostics.push( + diagnostics.push(Diagnostic::new( OverIndented { is_comment: logical_line.is_comment_only(), - } - .into(), - ); + }, + range, + )); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs index 03b8e16c4dab77..69171396fe3ac6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, DiagnosticKind, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_parser::TokenKind; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::helpers::is_non_logical_token; @@ -252,34 +252,31 @@ pub(crate) fn missing_whitespace_around_operator( match (has_leading_trivia, has_trailing_trivia) { // Operator with trailing but no leading space, enforce consistent spacing. (false, true) => { - let mut diagnostic = - Diagnostic::new(diagnostic_kind_for_operator(kind), token.range()); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - " ".to_string(), - token.start(), - ))); - context.push_diagnostic(diagnostic); + context.push_diagnostic( + diagnostic_kind_for_operator(kind, token.range()).with_fix(Fix::safe_edit( + Edit::insertion(" ".to_string(), token.start()), + )), + ); } // Operator with leading but no trailing space, enforce consistent spacing. (true, false) => { - let mut diagnostic = - Diagnostic::new(diagnostic_kind_for_operator(kind), token.range()); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - " ".to_string(), - token.end(), - ))); - context.push_diagnostic(diagnostic); + context.push_diagnostic( + diagnostic_kind_for_operator(kind, token.range()).with_fix(Fix::safe_edit( + Edit::insertion(" ".to_string(), token.end()), + )), + ); } // Operator with no space, require spaces if it is required by the operator. (false, false) => { if needs_space == NeedsSpace::Yes { - let mut diagnostic = - Diagnostic::new(diagnostic_kind_for_operator(kind), token.range()); - diagnostic.set_fix(Fix::safe_edits( - Edit::insertion(" ".to_string(), token.start()), - [Edit::insertion(" ".to_string(), token.end())], - )); - context.push_diagnostic(diagnostic); + context.push_diagnostic( + diagnostic_kind_for_operator(kind, token.range()).with_fix( + Fix::safe_edits( + Edit::insertion(" ".to_string(), token.start()), + [Edit::insertion(" ".to_string(), token.end())], + ), + ), + ); } } (true, true) => { @@ -317,15 +314,15 @@ impl From for NeedsSpace { } } -fn diagnostic_kind_for_operator(operator: TokenKind) -> DiagnosticKind { +fn diagnostic_kind_for_operator(operator: TokenKind, range: TextRange) -> Diagnostic { if operator == TokenKind::Percent { - DiagnosticKind::from(MissingWhitespaceAroundModuloOperator) + Diagnostic::new(MissingWhitespaceAroundModuloOperator, range) } else if operator.is_bitwise_or_shift() { - DiagnosticKind::from(MissingWhitespaceAroundBitwiseOrShiftOperator) + Diagnostic::new(MissingWhitespaceAroundBitwiseOrShiftOperator, range) } else if operator.is_arithmetic() { - DiagnosticKind::from(MissingWhitespaceAroundArithmeticOperator) + Diagnostic::new(MissingWhitespaceAroundArithmeticOperator, range) } else { - DiagnosticKind::from(MissingWhitespaceAroundOperator) + Diagnostic::new(MissingWhitespaceAroundOperator, range) } } diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index eb4b4a7e1d52a5..dc20138649c6cb 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -777,7 +777,7 @@ mod tests { let actual = messages .iter() .filter_map(Message::as_diagnostic_message) - .map(|diagnostic| diagnostic.kind.rule()) + .map(AsRule::rule) .collect::>(); assert_eq!(actual, expected); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index 7a30ee07632264..959bb0aa6085e0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_parser::{Token, TokenKind}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -193,23 +193,23 @@ pub(crate) fn invalid_string_characters( }; for (column, match_) in text.match_indices(&['\x08', '\x1A', '\x1B', '\0', '\u{200b}']) { + let location = token.start() + TextSize::try_from(column).unwrap(); let c = match_.chars().next().unwrap(); - let (replacement, rule): (&str, DiagnosticKind) = match c { - '\x08' => ("\\b", InvalidCharacterBackspace.into()), - '\x1A' => ("\\x1A", InvalidCharacterSub.into()), - '\x1B' => ("\\x1B", InvalidCharacterEsc.into()), - '\0' => ("\\0", InvalidCharacterNul.into()), - '\u{200b}' => ("\\u200b", InvalidCharacterZeroWidthSpace.into()), + let range = TextRange::at(location, c.text_len()); + let (replacement, mut diagnostic) = match c { + '\x08' => ("\\b", Diagnostic::new(InvalidCharacterBackspace, range)), + '\x1A' => ("\\x1A", Diagnostic::new(InvalidCharacterSub, range)), + '\x1B' => ("\\x1B", Diagnostic::new(InvalidCharacterEsc, range)), + '\0' => ("\\0", Diagnostic::new(InvalidCharacterNul, range)), + '\u{200b}' => ( + "\\u200b", + Diagnostic::new(InvalidCharacterZeroWidthSpace, range), + ), _ => { continue; } }; - let location = token.start() + TextSize::try_from(column).unwrap(); - let range = TextRange::at(location, c.text_len()); - - let mut diagnostic = Diagnostic::new(rule, range); - if !token.unwrap_string_flags().is_raw_string() { let edit = Edit::range_replacement(replacement.to_string(), range); diagnostic.set_fix(Fix::safe_edit(edit)); diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs index 8f6cd66d2041e9..7bf363c3c89d78 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, DiagnosticKind, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -104,9 +104,9 @@ fn get_undecorated_methods(checker: &Checker, class_stmt: &Stmt, method_type: &M let mut explicit_decorator_calls: HashMap = HashMap::default(); - let (method_name, diagnostic_type): (&str, DiagnosticKind) = match method_type { - MethodType::Classmethod => ("classmethod", NoClassmethodDecorator.into()), - MethodType::Staticmethod => ("staticmethod", NoStaticmethodDecorator.into()), + let method_name = match method_type { + MethodType::Classmethod => "classmethod", + MethodType::Staticmethod => "staticmethod", }; // gather all explicit *method calls @@ -170,10 +170,11 @@ fn get_undecorated_methods(checker: &Checker, class_stmt: &Stmt, method_type: &M continue; } - let mut diagnostic = Diagnostic::new( - diagnostic_type.clone(), - TextRange::new(stmt.range().start(), stmt.range().start()), - ); + let range = TextRange::new(stmt.range().start(), stmt.range().start()); + let mut diagnostic = match method_type { + MethodType::Classmethod => Diagnostic::new(NoClassmethodDecorator, range), + MethodType::Staticmethod => Diagnostic::new(NoStaticmethodDecorator, range), + }; let indentation = indentation_at_offset(stmt.range().start(), checker.source()); diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 48b5737fc41c12..f40fe1f833d6b7 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -1,6 +1,4 @@ -use ruff_diagnostics::{ - Applicability, Diagnostic, DiagnosticKind, Edit, Fix, FixAvailability, Violation, -}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::helpers::{pep_604_optional, pep_604_union}; use ruff_python_ast::PythonVersion; @@ -150,15 +148,15 @@ pub(crate) fn non_pep604_annotation( match operator { Pep604Operator::Optional => { - let (rule, diagnostic_kind) = if is_defer_optional_to_up045_enabled(checker.settings) { + let (rule, mut diagnostic) = if is_defer_optional_to_up045_enabled(checker.settings) { ( Rule::NonPEP604AnnotationOptional, - DiagnosticKind::from(NonPEP604AnnotationOptional), + Diagnostic::new(NonPEP604AnnotationOptional, expr.range()), ) } else { ( Rule::NonPEP604AnnotationUnion, - DiagnosticKind::from(NonPEP604AnnotationUnion), + Diagnostic::new(NonPEP604AnnotationUnion, expr.range()), ) }; @@ -166,8 +164,6 @@ pub(crate) fn non_pep604_annotation( return; } - let mut diagnostic = Diagnostic::new(diagnostic_kind, expr.range()); - if fixable { match slice { Expr::Tuple(_) => { diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index 2b8f8d3e23b9d3..3167d61f172d9c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -2,7 +2,7 @@ use std::fmt; use bitflags::bitflags; -use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; +use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -355,27 +355,30 @@ impl Candidate { fn into_diagnostic(self, context: Context, settings: &LinterSettings) -> Option { if !settings.allowed_confusables.contains(&self.confusable) { let char_range = TextRange::at(self.offset, self.confusable.text_len()); - let diagnostic = Diagnostic::new::( - match context { - Context::String => AmbiguousUnicodeCharacterString { + let diagnostic = match context { + Context::String => Diagnostic::new( + AmbiguousUnicodeCharacterString { confusable: self.confusable, representant: self.representant, - } - .into(), - Context::Docstring => AmbiguousUnicodeCharacterDocstring { + }, + char_range, + ), + Context::Docstring => Diagnostic::new( + AmbiguousUnicodeCharacterDocstring { confusable: self.confusable, representant: self.representant, - } - .into(), - Context::Comment => AmbiguousUnicodeCharacterComment { + }, + char_range, + ), + Context::Comment => Diagnostic::new( + AmbiguousUnicodeCharacterComment { confusable: self.confusable, representant: self.representant, - } - .into(), - }, - char_range, - ); - if settings.rules.enabled(diagnostic.kind.rule()) { + }, + char_range, + ), + }; + if settings.rules.enabled(diagnostic.rule()) { return Some(diagnostic); } } diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 1c8e5274082e35..53d9d568d43c34 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -235,7 +235,7 @@ Source with applied fixes: .into_iter() .filter_map(Message::into_diagnostic_message) .map(|mut diagnostic| { - let rule = diagnostic.kind.rule(); + let rule = diagnostic.rule(); let fixable = diagnostic.fix.as_ref().is_some_and(|fix| { matches!( fix.applicability(), @@ -269,7 +269,7 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e } assert!( - !(fixable && diagnostic.kind.suggestion.is_none()), + !(fixable && diagnostic.suggestion.is_none()), "Diagnostic emitted by {rule:?} is fixable but \ `Violation::fix_title` returns `None`" ); diff --git a/crates/ruff_macros/Cargo.toml b/crates/ruff_macros/Cargo.toml index c0215053c53934..7e72d59855f72b 100644 --- a/crates/ruff_macros/Cargo.toml +++ b/crates/ruff_macros/Cargo.toml @@ -17,6 +17,7 @@ doctest = false [dependencies] ruff_python_trivia = { workspace = true } +heck = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true, features = ["derive", "parsing", "extra-traits", "full"] } diff --git a/crates/ruff_macros/src/kebab_case.rs b/crates/ruff_macros/src/kebab_case.rs index 1f6dcf42fd3875..3b7ef3f8db0a0f 100644 --- a/crates/ruff_macros/src/kebab_case.rs +++ b/crates/ruff_macros/src/kebab_case.rs @@ -1,19 +1,10 @@ +use heck::ToKebabCase; use proc_macro2::TokenStream; pub(crate) fn kebab_case(input: &syn::Ident) -> TokenStream { - let screaming_snake_case = input.to_string(); + let s = input.to_string(); - let mut kebab_case = String::with_capacity(screaming_snake_case.len()); - - for (i, word) in screaming_snake_case.split('_').enumerate() { - if i > 0 { - kebab_case.push('-'); - } - - kebab_case.push_str(&word.to_lowercase()); - } - - let kebab_case_lit = syn::LitStr::new(&kebab_case, input.span()); + let kebab_case_lit = syn::LitStr::new(&s.to_kebab_case(), input.span()); quote::quote!(#kebab_case_lit) } diff --git a/crates/ruff_macros/src/lib.rs b/crates/ruff_macros/src/lib.rs index 99bac807fe9207..9275cd6adcb5b2 100644 --- a/crates/ruff_macros/src/lib.rs +++ b/crates/ruff_macros/src/lib.rs @@ -49,7 +49,7 @@ pub fn derive_combine(input: TokenStream) -> TokenStream { .into() } -/// Converts a screaming snake case identifier to a kebab case string. +/// Converts an identifier to a kebab case string. #[proc_macro] pub fn kebab_case(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::Ident); diff --git a/crates/ruff_macros/src/map_codes.rs b/crates/ruff_macros/src/map_codes.rs index a38f7cc543b99f..25112809da55a3 100644 --- a/crates/ruff_macros/src/map_codes.rs +++ b/crates/ruff_macros/src/map_codes.rs @@ -404,8 +404,6 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { let mut rule_fixable_match_arms = quote!(); let mut rule_explanation_match_arms = quote!(); - let mut from_impls_for_diagnostic_kind = quote!(); - for Rule { name, attrs, path, .. } in input @@ -421,10 +419,6 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { quote! {#(#attrs)* Self::#name => <#path as ruff_diagnostics::Violation>::FIX_AVAILABILITY,}, ); rule_explanation_match_arms.extend(quote! {#(#attrs)* Self::#name => #path::explain(),}); - - // Enable conversion from `DiagnosticKind` to `Rule`. - from_impls_for_diagnostic_kind - .extend(quote! {#(#attrs)* stringify!(#name) => Rule::#name,}); } quote! { @@ -443,6 +437,9 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { ::ruff_macros::CacheKey, AsRefStr, ::strum_macros::IntoStaticStr, + ::strum_macros::EnumString, + ::serde::Serialize, + ::serde::Deserialize, )] #[repr(u16)] #[strum(serialize_all = "kebab-case")] @@ -466,13 +463,19 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { } } + impl AsRule for ruff_diagnostics::Diagnostic { + fn rule(&self) -> Rule { + self.name + .parse() + .unwrap_or_else(|_| unreachable!("invalid rule name: {}", self.name)) + } + } - impl AsRule for ruff_diagnostics::DiagnosticKind { + impl AsRule for crate::message::DiagnosticMessage { fn rule(&self) -> Rule { - match self.name.as_str() { - #from_impls_for_diagnostic_kind - _ => unreachable!("invalid rule name: {}", self.name), - } + self.name + .parse() + .unwrap_or_else(|_| unreachable!("invalid rule name: {}", self.name)) } } } diff --git a/crates/ruff_macros/src/violation_metadata.rs b/crates/ruff_macros/src/violation_metadata.rs index 5973c3db3e672a..3cf60307abf40a 100644 --- a/crates/ruff_macros/src/violation_metadata.rs +++ b/crates/ruff_macros/src/violation_metadata.rs @@ -12,7 +12,7 @@ pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result #[expect(deprecated)] impl ruff_diagnostics::ViolationMetadata for #name { fn rule_name() -> &'static str { - stringify!(#name) + ::ruff_macros::kebab_case!(#name) } fn explain() -> Option<&'static str> { diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index 670b43cb759b1c..586043a0249fd2 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -9,7 +9,7 @@ use crate::{ session::DocumentQuery, PositionEncoding, DIAGNOSTIC_NAME, }; -use ruff_diagnostics::{Applicability, DiagnosticKind, Edit, Fix}; +use ruff_diagnostics::{Applicability, Edit, Fix}; use ruff_linter::{ directives::{extract_directives, Flags}, generate_noqa_edits, @@ -32,7 +32,7 @@ use ruff_text_size::{Ranged, TextRange}; /// This is serialized on the diagnostic `data` field. #[derive(Serialize, Deserialize, Debug, Clone)] pub(crate) struct AssociatedDiagnosticData { - pub(crate) kind: DiagnosticKind, + pub(crate) title: String, /// Edits to fix the diagnostic. If this is empty, a fix /// does not exist. pub(crate) edits: Vec, @@ -227,10 +227,7 @@ pub(crate) fn fixes_for_diagnostics( Ok(Some(DiagnosticFix { fixed_diagnostic, code: associated_data.code, - title: associated_data - .kind - .suggestion - .unwrap_or(associated_data.kind.name), + title: associated_data.title, noqa_edit: associated_data.noqa_edit, edits: associated_data.edits, })) @@ -248,15 +245,16 @@ fn to_lsp_diagnostic( index: &LineIndex, encoding: PositionEncoding, ) -> (usize, lsp_types::Diagnostic) { + let rule = diagnostic.rule(); let DiagnosticMessage { - kind, range: diagnostic_range, fix, + name, + body, + suggestion, .. } = diagnostic; - let rule = kind.rule(); - let fix = fix.and_then(|fix| fix.applies(Applicability::Unsafe).then_some(fix)); let data = (fix.is_some() || noqa_edit.is_some()) @@ -275,7 +273,7 @@ fn to_lsp_diagnostic( new_text: noqa_edit.into_content().unwrap_or_default().into_string(), }); serde_json::to_value(AssociatedDiagnosticData { - kind: kind.clone(), + title: suggestion.unwrap_or_else(|| name.to_string()), noqa_edit, edits, code: rule.noqa_code().to_string(), @@ -314,7 +312,7 @@ fn to_lsp_diagnostic( }) }), source: Some(DIAGNOSTIC_NAME.into()), - message: kind.body, + message: body, related_information: None, data, }, diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index dc9c20323b4b1a..cf2d1857fa4de7 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -210,26 +210,34 @@ impl Workspace { let messages: Vec = messages .into_iter() .map(|message| match message { - Message::Diagnostic(DiagnosticMessage { - kind, range, fix, .. - }) => ExpandedMessage { - code: Some(kind.rule().noqa_code().to_string()), - message: kind.body, - start_location: source_code.line_column(range.start()).into(), - end_location: source_code.line_column(range.end()).into(), - fix: fix.map(|fix| ExpandedFix { - message: kind.suggestion, - edits: fix - .edits() - .iter() - .map(|edit| ExpandedEdit { - location: source_code.line_column(edit.start()).into(), - end_location: source_code.line_column(edit.end()).into(), - content: edit.content().map(ToString::to_string), - }) - .collect(), - }), - }, + Message::Diagnostic(m) => { + let rule = m.rule(); + let DiagnosticMessage { + body, + suggestion, + range, + fix, + .. + } = m; + ExpandedMessage { + code: Some(rule.noqa_code().to_string()), + message: body, + start_location: source_code.line_column(range.start()).into(), + end_location: source_code.line_column(range.end()).into(), + fix: fix.map(|fix| ExpandedFix { + message: suggestion, + edits: fix + .edits() + .iter() + .map(|edit| ExpandedEdit { + location: source_code.line_column(edit.start()).into(), + end_location: source_code.line_column(edit.end()).into(), + content: edit.content().map(ToString::to_string), + }) + .collect(), + }), + } + } Message::SyntaxError(_) => ExpandedMessage { code: None, message: message.body().to_string(), From a5ee1a3bb1b00ae92a22e5e4bf30518dc94d1b81 Mon Sep 17 00:00:00 2001 From: Max Mynter <32773644+maxmynter@users.noreply.github.com> Date: Thu, 15 May 2025 16:47:37 +0200 Subject: [PATCH 112/487] Bump `py-fuzzer` Dependencies (#18113) --- python/py-fuzzer/fuzz.py | 2 +- python/py-fuzzer/pyproject.toml | 8 +- python/py-fuzzer/uv.lock | 126 +++++++++----------------------- 3 files changed, 40 insertions(+), 96 deletions(-) diff --git a/python/py-fuzzer/fuzz.py b/python/py-fuzzer/fuzz.py index 2a1f4b73d75f56..3b3c3f042344ae 100644 --- a/python/py-fuzzer/fuzz.py +++ b/python/py-fuzzer/fuzz.py @@ -12,7 +12,7 @@ `uvx --from ./python/py-fuzzer fuzz --bin ruff 0-2 78 93` - Run the fuzzer concurrently using seeds in range 0-10 inclusive, but only reporting bugs that are new on your branch: - `uvx --from ./python/py-fuzzer fuzz --bin ruff 0-10 --new-bugs-only` + `uvx --from ./python/py-fuzzer fuzz --bin ruff 0-10 --only-new-bugs` - Run the fuzzer concurrently on 10,000 different Python source-code files, using a random selection of seeds, and only print a summary at the end (the `shuf` command is Unix-specific): diff --git a/python/py-fuzzer/pyproject.toml b/python/py-fuzzer/pyproject.toml index fe6a35a6f89dfa..d6e380395ac6dd 100644 --- a/python/py-fuzzer/pyproject.toml +++ b/python/py-fuzzer/pyproject.toml @@ -5,10 +5,10 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "pysource-codegen>=0.6.0", - "pysource-minimize>=0.7.0", - "rich-argparse>=1.6.0", - "ruff>=0.8.0", - "termcolor>=2.5.0", + "pysource-minimize>=0.8.0", + "rich-argparse>=1.7.0", + "ruff>=0.11.9", + "termcolor>=3.1.0", ] [project.scripts] diff --git a/python/py-fuzzer/uv.lock b/python/py-fuzzer/uv.lock index ce3e7d6ef18097..1be4ceed235b2c 100644 --- a/python/py-fuzzer/uv.lock +++ b/python/py-fuzzer/uv.lock @@ -1,40 +1,7 @@ version = 1 +revision = 1 requires-python = ">=3.12" -[[package]] -name = "astunparse" -version = "1.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, - { name = "wheel" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 }, -] - -[[package]] -name = "click" -version = "8.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -109,10 +76,10 @@ dev = [ [package.metadata] requires-dist = [ { name = "pysource-codegen", specifier = ">=0.6.0" }, - { name = "pysource-minimize", specifier = ">=0.7.0" }, - { name = "rich-argparse", specifier = ">=1.6.0" }, - { name = "ruff", specifier = ">=0.8.0" }, - { name = "termcolor", specifier = ">=2.5.0" }, + { name = "pysource-minimize", specifier = ">=0.8.0" }, + { name = "rich-argparse", specifier = ">=1.7.0" }, + { name = "ruff", specifier = ">=0.11.9" }, + { name = "termcolor", specifier = ">=3.1.0" }, ] [package.metadata.requires-dev] @@ -144,16 +111,11 @@ wheels = [ [[package]] name = "pysource-minimize" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astunparse" }, - { name = "click" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/74/a095ade9a0d8b33b3eec3d84510dc2ff4327cd9fcd8340e107aaad8721fd/pysource_minimize-0.7.0.tar.gz", hash = "sha256:5fd477cba4d6251912d4e6cd21a19e6de03a72d1da1ec3bb40aa82ce468ddbe1", size = 792926 } +sdist = { url = "https://files.pythonhosted.org/packages/90/d3/6786a52121987875b2e9d273399504e2bdb868e7b80b603ecb29c900846f/pysource_minimize-0.8.0.tar.gz", hash = "sha256:e9a88c68717891dc7dc74beab769ef4c2e397599e1620b2046b89783fb500652", size = 715267 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/0f/68b7bef766029025412b919bb25694779666dfc09fe6d8604f0034778b3d/pysource_minimize-0.7.0-py3-none-any.whl", hash = "sha256:3d1f3e53ee51a697a1ed9416e243aae5d7bf5e8d21af534108901016bf50d535", size = 13525 }, + { url = "https://files.pythonhosted.org/packages/4a/7d/4e9ed2a376bb7372d74fdec557f35f70c2bf5373f2c67e05535555d0a6d4/pysource_minimize-0.8.0-py3-none-any.whl", hash = "sha256:edee433c24a2e8f81701aa7e01ba4c1e63f481f683dd3a561610762bc03ed6c3", size = 14635 }, ] [[package]] @@ -171,57 +133,48 @@ wheels = [ [[package]] name = "rich-argparse" -version = "1.6.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/ee/c410251ff6123d4417f2fe8e72c8628f187682b70ce34134a2a3e307a2d5/rich_argparse-1.6.0.tar.gz", hash = "sha256:092083c30da186f25bcdff8b1d47fdfb571288510fb051e0488a72cc3128de13", size = 17499 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b9/ff53663ee7fa6a4195fa96d91da499f2e00ca067541e016d345cce1c9ad2/rich_argparse-1.7.0.tar.gz", hash = "sha256:f31d809c465ee43f367d599ccaf88b73bc2c4d75d74ed43f2d538838c53544ba", size = 38009 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/45/54b95bb72bb17c27a7252bee5034955020b5869a33918b660ffc29cbf608/rich_argparse-1.6.0-py3-none-any.whl", hash = "sha256:fbe70a1d821b3f2fa8958cddf0cae131870a6e9faa04ab52b409cb1eda809bd7", size = 20072 }, + { url = "https://files.pythonhosted.org/packages/bb/9c/dc7cbeb99a7b7422392ed7f327efdbb958bc0faf424aef5f130309320bda/rich_argparse-1.7.0-py3-none-any.whl", hash = "sha256:b8ec8943588e9731967f4f97b735b03dc127c416f480a083060433a97baf2fd3", size = 25339 }, ] [[package]] name = "ruff" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, - { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, - { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, - { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, - { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, - { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, - { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, - { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, - { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, - { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, - { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, - { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, - { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, - { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, - { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, - { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, - { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, -] - -[[package]] -name = "six" -version = "1.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +version = "0.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453 }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566 }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020 }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935 }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971 }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631 }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236 }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436 }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759 }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985 }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775 }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307 }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026 }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627 }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340 }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080 }, ] [[package]] name = "termcolor" -version = "2.5.0" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684 }, ] [[package]] @@ -232,12 +185,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec3 wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] - -[[package]] -name = "wheel" -version = "0.45.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494 }, -] From c066bf0127482fc18c1337b50502cc63bdf805b5 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 15 May 2025 17:13:47 +0200 Subject: [PATCH 113/487] =?UTF-8?q?[ty]=20`type[=E2=80=A6]`=20is=20always?= =?UTF-8?q?=20assignable=20to=20`type`=20(#18121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Model that `type[C]` is always assignable to `type`, even if `C` is not fully static. closes https://github.com/astral-sh/ty/issues/312 ## Test Plan * New Markdown tests * Property tests --- .../resources/mdtest/type_properties/is_assignable_to.md | 6 ++++++ crates/ty_python_semantic/src/types.rs | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index d70151109dbc59..6cfc89bd445bf4 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -196,6 +196,12 @@ static_assert(is_assignable_to(type[Any], Meta)) static_assert(is_assignable_to(type[Unknown], Meta)) static_assert(is_assignable_to(Meta, type[Any])) static_assert(is_assignable_to(Meta, type[Unknown])) + +class AnyMeta(metaclass=Any): ... + +static_assert(is_assignable_to(type[AnyMeta], type)) +static_assert(is_assignable_to(type[AnyMeta], type[object])) +static_assert(is_assignable_to(type[AnyMeta], type[Any])) ``` ## Heterogeneous tuple types diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 77da747c460118..ee59b8bf633a9b 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1516,6 +1516,15 @@ impl<'db> Type<'db> { true } + // Every `type[...]` is assignable to `type` + (Type::SubclassOf(_), _) + if KnownClass::Type + .to_instance(db) + .is_assignable_to(db, target) => + { + true + } + // All `type[...]` types are assignable to `type[Any]`, because `type[Any]` can // materialize to any `type[...]` type. // From 69393b2e6ee1eec4fc69906f8f6514994218e3b3 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 15 May 2025 11:39:14 -0400 Subject: [PATCH 114/487] [ty] Improve invalid method calls for unmatched overloads (#18122) This makes an easy tweak to allow our diagnostics for unmatched overloads to apply to method calls. Previously, they only worked for function calls. There is at least one other case worth addressing too, namely, class literals. e.g., `type()`. We had a diagnostic snapshot test case to track it. Closes astral-sh/ty#274 --- .../diagnostics/no_matching_overload.md | 26 ++++++++ ...uctor_\342\200\246_(dd9f8a8f736a329).snap" | 29 +++++++++ ...ith_u\342\200\246_(31cb5f881221158e).snap" | 63 +++++++++++++++++++ .../ty_python_semantic/src/types/call/bind.rs | 16 ++++- 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_class_constructor_\342\200\246_(dd9f8a8f736a329).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md index c71e608e6ee145..19627f83510437 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md @@ -278,3 +278,29 @@ def f( f(b"foo") # error: [no-matching-overload] ``` + +## A method call with unmatched overloads + +```py +from typing import overload + +class Foo: + @overload + def bar(self, x: int) -> int: ... + @overload + def bar(self, x: str) -> str: ... + def bar(self, x: int | str) -> int | str: + return x + +foo = Foo() +foo.bar(b"wat") # error: [no-matching-overload] +``` + +## A class constructor with unmatched overloads + +TODO: At time of writing (2025-05-15), this has non-ideal diagnostics that doesn't show the +unmatched overloads. + +```py +type() # error: [no-matching-overload] +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_class_constructor_\342\200\246_(dd9f8a8f736a329).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_class_constructor_\342\200\246_(dd9f8a8f736a329).snap" new file mode 100644 index 00000000000000..9a446afe71840c --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_class_constructor_\342\200\246_(dd9f8a8f736a329).snap" @@ -0,0 +1,29 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - A class constructor with unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | type() # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of class `type` matches arguments + --> src/mdtest_snippet.py:1:1 + | +1 | type() # error: [no-matching-overload] + | ^^^^^^ + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" new file mode 100644 index 00000000000000..66eccf602ade69 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" @@ -0,0 +1,63 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: no_matching_overload.md - No matching overload diagnostics - A method call with unmatched overloads +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import overload + 2 | + 3 | class Foo: + 4 | @overload + 5 | def bar(self, x: int) -> int: ... + 6 | @overload + 7 | def bar(self, x: str) -> str: ... + 8 | def bar(self, x: int | str) -> int | str: + 9 | return x +10 | +11 | foo = Foo() +12 | foo.bar(b"wat") # error: [no-matching-overload] +``` + +# Diagnostics + +``` +error[no-matching-overload]: No overload of bound method `bar` matches arguments + --> src/mdtest_snippet.py:12:1 + | +11 | foo = Foo() +12 | foo.bar(b"wat") # error: [no-matching-overload] + | ^^^^^^^^^^^^^^^ + | +info: First overload defined here + --> src/mdtest_snippet.py:5:9 + | +3 | class Foo: +4 | @overload +5 | def bar(self, x: int) -> int: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^ +6 | @overload +7 | def bar(self, x: str) -> str: ... + | +info: Possible overloads for bound method `bar`: +info: (self, x: int) -> int +info: (self, x: str) -> str +info: Overload implementation defined here + --> src/mdtest_snippet.py:8:9 + | +6 | @overload +7 | def bar(self, x: str) -> str: ... +8 | def bar(self, x: int | str) -> int | str: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +9 | return x + | +info: rule `no-matching-overload` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 52189b96026fc6..7f0500815b4b11 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1120,7 +1120,19 @@ impl<'db> CallableBinding<'db> { String::new() } )); - if let Some(function) = self.signature_type.into_function_literal() { + // TODO: This should probably be adapted to handle more + // types of callables[1]. At present, it just handles + // standard function and method calls. + // + // [1]: https://github.com/astral-sh/ty/issues/274#issuecomment-2881856028 + let function_type_and_kind = match self.signature_type { + Type::FunctionLiteral(function) => Some(("function", function)), + Type::BoundMethod(bound_method) => { + Some(("bound method", bound_method.function(context.db()))) + } + _ => None, + }; + if let Some((kind, function)) = function_type_and_kind { if let Some(overloaded_function) = function.to_overloaded(context.db()) { if let Some(spans) = overloaded_function .overloads @@ -1134,7 +1146,7 @@ impl<'db> CallableBinding<'db> { } diag.info(format_args!( - "Possible overloads for function `{}`:", + "Possible overloads for {kind} `{}`:", function.name(context.db()) )); From d3a7cb3fe4f42ae2817c168cd189759e53ddd665 Mon Sep 17 00:00:00 2001 From: Felix Scherz Date: Thu, 15 May 2025 22:01:38 +0200 Subject: [PATCH 115/487] [ty] support accessing `__builtins__` global (#18118) ## Summary The PR adds an explicit check for `"__builtins__"` during name lookup, similar to how `"__file__"` is implemented. The inferred type is `Any`. closes https://github.com/astral-sh/ty/issues/393 ## Test Plan Added a markdown test for `__builtins__`. --------- Co-authored-by: David Peter --- .../resources/mdtest/scopes/moduletype_attrs.md | 9 +++++++++ crates/ty_python_semantic/src/symbol.rs | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 152ab6edb3dc3d..140a4ac72bacb2 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -15,6 +15,15 @@ reveal_type(__package__) # revealed: str | None reveal_type(__doc__) # revealed: str | None reveal_type(__spec__) # revealed: ModuleSpec | None reveal_type(__path__) # revealed: MutableSequence[str] +reveal_type(__builtins__) # revealed: Any + +import sys + +reveal_type(sys.__builtins__) # revealed: Any + +from builtins import __builtins__ as __bi__ + +reveal_type(__bi__) # revealed: Any class X: reveal_type(__name__) # revealed: str diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index 1d1156767985c1..2c93e8dae58571 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -324,6 +324,8 @@ pub(crate) fn imported_symbol<'db>( || { if name == "__getattr__" { Symbol::Unbound.into() + } else if name == "__builtins__" { + Symbol::bound(Type::any()).into() } else { KnownClass::ModuleType.to_instance(db).member(db, name) } @@ -1013,6 +1015,8 @@ mod implicit_globals { // None`. if name == "__file__" { Symbol::bound(KnownClass::Str.to_instance(db)).into() + } else if name == "__builtins__" { + Symbol::bound(Type::any()).into() } // In general we wouldn't check to see whether a symbol exists on a class before doing the // `.member()` call on the instance type -- we'd just do the `.member`() call on the instance From 2ceba6ae67854aa2f766f48bff56d9a314ce2e7f Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 16 May 2025 04:03:02 +0800 Subject: [PATCH 116/487] [`airflow`] Add autofixes for `AIR302` and `AIR312` (#17942) ## Summary `ProviderReplacement::Name` was designed back when we only wanted to do linting. Now we also want to fix the user code. It would be easier for us to replace them with better AutoImport struct. ## Test Plan The test fixture has been updated as some cases can now be fixed --- .../ruff_linter/src/rules/airflow/helpers.rs | 5 - .../airflow/rules/moved_to_provider_in_3.rs | 39 +++----- .../suggested_to_move_to_provider_in_3.rs | 18 +--- ...w__tests__AIR302_AIR302_common_sql.py.snap | 93 +++++++++++++++++-- 4 files changed, 103 insertions(+), 52 deletions(-) diff --git a/crates/ruff_linter/src/rules/airflow/helpers.rs b/crates/ruff_linter/src/rules/airflow/helpers.rs index 54a4d2bda574b6..90c99234d39905 100644 --- a/crates/ruff_linter/src/rules/airflow/helpers.rs +++ b/crates/ruff_linter/src/rules/airflow/helpers.rs @@ -31,11 +31,6 @@ pub(crate) enum Replacement { #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum ProviderReplacement { None, - ProviderName { - name: &'static str, - provider: &'static str, - version: &'static str, - }, AutoImport { module: &'static str, name: &'static str, diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index b0b7e36f57f9da..8ce561c62f730d 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -46,12 +46,7 @@ impl Violation for Airflow3MovedToProvider { ProviderReplacement::None => { format!("`{deprecated}` is removed in Airflow 3.0") } - ProviderReplacement::ProviderName { - name: _, - provider, - version: _, - } - | ProviderReplacement::AutoImport { + ProviderReplacement::AutoImport { name: _, module: _, provider, @@ -72,15 +67,6 @@ impl Violation for Airflow3MovedToProvider { let Airflow3MovedToProvider { replacement, .. } = self; match replacement { ProviderReplacement::None => {None} - ProviderReplacement::ProviderName { - name, - provider, - version, - } => { - Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{name}` instead." - )) - }, ProviderReplacement::AutoImport { name, module, @@ -122,7 +108,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan }; let replacement = match qualified_name.segments() { - // ProviderName: for cases that only one name has been moved // apache-airflow-providers-amazon ["airflow", "hooks", "S3_hook", rest @ ("S3Hook" | "provide_bucket_name")] => { ProviderReplacement::SourceModuleMovedToProvider { @@ -238,8 +223,9 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan } ["airflow", "operators", "druid_check_operator", "DruidCheckOperator"] | ["airflow", "operators", "presto_check_operator", "PrestoCheckOperator"] => { - ProviderReplacement::ProviderName { - name: "airflow.providers.common.sql.operators.sql.SQLCheckOperator", + ProviderReplacement::AutoImport { + module: "airflow.providers.common.sql.operators.sql", + name: "SQLCheckOperator", provider: "common-sql", version: "1.1.0", } @@ -255,8 +241,9 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan } } ["airflow", "operators", "presto_check_operator", "PrestoIntervalCheckOperator"] => { - ProviderReplacement::ProviderName { - name: "airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator", + ProviderReplacement::AutoImport { + module: "airflow.providers.common.sql.operators.sql", + name: "SQLIntervalCheckOperator", provider: "common-sql", version: "1.1.0", } @@ -281,8 +268,9 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan } } ["airflow", "operators", "presto_check_operator", "PrestoValueCheckOperator"] => { - ProviderReplacement::ProviderName { - name: "airflow.providers.common.sql.operators.sql.SQLValueCheckOperator", + ProviderReplacement::AutoImport { + module: "airflow.providers.common.sql.operators.sql", + name: "SQLValueCheckOperator", provider: "common-sql", version: "1.1.0", } @@ -320,8 +308,9 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan | ["airflow", "operators", "oracle_operator", "OracleOperator"] | ["airflow", "operators", "postgres_operator", "PostgresOperator"] | ["airflow", "operators", "sqlite_operator", "SqliteOperator"] => { - ProviderReplacement::ProviderName { - name: "airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator", + ProviderReplacement::AutoImport { + module: "airflow.providers.common.sql.operators.sql", + name: "SQLExecuteQueryOperator", provider: "common-sql", version: "1.3.0", } @@ -943,7 +932,7 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { Some((module, name.as_str())) } - _ => None, + ProviderReplacement::None => None, } { if is_guarded_by_try_except(expr, module, name, semantic) { return; diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index a95bdeef844775..d8807d96239b75 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -47,12 +47,7 @@ impl Violation for Airflow3SuggestedToMoveToProvider { ProviderReplacement::None => { format!("`{deprecated}` is removed in Airflow 3.0") } - ProviderReplacement::ProviderName { - name: _, - provider, - version: _, - } - | ProviderReplacement::AutoImport { + ProviderReplacement::AutoImport { name: _, module: _, provider, @@ -76,15 +71,6 @@ impl Violation for Airflow3SuggestedToMoveToProvider { let Airflow3SuggestedToMoveToProvider { replacement, .. } = self; match replacement { ProviderReplacement::None => None, - ProviderReplacement::ProviderName { - name, - provider, - version, - } => { - Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{name}` instead." - )) - }, ProviderReplacement::AutoImport { module, name, @@ -285,7 +271,7 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { Some((module, name.as_str())) } - _ => None, + ProviderReplacement::None => None, } { if is_guarded_by_try_except(expr, module, name, semantic) { return; diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap index 42f27ffd6b1f71..693e7f44bdce93 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap @@ -289,7 +289,7 @@ AIR302_common_sql.py:114:1: AIR302 `airflow.sensors.sql_sensor.SqlSensor` is mov | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.sensors.sql.SqlSensor` instead. -AIR302_common_sql.py:124:1: AIR302 `airflow.operators.jdbc_operator.JdbcOperator` is moved into `common-sql` provider in Airflow 3.0; +AIR302_common_sql.py:124:1: AIR302 [*] `airflow.operators.jdbc_operator.JdbcOperator` is moved into `common-sql` provider in Airflow 3.0; | 122 | from airflow.operators.sqlite_operator import SqliteOperator 123 | @@ -300,7 +300,19 @@ AIR302_common_sql.py:124:1: AIR302 `airflow.operators.jdbc_operator.JdbcOperator | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. -AIR302_common_sql.py:125:1: AIR302 `airflow.operators.mssql_operator.MsSqlOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 |-JdbcOperator() + 125 |+SQLExecuteQueryOperator() +125 126 | MsSqlOperator() +126 127 | MySqlOperator() +127 128 | OracleOperator() + +AIR302_common_sql.py:125:1: AIR302 [*] `airflow.operators.mssql_operator.MsSqlOperator` is moved into `common-sql` provider in Airflow 3.0; | 124 | JdbcOperator() 125 | MsSqlOperator() @@ -310,7 +322,20 @@ AIR302_common_sql.py:125:1: AIR302 `airflow.operators.mssql_operator.MsSqlOperat | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. -AIR302_common_sql.py:126:1: AIR302 `airflow.operators.mysql_operator.MySqlOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 125 | JdbcOperator() +125 |-MsSqlOperator() + 126 |+SQLExecuteQueryOperator() +126 127 | MySqlOperator() +127 128 | OracleOperator() +128 129 | PostgresOperator() + +AIR302_common_sql.py:126:1: AIR302 [*] `airflow.operators.mysql_operator.MySqlOperator` is moved into `common-sql` provider in Airflow 3.0; | 124 | JdbcOperator() 125 | MsSqlOperator() @@ -321,7 +346,21 @@ AIR302_common_sql.py:126:1: AIR302 `airflow.operators.mysql_operator.MySqlOperat | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. -AIR302_common_sql.py:127:1: AIR302 `airflow.operators.oracle_operator.OracleOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 125 | JdbcOperator() +125 126 | MsSqlOperator() +126 |-MySqlOperator() + 127 |+SQLExecuteQueryOperator() +127 128 | OracleOperator() +128 129 | PostgresOperator() +129 130 | SqliteOperator() + +AIR302_common_sql.py:127:1: AIR302 [*] `airflow.operators.oracle_operator.OracleOperator` is moved into `common-sql` provider in Airflow 3.0; | 125 | MsSqlOperator() 126 | MySqlOperator() @@ -332,7 +371,21 @@ AIR302_common_sql.py:127:1: AIR302 `airflow.operators.oracle_operator.OracleOper | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. -AIR302_common_sql.py:128:1: AIR302 `airflow.operators.postgres_operator.PostgresOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 125 | JdbcOperator() +125 126 | MsSqlOperator() +126 127 | MySqlOperator() +127 |-OracleOperator() + 128 |+SQLExecuteQueryOperator() +128 129 | PostgresOperator() +129 130 | SqliteOperator() + +AIR302_common_sql.py:128:1: AIR302 [*] `airflow.operators.postgres_operator.PostgresOperator` is moved into `common-sql` provider in Airflow 3.0; | 126 | MySqlOperator() 127 | OracleOperator() @@ -342,7 +395,21 @@ AIR302_common_sql.py:128:1: AIR302 `airflow.operators.postgres_operator.Postgres | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. -AIR302_common_sql.py:129:1: AIR302 `airflow.operators.sqlite_operator.SqliteOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 125 | JdbcOperator() +125 126 | MsSqlOperator() +126 127 | MySqlOperator() +127 128 | OracleOperator() +128 |-PostgresOperator() + 129 |+SQLExecuteQueryOperator() +129 130 | SqliteOperator() + +AIR302_common_sql.py:129:1: AIR302 [*] `airflow.operators.sqlite_operator.SqliteOperator` is moved into `common-sql` provider in Airflow 3.0; | 127 | OracleOperator() 128 | PostgresOperator() @@ -350,3 +417,17 @@ AIR302_common_sql.py:129:1: AIR302 `airflow.operators.sqlite_operator.SqliteOper | ^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. + +ℹ Safe fix +120 120 | from airflow.operators.oracle_operator import OracleOperator +121 121 | from airflow.operators.postgres_operator import PostgresOperator +122 122 | from airflow.operators.sqlite_operator import SqliteOperator + 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator +123 124 | +124 125 | JdbcOperator() +125 126 | MsSqlOperator() +126 127 | MySqlOperator() +127 128 | OracleOperator() +128 129 | PostgresOperator() +129 |-SqliteOperator() + 130 |+SQLExecuteQueryOperator() From f53c580c53742011dade3a82ea01a21d2cadf392 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Thu, 15 May 2025 17:17:07 -0300 Subject: [PATCH 117/487] [`pylint`] Fix `PLW1514` not recognizing the `encoding` positional argument of `codecs.open` (#18109) ## Summary Fixes #18107 ## Test Plan Snapshot tests --- .../test/fixtures/pylint/unspecified_encoding.py | 3 +++ .../src/rules/pylint/rules/unspecified_encoding.rs | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py index fca823bcbc8a2f..db89cbf4e2b7b5 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py @@ -94,3 +94,6 @@ def func(*args, **kwargs): # Violation but not detectable x = Path("foo.txt") x.open() + +# https://github.com/astral-sh/ruff/issues/18107 +codecs.open("plw1514.py", "r", "utf-8").close() # this is fine diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 66a44e0fd702ed..9f49c5beae0b1e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -215,8 +215,18 @@ fn is_violation(call: &ast::ExprCall, qualified_name: &Callee) -> bool { return false; } } + + let encoding_param_pos = match qualified_name.segments() { + // The `encoding` parameter position for `codecs.open` + ["codecs", _] => 2, + // The `encoding` parameter position for `_io.open` and the builtin `open` + _ => 3, + }; + // else mode not specified, defaults to text mode - call.arguments.find_argument_value("encoding", 3).is_none() + call.arguments + .find_argument_value("encoding", encoding_param_pos) + .is_none() } ["tempfile", tempfile_class @ ("TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile")] => { From e5435eb106b792b376057eddfa88d25ba27ec944 Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Thu, 15 May 2025 22:26:10 +0200 Subject: [PATCH 118/487] [`flake8-simplify`] add fix safety section (`SIM210`) (#18100) The PR add the `fix safety` section for rule `SIM210` (#15584 ) It is a little cheating, as the Fix safety section is copy/pasted by #18086 as the problem is the same. ### Unsafe Fix Example ```python class Foo(): def __eq__(self, other): return 0 def foo(): return True if Foo() == 0 else False def foo_fix(): return Foo() == 0 print(foo()) # False print(foo_fix()) # 0 ``` --- .../src/rules/flake8_simplify/rules/ast_ifexp.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs index 9f054ef51e0041..afa332011eaa8d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs @@ -27,6 +27,13 @@ use crate::checkers::ast::Checker; /// bool(a) /// ``` /// +/// ## Fix safety +/// +/// This fix is marked as unsafe because it may change the program’s behavior if the condition does not +/// return a proper Boolean. While the fix will try to wrap non-boolean values in a call to bool, +/// custom implementations of comparison functions like `__eq__` can avoid the bool call and still +/// lead to altered behavior. Moreover, the fix may remove comments. +/// /// ## References /// - [Python documentation: Truth Value Testing](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) #[derive(ViolationMetadata)] From 7dc4fefb47ff813339487722a0e98bfeb2a89907 Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Fri, 16 May 2025 01:57:00 +0100 Subject: [PATCH 119/487] Remove ty property tests (#18124) --- .github/workflows/daily_property_tests.yaml | 72 --------------------- 1 file changed, 72 deletions(-) delete mode 100644 .github/workflows/daily_property_tests.yaml diff --git a/.github/workflows/daily_property_tests.yaml b/.github/workflows/daily_property_tests.yaml deleted file mode 100644 index 867298d101bf25..00000000000000 --- a/.github/workflows/daily_property_tests.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: Daily property test run - -on: - workflow_dispatch: - schedule: - - cron: "0 12 * * *" - pull_request: - paths: - - ".github/workflows/daily_property_tests.yaml" - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - CARGO_INCREMENTAL: 0 - CARGO_NET_RETRY: 10 - CARGO_TERM_COLOR: always - RUSTUP_MAX_RETRIES: 10 - FORCE_COLOR: 1 - -jobs: - property_tests: - name: Property tests - runs-on: ubuntu-latest - timeout-minutes: 20 - # Don't run the cron job on forks: - if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - name: "Install Rust toolchain" - run: rustup show - - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 - - name: Build ty - # A release build takes longer (2 min vs 1 min), but the property tests run much faster in release - # mode (1.5 min vs 14 min), so the overall time is shorter with a release build. - run: cargo build --locked --release --package ty_python_semantic --tests - - name: Run property tests - shell: bash - run: | - export QUICKCHECK_TESTS=100000 - for _ in {1..5}; do - cargo test --locked --release --package ty_python_semantic -- --ignored list::property_tests - cargo test --locked --release --package ty_python_semantic -- --ignored types::property_tests::stable - done - - create-issue-on-failure: - name: Create an issue if the daily property test run surfaced any bugs - runs-on: ubuntu-latest - needs: property_tests - if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.property_tests.result == 'failure' }} - permissions: - issues: write - steps: - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.issues.create({ - owner: "astral-sh", - repo: "ruff", - title: `Daily property test run failed on ${new Date().toDateString()}`, - body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", - labels: ["bug", "ty", "testing"], - }) From 6e39250015c3625f69e286fb639c4167ebe9496a Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 16 May 2025 06:57:26 +0200 Subject: [PATCH 120/487] [ty] Allow unions including `Any`/`Unknown` as bases (#18094) ## Summary Alternative fix for https://github.com/astral-sh/ty/issues/312 ## Test Plan New Markdown test --- .../resources/mdtest/mro.md | 19 +++++++++++++ ...ts_wi\342\200\246_(ea7ebc83ec359b54).snap" | 18 ++++++------- .../src/types/class_base.rs | 27 ++++++++++++++++++- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 3252178cf9017a..4f6e1c9c72fb90 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -256,6 +256,25 @@ class Foo(x): ... reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] ``` +## `__bases__` is a union of a dynamic type and valid bases + +If a dynamic type such as `Any` or `Unknown` is one of the elements in the union, and all other +types *would be* valid class bases, we do not emit an `invalid-base` diagnostic and use the dynamic +type as a base to prevent further downstream errors. + +```py +from typing import Any + +def _(flag: bool, any: Any): + if flag: + Base = any + else: + class Base: ... + + class Foo(Base): ... + reveal_type(Foo.__mro__) # revealed: tuple[, Any, ] +``` + ## `__bases__` includes multiple `Union`s ```py diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" index ec876301783ea0..fceb6462c89ecd 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_lists_wi\342\200\246_(ea7ebc83ec359b54).snap" @@ -141,7 +141,7 @@ info[revealed-type]: Revealed type ``` ``` -error[duplicate-base]: Duplicate base class `Eggs` +error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -160,17 +160,18 @@ error[duplicate-base]: Duplicate base class `Eggs` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:18:5 + --> src/mdtest_snippet.py:17:5 | +15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, + | ---- Class `Spam` first included in bases list here 18 | Eggs, - | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, + | ^^^^ Class `Spam` later repeated here 22 | Eggs, - | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default @@ -178,7 +179,7 @@ info: rule `duplicate-base` is enabled by default ``` ``` -error[duplicate-base]: Duplicate base class `Spam` +error[duplicate-base]: Duplicate base class `Eggs` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -197,18 +198,17 @@ error[duplicate-base]: Duplicate base class `Spam` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:17:5 + --> src/mdtest_snippet.py:18:5 | -15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, - | ---- Class `Spam` first included in bases list here 18 | Eggs, + | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, - | ^^^^ Class `Spam` later repeated here 22 | Eggs, + | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index bcd2af97cbf6cc..073e77ca117db8 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -123,7 +123,32 @@ impl<'db> ClassBase<'db> { Some(valid_element) } } - Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? + Type::Union(union) => { + // We do not support full unions of MROs (yet). Until we do, + // support the cases where one of the types in the union is + // a dynamic type such as `Any` or `Unknown`, and all other + // types *would be* valid class bases. In this case, we can + // "fold" the other potential bases into the dynamic type, + // and return `Any`/`Unknown` as the class base to prevent + // invalid-base diagnostics and further downstream errors. + let Some(Type::Dynamic(dynamic)) = union + .elements(db) + .iter() + .find(|elem| matches!(elem, Type::Dynamic(_))) + else { + return None; + }; + + if union + .elements(db) + .iter() + .all(|elem| ClassBase::try_from_type(db, *elem).is_some()) + { + Some(ClassBase::Dynamic(*dynamic)) + } else { + None + } + } Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`? Type::PropertyInstance(_) => None, Type::Never From 196e4befba350f95c5aa4d3b82911af8a7e23b04 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 16 May 2025 09:19:55 +0200 Subject: [PATCH 121/487] Update MSRV to 1.85 and toolchain to 1.87 (#18126) --- Cargo.toml | 3 +- crates/ruff_db/src/source.rs | 4 +- crates/ruff_db/src/system/memory_fs.rs | 6 +-- crates/ruff_formatter/src/builders.rs | 22 +++++----- crates/ruff_formatter/src/printer/mod.rs | 6 +-- crates/ruff_linter/src/linter.rs | 4 +- .../rules/hardcoded_sql_expression.rs | 2 +- .../src/rules/ruff/rules/sort_dunder_slots.rs | 2 +- crates/ruff_linter/src/source_kind.rs | 40 +++++++++++++++++-- crates/ruff_linter/src/test.rs | 2 +- .../src/expression/parentheses.rs | 2 +- .../src/statement/stmt_assign.rs | 10 ++--- .../src/analyze/typing.rs | 8 +--- crates/ruff_server/src/session/index.rs | 2 +- .../src/session/index/ruff_settings.rs | 10 ++--- crates/ruff_server/src/session/settings.rs | 8 ++-- .../ty_python_semantic/src/site_packages.rs | 2 +- crates/ty_python_semantic/src/types.rs | 1 - rust-toolchain.toml | 2 +- 19 files changed, 82 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27c02132d43a6a..2704d59a463086 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -rust-version = "1.84" +rust-version = "1.85" homepage = "https://docs.astral.sh/ruff" documentation = "https://docs.astral.sh/ruff" repository = "https://github.com/astral-sh/ruff" @@ -215,6 +215,7 @@ similar_names = "allow" single_match_else = "allow" too_many_lines = "allow" needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. +unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often. # Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250 needless_raw_string_hashes = "allow" # Disallowed restriction lints diff --git a/crates/ruff_db/src/source.rs b/crates/ruff_db/src/source.rs index 6d7d9157519c26..5e4f15cff17bc8 100644 --- a/crates/ruff_db/src/source.rs +++ b/crates/ruff_db/src/source.rs @@ -133,7 +133,7 @@ struct SourceTextInner { #[derive(Eq, PartialEq)] enum SourceTextKind { Text(String), - Notebook(Notebook), + Notebook(Box), } impl From for SourceTextKind { @@ -144,7 +144,7 @@ impl From for SourceTextKind { impl From for SourceTextKind { fn from(notebook: Notebook) -> Self { - SourceTextKind::Notebook(notebook) + SourceTextKind::Notebook(Box::new(notebook)) } } diff --git a/crates/ruff_db/src/system/memory_fs.rs b/crates/ruff_db/src/system/memory_fs.rs index f773b53d73b09b..afb9a1a87e8ad1 100644 --- a/crates/ruff_db/src/system/memory_fs.rs +++ b/crates/ruff_db/src/system/memory_fs.rs @@ -463,17 +463,17 @@ fn not_found() -> std::io::Error { fn is_a_directory() -> std::io::Error { // Note: Rust returns `ErrorKind::IsADirectory` for this error but this is a nightly only variant :(. // So we have to use other for now. - std::io::Error::new(std::io::ErrorKind::Other, "Is a directory") + std::io::Error::other("Is a directory") } fn not_a_directory() -> std::io::Error { // Note: Rust returns `ErrorKind::NotADirectory` for this error but this is a nightly only variant :(. // So we have to use `Other` for now. - std::io::Error::new(std::io::ErrorKind::Other, "Not a directory") + std::io::Error::other("Not a directory") } fn directory_not_empty() -> std::io::Error { - std::io::Error::new(std::io::ErrorKind::Other, "directory not empty") + std::io::Error::other("directory not empty") } fn create_dir_all( diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 21ab988b5e38e0..a074be5bae5550 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -1388,7 +1388,7 @@ pub fn soft_space_or_block_indent(content: &impl Format) -> Bl pub fn group(content: &impl Format) -> Group { Group { content: Argument::new(content), - group_id: None, + id: None, should_expand: false, } } @@ -1396,14 +1396,14 @@ pub fn group(content: &impl Format) -> Group { #[derive(Copy, Clone)] pub struct Group<'a, Context> { content: Argument<'a, Context>, - group_id: Option, + id: Option, should_expand: bool, } impl Group<'_, Context> { #[must_use] - pub fn with_group_id(mut self, group_id: Option) -> Self { - self.group_id = group_id; + pub fn with_id(mut self, group_id: Option) -> Self { + self.id = group_id; self } @@ -1429,7 +1429,7 @@ impl Format for Group<'_, Context> { }; f.write_element(FormatElement::Tag(StartGroup( - tag::Group::new().with_id(self.group_id).with_mode(mode), + tag::Group::new().with_id(self.id).with_mode(mode), ))); Arguments::from(&self.content).fmt(f)?; @@ -1443,7 +1443,7 @@ impl Format for Group<'_, Context> { impl std::fmt::Debug for Group<'_, Context> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Group") - .field("group_id", &self.group_id) + .field("id", &self.id) .field("should_expand", &self.should_expand) .field("content", &"{{content}}") .finish() @@ -1642,7 +1642,7 @@ impl std::fmt::Debug for BestFitParenthesize<'_, Context> { /// soft_line_break(), /// if_group_breaks(&token(")")) /// ]) -/// .with_group_id(Some(parentheses_id)) +/// .with_id(Some(parentheses_id)) /// .fmt(f) /// }); /// @@ -1991,7 +1991,7 @@ impl IfGroupBreaks<'_, Context> { /// })), /// token("]") /// ], - /// ).with_group_id(Some(group_id)) + /// ).with_id(Some(group_id)) /// ]) /// })])?; /// @@ -2046,7 +2046,7 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// let id = f.group_id("head"); /// /// write!(f, [ -/// group(&token("Head")).with_group_id(Some(id)), +/// group(&token("Head")).with_id(Some(id)), /// if_group_breaks(&indent(&token("indented"))).with_group_id(Some(id)), /// if_group_fits_on_line(&token("indented")).with_group_id(Some(id)) /// ]) @@ -2071,7 +2071,7 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// let group_id = f.group_id("header"); /// /// write!(f, [ -/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_id(Some(group_id)), /// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id) /// ]) /// }); @@ -2101,7 +2101,7 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// let group_id = f.group_id("header"); /// /// write!(f, [ -/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_id(Some(group_id)), /// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id) /// ]) /// }); diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 071432500e83d3..f6160ed79a606f 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -2002,7 +2002,7 @@ two lines`, token("The referenced group breaks."), hard_line_break() ]) - .with_group_id(Some(group_id)), + .with_id(Some(group_id)), group(&format_args![ token("This group breaks because:"), soft_line_break_or_space(), @@ -2027,7 +2027,7 @@ two lines`, write!( f, [ - group(&token("Group with id-2")).with_group_id(Some(id_2)), + group(&token("Group with id-2")).with_id(Some(id_2)), hard_line_break() ] )?; @@ -2035,7 +2035,7 @@ two lines`, write!( f, [ - group(&token("Group with id-1 does not fit on the line because it exceeds the line width of 80 characters by")).with_group_id(Some(id_1)), + group(&token("Group with id-1 does not fit on the line because it exceeds the line width of 80 characters by")).with_id(Some(id_1)), hard_line_break() ] )?; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 58c0a3b2ee1c80..12a91a784ed360 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -940,7 +940,7 @@ mod tests { #[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")] fn test_cell_id(path: &Path, has_id: bool) -> Result<()> { let source_notebook = Notebook::from_path(¬ebook_path(path))?; - let source_kind = SourceKind::IpyNotebook(source_notebook); + let source_kind = SourceKind::ipy_notebook(source_notebook); let (_, transformed) = test_contents( &source_kind, path, @@ -1231,7 +1231,7 @@ mod tests { format!("async_comprehension_in_sync_comprehension_notebook_{python_version}"); let path = Path::new("resources/test/fixtures/syntax_errors/async_comprehension.ipynb"); let messages = test_contents_syntax_errors( - &SourceKind::IpyNotebook(Notebook::from_path(path)?), + &SourceKind::ipy_notebook(Notebook::from_path(path)?), path, &LinterSettings { unresolved_target_version: python_version.into(), diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index df0effe5e8847d..6352e05052dad1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -198,7 +198,7 @@ fn is_explicit_concatenation(expr: &Expr) -> Option { .iter() .map(is_explicit_concatenation) .collect::>(); - if values.iter().any(|v| *v == Some(true)) { + if values.contains(&Some(true)) { Some(true) } else if values.iter().all(|v| *v == Some(false)) { Some(false) diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index fd588bf82f82cb..00e9c755ec7b28 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -174,7 +174,7 @@ struct StringLiteralDisplay<'a> { /// The elts from the original AST node representing the display. /// Each elt is the AST representation of a single string literal /// element in the display - elts: Cow<'a, Vec>, + elts: Cow<'a, [ast::Expr]>, /// The source-code range of the display as a whole range: TextRange, /// What kind of a display is it? A dict, set, list or tuple? diff --git a/crates/ruff_linter/src/source_kind.rs b/crates/ruff_linter/src/source_kind.rs index f067fe49dbb4c1..a146d2db137101 100644 --- a/crates/ruff_linter/src/source_kind.rs +++ b/crates/ruff_linter/src/source_kind.rs @@ -16,15 +16,47 @@ use colored::Colorize; use crate::fs; use crate::text_helpers::ShowNonprinting; -#[derive(Clone, Debug, PartialEq, is_macro::Is)] +#[derive(Clone, Debug, PartialEq)] pub enum SourceKind { /// The source contains Python source code. Python(String), /// The source contains a Jupyter notebook. - IpyNotebook(Notebook), + IpyNotebook(Box), } impl SourceKind { + pub fn ipy_notebook(notebook: Notebook) -> Self { + SourceKind::IpyNotebook(Box::new(notebook)) + } + + pub fn as_ipy_notebook(&self) -> Option<&Notebook> { + match self { + SourceKind::IpyNotebook(notebook) => Some(notebook), + SourceKind::Python(_) => None, + } + } + + pub fn as_python(&self) -> Option<&str> { + match self { + SourceKind::Python(code) => Some(code), + SourceKind::IpyNotebook(_) => None, + } + } + + pub fn expect_python(self) -> String { + match self { + SourceKind::Python(code) => code, + SourceKind::IpyNotebook(_) => panic!("expected python code"), + } + } + + pub fn expect_ipy_notebook(self) -> Notebook { + match self { + SourceKind::IpyNotebook(notebook) => *notebook, + SourceKind::Python(_) => panic!("expected ipy notebook"), + } + } + #[must_use] pub(crate) fn updated(&self, new_source: String, source_map: &SourceMap) -> Self { match self { @@ -52,7 +84,7 @@ impl SourceKind { let notebook = Notebook::from_path(path)?; Ok(notebook .is_python_notebook() - .then_some(Self::IpyNotebook(notebook))) + .then_some(Self::IpyNotebook(Box::new(notebook)))) } else { let contents = std::fs::read_to_string(path)?; Ok(Some(Self::Python(contents))) @@ -69,7 +101,7 @@ impl SourceKind { let notebook = Notebook::from_source_code(&source_code)?; Ok(notebook .is_python_notebook() - .then_some(Self::IpyNotebook(notebook))) + .then_some(Self::IpyNotebook(Box::new(notebook)))) } else { Ok(Some(Self::Python(source_code))) } diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 53d9d568d43c34..f0c3063c95c951 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -60,7 +60,7 @@ pub(crate) fn assert_notebook_path( ) -> Result { let source_notebook = Notebook::from_path(path.as_ref())?; - let source_kind = SourceKind::IpyNotebook(source_notebook); + let source_kind = SourceKind::ipy_notebook(source_notebook); let (messages, transformed) = test_contents(&source_kind, path.as_ref(), settings); let expected_notebook = Notebook::from_path(expected.as_ref())?; let linted_notebook = transformed.into_owned().expect_ipy_notebook(); diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index 8fa13546b32453..e3cdf852abfbc9 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -255,7 +255,7 @@ impl<'ast> Format> for FormatOptionalParentheses<'_, 'ast> soft_line_break(), if_group_breaks(&token(")")) ]) - .with_group_id(Some(parens_id))] + .with_id(Some(parens_id))] ) } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index 80dfc81884bdbb..f7bb76d6282506 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -413,7 +413,7 @@ impl Format> for FormatStatementsLastExpression<'_> { soft_block_indent(&format_args![flat, inline_comments]), token(")"), ]) - .with_group_id(Some(group_id)) + .with_id(Some(group_id)) .should_expand(true) .fmt(f) }); @@ -433,7 +433,7 @@ impl Format> for FormatStatementsLastExpression<'_> { token(")"), inline_comments, ]) - .with_group_id(Some(group_id)) + .with_id(Some(group_id)) .should_expand(true) .fmt(f) }); @@ -501,7 +501,7 @@ impl Format> for FormatStatementsLastExpression<'_> { soft_block_indent(&format_args![f_string_flat, inline_comments]), token(")"), ]) - .with_group_id(Some(group_id)) + .with_id(Some(group_id)) .should_expand(true) .fmt(f) }); @@ -817,7 +817,7 @@ impl Format> for FormatStatementsLastExpression<'_> { space(), token("("), group(&soft_block_indent(&format_expanded)) - .with_group_id(Some(group_id)) + .with_id(Some(group_id)) .should_expand(true), token(")"), inline_comments @@ -875,7 +875,7 @@ impl Format> for FormatStatementsLastExpression<'_> { space(), token("("), group(&soft_block_indent(&format_expanded)) - .with_group_id(Some(group_id)) + .with_id(Some(group_id)) .should_expand(true), token(")"), inline_comments diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index be1d529ce9e5f0..93213d8dea53ac 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -252,9 +252,7 @@ pub fn is_immutable_annotation( .is_some_and(|qualified_name| { is_immutable_non_generic_type(qualified_name.segments()) || is_immutable_generic_type(qualified_name.segments()) - || extend_immutable_calls - .iter() - .any(|target| qualified_name == *target) + || extend_immutable_calls.contains(&qualified_name) }) } Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => semantic @@ -308,9 +306,7 @@ pub fn is_immutable_func( .resolve_qualified_name(map_subscript(func)) .is_some_and(|qualified_name| { is_immutable_return_type(qualified_name.segments()) - || extend_immutable_calls - .iter() - .any(|target| qualified_name == *target) + || extend_immutable_calls.contains(&qualified_name) }) } diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 868b91a0cb489b..4fa485bb877c36 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -559,7 +559,7 @@ impl DocumentQuery { ruff_linter::source_kind::SourceKind::Python(document.contents().to_string()) } Self::Notebook { notebook, .. } => { - ruff_linter::source_kind::SourceKind::IpyNotebook(notebook.make_ruff_notebook()) + ruff_linter::source_kind::SourceKind::ipy_notebook(notebook.make_ruff_notebook()) } } } diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index c004f1d9f6a733..ca7fa918ff5533 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -462,7 +462,7 @@ impl ConfigurationTransformer for EditorConfigurationTransformer<'_> { tracing::debug!( "Combining settings from editor-specified inline configuration" ); - match Configuration::from_options(options, None, project_root) { + match Configuration::from_options(*options, None, project_root) { Ok(configuration) => editor_configuration.combine(configuration), Err(err) => { tracing::error!( @@ -516,10 +516,10 @@ mod tests { #[test] fn inline_settings() { let editor_settings = ResolvedEditorSettings { - configuration: Some(ResolvedConfiguration::Inline(Options { + configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { line_length: Some(LineLength::try_from(120).unwrap()), ..Default::default() - })), + }))), ..Default::default() }; @@ -534,10 +534,10 @@ mod tests { #[test] fn inline_and_specific_settings_resolution_order() { let editor_settings = ResolvedEditorSettings { - configuration: Some(ResolvedConfiguration::Inline(Options { + configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { line_length: Some(LineLength::try_from(120).unwrap()), ..Default::default() - })), + }))), line_length: Some(LineLength::try_from(100).unwrap()), ..Default::default() }; diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs index d10b493737fcf3..fb5d2ee6365199 100644 --- a/crates/ruff_server/src/session/settings.rs +++ b/crates/ruff_server/src/session/settings.rs @@ -52,7 +52,7 @@ pub(crate) struct ResolvedEditorSettings { #[cfg_attr(test, derive(PartialEq, Eq))] pub(crate) enum ResolvedConfiguration { FilePath(PathBuf), - Inline(Options), + Inline(Box), } impl TryFrom<&ClientConfiguration> for ResolvedConfiguration { @@ -68,7 +68,7 @@ impl TryFrom<&ClientConfiguration> for ResolvedConfiguration { if options.extend.is_some() { Err(ResolvedConfigurationError::ExtendNotSupported) } else { - Ok(ResolvedConfiguration::Inline(options)) + Ok(ResolvedConfiguration::Inline(Box::new(options))) } } } @@ -991,7 +991,7 @@ mod tests { fix_violation_enable: true, show_syntax_errors: true, editor_settings: ResolvedEditorSettings { - configuration: Some(ResolvedConfiguration::Inline(Options { + configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { line_length: Some(LineLength::try_from(100).unwrap()), lint: Some(LintOptions { common: LintCommonOptions { @@ -1005,7 +1005,7 @@ mod tests { ..Default::default() }), ..Default::default() - })), + }))), extend_select: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), ..Default::default() } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 53d8a037a21e95..ccad356046c8e8 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -594,7 +594,7 @@ impl PythonHomePath { system .is_directory(&canonicalized) .then_some(Self(canonicalized)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "not a directory")) + .ok_or_else(|| io::Error::other("not a directory")) } } diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ee59b8bf633a9b..ab6a0a5ab5529a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5034,7 +5034,6 @@ impl<'db> Type<'db> { /// Note that this does not specialize generic classes, functions, or type aliases! That is a /// different operation that is performed explicitly (via a subscript operation), or implicitly /// via a call to the generic object. - #[must_use] #[salsa::tracked] pub fn apply_specialization( self, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e7f22fb8ba993e..0837c1fca7304b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.86" +channel = "1.87" From 8644c9da43a47d557dcbe29f5a36b35a0db414ad Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 16 May 2025 12:49:35 +0200 Subject: [PATCH 122/487] [ty] Regression test for relative import in stubs package (#18123) ## Summary Regression test for https://github.com/astral-sh/ty/issues/408 --- .../resources/mdtest/import/stub_packages.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/import/stub_packages.md b/crates/ty_python_semantic/resources/mdtest/import/stub_packages.md index fef47bf47deb86..5224e6e6a22992 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/stub_packages.md +++ b/crates/ty_python_semantic/resources/mdtest/import/stub_packages.md @@ -284,3 +284,33 @@ from shapes import Hexagon, Pentagon reveal_type(Pentagon().sides) # revealed: int reveal_type(Hexagon().area) # revealed: int | float ``` + +## Relative import in stub package + +Regression test for + +```toml +[environment] +extra-paths = ["/packages"] +``` + +`/packages/yaml-stubs/__init__.pyi`: + +```pyi +from .loader import * +``` + +`/packages/yaml-stubs/loader.pyi`: + +```pyi +class YamlLoader: ... +``` + +`main.py`: + +```py +import yaml + +# TODO: This should not be an error +yaml.YamlLoader # error: [unresolved-attribute] "Type `` has no attribute `YamlLoader`" +``` From e67b35743ad756ba26fbebd2cbc9e5e7e85e63b1 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 16 May 2025 12:56:43 +0200 Subject: [PATCH 123/487] [ty] NamedTuple 'fallback' attributes (#18127) ## Summary Add various attributes to `NamedTuple` classes/instances that are available at runtime. closes https://github.com/astral-sh/ty/issues/417 ## Test Plan New Markdown tests --- .../resources/mdtest/named_tuple.md | 27 ++++++++++++++++++ .../src/module_resolver/module.rs | 3 ++ crates/ty_python_semantic/src/types/class.rs | 28 +++++++++++++++---- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index b7cf17ea46b953..8c5a79d62f9095 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -139,6 +139,33 @@ class Property[T](NamedTuple): reveal_type(Property("height", 3.4)) # revealed: Property[Unknown] ``` +## Attributes on `NamedTuple` + +The following attributes are available on `NamedTuple` classes / instances: + +```py +from typing import NamedTuple + +class Person(NamedTuple): + name: str + age: int | None = None + +reveal_type(Person._field_defaults) # revealed: dict[str, Any] +reveal_type(Person._fields) # revealed: tuple[str, ...] +reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self +reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] +reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self + +# TODO: should be `Person` once we support `Self` +reveal_type(Person._make(("Alice", 42))) # revealed: Unknown + +person = Person("Alice", 42) + +reveal_type(person._asdict()) # revealed: dict[str, Any] +# TODO: should be `Person` once we support `Self` +reveal_type(person._replace(name="Bob")) # revealed: Unknown +``` + ## `collections.namedtuple` ```py diff --git a/crates/ty_python_semantic/src/module_resolver/module.rs b/crates/ty_python_semantic/src/module_resolver/module.rs index 2b02d4daca5990..c56d16dbdcab82 100644 --- a/crates/ty_python_semantic/src/module_resolver/module.rs +++ b/crates/ty_python_semantic/src/module_resolver/module.rs @@ -118,6 +118,8 @@ pub enum KnownModule { Dataclasses, Collections, Inspect, + #[strum(serialize = "_typeshed._type_checker_internals")] + TypeCheckerInternals, TyExtensions, } @@ -135,6 +137,7 @@ impl KnownModule { Self::Dataclasses => "dataclasses", Self::Collections => "collections", Self::Inspect => "inspect", + Self::TypeCheckerInternals => "_typeshed._type_checker_internals", Self::TyExtensions => "ty_extensions", } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index d5763dfe62812c..5e2e780d8bcba1 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1289,6 +1289,14 @@ impl<'db> ClassLiteral<'db> { Some(Type::Callable(CallableType::single(db, signature))) } + (CodeGeneratorKind::NamedTuple, name) if name != "__init__" => { + KnownClass::NamedTupleFallback + .to_class_literal(db) + .into_class_literal()? + .own_class_member(db, None, name) + .symbol + .ignore_possibly_unbound() + } _ => None, } } @@ -1985,6 +1993,8 @@ pub enum KnownClass { NotImplementedType, // dataclasses Field, + // _typeshed._type_checker_internals + NamedTupleFallback, } impl<'db> KnownClass { @@ -2067,7 +2077,8 @@ impl<'db> KnownClass { // (see https://docs.python.org/3/library/constants.html#NotImplemented) | Self::NotImplementedType | Self::Classmethod - | Self::Field => Truthiness::Ambiguous, + | Self::Field + | Self::NamedTupleFallback => Truthiness::Ambiguous, } } @@ -2141,7 +2152,8 @@ impl<'db> KnownClass { | Self::EllipsisType | Self::NotImplementedType | Self::UnionType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2217,6 +2229,7 @@ impl<'db> KnownClass { } Self::NotImplementedType => "_NotImplementedType", Self::Field => "Field", + Self::NamedTupleFallback => "NamedTupleFallback", } } @@ -2446,6 +2459,7 @@ impl<'db> KnownClass { | Self::Deque | Self::OrderedDict => KnownModule::Collections, Self::Field => KnownModule::Dataclasses, + Self::NamedTupleFallback => KnownModule::TypeCheckerInternals, } } @@ -2508,7 +2522,8 @@ impl<'db> KnownClass { | Self::Super | Self::NamedTuple | Self::NewType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2573,7 +2588,8 @@ impl<'db> KnownClass { | Self::UnionType | Self::NamedTuple | Self::NewType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2646,6 +2662,7 @@ impl<'db> KnownClass { } "_NotImplementedType" => Self::NotImplementedType, "Field" => Self::Field, + "NamedTupleFallback" => Self::NamedTupleFallback, _ => return None, }; @@ -2700,7 +2717,8 @@ impl<'db> KnownClass { | Self::GeneratorType | Self::AsyncGeneratorType | Self::WrapperDescriptorType - | Self::Field => module == self.canonical_module(db), + | Self::Field + | Self::NamedTupleFallback => module == self.canonical_module(db), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::SpecialForm | Self::TypeVar From 9ae698fe30cf3526f0e7ae237b800b3ed19a819f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 16 May 2025 13:25:28 +0200 Subject: [PATCH 124/487] Switch to Rust 2024 edition (#18129) --- .github/workflows/ci.yaml | 4 +- Cargo.toml | 2 +- crates/ruff/src/args.rs | 26 +- crates/ruff/src/cache.rs | 12 +- crates/ruff/src/commands/add_noqa.rs | 2 +- crates/ruff/src/commands/analyze_graph.rs | 4 +- crates/ruff/src/commands/check.rs | 10 +- crates/ruff/src/commands/check_stdin.rs | 4 +- crates/ruff/src/commands/config.rs | 2 +- crates/ruff/src/commands/format.rs | 67 +- crates/ruff/src/commands/format_stdin.rs | 8 +- crates/ruff/src/commands/show_files.rs | 2 +- crates/ruff/src/commands/show_settings.rs | 4 +- crates/ruff/src/diagnostics.rs | 6 +- crates/ruff/src/lib.rs | 8 +- crates/ruff/src/main.rs | 2 +- crates/ruff/src/printer.rs | 28 +- crates/ruff/src/resolve.rs | 6 +- crates/ruff/tests/lint.rs | 64 +- .../src/renderer/display_list.rs | 9 +- .../tests/fixtures/main.rs | 2 +- crates/ruff_benchmark/benches/formatter.rs | 8 +- crates/ruff_benchmark/benches/lexer.rs | 6 +- crates/ruff_benchmark/benches/linter.rs | 12 +- crates/ruff_benchmark/benches/parser.rs | 6 +- crates/ruff_benchmark/benches/ty.rs | 4 +- crates/ruff_cache/src/cache_key.rs | 4 +- crates/ruff_db/src/diagnostic/mod.rs | 2 +- crates/ruff_db/src/diagnostic/render.rs | 8 +- crates/ruff_db/src/files.rs | 4 +- crates/ruff_db/src/files/file_root.rs | 2 +- crates/ruff_db/src/files/path.rs | 4 +- crates/ruff_db/src/parsed.rs | 6 +- crates/ruff_db/src/source.rs | 10 +- crates/ruff_db/src/system.rs | 6 +- crates/ruff_db/src/system/memory_fs.rs | 8 +- crates/ruff_db/src/system/os.rs | 10 +- crates/ruff_db/src/system/test.rs | 8 +- crates/ruff_db/src/testing.rs | 2 +- crates/ruff_db/src/vendored.rs | 10 +- crates/ruff_dev/src/format_dev.rs | 12 +- crates/ruff_dev/src/generate_cli_help.rs | 6 +- crates/ruff_dev/src/generate_json_schema.rs | 6 +- crates/ruff_dev/src/generate_options.rs | 4 +- crates/ruff_dev/src/generate_rules_table.rs | 16 +- .../ruff_dev/src/generate_ty_cli_reference.rs | 38 +- crates/ruff_dev/src/generate_ty_options.rs | 4 +- crates/ruff_dev/src/generate_ty_rules.rs | 6 +- crates/ruff_dev/src/generate_ty_schema.rs | 6 +- crates/ruff_dev/src/print_ast.rs | 2 +- crates/ruff_dev/src/print_cst.rs | 2 +- crates/ruff_formatter/src/arguments.rs | 6 +- crates/ruff_formatter/src/buffer.rs | 2 +- crates/ruff_formatter/src/builders.rs | 13 +- crates/ruff_formatter/src/diagnostics.rs | 61 +- crates/ruff_formatter/src/format_element.rs | 2 +- .../src/format_element/document.rs | 6 +- .../ruff_formatter/src/format_element/tag.rs | 5 +- crates/ruff_formatter/src/lib.rs | 4 +- crates/ruff_formatter/src/macros.rs | 60 +- crates/ruff_formatter/src/prelude.rs | 4 +- .../ruff_formatter/src/printer/call_stack.rs | 2 +- .../src/printer/line_suffixes.rs | 2 +- crates/ruff_formatter/src/printer/mod.rs | 34 +- crates/ruff_formatter/src/printer/queue.rs | 2 +- crates/ruff_formatter/src/source_code.rs | 5 +- crates/ruff_graph/src/collector.rs | 2 +- crates/ruff_graph/src/db.rs | 4 +- crates/ruff_graph/src/lib.rs | 2 +- crates/ruff_graph/src/resolver.rs | 2 +- crates/ruff_index/src/slice.rs | 2 +- crates/ruff_index/src/vec.rs | 4 +- .../src/checkers/ast/analyze/expression.rs | 2 +- .../src/checkers/ast/annotation.rs | 4 +- crates/ruff_linter/src/checkers/ast/mod.rs | 16 +- crates/ruff_linter/src/checkers/filesystem.rs | 2 +- crates/ruff_linter/src/checkers/imports.rs | 2 +- .../ruff_linter/src/checkers/logical_lines.rs | 11 +- crates/ruff_linter/src/checkers/noqa.rs | 2 +- .../src/checkers/physical_lines.rs | 4 +- crates/ruff_linter/src/checkers/tokens.rs | 2 +- crates/ruff_linter/src/cst/helpers.rs | 4 +- crates/ruff_linter/src/cst/matchers.rs | 2 +- crates/ruff_linter/src/directives.rs | 6 +- crates/ruff_linter/src/doc_lines.rs | 2 +- crates/ruff_linter/src/docstrings/sections.rs | 3 +- crates/ruff_linter/src/fix/codemods.rs | 8 +- crates/ruff_linter/src/fix/edits.rs | 10 +- crates/ruff_linter/src/fix/mod.rs | 6 +- crates/ruff_linter/src/importer/insertion.rs | 4 +- crates/ruff_linter/src/importer/mod.rs | 2 +- crates/ruff_linter/src/lib.rs | 2 +- crates/ruff_linter/src/line_width.rs | 2 +- crates/ruff_linter/src/linter.rs | 22 +- crates/ruff_linter/src/message/azure.rs | 2 +- crates/ruff_linter/src/message/github.rs | 6 +- crates/ruff_linter/src/message/gitlab.rs | 4 +- crates/ruff_linter/src/message/grouped.rs | 4 +- crates/ruff_linter/src/message/json.rs | 4 +- crates/ruff_linter/src/message/junit.rs | 4 +- crates/ruff_linter/src/message/mod.rs | 6 +- crates/ruff_linter/src/message/pylint.rs | 2 +- crates/ruff_linter/src/message/rdjson.rs | 4 +- crates/ruff_linter/src/message/sarif.rs | 4 +- crates/ruff_linter/src/message/text.rs | 4 +- crates/ruff_linter/src/noqa.rs | 26 +- crates/ruff_linter/src/pyproject_toml.rs | 2 +- crates/ruff_linter/src/renamer.rs | 14 +- crates/ruff_linter/src/rule_selector.rs | 14 +- .../airflow/rules/dag_schedule_argument.rs | 2 +- .../airflow/rules/moved_to_provider_in_3.rs | 1106 ++++++++++------- .../src/rules/airflow/rules/removal_in_3.rs | 285 +++-- .../suggested_to_move_to_provider_in_3.rs | 200 +-- .../airflow/rules/suggested_to_update_3_0.rs | 35 +- .../rules/airflow/rules/task_variable_name.rs | 2 +- .../eradicate/rules/commented_out_code.rs | 6 +- .../rules/fastapi_non_annotated_dependency.rs | 2 +- .../rules/fastapi_redundant_response_model.rs | 4 +- .../rules/fastapi_unused_path_parameter.rs | 6 +- .../src/rules/fastapi/rules/mod.rs | 2 +- .../src/rules/flake8_2020/rules/compare.rs | 32 +- .../flake8_2020/rules/name_or_attribute.rs | 2 +- .../src/rules/flake8_2020/rules/subscript.rs | 2 +- .../src/rules/flake8_annotations/helpers.rs | 2 +- .../flake8_annotations/rules/definition.rs | 4 +- .../flake8_async/rules/async_busy_wait.rs | 2 +- .../rules/async_function_with_timeout.rs | 2 +- .../flake8_async/rules/async_zero_sleep.rs | 2 +- .../flake8_async/rules/blocking_http_call.rs | 2 +- .../flake8_async/rules/blocking_open_call.rs | 4 +- .../rules/blocking_process_invocation.rs | 18 +- .../flake8_async/rules/blocking_sleep.rs | 2 +- .../rules/cancel_scope_no_checkpoint.rs | 8 +- .../rules/long_sleep_not_forever.rs | 2 +- .../src/rules/flake8_async/rules/sync_call.rs | 2 +- .../src/rules/flake8_bandit/mod.rs | 2 +- .../rules/flake8_bandit/rules/assert_used.rs | 2 +- .../rules/bad_file_permissions.rs | 2 +- .../rules/flake8_bandit/rules/django_extra.rs | 2 +- .../flake8_bandit/rules/django_raw_sql.rs | 2 +- .../rules/flake8_bandit/rules/exec_used.rs | 2 +- .../flake8_bandit/rules/flask_debug_true.rs | 2 +- .../rules/hardcoded_bind_all_interfaces.rs | 2 +- .../rules/hardcoded_password_default.rs | 2 +- .../rules/hardcoded_password_func_arg.rs | 2 +- .../rules/hardcoded_password_string.rs | 2 +- .../rules/hardcoded_sql_expression.rs | 4 +- .../rules/hardcoded_tmp_directory.rs | 2 +- .../rules/hashlib_insecure_hash_functions.rs | 2 +- .../rules/jinja2_autoescape_false.rs | 2 +- .../rules/logging_config_insecure_listen.rs | 2 +- .../flake8_bandit/rules/mako_templates.rs | 2 +- .../flake8_bandit/rules/paramiko_calls.rs | 2 +- .../rules/request_with_no_cert_validation.rs | 16 +- .../rules/request_without_timeout.rs | 27 +- .../flake8_bandit/rules/shell_injection.rs | 2 +- .../rules/snmp_insecure_version.rs | 2 +- .../rules/snmp_weak_cryptography.rs | 2 +- .../rules/ssh_no_host_key_verification.rs | 2 +- .../rules/ssl_insecure_version.rs | 2 +- .../rules/ssl_with_bad_defaults.rs | 2 +- .../rules/ssl_with_no_version.rs | 2 +- .../rules/suspicious_function_call.rs | 101 +- .../flake8_bandit/rules/suspicious_imports.rs | 2 +- .../rules/tarfile_unsafe_members.rs | 2 +- .../rules/try_except_continue.rs | 2 +- .../flake8_bandit/rules/try_except_pass.rs | 2 +- .../flake8_bandit/rules/unsafe_markup_use.rs | 2 +- .../flake8_bandit/rules/unsafe_yaml_load.rs | 2 +- .../rules/weak_cryptographic_key.rs | 65 +- .../flake8_blind_except/rules/blind_except.rs | 6 +- .../src/rules/flake8_boolean_trap/mod.rs | 2 +- ...olean_default_value_positional_argument.rs | 2 +- .../rules/boolean_positional_value_in_call.rs | 2 +- .../boolean_type_hint_positional_argument.rs | 4 +- .../rules/abstract_base_class.rs | 4 +- .../flake8_bugbear/rules/assert_false.rs | 2 +- .../rules/assert_raises_exception.rs | 2 +- .../rules/assignment_to_os_environ.rs | 2 +- .../rules/batched_without_explicit_strict.rs | 2 +- .../rules/cached_instance_method.rs | 2 +- .../rules/class_as_data_structure.rs | 2 +- .../rules/duplicate_exceptions.rs | 2 +- .../flake8_bugbear/rules/duplicate_value.rs | 4 +- .../rules/except_with_empty_tuple.rs | 2 +- .../except_with_non_exception_classes.rs | 2 +- .../rules/f_string_docstring.rs | 2 +- .../function_call_in_argument_default.rs | 8 +- .../rules/function_uses_loop_variable.rs | 2 +- .../rules/getattr_with_constant.rs | 2 +- .../rules/jump_statement_in_finally.rs | 2 +- .../rules/loop_iterator_mutation.rs | 4 +- .../rules/loop_variable_overrides_iterator.rs | 2 +- .../rules/mutable_argument_default.rs | 6 +- .../rules/mutable_contextvar_default.rs | 4 +- .../rules/no_explicit_stacklevel.rs | 2 +- .../flake8_bugbear/rules/raise_literal.rs | 2 +- .../rules/raise_without_from_inside_except.rs | 2 +- .../rules/re_sub_positional_args.rs | 2 +- .../redundant_tuple_in_exception_handler.rs | 2 +- .../rules/return_in_generator.rs | 2 +- .../rules/reuse_of_groupby_generator.rs | 2 +- .../rules/setattr_with_constant.rs | 2 +- .../star_arg_unpacking_after_keyword_arg.rs | 2 +- .../rules/static_key_dict_comprehension.rs | 2 +- .../rules/strip_with_multi_characters.rs | 2 +- .../rules/unary_prefix_increment_decrement.rs | 2 +- .../rules/unintentional_type_annotation.rs | 2 +- .../rules/unreliable_callable_check.rs | 2 +- .../rules/unused_loop_control_variable.rs | 2 +- .../rules/useless_comparison.rs | 2 +- .../rules/useless_contextlib_suppress.rs | 2 +- .../rules/useless_expression.rs | 4 +- .../rules/zip_without_explicit_strict.rs | 2 +- .../rules/builtin_argument_shadowing.rs | 2 +- .../rules/builtin_attribute_shadowing.rs | 2 +- .../rules/builtin_import_shadowing.rs | 2 +- .../builtin_lambda_argument_shadowing.rs | 2 +- .../rules/builtin_variable_shadowing.rs | 2 +- .../rules/stdlib_module_shadowing.rs | 2 +- .../flake8_commas/rules/trailing_commas.rs | 2 +- .../src/rules/flake8_comprehensions/fixes.rs | 14 +- .../src/rules/flake8_comprehensions/mod.rs | 2 +- .../rules/unnecessary_call_around_sorted.rs | 2 +- .../rules/unnecessary_collection_call.rs | 2 +- .../rules/unnecessary_comprehension.rs | 26 +- .../unnecessary_comprehension_in_call.rs | 2 +- ...cessary_dict_comprehension_for_iterable.rs | 2 +- .../unnecessary_double_cast_or_process.rs | 2 +- .../rules/unnecessary_generator_dict.rs | 2 +- .../rules/unnecessary_generator_list.rs | 4 +- .../rules/unnecessary_generator_set.rs | 4 +- .../rules/unnecessary_list_call.rs | 2 +- .../unnecessary_list_comprehension_dict.rs | 2 +- .../unnecessary_list_comprehension_set.rs | 2 +- .../rules/unnecessary_literal_dict.rs | 2 +- .../rules/unnecessary_literal_set.rs | 2 +- .../unnecessary_literal_within_dict_call.rs | 2 +- .../unnecessary_literal_within_list_call.rs | 2 +- .../unnecessary_literal_within_tuple_call.rs | 2 +- .../rules/unnecessary_map.rs | 4 +- .../rules/unnecessary_subscript_reversal.rs | 2 +- .../rules/missing_copyright_notice.rs | 4 +- .../rules/call_date_fromtimestamp.rs | 2 +- .../flake8_datetimez/rules/call_date_today.rs | 2 +- .../rules/call_datetime_fromtimestamp.rs | 2 +- .../rules/call_datetime_now_without_tzinfo.rs | 2 +- .../call_datetime_strptime_without_zone.rs | 2 +- .../rules/call_datetime_today.rs | 2 +- .../rules/call_datetime_utcfromtimestamp.rs | 2 +- .../rules/call_datetime_utcnow.rs | 2 +- .../rules/call_datetime_without_tzinfo.rs | 2 +- .../rules/datetime_min_max.rs | 2 +- .../rules/flake8_debugger/rules/debugger.rs | 2 +- .../rules/all_with_model_form.rs | 2 +- .../rules/exclude_with_model_form.rs | 2 +- .../src/rules/flake8_django/rules/helpers.rs | 2 +- .../rules/locals_in_render_function.rs | 2 +- .../rules/model_without_dunder_str.rs | 4 +- .../rules/non_leading_receiver_decorator.rs | 2 +- .../rules/nullable_model_string_field.rs | 2 +- .../rules/unordered_body_content_in_model.rs | 6 +- .../rules/string_in_exception.rs | 4 +- .../src/rules/flake8_executable/rules/mod.rs | 2 +- .../rules/shebang_leading_whitespace.rs | 2 +- .../rules/shebang_missing_executable_file.rs | 2 +- .../rules/shebang_missing_python.rs | 2 +- .../rules/shebang_not_executable.rs | 2 +- .../rules/shebang_not_first_line.rs | 2 +- .../src/rules/flake8_fixme/rules/todos.rs | 2 +- .../rules/future_required_type_annotation.rs | 2 +- .../future_rewritable_type_annotation.rs | 2 +- .../rules/f_string_in_gettext_func_call.rs | 2 +- .../rules/format_in_gettext_func_call.rs | 2 +- .../rules/printf_in_gettext_func_call.rs | 2 +- .../rules/explicit.rs | 4 +- .../rules/implicit.rs | 4 +- .../rules/flake8_import_conventions/mod.rs | 2 +- .../rules/banned_import_alias.rs | 2 +- .../rules/banned_import_from.rs | 2 +- .../rules/unconventional_import_alias.rs | 2 +- .../rules/direct_logger_instantiation.rs | 2 +- .../rules/exc_info_outside_except_handler.rs | 4 +- .../rules/exception_without_exc_info.rs | 2 +- .../rules/invalid_get_logger_argument.rs | 2 +- .../log_exception_outside_except_handler.rs | 2 +- .../flake8_logging/rules/root_logger_call.rs | 2 +- .../flake8_logging/rules/undocumented_warn.rs | 2 +- .../rules/flake8_logging_format/violations.rs | 2 +- .../rules/implicit_namespace_package.rs | 14 +- .../rules/duplicate_class_field_definition.rs | 2 +- .../rules/multiple_starts_ends_with.rs | 4 +- .../flake8_pie/rules/non_unique_enums.rs | 2 +- .../rules/reimplemented_container_builtin.rs | 2 +- .../rules/unnecessary_dict_kwargs.rs | 4 +- .../rules/unnecessary_placeholder.rs | 2 +- .../rules/unnecessary_range_start.rs | 4 +- .../flake8_pie/rules/unnecessary_spread.rs | 2 +- .../rules/flake8_print/rules/print_call.rs | 2 +- .../flake8_pyi/rules/any_eq_ne_annotation.rs | 2 +- .../rules/bad_generator_return_type.rs | 2 +- .../rules/bad_version_info_comparison.rs | 2 +- .../flake8_pyi/rules/bytestring_usage.rs | 2 +- .../rules/collections_named_tuple.rs | 2 +- .../rules/complex_assignment_in_stub.rs | 2 +- .../rules/complex_if_statement_in_stub.rs | 2 +- .../rules/custom_type_var_for_self.rs | 4 +- .../flake8_pyi/rules/docstring_in_stubs.rs | 2 +- .../rules/duplicate_literal_member.rs | 2 +- .../rules/duplicate_union_member.rs | 2 +- .../rules/ellipsis_in_non_empty_class_body.rs | 2 +- .../flake8_pyi/rules/exit_annotations.rs | 34 +- .../rules/future_annotations_in_stub.rs | 2 +- .../rules/generic_not_last_base_class.rs | 4 +- .../rules/iter_method_return_iterable.rs | 2 +- .../src/rules/flake8_pyi/rules/mod.rs | 2 +- .../rules/no_return_argument_annotation.rs | 2 +- .../flake8_pyi/rules/non_empty_stub_body.rs | 2 +- .../flake8_pyi/rules/non_self_return_type.rs | 8 +- .../rules/numeric_literal_too_long.rs | 2 +- .../flake8_pyi/rules/pass_in_class_body.rs | 2 +- .../rules/pass_statement_stub_body.rs | 2 +- .../rules/pre_pep570_positional_argument.rs | 2 +- .../flake8_pyi/rules/prefix_type_params.rs | 2 +- .../rules/quoted_annotation_in_stub.rs | 2 +- .../rules/redundant_final_literal.rs | 4 +- .../rules/redundant_literal_union.rs | 4 +- .../rules/redundant_none_literal.rs | 5 +- .../rules/redundant_numeric_union.rs | 2 +- .../rules/flake8_pyi/rules/simple_defaults.rs | 10 +- .../rules/str_or_repr_defined_in_stub.rs | 2 +- .../rules/string_or_bytes_too_long.rs | 2 +- .../rules/stub_body_multiple_statements.rs | 4 +- .../flake8_pyi/rules/type_alias_naming.rs | 6 +- .../flake8_pyi/rules/type_comment_in_stub.rs | 2 +- .../unaliased_collections_abc_set_import.rs | 2 +- .../rules/unnecessary_literal_union.rs | 2 +- .../rules/unnecessary_type_union.rs | 2 +- .../flake8_pyi/rules/unrecognized_platform.rs | 2 +- .../rules/unrecognized_version_info.rs | 2 +- .../rules/unsupported_method_call_on_all.rs | 6 +- .../rules/unused_private_type_definition.rs | 8 +- .../flake8_pytest_style/rules/assertion.rs | 6 +- .../rules/flake8_pytest_style/rules/fail.rs | 2 +- .../flake8_pytest_style/rules/fixture.rs | 12 +- .../flake8_pytest_style/rules/imports.rs | 2 +- .../rules/flake8_pytest_style/rules/marks.rs | 4 +- .../flake8_pytest_style/rules/parametrize.rs | 6 +- .../rules/flake8_pytest_style/rules/patch.rs | 2 +- .../rules/flake8_pytest_style/rules/raises.rs | 2 +- .../rules/test_functions.rs | 2 +- .../rules/unittest_assert.rs | 2 +- .../rules/flake8_pytest_style/rules/warns.rs | 2 +- .../rules/avoidable_escaped_quote.rs | 6 +- .../rules/check_string_quotes.rs | 4 +- .../rules/unnecessary_escaped_quote.rs | 4 +- .../unnecessary_paren_on_raise_exception.rs | 2 +- .../src/rules/flake8_return/mod.rs | 2 +- .../src/rules/flake8_return/rules/function.rs | 8 +- .../rules/private_member_access.rs | 2 +- .../src/rules/flake8_simplify/mod.rs | 2 +- .../flake8_simplify/rules/ast_bool_op.rs | 4 +- .../rules/flake8_simplify/rules/ast_expr.rs | 6 +- .../rules/flake8_simplify/rules/ast_ifexp.rs | 2 +- .../flake8_simplify/rules/ast_unary_op.rs | 2 +- .../rules/flake8_simplify/rules/ast_with.rs | 16 +- .../flake8_simplify/rules/collapsible_if.rs | 32 +- .../rules/enumerate_for_loop.rs | 4 +- .../rules/flake8_simplify/rules/fix_with.rs | 6 +- .../if_else_block_instead_of_dict_get.rs | 14 +- .../if_else_block_instead_of_dict_lookup.rs | 2 +- .../rules/if_else_block_instead_of_if_exp.rs | 38 +- .../rules/if_with_same_arms.rs | 6 +- .../flake8_simplify/rules/key_in_dict.rs | 4 +- .../flake8_simplify/rules/needless_bool.rs | 42 +- .../rules/open_file_with_context_handler.rs | 2 +- .../rules/reimplemented_builtin.rs | 36 +- .../rules/return_in_try_except_finally.rs | 2 +- .../rules/split_static_string.rs | 6 +- .../rules/suppressible_exception.rs | 2 +- .../flake8_simplify/rules/yoda_conditions.rs | 4 +- .../rules/zip_dict_keys_and_values.rs | 10 +- .../rules/no_slots_in_namedtuple_subclass.rs | 4 +- .../rules/no_slots_in_str_subclass.rs | 4 +- .../rules/no_slots_in_tuple_subclass.rs | 2 +- .../flake8_tidy_imports/rules/banned_api.rs | 2 +- .../rules/banned_module_level_imports.rs | 2 +- .../rules/relative_imports.rs | 2 +- .../src/rules/flake8_todos/rules/todos.rs | 4 +- .../src/rules/flake8_type_checking/helpers.rs | 6 +- .../rules/empty_type_checking_block.rs | 2 +- .../rules/runtime_cast_value.rs | 2 +- .../runtime_import_in_type_checking_block.rs | 2 +- .../rules/runtime_string_union.rs | 2 +- .../rules/type_alias_quotes.rs | 2 +- .../rules/typing_only_runtime_import.rs | 4 +- .../rules/unused_arguments.rs | 13 +- .../flake8_use_pathlib/rules/glob_rule.rs | 2 +- .../rules/invalid_pathlib_with_suffix.rs | 4 +- .../rules/os_path_getatime.rs | 2 +- .../rules/os_path_getctime.rs | 2 +- .../rules/os_path_getmtime.rs | 2 +- .../rules/os_path_getsize.rs | 2 +- .../flake8_use_pathlib/rules/os_sep_split.rs | 2 +- .../path_constructor_current_directory.rs | 4 +- .../rules/replaceable_by_pathlib.rs | 2 +- .../rules/flake8_use_pathlib/violations.rs | 2 +- .../flynt/rules/static_join_to_fstring.rs | 2 +- crates/ruff_linter/src/rules/isort/block.rs | 2 +- .../ruff_linter/src/rules/isort/categorize.rs | 6 +- crates/ruff_linter/src/rules/isort/helpers.rs | 2 +- crates/ruff_linter/src/rules/isort/mod.rs | 4 +- .../ruff_linter/src/rules/isort/normalize.rs | 2 +- crates/ruff_linter/src/rules/isort/order.rs | 6 +- .../rules/isort/rules/add_required_imports.rs | 4 +- .../src/rules/isort/rules/organize_imports.rs | 6 +- .../ruff_linter/src/rules/isort/settings.rs | 2 +- .../mccabe/rules/function_is_too_complex.rs | 2 +- crates/ruff_linter/src/rules/numpy/helpers.rs | 4 +- .../rules/numpy/rules/deprecated_function.rs | 2 +- .../numpy/rules/deprecated_type_alias.rs | 2 +- .../src/rules/numpy/rules/legacy_random.rs | 2 +- .../numpy/rules/numpy_2_0_deprecation.rs | 38 +- .../pandas_vet/rules/assignment_to_df.rs | 2 +- .../src/rules/pandas_vet/rules/attr.rs | 4 +- .../src/rules/pandas_vet/rules/call.rs | 4 +- .../pandas_vet/rules/inplace_argument.rs | 6 +- .../rules/nunique_constant_series_check.rs | 4 +- .../src/rules/pandas_vet/rules/pd_merge.rs | 2 +- .../src/rules/pandas_vet/rules/read_table.rs | 2 +- .../src/rules/pandas_vet/rules/subscript.rs | 4 +- .../src/rules/pep8_naming/helpers.rs | 2 +- .../rules/camelcase_imported_as_acronym.rs | 2 +- .../rules/camelcase_imported_as_constant.rs | 2 +- .../rules/camelcase_imported_as_lowercase.rs | 2 +- .../constant_imported_as_non_constant.rs | 2 +- .../pep8_naming/rules/dunder_function_name.rs | 2 +- .../rules/error_suffix_on_exception_name.rs | 2 +- .../rules/invalid_argument_name.rs | 4 +- .../pep8_naming/rules/invalid_class_name.rs | 2 +- .../rules/invalid_first_argument_name.rs | 4 +- .../rules/invalid_function_name.rs | 4 +- .../pep8_naming/rules/invalid_module_name.rs | 2 +- .../lowercase_imported_as_non_lowercase.rs | 2 +- .../mixed_case_variable_in_class_scope.rs | 2 +- .../mixed_case_variable_in_global_scope.rs | 2 +- .../non_lowercase_variable_in_function.rs | 2 +- crates/ruff_linter/src/rules/perflint/mod.rs | 2 +- .../perflint/rules/incorrect_dict_iterator.rs | 2 +- .../rules/manual_dict_comprehension.rs | 32 +- .../rules/manual_list_comprehension.rs | 20 +- .../rules/perflint/rules/manual_list_copy.rs | 2 +- .../perflint/rules/try_except_in_loop.rs | 4 +- .../perflint/rules/unnecessary_list_cast.rs | 4 +- .../ruff_linter/src/rules/pycodestyle/mod.rs | 4 +- .../src/rules/pycodestyle/overlong.rs | 2 +- .../pycodestyle/rules/ambiguous_class_name.rs | 2 +- .../rules/ambiguous_function_name.rs | 2 +- .../rules/ambiguous_variable_name.rs | 2 +- .../rules/pycodestyle/rules/bare_except.rs | 2 +- .../rules/pycodestyle/rules/blank_lines.rs | 4 +- .../pycodestyle/rules/compound_statements.rs | 2 +- .../pycodestyle/rules/doc_line_too_long.rs | 2 +- .../src/rules/pycodestyle/rules/errors.rs | 2 +- .../rules/invalid_escape_sequence.rs | 4 +- .../pycodestyle/rules/lambda_assignment.rs | 2 +- .../rules/pycodestyle/rules/line_too_long.rs | 2 +- .../pycodestyle/rules/literal_comparisons.rs | 2 +- .../logical_lines/extraneous_whitespace.rs | 2 +- .../rules/logical_lines/indentation.rs | 2 +- .../rules/logical_lines/missing_whitespace.rs | 2 +- .../missing_whitespace_after_keyword.rs | 2 +- .../missing_whitespace_around_operator.rs | 2 +- .../pycodestyle/rules/logical_lines/mod.rs | 2 +- .../logical_lines/redundant_backslash.rs | 4 +- .../logical_lines/space_around_operator.rs | 2 +- .../whitespace_around_keywords.rs | 2 +- ...hitespace_around_named_parameter_equals.rs | 2 +- .../whitespace_before_comment.rs | 4 +- .../whitespace_before_parameters.rs | 2 +- .../rules/missing_newline_at_end_of_file.rs | 2 +- .../rules/mixed_spaces_and_tabs.rs | 2 +- .../rules/module_import_not_at_top_of_file.rs | 2 +- .../rules/multiple_imports_on_one_line.rs | 4 +- .../src/rules/pycodestyle/rules/not_tests.rs | 2 +- .../pycodestyle/rules/tab_indentation.rs | 2 +- .../rules/too_many_newlines_at_end_of_file.rs | 2 +- .../pycodestyle/rules/trailing_whitespace.rs | 4 +- .../pycodestyle/rules/type_comparison.rs | 2 +- .../rules/whitespace_after_decorator.rs | 2 +- .../rules/pydoclint/rules/check_docstring.rs | 32 +- .../src/rules/pydocstyle/helpers.rs | 2 +- .../src/rules/pydocstyle/rules/backslashes.rs | 2 +- .../pydocstyle/rules/blank_after_summary.rs | 2 +- .../rules/blank_before_after_class.rs | 4 +- .../rules/blank_before_after_function.rs | 2 +- .../src/rules/pydocstyle/rules/capitalized.rs | 2 +- .../pydocstyle/rules/ends_with_period.rs | 4 +- .../pydocstyle/rules/ends_with_punctuation.rs | 4 +- .../src/rules/pydocstyle/rules/if_needed.rs | 2 +- .../src/rules/pydocstyle/rules/indent.rs | 2 +- .../rules/multi_line_summary_start.rs | 2 +- .../rules/newline_after_last_paragraph.rs | 2 +- .../rules/pydocstyle/rules/no_signature.rs | 2 +- .../rules/no_surrounding_whitespace.rs | 2 +- .../pydocstyle/rules/non_imperative_mood.rs | 2 +- .../src/rules/pydocstyle/rules/not_empty.rs | 2 +- .../src/rules/pydocstyle/rules/not_missing.rs | 4 +- .../src/rules/pydocstyle/rules/one_liner.rs | 2 +- .../src/rules/pydocstyle/rules/sections.rs | 4 +- .../pydocstyle/rules/starts_with_this.rs | 2 +- .../rules/pydocstyle/rules/triple_quotes.rs | 2 +- .../ruff_linter/src/rules/pyflakes/fixes.rs | 2 +- crates/ruff_linter/src/rules/pyflakes/mod.rs | 4 +- .../src/rules/pyflakes/rules/assert_tuple.rs | 2 +- .../pyflakes/rules/break_outside_loop.rs | 2 +- .../pyflakes/rules/continue_outside_loop.rs | 2 +- .../pyflakes/rules/default_except_not_last.rs | 2 +- .../rules/f_string_missing_placeholders.rs | 4 +- .../rules/forward_annotation_syntax_error.rs | 2 +- .../rules/future_feature_not_defined.rs | 2 +- .../src/rules/pyflakes/rules/if_tuple.rs | 2 +- .../src/rules/pyflakes/rules/imports.rs | 2 +- .../rules/invalid_literal_comparisons.rs | 6 +- .../pyflakes/rules/invalid_print_syntax.rs | 2 +- .../pyflakes/rules/raise_not_implemented.rs | 2 +- .../pyflakes/rules/redefined_while_unused.rs | 2 +- .../src/rules/pyflakes/rules/repeated_keys.rs | 2 +- .../pyflakes/rules/return_outside_function.rs | 2 +- .../pyflakes/rules/starred_expressions.rs | 2 +- .../src/rules/pyflakes/rules/strings.rs | 2 +- .../rules/pyflakes/rules/undefined_export.rs | 2 +- .../rules/pyflakes/rules/undefined_local.rs | 2 +- .../rules/pyflakes/rules/undefined_name.rs | 2 +- .../rules/pyflakes/rules/unused_annotation.rs | 2 +- .../src/rules/pyflakes/rules/unused_import.rs | 18 +- .../rules/pyflakes/rules/unused_variable.rs | 2 +- .../pyflakes/rules/yield_outside_function.rs | 2 +- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 4 +- .../pygrep_hooks/rules/blanket_type_ignore.rs | 4 +- .../pygrep_hooks/rules/deprecated_log_warn.rs | 2 +- .../pygrep_hooks/rules/invalid_mock_access.rs | 2 +- .../src/rules/pygrep_hooks/rules/no_eval.rs | 2 +- .../ruff_linter/src/rules/pylint/helpers.rs | 2 +- crates/ruff_linter/src/rules/pylint/mod.rs | 2 +- .../pylint/rules/assert_on_string_literal.rs | 2 +- .../rules/pylint/rules/await_outside_async.rs | 2 +- .../pylint/rules/bad_dunder_method_name.rs | 2 +- .../src/rules/pylint/rules/bad_open_mode.rs | 2 +- .../pylint/rules/bad_staticmethod_argument.rs | 4 +- .../rules/pylint/rules/bad_str_strip_call.rs | 4 +- .../rules/bad_string_format_character.rs | 2 +- .../pylint/rules/bad_string_format_type.rs | 2 +- .../pylint/rules/bidirectional_unicode.rs | 2 +- .../rules/pylint/rules/binary_op_exception.rs | 2 +- .../rules/boolean_chained_comparison.rs | 4 +- .../rules/pylint/rules/collapsible_else_if.rs | 4 +- .../pylint/rules/compare_to_empty_string.rs | 2 +- .../pylint/rules/comparison_of_constant.rs | 2 +- .../pylint/rules/comparison_with_itself.rs | 2 +- .../rules/pylint/rules/continue_in_finally.rs | 2 +- .../pylint/rules/dict_index_missing_items.rs | 7 +- .../pylint/rules/dict_iter_missing_items.rs | 2 +- .../src/rules/pylint/rules/duplicate_bases.rs | 4 +- .../src/rules/pylint/rules/empty_comment.rs | 4 +- .../src/rules/pylint/rules/eq_without_hash.rs | 4 +- .../pylint/rules/global_at_module_level.rs | 2 +- .../rules/pylint/rules/global_statement.rs | 2 +- .../rules/global_variable_not_assigned.rs | 2 +- .../src/rules/pylint/rules/if_stmt_min_max.rs | 14 +- .../pylint/rules/import_outside_top_level.rs | 2 +- .../rules/pylint/rules/import_private_name.rs | 2 +- .../src/rules/pylint/rules/import_self.rs | 2 +- .../rules/pylint/rules/invalid_all_format.rs | 2 +- .../rules/pylint/rules/invalid_all_object.rs | 2 +- .../rules/pylint/rules/invalid_bool_return.rs | 2 +- .../pylint/rules/invalid_bytes_return.rs | 2 +- .../pylint/rules/invalid_envvar_default.rs | 4 +- .../pylint/rules/invalid_envvar_value.rs | 4 +- .../rules/pylint/rules/invalid_hash_return.rs | 2 +- .../pylint/rules/invalid_index_return.rs | 2 +- .../pylint/rules/invalid_length_return.rs | 2 +- .../rules/pylint/rules/invalid_str_return.rs | 2 +- .../pylint/rules/invalid_string_characters.rs | 2 +- .../rules/pylint/rules/iteration_over_set.rs | 4 +- .../src/rules/pylint/rules/len_test.rs | 2 +- .../rules/pylint/rules/literal_membership.rs | 2 +- .../rules/load_before_global_declaration.rs | 2 +- .../src/rules/pylint/rules/logging.rs | 2 +- .../pylint/rules/magic_value_comparison.rs | 2 +- .../rules/pylint/rules/manual_import_from.rs | 2 +- .../pylint/rules/misplaced_bare_raise.rs | 2 +- .../pylint/rules/modified_iterating_set.rs | 2 +- .../rules/named_expr_without_context.rs | 2 +- .../src/rules/pylint/rules/nan_comparison.rs | 2 +- .../src/rules/pylint/rules/nested_min_max.rs | 2 +- .../rules/pylint/rules/no_method_decorator.rs | 2 +- .../src/rules/pylint/rules/no_self_use.rs | 4 +- .../pylint/rules/non_ascii_module_import.rs | 2 +- .../src/rules/pylint/rules/non_ascii_name.rs | 2 +- .../pylint/rules/non_augmented_assignment.rs | 2 +- .../rules/pylint/rules/non_slot_assignment.rs | 2 +- .../rules/pylint/rules/nonlocal_and_global.rs | 2 +- .../pylint/rules/nonlocal_without_binding.rs | 2 +- .../pylint/rules/potential_index_error.rs | 2 +- .../pylint/rules/property_with_parameters.rs | 4 +- .../pylint/rules/redeclared_assigned_name.rs | 2 +- .../rules/redefined_argument_from_local.rs | 2 +- .../rules/pylint/rules/redefined_loop_name.rs | 4 +- .../rules/redefined_slots_in_subclass.rs | 4 +- .../rules/repeated_equality_comparison.rs | 8 +- .../pylint/rules/repeated_isinstance_calls.rs | 2 +- .../pylint/rules/repeated_keyword_argument.rs | 2 +- .../src/rules/pylint/rules/return_in_init.rs | 2 +- .../pylint/rules/self_assigning_variable.rs | 2 +- .../pylint/rules/self_or_cls_assignment.rs | 4 +- .../pylint/rules/shallow_copy_environ.rs | 2 +- .../rules/pylint/rules/single_string_slots.rs | 2 +- .../pylint/rules/singledispatch_method.rs | 4 +- .../rules/singledispatchmethod_function.rs | 4 +- .../rules/subprocess_popen_preexec_fn.rs | 2 +- .../rules/subprocess_run_without_check.rs | 2 +- .../pylint/rules/super_without_brackets.rs | 4 +- .../src/rules/pylint/rules/sys_exit_alias.rs | 2 +- .../rules/pylint/rules/too_many_arguments.rs | 2 +- .../rules/too_many_boolean_expressions.rs | 2 +- .../rules/pylint/rules/too_many_branches.rs | 2 +- .../src/rules/pylint/rules/too_many_locals.rs | 2 +- .../pylint/rules/too_many_nested_blocks.rs | 2 +- .../rules/too_many_positional_arguments.rs | 2 +- .../pylint/rules/too_many_public_methods.rs | 2 +- .../rules/too_many_return_statements.rs | 2 +- .../rules/pylint/rules/too_many_statements.rs | 2 +- .../src/rules/pylint/rules/type_bivariance.rs | 2 +- .../rules/type_name_incorrect_variance.rs | 6 +- .../pylint/rules/type_param_name_mismatch.rs | 2 +- .../unexpected_special_method_signature.rs | 2 +- .../rules/unnecessary_dict_index_lookup.rs | 2 +- .../rules/unnecessary_direct_lambda_call.rs | 2 +- .../pylint/rules/unnecessary_dunder_call.rs | 2 +- .../rules/pylint/rules/unnecessary_lambda.rs | 4 +- .../rules/unnecessary_list_index_lookup.rs | 2 +- .../src/rules/pylint/rules/unreachable.rs | 4 +- .../pylint/rules/unspecified_encoding.rs | 15 +- .../pylint/rules/useless_else_on_loop.rs | 4 +- .../rules/useless_exception_statement.rs | 2 +- .../pylint/rules/useless_import_alias.rs | 2 +- .../src/rules/pylint/rules/useless_return.rs | 2 +- .../rules/pylint/rules/useless_with_lock.rs | 2 +- .../rules/yield_from_in_async_function.rs | 2 +- .../src/rules/pylint/rules/yield_in_init.rs | 2 +- ...convert_named_tuple_functional_to_class.rs | 2 +- .../convert_typed_dict_functional_to_class.rs | 2 +- .../pyupgrade/rules/datetime_utc_alias.rs | 2 +- .../rules/deprecated_c_element_tree.rs | 2 +- .../pyupgrade/rules/deprecated_import.rs | 4 +- .../pyupgrade/rules/deprecated_mock_import.rs | 4 +- .../rules/deprecated_unittest_alias.rs | 2 +- .../pyupgrade/rules/extraneous_parentheses.rs | 2 +- .../src/rules/pyupgrade/rules/f_strings.rs | 4 +- .../rules/pyupgrade/rules/format_literals.rs | 6 +- .../rules/lru_cache_with_maxsize_none.rs | 2 +- .../rules/lru_cache_without_parameters.rs | 2 +- .../rules/pyupgrade/rules/native_literals.rs | 2 +- .../pyupgrade/rules/non_pep646_unpack.rs | 2 +- .../src/rules/pyupgrade/rules/open_alias.rs | 2 +- .../rules/pyupgrade/rules/os_error_alias.rs | 2 +- .../pyupgrade/rules/outdated_version_block.rs | 4 +- .../src/rules/pyupgrade/rules/pep695/mod.rs | 7 +- .../rules/pep695/non_pep695_generic_class.rs | 6 +- .../pep695/non_pep695_generic_function.rs | 6 +- .../rules/pep695/non_pep695_type_alias.rs | 16 +- .../rules/pep695/private_type_parameter.rs | 2 +- .../rules/printf_string_formatting.rs | 10 +- .../pyupgrade/rules/quoted_annotation.rs | 2 +- .../pyupgrade/rules/redundant_open_modes.rs | 2 +- .../pyupgrade/rules/replace_stdout_stderr.rs | 4 +- .../rules/pyupgrade/rules/replace_str_enum.rs | 2 +- .../rules/replace_universal_newlines.rs | 4 +- .../rules/super_call_with_parameters.rs | 2 +- .../pyupgrade/rules/timeout_error_alias.rs | 2 +- .../pyupgrade/rules/type_of_primitive.rs | 2 +- .../pyupgrade/rules/typing_text_str_alias.rs | 2 +- .../pyupgrade/rules/unicode_kind_prefix.rs | 2 +- .../rules/unnecessary_builtin_import.rs | 2 +- .../rules/unnecessary_class_parentheses.rs | 2 +- .../rules/unnecessary_coding_comment.rs | 2 +- .../rules/unnecessary_default_type_args.rs | 2 +- .../rules/unnecessary_encode_utf8.rs | 6 +- .../rules/unnecessary_future_import.rs | 2 +- .../pyupgrade/rules/use_pep585_annotation.rs | 2 +- .../pyupgrade/rules/use_pep604_annotation.rs | 4 +- .../pyupgrade/rules/use_pep604_isinstance.rs | 4 +- .../pyupgrade/rules/useless_metaclass_type.rs | 2 +- .../rules/useless_object_inheritance.rs | 4 +- .../pyupgrade/rules/yield_in_for_loop.rs | 2 +- .../src/rules/refurb/rules/bit_count.rs | 2 +- .../refurb/rules/check_and_remove_from_set.rs | 4 +- .../rules/refurb/rules/delete_full_slice.rs | 4 +- .../refurb/rules/for_loop_set_mutations.rs | 2 +- .../src/rules/refurb/rules/for_loop_writes.rs | 2 +- .../refurb/rules/fromisoformat_replace_z.rs | 2 +- .../refurb/rules/fstring_number_format.rs | 2 +- .../refurb/rules/hardcoded_string_charset.rs | 2 +- .../rules/refurb/rules/hashlib_digest_hex.rs | 2 +- .../rules/if_exp_instead_of_or_operator.rs | 6 +- .../src/rules/refurb/rules/if_expr_min_max.rs | 2 +- .../src/rules/refurb/rules/implicit_cwd.rs | 2 +- .../rules/refurb/rules/int_on_sliced_str.rs | 2 +- .../refurb/rules/isinstance_type_none.rs | 2 +- .../rules/refurb/rules/list_reverse_copy.rs | 4 +- .../src/rules/refurb/rules/math_constant.rs | 2 +- .../rules/refurb/rules/metaclass_abcmeta.rs | 2 +- .../rules/refurb/rules/print_empty_string.rs | 2 +- .../src/rules/refurb/rules/read_whole_file.rs | 4 +- .../rules/refurb/rules/readlines_in_for.rs | 2 +- .../rules/refurb/rules/redundant_log_base.rs | 2 +- .../rules/refurb/rules/regex_flag_alias.rs | 2 +- .../refurb/rules/reimplemented_operator.rs | 4 +- .../refurb/rules/reimplemented_starmap.rs | 4 +- .../src/rules/refurb/rules/repeated_append.rs | 10 +- .../src/rules/refurb/rules/repeated_global.rs | 2 +- .../rules/single_item_membership_test.rs | 2 +- .../src/rules/refurb/rules/slice_copy.rs | 2 +- .../rules/slice_to_remove_prefix_or_suffix.rs | 4 +- .../src/rules/refurb/rules/sorted_min_max.rs | 2 +- .../rules/refurb/rules/subclass_builtin.rs | 4 +- .../refurb/rules/type_none_comparison.rs | 2 +- .../refurb/rules/unnecessary_enumerate.rs | 2 +- .../refurb/rules/unnecessary_from_float.rs | 2 +- .../rules/verbose_decimal_constructor.rs | 2 +- .../rules/refurb/rules/write_whole_file.rs | 4 +- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- .../ruff/rules/ambiguous_unicode_character.rs | 6 +- .../ruff/rules/assert_with_print_message.rs | 2 +- .../rules/ruff/rules/assignment_in_assert.rs | 2 +- .../rules/ruff/rules/asyncio_dangling_task.rs | 4 +- .../ruff/rules/class_with_mixed_type_vars.rs | 6 +- .../rules/collection_literal_concatenation.rs | 2 +- .../src/rules/ruff/rules/dataclass_enum.rs | 4 +- .../ruff/rules/decimal_from_float_literal.rs | 4 +- .../rules/ruff/rules/default_factory_kwarg.rs | 6 +- .../explicit_f_string_type_conversion.rs | 6 +- .../ruff/rules/falsy_dict_get_fallback.rs | 6 +- .../function_call_in_dataclass_default.rs | 6 +- .../src/rules/ruff/rules/helpers.rs | 4 +- .../rules/ruff/rules/if_key_in_dict_del.rs | 2 +- .../rules/implicit_classvar_in_dataclass.rs | 4 +- .../src/rules/ruff/rules/implicit_optional.rs | 2 +- .../rules/ruff/rules/in_empty_collection.rs | 2 +- ...rectly_parenthesized_tuple_in_subscript.rs | 2 +- .../rules/ruff/rules/indented_form_feed.rs | 2 +- ...invalid_assert_message_literal_argument.rs | 2 +- .../invalid_formatter_suppression_comment.rs | 8 +- .../rules/ruff/rules/invalid_index_type.rs | 6 +- .../ruff/rules/invalid_pyproject_toml.rs | 2 +- .../src/rules/ruff/rules/invalid_rule_code.rs | 4 +- .../ruff/rules/map_int_version_parsing.rs | 2 +- .../ruff/rules/missing_fstring_syntax.rs | 4 +- .../rules/ruff/rules/mutable_class_default.rs | 2 +- .../ruff/rules/mutable_dataclass_default.rs | 2 +- .../ruff/rules/mutable_fromkeys_value.rs | 2 +- .../src/rules/ruff/rules/needless_else.rs | 2 +- .../src/rules/ruff/rules/never_union.rs | 4 +- .../ruff/rules/none_not_at_end_of_union.rs | 2 +- .../rules/parenthesize_chained_operators.rs | 2 +- .../src/rules/ruff/rules/post_init_default.rs | 4 +- .../rules/pytest_raises_ambiguous_pattern.rs | 2 +- .../ruff/rules/quadratic_list_summation.rs | 2 +- .../src/rules/ruff/rules/redirected_noqa.rs | 2 +- .../ruff/rules/redundant_bool_literal.rs | 2 +- .../src/rules/ruff/rules/sequence_sorting.rs | 6 +- .../src/rules/ruff/rules/sort_dunder_all.rs | 6 +- .../src/rules/ruff/rules/sort_dunder_slots.rs | 8 +- .../src/rules/ruff/rules/starmap_zip.rs | 4 +- .../rules/static_key_dict_comprehension.rs | 2 +- .../ruff/rules/suppression_comment_visitor.rs | 4 +- .../src/rules/ruff/rules/test_rules.rs | 4 +- .../ruff/rules/unnecessary_cast_to_int.rs | 17 +- ...y_iterable_allocation_for_first_element.rs | 10 +- .../rules/ruff/rules/unnecessary_key_check.rs | 2 +- .../unnecessary_literal_within_deque_call.rs | 2 +- .../ruff/rules/unnecessary_nested_literal.rs | 2 +- .../rules/unnecessary_regular_expression.rs | 2 +- .../src/rules/ruff/rules/unnecessary_round.rs | 6 +- .../src/rules/ruff/rules/unraw_re_pattern.rs | 2 +- .../src/rules/ruff/rules/unsafe_markup_use.rs | 2 +- .../src/rules/ruff/rules/unused_async.rs | 4 +- .../src/rules/ruff/rules/unused_noqa.rs | 2 +- .../ruff/rules/unused_unpacked_variable.rs | 2 +- .../rules/ruff/rules/used_dummy_variable.rs | 2 +- .../src/rules/ruff/rules/useless_if_else.rs | 2 +- .../ruff/rules/zip_instead_of_pairwise.rs | 2 +- .../src/rules/tryceratops/helpers.rs | 2 +- .../rules/error_instead_of_exception.rs | 2 +- .../tryceratops/rules/raise_vanilla_args.rs | 2 +- .../tryceratops/rules/raise_vanilla_class.rs | 4 +- .../tryceratops/rules/raise_within_try.rs | 4 +- .../tryceratops/rules/reraise_no_cause.rs | 2 +- .../tryceratops/rules/try_consider_else.rs | 2 +- .../rules/type_check_without_type_error.rs | 4 +- .../tryceratops/rules/useless_try_except.rs | 2 +- .../tryceratops/rules/verbose_log_message.rs | 2 +- .../rules/tryceratops/rules/verbose_raise.rs | 4 +- .../src/settings/fix_safety_table.rs | 3 +- crates/ruff_linter/src/settings/flags.rs | 6 +- crates/ruff_linter/src/settings/mod.rs | 2 +- crates/ruff_linter/src/settings/types.rs | 8 +- crates/ruff_linter/src/test.rs | 6 +- crates/ruff_macros/src/cache_key.rs | 4 +- crates/ruff_macros/src/config.rs | 15 +- .../ruff_macros/src/derive_message_formats.rs | 2 +- crates/ruff_macros/src/lib.rs | 2 +- crates/ruff_macros/src/map_codes.rs | 6 +- crates/ruff_notebook/src/cell.rs | 2 +- crates/ruff_notebook/src/notebook.rs | 6 +- crates/ruff_python_ast/src/helpers.rs | 2 +- crates/ruff_python_ast/src/identifier.rs | 2 +- crates/ruff_python_ast/src/name.rs | 12 +- crates/ruff_python_ast/src/node.rs | 87 +- crates/ruff_python_ast/src/nodes.rs | 17 +- crates/ruff_python_ast/src/python_version.rs | 6 +- crates/ruff_python_ast/src/relocate.rs | 2 +- crates/ruff_python_ast/src/whitespace.rs | 2 +- .../tests/comparable.rs | 2 +- .../tests/identifier.rs | 2 +- .../tests/source_order.rs | 2 +- .../tests/stmt_if.rs | 2 +- .../tests/visitor.rs | 5 +- crates/ruff_python_codegen/src/generator.rs | 16 +- crates/ruff_python_codegen/src/lib.rs | 2 +- crates/ruff_python_codegen/src/stylist.rs | 8 +- crates/ruff_python_formatter/src/builders.rs | 2 +- crates/ruff_python_formatter/src/cli.rs | 6 +- .../src/comments/format.rs | 11 +- .../ruff_python_formatter/src/comments/mod.rs | 2 +- .../src/comments/placement.rs | 10 +- crates/ruff_python_formatter/src/context.rs | 2 +- crates/ruff_python_formatter/src/db.rs | 2 +- .../src/expression/binary_like.rs | 8 +- .../src/expression/expr_attribute.rs | 8 +- .../src/expression/expr_await.rs | 2 +- .../src/expression/expr_bytes_literal.rs | 4 +- .../src/expression/expr_call.rs | 4 +- .../src/expression/expr_dict.rs | 4 +- .../src/expression/expr_dict_comp.rs | 2 +- .../src/expression/expr_f_string.rs | 4 +- .../src/expression/expr_generator.rs | 4 +- .../src/expression/expr_if.rs | 6 +- .../src/expression/expr_list.rs | 2 +- .../src/expression/expr_list_comp.rs | 4 +- .../src/expression/expr_named.rs | 2 +- .../src/expression/expr_set.rs | 2 +- .../src/expression/expr_set_comp.rs | 4 +- .../src/expression/expr_slice.rs | 4 +- .../src/expression/expr_string_literal.rs | 4 +- .../src/expression/expr_subscript.rs | 6 +- .../src/expression/expr_tuple.rs | 4 +- .../src/expression/expr_unary_op.rs | 2 +- .../src/expression/expr_yield.rs | 2 +- .../src/expression/mod.rs | 12 +- .../src/expression/parentheses.rs | 14 +- crates/ruff_python_formatter/src/lib.rs | 18 +- crates/ruff_python_formatter/src/main.rs | 6 +- .../src/module/mod_module.rs | 2 +- crates/ruff_python_formatter/src/options.rs | 8 +- .../src/other/arguments.rs | 4 +- .../ruff_python_formatter/src/other/commas.rs | 2 +- .../src/other/comprehension.rs | 4 +- .../other/except_handler_except_handler.rs | 4 +- .../src/other/f_string_element.rs | 2 +- .../src/other/match_case.rs | 4 +- .../src/other/parameter_with_default.rs | 34 +- .../src/other/parameters.rs | 28 +- .../src/other/string_literal.rs | 4 +- .../src/other/with_item.rs | 4 +- .../ruff_python_formatter/src/pattern/mod.rs | 4 +- .../src/pattern/pattern_arguments.rs | 2 +- .../src/pattern/pattern_match_mapping.rs | 4 +- .../src/pattern/pattern_match_or.rs | 4 +- .../src/pattern/pattern_match_sequence.rs | 8 +- crates/ruff_python_formatter/src/prelude.rs | 4 +- crates/ruff_python_formatter/src/range.rs | 10 +- .../src/statement/clause.rs | 24 +- .../src/statement/stmt_assign.rs | 15 +- .../src/statement/stmt_aug_assign.rs | 6 +- .../src/statement/stmt_class_def.rs | 4 +- .../src/statement/stmt_delete.rs | 4 +- .../src/statement/stmt_for.rs | 2 +- .../src/statement/stmt_function_def.rs | 2 +- .../src/statement/stmt_if.rs | 2 +- .../src/statement/stmt_import_from.rs | 2 +- .../src/statement/stmt_match.rs | 2 +- .../src/statement/stmt_try.rs | 6 +- .../src/statement/stmt_while.rs | 2 +- .../src/statement/stmt_with.rs | 4 +- .../src/statement/suite.rs | 6 +- .../src/string/docstring.rs | 10 +- .../src/string/implicit.rs | 9 +- .../ruff_python_formatter/src/string/mod.rs | 9 +- .../src/string/normalize.rs | 6 +- crates/ruff_python_formatter/src/verbatim.rs | 8 +- .../ruff_python_formatter/tests/fixtures.rs | 4 +- .../ruff_python_formatter/tests/normalizer.rs | 2 +- crates/ruff_python_index/src/indexer.rs | 2 +- crates/ruff_python_literal/src/escape.rs | 2 +- crates/ruff_python_parser/src/error.rs | 10 +- crates/ruff_python_parser/src/lexer.rs | 25 +- .../src/parser/expression.rs | 4 +- .../ruff_python_parser/src/parser/helpers.rs | 4 +- .../ruff_python_parser/src/parser/pattern.rs | 4 +- .../src/parser/statement.rs | 6 +- crates/ruff_python_parser/src/parser/tests.rs | 2 +- .../ruff_python_parser/src/semantic_errors.rs | 14 +- crates/ruff_python_parser/src/string.rs | 4 +- crates/ruff_python_parser/src/token_source.rs | 2 +- crates/ruff_python_parser/src/typing.rs | 2 +- crates/ruff_python_parser/tests/fixtures.rs | 15 +- crates/ruff_python_resolver/src/lib.rs | 2 +- crates/ruff_python_resolver/src/resolver.rs | 4 +- .../src/analyze/type_inference.rs | 6 +- .../src/analyze/typing.rs | 4 +- crates/ruff_python_semantic/src/binding.rs | 4 +- crates/ruff_python_semantic/src/branches.rs | 2 +- crates/ruff_python_semantic/src/cfg/graph.rs | 4 +- .../ruff_python_semantic/src/cfg/visualize.rs | 2 +- crates/ruff_python_semantic/src/definition.rs | 8 +- crates/ruff_python_semantic/src/globals.rs | 4 +- crates/ruff_python_semantic/src/imports.rs | 2 +- crates/ruff_python_semantic/src/model.rs | 2 +- crates/ruff_python_semantic/src/model/all.rs | 2 +- crates/ruff_python_semantic/src/nodes.rs | 2 +- crates/ruff_python_semantic/src/reference.rs | 2 +- crates/ruff_python_semantic/src/scope.rs | 2 +- crates/ruff_python_stdlib/src/str.rs | 4 +- crates/ruff_python_trivia/src/comments.rs | 2 +- crates/ruff_python_trivia/src/tokenizer.rs | 2 +- .../tests/block_comments.rs | 2 +- .../tests/simple_tokenizer.rs | 4 +- .../tests/whitespace.rs | 2 +- crates/ruff_server/src/edit/range.rs | 4 +- crates/ruff_server/src/edit/text_document.rs | 8 +- crates/ruff_server/src/fix.rs | 4 +- crates/ruff_server/src/format.rs | 4 +- crates/ruff_server/src/lint.rs | 6 +- crates/ruff_server/src/logging.rs | 2 +- crates/ruff_server/src/server.rs | 12 +- crates/ruff_server/src/server/api.rs | 6 +- .../src/server/api/notifications/cancel.rs | 2 +- .../server/api/notifications/did_change.rs | 4 +- .../notifications/did_change_configuration.rs | 2 +- .../api/notifications/did_change_notebook.rs | 4 +- .../notifications/did_change_watched_files.rs | 4 +- .../api/notifications/did_change_workspace.rs | 2 +- .../src/server/api/notifications/did_close.rs | 4 +- .../api/notifications/did_close_notebook.rs | 2 +- .../src/server/api/notifications/did_open.rs | 6 +- .../api/notifications/did_open_notebook.rs | 4 +- .../src/server/api/requests/code_action.rs | 8 +- .../api/requests/code_action_resolve.rs | 6 +- .../src/server/api/requests/diagnostic.rs | 2 +- .../server/api/requests/execute_command.rs | 6 +- .../src/server/api/requests/format.rs | 2 +- .../src/server/api/requests/format_range.rs | 4 +- .../src/server/api/requests/hover.rs | 2 +- crates/ruff_server/src/server/client.rs | 2 +- crates/ruff_server/src/server/connection.rs | 4 +- crates/ruff_server/src/server/schedule.rs | 2 +- .../src/server/schedule/thread/pool.rs | 2 +- crates/ruff_server/src/session/index.rs | 4 +- .../src/session/index/ruff_settings.rs | 4 +- crates/ruff_server/src/session/settings.rs | 8 +- crates/ruff_server/src/workspace.rs | 2 +- crates/ruff_server/tests/notebook.rs | 8 +- crates/ruff_source_file/src/lib.rs | 4 +- crates/ruff_text_size/src/schemars_impls.rs | 10 +- crates/ruff_wasm/src/lib.rs | 10 +- crates/ruff_workspace/src/configuration.rs | 26 +- crates/ruff_workspace/src/options.rs | 10 +- crates/ruff_workspace/src/pyproject.rs | 40 +- crates/ruff_workspace/src/resolver.rs | 14 +- crates/ruff_workspace/src/settings.rs | 2 +- crates/ty/src/lib.rs | 12 +- crates/ty/src/logging.rs | 2 +- crates/ty/src/main.rs | 2 +- crates/ty/tests/file_watching.rs | 118 +- crates/ty_ide/src/db.rs | 2 +- crates/ty_ide/src/find_node.rs | 2 +- crates/ty_ide/src/goto.rs | 6 +- crates/ty_ide/src/hover.rs | 10 +- crates/ty_ide/src/inlay_hints.rs | 2 +- crates/ty_ide/src/lib.rs | 4 +- crates/ty_project/src/db.rs | 6 +- crates/ty_project/src/db/changes.rs | 6 +- crates/ty_project/src/files.rs | 4 +- crates/ty_project/src/lib.rs | 16 +- crates/ty_project/src/metadata.rs | 45 +- crates/ty_project/src/metadata/options.rs | 10 +- crates/ty_project/src/metadata/pyproject.rs | 11 +- crates/ty_project/src/metadata/value.rs | 2 +- crates/ty_project/src/walk.rs | 2 +- crates/ty_project/src/watch.rs | 2 +- .../ty_project/src/watch/project_watcher.rs | 6 +- crates/ty_project/src/watch/watcher.rs | 2 +- crates/ty_project/tests/check.rs | 13 +- crates/ty_python_semantic/src/ast_node_ref.rs | 2 +- crates/ty_python_semantic/src/db.rs | 2 +- crates/ty_python_semantic/src/dunder_all.rs | 8 +- crates/ty_python_semantic/src/lib.rs | 2 +- crates/ty_python_semantic/src/list.rs | 2 +- .../src/module_resolver/mod.rs | 4 +- .../src/module_resolver/path.rs | 24 +- .../src/module_resolver/resolver.rs | 48 +- .../src/module_resolver/typeshed.rs | 4 +- crates/ty_python_semantic/src/program.rs | 2 +- .../ty_python_semantic/src/python_platform.rs | 4 +- .../ty_python_semantic/src/semantic_index.rs | 19 +- .../src/semantic_index/ast_ids.rs | 2 +- .../src/semantic_index/builder.rs | 16 +- .../src/semantic_index/definition.rs | 64 +- .../src/semantic_index/predicate.rs | 2 +- .../src/semantic_index/re_exports.rs | 4 +- .../src/semantic_index/symbol.rs | 76 +- .../src/semantic_index/use_def.rs | 11 +- .../semantic_index/use_def/symbol_state.rs | 2 +- .../semantic_index/visibility_constraints.rs | 6 +- .../ty_python_semantic/src/semantic_model.rs | 6 +- .../ty_python_semantic/src/site_packages.rs | 12 +- crates/ty_python_semantic/src/suppression.rs | 4 +- crates/ty_python_semantic/src/symbol.rs | 20 +- crates/ty_python_semantic/src/types.rs | 66 +- .../ty_python_semantic/src/types/call/bind.rs | 71 +- crates/ty_python_semantic/src/types/class.rs | 24 +- .../src/types/class_base.rs | 4 +- .../ty_python_semantic/src/types/context.rs | 8 +- .../src/types/diagnostic.rs | 6 +- .../ty_python_semantic/src/types/display.rs | 2 +- .../ty_python_semantic/src/types/generics.rs | 4 +- crates/ty_python_semantic/src/types/infer.rs | 150 ++- .../ty_python_semantic/src/types/instance.rs | 2 +- .../src/types/known_instance.rs | 4 +- crates/ty_python_semantic/src/types/mro.rs | 2 +- crates/ty_python_semantic/src/types/narrow.rs | 8 +- .../src/types/property_tests/setup.rs | 2 +- .../src/types/signatures.rs | 74 +- .../src/types/type_ordering.rs | 4 +- .../ty_python_semantic/src/types/unpacker.rs | 4 +- crates/ty_python_semantic/src/unpack.rs | 2 +- crates/ty_python_semantic/tests/mdtest.rs | 2 +- crates/ty_server/src/document/location.rs | 2 +- crates/ty_server/src/document/range.rs | 4 +- .../ty_server/src/document/text_document.rs | 8 +- crates/ty_server/src/logging.rs | 2 +- crates/ty_server/src/server.rs | 6 +- crates/ty_server/src/server/api.rs | 4 +- .../ty_server/src/server/api/diagnostics.rs | 4 +- .../server/api/notifications/did_change.rs | 8 +- .../notifications/did_change_watched_files.rs | 10 +- .../src/server/api/notifications/did_close.rs | 8 +- .../api/notifications/did_close_notebook.rs | 8 +- .../src/server/api/notifications/did_open.rs | 6 +- .../api/notifications/did_open_notebook.rs | 8 +- .../src/server/api/requests/completion.rs | 2 +- .../src/server/api/requests/diagnostic.rs | 2 +- .../api/requests/goto_type_definition.rs | 2 +- .../src/server/api/requests/hover.rs | 4 +- .../src/server/api/requests/inlay_hints.rs | 2 +- crates/ty_server/src/server/client.rs | 2 +- crates/ty_server/src/server/connection.rs | 4 +- crates/ty_server/src/server/schedule.rs | 2 +- .../src/server/schedule/thread/pool.rs | 2 +- crates/ty_server/src/session.rs | 6 +- crates/ty_server/src/session/index.rs | 2 +- crates/ty_server/src/system.rs | 4 +- crates/ty_test/src/assertion.rs | 12 +- crates/ty_test/src/db.rs | 4 +- crates/ty_test/src/lib.rs | 8 +- crates/ty_test/src/matcher.rs | 4 +- crates/ty_test/src/parser.rs | 27 +- crates/ty_vendored/build.rs | 2 +- crates/ty_wasm/src/lib.rs | 10 +- scripts/add_rule.py | 2 +- 1082 files changed, 4193 insertions(+), 3282 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 484b3b35195cb9..d4232fb7bde123 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -495,7 +495,7 @@ jobs: persist-credentials: false - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 - name: "Install Rust toolchain" - run: rustup component add rustfmt + run: rustup show # Run all code generation scripts, and verify that the current output is # already checked into git. - run: python crates/ruff_python_ast/generate.py @@ -504,12 +504,10 @@ jobs: # Verify that adding a plugin or rule produces clean code. - run: ./scripts/add_rule.py --name DoTheThing --prefix F --code 999 --linter pyflakes - run: cargo check - - run: cargo fmt --all --check - run: | ./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST ./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test - run: cargo check - - run: cargo fmt --all --check ecosystem: name: "ecosystem" diff --git a/Cargo.toml b/Cargo.toml index 2704d59a463086..285bbd7bf86c69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -edition = "2021" +edition = "2024" rust-version = "1.85" homepage = "https://docs.astral.sh/ruff" documentation = "https://docs.astral.sh/ruff" diff --git a/crates/ruff/src/args.rs b/crates/ruff/src/args.rs index fb2f9a273d00fc..45db84d0536e8a 100644 --- a/crates/ruff/src/args.rs +++ b/crates/ruff/src/args.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use crate::commands::completions::config::{OptionString, OptionStringParser}; use anyhow::bail; use clap::builder::{TypedValueParser, ValueParserFactory}; -use clap::{command, Parser, Subcommand}; +use clap::{Parser, Subcommand, command}; use colored::Colorize; use itertools::Itertools; use path_absolutize::path_dedot; @@ -1126,10 +1126,10 @@ impl std::fmt::Display for FormatRangeParseError { write!( f, "the start position '{start_invalid}' is greater than the end position '{end_invalid}'.\n {tip} Try switching start and end: '{end}-{start}'", - start_invalid=start.to_string().bold().yellow(), - end_invalid=end.to_string().bold().yellow(), - start=start.to_string().green().bold(), - end=end.to_string().green().bold() + start_invalid = start.to_string().bold().yellow(), + end_invalid = end.to_string().bold().yellow(), + start = start.to_string().green().bold(), + end = end.to_string().green().bold() ) } FormatRangeParseError::InvalidStart(inner) => inner.write(f, true), @@ -1230,30 +1230,36 @@ impl LineColumnParseError { match self { LineColumnParseError::ColumnParseError(inner) => { - write!(f, "the {range}s column is not a valid number ({inner})'\n {tip} The format is 'line:column'.") + write!( + f, + "the {range}s column is not a valid number ({inner})'\n {tip} The format is 'line:column'." + ) } LineColumnParseError::LineParseError(inner) => { - write!(f, "the {range} line is not a valid number ({inner})\n {tip} The format is 'line:column'.") + write!( + f, + "the {range} line is not a valid number ({inner})\n {tip} The format is 'line:column'." + ) } LineColumnParseError::ZeroColumnIndex { line } => { write!( f, "the {range} column is 0, but it should be 1 or greater.\n {tip} The column numbers start at 1.\n {tip} Try {suggestion} instead.", - suggestion=format!("{line}:1").green().bold() + suggestion = format!("{line}:1").green().bold() ) } LineColumnParseError::ZeroLineIndex { column } => { write!( f, "the {range} line is 0, but it should be 1 or greater.\n {tip} The line numbers start at 1.\n {tip} Try {suggestion} instead.", - suggestion=format!("1:{column}").green().bold() + suggestion = format!("1:{column}").green().bold() ) } LineColumnParseError::ZeroLineAndColumnIndex => { write!( f, "the {range} line and column are both 0, but they should be 1 or greater.\n {tip} The line and column numbers start at 1.\n {tip} Try {suggestion} instead.", - suggestion="1:1".to_string().green().bold() + suggestion = "1:1".to_string().green().bold() ) } } diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 99eed7cbafca67..056c7420b0289e 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -3,8 +3,8 @@ use std::fs::{self, File}; use std::hash::Hasher; use std::io::{self, BufReader, Write}; use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Mutex; +use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, SystemTime}; use anyhow::{Context, Result}; @@ -22,13 +22,13 @@ use ruff_cache::{CacheKey, CacheKeyHasher}; use ruff_diagnostics::Fix; use ruff_linter::message::{DiagnosticMessage, Message}; use ruff_linter::package::PackageRoot; -use ruff_linter::{warn_user, VERSION}; +use ruff_linter::{VERSION, warn_user}; use ruff_macros::CacheKey; use ruff_notebook::NotebookIndex; use ruff_source_file::SourceFileBuilder; use ruff_text_size::{TextRange, TextSize}; -use ruff_workspace::resolver::Resolver; use ruff_workspace::Settings; +use ruff_workspace::resolver::Resolver; use crate::diagnostics::Diagnostics; @@ -597,7 +597,7 @@ mod tests { use std::time::SystemTime; use anyhow::Result; - use filetime::{set_file_mtime, FileTime}; + use filetime::{FileTime, set_file_mtime}; use itertools::Itertools; use ruff_linter::settings::LinterSettings; use test_case::test_case; @@ -612,8 +612,8 @@ mod tests { use crate::cache::{self, FileCache, FileCacheData, FileCacheKey}; use crate::cache::{Cache, RelativePathBuf}; - use crate::commands::format::{format_path, FormatCommandError, FormatMode, FormatResult}; - use crate::diagnostics::{lint_path, Diagnostics}; + use crate::commands::format::{FormatCommandError, FormatMode, FormatResult, format_path}; + use crate::diagnostics::{Diagnostics, lint_path}; #[test_case("../ruff_linter/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_linter"; "ruff_linter_fixtures")] #[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")] diff --git a/crates/ruff/src/commands/add_noqa.rs b/crates/ruff/src/commands/add_noqa.rs index c5a417ec3cf6f1..d5eaeb0170ccb6 100644 --- a/crates/ruff/src/commands/add_noqa.rs +++ b/crates/ruff/src/commands/add_noqa.rs @@ -11,7 +11,7 @@ use ruff_linter::source_kind::SourceKind; use ruff_linter::warn_user_once; use ruff_python_ast::{PySourceType, SourceType}; use ruff_workspace::resolver::{ - match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, + PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path, }; use crate::args::ConfigArguments; diff --git a/crates/ruff/src/commands/analyze_graph.rs b/crates/ruff/src/commands/analyze_graph.rs index d0bf72edf78f00..75ff28d0dcbc45 100644 --- a/crates/ruff/src/commands/analyze_graph.rs +++ b/crates/ruff/src/commands/analyze_graph.rs @@ -1,6 +1,6 @@ use crate::args::{AnalyzeGraphArgs, ConfigArguments}; use crate::resolve::resolve; -use crate::{resolve_default_files, ExitStatus}; +use crate::{ExitStatus, resolve_default_files}; use anyhow::Result; use log::{debug, warn}; use path_absolutize::CWD; @@ -9,7 +9,7 @@ use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports}; use ruff_linter::package::PackageRoot; use ruff_linter::{warn_user, warn_user_once}; use ruff_python_ast::{PySourceType, SourceType}; -use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile}; +use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path}; use rustc_hash::FxHashMap; use std::io::Write; use std::path::{Path, PathBuf}; diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index 9b7104e4dc95d1..72ae7162d90e62 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -17,12 +17,12 @@ use ruff_linter::message::Message; use ruff_linter::package::PackageRoot; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; -use ruff_linter::settings::{flags, LinterSettings}; -use ruff_linter::{fs, warn_user_once, IOError}; +use ruff_linter::settings::{LinterSettings, flags}; +use ruff_linter::{IOError, fs, warn_user_once}; use ruff_source_file::SourceFileBuilder; use ruff_text_size::{TextRange, TextSize}; use ruff_workspace::resolver::{ - match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, + PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path, }; use crate::args::ConfigArguments; @@ -228,9 +228,9 @@ mod test { use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; - use ruff_linter::settings::{flags, LinterSettings}; - use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; + use ruff_linter::settings::{LinterSettings, flags}; use ruff_workspace::Settings; + use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use crate::args::ConfigArguments; diff --git a/crates/ruff/src/commands/check_stdin.rs b/crates/ruff/src/commands/check_stdin.rs index 4e7be0b540e144..4b019433537ca5 100644 --- a/crates/ruff/src/commands/check_stdin.rs +++ b/crates/ruff/src/commands/check_stdin.rs @@ -4,10 +4,10 @@ use anyhow::Result; use ruff_linter::package::PackageRoot; use ruff_linter::packaging; use ruff_linter::settings::flags; -use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver}; +use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, python_file_at_path}; use crate::args::ConfigArguments; -use crate::diagnostics::{lint_stdin, Diagnostics}; +use crate::diagnostics::{Diagnostics, lint_stdin}; use crate::stdin::{parrot_stdin, read_from_stdin}; /// Run the linter over a single file, read from `stdin`. diff --git a/crates/ruff/src/commands/config.rs b/crates/ruff/src/commands/config.rs index 98bd9d70ae6af0..f6c00548394ab2 100644 --- a/crates/ruff/src/commands/config.rs +++ b/crates/ruff/src/commands/config.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use crate::args::HelpFormat; diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index 57414c249d0423..a09fb9b8047766 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::fs::File; use std::io; -use std::io::{stderr, stdout, Write}; +use std::io::{Write, stderr, stdout}; use std::path::{Path, PathBuf}; use std::time::Instant; @@ -16,7 +16,7 @@ use rustc_hash::FxHashSet; use thiserror::Error; use tracing::debug; -use ruff_db::panic::{catch_unwind, PanicError}; +use ruff_db::panic::{PanicError, catch_unwind}; use ruff_diagnostics::SourceMap; use ruff_linter::fs; use ruff_linter::logging::{DisplayParseError, LogLevel}; @@ -26,16 +26,16 @@ use ruff_linter::rules::flake8_quotes::settings::Quote; use ruff_linter::source_kind::{SourceError, SourceKind}; use ruff_linter::warn_user_once; use ruff_python_ast::{PySourceType, SourceType}; -use ruff_python_formatter::{format_module_source, format_range, FormatModuleError, QuoteStyle}; +use ruff_python_formatter::{FormatModuleError, QuoteStyle, format_module_source, format_range}; use ruff_source_file::LineIndex; use ruff_text_size::{TextLen, TextRange, TextSize}; -use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver}; use ruff_workspace::FormatterSettings; +use ruff_workspace::resolver::{ResolvedFile, Resolver, match_exclusion, python_files_in_path}; use crate::args::{ConfigArguments, FormatArguments, FormatRange}; use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches}; use crate::resolve::resolve; -use crate::{resolve_default_files, ExitStatus}; +use crate::{ExitStatus, resolve_default_files}; #[derive(Debug, Copy, Clone, is_macro::Is)] pub(crate) enum FormatMode { @@ -821,9 +821,14 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { .collect(); rule_names.sort(); if let [rule] = rule_names.as_slice() { - warn_user_once!("The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration."); + warn_user_once!( + "The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration." + ); } else { - warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", ")); + warn_user_once!( + "The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", + rule_names.join(", ") + ); } } @@ -833,7 +838,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { if setting.linter.rules.enabled(Rule::TabIndentation) && setting.formatter.indent_style.is_tab() { - warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`."); + warn_user_once!( + "The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`." + ); } if !setting @@ -846,14 +853,18 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { .enabled(Rule::MultiLineImplicitStringConcatenation) && !setting.linter.flake8_implicit_str_concat.allow_multiline { - warn_user_once!("The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`."); + warn_user_once!( + "The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`." + ); } // Validate all rules that rely on tab styles. if setting.linter.rules.enabled(Rule::DocstringTabIndentation) && setting.formatter.indent_style.is_tab() { - warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`."); + warn_user_once!( + "The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`." + ); } // Validate all rules that rely on custom indent widths. @@ -862,7 +873,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { Rule::IndentationWithInvalidMultipleComment, ]) && setting.formatter.indent_width.value() != 4 { - warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`."); + warn_user_once!( + "The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`." + ); } // Validate all rules that rely on quote styles. @@ -876,10 +889,14 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { setting.formatter.quote_style, ) { (Quote::Double, QuoteStyle::Single) => { - warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`."); + warn_user_once!( + "The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`." + ); } (Quote::Single, QuoteStyle::Double) => { - warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`."); + warn_user_once!( + "The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`." + ); } _ => {} } @@ -892,7 +909,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { QuoteStyle::Single | QuoteStyle::Double ) { - warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`"); + warn_user_once!( + "The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`" + ); } if setting.linter.rules.enabled(Rule::BadQuotesDocstring) @@ -902,7 +921,9 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { QuoteStyle::Single | QuoteStyle::Double ) { - warn_user_once!("The `flake8-quotes.docstring-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`"); + warn_user_once!( + "The `flake8-quotes.docstring-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`" + ); } // Validate all isort settings. @@ -910,12 +931,16 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { // The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports. // Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks). if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) { - warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default)."); + warn_user_once!( + "The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default)." + ); } // Values larger than two get reduced to one line by the formatter if the import is in a nested block. if setting.linter.isort.lines_between_types > 1 { - warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default)."); + warn_user_once!( + "The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default)." + ); } // isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false. @@ -924,11 +949,15 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) { && !setting.linter.isort.force_single_line { if setting.linter.isort.force_wrap_aliases { - warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`."); + warn_user_once!( + "The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`." + ); } if setting.linter.isort.split_on_trailing_comma { - warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`."); + warn_user_once!( + "The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`." + ); } } } diff --git a/crates/ruff/src/commands/format_stdin.rs b/crates/ruff/src/commands/format_stdin.rs index 9c2d8ff3e0e079..f5b8ea6c603c35 100644 --- a/crates/ruff/src/commands/format_stdin.rs +++ b/crates/ruff/src/commands/format_stdin.rs @@ -6,17 +6,17 @@ use log::error; use ruff_linter::source_kind::SourceKind; use ruff_python_ast::{PySourceType, SourceType}; -use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver}; use ruff_workspace::FormatterSettings; +use ruff_workspace::resolver::{Resolver, match_exclusion, python_file_at_path}; +use crate::ExitStatus; use crate::args::{ConfigArguments, FormatArguments, FormatRange}; use crate::commands::format::{ - format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode, - FormatResult, FormattedSource, + FormatCommandError, FormatMode, FormatResult, FormattedSource, format_source, + warn_incompatible_formatter_settings, }; use crate::resolve::resolve; use crate::stdin::{parrot_stdin, read_from_stdin}; -use crate::ExitStatus; /// Run the formatter over a single file, read from `stdin`. pub(crate) fn format_stdin( diff --git a/crates/ruff/src/commands/show_files.rs b/crates/ruff/src/commands/show_files.rs index f21a9aa9430cc3..7c74837fd34952 100644 --- a/crates/ruff/src/commands/show_files.rs +++ b/crates/ruff/src/commands/show_files.rs @@ -5,7 +5,7 @@ use anyhow::Result; use itertools::Itertools; use ruff_linter::warn_user_once; -use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile}; +use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path}; use crate::args::ConfigArguments; diff --git a/crates/ruff/src/commands/show_settings.rs b/crates/ruff/src/commands/show_settings.rs index 679c2733dff370..ec4b0d54825d11 100644 --- a/crates/ruff/src/commands/show_settings.rs +++ b/crates/ruff/src/commands/show_settings.rs @@ -1,10 +1,10 @@ use std::io::Write; use std::path::PathBuf; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use itertools::Itertools; -use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile}; +use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path}; use crate::args::ConfigArguments; diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index 4610eca5c4c2f1..5ce39f49a86c33 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -14,14 +14,14 @@ use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; use ruff_linter::codes::Rule; -use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource}; +use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only}; use ruff_linter::message::Message; use ruff_linter::package::PackageRoot; use ruff_linter::pyproject_toml::lint_pyproject_toml; use ruff_linter::settings::types::UnsafeFixes; -use ruff_linter::settings::{flags, LinterSettings}; +use ruff_linter::settings::{LinterSettings, flags}; use ruff_linter::source_kind::{SourceError, SourceKind}; -use ruff_linter::{fs, IOError}; +use ruff_linter::{IOError, fs}; use ruff_notebook::{Notebook, NotebookError, NotebookIndex}; use ruff_python_ast::{PySourceType, SourceType, TomlSourceType}; use ruff_source_file::SourceFileBuilder; diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 11c7ba3e70242e..ef8f6397468806 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::print_stdout)] use std::fs::File; -use std::io::{self, stdout, BufWriter, Write}; +use std::io::{self, BufWriter, Write, stdout}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::process::ExitCode; @@ -11,10 +11,10 @@ use anyhow::Result; use clap::CommandFactory; use colored::Colorize; use log::warn; -use notify::{recommended_watcher, RecursiveMode, Watcher}; +use notify::{RecursiveMode, Watcher, recommended_watcher}; use args::{GlobalConfigArgs, ServerCommand}; -use ruff_linter::logging::{set_up_logging, LogLevel}; +use ruff_linter::logging::{LogLevel, set_up_logging}; use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::OutputFormat; use ruff_linter::{fs, warn_user, warn_user_once}; @@ -488,7 +488,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result
    --output-format output-format

    The format to use for printing diagnostic messages

    Possible values:

      -
    • full: Print diagnostics verbosely, with context and helpful hints
    • +
    • full: Print diagnostics verbosely, with context and helpful hints [default]
    • concise: Print diagnostics concisely, one per line
    --project project

    Run the command within the given project directory.

    All pyproject.toml files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (.venv) unless the venv-path option is set.

    diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index 9321319fad4cea..e5034cfdf94920 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -283,7 +283,7 @@ impl clap::Args for RulesArg { /// The diagnostic output format. #[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)] pub enum OutputFormat { - /// Print diagnostics verbosely, with context and helpful hints. + /// Print diagnostics verbosely, with context and helpful hints \[default\]. /// /// Diagnostic messages may include additional context and /// annotations on the input to help understand the message. From 98da200d45b040401bc5c1ff04fd678d37d3dd3e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 22 May 2025 16:10:07 +0200 Subject: [PATCH 204/487] [ty] Fix server panic when calling `system_mut` (#18252) --- crates/ty_project/src/db.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index ecbd3f65a8c678..14fb3ae24471af 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -25,9 +25,17 @@ pub trait Db: SemanticDb + Upcast { #[derive(Clone)] pub struct ProjectDatabase { project: Option, - storage: salsa::Storage, files: Files, + + // IMPORTANT: Never return clones of `system` outside `ProjectDatabase` (only return references) + // or the "trick" to get a mutable `Arc` in `Self::system_mut` is no longer guaranteed to work. system: Arc, + + // IMPORTANT: This field must be the last because we use `zalsa_mut` (drops all other storage references) + // to drop all other references to the database, which gives us exclusive access to other `Arc`s stored on this db. + // However, for this to work it's important that the `storage` is dropped AFTER any `Arc` that + // we try to mutably borrow using `Arc::get_mut` (like `system`). + storage: salsa::Storage, } impl ProjectDatabase { From 91b7a570c2bd1c9e1cab894ded866e885f28946a Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 22 May 2025 10:42:29 -0400 Subject: [PATCH 205/487] [ty] Implement Python's floor division semantics for `Literal` `int`s (#18249) Division works differently in Python than in Rust. If the result is negative and there is a remainder, the division rounds down (instead of towards zero). The remainder needs to be adjusted to compensate so that `(lhs // rhs) * rhs + (lhs % rhs) == lhs`. Fixes astral-sh/ty#481. --- .../resources/mdtest/binary/integers.md | 28 ++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 32 +++++++++++++------ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/binary/integers.md b/crates/ty_python_semantic/resources/mdtest/binary/integers.md index d7e920ed0efc19..95561a295ee504 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/integers.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/integers.md @@ -72,6 +72,34 @@ reveal_type(2 ** (-1)) # revealed: float reveal_type((-1) ** (-1)) # revealed: float ``` +## Division and Modulus + +Division works differently in Python than in Rust. If the result is negative and there is a +remainder, the division rounds down (instead of towards zero). The remainder needs to be adjusted to +compensate so that `(lhs // rhs) * rhs + (lhs % rhs) == lhs`: + +```py +reveal_type(256 % 129) # revealed: Literal[127] +reveal_type(-256 % 129) # revealed: Literal[2] +reveal_type(256 % -129) # revealed: Literal[-2] +reveal_type(-256 % -129) # revealed: Literal[-127] + +reveal_type(129 % 16) # revealed: Literal[1] +reveal_type(-129 % 16) # revealed: Literal[15] +reveal_type(129 % -16) # revealed: Literal[-15] +reveal_type(-129 % -16) # revealed: Literal[-1] + +reveal_type(10 // 8) # revealed: Literal[1] +reveal_type(-10 // 8) # revealed: Literal[-2] +reveal_type(10 // -8) # revealed: Literal[-2] +reveal_type(-10 // -8) # revealed: Literal[1] + +reveal_type(10 // 6) # revealed: Literal[1] +reveal_type(-10 // 6) # revealed: Literal[-2] +reveal_type(10 // -6) # revealed: Literal[-2] +reveal_type(-10 // -6) # revealed: Literal[1] +``` + ## Division by Zero This error is really outside the current Python type system, because e.g. `int.__truediv__` and diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 46c383eaa76bf8..01668d6af7bdc0 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -6108,17 +6108,29 @@ impl<'db> TypeInferenceBuilder<'db> { Some(KnownClass::Float.to_instance(self.db())) } - (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::FloorDiv) => Some( - n.checked_div(m) - .map(Type::IntLiteral) - .unwrap_or_else(|| KnownClass::Int.to_instance(self.db())), - ), + (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::FloorDiv) => Some({ + let mut q = n.checked_div(m); + let r = n.checked_rem(m); + // Division works differently in Python than in Rust. If the result is negative and + // there is a remainder, the division rounds down (instead of towards zero): + if n.is_negative() != m.is_negative() && r.unwrap_or(0) != 0 { + q = q.map(|q| q - 1); + } + q.map(Type::IntLiteral) + .unwrap_or_else(|| KnownClass::Int.to_instance(self.db())) + }), - (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => Some( - n.checked_rem(m) - .map(Type::IntLiteral) - .unwrap_or_else(|| KnownClass::Int.to_instance(self.db())), - ), + (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => Some({ + let mut r = n.checked_rem(m); + // Division works differently in Python than in Rust. If the result is negative and + // there is a remainder, the division rounds down (instead of towards zero). Adjust + // the remainder to compensate so that q * m + r == n: + if n.is_negative() != m.is_negative() && r.unwrap_or(0) != 0 { + r = r.map(|x| x + m); + } + r.map(Type::IntLiteral) + .unwrap_or_else(|| KnownClass::Int.to_instance(self.db())) + }), (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Pow) => Some({ if m < 0 { From bcefa459f4069970bc9776575e2f776b8d130dc9 Mon Sep 17 00:00:00 2001 From: InSync Date: Thu, 22 May 2025 22:25:51 +0700 Subject: [PATCH 206/487] [ty] Rename `call-possibly-unbound-method` to `possibly-unbound-implicit-call` (#18017) --- crates/ty/docs/rules.md | 153 ++++++++++-------- .../resources/mdtest/call/constructor.md | 4 +- .../resources/mdtest/call/dunder.md | 4 +- .../resources/mdtest/subscript/class.md | 2 +- crates/ty_python_semantic/src/types.rs | 8 +- .../src/types/diagnostic.rs | 21 ++- crates/ty_python_semantic/src/types/infer.rs | 16 +- ty.schema.json | 20 +-- 8 files changed, 127 insertions(+), 101 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index ca0fdf429fb221..f64e9152945b3b 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -81,7 +81,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L121) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134) ## `conflicting-declarations` @@ -111,7 +111,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L147) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160) ## `conflicting-metaclass` @@ -142,7 +142,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L172) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185) ## `cyclic-class-definition` @@ -173,7 +173,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L198) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211) ## `duplicate-base` @@ -199,7 +199,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L242) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L255) ## `escape-character-in-forward-annotation` @@ -336,7 +336,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L263) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L276) ## `inconsistent-mro` @@ -365,7 +365,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L349) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L362) ## `index-out-of-bounds` @@ -390,7 +390,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L373) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L386) ## `invalid-argument-type` @@ -416,7 +416,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L393) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L406) ## `invalid-assignment` @@ -443,7 +443,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L433) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L446) ## `invalid-attribute-access` @@ -476,7 +476,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1381) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394) ## `invalid-base` @@ -499,7 +499,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L455) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L468) ## `invalid-context-manager` @@ -525,7 +525,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L506) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L519) ## `invalid-declaration` @@ -553,7 +553,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L527) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L540) ## `invalid-exception-caught` @@ -594,7 +594,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L550) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L563) ## `invalid-generic-class` @@ -625,7 +625,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L586) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L599) ## `invalid-legacy-type-variable` @@ -658,7 +658,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L612) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625) ## `invalid-metaclass` @@ -690,7 +690,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L661) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L674) ## `invalid-overload` @@ -738,7 +738,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L688) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L701) ## `invalid-parameter-default` @@ -763,7 +763,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L731) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L744) ## `invalid-protocol` @@ -796,7 +796,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334) ## `invalid-raise` @@ -844,7 +844,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L751) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764) ## `invalid-return-type` @@ -868,7 +868,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L414) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427) ## `invalid-super-argument` @@ -912,7 +912,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L794) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L807) ## `invalid-syntax-in-forward-annotation` @@ -952,7 +952,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L640) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653) ## `invalid-type-checking-constant` @@ -981,7 +981,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L833) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846) ## `invalid-type-form` @@ -1010,7 +1010,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L857) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L870) ## `invalid-type-variable-constraints` @@ -1044,7 +1044,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L881) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894) ## `missing-argument` @@ -1068,7 +1068,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L910) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923) ## `no-matching-overload` @@ -1096,7 +1096,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L929) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L942) ## `non-subscriptable` @@ -1119,7 +1119,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L952) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L965) ## `not-iterable` @@ -1144,7 +1144,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L970) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) ## `parameter-already-assigned` @@ -1170,7 +1170,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034) ## `raw-string-type-annotation` @@ -1229,7 +1229,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1357) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) ## `subclass-of-final-class` @@ -1257,7 +1257,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1112) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1125) ## `too-many-positional-arguments` @@ -1283,7 +1283,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1170) ## `type-assertion-failure` @@ -1310,7 +1310,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1135) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148) ## `unavailable-implicit-super-arguments` @@ -1354,7 +1354,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1178) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1191) ## `unknown-argument` @@ -1380,7 +1380,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1248) ## `unresolved-attribute` @@ -1407,7 +1407,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) ## `unresolved-import` @@ -1431,7 +1431,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1278) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291) ## `unresolved-reference` @@ -1455,7 +1455,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1297) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1310) ## `unsupported-bool-conversion` @@ -1491,7 +1491,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L990) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1003) ## `unsupported-operator` @@ -1518,7 +1518,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1316) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) ## `zero-stepsize-in-slice` @@ -1542,25 +1542,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1338) - - -## `call-possibly-unbound-method` - -**Default level**: warn - -
    -detects calls to possibly unbound methods - -### What it does -Checks for calls to possibly unbound methods. - -### Why is this bad? -Calling an unbound method will raise an `AttributeError` at runtime. - -### Links -* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-possibly-unbound-method) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L108) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351)
    ## `invalid-ignore-comment` @@ -1616,7 +1598,38 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1042) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1055) + + +## `possibly-unbound-implicit-call` + +**Default level**: warn + +
    +detects implicit calls to possibly unbound methods + +### What it does +Checks for implicit calls to possibly unbound methods. + +### Why is this bad? +Expressions such as `x[y]` and `x * y` call methods +under the hood (`__getitem__` and `__mul__` respectively). +Calling an unbound method will raise an `AttributeError` at runtime. + +### Examples +```python +import datetime + +class A: + if datetime.date.today().weekday() != 6: + def __getitem__(self, v): ... + +A()[0] # TypeError: 'A' object is not subscriptable +``` + +### Links +* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L108)
    ## `possibly-unbound-import` @@ -1647,7 +1660,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1064) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077) ## `redundant-cast` @@ -1673,7 +1686,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1409) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1422) ## `undefined-reveal` @@ -1696,7 +1709,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1217) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) ## `unknown-rule` @@ -1764,7 +1777,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L473) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L486) ## `division-by-zero` @@ -1787,7 +1800,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L224) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237) ## `possibly-unresolved-reference` @@ -1814,7 +1827,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1090) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1103) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index 0e89cb5d32e571..c8bdd664cda20f 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -158,10 +158,10 @@ def _(flag: bool) -> None: def __new__(cls): return object.__new__(cls) - # error: [call-possibly-unbound-method] + # error: [possibly-unbound-implicit-call] reveal_type(Foo()) # revealed: Foo - # error: [call-possibly-unbound-method] + # error: [possibly-unbound-implicit-call] # error: [too-many-positional-arguments] reveal_type(Foo(1)) # revealed: Foo ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 1f31f8bbdda643..5caeb1f3b5d558 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -112,7 +112,7 @@ def _(flag: bool): this_fails = ThisFails() - # error: [call-possibly-unbound-method] + # error: [possibly-unbound-implicit-call] reveal_type(this_fails[0]) # revealed: Unknown | str ``` @@ -236,6 +236,6 @@ def _(flag: bool): return str(key) c = C() - # error: [call-possibly-unbound-method] + # error: [possibly-unbound-implicit-call] reveal_type(c[0]) # revealed: str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/class.md b/crates/ty_python_semantic/resources/mdtest/subscript/class.md index ca6f364cdd707f..8edb2d94e0f3f2 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/class.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/class.md @@ -63,7 +63,7 @@ def _(flag: bool): else: class Spam: ... - # error: [call-possibly-unbound-method] "Method `__class_getitem__` of type ` | ` is possibly unbound" + # error: [possibly-unbound-implicit-call] "Method `__class_getitem__` of type ` | ` is possibly unbound" # revealed: str reveal_type(Spam[42]) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6c9123acaeb3f2..1db7479afa9f89 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8,7 +8,7 @@ use bitflags::bitflags; use call::{CallDunderError, CallError, CallErrorKind}; use context::InferContext; use diagnostic::{ - CALL_POSSIBLY_UNBOUND_METHOD, INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, + INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS, }; use ruff_db::diagnostic::{ @@ -6652,7 +6652,7 @@ impl<'db> ConstructorCallError<'db> { let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error { CallDunderError::MethodNotAvailable => { if let Some(builder) = - context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node) + context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) { // If we are using vendored typeshed, it should be impossible to have missing // or unbound `__init__` method on a class, as all classes have `object` in MRO. @@ -6666,7 +6666,7 @@ impl<'db> ConstructorCallError<'db> { } CallDunderError::PossiblyUnbound(bindings) => { if let Some(builder) = - context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node) + context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) { builder.into_diagnostic(format_args!( "Method `__init__` on type `{}` is possibly unbound.", @@ -6684,7 +6684,7 @@ impl<'db> ConstructorCallError<'db> { let report_new_error = |error: &DunderNewCallError<'db>| match error { DunderNewCallError::PossiblyUnbound(call_error) => { if let Some(builder) = - context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node) + context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) { builder.into_diagnostic(format_args!( "Method `__new__` on type `{}` is possibly unbound.", diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index af7c67894758d0..d394650d1d282d 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -24,7 +24,7 @@ use std::fmt::Formatter; /// Registers all known type check lints. pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&CALL_NON_CALLABLE); - registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD); + registry.register_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL); registry.register_lint(&CONFLICTING_ARGUMENT_FORMS); registry.register_lint(&CONFLICTING_DECLARATIONS); registry.register_lint(&CONFLICTING_METACLASS); @@ -107,12 +107,25 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for calls to possibly unbound methods. + /// Checks for implicit calls to possibly unbound methods. /// /// ## Why is this bad? + /// Expressions such as `x[y]` and `x * y` call methods + /// under the hood (`__getitem__` and `__mul__` respectively). /// Calling an unbound method will raise an `AttributeError` at runtime. - pub(crate) static CALL_POSSIBLY_UNBOUND_METHOD = { - summary: "detects calls to possibly unbound methods", + /// + /// ## Examples + /// ```python + /// import datetime + /// + /// class A: + /// if datetime.date.today().weekday() != 6: + /// def __getitem__(self, v): ... + /// + /// A()[0] # TypeError: 'A' object is not subscriptable + /// ``` + pub(crate) static POSSIBLY_UNBOUND_IMPLICIT_CALL = { + summary: "detects implicit calls to possibly unbound methods", status: LintStatus::preview("1.0.0"), default_level: Level::Warn, } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 01668d6af7bdc0..9ad791217fef24 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -67,12 +67,12 @@ use crate::symbol::{ use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ - self, CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, - CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, - INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, - INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, - INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, - INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, + self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, + CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, + INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, + INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, + INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_VARIABLE_CONSTRAINTS, + POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR, report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, @@ -7428,7 +7428,7 @@ impl<'db> TypeInferenceBuilder<'db> { Err(err @ CallDunderError::PossiblyUnbound { .. }) => { if let Some(builder) = self .context - .report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, value_node) + .report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node) { builder.into_diagnostic(format_args!( "Method `__getitem__` of type `{}` is possibly unbound", @@ -7476,7 +7476,7 @@ impl<'db> TypeInferenceBuilder<'db> { if boundness == Boundness::PossiblyUnbound { if let Some(builder) = self .context - .report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, value_node) + .report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node) { builder.into_diagnostic(format_args!( "Method `__class_getitem__` of type `{}` \ diff --git a/ty.schema.json b/ty.schema.json index 48f089f1986729..3d828628150acd 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -240,16 +240,6 @@ } ] }, - "call-possibly-unbound-method": { - "title": "detects calls to possibly unbound methods", - "description": "## What it does\nChecks for calls to possibly unbound methods.\n\n## Why is this bad?\nCalling an unbound method will raise an `AttributeError` at runtime.", - "default": "warn", - "oneOf": [ - { - "$ref": "#/definitions/Level" - } - ] - }, "conflicting-argument-forms": { "title": "detects when an argument is used as both a value and a type form in a call", "description": "## What it does\nChecks whether an argument is used as both a value and a type form in a call.\n\n## Why is this bad?\nSuch calls have confusing semantics and often indicate a logic error.\n\n## Examples\n```python\nfrom typing import reveal_type\nfrom ty_extensions import is_fully_static\n\nif flag:\n f = repr # Expects a value\nelse:\n f = is_fully_static # Expects a type form\n\nf(int) # error\n```", @@ -650,6 +640,16 @@ } ] }, + "possibly-unbound-implicit-call": { + "title": "detects implicit calls to possibly unbound methods", + "description": "## What it does\nChecks for implicit calls to possibly unbound methods.\n\n## Why is this bad?\nExpressions such as `x[y]` and `x * y` call methods\nunder the hood (`__getitem__` and `__mul__` respectively).\nCalling an unbound method will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nimport datetime\n\nclass A:\n if datetime.date.today().weekday() != 6:\n def __getitem__(self, v): ...\n\nA()[0] # TypeError: 'A' object is not subscriptable\n```", + "default": "warn", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "possibly-unbound-import": { "title": "detects possibly unbound imports", "description": "## What it does\nChecks for imports of symbols that may be unbound.\n\n## Why is this bad?\nImporting an unbound module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.\n\n## Examples\n```python\n# module.py\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n\n# main.py\nfrom module import a # ImportError: cannot import name 'a' from 'module'\n```", From 0397682f1f50c9c1cc29293ac870f7720b0eda33 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 22 May 2025 13:09:44 -0500 Subject: [PATCH 207/487] Bump 0.11.11 (#18259) --- CHANGELOG.md | 25 +++++++++++++++++++++++++ Cargo.lock | 6 +++--- README.md | 6 +++--- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_wasm/Cargo.toml | 2 +- docs/integrations.md | 8 ++++---- docs/tutorial.md | 2 +- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 10 files changed, 41 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9345ec6570742d..3ca71fb47d6fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.11.11 + +### Preview features + +- \[`airflow`\] Add autofixes for `AIR302` and `AIR312` ([#17942](https://github.com/astral-sh/ruff/pull/17942)) +- \[`airflow`\] Move rules from `AIR312` to `AIR302` ([#17940](https://github.com/astral-sh/ruff/pull/17940)) +- \[`airflow`\] Update `AIR301` and `AIR311` with the latest Airflow implementations ([#17985](https://github.com/astral-sh/ruff/pull/17985)) +- \[`flake8-simplify`\] Enable fix in preview mode (`SIM117`) ([#18208](https://github.com/astral-sh/ruff/pull/18208)) + +### Bug fixes + +- Fix inconsistent formatting of match-case on `[]` and `_` ([#18147](https://github.com/astral-sh/ruff/pull/18147)) +- \[`pylint`\] Fix `PLW1514` not recognizing the `encoding` positional argument of `codecs.open` ([#18109](https://github.com/astral-sh/ruff/pull/18109)) + +### CLI + +- Add full option name in formatter warning ([#18217](https://github.com/astral-sh/ruff/pull/18217)) + +### Documentation + +- Fix rendering of admonition in docs ([#18163](https://github.com/astral-sh/ruff/pull/18163)) +- \[`flake8-print`\] Improve print/pprint docs for `T201` and `T203` ([#18130](https://github.com/astral-sh/ruff/pull/18130)) +- \[`flake8-simplify`\] Add fix safety section (`SIM110`,`SIM210`) ([#18114](https://github.com/astral-sh/ruff/pull/18114),[#18100](https://github.com/astral-sh/ruff/pull/18100)) +- \[`pylint`\] Fix docs example that produced different output (`PLW0603`) ([#18216](https://github.com/astral-sh/ruff/pull/18216)) + ## 0.11.10 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index 55859345a818d1..83cc5f7d6548fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.11.10" +version = "0.11.11" dependencies = [ "anyhow", "argfile", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.11.10" +version = "0.11.11" dependencies = [ "aho-corasick", "anyhow", @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "ruff_wasm" -version = "0.11.10" +version = "0.11.11" dependencies = [ "console_error_panic_hook", "console_log", diff --git a/README.md b/README.md index 99612af0d0fc6f..225c8b47daae32 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. -curl -LsSf https://astral.sh/ruff/0.11.10/install.sh | sh -powershell -c "irm https://astral.sh/ruff/0.11.10/install.ps1 | iex" +curl -LsSf https://astral.sh/ruff/0.11.11/install.sh | sh +powershell -c "irm https://astral.sh/ruff/0.11.11/install.ps1 | iex" ``` You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), @@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.10 + rev: v0.11.11 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 777f99ebafd4e6..ac983938e8ac18 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.11.10" +version = "0.11.11" publish = true authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index af3c527efb88fa..766adc8960ad06 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.11.10" +version = "0.11.11" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index 15623862c32619..b0c8fff39f416a 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_wasm" -version = "0.11.10" +version = "0.11.11" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/docs/integrations.md b/docs/integrations.md index 7be48ce36a001d..f5b1098638de59 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma stage: build interruptible: true image: - name: ghcr.io/astral-sh/ruff:0.11.10-alpine + name: ghcr.io/astral-sh/ruff:0.11.11-alpine before_script: - cd $CI_PROJECT_DIR - ruff --version @@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.10 + rev: v0.11.11 hooks: # Run the linter. - id: ruff @@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.10 + rev: v0.11.11 hooks: # Run the linter. - id: ruff @@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.10 + rev: v0.11.11 hooks: # Run the linter. - id: ruff diff --git a/docs/tutorial.md b/docs/tutorial.md index 1e249e10c8c51f..e3ab734582151e 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.10 + rev: v0.11.11 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index b034bca36b7193..0b983b27a04d20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.11.10" +version = "0.11.11" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index db8d6ca413070d..2b11e73624e98b 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scripts" -version = "0.11.10" +version = "0.11.11" description = "" authors = ["Charles Marsh "] From 0b181bc2adbc285fedb6f1f30a698383c6b6651c Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 22 May 2025 14:47:05 -0500 Subject: [PATCH 208/487] Fix instance vs callable subtyping/assignability (#18260) ## Summary Fix some issues with subtying/assignability for instances vs callables. We need to look up dunders on the class, not the instance, and we should limit our logic here to delegating to the type of `__call__`, so it doesn't get out of sync with the calls we allow. Also, we were just entirely missing assignability handling for `__call__` implemented as anything other than a normal bound method (though we had it for subtyping.) A first step towards considering what else we want to change in https://github.com/astral-sh/ty/issues/491 ## Test Plan mdtests --------- Co-authored-by: med Co-authored-by: Alex Waygood --- .../resources/mdtest/call/dunder.md | 34 +++++++++++++++ .../type_properties/is_assignable_to.md | 23 +++++++++++ .../mdtest/type_properties/is_subtype_of.md | 23 +++++++++++ crates/ty_python_semantic/src/types.rs | 41 +++++++++++++------ 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 5caeb1f3b5d558..8c2a70a2c4deb2 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -239,3 +239,37 @@ def _(flag: bool): # error: [possibly-unbound-implicit-call] reveal_type(c[0]) # revealed: str ``` + +## Dunder methods cannot be looked up on instances + +Class-level annotations with no value assigned are considered instance-only, and aren't available as +dunder methods: + +```py +from typing import Callable + +class C: + __call__: Callable[..., None] + +# error: [call-non-callable] +C()() + +# error: [invalid-assignment] +_: Callable[..., None] = C() +``` + +And of course the same is true if we have only an implicit assignment inside a method: + +```py +from typing import Callable + +class C: + def __init__(self): + self.__call__ = lambda *a, **kw: None + +# error: [call-non-callable] +C()() + +# error: [invalid-assignment] +_: Callable[..., None] = C() +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 597fd4a61464e0..afc28ac58710c5 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -672,6 +672,29 @@ def f(x: int, y: str) -> None: ... c1: Callable[[int], None] = partial(f, y="a") ``` +### Classes with `__call__` as attribute + +An instance type is assignable to a compatible callable type if the instance type's class has a +callable `__call__` attribute. + +TODO: for the moment, we don't consider the callable type as a bound-method descriptor, but this may +change for better compatibility with mypy/pyright. + +```py +from typing import Callable +from ty_extensions import static_assert, is_assignable_to + +def call_impl(a: int) -> str: + return "" + +class A: + __call__: Callable[[int], str] = call_impl + +static_assert(is_assignable_to(A, Callable[[int], str])) +static_assert(not is_assignable_to(A, Callable[[int], int])) +reveal_type(A()(1)) # revealed: str +``` + ## Generics ### Assignability of generic types parameterized by gradual types diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index f9a97c8c51ef0a..8f0f360f096078 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -1157,6 +1157,29 @@ def f(fn: Callable[[int], int]) -> None: ... f(a) ``` +### Classes with `__call__` as attribute + +An instance type can be a subtype of a compatible callable type if the instance type's class has a +callable `__call__` attribute. + +TODO: for the moment, we don't consider the callable type as a bound-method descriptor, but this may +change for better compatibility with mypy/pyright. + +```py +from typing import Callable +from ty_extensions import static_assert, is_subtype_of + +def call_impl(a: int) -> str: + return "" + +class A: + __call__: Callable[[int], str] = call_impl + +static_assert(is_subtype_of(A, Callable[[int], str])) +static_assert(not is_subtype_of(A, Callable[[int], int])) +reveal_type(A()(1)) # revealed: str +``` + ### Class literals #### Classes with metaclasses diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 1db7479afa9f89..8a91eac8576380 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1291,12 +1291,20 @@ impl<'db> Type<'db> { } (Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => { - let call_symbol = self.member(db, "__call__").symbol; - match call_symbol { - Symbol::Type(Type::BoundMethod(call_function), _) => call_function - .into_callable_type(db) - .is_subtype_of(db, target), - _ => false, + let call_symbol = self + .member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) + .symbol; + // If the type of __call__ is a subtype of a callable type, this instance is. + // Don't add other special cases here; our subtyping of a callable type + // shouldn't get out of sync with the calls we will actually allow. + if let Symbol::Type(t, Boundness::Bound) = call_symbol { + t.is_subtype_of(db, target) + } else { + false } } (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { @@ -1641,12 +1649,20 @@ impl<'db> Type<'db> { } (Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => { - let call_symbol = self.member(db, "__call__").symbol; - match call_symbol { - Symbol::Type(Type::BoundMethod(call_function), _) => call_function - .into_callable_type(db) - .is_assignable_to(db, target), - _ => false, + let call_symbol = self + .member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) + .symbol; + // If the type of __call__ is assignable to a callable type, this instance is. + // Don't add other special cases here; our assignability to a callable type + // shouldn't get out of sync with the calls we will actually allow. + if let Symbol::Type(t, Boundness::Bound) = call_symbol { + t.is_assignable_to(db, target) + } else { + false } } @@ -2746,6 +2762,7 @@ impl<'db> Type<'db> { instance.display(db), owner.display(db) ); + let descr_get = self.class_member(db, "__get__".into()).symbol; if let Symbol::Type(descr_get, descr_get_boundness) = descr_get { From 6c0a59ea786412bfd3e75a0e146606d2a9e6c5fd Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 22 May 2025 16:21:51 -0500 Subject: [PATCH 209/487] Fix insider docs requirement syntax (#18265) Attempting to fix the `mkdocs` workflow (maybe `uv` is more forgiving than `pip` for the syntax in `requirements.txt`?) --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index bd78766ab99416..4b0f7df5d26a93 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -5,4 +5,4 @@ mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.gi mkdocs-redirects==1.2.2 mdformat==0.7.22 mdformat-mkdocs==4.1.2 -mkdocs-github-admonitions-plugin @ https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 +mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 diff --git a/docs/requirements.txt b/docs/requirements.txt index b1bfb2b6409172..e18d1ecfc6bae7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,4 +5,4 @@ mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 mdformat==0.7.22 mdformat-mkdocs==4.1.2 -mkdocs-github-admonitions-plugin @ https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 +mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 From d02c9ada5d077ff720ca780db350a546d7f787d3 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 22 May 2025 21:37:03 -0400 Subject: [PATCH 210/487] [ty] Do not carry the generic context of `Protocol` or `Generic` in the `ClassBase` enum (#17989) ## Summary It doesn't seem to be necessary for our generics implementation to carry the `GenericContext` in the `ClassBase` variants. Removing it simplifies the code, fixes many TODOs about `Generic` or `Protocol` appearing multiple times in MROs when each should only appear at most once, and allows us to more accurately detect runtime errors that occur due to `Generic` or `Protocol` appearing multiple times in a class's bases. In order to remove the `GenericContext` from the `ClassBase` variant, it turns out to be necessary to emulate `typing._GenericAlias.__mro_entries__`, or we end up with a large number of false-positive `inconsistent-mro` errors. This PR therefore also does that. Lastly, this PR fixes the inferred MROs of PEP-695 generic classes, which implicitly inherit from `Generic` even if they have no explicit bases. ## Test Plan mdtests --- crates/ty/docs/rules.md | 108 +++++++------- .../annotations/stdlib_typing_aliases.md | 23 ++- .../mdtest/generics/legacy/classes.md | 4 +- .../resources/mdtest/mro.md | 39 +++++ .../resources/mdtest/protocols.md | 6 +- ...Os_in\342\200\246_(e2b355c09a967862).snap" | 37 +++++ .../resources/mdtest/stubs/class.md | 2 +- .../resources/mdtest/subscript/tuple.md | 4 +- crates/ty_python_semantic/src/types.rs | 4 + crates/ty_python_semantic/src/types/class.rs | 17 +-- .../src/types/class_base.rs | 84 ++--------- .../src/types/diagnostic.rs | 6 +- crates/ty_python_semantic/src/types/infer.rs | 2 +- crates/ty_python_semantic/src/types/mro.rs | 140 ++++++++++++------ .../src/types/type_ordering.rs | 10 +- 15 files changed, 271 insertions(+), 215 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_Unresolvable_MROs_in\342\200\246_(e2b355c09a967862).snap" diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index f64e9152945b3b..6eac8f74083b4c 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -50,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L90) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91) ## `conflicting-argument-forms` @@ -81,7 +81,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135) ## `conflicting-declarations` @@ -111,7 +111,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161) ## `conflicting-metaclass` @@ -142,7 +142,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186) ## `cyclic-class-definition` @@ -173,7 +173,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212) ## `duplicate-base` @@ -199,7 +199,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L255) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256) ## `escape-character-in-forward-annotation` @@ -336,7 +336,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L276) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277) ## `inconsistent-mro` @@ -365,7 +365,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L362) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363) ## `index-out-of-bounds` @@ -390,7 +390,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L386) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) ## `invalid-argument-type` @@ -416,7 +416,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L406) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407) ## `invalid-assignment` @@ -443,7 +443,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L446) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447) ## `invalid-attribute-access` @@ -476,7 +476,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395) ## `invalid-base` @@ -499,7 +499,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L468) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469) ## `invalid-context-manager` @@ -525,7 +525,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L519) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520) ## `invalid-declaration` @@ -553,7 +553,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L540) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541) ## `invalid-exception-caught` @@ -594,7 +594,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L563) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) ## `invalid-generic-class` @@ -625,7 +625,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L599) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600) ## `invalid-legacy-type-variable` @@ -658,7 +658,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) ## `invalid-metaclass` @@ -690,7 +690,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L674) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675) ## `invalid-overload` @@ -738,7 +738,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L701) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702) ## `invalid-parameter-default` @@ -763,7 +763,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L744) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745) ## `invalid-protocol` @@ -796,7 +796,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335) ## `invalid-raise` @@ -844,7 +844,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) ## `invalid-return-type` @@ -868,7 +868,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) ## `invalid-super-argument` @@ -912,7 +912,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L807) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808) ## `invalid-syntax-in-forward-annotation` @@ -952,7 +952,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654) ## `invalid-type-checking-constant` @@ -981,7 +981,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847) ## `invalid-type-form` @@ -1010,7 +1010,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L870) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871) ## `invalid-type-variable-constraints` @@ -1044,7 +1044,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895) ## `missing-argument` @@ -1068,7 +1068,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924) ## `no-matching-overload` @@ -1096,7 +1096,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L942) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943) ## `non-subscriptable` @@ -1119,7 +1119,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L965) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966) ## `not-iterable` @@ -1144,7 +1144,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984) ## `parameter-already-assigned` @@ -1170,7 +1170,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035) ## `raw-string-type-annotation` @@ -1229,7 +1229,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371) ## `subclass-of-final-class` @@ -1257,7 +1257,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1125) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126) ## `too-many-positional-arguments` @@ -1283,7 +1283,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1170) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171) ## `type-assertion-failure` @@ -1310,7 +1310,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149) ## `unavailable-implicit-super-arguments` @@ -1354,7 +1354,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1191) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192) ## `unknown-argument` @@ -1380,7 +1380,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1248) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249) ## `unresolved-attribute` @@ -1407,7 +1407,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270) ## `unresolved-import` @@ -1431,7 +1431,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292) ## `unresolved-reference` @@ -1455,7 +1455,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1310) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) ## `unsupported-bool-conversion` @@ -1491,7 +1491,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1003) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004) ## `unsupported-operator` @@ -1518,7 +1518,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330) ## `zero-stepsize-in-slice` @@ -1542,7 +1542,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352) ## `invalid-ignore-comment` @@ -1598,7 +1598,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1055) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056) ## `possibly-unbound-implicit-call` @@ -1629,7 +1629,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L108) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109) ## `possibly-unbound-import` @@ -1660,7 +1660,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078) ## `redundant-cast` @@ -1686,7 +1686,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1422) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423) ## `undefined-reveal` @@ -1709,7 +1709,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231) ## `unknown-rule` @@ -1777,7 +1777,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L486) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487) ## `division-by-zero` @@ -1800,7 +1800,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238) ## `possibly-unresolved-reference` @@ -1827,7 +1827,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1103) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md index 94d6a9ec7fe81c..0801b2d0dbcd01 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md @@ -81,23 +81,22 @@ import typing class ListSubclass(typing.List): ... -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(ListSubclass.__mro__) class DictSubclass(typing.Dict): ... -# TODO: should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DictSubclass.__mro__) class SetSubclass(typing.Set): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(SetSubclass.__mro__) class FrozenSetSubclass(typing.FrozenSet): ... -# revealed: tuple[, , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , typing.Protocol, typing.Generic, ] reveal_type(FrozenSetSubclass.__mro__) #################### @@ -106,30 +105,26 @@ reveal_type(FrozenSetSubclass.__mro__) class ChainMapSubclass(typing.ChainMap): ... -# TODO: should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(ChainMapSubclass.__mro__) class CounterSubclass(typing.Counter): ... -# TODO: Should have one `Generic[]` element, not three(!) -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(CounterSubclass.__mro__) class DefaultDictSubclass(typing.DefaultDict): ... -# TODO: Should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DefaultDictSubclass.__mro__) class DequeSubclass(typing.Deque): ... -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DequeSubclass.__mro__) class OrderedDictSubclass(typing.OrderedDict): ... -# TODO: Should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(OrderedDictSubclass.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 66f75742433392..d952ed9e86d734 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -24,9 +24,7 @@ class: ```py class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base] - -# TODO: should emit an error (fails at runtime) -class AlsoBad(Generic[T], Generic[S]): ... +class AlsoBad(Generic[T], Generic[S]): ... # error: [duplicate-base] ``` You cannot use the same typevar more than once. diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 810e716688dfcb..47d607fcda9ae3 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -527,6 +527,45 @@ reveal_type(unknown_object) # revealed: Unknown reveal_type(unknown_object.__mro__) # revealed: Unknown ``` +## MROs of classes that use multiple inheritance with generic aliases and subscripted `Generic` + +```py +from typing import Generic, TypeVar, Iterator + +T = TypeVar("T") + +class peekable(Generic[T], Iterator[T]): ... + +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +reveal_type(peekable.__mro__) + +class peekable2(Iterator[T], Generic[T]): ... + +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +reveal_type(peekable2.__mro__) + +class Base: ... +class Intermediate(Base, Generic[T]): ... +class Sub(Intermediate[T], Base): ... + +# revealed: tuple[, , , typing.Generic, ] +reveal_type(Sub.__mro__) +``` + +## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases + + + +```py +from typing_extensions import Protocol, TypeVar, Generic + +T = TypeVar("T") + +class Foo(Protocol): ... +class Bar(Protocol[T]): ... +class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] +``` + ## Classes that inherit from themselves These are invalid, but we need to be able to handle them gracefully without panicking. diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 28b12d8d859444..f14b838b6a5e0d 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -67,12 +67,10 @@ It's an error to include both bare `Protocol` and subscripted `Protocol[]` in th simultaneously: ```py -# TODO: should emit a `[duplicate-bases]` error here: -class DuplicateBases(Protocol, Protocol[T]): +class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base] x: T -# TODO: should not have `Protocol` or `Generic` multiple times -# revealed: tuple[, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], ] +# revealed: tuple[, Unknown, ] reveal_type(DuplicateBases.__mro__) ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_Unresolvable_MROs_in\342\200\246_(e2b355c09a967862).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_Unresolvable_MROs_in\342\200\246_(e2b355c09a967862).snap" new file mode 100644 index 00000000000000..81c10d03611365 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_Unresolvable_MROs_in\342\200\246_(e2b355c09a967862).snap" @@ -0,0 +1,37 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: mro.md - Method Resolution Order tests - Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases +mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from typing_extensions import Protocol, TypeVar, Generic +2 | +3 | T = TypeVar("T") +4 | +5 | class Foo(Protocol): ... +6 | class Bar(Protocol[T]): ... +7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] +``` + +# Diagnostics + +``` +error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], , ]` + --> src/mdtest_snippet.py:7:1 + | +5 | class Foo(Protocol): ... +6 | class Bar(Protocol[T]): ... +7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +info: rule `inconsistent-mro` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/stubs/class.md b/crates/ty_python_semantic/resources/mdtest/stubs/class.md index 64fe0bf48d1142..58355ec62b1438 100644 --- a/crates/ty_python_semantic/resources/mdtest/stubs/class.md +++ b/crates/ty_python_semantic/resources/mdtest/stubs/class.md @@ -16,7 +16,7 @@ class Foo[T]: ... class Bar(Foo[Bar]): ... reveal_type(Bar) # revealed: -reveal_type(Bar.__mro__) # revealed: tuple[, , ] +reveal_type(Bar.__mro__) # revealed: tuple[, , typing.Generic, ] ``` ## Access to attributes declared in stubs diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index fbd4434d025bf8..c286b86e02f840 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -83,7 +83,7 @@ python-version = "3.9" ```py class A(tuple[int, str]): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(A.__mro__) ``` @@ -114,6 +114,6 @@ from typing import Tuple class C(Tuple): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(C.__mro__) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8a91eac8576380..c21144b719c754 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -598,6 +598,10 @@ impl<'db> Type<'db> { matches!(self, Type::Dynamic(DynamicType::Todo(_))) } + pub const fn is_generic_alias(&self) -> bool { + matches!(self, Type::GenericAlias(_)) + } + /// Replace references to the class `class` with a self-reference marker. This is currently /// used for recursive protocols, but could probably be extended to self-referential type- /// aliases and similar. diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 4278fe0defc9a1..83870384ee971f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -223,6 +223,10 @@ impl<'db> ClassType<'db> { } } + pub(super) const fn is_generic(self) -> bool { + matches!(self, Self::Generic(_)) + } + /// Returns the class literal and specialization for this class. For a non-generic class, this /// is the class itself. For a generic alias, this is the alias's origin. pub(crate) fn class_literal( @@ -352,7 +356,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol(_) | ClassBase::Generic(_) => false, + ClassBase::Protocol | ClassBase::Generic => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -390,7 +394,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol(_) | ClassBase::Generic(_) => false, + ClassBase::Protocol | ClassBase::Generic => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -602,11 +606,6 @@ impl<'db> ClassLiteral<'db> { ) } - /// Return `true` if this class represents the builtin class `object` - pub(crate) fn is_object(self, db: &'db dyn Db) -> bool { - self.is_known(db, KnownClass::Object) - } - fn file(self, db: &dyn Db) -> File { self.body_scope(db).file(db) } @@ -1068,7 +1067,7 @@ impl<'db> ClassLiteral<'db> { for superclass in mro_iter { match superclass { - ClassBase::Generic(_) | ClassBase::Protocol(_) => { + ClassBase::Generic | ClassBase::Protocol => { // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { @@ -1427,7 +1426,7 @@ impl<'db> ClassLiteral<'db> { for superclass in self.iter_mro(db, specialization) { match superclass { - ClassBase::Generic(_) | ClassBase::Protocol(_) => { + ClassBase::Generic | ClassBase::Protocol => { // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 9ff7fcad6003c6..b52c525fa797f1 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,5 +1,5 @@ use crate::Db; -use crate::types::generics::{GenericContext, Specialization}; +use crate::types::generics::Specialization; use crate::types::{ ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, TypeMapping, todo_type, @@ -19,11 +19,11 @@ pub enum ClassBase<'db> { Class(ClassType<'db>), /// Although `Protocol` is not a class in typeshed's stubs, it is at runtime, /// and can appear in the MRO of a class. - Protocol(Option>), + Protocol, /// Bare `Generic` cannot be subclassed directly in user code, /// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`, /// `Protocol[T]`, or bare `Protocol`. - Generic(Option>), + Generic, } impl<'db> ClassBase<'db> { @@ -35,50 +35,8 @@ impl<'db> ClassBase<'db> { match self { Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), Self::Class(class) => Self::Class(class.normalized(db)), - Self::Protocol(generic_context) => { - Self::Protocol(generic_context.map(|context| context.normalized(db))) - } - Self::Generic(generic_context) => { - Self::Generic(generic_context.map(|context| context.normalized(db))) - } - } - } - - pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { - struct Display<'db> { - base: ClassBase<'db>, - db: &'db dyn Db, - } - - impl std::fmt::Display for Display<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.base { - ClassBase::Dynamic(dynamic) => dynamic.fmt(f), - ClassBase::Class(class @ ClassType::NonGeneric(_)) => { - write!(f, "", class.name(self.db)) - } - ClassBase::Class(ClassType::Generic(alias)) => { - write!(f, "", alias.display(self.db)) - } - ClassBase::Protocol(generic_context) => { - f.write_str("typing.Protocol")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - ClassBase::Generic(generic_context) => { - f.write_str("typing.Generic")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - } - } + Self::Protocol | Self::Generic => self, } - - Display { base: self, db } } pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { @@ -87,8 +45,8 @@ impl<'db> ClassBase<'db> { ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo", - ClassBase::Protocol(_) => "Protocol", - ClassBase::Generic(_) => "Generic", + ClassBase::Protocol => "Protocol", + ClassBase::Generic => "Generic", } } @@ -255,12 +213,8 @@ impl<'db> ClassBase<'db> { KnownInstanceType::Callable => { Self::try_from_type(db, todo_type!("Support for Callable as a base class")) } - KnownInstanceType::Protocol(generic_context) => { - Some(ClassBase::Protocol(generic_context)) - } - KnownInstanceType::Generic(generic_context) => { - Some(ClassBase::Generic(generic_context)) - } + KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol), + KnownInstanceType::Generic(_) => Some(ClassBase::Generic), }, } } @@ -268,14 +222,14 @@ impl<'db> ClassBase<'db> { pub(super) fn into_class(self) -> Option> { match self { Self::Class(class) => Some(class), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None, + Self::Dynamic(_) | Self::Generic | Self::Protocol => None, } } fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { match self { Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self, + Self::Dynamic(_) | Self::Generic | Self::Protocol => self, } } @@ -299,7 +253,7 @@ impl<'db> ClassBase<'db> { .try_mro(db, specialization) .is_err_and(MroError::is_cycle) } - ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false, + ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false, } } @@ -310,12 +264,8 @@ impl<'db> ClassBase<'db> { additional_specialization: Option>, ) -> impl Iterator> { match self { - ClassBase::Protocol(context) => { - ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context)) - } - ClassBase::Dynamic(_) | ClassBase::Generic(_) => { - ClassBaseMroIterator::length_2(db, self) - } + ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic), + ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self), ClassBase::Class(class) => { ClassBaseMroIterator::from_class(db, class, additional_specialization) } @@ -338,12 +288,8 @@ impl<'db> From> for Type<'db> { match value { ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Class(class) => class.into(), - ClassBase::Protocol(generic_context) => { - Type::KnownInstance(KnownInstanceType::Protocol(generic_context)) - } - ClassBase::Generic(generic_context) => { - Type::KnownInstance(KnownInstanceType::Generic(generic_context)) - } + ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)), + ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)), } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index d394650d1d282d..c996be3d0bf109 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -13,6 +13,7 @@ use crate::types::string_annotation::{ }; use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Program, PythonVersionWithSource, declare_lint}; +use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; use ruff_db::files::system_path_to_file; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -1698,10 +1699,7 @@ pub(super) fn report_implicit_return_type( let Some(class) = enclosing_class_of_method else { return; }; - if class - .iter_mro(db, None) - .any(|base| matches!(base, ClassBase::Protocol(_))) - { + if class.iter_mro(db, None).contains(&ClassBase::Protocol) { diagnostic.info( "Only functions in stub files, methods on protocol classes, \ or methods with `@abstractmethod` are permitted to have empty bodies", diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 9ad791217fef24..649f4992ac4948 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -7529,7 +7529,7 @@ impl<'db> TypeInferenceBuilder<'db> { if !value_ty.into_class_literal().is_some_and(|class| { class .iter_mro(self.db(), None) - .any(|base| matches!(base, ClassBase::Generic(_))) + .contains(&ClassBase::Generic) }) { report_non_subscriptable( &self.context, diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 151fbf69665b13..72f3eceafa8466 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher; use crate::Db; use crate::types::class_base::ClassBase; use crate::types::generics::Specialization; -use crate::types::{ClassLiteral, ClassType, Type}; +use crate::types::{ClassLiteral, ClassType, KnownInstanceType, Type}; /// The inferred method resolution order of a given class. /// @@ -48,12 +48,12 @@ impl<'db> Mro<'db> { /// [`super::infer::TypeInferenceBuilder::infer_region_scope`].) pub(super) fn of_class( db: &'db dyn Db, - class: ClassLiteral<'db>, + class_literal: ClassLiteral<'db>, specialization: Option>, ) -> Result> { - Self::of_class_impl(db, class, specialization).map_err(|err| { - err.into_mro_error(db, class.apply_optional_specialization(db, specialization)) - }) + let class = class_literal.apply_optional_specialization(db, specialization); + Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization) + .map_err(|err| err.into_mro_error(db, class)) } pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self { @@ -66,17 +66,16 @@ impl<'db> Mro<'db> { fn of_class_impl( db: &'db dyn Db, - class: ClassLiteral<'db>, + class: ClassType<'db>, + bases: &[Type<'db>], specialization: Option>, ) -> Result> { - let class_type = class.apply_optional_specialization(db, specialization); - - match class.explicit_bases(db) { + match bases { // `builtins.object` is the special case: // the only class in Python that has an MRO with length <2 [] if class.is_object(db) => Ok(Self::from([ // object is not generic, so the default specialization should be a no-op - ClassBase::Class(class_type), + ClassBase::Class(class), ])), // All other classes in Python have an MRO with length >=2. @@ -92,44 +91,82 @@ impl<'db> Mro<'db> { // >>> Foo.__mro__ // (, ) // ``` - [] => Ok(Self::from([ - ClassBase::Class(class_type), - ClassBase::object(db), - ])), + [] => { + // e.g. `class Foo[T]: ...` implicitly has `Generic` inserted into its bases + if class.is_generic() { + Ok(Self::from([ + ClassBase::Class(class), + ClassBase::Generic, + ClassBase::object(db), + ])) + } else { + Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)])) + } + } // Fast path for a class that has only a single explicit base. // // This *could* theoretically be handled by the final branch below, // but it's a common case (i.e., worth optimizing for), // and the `c3_merge` function requires lots of allocations. - [single_base] => ClassBase::try_from_type(db, *single_base).map_or_else( - || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), - |single_base| { - if single_base.has_cyclic_mro(db) { - Err(MroErrorKind::InheritanceCycle) - } else { - Ok(std::iter::once(ClassBase::Class( - class.apply_optional_specialization(db, specialization), - )) - .chain(single_base.mro(db, specialization)) - .collect()) - } - }, - ), + [single_base] + if !matches!( + single_base, + Type::GenericAlias(_) + | Type::KnownInstance( + KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) + ) + ) => + { + ClassBase::try_from_type(db, *single_base).map_or_else( + || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), + |single_base| { + if single_base.has_cyclic_mro(db) { + Err(MroErrorKind::InheritanceCycle) + } else { + Ok(std::iter::once(ClassBase::Class(class)) + .chain(single_base.mro(db, specialization)) + .collect()) + } + }, + ) + } // The class has multiple explicit bases. // // We'll fallback to a full implementation of the C3-merge algorithm to determine // what MRO Python will give this class at runtime // (if an MRO is indeed resolvable at all!) - multiple_bases => { - let mut valid_bases = vec![]; + original_bases => { + let mut resolved_bases = vec![]; let mut invalid_bases = vec![]; - for (i, base) in multiple_bases.iter().enumerate() { - match ClassBase::try_from_type(db, *base) { - Some(valid_base) => valid_bases.push(valid_base), - None => invalid_bases.push((i, *base)), + for (i, base) in original_bases.iter().enumerate() { + // This emulates the behavior of `typing._GenericAlias.__mro_entries__` at + // . + // + // Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere + // (see `infer::TypeInferenceBuilder::check_class_definitions`), + // which is why we only care about `KnownInstanceType::Generic(Some(_))`, + // not `KnownInstanceType::Generic(None)`. + if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base { + if original_bases + .contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) + { + continue; + } + if original_bases[i + 1..] + .iter() + .any(|b| b.is_generic_alias() && b != base) + { + continue; + } + resolved_bases.push(ClassBase::Generic); + } else { + match ClassBase::try_from_type(db, *base) { + Some(valid_base) => resolved_bases.push(valid_base), + None => invalid_bases.push((i, *base)), + } } } @@ -137,15 +174,15 @@ impl<'db> Mro<'db> { return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice())); } - let mut seqs = vec![VecDeque::from([ClassBase::Class(class_type)])]; - for base in &valid_bases { + let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])]; + for base in &resolved_bases { if base.has_cyclic_mro(db) { return Err(MroErrorKind::InheritanceCycle); } seqs.push(base.mro(db, specialization).collect()); } seqs.push( - valid_bases + resolved_bases .iter() .map(|base| base.apply_optional_specialization(db, specialization)) .collect(), @@ -161,8 +198,20 @@ impl<'db> Mro<'db> { let mut base_to_indices: IndexMap, Vec, FxBuildHasher> = IndexMap::default(); - for (index, base) in valid_bases.iter().enumerate() { - base_to_indices.entry(*base).or_default().push(index); + // We need to iterate over `original_bases` here rather than `resolved_bases` + // so that we get the correct index of the duplicate bases if there were any + // (`resolved_bases` may be a longer list than `original_bases`!). However, we + // need to use a `ClassBase` rather than a `Type` as the key type for the + // `base_to_indices` map so that a class such as + // `class Foo(Protocol[T], Protocol): ...` correctly causes us to emit a + // `duplicate-base` diagnostic (matching the runtime behaviour) rather than an + // `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as + // precise!). + for (index, base) in original_bases.iter().enumerate() { + let Some(base) = ClassBase::try_from_type(db, *base) else { + continue; + }; + base_to_indices.entry(base).or_default().push(index); } let mut errors = vec![]; @@ -175,9 +224,7 @@ impl<'db> Mro<'db> { continue; } match base { - ClassBase::Class(_) - | ClassBase::Generic(_) - | ClassBase::Protocol(_) => { + ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => { errors.push(DuplicateBaseError { duplicate_base: base, first_index: *first_index, @@ -193,13 +240,10 @@ impl<'db> Mro<'db> { if duplicate_bases.is_empty() { if duplicate_dynamic_bases { - Ok(Mro::from_error( - db, - class.apply_optional_specialization(db, specialization), - )) + Ok(Mro::from_error(db, class)) } else { Err(MroErrorKind::UnresolvableMro { - bases_list: valid_bases.into_boxed_slice(), + bases_list: original_bases.iter().copied().collect(), }) } } else { @@ -378,7 +422,7 @@ pub(super) enum MroErrorKind<'db> { /// The MRO is otherwise unresolvable through the C3-merge algorithm. /// /// See [`c3_merge`] for more details. - UnresolvableMro { bases_list: Box<[ClassBase<'db>]> }, + UnresolvableMro { bases_list: Box<[Type<'db>]> }, } impl<'db> MroErrorKind<'db> { diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 465fc389005b5d..a52dda24af0cb1 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -152,13 +152,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (ClassBase::Class(_), _) => Ordering::Less, (_, ClassBase::Class(_)) => Ordering::Greater, - (ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right), - (ClassBase::Protocol(_), _) => Ordering::Less, - (_, ClassBase::Protocol(_)) => Ordering::Greater, + (ClassBase::Protocol, _) => Ordering::Less, + (_, ClassBase::Protocol) => Ordering::Greater, - (ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right), - (ClassBase::Generic(_), _) => Ordering::Less, - (_, ClassBase::Generic(_)) => Ordering::Greater, + (ClassBase::Generic, _) => Ordering::Less, + (_, ClassBase::Generic) => Ordering::Greater, (ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => { dynamic_elements_ordering(left, right) From aae4482c552eb297ad552366a12e73c43f425daf Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 23 May 2025 10:44:46 +0200 Subject: [PATCH 211/487] [ty] Replace remaining knot.toml reference (#18269) ## Summary Fix remaining `knot.toml` reference and replace it with `ty.toml`. This change was probably still in flight while we renamed things. ## Test Plan Added a second assertion which ensures that the config file has any effect. --- crates/ty/tests/cli.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index c0a510a1438c69..d42ab07b45b4f5 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -1454,10 +1454,10 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { } #[test] -fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { +fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> { let case = TestCase::with_files(vec![ ( - "knot.toml", + "ty.toml", r#" [terminal] error-on-warning = true @@ -1465,6 +1465,27 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> { ), ("test.py", r"print(x) # [unresolved-reference]"), ])?; + + // Exit code of 1 due to the setting in `ty.toml` + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Exit code of 0 because the `ty.toml` setting is overwritten by `--config` assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r" success: true exit_code: 0 From 93ac0934dd0d9792276c70b486ba5f53f79bed2e Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 23 May 2025 11:41:31 +0200 Subject: [PATCH 212/487] [ty] Type compendium (#18263) ## Summary This is something I wrote a few months ago, and continued to update from time to time. It was mostly written for my own education. I found a few bugs while writing it at the time (there are still one or two TODOs in the test assertions that are probably bugs). Our other tests are fairly comprehensive, but they are usually structured around a certain functionality or operation (subtyping, assignability, narrowing). The idea here was to focus on individual *types and their properties*. closes #197 (added `JustFloat` and `JustComplex` to `ty_extensions`). --- .../mdtest/type_compendium/README.md | 20 ++ .../type_compendium/always_truthy_falsy.md | 175 +++++++++++++ .../resources/mdtest/type_compendium/any.md | 141 +++++++++++ .../type_compendium/integer_literals.md | 234 ++++++++++++++++++ .../resources/mdtest/type_compendium/never.md | 185 ++++++++++++++ .../resources/mdtest/type_compendium/none.md | 80 ++++++ .../resources/mdtest/type_compendium/not_t.md | 120 +++++++++ .../mdtest/type_compendium/object.md | 78 ++++++ .../resources/mdtest/type_compendium/tuple.md | 166 +++++++++++++ .../mdtest/type_properties/is_subtype_of.md | 9 +- .../ty_extensions/ty_extensions.pyi | 9 + 11 files changed, 1210 insertions(+), 7 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/README.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/always_truthy_falsy.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/any.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/integer_literals.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/never.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/none.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/not_t.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/object.md create mode 100644 crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/README.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/README.md new file mode 100644 index 00000000000000..99fa88d552b0e5 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/README.md @@ -0,0 +1,20 @@ +# Type compendium + +The type compendium contains "fact sheets" about important, interesting, and peculiar types in (ty's +interpretation of) Python's type system. It is meant to be an educational reference for developers +and users of ty. It is also a living document that ensures that our implementation of these types +and their properties is consistent with the specification. + +## Table of contents + +- [`Never`](never.md) +- [`Object`](object.md) +- [`None`](none.md) +- [Integer `Literal`s](integer_literals.md) +- String `Literal`s, `LiteralString` +- [`tuple` types](tuple.md) +- Class instance types +- [`Any`](any.md) +- Class literal types, `type[C]`, `type[object]`, `type[Any]` +- [`AlwaysTruthy`, `AlwaysFalsy`](always_truthy_falsy.md) +- [`Not[T]`](not_t.md) diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/always_truthy_falsy.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/always_truthy_falsy.md new file mode 100644 index 00000000000000..ede8b40a30e5e8 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/always_truthy_falsy.md @@ -0,0 +1,175 @@ +# `AlwaysTruthy` and `AlwaysFalsy` + +```toml +[environment] +python-version = "3.12" +``` + +The types `AlwaysTruthy` and `AlwaysFalsy` describe the set of values that are always truthy or +always falsy, respectively. More concretely, a value `at` is of type `AlwaysTruthy` if we can +statically infer that `bool(at)` is always `True`, i.e. that the expression `bool(at)` has type +`Literal[True]`. Conversely, a value `af` is of type `AlwaysFalsy` if we can statically infer that +`bool(af)` is always `False`, i.e. that `bool(af)` has type `Literal[False]`. + +## Examples + +Here, we give a few examples of values that belong to these types: + +```py +from ty_extensions import AlwaysTruthy, AlwaysFalsy +from typing_extensions import Literal + +class CustomAlwaysTruthyType: + def __bool__(self) -> Literal[True]: + return True + +class CustomAlwaysFalsyType: + def __bool__(self) -> Literal[False]: + return False + +at: AlwaysTruthy +at = True +at = 1 +at = 123 +at = -1 +at = "non empty" +at = b"non empty" +at = CustomAlwaysTruthyType() + +af: AlwaysFalsy +af = False +af = None +af = 0 +af = "" +af = b"" +af = CustomAlwaysFalsyType() +``` + +## `AlwaysTruthy` and `AlwaysFalsy` are disjoint + +It follows directly from the definition that `AlwaysTruthy` and `AlwaysFalsy` are disjoint types: + +```py +from ty_extensions import static_assert, is_disjoint_from, AlwaysTruthy, AlwaysFalsy + +static_assert(is_disjoint_from(AlwaysTruthy, AlwaysFalsy)) +``` + +## `Truthy` and `Falsy` + +It is useful to also define the types `Truthy = ~AlwaysFalsy` and `Falsy = ~AlwaysTruthy`. These +types describe the set of values that *can* be truthy (`bool(t)` can return `True`) or falsy +(`bool(f)` can return `False`), respectively. + +Finally, we can also define the type `AmbiguousTruthiness = Truthy & Falsy`, which describes the set +of values that can be truthy *and* falsy. This intersection is not empty. In the following, we give +examples for values that belong to these three types: + +```py +from ty_extensions import static_assert, is_equivalent_to, is_disjoint_from, Not, Intersection, AlwaysTruthy, AlwaysFalsy +from typing_extensions import Never +from random import choice + +type Truthy = Not[AlwaysFalsy] +type Falsy = Not[AlwaysTruthy] + +type AmbiguousTruthiness = Intersection[Truthy, Falsy] + +static_assert(is_disjoint_from(AlwaysTruthy, AmbiguousTruthiness)) +static_assert(is_disjoint_from(AlwaysFalsy, AmbiguousTruthiness)) +static_assert(not is_disjoint_from(Truthy, Falsy)) + +class CustomAmbiguousTruthinessType: + def __bool__(self) -> bool: + return choice((True, False)) + +def maybe_empty_list() -> list[int]: + return choice(([], [1, 2, 3])) + +reveal_type(bool(maybe_empty_list())) # revealed: bool +reveal_type(bool(CustomAmbiguousTruthinessType())) # revealed: bool + +t: Truthy +t = True +t = 1 +# TODO: This assignment should be okay +t = maybe_empty_list() # error: [invalid-assignment] +# TODO: This assignment should be okay +t = CustomAmbiguousTruthinessType() # error: [invalid-assignment] + +a: AmbiguousTruthiness +# TODO: This assignment should be okay +a = maybe_empty_list() # error: [invalid-assignment] +# TODO: This assignment should be okay +a = CustomAmbiguousTruthinessType() # error: [invalid-assignment] + +f: Falsy +f = False +f = None +# TODO: This assignment should be okay +f = maybe_empty_list() # error: [invalid-assignment] +# TODO: This assignment should be okay +f = CustomAmbiguousTruthinessType() # error: [invalid-assignment] +``` + +## Subtypes of `AlwaysTruthy`, `AlwaysFalsy` + +```py +from ty_extensions import static_assert, is_subtype_of, is_disjoint_from, AlwaysTruthy, AlwaysFalsy +from typing_extensions import Literal +``` + +These two types are disjoint, so types (that are not equivalent to Never) can only be a subtype of +either one of them. + +```py +static_assert(is_disjoint_from(AlwaysTruthy, AlwaysFalsy)) +``` + +Types that only contain always-truthy values + +```py +static_assert(is_subtype_of(Literal[True], AlwaysTruthy)) +static_assert(is_subtype_of(Literal[1], AlwaysTruthy)) +static_assert(is_subtype_of(Literal[-1], AlwaysTruthy)) +static_assert(is_subtype_of(Literal["non empty"], AlwaysTruthy)) +static_assert(is_subtype_of(Literal[b"non empty"], AlwaysTruthy)) +``` + +Types that only contain always-falsy values + +```py +static_assert(is_subtype_of(None, AlwaysFalsy)) +static_assert(is_subtype_of(Literal[False], AlwaysFalsy)) +static_assert(is_subtype_of(Literal[0], AlwaysFalsy)) +static_assert(is_subtype_of(Literal[""], AlwaysFalsy)) +static_assert(is_subtype_of(Literal[b""], AlwaysFalsy)) +static_assert(is_subtype_of(Literal[False] | Literal[0], AlwaysFalsy)) +``` + +Ambiguous truthiness types + +```py +static_assert(not is_subtype_of(bool, AlwaysTruthy)) +static_assert(not is_subtype_of(bool, AlwaysFalsy)) + +static_assert(not is_subtype_of(list[int], AlwaysTruthy)) +static_assert(not is_subtype_of(list[int], AlwaysFalsy)) +``` + +## Open questions + +Is `tuple[()]` always falsy? We currently model it this way, but this is +[under discussion](https://github.com/astral-sh/ruff/issues/15528). + +```py +from ty_extensions import static_assert, is_subtype_of, AlwaysFalsy + +static_assert(is_subtype_of(tuple[()], AlwaysFalsy)) +``` + +## References + +See also: + +- Our test suite on [narrowing for `if x` and `if not x`](../narrow/truthiness.md). diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/any.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/any.md new file mode 100644 index 00000000000000..da545f70a22f91 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/any.md @@ -0,0 +1,141 @@ +# `Any` + +## Introduction + +The type `Any` is the dynamic type in Python's gradual type system. It represents an unknown +fully-static type, which means that it represents an *unknown* set of runtime values. + +```py +from ty_extensions import static_assert, is_fully_static +from typing import Any +``` + +`Any` is a dynamic type: + +```py +static_assert(not is_fully_static(Any)) +``` + +## Every type is assignable to `Any`, and `Any` is assignable to every type + +```py +from ty_extensions import static_assert, is_fully_static, is_assignable_to +from typing_extensions import Never, Any + +class C: ... + +static_assert(is_assignable_to(C, Any)) +static_assert(is_assignable_to(Any, C)) + +static_assert(is_assignable_to(object, Any)) +static_assert(is_assignable_to(Any, object)) + +static_assert(is_assignable_to(Never, Any)) +static_assert(is_assignable_to(Any, Never)) + +static_assert(is_assignable_to(type, Any)) +static_assert(is_assignable_to(Any, type)) + +static_assert(is_assignable_to(type[Any], Any)) +static_assert(is_assignable_to(Any, type[Any])) +``` + +`Any` is also assignable to itself (like every type): + +```py +static_assert(is_assignable_to(Any, Any)) +``` + +## Unions with `Any`: `Any | T` + +The union `Any | T` of `Any` with a fully static type `T` describes an unknown set of values that is +*at least as large* as the set of values described by `T`. It represents an unknown fully-static +type with *lower bound* `T`. Again, this can be demonstrated using the assignable-to relation: + +```py +from ty_extensions import static_assert, is_assignable_to, is_equivalent_to +from typing_extensions import Any + +# A class hierarchy Small <: Medium <: Big + +class Big: ... +class Medium(Big): ... +class Small(Medium): ... + +static_assert(is_assignable_to(Any | Medium, Big)) +static_assert(is_assignable_to(Any | Medium, Medium)) + +# `Any | Medium` is at least as large as `Medium`, so we can not assign it to `Small`: +static_assert(not is_assignable_to(Any | Medium, Small)) +``` + +The union `Any | object` is equivalent to `object`. This is true for every union with `object`, but +it is worth demonstrating: + +```py +static_assert(is_equivalent_to(Any | object, object)) +static_assert(is_equivalent_to(object | Any, object)) +``` + +## Intersections with `Any`: `Any & T` + +The intersection `Any & T` of `Any` with a fully static type `T` describes an unknown set of values +that is *no larger than* the set of values described by `T`. It represents an unknown fully-static +type with *upper bound* `T`: + +```py +from ty_extensions import static_assert, is_assignable_to, Intersection, is_equivalent_to +from typing import Any + +class Big: ... +class Medium(Big): ... +class Small(Medium): ... + +static_assert(is_assignable_to(Small, Intersection[Any, Medium])) +static_assert(is_assignable_to(Medium, Intersection[Any, Medium])) +``` + +`Any & Medium` is no larger than `Medium`, so we can not assign `Big` to it. There is no possible +materialization of `Any & Medium` that would make it as big as `Big`: + +```py +static_assert(not is_assignable_to(Big, Intersection[Any, Medium])) +``` + +`Any & Never` represents an "unknown" fully-static type which is no larger than `Never`. There is no +such fully-static type, except for `Never` itself. So `Any & Never` is equivalent to `Never`: + +```py +from typing_extensions import Never + +static_assert(is_equivalent_to(Intersection[Any, Never], Never)) +static_assert(is_equivalent_to(Intersection[Never, Any], Never)) +``` + +## Tuples with `Any` + +This section demonstrates the following passage from the [type system concepts] documentation on +gradual types: + +> A type such as `tuple[int, Any]` […] does not represent a single set of Python objects; rather, it +> represents a (bounded) range of possible sets of values. […] In the same way that `Any` does not +> represent "the set of all Python objects" but rather "an unknown set of objects", +> `tuple[int, Any]` does not represent "the set of all length-two tuples whose first element is an +> integer". That is a fully static type, spelled `tuple[int, object]`. By contrast, +> `tuple[int, Any]` represents some unknown set of tuple values; it might be the set of all tuples +> of two integers, or the set of all tuples of an integer and a string, or some other set of tuple +> values. +> +> In practice, this difference is seen (for example) in the fact that we can assign an expression of +> type `tuple[int, Any]` to a target typed as `tuple[int, int]`, whereas assigning +> `tuple[int, object]` to `tuple[int, int]` is a static type error. + +```py +from ty_extensions import static_assert, is_assignable_to +from typing import Any + +static_assert(is_assignable_to(tuple[int, Any], tuple[int, int])) +static_assert(not is_assignable_to(tuple[int, object], tuple[int, int])) +``` + +[type system concepts]: https://typing.readthedocs.io/en/latest/spec/concepts.html#gradual-types diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/integer_literals.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/integer_literals.md new file mode 100644 index 00000000000000..d8d42ae7ad2d0f --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/integer_literals.md @@ -0,0 +1,234 @@ +# Integer `Literal`s + +An integer literal type represents the set of all integer objects with one specific value. For +example, the type `Literal[54165]` represents the set of all integer objects with the value `54165`. + +## Integer `Literal`s are not singleton types + +This does not necessarily mean that the type is a singleton type, i.e., a type with only one +inhabitant. The reason for this is that there might be multiple Python runtime objects (at different +memory locations) that all represent the same integer value. For example, the following code snippet +may print `False`. + +```py +x = 54165 +y = 54165 + +print(x is y) +``` + +In practice, on CPython 3.13.0, this program prints `True` when executed as a script, but `False` +when executed in the REPL. + +Since this is an implementation detail of the Python runtime, we model all integer literals as +non-singleton types: + +```py +from ty_extensions import static_assert, is_singleton +from typing import Literal + +static_assert(not is_singleton(Literal[0])) +static_assert(not is_singleton(Literal[1])) +static_assert(not is_singleton(Literal[54165])) +``` + +This has implications for type-narrowing. For example, you can not use the `is not` operator to +check whether a variable has a specific integer literal type, but this is not a recommended practice +anyway. + +```py +def f(x: int): + if x is 54165: + # This works, because if `x` is the same object as that left-hand-side literal, then it + # must have the same value. + reveal_type(x) # revealed: Literal[54165] + + if x is not 54165: + # But here, we can not narrow the type (to `int & ~Literal[54165]`), because `x` might also + # have the value `54165`, but a different object identity. + reveal_type(x) # revealed: int +``` + +## Integer `Literal`s are single-valued types + +There is a slightly weaker property that integer literals have. They are single-valued types, which +means that all objects of the type have the same value, i.e. they compare equal to each other: + +```py +from ty_extensions import static_assert, is_single_valued +from typing import Literal + +static_assert(is_single_valued(Literal[0])) +static_assert(is_single_valued(Literal[1])) +static_assert(is_single_valued(Literal[54165])) +``` + +And this can be used for type-narrowing using not-equal comparisons: + +```py +def f(x: int): + if x == 54165: + # The reason that no narrowing occurs here is that there might be subclasses of `int` + # that override `__eq__`. This is not specific to integer literals though, and generally + # applies to `==` comparisons. + reveal_type(x) # revealed: int + + if x != 54165: + reveal_type(x) # revealed: int & ~Literal[54165] +``` + +## Subtyping relationships + +### Subtypes of `int` + +All integer literals are subtypes of `int`: + +```py +from ty_extensions import static_assert, is_subtype_of +from typing import Literal + +static_assert(is_subtype_of(Literal[0], int)) +static_assert(is_subtype_of(Literal[1], int)) +static_assert(is_subtype_of(Literal[54165], int)) +``` + +It is tempting to think that `int` is equivalent to the union of all integer literals, +`… | Literal[-1] | Literal[0] | Literal[1] | …`, but this is not the case. `True` and `False` are +also inhabitants of the `int` type, but they are not inhabitants of any integer literal type: + +```py +static_assert(is_subtype_of(Literal[True], int)) +static_assert(is_subtype_of(Literal[False], int)) + +static_assert(not is_subtype_of(Literal[True], Literal[1])) +static_assert(not is_subtype_of(Literal[False], Literal[0])) +``` + +Also, `int` can be subclassed, and instances of that subclass are also subtypes of `int`: + +```py +class CustomInt(int): + pass + +static_assert(is_subtype_of(CustomInt, int)) +``` + +### No subtypes of `float` and `complex` + +```toml +[environment] +python-version = "3.12" +``` + +Integer literals are _not_ subtypes of `float`, but the typing spec describes a special case for +[`float` and `complex`] which accepts integers (and therefore also integer literals) in places where +a `float` or `complex` is expected. We use the types `JustFloat` and `JustComplex` below, because ty +recognizes an annotation of `float` as `int | float` to support that typing system special case. + +```py +from ty_extensions import static_assert, is_subtype_of, JustFloat, JustComplex +from typing import Literal + +# Not subtypes of `float` and `complex` +static_assert(not is_subtype_of(Literal[0], JustFloat) and not is_subtype_of(Literal[0], JustComplex)) +static_assert(not is_subtype_of(Literal[1], JustFloat) and not is_subtype_of(Literal[1], JustComplex)) +static_assert(not is_subtype_of(Literal[54165], JustFloat) and not is_subtype_of(Literal[54165], JustComplex)) +``` + +The typing system special case can be seen in the following example: + +```py +a: JustFloat = 1 # error: [invalid-assignment] +b: JustComplex = 1 # error: [invalid-assignment] + +x: float = 1 +y: complex = 1 +``` + +### Subtypes of integer `Literal`s? + +The only subtypes of an integer literal type _that can be named_ are the type itself and `Never`: + +```py +from ty_extensions import static_assert, is_subtype_of +from typing_extensions import Never, Literal + +static_assert(is_subtype_of(Literal[54165], Literal[54165])) +static_assert(is_subtype_of(Never, Literal[54165])) +``` + +## Disjointness of integer `Literal`s + +Two integer literal types `Literal[a]` and `Literal[b]` are disjoint if `a != b`: + +```py +from ty_extensions import static_assert, is_disjoint_from +from typing import Literal + +static_assert(is_disjoint_from(Literal[0], Literal[1])) +static_assert(is_disjoint_from(Literal[0], Literal[54165])) + +static_assert(not is_disjoint_from(Literal[0], Literal[0])) +static_assert(not is_disjoint_from(Literal[54165], Literal[54165])) +``` + +## Integer literal math + +```toml +[environment] +python-version = "3.12" +``` + +We support a whole range of arithmetic operations on integer literal types. For example, we can +statically verify that (3, 4, 5) is a Pythagorean triple: + +```py +from ty_extensions import static_assert + +static_assert(3**2 + 4**2 == 5**2) +``` + +Using unions of integer literals, we can even use this to solve equations over a finite domain +(determine whether there is a solution or not): + +```py +from typing import Literal, assert_type + +type Nat = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +def pythagorean_triples(a: Nat, b: Nat, c: Nat): + # Answer is `bool`, because solutions do exist (3² + 4² = 5²) + assert_type(a**2 + b**2 == c**2, bool) + +def fermats_last_theorem(a: Nat, b: Nat, c: Nat): + # Answer is `Literal[False]`, because no solutions exist + assert_type(a**3 + b**3 == c**3, Literal[False]) +``` + +## Truthiness + +Integer literals are always-truthy, except for `0`, which is always-falsy: + +```py +from ty_extensions import static_assert + +static_assert(-54165) +static_assert(-1) +static_assert(not 0) +static_assert(1) +static_assert(54165) +``` + +This can be used for type-narrowing: + +```py +from typing_extensions import Literal, assert_type + +def f(x: Literal[0, 1, 54365]): + if x: + assert_type(x, Literal[1, 54365]) + else: + assert_type(x, Literal[0]) +``` + +[`float` and `complex`]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md new file mode 100644 index 00000000000000..98bff577a289c1 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md @@ -0,0 +1,185 @@ +# `Never` + +`Never` represents the empty set of values. + +## `Never` is a subtype of every type + +The `Never` type is the bottom type of Python's type system. It is a subtype of every type, but no +type is a subtype of `Never`, except for `Never` itself. + +```py +from ty_extensions import static_assert, is_subtype_of +from typing_extensions import Never + +class C: ... + +static_assert(is_subtype_of(Never, int)) +static_assert(is_subtype_of(Never, object)) +static_assert(is_subtype_of(Never, C)) +static_assert(is_subtype_of(Never, Never)) + +static_assert(not is_subtype_of(int, Never)) +``` + +## `Never` is assignable to every type + +`Never` is assignable to every type. This fact is useful when calling error-handling functions in a +context that requires a value of a specific type. For example, changing the `Never` return type to +`None` below would cause a type error: + +```py +from ty_extensions import static_assert, is_assignable_to +from typing_extensions import Never, Any + +static_assert(is_assignable_to(Never, int)) +static_assert(is_assignable_to(Never, object)) +static_assert(is_assignable_to(Never, Any)) +static_assert(is_assignable_to(Never, Never)) + +def raise_error() -> Never: + raise Exception("...") + +def f(divisor: int) -> None: + x: float = (1 / divisor) if divisor != 0 else raise_error() +``` + +## `Never` in annotations + +`Never` can be used in functions to indicate that the function never returns. For example, if a +function always raises an exception, if it calls `sys.exit()`, if it enters an infinite loop, or if +it calls itself recursively. All of these functions "Never" return control back to the caller: + +```py +from typing_extensions import Never + +def raises_unconditionally() -> Never: + raise Exception("This function always raises an exception") + +def exits_unconditionally() -> Never: + import sys + + return sys.exit(1) + +def loops_forever() -> Never: + while True: + pass + +def recursive_never() -> Never: + return recursive_never() +``` + +Similarly, if `Never` is used in parameter positions, it indicates that the function can "Never" be +called, because it can never be passed a value of type `Never` (there are none): + +```py +def can_not_be_called(n: Never) -> int: + return 0 +``` + +## `Never` is disjoint from every other type + +Two types `A` and `B` are disjoint if their intersection is empty. Since `Never` has no inhabitants, +it is disjoint from every other type: + +```py +from ty_extensions import static_assert, is_disjoint_from +from typing_extensions import Never + +class C: ... + +static_assert(is_disjoint_from(Never, int)) +static_assert(is_disjoint_from(Never, object)) +static_assert(is_disjoint_from(Never, C)) +static_assert(is_disjoint_from(Never, Never)) +``` + +## Unions with `Never` + +`Never` can always be removed from unions: + +```py +from ty_extensions import static_assert, is_equivalent_to +from typing_extensions import Never + +class P: ... +class Q: ... + +static_assert(is_equivalent_to(P | Never | Q | None, P | Q | None)) +``` + +## Intersections with `Never` + +Intersecting with `Never` results in `Never`: + +```py +from ty_extensions import static_assert, is_equivalent_to, Intersection +from typing_extensions import Never + +class P: ... +class Q: ... + +static_assert(is_equivalent_to(Intersection[P, Never, Q], Never)) +``` + +## `Never` is the complement of `object` + +`object` describes the set of all possible values, while `Never` describes the empty set. The two +types are complements of each other: + +```py +from ty_extensions import static_assert, is_equivalent_to, Not +from typing_extensions import Never + +static_assert(is_equivalent_to(Not[object], Never)) +static_assert(is_equivalent_to(Not[Never], object)) +``` + +This duality is also reflected in other facts: + +- `Never` is a subtype of every type, while `object` is a supertype of every type. +- `Never` is assignable to every type, while `object` is assignable from every type. +- `Never` is disjoint from every type, while `object` overlaps with every type. +- Building a union with `Never` is a no-op, intersecting with `object` is a no-op. +- Interecting with `Never` results in `Never`, building a union with `object` results in `object`. + +## Lists of `Never` + +`list[Never]` is a reasonable type that is *not* equivalent to `Never`. The empty list inhabits this +type: + +```py +from typing_extensions import Never + +x: list[Never] = [] +``` + +## Tuples involving `Never` + +A type like `tuple[int, Never]` has no inhabitants, and so it is equivalent to `Never`: + +```py +from ty_extensions import static_assert, is_equivalent_to +from typing_extensions import Never + +static_assert(is_equivalent_to(tuple[int, Never], Never)) +``` + +Note that this is not the case for the homogenous tuple type `tuple[Never, ...]` though, because +that type is inhabited by the empty tuple: + +```py +static_assert(not is_equivalent_to(tuple[Never, ...], Never)) + +t: tuple[Never, ...] = () +``` + +## `NoReturn` is the same as `Never` + +The `NoReturn` type is a different name for `Never`: + +```py +from ty_extensions import static_assert, is_equivalent_to +from typing_extensions import NoReturn, Never + +static_assert(is_equivalent_to(NoReturn, Never)) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/none.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/none.md new file mode 100644 index 00000000000000..08fcd7905b1172 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/none.md @@ -0,0 +1,80 @@ +# `None` + +## `None` as a singleton type + +The type `None` (or `NoneType`, see below) is a singleton type that has only one inhabitant: the +object `None`. + +```py +from ty_extensions import static_assert, is_singleton, is_equivalent_to + +n: None = None + +static_assert(is_singleton(None)) +``` + +Just like for other singleton types, the only subtypes of `None` are `None` itself and `Never`: + +```py +from ty_extensions import static_assert, is_subtype_of +from typing_extensions import Never + +static_assert(is_subtype_of(None, None)) +static_assert(is_subtype_of(Never, None)) +``` + +## Relationship to `Optional[T]` + +The type `Optional[T]` is an alias for `T | None` (or `Union[T, None]`): + +```py +from ty_extensions import static_assert, is_equivalent_to +from typing import Optional, Union + +class T: ... + +static_assert(is_equivalent_to(Optional[T], T | None)) +static_assert(is_equivalent_to(Optional[T], Union[T, None])) +``` + +## Type narrowing using `is` + +Just like for other singleton types, we support type narrowing using `is` or `is not` checks: + +```py +from typing_extensions import assert_type + +class T: ... + +def f(x: T | None): + if x is None: + assert_type(x, None) + else: + assert_type(x, T) + + assert_type(x, T | None) + + if x is not None: + assert_type(x, T) + else: + assert_type(x, None) +``` + +## `NoneType` + +`None` is special in that the name of the instance at runtime can be used as a type as well: The +object `None` is an instance of type `None`. When a distinction between the two is needed, the +spelling `NoneType` can be used, which is available since Python 3.10. `NoneType` is equivalent to +`None`: + +```toml +[environment] +python-version = "3.10" +``` + +```py +from ty_extensions import static_assert, is_equivalent_to +from types import NoneType + +static_assert(is_equivalent_to(NoneType, None)) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/not_t.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/not_t.md new file mode 100644 index 00000000000000..962e30f5071291 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/not_t.md @@ -0,0 +1,120 @@ +# `Not[T]` + +The type `Not[T]` is the complement of the type `T`. It describes the set of all values that are +*not* in `T`. + +## `Not[T]` is disjoint from `T` + +`Not[T]` is disjoint from `T`: + +```py +from ty_extensions import Not, static_assert, is_disjoint_from + +class T: ... +class S(T): ... + +static_assert(is_disjoint_from(Not[T], T)) +static_assert(is_disjoint_from(Not[T], S)) +``` + +## The union of `T` and `Not[T]` is equivalent to `object` + +Together, `T` and `Not[T]` describe the set of all values. So the union of both types is equivalent +to `object`: + +```py +from ty_extensions import Not, static_assert, is_equivalent_to + +class T: ... + +static_assert(is_equivalent_to(T | Not[T], object)) +``` + +## `Not[T]` reverses subtyping relationships + +If `S <: T`, then `Not[T] <: Not[S]`:, similar to how negation in logic reverses the order of `<=`: + +```py +from ty_extensions import Not, static_assert, is_subtype_of + +class T: ... +class S(T): ... + +static_assert(is_subtype_of(S, T)) +static_assert(is_subtype_of(Not[T], Not[S])) +``` + +## `Not[T]` reverses assignability relationships + +Assignability relationships are similarly reversed: + +```py +from ty_extensions import Not, Intersection, static_assert, is_assignable_to +from typing import Any + +class T: ... +class S(T): ... + +static_assert(is_assignable_to(S, T)) +static_assert(is_assignable_to(Not[T], Not[S])) + +static_assert(is_assignable_to(Intersection[Any, S], Intersection[Any, T])) + +static_assert(is_assignable_to(Not[Intersection[Any, S]], Not[Intersection[Any, T]])) +``` + +## Subtyping and disjointness + +If two types `P` and `Q` are disjoint, then `P` must be a subtype of `Not[Q]`, and vice versa: + +```py +from ty_extensions import Not, static_assert, is_subtype_of, is_disjoint_from +from typing import final + +@final +class P: ... + +@final +class Q: ... + +static_assert(is_disjoint_from(P, Q)) + +static_assert(is_subtype_of(P, Not[Q])) +static_assert(is_subtype_of(Q, Not[P])) +``` + +## De-Morgan's laws + +Given two unrelated types `P` and `Q`, we can demonstrate De-Morgan's laws in the context of +set-theoretic types: + +```py +from ty_extensions import Not, static_assert, is_equivalent_to, Intersection + +class P: ... +class Q: ... +``` + +The negation of a union is the intersection of the negations: + +```py +static_assert(is_equivalent_to(Not[P | Q], Intersection[Not[P], Not[Q]])) +``` + +Conversely, the negation of an intersection is the union of the negations: + +```py +static_assert(is_equivalent_to(Not[Intersection[P, Q]], Not[P] | Not[Q])) +``` + +## Negation of gradual types + +`Any` represents an unknown set of values. So `Not[Any]` also represents an unknown set of values. +The two gradual types are equivalent: + +```py +from ty_extensions import static_assert, is_gradual_equivalent_to, Not +from typing import Any + +static_assert(is_gradual_equivalent_to(Not[Any], Any)) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/object.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/object.md new file mode 100644 index 00000000000000..f8c9e4fec2b78e --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/object.md @@ -0,0 +1,78 @@ +# `object` + +The `object` type represents the set of all Python objects. + +## `object` is a supertype of all types + +It is the top type in Python's type system, i.e., it is a supertype of all other types: + +```py +from ty_extensions import static_assert, is_subtype_of + +static_assert(is_subtype_of(int, object)) +static_assert(is_subtype_of(str, object)) +static_assert(is_subtype_of(type, object)) +static_assert(is_subtype_of(object, object)) +``` + +## Every type is assignable to `object` + +Everything can be assigned to the type `object`. This fact can be used to create heterogeneous +collections of objects (but also erases more specific type information): + +```py +from ty_extensions import static_assert, is_assignable_to +from typing_extensions import Any, Never + +static_assert(is_assignable_to(int, object)) +static_assert(is_assignable_to(str | bytes, object)) +static_assert(is_assignable_to(type, object)) +static_assert(is_assignable_to(object, object)) +static_assert(is_assignable_to(Never, object)) +static_assert(is_assignable_to(Any, object)) + +x: list[object] = [1, "a", ()] +``` + +## `object` overlaps with all types + +There is no type that is disjoint from `object` except for `Never`: + +```py +from ty_extensions import static_assert, is_disjoint_from +from typing_extensions import Any, Never + +static_assert(not is_disjoint_from(int, object)) +static_assert(not is_disjoint_from(str, object)) +static_assert(not is_disjoint_from(type, object)) +static_assert(not is_disjoint_from(object, object)) +static_assert(not is_disjoint_from(Any, object)) +static_assert(is_disjoint_from(Never, object)) +``` + +## Unions with `object` + +Unions with `object` are equivalent to `object`: + +```py +from ty_extensions import static_assert, is_equivalent_to + +static_assert(is_equivalent_to(int | object | None, object)) +``` + +## Intersections with `object` + +Intersecting with `object` is equivalent to the original type: + +```py +from ty_extensions import static_assert, is_equivalent_to, Intersection + +class P: ... +class Q: ... + +static_assert(is_equivalent_to(Intersection[P, object, Q], Intersection[P, Q])) +``` + +## `object` is the complement of `Never` + +See corresponding section in the fact sheet for [`Never`](never.md). diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md new file mode 100644 index 00000000000000..350ee0123e9068 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md @@ -0,0 +1,166 @@ +# Tuples + +## Tuples as product types + +Tuples can be used to construct product types. Inhabitants of the type `tuple[P, Q]` are ordered +pairs `(p, q)` where `p` is an inhabitant of `P` and `q` is an inhabitant of `Q`, analogous to the +Cartesian product of sets. + +```py +from typing_extensions import assert_type + +class P: ... +class Q: ... + +def _(p: P, q: Q): + assert_type((p, q), tuple[P, Q]) +``` + +## Subtyping relationships + +The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1` +and `S2` is a subtype of `T2`, and similar for other lengths of tuples: + +```py +from ty_extensions import static_assert, is_subtype_of + +class T1: ... +class S1(T1): ... +class T2: ... +class S2(T2): ... + +static_assert(is_subtype_of(tuple[S1], tuple[T1])) +static_assert(not is_subtype_of(tuple[T1], tuple[S1])) + +static_assert(is_subtype_of(tuple[S1, S2], tuple[T1, T2])) +static_assert(not is_subtype_of(tuple[T1, S2], tuple[S1, T2])) +static_assert(not is_subtype_of(tuple[S1, T2], tuple[T1, S2])) +``` + +Different-length tuples are not related via subtyping: + +```py +static_assert(not is_subtype_of(tuple[S1], tuple[T1, T2])) +``` + +## The empty tuple + +The type of the empty tuple `()` is spelled `tuple[()]`. It is [not a singleton type], because +different instances of `()` are not guaranteed to be the same object (even if this is the case in +CPython at the time of writing). + +The empty tuple can also be subclassed (further clarifying that it is not a singleton type): + +```py +from ty_extensions import static_assert, is_singleton, is_subtype_of, is_equivalent_to, is_assignable_to + +static_assert(not is_singleton(tuple[()])) + +class AnotherEmptyTuple(tuple[()]): ... + +static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()])) + +# TODO: These should not be errors +# error: [static-assert-error] +static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()])) +# error: [static-assert-error] +static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()])) +``` + +## Non-empty tuples + +For the same reason as above (two instances of a tuple with the same elements might not be the same +object), non-empty tuples are also not singleton types — even if all their elements are singletons: + +```py +from ty_extensions import static_assert, is_singleton + +static_assert(is_singleton(None)) + +static_assert(not is_singleton(tuple[None])) +``` + +## Disjointness + +A tuple `tuple[P1, P2]` is disjoint from a tuple `tuple[Q1, Q2]` if either `P1` is disjoint from +`Q1` or if `P2` is disjoint from `Q2`: + +```py +from ty_extensions import static_assert, is_disjoint_from +from typing import final + +@final +class F1: ... + +@final +class F2: ... + +class N1: ... +class N2: ... + +static_assert(is_disjoint_from(F1, F2)) +static_assert(not is_disjoint_from(N1, N2)) + +static_assert(is_disjoint_from(tuple[F1, F2], tuple[F2, F1])) +static_assert(is_disjoint_from(tuple[F1, N1], tuple[F2, N2])) +static_assert(is_disjoint_from(tuple[N1, F1], tuple[N2, F2])) +static_assert(not is_disjoint_from(tuple[N1, N2], tuple[N2, N1])) +``` + +We currently model tuple types to *not* be disjoint from arbitrary instance types, because we allow +for the possibility of `tuple` to be subclassed + +```py +class C: ... + +static_assert(not is_disjoint_from(tuple[int, str], C)) + +class CommonSubtype(tuple[int, str], C): ... +``` + +Note: This is inconsistent with the fact that we model heterogeneous tuples to be disjoint from +other heterogeneous tuples above: + +```py +class I1(tuple[F1, F2]): ... +class I2(tuple[F2, F1]): ... + +# TODO +# This is a subtype of both `tuple[F1, F2]` and `tuple[F2, F1]`, so those two heterogeneous tuples +# should not be disjoint from each other (see conflicting test above). +class CommonSubtypeOfTuples(I1, I2): ... +``` + +## Truthiness + +The truthiness of the empty tuple is `False`: + +```py +from typing_extensions import assert_type, Literal + +assert_type(bool(()), Literal[False]) +``` + +The truthiness of non-empty tuples is always `True`, even if all elements are falsy: + +```py +from typing_extensions import assert_type, Literal + +assert_type(bool((False,)), Literal[True]) +assert_type(bool((False, False)), Literal[True]) +``` + +Both of these results are conflicting with the fact that tuples can be subclassed, and that we +currently allow subclasses of `tuple` to overwrite `__bool__` (or `__len__`): + +```py +class NotAlwaysTruthyTuple(tuple[int]): + def __bool__(self) -> bool: + return False + +# TODO: This assignment should be allowed +# error: [invalid-assignment] +t: tuple[int] = NotAlwaysTruthyTuple((1,)) +``` + +[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 8f0f360f096078..b14e12f30688a1 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -21,10 +21,7 @@ See the [typing documentation] for more information. as `int | float` and `int | float | complex`, respectively. ```py -from ty_extensions import is_subtype_of, static_assert, TypeOf - -type JustFloat = TypeOf[1.0] -type JustComplex = TypeOf[1j] +from ty_extensions import is_subtype_of, static_assert, JustFloat, JustComplex static_assert(is_subtype_of(bool, bool)) static_assert(is_subtype_of(bool, int)) @@ -88,9 +85,7 @@ static_assert(is_subtype_of(C, object)) ```py from typing_extensions import Literal, LiteralString -from ty_extensions import is_subtype_of, static_assert, TypeOf - -type JustFloat = TypeOf[1.0] +from ty_extensions import is_subtype_of, static_assert, TypeOf, JustFloat # Boolean literals static_assert(is_subtype_of(Literal[True], bool)) diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 0ed447e11237b8..127f97c3c572c0 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -14,6 +14,15 @@ Intersection: _SpecialForm TypeOf: _SpecialForm CallableTypeOf: _SpecialForm +# ty treats annotations of `float` to mean `float | int`, and annotations of `complex` +# to mean `complex | float | int`. This is to support a typing-system special case [1]. +# We therefore provide `JustFloat` and `JustComplex` to represent the "bare" `float` and +# `complex` types, respectively. +# +# [1]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex +type JustFloat = TypeOf[1.0] +type JustComplex = TypeOf[1.0j] + # Predicates on types # # Ideally, these would be annotated using `TypeForm`, but that has not been From 6392dccd24a22b7a5cb7b1f88a444d723a4c3b3d Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 23 May 2025 11:58:16 +0200 Subject: [PATCH 213/487] [ty] Add warning that docs are autogenerated (#18270) ## Summary This is a practice I followed on previous projects. Should hopefully further help developers who want to update the documentation. The big downside is that it's annoying to see this *as a user of the documentation* if you don't open the Markdown file in the browser. But I'd argue that those files don't really follow the original Markdown spirit anyway with all the inline HTML. --- crates/ruff_dev/src/generate_ty_cli_reference.rs | 1 + crates/ruff_dev/src/generate_ty_options.rs | 4 ++++ crates/ruff_dev/src/generate_ty_rules.rs | 4 ++++ crates/ty/docs/cli.md | 2 ++ crates/ty/docs/configuration.md | 2 ++ crates/ty/docs/rules.md | 2 ++ 6 files changed, 15 insertions(+) diff --git a/crates/ruff_dev/src/generate_ty_cli_reference.rs b/crates/ruff_dev/src/generate_ty_cli_reference.rs index c240e76537e372..58914d74147a61 100644 --- a/crates/ruff_dev/src/generate_ty_cli_reference.rs +++ b/crates/ruff_dev/src/generate_ty_cli_reference.rs @@ -80,6 +80,7 @@ fn generate() -> String { let mut parents = Vec::new(); + output.push_str("\n\n"); output.push_str("# CLI Reference\n\n"); generate_command(&mut output, &ty, &mut parents); diff --git a/crates/ruff_dev/src/generate_ty_options.rs b/crates/ruff_dev/src/generate_ty_options.rs index 1542e3e45d3651..b764278b21cf74 100644 --- a/crates/ruff_dev/src/generate_ty_options.rs +++ b/crates/ruff_dev/src/generate_ty_options.rs @@ -25,6 +25,10 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<()> { let file_name = "crates/ty/docs/configuration.md"; let markdown_path = PathBuf::from(ROOT_DIR).join(file_name); + output.push_str( + "\n\n", + ); + generate_set( &mut output, Set::Toplevel(Options::metadata()), diff --git a/crates/ruff_dev/src/generate_ty_rules.rs b/crates/ruff_dev/src/generate_ty_rules.rs index d28a2feafbea5f..cf1f3f8aaa1754 100644 --- a/crates/ruff_dev/src/generate_ty_rules.rs +++ b/crates/ruff_dev/src/generate_ty_rules.rs @@ -56,6 +56,10 @@ fn generate_markdown() -> String { let mut output = String::new(); + let _ = writeln!( + &mut output, + "\n" + ); let _ = writeln!(&mut output, "# Rules\n"); let mut lints: Vec<_> = registry.lints().iter().collect(); diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index d5cfaa311ba4ae..40ef8cf65164b3 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -1,3 +1,5 @@ + + # CLI Reference ## ty diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 1d18d9d093bb52..3efd6e541e009d 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -1,3 +1,5 @@ + + # Configuration #### `respect-ignore-files` diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 6eac8f74083b4c..40a2e2fa13da97 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -1,3 +1,5 @@ + + # Rules ## `byte-string-type-annotation` From a1399656c9109293781405c8e33f4bf27f1b8694 Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 23 May 2025 17:55:17 +0700 Subject: [PATCH 214/487] [ty] Fix binary intersection comparison inference logic (#18266) ## Summary Resolves https://github.com/astral-sh/ty/issues/485. `infer_binary_intersection_type_comparison()` now checks for all positive members before concluding that an operation is unsupported for a given intersection type. ## Test Plan Markdown tests. --------- Co-authored-by: David Peter --- .../mdtest/comparison/intersections.md | 43 +++++++++-- crates/ty_python_semantic/src/types/infer.rs | 75 +++++++++++++++---- 2 files changed, 94 insertions(+), 24 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md b/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md index f1750dde1f4798..35d7203c989d13 100644 --- a/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md +++ b/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md @@ -109,23 +109,50 @@ def _(o: object): ### Unsupported operators for positive contributions -Raise an error if any of the positive contributions to the intersection type are unsupported for the -given operator: +Raise an error if the given operator is unsupported for all positive contributions to the +intersection type: + +```py +class NonContainer1: ... +class NonContainer2: ... + +def _(x: object): + if isinstance(x, NonContainer1): + if isinstance(x, NonContainer2): + reveal_type(x) # revealed: NonContainer1 & NonContainer2 + + # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer1`" + reveal_type(2 in x) # revealed: bool +``` + +Do not raise an error if at least one of the positive contributions to the intersection type support +the operator: ```py class Container: def __contains__(self, x) -> bool: return False -class NonContainer: ... +def _(x: object): + if isinstance(x, NonContainer1): + if isinstance(x, Container): + if isinstance(x, NonContainer2): + reveal_type(x) # revealed: NonContainer1 & Container & NonContainer2 + reveal_type(2 in x) # revealed: bool +``` + +Do also raise an error if the intersection has no positive contributions at all, unless the operator +is supported on `object`: +```py def _(x: object): - if isinstance(x, Container): - if isinstance(x, NonContainer): - reveal_type(x) # revealed: Container & NonContainer + if not isinstance(x, NonContainer1): + reveal_type(x) # revealed: ~NonContainer1 - # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`" - reveal_type(2 in x) # revealed: bool + # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `object`, in comparing `Literal[2]` with `~NonContainer1`" + reveal_type(2 in x) # revealed: bool + + reveal_type(2 is x) # revealed: bool ``` ### Unsupported operators for negative contributions diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 649f4992ac4948..9f3f48974525d8 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -6537,20 +6537,27 @@ impl<'db> TypeInferenceBuilder<'db> { intersection_on: IntersectionOn, range: TextRange, ) -> Result, CompareUnsupportedError<'db>> { + enum State<'db> { + // We have not seen any positive elements (yet) + NoPositiveElements, + // The operator was unsupported on all elements that we have seen so far. + // Contains the first error we encountered. + UnsupportedOnAllElements(CompareUnsupportedError<'db>), + // The operator was supported on at least one positive element. + Supported, + } + // If a comparison yields a definitive true/false answer on a (positive) part // of an intersection type, it will also yield a definitive answer on the full // intersection type, which is even more specific. for pos in intersection.positive(self.db()) { let result = match intersection_on { - IntersectionOn::Left => { - self.infer_binary_type_comparison(*pos, op, other, range)? - } - IntersectionOn::Right => { - self.infer_binary_type_comparison(other, op, *pos, range)? - } + IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other, range), + IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos, range), }; - if let Type::BooleanLiteral(b) = result { - return Ok(Type::BooleanLiteral(b)); + + if let Ok(Type::BooleanLiteral(_)) = result { + return result; } } @@ -6619,19 +6626,55 @@ impl<'db> TypeInferenceBuilder<'db> { builder = builder.add_positive(KnownClass::Bool.to_instance(self.db())); + let mut state = State::NoPositiveElements; + for pos in intersection.positive(self.db()) { let result = match intersection_on { - IntersectionOn::Left => { - self.infer_binary_type_comparison(*pos, op, other, range)? - } - IntersectionOn::Right => { - self.infer_binary_type_comparison(other, op, *pos, range)? - } + IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other, range), + IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos, range), }; - builder = builder.add_positive(result); + + match result { + Ok(ty) => { + state = State::Supported; + builder = builder.add_positive(ty); + } + Err(error) => { + match state { + State::NoPositiveElements => { + // This is the first positive element, but the operation is not supported. + // Store the error and continue. + state = State::UnsupportedOnAllElements(error); + } + State::UnsupportedOnAllElements(_) => { + // We already have an error stored, and continue to see elements on which + // the operator is not supported. Continue with the same state (only keep + // the first error). + } + State::Supported => { + // We previously saw a positive element that supported the operator, + // so the overall operation is still supported. + } + } + } + } } - Ok(builder.build()) + match state { + State::Supported => Ok(builder.build()), + State::NoPositiveElements => { + // We didn't see any positive elements, check if the operation is supported on `object`: + match intersection_on { + IntersectionOn::Left => { + self.infer_binary_type_comparison(Type::object(self.db()), op, other, range) + } + IntersectionOn::Right => { + self.infer_binary_type_comparison(other, op, Type::object(self.db()), range) + } + } + } + State::UnsupportedOnAllElements(error) => Err(error), + } } /// Infers the type of a binary comparison (e.g. 'left == right'). See From 53d19f8368154c201e5098678d82a4c49ee14e26 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Fri, 23 May 2025 20:00:42 +0200 Subject: [PATCH 215/487] [ty] Resolving Python path using `CONDA_PREFIX` variable to support Conda and Pixi (#18267) --- crates/ty/tests/cli.rs | 77 +++++++++++++++++++ crates/ty_project/src/metadata/options.rs | 5 ++ crates/ty_python_semantic/src/program.rs | 4 + .../ty_python_semantic/src/site_packages.rs | 4 +- 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index d42ab07b45b4f5..6daa3d27d8fddf 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -1555,6 +1555,83 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> { Ok(()) } +/// The `site-packages` directory is used by ty for external import. +/// Ty does the following checks to discover the `site-packages` directory in the order: +/// 1) If `VIRTUAL_ENV` environment variable is set +/// 2) If `CONDA_PREFIX` environment variable is set +/// 3) If a `.venv` directory exists at the project root +/// +/// This test is aiming at validating the logic around `CONDA_PREFIX`. +/// +/// A conda-like environment file structure is used +/// We test by first not setting the `CONDA_PREFIX` and expect a fail. +/// Then we test by setting `CONDA_PREFIX` to `conda-env` and expect a pass. +/// +/// ├── project +/// │ └── test.py +/// └── conda-env +/// └── lib +/// └── python3.13 +/// └── site-packages +/// └── package1 +/// └── __init__.py +/// +/// test.py imports package1 +/// And the command is run in the `project` directory. +#[test] +fn check_conda_prefix_var_to_resolve_path() -> anyhow::Result<()> { + let conda_package1_path = if cfg!(windows) { + "conda-env/Lib/site-packages/package1/__init__.py" + } else { + "conda-env/lib/python3.13/site-packages/package1/__init__.py" + }; + + let case = TestCase::with_files([ + ( + "project/test.py", + r#" + import package1 + "#, + ), + ( + conda_package1_path, + r#" + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `package1` + --> test.py:2:8 + | + 2 | import package1 + | ^^^^^^^^ + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // do command : CONDA_PREFIX=/conda_env + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")).env("CONDA_PREFIX", case.root().join("conda-env")), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) +} + struct TestCase { _temp_dir: TempDir, _settings_scope: SettingsBindDropGuard, diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 00bab6dd9575dc..82cf45ab8b6865 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -186,6 +186,11 @@ impl Options { .ok() .map(PythonPath::from_virtual_env_var) }) + .or_else(|| { + std::env::var("CONDA_PREFIX") + .ok() + .map(PythonPath::from_conda_prefix_var) + }) .unwrap_or_else(|| PythonPath::Discover(project_root.to_path_buf())), } } diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 74e662e355f8e6..ef059abff96388 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -202,6 +202,10 @@ impl PythonPath { Self::SysPrefix(path.into(), SysPrefixPathOrigin::VirtualEnvVar) } + pub fn from_conda_prefix_var(path: impl Into) -> Self { + Self::Resolve(path.into(), SysPrefixPathOrigin::CondaPrefixVar) + } + pub fn from_cli_flag(path: SystemPathBuf) -> Self { Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag) } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index e893c8817e968a..6204fff816af78 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -580,6 +580,7 @@ impl fmt::Display for SysPrefixPath { pub enum SysPrefixPathOrigin { PythonCliFlag, VirtualEnvVar, + CondaPrefixVar, Derived, LocalVenv, } @@ -590,7 +591,7 @@ impl SysPrefixPathOrigin { pub(crate) fn must_be_virtual_env(self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, - Self::PythonCliFlag | Self::Derived => false, + Self::PythonCliFlag | Self::Derived | Self::CondaPrefixVar => false, } } } @@ -600,6 +601,7 @@ impl Display for SysPrefixPathOrigin { match self { Self::PythonCliFlag => f.write_str("`--python` argument"), Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"), + Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"), Self::Derived => f.write_str("derived `sys.prefix` path"), Self::LocalVenv => f.write_str("local virtual environment"), } From e293411679e627b4080eef5291a93532f7748777 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 23 May 2025 19:20:34 -0400 Subject: [PATCH 216/487] [ty] `get_protocol_members` returns a frozenset, not a tuple (#18284) --- .../resources/mdtest/protocols.md | 27 +++++++------------ .../ty_python_semantic/src/types/call/bind.rs | 18 ++++++------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index f14b838b6a5e0d..a2116c8f972c4e 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -375,8 +375,7 @@ class Foo(Protocol): def method_member(self) -> bytes: return b"foo" -# TODO: actually a frozenset (requires support for legacy generics) -reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["method_member"], Literal["x"], Literal["y"], Literal["z"]] +reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["method_member", "x", "y", "z"]] ``` Certain special attributes and methods are not considered protocol members at runtime, and should @@ -394,8 +393,7 @@ class Lumberjack(Protocol): def __init__(self, x: int) -> None: self.x = x -# TODO: actually a frozenset -reveal_type(get_protocol_members(Lumberjack)) # revealed: tuple[Literal["x"]] +reveal_type(get_protocol_members(Lumberjack)) # revealed: frozenset[Literal["x"]] ``` A sub-protocol inherits and extends the members of its superclass protocol(s): @@ -407,13 +405,11 @@ class Bar(Protocol): class Baz(Bar, Protocol): ham: memoryview -# TODO: actually a frozenset -reveal_type(get_protocol_members(Baz)) # revealed: tuple[Literal["ham"], Literal["spam"]] +reveal_type(get_protocol_members(Baz)) # revealed: frozenset[Literal["ham", "spam"]] class Baz2(Bar, Foo, Protocol): ... -# TODO: actually a frozenset -# revealed: tuple[Literal["method_member"], Literal["spam"], Literal["x"], Literal["y"], Literal["z"]] +# revealed: frozenset[Literal["method_member", "spam", "x", "y", "z"]] reveal_type(get_protocol_members(Baz2)) ``` @@ -441,8 +437,7 @@ class Foo(Protocol): e = 56 def f(self) -> None: ... -# TODO: actually a frozenset -reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["d"], Literal["e"], Literal["f"]] +reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]] ``` ## Invalid calls to `get_protocol_members()` @@ -673,8 +668,7 @@ class LotsOfBindings(Protocol): case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared) ... -# TODO: actually a frozenset -# revealed: tuple[Literal["Nested"], Literal["NestedProtocol"], Literal["a"], Literal["b"], Literal["c"], Literal["d"], Literal["e"], Literal["f"], Literal["g"], Literal["h"], Literal["i"], Literal["j"], Literal["k"], Literal["l"]] +# revealed: frozenset[Literal["Nested", "NestedProtocol", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]] reveal_type(get_protocol_members(LotsOfBindings)) ``` @@ -702,9 +696,7 @@ class Foo(Protocol): # Note: the list of members does not include `a`, `b` or `c`, # as none of these attributes is declared in the class body. -# -# TODO: actually a frozenset -reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["non_init_method"], Literal["x"], Literal["y"]] +reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["non_init_method", "x", "y"]] ``` If a member is declared in a superclass of a protocol class, it is fine for it to be assigned to in @@ -717,9 +709,8 @@ class Super(Protocol): class Sub(Super, Protocol): x = 42 # no error here, since it's declared in the superclass -# TODO: actually frozensets -reveal_type(get_protocol_members(Super)) # revealed: tuple[Literal["x"]] -reveal_type(get_protocol_members(Sub)) # revealed: tuple[Literal["x"]] +reveal_type(get_protocol_members(Super)) # revealed: frozenset[Literal["x"]] +reveal_type(get_protocol_members(Sub)) # revealed: frozenset[Literal["x"]] ``` If a protocol has 0 members, then all other types are assignable to it, and all fully static types diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index a94022f9c31b93..67a8721be0688a 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -667,15 +667,15 @@ impl<'db> Bindings<'db> { Some(KnownFunction::GetProtocolMembers) => { if let [Some(Type::ClassLiteral(class))] = overload.parameter_types() { if let Some(protocol_class) = class.into_protocol_class(db) { - // TODO: actually a frozenset at runtime (requires support for legacy generic classes) - overload.set_return_type(Type::Tuple(TupleType::new( - db, - protocol_class - .interface(db) - .members(db) - .map(|member| Type::string_literal(db, member.name())) - .collect::]>>(), - ))); + let member_names = protocol_class + .interface(db) + .members(db) + .map(|member| Type::string_literal(db, member.name())); + let specialization = UnionType::from_elements(db, member_names); + overload.set_return_type( + KnownClass::FrozenSet + .to_specialized_instance(db, [specialization]), + ); } } } From be76fadb055110894ec542b23675244206c302d6 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 25 May 2025 13:44:21 +0300 Subject: [PATCH 217/487] [pyupgrade] make fix unsafe if it deletes comments (UP010, unnecessary-future-import) (#18291) --- .../test/fixtures/pyupgrade/UP010.py | 1 + .../rules/unnecessary_future_import.rs | 18 ++++++++++++++---- ...er__rules__pyupgrade__tests__UP010.py.snap | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP010.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP010.py index eb8ece827e4c8a..ef7d9537c7d703 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP010.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP010.py @@ -12,3 +12,4 @@ if True: from __future__ import generator_stop from __future__ import invalid_module, generators + from __future__ import generators # comment diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs index 50ef3d89f5e84c..8a220f7b8cd69b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -123,9 +123,19 @@ pub(crate) fn unnecessary_future_import(checker: &Checker, stmt: &Stmt, names: & checker.stylist(), checker.indexer(), )?; - Ok(Fix::safe_edit(edit).isolate(Checker::isolation( - checker.semantic().current_statement_parent_id(), - ))) + + let range = edit.range(); + let applicability = if checker.comment_ranges().intersects(range) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + Ok( + Fix::applicable_edit(edit, applicability).isolate(Checker::isolation( + checker.semantic().current_statement_parent_id(), + )), + ) }); checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP010.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP010.py.snap index 89d50cf2f49f80..4696822fe94e7e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP010.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP010.py.snap @@ -156,6 +156,7 @@ UP010.py:13:5: UP010 [*] Unnecessary `__future__` import `generator_stop` for ta 13 | from __future__ import generator_stop | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010 14 | from __future__ import invalid_module, generators +15 | from __future__ import generators # comment | = help: Remove unnecessary `__future__` import @@ -165,6 +166,7 @@ UP010.py:13:5: UP010 [*] Unnecessary `__future__` import `generator_stop` for ta 12 12 | if True: 13 |- from __future__ import generator_stop 14 13 | from __future__ import invalid_module, generators +15 14 | from __future__ import generators # comment UP010.py:14:5: UP010 [*] Unnecessary `__future__` import `generators` for target Python version | @@ -172,6 +174,7 @@ UP010.py:14:5: UP010 [*] Unnecessary `__future__` import `generators` for target 13 | from __future__ import generator_stop 14 | from __future__ import invalid_module, generators | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010 +15 | from __future__ import generators # comment | = help: Remove unnecessary `__future__` import @@ -181,3 +184,19 @@ UP010.py:14:5: UP010 [*] Unnecessary `__future__` import `generators` for target 13 13 | from __future__ import generator_stop 14 |- from __future__ import invalid_module, generators 14 |+ from __future__ import invalid_module +15 15 | from __future__ import generators # comment + +UP010.py:15:5: UP010 [*] Unnecessary `__future__` import `generators` for target Python version + | +13 | from __future__ import generator_stop +14 | from __future__ import invalid_module, generators +15 | from __future__ import generators # comment + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010 + | + = help: Remove unnecessary `__future__` import + +ℹ Unsafe fix +12 12 | if True: +13 13 | from __future__ import generator_stop +14 14 | from __future__ import invalid_module, generators +15 |- from __future__ import generators # comment From 83a036960b8e7d31856a5f10d208b9eac894a187 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Sun, 25 May 2025 19:09:02 +0800 Subject: [PATCH 218/487] [ty] Add long help for `--config` argument (#18285) --- crates/ty/docs/cli.md | 5 ++++- crates/ty/src/args.rs | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index 40ef8cf65164b3..3df1548a31c1b3 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -43,7 +43,10 @@ ty check [OPTIONS] [PATH]...
  • auto: Display colors if the output goes to an interactive terminal
  • always: Always display colors
  • never: Never display colors
  • -
--config, -c config-option

A TOML <KEY> = <VALUE> pair

+
--config, -c config-option

A TOML <KEY> = <VALUE> pair (such as you might find in a ty.toml configuration file) +overriding a specific configuration option.

+

Overrides of individual settings using this option always take precedence +over all configuration files.

--error rule

Treat the given rule as having severity 'error'. Can be specified multiple times.

--error-on-warning

Use exit code 1 if there are any warning-level diagnostics

--exit-zero

Always use exit code 0, even when there are error-level diagnostics

diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index e5034cfdf94920..d4d49e45651f58 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -322,9 +322,11 @@ pub(crate) enum TerminalColor { /// Never display colors. Never, } + /// A TOML ` = ` pair /// (such as you might find in a `ty.toml` configuration file) /// overriding a specific configuration option. +/// /// Overrides of individual settings using this option always take precedence /// over all configuration files. #[derive(Debug, Clone)] @@ -359,7 +361,15 @@ impl clap::Args for ConfigsArg { .short('c') .long("config") .value_name("CONFIG_OPTION") - .help("A TOML ` = ` pair") + .help("A TOML ` = ` pair overriding a specific configuration option.") + .long_help( + " +A TOML ` = ` pair (such as you might find in a `ty.toml` configuration file) +overriding a specific configuration option. + +Overrides of individual settings using this option always take precedence +over all configuration files.", + ) .action(ArgAction::Append), ) } From 14c37554456e2ceb9cf1d681a92189b71ee313df Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 25 May 2025 13:16:19 -0400 Subject: [PATCH 219/487] Fix YTT201 for '!=' comparisons (#18293) ## Summary Closes #18292. --- .../src/rules/flake8_2020/rules/compare.rs | 25 +++++++++++++------ ..._flake8_2020__tests__YTT201_YTT201.py.snap | 4 +-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs index 00e310e9d84fcb..359be0dbcb7949 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs @@ -52,17 +52,20 @@ impl Violation for SysVersionCmpStr3 { /// ## What it does /// Checks for equality comparisons against the major version returned by -/// `sys.version_info` (e.g., `sys.version_info[0] == 3`). +/// `sys.version_info` (e.g., `sys.version_info[0] == 3` or `sys.version_info[0] != 3`). /// /// ## Why is this bad? /// Using `sys.version_info[0] == 3` to verify that the major version is /// Python 3 or greater will fail if the major version number is ever /// incremented (e.g., to Python 4). This is likely unintended, as code /// that uses this comparison is likely intended to be run on Python 2, -/// but would now run on Python 4 too. +/// but would now run on Python 4 too. Similarly, using `sys.version_info[0] != 3` +/// to check for Python 2 will also fail if the major version number is +/// incremented. /// /// Instead, use `>=` to check if the major version number is 3 or greater, -/// to future-proof the code. +/// or `<` to check if the major version number is less than 3, to future-proof +/// the code. /// /// ## Example /// ```python @@ -88,12 +91,18 @@ impl Violation for SysVersionCmpStr3 { /// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version) /// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info) #[derive(ViolationMetadata)] -pub(crate) struct SysVersionInfo0Eq3; +pub(crate) struct SysVersionInfo0Eq3 { + eq: bool, +} impl Violation for SysVersionInfo0Eq3 { #[derive_message_formats] fn message(&self) -> String { - "`sys.version_info[0] == 3` referenced (python4), use `>=`".to_string() + if self.eq { + "`sys.version_info[0] == 3` referenced (python4), use `>=`".to_string() + } else { + "`sys.version_info[0] != 3` referenced (python4), use `<`".to_string() + } } } @@ -235,7 +244,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators { if *i == 0 { if let ( - [CmpOp::Eq | CmpOp::NotEq], + [operator @ (CmpOp::Eq | CmpOp::NotEq)], [ Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(n), @@ -246,7 +255,9 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators { if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) { checker.report_diagnostic(Diagnostic::new( - SysVersionInfo0Eq3, + SysVersionInfo0Eq3 { + eq: matches!(*operator, CmpOp::Eq), + }, left.range(), )); } diff --git a/crates/ruff_linter/src/rules/flake8_2020/snapshots/ruff_linter__rules__flake8_2020__tests__YTT201_YTT201.py.snap b/crates/ruff_linter/src/rules/flake8_2020/snapshots/ruff_linter__rules__flake8_2020__tests__YTT201_YTT201.py.snap index c2ae9a26987b03..f5422cad3de0a9 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/snapshots/ruff_linter__rules__flake8_2020__tests__YTT201_YTT201.py.snap +++ b/crates/ruff_linter/src/rules/flake8_2020/snapshots/ruff_linter__rules__flake8_2020__tests__YTT201_YTT201.py.snap @@ -20,7 +20,7 @@ YTT201.py:8:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=` 10 | PY2 = version_info[0] != 3 | -YTT201.py:9:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=` +YTT201.py:9:7: YTT201 `sys.version_info[0] != 3` referenced (python4), use `<` | 7 | PY3 = sys.version_info[0] == 3 8 | PY3 = version_info[0] == 3 @@ -29,7 +29,7 @@ YTT201.py:9:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=` 10 | PY2 = version_info[0] != 3 | -YTT201.py:10:7: YTT201 `sys.version_info[0] == 3` referenced (python4), use `>=` +YTT201.py:10:7: YTT201 `sys.version_info[0] != 3` referenced (python4), use `<` | 8 | PY3 = version_info[0] == 3 9 | PY2 = sys.version_info[0] != 3 From d95b0298627ac6e1569e028e3a04258146d9a38c Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 25 May 2025 23:16:38 -0500 Subject: [PATCH 220/487] [ty] Move diagnostics API for the server (#18308) ## Summary This PR moves the diagnostics API for the language server out from the request handler module to the diagnostics API module. This is in preparation to add support for publishing diagnostics. --- .../ty_server/src/server/api/diagnostics.rs | 163 +++++++++++++++++- .../src/server/api/requests/diagnostic.rs | 157 +---------------- 2 files changed, 167 insertions(+), 153 deletions(-) diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index c1b3449acc8c90..367f65b96ee2a9 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -1,6 +1,18 @@ use lsp_server::ErrorCode; -use lsp_types::{PublishDiagnosticsParams, Url, notification::PublishDiagnostics}; +use lsp_types::notification::PublishDiagnostics; +use lsp_types::{ + Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, NumberOrString, + PublishDiagnosticsParams, Range, Url, +}; +use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; +use ruff_db::files::FileRange; +use ruff_db::source::{line_index, source_text}; +use ty_project::{Db, ProjectDatabase}; + +use crate::DocumentSnapshot; +use crate::PositionEncoding; +use crate::document::{FileRangeExt, ToRangeExt}; use crate::server::Result; use crate::server::client::Notifier; @@ -16,3 +28,152 @@ pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> { .with_failure_code(ErrorCode::InternalError)?; Ok(()) } + +pub(super) fn compute_diagnostics( + db: &ProjectDatabase, + snapshot: &DocumentSnapshot, +) -> Vec { + let Some(file) = snapshot.file(db) else { + tracing::info!( + "No file found for snapshot for `{}`", + snapshot.query().file_url() + ); + return vec![]; + }; + + let diagnostics = match db.check_file(file) { + Ok(diagnostics) => diagnostics, + Err(cancelled) => { + tracing::info!("Diagnostics computation {cancelled}"); + return vec![]; + } + }; + + diagnostics + .as_slice() + .iter() + .map(|message| to_lsp_diagnostic(db, message, snapshot.encoding())) + .collect() +} + +/// Converts the tool specific [`Diagnostic`][ruff_db::diagnostic::Diagnostic] to an LSP +/// [`Diagnostic`]. +fn to_lsp_diagnostic( + db: &dyn Db, + diagnostic: &ruff_db::diagnostic::Diagnostic, + encoding: PositionEncoding, +) -> Diagnostic { + let range = if let Some(span) = diagnostic.primary_span() { + let file = span.expect_ty_file(); + let index = line_index(db.upcast(), file); + let source = source_text(db.upcast(), file); + + span.range() + .map(|range| range.to_lsp_range(&source, &index, encoding)) + .unwrap_or_default() + } else { + Range::default() + }; + + let severity = match diagnostic.severity() { + Severity::Info => DiagnosticSeverity::INFORMATION, + Severity::Warning => DiagnosticSeverity::WARNING, + Severity::Error | Severity::Fatal => DiagnosticSeverity::ERROR, + }; + + let tags = diagnostic + .primary_tags() + .map(|tags| { + tags.iter() + .map(|tag| match tag { + ruff_db::diagnostic::DiagnosticTag::Unnecessary => DiagnosticTag::UNNECESSARY, + ruff_db::diagnostic::DiagnosticTag::Deprecated => DiagnosticTag::DEPRECATED, + }) + .collect::>() + }) + .filter(|mapped_tags| !mapped_tags.is_empty()); + + let code_description = diagnostic + .id() + .is_lint() + .then(|| { + Some(lsp_types::CodeDescription { + href: lsp_types::Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id())) + .ok()?, + }) + }) + .flatten(); + + let mut related_information = Vec::new(); + + related_information.extend( + diagnostic + .secondary_annotations() + .filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)), + ); + + for sub_diagnostic in diagnostic.sub_diagnostics() { + related_information.extend(sub_diagnostic_to_related_information( + db, + sub_diagnostic, + encoding, + )); + + related_information.extend( + sub_diagnostic + .annotations() + .iter() + .filter_map(|annotation| { + annotation_to_related_information(db, annotation, encoding) + }), + ); + } + + Diagnostic { + range, + severity: Some(severity), + tags, + code: Some(NumberOrString::String(diagnostic.id().to_string())), + code_description, + source: Some("ty".into()), + message: diagnostic.concise_message().to_string(), + related_information: Some(related_information), + data: None, + } +} + +/// Converts an [`Annotation`] to a [`DiagnosticRelatedInformation`]. +fn annotation_to_related_information( + db: &dyn Db, + annotation: &Annotation, + encoding: PositionEncoding, +) -> Option { + let span = annotation.get_span(); + + let annotation_message = annotation.get_message()?; + let range = FileRange::try_from(span).ok()?; + let location = range.to_location(db.upcast(), encoding)?; + + Some(DiagnosticRelatedInformation { + location, + message: annotation_message.to_string(), + }) +} + +/// Converts a [`SubDiagnostic`] to a [`DiagnosticRelatedInformation`]. +fn sub_diagnostic_to_related_information( + db: &dyn Db, + diagnostic: &SubDiagnostic, + encoding: PositionEncoding, +) -> Option { + let primary_annotation = diagnostic.primary_annotation()?; + + let span = primary_annotation.get_span(); + let range = FileRange::try_from(span).ok()?; + let location = range.to_location(db.upcast(), encoding)?; + + Some(DiagnosticRelatedInformation { + location, + message: diagnostic.concise_message().to_string(), + }) +} diff --git a/crates/ty_server/src/server/api/requests/diagnostic.rs b/crates/ty_server/src/server/api/requests/diagnostic.rs index 1824bc86937380..a6b90f9fa10e71 100644 --- a/crates/ty_server/src/server/api/requests/diagnostic.rs +++ b/crates/ty_server/src/server/api/requests/diagnostic.rs @@ -2,20 +2,15 @@ use std::borrow::Cow; use lsp_types::request::DocumentDiagnosticRequest; use lsp_types::{ - Diagnostic, DiagnosticSeverity, DiagnosticTag, DocumentDiagnosticParams, - DocumentDiagnosticReport, DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, - NumberOrString, Range, RelatedFullDocumentDiagnosticReport, + DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult, + FullDocumentDiagnosticReport, RelatedFullDocumentDiagnosticReport, }; -use crate::PositionEncoding; -use crate::document::{FileRangeExt, ToRangeExt}; +use crate::server::api::diagnostics::compute_diagnostics; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; use crate::server::{Result, client::Notifier}; use crate::session::DocumentSnapshot; -use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; -use ruff_db::files::FileRange; -use ruff_db::source::{line_index, source_text}; -use ty_project::{Db, ProjectDatabase}; +use ty_project::ProjectDatabase; pub(crate) struct DocumentDiagnosticRequestHandler; @@ -34,7 +29,7 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { _notifier: Notifier, _params: DocumentDiagnosticParams, ) -> Result { - let diagnostics = compute_diagnostics(&snapshot, db); + let diagnostics = compute_diagnostics(db, &snapshot); Ok(DocumentDiagnosticReportResult::Report( DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport { @@ -47,145 +42,3 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { )) } } - -fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &ProjectDatabase) -> Vec { - let Some(file) = snapshot.file(db) else { - tracing::info!( - "No file found for snapshot for `{}`", - snapshot.query().file_url() - ); - return vec![]; - }; - - let diagnostics = match db.check_file(file) { - Ok(diagnostics) => diagnostics, - Err(cancelled) => { - tracing::info!("Diagnostics computation {cancelled}"); - return vec![]; - } - }; - - diagnostics - .as_slice() - .iter() - .map(|message| to_lsp_diagnostic(db, message, snapshot.encoding())) - .collect() -} - -fn to_lsp_diagnostic( - db: &dyn Db, - diagnostic: &ruff_db::diagnostic::Diagnostic, - encoding: crate::PositionEncoding, -) -> Diagnostic { - let range = if let Some(span) = diagnostic.primary_span() { - let file = span.expect_ty_file(); - let index = line_index(db.upcast(), file); - let source = source_text(db.upcast(), file); - - span.range() - .map(|range| range.to_lsp_range(&source, &index, encoding)) - .unwrap_or_default() - } else { - Range::default() - }; - - let severity = match diagnostic.severity() { - Severity::Info => DiagnosticSeverity::INFORMATION, - Severity::Warning => DiagnosticSeverity::WARNING, - Severity::Error | Severity::Fatal => DiagnosticSeverity::ERROR, - }; - - let tags = diagnostic - .primary_tags() - .map(|tags| { - tags.iter() - .map(|tag| match tag { - ruff_db::diagnostic::DiagnosticTag::Unnecessary => DiagnosticTag::UNNECESSARY, - ruff_db::diagnostic::DiagnosticTag::Deprecated => DiagnosticTag::DEPRECATED, - }) - .collect::>() - }) - .filter(|mapped_tags| !mapped_tags.is_empty()); - - let code_description = diagnostic - .id() - .is_lint() - .then(|| { - Some(lsp_types::CodeDescription { - href: lsp_types::Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id())) - .ok()?, - }) - }) - .flatten(); - - let mut related_information = Vec::new(); - - related_information.extend( - diagnostic - .secondary_annotations() - .filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)), - ); - - for sub_diagnostic in diagnostic.sub_diagnostics() { - related_information.extend(sub_diagnostic_to_related_information( - db, - sub_diagnostic, - encoding, - )); - - related_information.extend( - sub_diagnostic - .annotations() - .iter() - .filter_map(|annotation| { - annotation_to_related_information(db, annotation, encoding) - }), - ); - } - - Diagnostic { - range, - severity: Some(severity), - tags, - code: Some(NumberOrString::String(diagnostic.id().to_string())), - code_description, - source: Some("ty".into()), - message: diagnostic.concise_message().to_string(), - related_information: Some(related_information), - data: None, - } -} - -fn annotation_to_related_information( - db: &dyn Db, - annotation: &Annotation, - encoding: PositionEncoding, -) -> Option { - let span = annotation.get_span(); - - let annotation_message = annotation.get_message()?; - let range = FileRange::try_from(span).ok()?; - let location = range.to_location(db.upcast(), encoding)?; - - Some(lsp_types::DiagnosticRelatedInformation { - location, - message: annotation_message.to_string(), - }) -} - -fn sub_diagnostic_to_related_information( - db: &dyn Db, - diagnostic: &SubDiagnostic, - encoding: PositionEncoding, -) -> Option { - let primary_annotation = diagnostic.primary_annotation()?; - - let span = primary_annotation.get_span(); - let range = FileRange::try_from(span).ok()?; - let location = range.to_location(db.upcast(), encoding)?; - - Some(lsp_types::DiagnosticRelatedInformation { - location, - message: diagnostic.concise_message().to_string(), - }) -} From 03d7be37478a59bc2cf6258087fec96e5d1dca9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:38:37 +0200 Subject: [PATCH 221/487] Update Rust crate jiff to v0.2.14 (#18303) --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83cc5f7d6548fc..17fdd7c8199416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -495,7 +495,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -909,7 +909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1444,7 +1444,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.1", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1498,9 +1498,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1508,14 +1508,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "jiff-static" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -3162,7 +3162,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3531,7 +3531,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4523,7 +4523,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From 83498b95fb2fa7b6d11d93e5ac729e4a944b0ec3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:40:01 +0200 Subject: [PATCH 222/487] Update Rust crate uuid to v1.17.0 (#18306) --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17fdd7c8199416..44e8e2bf51ea48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4240,9 +4240,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -4253,9 +4253,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862" +checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7" dependencies = [ "proc-macro2", "quote", From f3fb7429ca567f2d62bd699b6c84184e78d56fff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:40:51 +0200 Subject: [PATCH 223/487] Update astral-sh/setup-uv action to v6.1.0 (#18304) --- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/daily_fuzz.yaml | 2 +- .github/workflows/mypy_primer.yaml | 2 +- .github/workflows/publish-pypi.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d7649eac405bf1..486f36fc8238ed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -459,7 +459,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 name: Download Ruff binary to test id: download-cached-binary @@ -660,7 +660,7 @@ jobs: branch: ${{ github.event.pull_request.base.ref }} workflow: "ci.yaml" check_artifacts: true - - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - name: Fuzz env: FORCE_COLOR: 1 @@ -730,7 +730,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: @@ -773,7 +773,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: Install uv - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - name: "Install Insiders dependencies" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} run: uv pip install -r docs/requirements-insiders.txt --system diff --git a/.github/workflows/daily_fuzz.yaml b/.github/workflows/daily_fuzz.yaml index a527a3c8baece9..9ea4fa2271e9c0 100644 --- a/.github/workflows/daily_fuzz.yaml +++ b/.github/workflows/daily_fuzz.yaml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - name: "Install Rust toolchain" run: rustup show - name: "Install mold" diff --git a/.github/workflows/mypy_primer.yaml b/.github/workflows/mypy_primer.yaml index dfa7ef12a2cc34..68e9bd411bc101 100644 --- a/.github/workflows/mypy_primer.yaml +++ b/.github/workflows/mypy_primer.yaml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 with: diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 81c669f239b42b..e8bd30980c5e15 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -22,7 +22,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels-* From a43f5b2129e0ab36482a5a1386eef5ddfa82cc9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:41:18 +0200 Subject: [PATCH 224/487] Update taiki-e/install-action action to v2.52.1 (#18307) --- .github/workflows/ci.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 486f36fc8238ed..fea84d94564183 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -239,11 +239,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-insta - name: ty mdtests (GitHub annotations) @@ -297,11 +297,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-insta - name: "Run tests" @@ -324,7 +324,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-nextest - name: "Run tests" @@ -407,11 +407,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-insta - name: "Run tests" @@ -910,7 +910,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@941e8a4d9d7cdb696bd4f017cf54aca281f8ffff # v2.51.2 + uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: tool: cargo-codspeed From 2b90e7fcd70f90d8285faed960a598da1651b928 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:41:37 +0200 Subject: [PATCH 225/487] Update NPM Development dependencies (#18305) --- playground/api/package-lock.json | 103 ++++++++++++++----------------- playground/package-lock.json | 22 ++++--- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index f9f9a44bba0a4a..7db60f8b73f243 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -32,6 +32,22 @@ "node": ">=18.0.0" } }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.2.tgz", + "integrity": "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.17", + "workerd": "^1.20250508.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, "node_modules/@cloudflare/workerd-darwin-64": { "version": "1.20250508.0", "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250508.0.tgz", @@ -118,9 +134,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20250519.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250519.0.tgz", - "integrity": "sha512-glS4kimqTyxVuNNPCoOzE/BmQ3w+HVQuPwYdPAQrUDaj2AKS8Y3O5w85TVFTKZGhCtwfNs8h4iohldjEs/P5Vw==", + "version": "4.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250525.0.tgz", + "integrity": "sha512-3loeNVJkcDLb9giarUIHmDgvh+/4RtH+R/rHn4BCmME1qKdu73n/hvECYhH8BabCZplF8zQy1wok1MKwXEWC/A==", "dev": true, "license": "MIT OR Apache-2.0" }, @@ -1090,7 +1106,6 @@ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -1105,7 +1120,6 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1118,8 +1132,7 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", @@ -1127,7 +1140,6 @@ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -1175,7 +1187,6 @@ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -1316,8 +1327,7 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/is-stream": { "version": "3.0.0", @@ -1373,9 +1383,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250508.2", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250508.2.tgz", - "integrity": "sha512-+2XoHLSbY49LNQgZoAJRX+SyUwC767Cz46pgx4T/j1YGKSrMzAxCOk59b12QoFNnN50Gtd9HkT3ukZn2nzrIVw==", + "version": "4.20250508.3", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250508.3.tgz", + "integrity": "sha512-43oTmZ0CCmUcieetI5YDDyX0IiwhOcVIWzdBBCqWXK3F1XgQwg4d3fTqRyJnCmHIoaYx9CE1kTEKZC1UahPQhA==", "dev": true, "license": "MIT", "dependencies": { @@ -1384,6 +1394,7 @@ "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250508.0", @@ -1527,7 +1538,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -1591,7 +1601,6 @@ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -1659,9 +1668,9 @@ } }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true, "license": "MIT" }, @@ -1678,6 +1687,20 @@ "node": ">=14.0" } }, + "node_modules/unenv": { + "version": "2.0.0-rc.17", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.17.tgz", + "integrity": "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.4", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -1738,19 +1761,19 @@ } }, "node_modules/wrangler": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.15.2.tgz", - "integrity": "sha512-Rv7zP61DOVzIS3af+/1UzJkRVpqu6VDRi6uIVMiwD1LkXG5Ov08tv94jgnE9bSjVf0paQg3dl0E89h+wQ0x/Bw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.16.1.tgz", + "integrity": "sha512-YiLdWXcaQva2K/bqokpsZbySPmoT8TJFyJPsQPZumnkgimM9+/g/yoXArByA+pf+xU8jhw7ybQ8X1yBGXv731g==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.3.1", + "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250508.2", + "miniflare": "4.20250508.3", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.15", + "unenv": "2.0.0-rc.17", "workerd": "1.20250508.0" }, "bin": { @@ -1773,36 +1796,6 @@ } } }, - "node_modules/wrangler/node_modules/@cloudflare/unenv-preset": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.1.tgz", - "integrity": "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.15", - "workerd": "^1.20250320.0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, - "node_modules/wrangler/node_modules/unenv": { - "version": "2.0.0-rc.15", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.15.tgz", - "integrity": "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==", - "dev": true, - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "exsolve": "^1.0.4", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "ufo": "^1.5.4" - } - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/playground/package-lock.json b/playground/package-lock.json index bfebd62270630f..4307b2ee1a6edb 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -813,6 +813,13 @@ "node": ">= 8" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.36.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz", @@ -1748,9 +1755,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", - "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", + "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1997,13 +2004,14 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", - "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.0.tgz", + "integrity": "sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==", "dev": true, "license": "MIT", "dependencies": { - "@swc/core": "^1.11.21" + "@rolldown/pluginutils": "1.0.0-beta.9", + "@swc/core": "^1.11.22" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" From cc59ff8aad2d9d933bc556b86544797872d0c5eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:41:54 +0200 Subject: [PATCH 226/487] Update dependency ruff to v0.11.11 (#18301) --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index 4b0f7df5d26a93..a738338a459d3c 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.10 +ruff==0.11.11 mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 diff --git a/docs/requirements.txt b/docs/requirements.txt index e18d1ecfc6bae7..e5b667341f0a7c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.10 +ruff==0.11.11 mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 From 6a0b93170e4e0ddd67815ed06b9f8ae960d6bcc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:43:31 +0200 Subject: [PATCH 227/487] Update pre-commit dependencies (#18302) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68cf34f6066783..beaee6fed4d01b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,7 +80,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.10 + rev: v0.11.11 hooks: - id: ruff-format - id: ruff @@ -98,7 +98,7 @@ repos: # zizmor detects security vulnerabilities in GitHub Actions workflows. # Additional configuration for the tool is found in `.github/zizmor.yml` - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.7.0 + rev: v1.8.0 hooks: - id: zizmor From 1f7134f727fdbe74da2dbbd577500341963cd04b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 07:43:52 +0200 Subject: [PATCH 228/487] Update rui314/setup-mold digest to 67424c1 (#18300) --- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/daily_fuzz.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fea84d94564183..dd16b14a4ce9ec 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -237,7 +237,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 + uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: @@ -295,7 +295,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 + uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: @@ -380,7 +380,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 + uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Build" run: cargo build --release --locked @@ -405,7 +405,7 @@ jobs: MSRV: ${{ steps.msrv.outputs.value }} run: rustup default "${MSRV}" - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 + uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 with: diff --git a/.github/workflows/daily_fuzz.yaml b/.github/workflows/daily_fuzz.yaml index 9ea4fa2271e9c0..fea1adfc1b6cc0 100644 --- a/.github/workflows/daily_fuzz.yaml +++ b/.github/workflows/daily_fuzz.yaml @@ -38,7 +38,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 + uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 - name: Build ruff # A debug build means the script runs slower once it gets started, From 97ff015c88354b5d45b52a9b1460c30978b5c29b Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Mon, 26 May 2025 16:08:57 +0800 Subject: [PATCH 229/487] [ty] Add `tests` to `src.root` if it exists and is not a package (#18286) --- crates/ty/docs/configuration.md | 3 ++ crates/ty_project/src/metadata.rs | 46 +++++++++++++++++++++++ crates/ty_project/src/metadata/options.rs | 22 ++++++++++- ty.schema.json | 2 +- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 3efd6e541e009d..b3ad865ee047b4 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -172,6 +172,9 @@ If left unspecified, ty will try to detect common project layouts and initialize * if a `.//` directory exists, include `.` and `./` in the first party search path * otherwise, default to `.` (flat layout) +Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), +it will also be included in the first party search path. + **Default value**: `null` **Type**: `str` diff --git a/crates/ty_project/src/metadata.rs b/crates/ty_project/src/metadata.rs index 71bc12264c8e3c..2d866114cab667 100644 --- a/crates/ty_project/src/metadata.rs +++ b/crates/ty_project/src/metadata.rs @@ -1029,6 +1029,52 @@ expected `.`, `]` Ok(()) } + #[test] + fn src_root_with_tests() -> anyhow::Result<()> { + let system = TestSystem::default(); + let root = SystemPathBuf::from("/app"); + + // pytest will find `tests/test_foo.py` and realize it is NOT part of a package + // given that there's no `__init__.py` file in the same folder. + // It will then add `tests` to `sys.path` + // in order to import `test_foo.py` as the module `test_foo`. + system + .memory_file_system() + .write_files_all([ + (root.join("src/main.py"), ""), + (root.join("tests/conftest.py"), ""), + (root.join("tests/test_foo.py"), ""), + ]) + .context("Failed to write files")?; + + let metadata = ProjectMetadata::discover(&root, &system)?; + let settings = metadata + .options + .to_program_settings(&root, "my_package", &system); + + assert_eq!( + settings.search_paths.src_roots, + vec![root.clone(), root.join("src"), root.join("tests")] + ); + + // If `tests/__init__.py` is present, it is considered a package and `tests` is not added to `sys.path`. + system + .memory_file_system() + .write_file(root.join("tests/__init__.py"), "") + .context("Failed to write tests/__init__.py")?; + let metadata = ProjectMetadata::discover(&root, &system)?; + let settings = metadata + .options + .to_program_settings(&root, "my_package", &system); + + assert_eq!( + settings.search_paths.src_roots, + vec![root.clone(), root.join("src")] + ); + + Ok(()) + } + #[track_caller] fn assert_error_eq(error: &ProjectDiscoveryError, message: &str) { assert_eq!(error.to_string().replace('\\', "/"), message); diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 82cf45ab8b6865..46fd5eadd8ea7c 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -135,7 +135,7 @@ impl Options { } else { let src = project_root.join("src"); - if system.is_directory(&src) { + let mut roots = if system.is_directory(&src) { // Default to `src` and the project root if `src` exists and the root hasn't been specified. // This corresponds to the `src-layout` tracing::debug!( @@ -154,7 +154,24 @@ impl Options { // Default to a [flat project structure](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/). tracing::debug!("Defaulting `src.root` to `.`"); vec![project_root.to_path_buf()] + }; + + // Considering pytest test discovery conventions, + // we also include the `tests` directory if it exists and is not a package. + let tests_dir = project_root.join("tests"); + if system.is_directory(&tests_dir) + && !system.is_file(&tests_dir.join("__init__.py")) + && !roots.contains(&tests_dir) + { + // If the `tests` directory exists and is not a package, include it as a source root. + tracing::debug!( + "Including `./tests` in `src.root` because a `./tests` directory exists" + ); + + roots.push(tests_dir); } + + roots }; let (extra_paths, python, typeshed) = self @@ -392,6 +409,9 @@ pub struct SrcOptions { /// * if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) /// * if a `.//` directory exists, include `.` and `./` in the first party search path /// * otherwise, default to `.` (flat layout) + /// + /// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), + /// it will also be included in the first party search path. #[serde(skip_serializing_if = "Option::is_none")] #[option( default = r#"null"#, diff --git a/ty.schema.json b/ty.schema.json index 3d828628150acd..0aed4e9558c71e 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -859,7 +859,7 @@ "type": "object", "properties": { "root": { - "description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `.//` directory exists, include `.` and `./` in the first party search path * otherwise, default to `.` (flat layout)", + "description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `.//` directory exists, include `.` and `./` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), it will also be included in the first party search path.", "type": [ "string", "null" From c3feb8ce27350a4c7a560671f4668dd47761260e Mon Sep 17 00:00:00 2001 From: otakutyrant <64188229+otakutyrant@users.noreply.github.com> Date: Mon, 26 May 2025 16:50:09 +0800 Subject: [PATCH 230/487] Update editor integrations link in README (#17977) Co-authored-by: Oscar Gustafsson Co-authored-by: Micha Reiser --- README.md | 3 +-- scripts/generate_mkdocs.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 225c8b47daae32..d54cb7bb9d97fc 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ An extremely fast Python linter and code formatter, written in Rust. - 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports) - 📏 Over [800 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations of popular Flake8 plugins, like flake8-bugbear -- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for - [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://docs.astral.sh/ruff/editors/setup) +- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editors) for [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://docs.astral.sh/ruff/editors/setup) - 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#config-file-discovery) Ruff aims to be orders of magnitude faster than alternative tools while integrating more diff --git a/scripts/generate_mkdocs.py b/scripts/generate_mkdocs.py index 54e224cd66df6c..d78307b28e42cb 100644 --- a/scripts/generate_mkdocs.py +++ b/scripts/generate_mkdocs.py @@ -54,7 +54,6 @@ class Section(NamedTuple): Section("Contributing", "contributing.md", generated=True), ] - LINK_REWRITES: dict[str, str] = { "https://docs.astral.sh/ruff/": "index.md", "https://docs.astral.sh/ruff/configuration/": "configuration.md", @@ -62,8 +61,8 @@ class Section(NamedTuple): "configuration.md#config-file-discovery" ), "https://docs.astral.sh/ruff/contributing/": "contributing.md", + "https://docs.astral.sh/ruff/editors": "editors/index.md", "https://docs.astral.sh/ruff/editors/setup": "editors/setup.md", - "https://docs.astral.sh/ruff/integrations/": "integrations.md", "https://docs.astral.sh/ruff/faq/#how-does-ruffs-linter-compare-to-flake8": ( "faq.md#how-does-ruffs-linter-compare-to-flake8" ), From d8a5b9de177f1a748caa7ab33dcf02bf55e62a4e Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Mon, 26 May 2025 17:31:48 +0800 Subject: [PATCH 231/487] [`airflow`] Revise fix title `AIR3` (#18215) --- .../fixtures/airflow/AIR302_common_sql.py | 21 +- .../airflow/rules/moved_to_provider_in_3.rs | 22 +- .../src/rules/airflow/rules/removal_in_3.rs | 4 +- .../suggested_to_move_to_provider_in_3.rs | 4 +- .../airflow/rules/suggested_to_update_3_0.rs | 4 +- ...sts__AIR301_AIR301_class_attribute.py.snap | 4 +- ...irflow__tests__AIR301_AIR301_names.py.snap | 40 ++-- ...ow__tests__AIR301_AIR301_names_fix.py.snap | 22 +- ...__AIR301_AIR301_provider_names_fix.py.snap | 16 +- ...rflow__tests__AIR302_AIR302_amazon.py.snap | 22 +- ...rflow__tests__AIR302_AIR302_celery.py.snap | 6 +- ...w__tests__AIR302_AIR302_common_sql.py.snap | 203 +++--------------- ..._tests__AIR302_AIR302_daskexecutor.py.snap | 2 +- ...irflow__tests__AIR302_AIR302_druid.py.snap | 8 +- ..._airflow__tests__AIR302_AIR302_fab.py.snap | 36 ++-- ...airflow__tests__AIR302_AIR302_hdfs.py.snap | 4 +- ...airflow__tests__AIR302_AIR302_hive.py.snap | 40 ++-- ...airflow__tests__AIR302_AIR302_http.py.snap | 6 +- ...airflow__tests__AIR302_AIR302_jdbc.py.snap | 4 +- ...w__tests__AIR302_AIR302_kubernetes.py.snap | 78 +++---- ...irflow__tests__AIR302_AIR302_mysql.py.snap | 6 +- ...rflow__tests__AIR302_AIR302_oracle.py.snap | 2 +- ...ow__tests__AIR302_AIR302_papermill.py.snap | 2 +- ..._airflow__tests__AIR302_AIR302_pig.py.snap | 4 +- ...low__tests__AIR302_AIR302_postgres.py.snap | 2 +- ...rflow__tests__AIR302_AIR302_presto.py.snap | 2 +- ...irflow__tests__AIR302_AIR302_samba.py.snap | 2 +- ...irflow__tests__AIR302_AIR302_slack.py.snap | 6 +- ...airflow__tests__AIR302_AIR302_smtp.py.snap | 4 +- ...rflow__tests__AIR302_AIR302_sqlite.py.snap | 2 +- ...low__tests__AIR302_AIR302_standard.py.snap | 42 ++-- ...flow__tests__AIR302_AIR302_zendesk.py.snap | 2 +- ...irflow__tests__AIR311_AIR311_names.py.snap | 48 ++--- ...les__airflow__tests__AIR312_AIR312.py.snap | 56 ++--- 34 files changed, 290 insertions(+), 436 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py index 47f4cbda6e3775..1ceed1949bc51a 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py @@ -114,16 +114,11 @@ SqlSensor() -from airflow.operators.jdbc_operator import JdbcOperator -from airflow.operators.mssql_operator import MsSqlOperator -from airflow.operators.mysql_operator import MySqlOperator -from airflow.operators.oracle_operator import OracleOperator -from airflow.operators.postgres_operator import PostgresOperator -from airflow.operators.sqlite_operator import SqliteOperator - -JdbcOperator() -MsSqlOperator() -MySqlOperator() -OracleOperator() -PostgresOperator() -SqliteOperator() +from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator + +SQLExecuteQueryOperator() +SQLExecuteQueryOperator() +SQLExecuteQueryOperator() +SQLExecuteQueryOperator() +SQLExecuteQueryOperator() +SQLExecuteQueryOperator() diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index b67d1454502dc5..1a9c7433d6bb82 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -65,24 +65,26 @@ impl Violation for Airflow3MovedToProvider { fn fix_title(&self) -> Option { let Airflow3MovedToProvider { replacement, .. } = self; - match replacement { - ProviderReplacement::None => None, + if let Some((module, name, provider, version)) = match &replacement { ProviderReplacement::AutoImport { - name, module, + name, provider, version, - } => Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{module}.{name}` instead." - )), + } => Some((module, *name, provider, version)), ProviderReplacement::SourceModuleMovedToProvider { - name, module, + name, provider, version, - } => Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{module}.{name}` instead." - )), + } => Some((module, name.as_str(), provider, version)), + ProviderReplacement::None => None, + } { + Some(format!( + "Install `apache-airflow-providers-{provider}>={version}` and use `{name}` from `{module}` instead." + )) + } else { + None } } } diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index eeacdadd49ba26..7a77228221cd5c 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -73,10 +73,10 @@ impl Violation for Airflow3Removal { Replacement::AttrName(name) => Some(format!("Use `{name}` instead")), Replacement::Message(message) => Some((*message).to_string()), Replacement::AutoImport { module, name } => { - Some(format!("Use `{module}.{name}` instead")) + Some(format!("Use `{name}` from `{module}` instead.")) } Replacement::SourceModuleMoved { module, name } => { - Some(format!("Use `{module}.{name}` instead")) + Some(format!("Use `{name}` from `{module}` instead.")) } } } diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index 2f491e24c057b4..b5db71486e22f6 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -77,7 +77,7 @@ impl Violation for Airflow3SuggestedToMoveToProvider { provider, version, } => Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{module}.{name}` instead." + "Install `apache-airflow-providers-{provider}>={version}` and use `{name}` from `{module}` instead." )), ProviderReplacement::SourceModuleMovedToProvider { module, @@ -85,7 +85,7 @@ impl Violation for Airflow3SuggestedToMoveToProvider { provider, version, } => Some(format!( - "Install `apache-airflow-providers-{provider}>={version}` and use `{module}.{name}` instead." + "Install `apache-airflow-providers-{provider}>={version}` and use `{name}` from `{module}` instead." )), } } diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index c8a8e208d1ea8c..0bc310f3fe27d3 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -72,10 +72,10 @@ impl Violation for Airflow3SuggestedUpdate { Replacement::AttrName(name) => Some(format!("Use `{name}` instead")), Replacement::Message(message) => Some((*message).to_string()), Replacement::AutoImport { module, name } => { - Some(format!("Use `{module}.{name}` instead")) + Some(format!("Use `{name}` from `{module}` instead.")) } Replacement::SourceModuleMoved { module, name } => { - Some(format!("Use `{module}.{name}` instead")) + Some(format!("Use `{name}` from `{module}` instead.")) } } } diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap index 64c7197140a8fc..4fdecfe8a15c1d 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap @@ -171,7 +171,7 @@ AIR301_class_attribute.py:42:6: AIR301 [*] `airflow.datasets.manager.DatasetMana 43 | dm.register_dataset_change() 44 | dm.create_datasets() | - = help: Use `airflow.assets.manager.AssetManager` instead + = help: Use `AssetManager` from `airflow.assets.manager` instead. ℹ Safe fix 19 19 | from airflow.providers_manager import ProvidersManager @@ -302,7 +302,7 @@ AIR301_class_attribute.py:50:11: AIR301 [*] `airflow.lineage.hook.DatasetLineage | ^^^^^^^^^^^^^^^^^^ AIR301 51 | dl_info.dataset | - = help: Use `airflow.lineage.hook.AssetLineageInfo` instead + = help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead. ℹ Safe fix 9 9 | DatasetAny, diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap index 3763881c296788..6385190a4e79e2 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap @@ -85,7 +85,7 @@ AIR301_names.py:60:1: AIR301 [*] `airflow.configuration.get` is removed in Airfl 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^ AIR301 | - = help: Use `airflow.configuration.conf.get` instead + = help: Use `conf.get` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -111,7 +111,7 @@ AIR301_names.py:60:6: AIR301 [*] `airflow.configuration.getboolean` is removed i 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.getboolean` instead + = help: Use `conf.getboolean` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -137,7 +137,7 @@ AIR301_names.py:60:18: AIR301 [*] `airflow.configuration.getfloat` is removed in 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.getfloat` instead + = help: Use `conf.getfloat` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -163,7 +163,7 @@ AIR301_names.py:60:28: AIR301 [*] `airflow.configuration.getint` is removed in A 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.getint` instead + = help: Use `conf.getint` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -189,7 +189,7 @@ AIR301_names.py:60:36: AIR301 [*] `airflow.configuration.has_option` is removed 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.has_option` instead + = help: Use `conf.has_option` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -215,7 +215,7 @@ AIR301_names.py:60:48: AIR301 [*] `airflow.configuration.remove_option` is remov 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.remove_option` instead + = help: Use `conf.remove_option` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -241,7 +241,7 @@ AIR301_names.py:60:63: AIR301 [*] `airflow.configuration.as_dict` is removed in 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^^^^^ AIR301 | - = help: Use `airflow.configuration.conf.as_dict` instead + = help: Use `conf.as_dict` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -267,7 +267,7 @@ AIR301_names.py:60:72: AIR301 [*] `airflow.configuration.set` is removed in Airf 60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set | ^^^ AIR301 | - = help: Use `airflow.configuration.conf.set` instead + = help: Use `conf.set` from `airflow.configuration` instead. ℹ Safe fix 19 19 | has_option, @@ -308,7 +308,7 @@ AIR301_names.py:72:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Ai 72 | BaseHook() | ^^^^^^^^ AIR301 | - = help: Use `airflow.hooks.base.BaseHook` instead + = help: Use `BaseHook` from `airflow.hooks.base` instead. AIR301_names.py:76:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0 | @@ -324,7 +324,7 @@ AIR301_names.py:85:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOpe 85 | BaseSensorOperator() | ^^^^^^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead + = help: Use `BaseSensorOperator` from `airflow.sdk.bases.sensor` instead. AIR301_names.py:89:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 | @@ -446,7 +446,7 @@ AIR301_names.py:117:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed | ^^^^^^^^^^^^^^^^^^ AIR301 118 | mkdirs | - = help: Use `tempfile.TemporaryDirectory` instead + = help: Use `TemporaryDirectory` from `tempfile` instead. AIR301_names.py:118:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 | @@ -466,7 +466,7 @@ AIR301_names.py:121:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Ai | ^^^^^^^^^^^^ AIR301 122 | helper_cross_downstream | - = help: Use `airflow.sdk.chain` instead + = help: Use `chain` from `airflow.sdk` instead. ℹ Safe fix 48 48 | from airflow.utils.trigger_rule import TriggerRule @@ -495,7 +495,7 @@ AIR301_names.py:122:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is re 123 | 124 | # airflow.utils.log | - = help: Use `airflow.sdk.cross_downstream` instead + = help: Use `cross_downstream` from `airflow.sdk` instead. ℹ Safe fix 48 48 | from airflow.utils.trigger_rule import TriggerRule @@ -523,7 +523,7 @@ AIR301_names.py:125:1: AIR301 `airflow.utils.log.secrets_masker` is removed in A 126 | 127 | # airflow.utils.state | - = help: Use `airflow.sdk.execution_time.secrets_masker` instead + = help: Use `secrets_masker` from `airflow.sdk.execution_time` instead. AIR301_names.py:128:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 | @@ -595,7 +595,7 @@ AIR301_names.py:146:1: AIR301 `airflow.operators.python.get_current_context` is 147 | 148 | # airflow.providers.mysql | - = help: Use `airflow.sdk.get_current_context` instead + = help: Use `get_current_context` from `airflow.sdk` instead. AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 | @@ -606,7 +606,7 @@ AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_u 152 | 153 | # airflow.providers.postgres | - = help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead + = help: Use `sanitize_uri` from `airflow.providers.mysql.assets.mysql` instead. AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 | @@ -617,7 +617,7 @@ AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sani 157 | 158 | # airflow.providers.trino | - = help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead + = help: Use `sanitize_uri` from `airflow.providers.postgres.assets.postgres` instead. AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 | @@ -628,7 +628,7 @@ AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_u 162 | 163 | # airflow.notifications.basenotifier | - = help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead + = help: Use `sanitize_uri` from `airflow.providers.trino.assets.trino` instead. AIR301_names.py:166:1: AIR301 `airflow.notifications.basenotifier.BaseNotifier` is removed in Airflow 3.0 | @@ -639,7 +639,7 @@ AIR301_names.py:166:1: AIR301 `airflow.notifications.basenotifier.BaseNotifier` 167 | 168 | # airflow.auth.manager | - = help: Use `airflow.sdk.bases.notifier.BaseNotifier` instead + = help: Use `BaseNotifier` from `airflow.sdk.bases.notifier` instead. AIR301_names.py:171:1: AIR301 `airflow.auth.managers.base_auth_manager.BaseAuthManager` is removed in Airflow 3.0 | @@ -648,4 +648,4 @@ AIR301_names.py:171:1: AIR301 `airflow.auth.managers.base_auth_manager.BaseAuthM 171 | BaseAuthManager() | ^^^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.api_fastapi.auth.managers.base_auth_manager.BaseAuthManager` instead + = help: Use `BaseAuthManager` from `airflow.api_fastapi.auth.managers.base_auth_manager` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap index b2f65f18e5abaf..fb1cc25229cb23 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap @@ -10,7 +10,7 @@ AIR301_names_fix.py:19:1: AIR301 [*] `airflow.api_connexion.security.requires_ac 20 | 21 | DatasetDetails() | - = help: Use `airflow.api_fastapi.core_api.security.requires_access_asset` instead + = help: Use `requires_access_asset` from `airflow.api_fastapi.core_api.security` instead. ℹ Safe fix 15 15 | from airflow.secrets.local_filesystm import load_connections @@ -31,7 +31,7 @@ AIR301_names_fix.py:21:1: AIR301 [*] `airflow.auth.managers.models.resource_deta 21 | DatasetDetails() | ^^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.api_fastapi.auth.managers.models.resource_details.AssetDetails` instead + = help: Use `AssetDetails` from `airflow.api_fastapi.auth.managers.models.resource_details` instead. ℹ Safe fix 15 15 | from airflow.secrets.local_filesystm import load_connections @@ -54,7 +54,7 @@ AIR301_names_fix.py:24:1: AIR301 [*] `airflow.datasets.manager.DatasetManager` i 25 | dataset_manager() 26 | resolve_dataset_manager() | - = help: Use `airflow.assets.manager.AssetManager` instead + = help: Use `AssetManager` from `airflow.assets.manager` instead. ℹ Safe fix 15 15 | from airflow.secrets.local_filesystm import load_connections @@ -80,7 +80,7 @@ AIR301_names_fix.py:25:1: AIR301 [*] `airflow.datasets.manager.dataset_manager` | ^^^^^^^^^^^^^^^ AIR301 26 | resolve_dataset_manager() | - = help: Use `airflow.assets.manager.asset_manager` instead + = help: Use `asset_manager` from `airflow.assets.manager` instead. ℹ Safe fix 15 15 | from airflow.secrets.local_filesystm import load_connections @@ -109,7 +109,7 @@ AIR301_names_fix.py:26:1: AIR301 [*] `airflow.datasets.manager.resolve_dataset_m 27 | 28 | DatasetLineageInfo() | - = help: Use `airflow.assets.manager.resolve_asset_manager` instead + = help: Use `resolve_asset_manager` from `airflow.assets.manager` instead. ℹ Safe fix 15 15 | from airflow.secrets.local_filesystm import load_connections @@ -138,7 +138,7 @@ AIR301_names_fix.py:28:1: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` i 29 | 30 | AllowListValidator() | - = help: Use `airflow.lineage.hook.AssetLineageInfo` instead + = help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead. ℹ Safe fix 10 10 | dataset_manager, @@ -167,7 +167,7 @@ AIR301_names_fix.py:30:1: AIR301 [*] `airflow.metrics.validators.AllowListValida | ^^^^^^^^^^^^^^^^^^ AIR301 31 | BlockListValidator() | - = help: Use `airflow.metrics.validators.PatternAllowListValidator` instead + = help: Use `PatternAllowListValidator` from `airflow.metrics.validators` instead. ℹ Safe fix 11 11 | resolve_dataset_manager, @@ -196,7 +196,7 @@ AIR301_names_fix.py:31:1: AIR301 [*] `airflow.metrics.validators.BlockListValida 32 | 33 | load_connections() | - = help: Use `airflow.metrics.validators.PatternBlockListValidator` instead + = help: Use `PatternBlockListValidator` from `airflow.metrics.validators` instead. ℹ Safe fix 11 11 | resolve_dataset_manager, @@ -226,7 +226,7 @@ AIR301_names_fix.py:35:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATA 36 | 37 | has_access_dataset() | - = help: Use `airflow.security.permissions.RESOURCE_ASSET` instead + = help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead. ℹ Safe fix 13 13 | from airflow.lineage.hook import DatasetLineageInfo @@ -265,7 +265,7 @@ AIR301_names_fix.py:44:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_ | ^^^^^^^^^^^^^^^^^^ AIR301 45 | on_dataset_changed() | - = help: Use `airflow.listeners.spec.asset.on_asset_created` instead + = help: Use `on_asset_created` from `airflow.listeners.spec.asset` instead. ℹ Safe fix 40 40 | on_dataset_changed, @@ -283,7 +283,7 @@ AIR301_names_fix.py:45:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_ 45 | on_dataset_changed() | ^^^^^^^^^^^^^^^^^^ AIR301 | - = help: Use `airflow.listeners.spec.asset.on_asset_changed` instead + = help: Use `on_asset_changed` from `airflow.listeners.spec.asset` instead. ℹ Safe fix 40 40 | on_dataset_changed, diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap index e1ac3ba9c1d7e4..dc2a39b486dae4 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap @@ -10,7 +10,7 @@ AIR301_provider_names_fix.py:25:1: AIR301 [*] `airflow.providers.amazon.aws.auth 26 | 27 | s3_create_dataset() | - = help: Use `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.ASSET` instead + = help: Use `AvpEntities.ASSET` from `airflow.providers.amazon.aws.auth_manager.avp.entities` instead. ℹ Safe fix 22 22 | translate_airflow_dataset, @@ -30,7 +30,7 @@ AIR301_provider_names_fix.py:27:1: AIR301 [*] `airflow.providers.amazon.aws.data | ^^^^^^^^^^^^^^^^^ AIR301 28 | s3_convert_dataset_to_openlineage() | - = help: Use `airflow.providers.amazon.aws.assets.s3.create_asset` instead + = help: Use `create_asset` from `airflow.providers.amazon.aws.assets.s3` instead. ℹ Safe fix 21 21 | DatasetInfo, @@ -54,7 +54,7 @@ AIR301_provider_names_fix.py:28:1: AIR301 [*] `airflow.providers.amazon.aws.data 29 | 30 | io_create_dataset() | - = help: Use `airflow.providers.amazon.aws.assets.s3.convert_asset_to_openlineage` instead + = help: Use `convert_asset_to_openlineage` from `airflow.providers.amazon.aws.assets.s3` instead. ℹ Safe fix 21 21 | DatasetInfo, @@ -79,7 +79,7 @@ AIR301_provider_names_fix.py:36:1: AIR301 [*] `airflow.providers.google.datasets 37 | # airflow.providers.google.datasets.gcs 38 | gcs_create_dataset() | - = help: Use `airflow.providers.google.assets.bigquery.create_asset` instead + = help: Use `create_asset` from `airflow.providers.google.assets.bigquery` instead. ℹ Safe fix 21 21 | DatasetInfo, @@ -108,7 +108,7 @@ AIR301_provider_names_fix.py:38:1: AIR301 [*] `airflow.providers.google.datasets 39 | gcs_convert_dataset_to_openlineage() 40 | # airflow.providers.openlineage.utils.utils | - = help: Use `airflow.providers.google.assets.gcs.create_asset` instead + = help: Use `create_asset` from `airflow.providers.google.assets.gcs` instead. ℹ Safe fix 21 21 | DatasetInfo, @@ -137,7 +137,7 @@ AIR301_provider_names_fix.py:39:1: AIR301 [*] `airflow.providers.google.datasets 40 | # airflow.providers.openlineage.utils.utils 41 | DatasetInfo() | - = help: Use `airflow.providers.google.assets.gcs.convert_asset_to_openlineage` instead + = help: Use `convert_asset_to_openlineage` from `airflow.providers.google.assets.gcs` instead. ℹ Safe fix 21 21 | DatasetInfo, @@ -166,7 +166,7 @@ AIR301_provider_names_fix.py:41:1: AIR301 [*] `airflow.providers.openlineage.uti 42 | translate_airflow_dataset() 43 | # | - = help: Use `airflow.providers.openlineage.utils.utils.AssetInfo` instead + = help: Use `AssetInfo` from `airflow.providers.openlineage.utils.utils` instead. ℹ Safe fix 20 20 | from airflow.providers.openlineage.utils.utils import ( @@ -195,7 +195,7 @@ AIR301_provider_names_fix.py:42:1: AIR301 [*] `airflow.providers.openlineage.uti 43 | # 44 | # airflow.secrets.local_filesystem | - = help: Use `airflow.providers.openlineage.utils.utils.translate_airflow_asset` instead + = help: Use `translate_airflow_asset` from `airflow.providers.openlineage.utils.utils` instead. ℹ Safe fix 20 20 | from airflow.providers.openlineage.utils.utils import ( diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap index f7f18a458b1e0f..6fe74a7765148d 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap @@ -9,7 +9,7 @@ AIR302_amazon.py:23:1: AIR302 `airflow.hooks.S3_hook.S3Hook` is moved into `amaz | ^^^^^^ AIR302 24 | provide_bucket_name() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.hooks.s3.S3Hook` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3Hook` from `airflow.providers.amazon.aws.hooks.s3` instead. AIR302_amazon.py:24:1: AIR302 `airflow.hooks.S3_hook.provide_bucket_name` is moved into `amazon` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_amazon.py:24:1: AIR302 `airflow.hooks.S3_hook.provide_bucket_name` is mov 25 | 26 | GCSToS3Operator() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.hooks.s3.provide_bucket_name` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `provide_bucket_name` from `airflow.providers.amazon.aws.hooks.s3` instead. AIR302_amazon.py:26:1: AIR302 `airflow.operators.gcs_to_s3.GCSToS3Operator` is moved into `amazon` provider in Airflow 3.0; | @@ -30,7 +30,7 @@ AIR302_amazon.py:26:1: AIR302 `airflow.operators.gcs_to_s3.GCSToS3Operator` is m 27 | 28 | GoogleApiToS3Operator() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.gcs_to_s3.GCSToS3Operator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GCSToS3Operator` from `airflow.providers.amazon.aws.transfers.gcs_to_s3` instead. AIR302_amazon.py:28:1: AIR302 `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Operator` is moved into `amazon` provider in Airflow 3.0; | @@ -40,7 +40,7 @@ AIR302_amazon.py:28:1: AIR302 `airflow.operators.google_api_to_s3_transfer.Googl | ^^^^^^^^^^^^^^^^^^^^^ AIR302 29 | GoogleApiToS3Transfer() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.google_api_to_s3.GoogleApiToS3Operator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead. AIR302_amazon.py:29:1: AIR302 `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Transfer` is moved into `amazon` provider in Airflow 3.0; | @@ -50,7 +50,7 @@ AIR302_amazon.py:29:1: AIR302 `airflow.operators.google_api_to_s3_transfer.Googl 30 | 31 | RedshiftToS3Operator() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.google_api_to_s3.GoogleApiToS3Operator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead. AIR302_amazon.py:31:1: AIR302 `airflow.operators.redshift_to_s3_operator.RedshiftToS3Operator` is moved into `amazon` provider in Airflow 3.0; | @@ -60,7 +60,7 @@ AIR302_amazon.py:31:1: AIR302 `airflow.operators.redshift_to_s3_operator.Redshif | ^^^^^^^^^^^^^^^^^^^^ AIR302 32 | RedshiftToS3Transfer() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.redshift_to_s3.RedshiftToS3Operator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead. AIR302_amazon.py:32:1: AIR302 `airflow.operators.redshift_to_s3_operator.RedshiftToS3Transfer` is moved into `amazon` provider in Airflow 3.0; | @@ -70,7 +70,7 @@ AIR302_amazon.py:32:1: AIR302 `airflow.operators.redshift_to_s3_operator.Redshif 33 | 34 | S3FileTransformOperator() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.redshift_to_s3.RedshiftToS3Operator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead. AIR302_amazon.py:34:1: AIR302 `airflow.operators.s3_file_transform_operator.S3FileTransformOperator` is moved into `amazon` provider in Airflow 3.0; | @@ -81,7 +81,7 @@ AIR302_amazon.py:34:1: AIR302 `airflow.operators.s3_file_transform_operator.S3Fi 35 | 36 | S3ToRedshiftOperator() | - = help: Install `apache-airflow-providers-amazon>=3.0.0` and use `airflow.providers.amazon.aws.operators.s3.S3FileTransformOperator` instead. + = help: Install `apache-airflow-providers-amazon>=3.0.0` and use `S3FileTransformOperator` from `airflow.providers.amazon.aws.operators.s3` instead. AIR302_amazon.py:36:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRedshiftOperator` is moved into `amazon` provider in Airflow 3.0; | @@ -91,7 +91,7 @@ AIR302_amazon.py:36:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRed | ^^^^^^^^^^^^^^^^^^^^ AIR302 37 | S3ToRedshiftTransfer() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.s3_to_redshift.S3ToRedshiftOperator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead. AIR302_amazon.py:37:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRedshiftTransfer` is moved into `amazon` provider in Airflow 3.0; | @@ -101,7 +101,7 @@ AIR302_amazon.py:37:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRed 38 | 39 | S3KeySensor() | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.transfers.s3_to_redshift.S3ToRedshiftOperator` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead. AIR302_amazon.py:39:1: AIR302 `airflow.sensors.s3_key_sensor.S3KeySensor` is moved into `amazon` provider in Airflow 3.0; | @@ -110,4 +110,4 @@ AIR302_amazon.py:39:1: AIR302 `airflow.sensors.s3_key_sensor.S3KeySensor` is mov 39 | S3KeySensor() | ^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `airflow.providers.amazon.aws.sensors.s3.S3KeySensor` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3KeySensor` from `airflow.providers.amazon.aws.sensors.s3` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap index 554871e39e481c..7a07ac8ac20bfe 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap @@ -10,7 +10,7 @@ AIR302_celery.py:9:1: AIR302 `airflow.config_templates.default_celery.DEFAULT_CE 10 | 11 | app | - = help: Install `apache-airflow-providers-celery>=3.3.0` and use `airflow.providers.celery.executors.default_celery.DEFAULT_CELERY_CONFIG` instead. + = help: Install `apache-airflow-providers-celery>=3.3.0` and use `DEFAULT_CELERY_CONFIG` from `airflow.providers.celery.executors.default_celery` instead. AIR302_celery.py:11:1: AIR302 `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0; | @@ -20,7 +20,7 @@ AIR302_celery.py:11:1: AIR302 `airflow.executors.celery_executor.app` is moved i | ^^^ AIR302 12 | CeleryExecutor() | - = help: Install `apache-airflow-providers-celery>=3.3.0` and use `airflow.providers.celery.executors.celery_executor_utils.app` instead. + = help: Install `apache-airflow-providers-celery>=3.3.0` and use `app` from `airflow.providers.celery.executors.celery_executor_utils` instead. AIR302_celery.py:12:1: AIR302 `airflow.executors.celery_executor.CeleryExecutor` is moved into `celery` provider in Airflow 3.0; | @@ -28,4 +28,4 @@ AIR302_celery.py:12:1: AIR302 `airflow.executors.celery_executor.CeleryExecutor` 12 | CeleryExecutor() | ^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-celery>=3.3.0` and use `airflow.providers.celery.executors.celery_executor.CeleryExecutor` instead. + = help: Install `apache-airflow-providers-celery>=3.3.0` and use `CeleryExecutor` from `airflow.providers.celery.executors.celery_executor` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap index 693e7f44bdce93..0ea625549ad523 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap @@ -10,7 +10,7 @@ AIR302_common_sql.py:10:1: AIR302 `airflow.hooks.dbapi.ConnectorProtocol` is mov 11 | DbApiHook() 12 | SQLCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.hooks.sql.ConnectorProtocol` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `ConnectorProtocol` from `airflow.providers.common.sql.hooks.sql` instead. AIR302_common_sql.py:11:1: AIR302 `airflow.hooks.dbapi_hook.DbApiHook` is moved into `common-sql` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_common_sql.py:11:1: AIR302 `airflow.hooks.dbapi_hook.DbApiHook` is moved | ^^^^^^^^^ AIR302 12 | SQLCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.hooks.sql.DbApiHook` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead. AIR302_common_sql.py:12:1: AIR302 `airflow.operators.check_operator.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -28,7 +28,7 @@ AIR302_common_sql.py:12:1: AIR302 `airflow.operators.check_operator.SQLCheckOper 12 | SQLCheckOperator() | ^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:18:1: AIR302 `airflow.operators.sql.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -38,7 +38,7 @@ AIR302_common_sql.py:18:1: AIR302 `airflow.operators.sql.SQLCheckOperator` is mo | ^^^^^^^^^^^^^^^^ AIR302 19 | CheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:19:1: AIR302 `airflow.operators.check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -46,7 +46,7 @@ AIR302_common_sql.py:19:1: AIR302 `airflow.operators.check_operator.CheckOperato 19 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:24:1: AIR302 `airflow.operators.druid_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -55,7 +55,7 @@ AIR302_common_sql.py:24:1: AIR302 `airflow.operators.druid_check_operator.CheckO 24 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:29:1: AIR302 `airflow.operators.presto_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -64,7 +64,7 @@ AIR302_common_sql.py:29:1: AIR302 `airflow.operators.presto_check_operator.Check 29 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:39:1: AIR302 `airflow.operators.druid_check_operator.DruidCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -75,7 +75,7 @@ AIR302_common_sql.py:39:1: AIR302 `airflow.operators.druid_check_operator.DruidC 40 | PrestoCheckOperator() 41 | IntervalCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:40:1: AIR302 `airflow.operators.presto_check_operator.PrestoCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -85,7 +85,7 @@ AIR302_common_sql.py:40:1: AIR302 `airflow.operators.presto_check_operator.Prest 41 | IntervalCheckOperator() 42 | SQLIntervalCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:41:1: AIR302 `airflow.operators.check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -95,7 +95,7 @@ AIR302_common_sql.py:41:1: AIR302 `airflow.operators.check_operator.IntervalChec | ^^^^^^^^^^^^^^^^^^^^^ AIR302 42 | SQLIntervalCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:42:1: AIR302 `airflow.operators.check_operator.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -104,7 +104,7 @@ AIR302_common_sql.py:42:1: AIR302 `airflow.operators.check_operator.SQLIntervalC 42 | SQLIntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:51:1: AIR302 `airflow.operators.presto_check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -115,7 +115,7 @@ AIR302_common_sql.py:51:1: AIR302 `airflow.operators.presto_check_operator.Inter 52 | SQLIntervalCheckOperator() 53 | PrestoIntervalCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:52:1: AIR302 `airflow.operators.sql.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -124,7 +124,7 @@ AIR302_common_sql.py:52:1: AIR302 `airflow.operators.sql.SQLIntervalCheckOperato | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 53 | PrestoIntervalCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:53:1: AIR302 `airflow.operators.presto_check_operator.PrestoIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -133,7 +133,7 @@ AIR302_common_sql.py:53:1: AIR302 `airflow.operators.presto_check_operator.Prest 53 | PrestoIntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLIntervalCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:61:1: AIR302 `airflow.operators.check_operator.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -143,7 +143,7 @@ AIR302_common_sql.py:61:1: AIR302 `airflow.operators.check_operator.SQLThreshold | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 62 | ThresholdCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLThresholdCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:62:1: AIR302 `airflow.operators.check_operator.ThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -151,7 +151,7 @@ AIR302_common_sql.py:62:1: AIR302 `airflow.operators.check_operator.ThresholdChe 62 | ThresholdCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLThresholdCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:67:1: AIR302 `airflow.operators.sql.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -160,7 +160,7 @@ AIR302_common_sql.py:67:1: AIR302 `airflow.operators.sql.SQLThresholdCheckOperat 67 | SQLThresholdCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLThresholdCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:75:1: AIR302 `airflow.operators.check_operator.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -170,7 +170,7 @@ AIR302_common_sql.py:75:1: AIR302 `airflow.operators.check_operator.SQLValueChec | ^^^^^^^^^^^^^^^^^^^^^ AIR302 76 | ValueCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLValueCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:76:1: AIR302 `airflow.operators.check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -178,7 +178,7 @@ AIR302_common_sql.py:76:1: AIR302 `airflow.operators.check_operator.ValueCheckOp 76 | ValueCheckOperator() | ^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLValueCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:85:1: AIR302 `airflow.operators.sql.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -189,7 +189,7 @@ AIR302_common_sql.py:85:1: AIR302 `airflow.operators.sql.SQLValueCheckOperator` 86 | ValueCheckOperator() 87 | PrestoValueCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLValueCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:86:1: AIR302 `airflow.operators.presto_check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -198,7 +198,7 @@ AIR302_common_sql.py:86:1: AIR302 `airflow.operators.presto_check_operator.Value | ^^^^^^^^^^^^^^^^^^ AIR302 87 | PrestoValueCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLValueCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:87:1: AIR302 `airflow.operators.presto_check_operator.PrestoValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -207,7 +207,7 @@ AIR302_common_sql.py:87:1: AIR302 `airflow.operators.presto_check_operator.Prest 87 | PrestoValueCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLValueCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:99:1: AIR302 `airflow.operators.sql.BaseSQLOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -218,7 +218,7 @@ AIR302_common_sql.py:99:1: AIR302 `airflow.operators.sql.BaseSQLOperator` is mov 100 | BranchSQLOperator() 101 | SQLTableCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.BaseSQLOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BaseSQLOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:100:1: AIR302 `airflow.operators.sql.BranchSQLOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -228,7 +228,7 @@ AIR302_common_sql.py:100:1: AIR302 `airflow.operators.sql.BranchSQLOperator` is 101 | SQLTableCheckOperator() 102 | SQLColumnCheckOperator() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.BranchSQLOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BranchSQLOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:101:1: AIR302 `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -239,7 +239,7 @@ AIR302_common_sql.py:101:1: AIR302 `airflow.operators.sql.SQLTableCheckOperator` 102 | SQLColumnCheckOperator() 103 | _convert_to_float_if_possible() | - = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `airflow.providers.common.sql.operators.sql.SQLTableCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLTableCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:102:1: AIR302 `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | @@ -250,7 +250,7 @@ AIR302_common_sql.py:102:1: AIR302 `airflow.operators.sql.SQLColumnCheckOperator 103 | _convert_to_float_if_possible() 104 | parse_boolean() | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.operators.sql.SQLColumnCheckOperator` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SQLColumnCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:103:1: AIR302 `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0; | @@ -260,7 +260,7 @@ AIR302_common_sql.py:103:1: AIR302 `airflow.operators.sql._convert_to_float_if_p | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 104 | parse_boolean() | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.operators.sql._convert_to_float_if_possible` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `_convert_to_float_if_possible` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:104:1: AIR302 `airflow.operators.sql.parse_boolean` is moved into `common-sql` provider in Airflow 3.0; | @@ -269,7 +269,7 @@ AIR302_common_sql.py:104:1: AIR302 `airflow.operators.sql.parse_boolean` is move 104 | parse_boolean() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.operators.sql.parse_boolean` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `parse_boolean` from `airflow.providers.common.sql.operators.sql` instead. AIR302_common_sql.py:109:1: AIR302 `airflow.sensors.sql.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; | @@ -278,7 +278,7 @@ AIR302_common_sql.py:109:1: AIR302 `airflow.sensors.sql.SqlSensor` is moved into 109 | SqlSensor() | ^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.sensors.sql.SqlSensor` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead. AIR302_common_sql.py:114:1: AIR302 `airflow.sensors.sql_sensor.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; | @@ -287,147 +287,4 @@ AIR302_common_sql.py:114:1: AIR302 `airflow.sensors.sql_sensor.SqlSensor` is mov 114 | SqlSensor() | ^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `airflow.providers.common.sql.sensors.sql.SqlSensor` instead. - -AIR302_common_sql.py:124:1: AIR302 [*] `airflow.operators.jdbc_operator.JdbcOperator` is moved into `common-sql` provider in Airflow 3.0; - | -122 | from airflow.operators.sqlite_operator import SqliteOperator -123 | -124 | JdbcOperator() - | ^^^^^^^^^^^^ AIR302 -125 | MsSqlOperator() -126 | MySqlOperator() - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 |-JdbcOperator() - 125 |+SQLExecuteQueryOperator() -125 126 | MsSqlOperator() -126 127 | MySqlOperator() -127 128 | OracleOperator() - -AIR302_common_sql.py:125:1: AIR302 [*] `airflow.operators.mssql_operator.MsSqlOperator` is moved into `common-sql` provider in Airflow 3.0; - | -124 | JdbcOperator() -125 | MsSqlOperator() - | ^^^^^^^^^^^^^ AIR302 -126 | MySqlOperator() -127 | OracleOperator() - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 125 | JdbcOperator() -125 |-MsSqlOperator() - 126 |+SQLExecuteQueryOperator() -126 127 | MySqlOperator() -127 128 | OracleOperator() -128 129 | PostgresOperator() - -AIR302_common_sql.py:126:1: AIR302 [*] `airflow.operators.mysql_operator.MySqlOperator` is moved into `common-sql` provider in Airflow 3.0; - | -124 | JdbcOperator() -125 | MsSqlOperator() -126 | MySqlOperator() - | ^^^^^^^^^^^^^ AIR302 -127 | OracleOperator() -128 | PostgresOperator() - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 125 | JdbcOperator() -125 126 | MsSqlOperator() -126 |-MySqlOperator() - 127 |+SQLExecuteQueryOperator() -127 128 | OracleOperator() -128 129 | PostgresOperator() -129 130 | SqliteOperator() - -AIR302_common_sql.py:127:1: AIR302 [*] `airflow.operators.oracle_operator.OracleOperator` is moved into `common-sql` provider in Airflow 3.0; - | -125 | MsSqlOperator() -126 | MySqlOperator() -127 | OracleOperator() - | ^^^^^^^^^^^^^^ AIR302 -128 | PostgresOperator() -129 | SqliteOperator() - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 125 | JdbcOperator() -125 126 | MsSqlOperator() -126 127 | MySqlOperator() -127 |-OracleOperator() - 128 |+SQLExecuteQueryOperator() -128 129 | PostgresOperator() -129 130 | SqliteOperator() - -AIR302_common_sql.py:128:1: AIR302 [*] `airflow.operators.postgres_operator.PostgresOperator` is moved into `common-sql` provider in Airflow 3.0; - | -126 | MySqlOperator() -127 | OracleOperator() -128 | PostgresOperator() - | ^^^^^^^^^^^^^^^^ AIR302 -129 | SqliteOperator() - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 125 | JdbcOperator() -125 126 | MsSqlOperator() -126 127 | MySqlOperator() -127 128 | OracleOperator() -128 |-PostgresOperator() - 129 |+SQLExecuteQueryOperator() -129 130 | SqliteOperator() - -AIR302_common_sql.py:129:1: AIR302 [*] `airflow.operators.sqlite_operator.SqliteOperator` is moved into `common-sql` provider in Airflow 3.0; - | -127 | OracleOperator() -128 | PostgresOperator() -129 | SqliteOperator() - | ^^^^^^^^^^^^^^ AIR302 - | - = help: Install `apache-airflow-providers-common-sql>=1.3.0` and use `airflow.providers.common.sql.operators.sql.SQLExecuteQueryOperator` instead. - -ℹ Safe fix -120 120 | from airflow.operators.oracle_operator import OracleOperator -121 121 | from airflow.operators.postgres_operator import PostgresOperator -122 122 | from airflow.operators.sqlite_operator import SqliteOperator - 123 |+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator -123 124 | -124 125 | JdbcOperator() -125 126 | MsSqlOperator() -126 127 | MySqlOperator() -127 128 | OracleOperator() -128 129 | PostgresOperator() -129 |-SqliteOperator() - 130 |+SQLExecuteQueryOperator() + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap index ba40037189fb61..9faa2e670baf44 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap @@ -8,4 +8,4 @@ AIR302_daskexecutor.py:5:1: AIR302 `airflow.executors.dask_executor.DaskExecutor 5 | DaskExecutor() | ^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-daskexecutor>=1.0.0` and use `airflow.providers.daskexecutor.executors.dask_executor.DaskExecutor` instead. + = help: Install `apache-airflow-providers-daskexecutor>=1.0.0` and use `DaskExecutor` from `airflow.providers.daskexecutor.executors.dask_executor` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap index 992a7d3da508dc..d033a654e03905 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap @@ -9,7 +9,7 @@ AIR302_druid.py:12:1: AIR302 `airflow.hooks.druid_hook.DruidDbApiHook` is moved | ^^^^^^^^^^^^^^ AIR302 13 | DruidHook() | - = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `airflow.providers.apache.druid.hooks.druid.DruidDbApiHook` instead. + = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidDbApiHook` from `airflow.providers.apache.druid.hooks.druid` instead. AIR302_druid.py:13:1: AIR302 `airflow.hooks.druid_hook.DruidHook` is moved into `apache-druid` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_druid.py:13:1: AIR302 `airflow.hooks.druid_hook.DruidHook` is moved into 14 | 15 | HiveToDruidOperator() | - = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `airflow.providers.apache.druid.hooks.druid.DruidHook` instead. + = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidHook` from `airflow.providers.apache.druid.hooks.druid` instead. AIR302_druid.py:15:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidOperator` is moved into `apache-druid` provider in Airflow 3.0; | @@ -29,7 +29,7 @@ AIR302_druid.py:15:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidOperato | ^^^^^^^^^^^^^^^^^^^ AIR302 16 | HiveToDruidTransfer() | - = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `airflow.providers.apache.druid.transfers.hive_to_druid.HiveToDruidOperator` instead. + = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead. AIR302_druid.py:16:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidTransfer` is moved into `apache-druid` provider in Airflow 3.0; | @@ -37,4 +37,4 @@ AIR302_druid.py:16:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidTransfe 16 | HiveToDruidTransfer() | ^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `airflow.providers.apache.druid.transfers.hive_to_druid.HiveToDruidOperator` instead. + = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap index 0889673ff422de..e8dcbedc9f9797 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap @@ -10,7 +10,7 @@ AIR302_fab.py:10:1: AIR302 `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is 11 | init_app() 12 | auth_current_user() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth.CLIENT_AUTH` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. AIR302_fab.py:11:1: AIR302 `airflow.api.auth.backend.basic_auth.init_app` is moved into `fab` provider in Airflow 3.0; | @@ -20,7 +20,7 @@ AIR302_fab.py:11:1: AIR302 `airflow.api.auth.backend.basic_auth.init_app` is mov 12 | auth_current_user() 13 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth.init_app` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. AIR302_fab.py:12:1: AIR302 `airflow.api.auth.backend.basic_auth.auth_current_user` is moved into `fab` provider in Airflow 3.0; | @@ -30,7 +30,7 @@ AIR302_fab.py:12:1: AIR302 `airflow.api.auth.backend.basic_auth.auth_current_use | ^^^^^^^^^^^^^^^^^ AIR302 13 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth.auth_current_user` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `auth_current_user` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. AIR302_fab.py:13:1: AIR302 `airflow.api.auth.backend.basic_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | @@ -41,7 +41,7 @@ AIR302_fab.py:13:1: AIR302 `airflow.api.auth.backend.basic_auth.requires_authent 14 | 15 | from airflow.api.auth.backend.kerberos_auth import ( | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth.requires_authentication` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. AIR302_fab.py:23:1: AIR302 `airflow.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; | @@ -52,7 +52,7 @@ AIR302_fab.py:23:1: AIR302 `airflow.api.auth.backend.kerberos_auth.log` is moved 24 | CLIENT_AUTH 25 | find_user() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.log` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:24:1: AIR302 `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; | @@ -62,7 +62,7 @@ AIR302_fab.py:24:1: AIR302 `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` 25 | find_user() 26 | init_app() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.CLIENT_AUTH` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:25:1: AIR302 `airflow.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; | @@ -73,7 +73,7 @@ AIR302_fab.py:25:1: AIR302 `airflow.api.auth.backend.kerberos_auth.find_user` is 26 | init_app() 27 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.find_user` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:26:1: AIR302 `airflow.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; | @@ -83,7 +83,7 @@ AIR302_fab.py:26:1: AIR302 `airflow.api.auth.backend.kerberos_auth.init_app` is | ^^^^^^^^ AIR302 27 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.init_app` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:27:1: AIR302 `airflow.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | @@ -94,7 +94,7 @@ AIR302_fab.py:27:1: AIR302 `airflow.api.auth.backend.kerberos_auth.requires_auth 28 | 29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import ( | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.requires_authentication` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:37:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; | @@ -105,7 +105,7 @@ AIR302_fab.py:37:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ 38 | CLIENT_AUTH 39 | find_user() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.log` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:38:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; | @@ -115,7 +115,7 @@ AIR302_fab.py:38:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ 39 | find_user() 40 | init_app() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.CLIENT_AUTH` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:39:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; | @@ -126,7 +126,7 @@ AIR302_fab.py:39:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ 40 | init_app() 41 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.find_user` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:40:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; | @@ -136,7 +136,7 @@ AIR302_fab.py:40:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | ^^^^^^^^ AIR302 41 | requires_authentication() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.init_app` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:41:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | @@ -147,7 +147,7 @@ AIR302_fab.py:41:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ 42 | 43 | from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth.requires_authentication` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. AIR302_fab.py:49:1: AIR302 `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0; | @@ -158,7 +158,7 @@ AIR302_fab.py:49:1: AIR302 `airflow.auth.managers.fab.fab_auth_manager.FabAuthMa 50 | MAX_NUM_DATABASE_USER_SESSIONS 51 | FabAirflowSecurityManagerOverride() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAuthManager` from `airflow.providers.fab.auth_manager.fab_auth_manager` instead. AIR302_fab.py:50:1: AIR302 `airflow.auth.managers.fab.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` is moved into `fab` provider in Airflow 3.0; | @@ -167,7 +167,7 @@ AIR302_fab.py:50:1: AIR302 `airflow.auth.managers.fab.security_manager.override. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 51 | FabAirflowSecurityManagerOverride() | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `MAX_NUM_DATABASE_USER_SESSIONS` from `airflow.providers.fab.auth_manager.security_manager.override` instead. AIR302_fab.py:51:1: AIR302 `airflow.auth.managers.fab.security_manager.override.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; | @@ -178,7 +178,7 @@ AIR302_fab.py:51:1: AIR302 `airflow.auth.managers.fab.security_manager.override. 52 | 53 | from airflow.www.security import FabAirflowSecurityManagerOverride | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.security_manager.override.FabAirflowSecurityManagerOverride` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead. AIR302_fab.py:55:1: AIR302 `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; | @@ -187,4 +187,4 @@ AIR302_fab.py:55:1: AIR302 `airflow.www.security.FabAirflowSecurityManagerOverri 55 | FabAirflowSecurityManagerOverride() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.security_manager.override.FabAirflowSecurityManagerOverride` instead. + = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap index 8ff613fea1cdbf..950f1ebc74bcc2 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap @@ -9,7 +9,7 @@ AIR302_hdfs.py:6:1: AIR302 `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved int | ^^^^^^^^^^^ AIR302 7 | WebHdfsSensor() | - = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `airflow.providers.apache.hdfs.hooks.webhdfs.WebHDFSHook` instead. + = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHDFSHook` from `airflow.providers.apache.hdfs.hooks.webhdfs` instead. AIR302_hdfs.py:7:1: AIR302 `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is moved into `apache-hdfs` provider in Airflow 3.0; | @@ -17,4 +17,4 @@ AIR302_hdfs.py:7:1: AIR302 `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is mo 7 | WebHdfsSensor() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `airflow.providers.apache.hdfs.sensors.web_hdfs.WebHdfsSensor` instead. + = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHdfsSensor` from `airflow.providers.apache.hdfs.sensors.web_hdfs` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap index aa035c4ff715cd..ef984b1ca92e69 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap @@ -9,7 +9,7 @@ AIR302_hive.py:36:1: AIR302 `airflow.macros.hive.closest_ds_partition` is moved | ^^^^^^^^^^^^^^^^^^^^ AIR302 37 | max_partition() | - = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `airflow.providers.apache.hive.macros.hive.closest_ds_partition` instead. + = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `closest_ds_partition` from `airflow.providers.apache.hive.macros.hive` instead. AIR302_hive.py:37:1: AIR302 `airflow.macros.hive.max_partition` is moved into `apache-hive` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_hive.py:37:1: AIR302 `airflow.macros.hive.max_partition` is moved into `a 38 | 39 | HiveCliHook() | - = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `airflow.providers.apache.hive.macros.hive.max_partition` instead. + = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `max_partition` from `airflow.providers.apache.hive.macros.hive` instead. AIR302_hive.py:39:1: AIR302 `airflow.hooks.hive_hooks.HiveCliHook` is moved into `apache-hive` provider in Airflow 3.0; | @@ -30,7 +30,7 @@ AIR302_hive.py:39:1: AIR302 `airflow.hooks.hive_hooks.HiveCliHook` is moved into 40 | HiveMetastoreHook() 41 | HiveServer2Hook() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.hooks.hive.HiveCliHook` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveCliHook` from `airflow.providers.apache.hive.hooks.hive` instead. AIR302_hive.py:40:1: AIR302 `airflow.hooks.hive_hooks.HiveMetastoreHook` is moved into `apache-hive` provider in Airflow 3.0; | @@ -40,7 +40,7 @@ AIR302_hive.py:40:1: AIR302 `airflow.hooks.hive_hooks.HiveMetastoreHook` is move 41 | HiveServer2Hook() 42 | HIVE_QUEUE_PRIORITIES | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.hooks.hive.HiveMetastoreHook` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveMetastoreHook` from `airflow.providers.apache.hive.hooks.hive` instead. AIR302_hive.py:41:1: AIR302 `airflow.hooks.hive_hooks.HiveServer2Hook` is moved into `apache-hive` provider in Airflow 3.0; | @@ -50,7 +50,7 @@ AIR302_hive.py:41:1: AIR302 `airflow.hooks.hive_hooks.HiveServer2Hook` is moved | ^^^^^^^^^^^^^^^ AIR302 42 | HIVE_QUEUE_PRIORITIES | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.hooks.hive.HiveServer2Hook` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveServer2Hook` from `airflow.providers.apache.hive.hooks.hive` instead. AIR302_hive.py:42:1: AIR302 `airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES` is moved into `apache-hive` provider in Airflow 3.0; | @@ -61,7 +61,7 @@ AIR302_hive.py:42:1: AIR302 `airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES` is 43 | 44 | HiveOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.hooks.hive.HIVE_QUEUE_PRIORITIES` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HIVE_QUEUE_PRIORITIES` from `airflow.providers.apache.hive.hooks.hive` instead. AIR302_hive.py:44:1: AIR302 `airflow.operators.hive_operator.HiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -72,7 +72,7 @@ AIR302_hive.py:44:1: AIR302 `airflow.operators.hive_operator.HiveOperator` is mo 45 | 46 | HiveStatsCollectionOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.operators.hive.HiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveOperator` from `airflow.providers.apache.hive.operators.hive` instead. AIR302_hive.py:46:1: AIR302 `airflow.operators.hive_stats_operator.HiveStatsCollectionOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -83,7 +83,7 @@ AIR302_hive.py:46:1: AIR302 `airflow.operators.hive_stats_operator.HiveStatsColl 47 | 48 | HiveToMySqlOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.operators.hive_stats.HiveStatsCollectionOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveStatsCollectionOperator` from `airflow.providers.apache.hive.operators.hive_stats` instead. AIR302_hive.py:48:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -93,7 +93,7 @@ AIR302_hive.py:48:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlOperator | ^^^^^^^^^^^^^^^^^^^ AIR302 49 | HiveToMySqlTransfer() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.hive_to_mysql.HiveToMySqlOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToMySqlOperator` from `airflow.providers.apache.hive.transfers.hive_to_mysql` instead. AIR302_hive.py:49:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlTransfer` is moved into `apache-hive` provider in Airflow 3.0; | @@ -103,7 +103,7 @@ AIR302_hive.py:49:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlTransfer 50 | 51 | HiveToSambaOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.hive_to_mysql.HiveToMySqlOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToMySqlOperator` from `airflow.providers.apache.hive.transfers.hive_to_mysql` instead. AIR302_hive.py:51:1: AIR302 `airflow.operators.hive_to_samba_operator.HiveToSambaOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -114,7 +114,7 @@ AIR302_hive.py:51:1: AIR302 `airflow.operators.hive_to_samba_operator.HiveToSamb 52 | 53 | MsSqlToHiveOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.hive_to_samba.HiveToSambaOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToSambaOperator` from `airflow.providers.apache.hive.transfers.hive_to_samba` instead. AIR302_hive.py:53:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -124,7 +124,7 @@ AIR302_hive.py:53:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveOperator | ^^^^^^^^^^^^^^^^^^^ AIR302 54 | MsSqlToHiveTransfer() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.mssql_to_hive.MsSqlToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. AIR302_hive.py:54:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | @@ -134,7 +134,7 @@ AIR302_hive.py:54:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveTransfer 55 | 56 | MySqlToHiveOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.mssql_to_hive.MsSqlToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. AIR302_hive.py:56:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -144,7 +144,7 @@ AIR302_hive.py:56:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveOperator | ^^^^^^^^^^^^^^^^^^^ AIR302 57 | MySqlToHiveTransfer() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.mysql_to_hive.MySqlToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. AIR302_hive.py:57:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | @@ -154,7 +154,7 @@ AIR302_hive.py:57:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveTransfer 58 | 59 | S3ToHiveOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.mysql_to_hive.MySqlToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. AIR302_hive.py:59:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | @@ -164,7 +164,7 @@ AIR302_hive.py:59:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveOpera | ^^^^^^^^^^^^^^^^ AIR302 60 | S3ToHiveTransfer() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.s3_to_hive.S3ToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `S3ToHiveOperator` from `airflow.providers.apache.hive.transfers.s3_to_hive` instead. AIR302_hive.py:60:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | @@ -174,7 +174,7 @@ AIR302_hive.py:60:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveTrans 61 | 62 | HivePartitionSensor() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.transfers.s3_to_hive.S3ToHiveOperator` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `S3ToHiveOperator` from `airflow.providers.apache.hive.transfers.s3_to_hive` instead. AIR302_hive.py:62:1: AIR302 `airflow.sensors.hive_partition_sensor.HivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | @@ -185,7 +185,7 @@ AIR302_hive.py:62:1: AIR302 `airflow.sensors.hive_partition_sensor.HivePartition 63 | 64 | MetastorePartitionSensor() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.sensors.hive_partition.HivePartitionSensor` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HivePartitionSensor` from `airflow.providers.apache.hive.sensors.hive_partition` instead. AIR302_hive.py:64:1: AIR302 `airflow.sensors.metastore_partition_sensor.MetastorePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | @@ -196,7 +196,7 @@ AIR302_hive.py:64:1: AIR302 `airflow.sensors.metastore_partition_sensor.Metastor 65 | 66 | NamedHivePartitionSensor() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.sensors.metastore_partition.MetastorePartitionSensor` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MetastorePartitionSensor` from `airflow.providers.apache.hive.sensors.metastore_partition` instead. AIR302_hive.py:66:1: AIR302 `airflow.sensors.named_hive_partition_sensor.NamedHivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | @@ -205,4 +205,4 @@ AIR302_hive.py:66:1: AIR302 `airflow.sensors.named_hive_partition_sensor.NamedHi 66 | NamedHivePartitionSensor() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `airflow.providers.apache.hive.sensors.named_hive_partition.NamedHivePartitionSensor` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `NamedHivePartitionSensor` from `airflow.providers.apache.hive.sensors.named_hive_partition` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap index d8ab77304dd638..ea7e022ac9e1d6 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap @@ -10,7 +10,7 @@ AIR302_http.py:7:1: AIR302 `airflow.hooks.http_hook.HttpHook` is moved into `htt 8 | SimpleHttpOperator() 9 | HttpSensor() | - = help: Install `apache-airflow-providers-http>=1.0.0` and use `airflow.providers.http.hooks.http.HttpHook` instead. + = help: Install `apache-airflow-providers-http>=1.0.0` and use `HttpHook` from `airflow.providers.http.hooks.http` instead. AIR302_http.py:8:1: AIR302 [*] `airflow.operators.http_operator.SimpleHttpOperator` is moved into `http` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_http.py:8:1: AIR302 [*] `airflow.operators.http_operator.SimpleHttpOperat | ^^^^^^^^^^^^^^^^^^ AIR302 9 | HttpSensor() | - = help: Install `apache-airflow-providers-http>=5.0.0` and use `airflow.providers.http.operators.http.HttpOperator` instead. + = help: Install `apache-airflow-providers-http>=5.0.0` and use `HttpOperator` from `airflow.providers.http.operators.http` instead. ℹ Safe fix 3 3 | from airflow.hooks.http_hook import HttpHook @@ -39,4 +39,4 @@ AIR302_http.py:9:1: AIR302 `airflow.sensors.http_sensor.HttpSensor` is moved int 9 | HttpSensor() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-http>=1.0.0` and use `airflow.providers.http.sensors.http.HttpSensor` instead. + = help: Install `apache-airflow-providers-http>=1.0.0` and use `HttpSensor` from `airflow.providers.http.sensors.http` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap index ab257a2b2479fc..f541547a7f796b 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap @@ -9,7 +9,7 @@ AIR302_jdbc.py:8:1: AIR302 `airflow.hooks.jdbc_hook.JdbcHook` is moved into `jdb | ^^^^^^^^ AIR302 9 | jaydebeapi() | - = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `airflow.providers.jdbc.hooks.jdbc.JdbcHook` instead. + = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `JdbcHook` from `airflow.providers.jdbc.hooks.jdbc` instead. AIR302_jdbc.py:9:1: AIR302 `airflow.hooks.jdbc_hook.jaydebeapi` is moved into `jdbc` provider in Airflow 3.0; | @@ -17,4 +17,4 @@ AIR302_jdbc.py:9:1: AIR302 `airflow.hooks.jdbc_hook.jaydebeapi` is moved into `j 9 | jaydebeapi() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `airflow.providers.jdbc.hooks.jdbc.jaydebeapi` instead. + = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `jaydebeapi` from `airflow.providers.jdbc.hooks.jdbc` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap index cd35c51ba2a928..9624ed1a53cadf 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap @@ -9,7 +9,7 @@ AIR302_kubernetes.py:29:1: AIR302 `airflow.executors.kubernetes_executor_types.A | ^^^^^^^^^^^^^^ AIR302 30 | POD_EXECUTOR_DONE_KEY | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.ALL_NAMESPACES` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `ALL_NAMESPACES` from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types` instead. AIR302_kubernetes.py:30:1: AIR302 `airflow.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_kubernetes.py:30:1: AIR302 `airflow.executors.kubernetes_executor_types.P 31 | 32 | K8SModel() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `POD_EXECUTOR_DONE_KEY` from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types` instead. AIR302_kubernetes.py:32:1: AIR302 `airflow.kubernetes.k8s_model.K8SModel` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -29,7 +29,7 @@ AIR302_kubernetes.py:32:1: AIR302 `airflow.kubernetes.k8s_model.K8SModel` is mov | ^^^^^^^^ AIR302 33 | append_to_pod() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.k8s_model.K8SModel` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `K8SModel` from `airflow.providers.cncf.kubernetes.k8s_model` instead. AIR302_kubernetes.py:33:1: AIR302 `airflow.kubernetes.k8s_model.append_to_pod` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -39,7 +39,7 @@ AIR302_kubernetes.py:33:1: AIR302 `airflow.kubernetes.k8s_model.append_to_pod` i 34 | 35 | _disable_verify_ssl() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.k8s_model.append_to_pod` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `append_to_pod` from `airflow.providers.cncf.kubernetes.k8s_model` instead. AIR302_kubernetes.py:35:1: AIR302 `airflow.kubernetes.kube_client._disable_verify_ssl` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -50,7 +50,7 @@ AIR302_kubernetes.py:35:1: AIR302 `airflow.kubernetes.kube_client._disable_verif 36 | _enable_tcp_keepalive() 37 | get_kube_client() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kube_client._disable_verify_ssl` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `_disable_verify_ssl` from `airflow.providers.cncf.kubernetes.kube_client` instead. AIR302_kubernetes.py:36:1: AIR302 `airflow.kubernetes.kube_client._enable_tcp_keepalive` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -59,7 +59,7 @@ AIR302_kubernetes.py:36:1: AIR302 `airflow.kubernetes.kube_client._enable_tcp_ke | ^^^^^^^^^^^^^^^^^^^^^ AIR302 37 | get_kube_client() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kube_client._enable_tcp_keepalive` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `_enable_tcp_keepalive` from `airflow.providers.cncf.kubernetes.kube_client` instead. AIR302_kubernetes.py:37:1: AIR302 `airflow.kubernetes.kube_client.get_kube_client` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -70,7 +70,7 @@ AIR302_kubernetes.py:37:1: AIR302 `airflow.kubernetes.kube_client.get_kube_clien 38 | 39 | add_pod_suffix() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kube_client.get_kube_client` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_kube_client` from `airflow.providers.cncf.kubernetes.kube_client` instead. AIR302_kubernetes.py:39:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -80,7 +80,7 @@ AIR302_kubernetes.py:39:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_func | ^^^^^^^^^^^^^^ AIR302 40 | create_pod_id() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.add_unique_suffix` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `add_unique_suffix` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. ℹ Safe fix 25 25 | Port, @@ -108,7 +108,7 @@ AIR302_kubernetes.py:40:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_func 41 | 42 | annotations_for_logging_task_metadata() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.create_unique_id` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `create_unique_id` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. ℹ Safe fix 25 25 | Port, @@ -137,7 +137,7 @@ AIR302_kubernetes.py:42:1: AIR302 `airflow.kubernetes.kubernetes_helper_function 43 | annotations_to_key() 44 | get_logs_task_metadata() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.annotations_for_logging_task_metadata` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `annotations_for_logging_task_metadata` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. AIR302_kubernetes.py:43:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.annotations_to_key` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -147,7 +147,7 @@ AIR302_kubernetes.py:43:1: AIR302 `airflow.kubernetes.kubernetes_helper_function 44 | get_logs_task_metadata() 45 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.annotations_to_key` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `annotations_to_key` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. AIR302_kubernetes.py:44:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.get_logs_task_metadata` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -157,7 +157,7 @@ AIR302_kubernetes.py:44:1: AIR302 `airflow.kubernetes.kubernetes_helper_function | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 45 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.get_logs_task_metadata` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_logs_task_metadata` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. AIR302_kubernetes.py:45:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.rand_str` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -168,7 +168,7 @@ AIR302_kubernetes.py:45:1: AIR302 `airflow.kubernetes.kubernetes_helper_function 46 | 47 | Port() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.rand_str` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. AIR302_kubernetes.py:47:1: AIR302 [*] `airflow.kubernetes.pod.Port` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -178,7 +178,7 @@ AIR302_kubernetes.py:47:1: AIR302 [*] `airflow.kubernetes.pod.Port` is moved int | ^^^^ AIR302 48 | Resources() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `kubernetes.client.models.V1ContainerPort` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1ContainerPort` from `kubernetes.client.models` instead. ℹ Safe fix 25 25 | Port, @@ -204,7 +204,7 @@ AIR302_kubernetes.py:48:1: AIR302 [*] `airflow.kubernetes.pod.Resources` is move 48 | Resources() | ^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `kubernetes.client.models.V1ResourceRequirements` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1ResourceRequirements` from `kubernetes.client.models` instead. ℹ Safe fix 25 25 | Port, @@ -233,7 +233,7 @@ AIR302_kubernetes.py:64:1: AIR302 `airflow.kubernetes.pod_generator.datetime_to_ 65 | extend_object_field() 66 | label_safe_datestring_to_datetime() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.datetime_to_label_safe_datestring` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `datetime_to_label_safe_datestring` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:65:1: AIR302 `airflow.kubernetes.pod_generator.extend_object_field` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -243,7 +243,7 @@ AIR302_kubernetes.py:65:1: AIR302 `airflow.kubernetes.pod_generator.extend_objec 66 | label_safe_datestring_to_datetime() 67 | make_safe_label_value() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.extend_object_field` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `extend_object_field` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:66:1: AIR302 `airflow.kubernetes.pod_generator.label_safe_datestring_to_datetime` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -254,7 +254,7 @@ AIR302_kubernetes.py:66:1: AIR302 `airflow.kubernetes.pod_generator.label_safe_d 67 | make_safe_label_value() 68 | merge_objects() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.label_safe_datestring_to_datetime` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `label_safe_datestring_to_datetime` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:67:1: AIR302 `airflow.kubernetes.pod_generator.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -265,7 +265,7 @@ AIR302_kubernetes.py:67:1: AIR302 `airflow.kubernetes.pod_generator.make_safe_la 68 | merge_objects() 69 | PodGenerator() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.make_safe_label_value` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `make_safe_label_value` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:68:1: AIR302 `airflow.kubernetes.pod_generator.merge_objects` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -276,7 +276,7 @@ AIR302_kubernetes.py:68:1: AIR302 `airflow.kubernetes.pod_generator.merge_object 69 | PodGenerator() 70 | PodDefaults() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.merge_objects` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `merge_objects` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:69:1: AIR302 `airflow.kubernetes.pod_generator.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -287,7 +287,7 @@ AIR302_kubernetes.py:69:1: AIR302 `airflow.kubernetes.pod_generator.PodGenerator 70 | PodDefaults() 71 | PodGeneratorDeprecated() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.PodGenerator` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:70:1: AIR302 `airflow.kubernetes.pod_generator.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -298,7 +298,7 @@ AIR302_kubernetes.py:70:1: AIR302 `airflow.kubernetes.pod_generator.PodDefaults` 71 | PodGeneratorDeprecated() 72 | add_pod_suffix() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.utils.xcom_sidecar.PodDefaults` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. AIR302_kubernetes.py:71:1: AIR302 `airflow.kubernetes.pod_generator.PodGeneratorDeprecated` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -309,7 +309,7 @@ AIR302_kubernetes.py:71:1: AIR302 `airflow.kubernetes.pod_generator.PodGenerator 72 | add_pod_suffix() 73 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.PodGenerator` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:72:1: AIR302 [*] `airflow.kubernetes.pod_generator.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -319,7 +319,7 @@ AIR302_kubernetes.py:72:1: AIR302 [*] `airflow.kubernetes.pod_generator.add_pod_ | ^^^^^^^^^^^^^^ AIR302 73 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.add_unique_suffix` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `add_unique_suffix` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. ℹ Safe fix 60 60 | merge_objects, @@ -346,7 +346,7 @@ AIR302_kubernetes.py:73:1: AIR302 `airflow.kubernetes.pod_generator.rand_str` is 73 | rand_str() | ^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kubernetes_helper_functions.rand_str` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. AIR302_kubernetes.py:86:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -357,7 +357,7 @@ AIR302_kubernetes.py:86:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.P 87 | PodGenerator() 88 | make_safe_label_value() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.utils.xcom_sidecar.PodDefaults` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. AIR302_kubernetes.py:87:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -366,7 +366,7 @@ AIR302_kubernetes.py:87:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.P | ^^^^^^^^^^^^ AIR302 88 | make_safe_label_value() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.PodGenerator` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:88:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -377,7 +377,7 @@ AIR302_kubernetes.py:88:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.m 89 | 90 | PodLauncher() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.pod_generator.make_safe_label_value` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `make_safe_label_value` from `airflow.providers.cncf.kubernetes.pod_generator` instead. AIR302_kubernetes.py:90:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -387,7 +387,7 @@ AIR302_kubernetes.py:90:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodLaunch | ^^^^^^^^^^^ AIR302 91 | PodStatus() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `airflow.providers.cncf.kubernetes.utils.pod_manager.PodManager` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodManager` from `airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix 82 82 | PodLauncher, @@ -411,7 +411,7 @@ AIR302_kubernetes.py:91:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodStatus 91 | PodStatus() | ^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use ` airflow.providers.cncf.kubernetes.utils.pod_manager.PodPhase` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodPhase` from ` airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix 82 82 | PodLauncher, @@ -439,7 +439,7 @@ AIR302_kubernetes.py:108:1: AIR302 `airflow.kubernetes.pod_launcher_deprecated.P 109 | PodLauncher() 110 | PodStatus() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.utils.xcom_sidecar.PodDefaults` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. AIR302_kubernetes.py:109:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -449,7 +449,7 @@ AIR302_kubernetes.py:109:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecat 110 | PodStatus() 111 | get_kube_client() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `airflow.providers.cncf.kubernetes.utils.pod_manager.PodManager` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodManager` from `airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix 104 104 | ) @@ -472,7 +472,7 @@ AIR302_kubernetes.py:110:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecat | ^^^^^^^^^ AIR302 111 | get_kube_client() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use ` airflow.providers.cncf.kubernetes.utils.pod_manager.PodPhase` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodPhase` from ` airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix 104 104 | ) @@ -497,7 +497,7 @@ AIR302_kubernetes.py:111:1: AIR302 `airflow.kubernetes.pod_launcher_deprecated.g 112 | 113 | PodRuntimeInfoEnv() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.kube_client.get_kube_client` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_kube_client` from `airflow.providers.cncf.kubernetes.kube_client` instead. AIR302_kubernetes.py:113:1: AIR302 [*] `airflow.kubernetes.pod_runtime_info_env.PodRuntimeInfoEnv` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -508,7 +508,7 @@ AIR302_kubernetes.py:113:1: AIR302 [*] `airflow.kubernetes.pod_runtime_info_env. 114 | K8SModel() 115 | Secret() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `kubernetes.client.models.V1EnvVar` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1EnvVar` from `kubernetes.client.models` instead. ℹ Safe fix 104 104 | ) @@ -535,7 +535,7 @@ AIR302_kubernetes.py:114:1: AIR302 `airflow.kubernetes.secret.K8SModel` is moved 115 | Secret() 116 | Volume() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.k8s_model.K8SModel` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `K8SModel` from `airflow.providers.cncf.kubernetes.k8s_model` instead. AIR302_kubernetes.py:115:1: AIR302 `airflow.kubernetes.secret.Secret` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -546,7 +546,7 @@ AIR302_kubernetes.py:115:1: AIR302 `airflow.kubernetes.secret.Secret` is moved i 116 | Volume() 117 | VolumeMount() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `airflow.providers.cncf.kubernetes.secret.Secret` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `Secret` from `airflow.providers.cncf.kubernetes.secret` instead. AIR302_kubernetes.py:116:1: AIR302 [*] `airflow.kubernetes.volume.Volume` is moved into `cncf-kubernetes` provider in Airflow 3.0; | @@ -556,7 +556,7 @@ AIR302_kubernetes.py:116:1: AIR302 [*] `airflow.kubernetes.volume.Volume` is mov | ^^^^^^ AIR302 117 | VolumeMount() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `kubernetes.client.models.V1Volume` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1Volume` from `kubernetes.client.models` instead. ℹ Safe fix 104 104 | ) @@ -581,7 +581,7 @@ AIR302_kubernetes.py:117:1: AIR302 [*] `airflow.kubernetes.volume_mount.VolumeMo 117 | VolumeMount() | ^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `kubernetes.client.models.V1VolumeMount` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1VolumeMount` from `kubernetes.client.models` instead. ℹ Safe fix 104 104 | ) diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap index d40283a7dd8a39..5032528ef3f467 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap @@ -10,7 +10,7 @@ AIR302_mysql.py:9:1: AIR302 `airflow.hooks.mysql_hook.MySqlHook` is moved into ` 10 | PrestoToMySqlOperator() 11 | PrestoToMySqlTransfer() | - = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `airflow.providers.mysql.hooks.mysql.MySqlHook` instead. + = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `MySqlHook` from `airflow.providers.mysql.hooks.mysql` instead. AIR302_mysql.py:10:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlOperator` is moved into `mysql` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_mysql.py:10:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlOpe | ^^^^^^^^^^^^^^^^^^^^^ AIR302 11 | PrestoToMySqlTransfer() | - = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `airflow.providers.mysql.transfers.presto_to_mysql.PrestoToMySqlOperator` instead. + = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `PrestoToMySqlOperator` from `airflow.providers.mysql.transfers.presto_to_mysql` instead. AIR302_mysql.py:11:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlTransfer` is moved into `mysql` provider in Airflow 3.0; | @@ -28,4 +28,4 @@ AIR302_mysql.py:11:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlTra 11 | PrestoToMySqlTransfer() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `airflow.providers.mysql.transfers.presto_to_mysql.PrestoToMySqlOperator` instead. + = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `PrestoToMySqlOperator` from `airflow.providers.mysql.transfers.presto_to_mysql` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap index 6b087c471593eb..300e736fe21ba6 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap @@ -8,4 +8,4 @@ AIR302_oracle.py:5:1: AIR302 `airflow.hooks.oracle_hook.OracleHook` is moved int 5 | OracleHook() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-oracle>=1.0.0` and use `airflow.providers.oracle.hooks.oracle.OracleHook` instead. + = help: Install `apache-airflow-providers-oracle>=1.0.0` and use `OracleHook` from `airflow.providers.oracle.hooks.oracle` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap index b0ebbed9d619dd..47c24b7bb3a24a 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap @@ -8,4 +8,4 @@ AIR302_papermill.py:5:1: AIR302 `airflow.operators.papermill_operator.PapermillO 5 | PapermillOperator() | ^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-papermill>=1.0.0` and use `airflow.providers.papermill.operators.papermill.PapermillOperator` instead. + = help: Install `apache-airflow-providers-papermill>=1.0.0` and use `PapermillOperator` from `airflow.providers.papermill.operators.papermill` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap index 10c5ee05fb1a3a..ebdf12601ef2ce 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap @@ -9,7 +9,7 @@ AIR302_pig.py:6:1: AIR302 `airflow.hooks.pig_hook.PigCliHook` is moved into `apa | ^^^^^^^^^^ AIR302 7 | PigOperator() | - = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `airflow.providers.apache.pig.hooks.pig.PigCliHook` instead. + = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `PigCliHook` from `airflow.providers.apache.pig.hooks.pig` instead. AIR302_pig.py:7:1: AIR302 `airflow.operators.pig_operator.PigOperator` is moved into `apache-pig` provider in Airflow 3.0; | @@ -17,4 +17,4 @@ AIR302_pig.py:7:1: AIR302 `airflow.operators.pig_operator.PigOperator` is moved 7 | PigOperator() | ^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `airflow.providers.apache.pig.operators.pig.PigOperator` instead. + = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `PigOperator` from `airflow.providers.apache.pig.operators.pig` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap index f54468142b7144..152004281dc4b0 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap @@ -9,7 +9,7 @@ AIR302_postgres.py:6:1: AIR302 `airflow.hooks.postgres_hook.PostgresHook` is mov | ^^^^^^^^^^^^ AIR302 7 | Mapping() | - = help: Install `apache-airflow-providers-postgres>=1.0.0` and use `airflow.providers.postgres.hooks.postgres.PostgresHook` instead. + = help: Install `apache-airflow-providers-postgres>=1.0.0` and use `PostgresHook` from `airflow.providers.postgres.hooks.postgres` instead. AIR302_postgres.py:7:1: AIR302 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0 | diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap index cb03cfaf254a1b..b83445e9b3776d 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap @@ -8,4 +8,4 @@ AIR302_presto.py:5:1: AIR302 `airflow.hooks.presto_hook.PrestoHook` is moved int 5 | PrestoHook() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-presto>=1.0.0` and use `airflow.providers.presto.hooks.presto.PrestoHook` instead. + = help: Install `apache-airflow-providers-presto>=1.0.0` and use `PrestoHook` from `airflow.providers.presto.hooks.presto` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap index 470f3faa0ed13d..ed5fd6f01c7d63 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap @@ -8,4 +8,4 @@ AIR302_samba.py:5:1: AIR302 `airflow.hooks.samba_hook.SambaHook` is moved into ` 5 | SambaHook() | ^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-samba>=1.0.0` and use `airflow.providers.samba.hooks.samba.SambaHook` instead. + = help: Install `apache-airflow-providers-samba>=1.0.0` and use `SambaHook` from `airflow.providers.samba.hooks.samba` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap index 40b728f9f505c0..bdcca0d6584003 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap @@ -10,7 +10,7 @@ AIR302_slack.py:6:1: AIR302 `airflow.hooks.slack_hook.SlackHook` is moved into ` 7 | SlackAPIOperator() 8 | SlackAPIPostOperator() | - = help: Install `apache-airflow-providers-slack>=1.0.0` and use `airflow.providers.slack.hooks.slack.SlackHook` instead. + = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackHook` from `airflow.providers.slack.hooks.slack` instead. AIR302_slack.py:7:1: AIR302 `airflow.operators.slack_operator.SlackAPIOperator` is moved into `slack` provider in Airflow 3.0; | @@ -19,7 +19,7 @@ AIR302_slack.py:7:1: AIR302 `airflow.operators.slack_operator.SlackAPIOperator` | ^^^^^^^^^^^^^^^^ AIR302 8 | SlackAPIPostOperator() | - = help: Install `apache-airflow-providers-slack>=1.0.0` and use `airflow.providers.slack.operators.slack.SlackAPIOperator` instead. + = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackAPIOperator` from `airflow.providers.slack.operators.slack` instead. AIR302_slack.py:8:1: AIR302 `airflow.operators.slack_operator.SlackAPIPostOperator` is moved into `slack` provider in Airflow 3.0; | @@ -28,4 +28,4 @@ AIR302_slack.py:8:1: AIR302 `airflow.operators.slack_operator.SlackAPIPostOperat 8 | SlackAPIPostOperator() | ^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-slack>=1.0.0` and use `airflow.providers.slack.operators.slack.SlackAPIPostOperator` instead. + = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackAPIPostOperator` from `airflow.providers.slack.operators.slack` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap index 5bf6522a4d8f47..57de3258a1803b 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap @@ -10,7 +10,7 @@ AIR302_smtp.py:5:1: AIR302 `airflow.operators.email_operator.EmailOperator` is m 6 | 7 | from airflow.operators.email import EmailOperator | - = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `airflow.providers.smtp.operators.smtp.EmailOperator` instead. + = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `EmailOperator` from `airflow.providers.smtp.operators.smtp` instead. AIR302_smtp.py:9:1: AIR302 `airflow.operators.email.EmailOperator` is moved into `smtp` provider in Airflow 3.0; | @@ -19,4 +19,4 @@ AIR302_smtp.py:9:1: AIR302 `airflow.operators.email.EmailOperator` is moved into 9 | EmailOperator() | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `airflow.providers.smtp.operators.smtp.EmailOperator` instead. + = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `EmailOperator` from `airflow.providers.smtp.operators.smtp` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap index 6b3b7dcbc4570b..c7f759ded27735 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap @@ -8,4 +8,4 @@ AIR302_sqlite.py:5:1: AIR302 `airflow.hooks.sqlite_hook.SqliteHook` is moved int 5 | SqliteHook() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-sqlite>=1.0.0` and use `airflow.providers.sqlite.hooks.sqlite.SqliteHook` instead. + = help: Install `apache-airflow-providers-sqlite>=1.0.0` and use `SqliteHook` from `airflow.providers.sqlite.hooks.sqlite` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap index 59eb6df5628d39..c5be3bce31105f 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap @@ -10,7 +10,7 @@ AIR302_standard.py:25:1: AIR302 `airflow.operators.bash_operator.BashOperator` i 26 | 27 | TriggerDagRunLink() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.bash.BashOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BashOperator` from `airflow.providers.standard.operators.bash` instead. AIR302_standard.py:27:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; | @@ -21,7 +21,7 @@ AIR302_standard.py:27:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRun 28 | TriggerDagRunOperator() 29 | DummyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.trigger_dagrun.TriggerDagRunLink` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunLink` from `airflow.providers.standard.operators.trigger_dagrun` instead. AIR302_standard.py:28:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRunOperator` is moved into `standard` provider in Airflow 3.0; | @@ -31,7 +31,7 @@ AIR302_standard.py:28:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRun 29 | DummyOperator() 30 | EmptyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.trigger_dagrun.TriggerDagRunOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunOperator` from `airflow.providers.standard.operators.trigger_dagrun` instead. AIR302_standard.py:29:1: AIR302 `airflow.operators.dummy.DummyOperator` is moved into `standard` provider in Airflow 3.0; | @@ -41,7 +41,7 @@ AIR302_standard.py:29:1: AIR302 `airflow.operators.dummy.DummyOperator` is moved | ^^^^^^^^^^^^^ AIR302 30 | EmptyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.empty.EmptyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. AIR302_standard.py:30:1: AIR302 `airflow.operators.dummy.EmptyOperator` is moved into `standard` provider in Airflow 3.0; | @@ -52,7 +52,7 @@ AIR302_standard.py:30:1: AIR302 `airflow.operators.dummy.EmptyOperator` is moved 31 | 32 | LatestOnlyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.empty.EmptyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. AIR302_standard.py:32:1: AIR302 `airflow.operators.latest_only_operator.LatestOnlyOperator` is moved into `standard` provider in Airflow 3.0; | @@ -63,7 +63,7 @@ AIR302_standard.py:32:1: AIR302 `airflow.operators.latest_only_operator.LatestOn 33 | 34 | BranchPythonOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.operators.latest_only.LatestOnlyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `LatestOnlyOperator` from `airflow.providers.standard.operators.latest_only` instead. AIR302_standard.py:34:1: AIR302 `airflow.operators.python_operator.BranchPythonOperator` is moved into `standard` provider in Airflow 3.0; | @@ -74,7 +74,7 @@ AIR302_standard.py:34:1: AIR302 `airflow.operators.python_operator.BranchPythonO 35 | PythonOperator() 36 | PythonVirtualenvOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.BranchPythonOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchPythonOperator` from `airflow.providers.standard.operators.python` instead. AIR302_standard.py:35:1: AIR302 `airflow.operators.python_operator.PythonOperator` is moved into `standard` provider in Airflow 3.0; | @@ -84,7 +84,7 @@ AIR302_standard.py:35:1: AIR302 `airflow.operators.python_operator.PythonOperato 36 | PythonVirtualenvOperator() 37 | ShortCircuitOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.PythonOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonOperator` from `airflow.providers.standard.operators.python` instead. AIR302_standard.py:36:1: AIR302 `airflow.operators.python_operator.PythonVirtualenvOperator` is moved into `standard` provider in Airflow 3.0; | @@ -94,7 +94,7 @@ AIR302_standard.py:36:1: AIR302 `airflow.operators.python_operator.PythonVirtual | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 37 | ShortCircuitOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.PythonVirtualenvOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonVirtualenvOperator` from `airflow.providers.standard.operators.python` instead. AIR302_standard.py:37:1: AIR302 `airflow.operators.python_operator.ShortCircuitOperator` is moved into `standard` provider in Airflow 3.0; | @@ -105,7 +105,7 @@ AIR302_standard.py:37:1: AIR302 `airflow.operators.python_operator.ShortCircuitO 38 | 39 | ExternalTaskMarker() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.ShortCircuitOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `ShortCircuitOperator` from `airflow.providers.standard.operators.python` instead. AIR302_standard.py:39:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskMarker` is moved into `standard` provider in Airflow 3.0; | @@ -116,7 +116,7 @@ AIR302_standard.py:39:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTa 40 | ExternalTaskSensor() 41 | ExternalTaskSensorLink() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalTaskMarker` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskMarker` from `airflow.providers.standard.sensors.external_task` instead. AIR302_standard.py:40:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskSensor` is moved into `standard` provider in Airflow 3.0; | @@ -125,7 +125,7 @@ AIR302_standard.py:40:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTa | ^^^^^^^^^^^^^^^^^^ AIR302 41 | ExternalTaskSensorLink() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalTaskSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensor` from `airflow.providers.standard.sensors.external_task` instead. AIR302_standard.py:41:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; | @@ -136,7 +136,7 @@ AIR302_standard.py:41:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTa 42 | 43 | from airflow.operators.dummy_operator import ( | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalTaskSensorLink` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensorLink` from `airflow.providers.standard.sensors.external_task` instead. AIR302_standard.py:48:1: AIR302 `airflow.operators.dummy_operator.DummyOperator` is moved into `standard` provider in Airflow 3.0; | @@ -146,7 +146,7 @@ AIR302_standard.py:48:1: AIR302 `airflow.operators.dummy_operator.DummyOperator` | ^^^^^^^^^^^^^ AIR302 49 | EmptyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.empty.EmptyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. AIR302_standard.py:49:1: AIR302 `airflow.operators.dummy_operator.EmptyOperator` is moved into `standard` provider in Airflow 3.0; | @@ -156,7 +156,7 @@ AIR302_standard.py:49:1: AIR302 `airflow.operators.dummy_operator.EmptyOperator` 50 | 51 | from airflow.hooks.subprocess import SubprocessResult | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.empty.EmptyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. AIR302_standard.py:52:1: AIR302 `airflow.hooks.subprocess.SubprocessResult` is moved into `standard` provider in Airflow 3.0; | @@ -166,7 +166,7 @@ AIR302_standard.py:52:1: AIR302 `airflow.hooks.subprocess.SubprocessResult` is m 53 | from airflow.hooks.subprocess import working_directory 54 | working_directory() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.hooks.subprocess.SubprocessResult` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `SubprocessResult` from `airflow.providers.standard.hooks.subprocess` instead. AIR302_standard.py:54:1: AIR302 `airflow.hooks.subprocess.working_directory` is moved into `standard` provider in Airflow 3.0; | @@ -177,7 +177,7 @@ AIR302_standard.py:54:1: AIR302 `airflow.hooks.subprocess.working_directory` is 55 | from airflow.operators.datetime import target_times_as_dates 56 | target_times_as_dates() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.hooks.subprocess.working_directory` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `working_directory` from `airflow.providers.standard.hooks.subprocess` instead. AIR302_standard.py:56:1: AIR302 `airflow.operators.datetime.target_times_as_dates` is moved into `standard` provider in Airflow 3.0; | @@ -188,7 +188,7 @@ AIR302_standard.py:56:1: AIR302 `airflow.operators.datetime.target_times_as_date 57 | from airflow.operators.trigger_dagrun import TriggerDagRunLink 58 | TriggerDagRunLink() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.datetime.target_times_as_dates` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `target_times_as_dates` from `airflow.providers.standard.operators.datetime` instead. AIR302_standard.py:58:1: AIR302 `airflow.operators.trigger_dagrun.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; | @@ -199,7 +199,7 @@ AIR302_standard.py:58:1: AIR302 `airflow.operators.trigger_dagrun.TriggerDagRunL 59 | from airflow.sensors.external_task import ExternalTaskSensorLink 60 | ExternalTaskSensorLink() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.trigger_dagrun.TriggerDagRunLink` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunLink` from `airflow.providers.standard.operators.trigger_dagrun` instead. AIR302_standard.py:60:1: AIR302 [*] `airflow.sensors.external_task.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; | @@ -210,7 +210,7 @@ AIR302_standard.py:60:1: AIR302 [*] `airflow.sensors.external_task.ExternalTaskS 61 | from airflow.sensors.time_delta import WaitSensor 62 | WaitSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalDagLink` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalDagLink` from `airflow.providers.standard.sensors.external_task` instead. ℹ Safe fix 57 57 | from airflow.operators.trigger_dagrun import TriggerDagRunLink @@ -229,4 +229,4 @@ AIR302_standard.py:62:1: AIR302 `airflow.sensors.time_delta.WaitSensor` is moved 62 | WaitSensor() | ^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.time_delta.WaitSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `WaitSensor` from `airflow.providers.standard.sensors.time_delta` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap index 731774ac962e46..8b6488034f21f6 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap @@ -8,4 +8,4 @@ AIR302_zendesk.py:5:1: AIR302 `airflow.hooks.zendesk_hook.ZendeskHook` is moved 5 | ZendeskHook() | ^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-zendesk>=1.0.0` and use `airflow.providers.zendesk.hooks.zendesk.ZendeskHook` instead. + = help: Install `apache-airflow-providers-zendesk>=1.0.0` and use `ZendeskHook` from `airflow.providers.zendesk.hooks.zendesk` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap index 6c6e72b7a1b3aa..840f84b87c20ea 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap @@ -9,7 +9,7 @@ AIR311_names.py:27:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It 28 | 29 | # airflow.datasets | - = help: Use `airflow.sdk.Asset` instead + = help: Use `Asset` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -32,7 +32,7 @@ AIR311_names.py:30:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflo 31 | DatasetAlias() 32 | DatasetAll() | - = help: Use `airflow.sdk.Asset` instead + = help: Use `Asset` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -59,7 +59,7 @@ AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in A 32 | DatasetAll() 33 | DatasetAny() | - = help: Use `airflow.sdk.AssetAlias` instead + = help: Use `AssetAlias` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -87,7 +87,7 @@ AIR311_names.py:32:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Air 33 | DatasetAny() 34 | Metadata() | - = help: Use `airflow.sdk.AssetAll` instead + = help: Use `AssetAll` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -116,7 +116,7 @@ AIR311_names.py:33:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Air 34 | Metadata() 35 | expand_alias_to_datasets() | - = help: Use `airflow.sdk.AssetAny` instead + = help: Use `AssetAny` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -144,7 +144,7 @@ AIR311_names.py:34:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in | ^^^^^^^^ AIR311 35 | expand_alias_to_datasets() | - = help: Use `airflow.sdk.Metadata` instead + = help: Use `Metadata` from `airflow.sdk` instead. AIR311_names.py:35:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -155,7 +155,7 @@ AIR311_names.py:35:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is 36 | 37 | # airflow.decorators | - = help: Use `airflow.sdk.expand_alias_to_assets` instead + = help: Use `expand_alias_to_assets` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -183,7 +183,7 @@ AIR311_names.py:38:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; 39 | task() 40 | task_group() | - = help: Use `airflow.sdk.dag` instead + = help: Use `dag` from `airflow.sdk` instead. AIR311_names.py:39:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -194,7 +194,7 @@ AIR311_names.py:39:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0 40 | task_group() 41 | setup() | - = help: Use `airflow.sdk.task` instead + = help: Use `task` from `airflow.sdk` instead. AIR311_names.py:40:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -205,7 +205,7 @@ AIR311_names.py:40:1: AIR311 `airflow.decorators.task_group` is removed in Airfl 41 | setup() 42 | teardown() | - = help: Use `airflow.sdk.task_group` instead + = help: Use `task_group` from `airflow.sdk` instead. AIR311_names.py:41:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -215,7 +215,7 @@ AIR311_names.py:41:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3. | ^^^^^ AIR311 42 | teardown() | - = help: Use `airflow.sdk.setup` instead + = help: Use `setup` from `airflow.sdk` instead. AIR311_names.py:42:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -226,7 +226,7 @@ AIR311_names.py:42:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 43 | 44 | # airflow.io | - = help: Use `airflow.sdk.teardown` instead + = help: Use `teardown` from `airflow.sdk` instead. AIR311_names.py:45:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -235,7 +235,7 @@ AIR311_names.py:45:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in A | ^^^^^^^^^^^^^^^^^ AIR311 46 | attach() | - = help: Use `airflow.sdk.ObjectStoragePath` instead + = help: Use `ObjectStoragePath` from `airflow.sdk` instead. AIR311_names.py:46:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -246,7 +246,7 @@ AIR311_names.py:46:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3 47 | 48 | # airflow.models | - = help: Use `airflow.sdk.io.attach` instead + = help: Use `attach` from `airflow.sdk.io` instead. AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -256,7 +256,7 @@ AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3 50 | DAGFromModel() 51 | Variable() | - = help: Use `airflow.sdk.Connection` instead + = help: Use `Connection` from `airflow.sdk` instead. AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -266,7 +266,7 @@ AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; | ^^^^^^^^^^^^ AIR311 51 | Variable() | - = help: Use `airflow.sdk.DAG` instead + = help: Use `DAG` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -295,7 +295,7 @@ AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0 52 | 53 | # airflow.models.baseoperator | - = help: Use `airflow.sdk.Variable` instead + = help: Use `Variable` from `airflow.sdk` instead. AIR311_names.py:54:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -305,7 +305,7 @@ AIR311_names.py:54:1: AIR311 `airflow.models.baseoperator.chain` is removed in A 55 | chain_linear() 56 | cross_downstream() | - = help: Use `airflow.sdk.chain` instead + = help: Use `chain` from `airflow.sdk` instead. AIR311_names.py:55:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -315,7 +315,7 @@ AIR311_names.py:55:1: AIR311 `airflow.models.baseoperator.chain_linear` is remov | ^^^^^^^^^^^^ AIR311 56 | cross_downstream() | - = help: Use `airflow.sdk.chain_linear` instead + = help: Use `chain_linear` from `airflow.sdk` instead. AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -326,7 +326,7 @@ AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is r 57 | 58 | # airflow.models.baseoperatolinker | - = help: Use `airflow.sdk.cross_downstream` instead + = help: Use `cross_downstream` from `airflow.sdk` instead. AIR311_names.py:59:1: AIR311 `airflow.models.baseoperatorlink.BaseOperatorLink` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -336,7 +336,7 @@ AIR311_names.py:59:1: AIR311 `airflow.models.baseoperatorlink.BaseOperatorLink` 60 | 61 | # airflow.models.dag | - = help: Use `airflow.sdk.definitions.baseoperatorlink.BaseOperatorLink` instead + = help: Use `BaseOperatorLink` from `airflow.sdk.definitions.baseoperatorlink` instead. AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -346,7 +346,7 @@ AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 63 | # airflow.timetables.datasets 64 | DatasetOrTimeSchedule() | - = help: Use `airflow.sdk.DAG` instead + = help: Use `DAG` from `airflow.sdk` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -375,7 +375,7 @@ AIR311_names.py:64:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSched 65 | 66 | # airflow.utils.dag_parsing_context | - = help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead + = help: Use `AssetOrTimeSchedule` from `airflow.timetables.assets` instead. ℹ Safe fix 22 22 | from airflow.models.dag import DAG as DAGFromDag @@ -401,4 +401,4 @@ AIR311_names.py:67:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_cont 67 | get_parsing_context() | ^^^^^^^^^^^^^^^^^^^ AIR311 | - = help: Use `airflow.sdk.get_parsing_context` instead + = help: Use `get_parsing_context` from `airflow.sdk` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap index 6c9523a3c0e13c..f30928c6c35193 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap @@ -10,7 +10,7 @@ AIR312.py:32:1: AIR312 `airflow.hooks.filesystem.FSHook` is deprecated and moved 33 | PackageIndexHook() 34 | SubprocessHook() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.hooks.filesystem.FSHook` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `FSHook` from `airflow.providers.standard.hooks.filesystem` instead. AIR312.py:33:1: AIR312 `airflow.hooks.package_index.PackageIndexHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -20,7 +20,7 @@ AIR312.py:33:1: AIR312 `airflow.hooks.package_index.PackageIndexHook` is depreca 34 | SubprocessHook() 35 | BashOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.hooks.package_index.PackageIndexHook` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PackageIndexHook` from `airflow.providers.standard.hooks.package_index` instead. AIR312.py:34:1: AIR312 `airflow.hooks.subprocess.SubprocessHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -31,7 +31,7 @@ AIR312.py:34:1: AIR312 `airflow.hooks.subprocess.SubprocessHook` is deprecated a 35 | BashOperator() 36 | BranchDateTimeOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.hooks.subprocess.SubprocessHook` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `SubprocessHook` from `airflow.providers.standard.hooks.subprocess` instead. AIR312.py:35:1: AIR312 `airflow.operators.bash.BashOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -42,7 +42,7 @@ AIR312.py:35:1: AIR312 `airflow.operators.bash.BashOperator` is deprecated and m 36 | BranchDateTimeOperator() 37 | TriggerDagRunOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.bash.BashOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BashOperator` from `airflow.providers.standard.operators.bash` instead. AIR312.py:36:1: AIR312 `airflow.operators.datetime.BranchDateTimeOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -53,7 +53,7 @@ AIR312.py:36:1: AIR312 `airflow.operators.datetime.BranchDateTimeOperator` is de 37 | TriggerDagRunOperator() 38 | EmptyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.datetime.BranchDateTimeOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchDateTimeOperator` from `airflow.providers.standard.operators.datetime` instead. AIR312.py:37:1: AIR312 `airflow.operators.trigger_dagrun.TriggerDagRunOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -64,7 +64,7 @@ AIR312.py:37:1: AIR312 `airflow.operators.trigger_dagrun.TriggerDagRunOperator` 38 | EmptyOperator() 39 | LatestOnlyOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.trigger_dagrun.TriggerDagRunOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunOperator` from `airflow.providers.standard.operators.trigger_dagrun` instead. AIR312.py:38:1: AIR312 `airflow.operators.empty.EmptyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -75,7 +75,7 @@ AIR312.py:38:1: AIR312 `airflow.operators.empty.EmptyOperator` is deprecated and 39 | LatestOnlyOperator() 40 | ( | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.operators.empty.EmptyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. AIR312.py:39:1: AIR312 `airflow.operators.latest_only.LatestOnlyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -86,7 +86,7 @@ AIR312.py:39:1: AIR312 `airflow.operators.latest_only.LatestOnlyOperator` is dep 40 | ( 41 | BranchPythonOperator(), | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.operators.latest_only.LatestOnlyOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `LatestOnlyOperator` from `airflow.providers.standard.operators.latest_only` instead. AIR312.py:41:5: AIR312 `airflow.operators.python.BranchPythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -97,7 +97,7 @@ AIR312.py:41:5: AIR312 `airflow.operators.python.BranchPythonOperator` is deprec 42 | PythonOperator(), 43 | PythonVirtualenvOperator(), | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.BranchPythonOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchPythonOperator` from `airflow.providers.standard.operators.python` instead. AIR312.py:42:5: AIR312 `airflow.operators.python.PythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -108,7 +108,7 @@ AIR312.py:42:5: AIR312 `airflow.operators.python.PythonOperator` is deprecated a 43 | PythonVirtualenvOperator(), 44 | ShortCircuitOperator(), | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.PythonOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonOperator` from `airflow.providers.standard.operators.python` instead. AIR312.py:43:5: AIR312 `airflow.operators.python.PythonVirtualenvOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -119,7 +119,7 @@ AIR312.py:43:5: AIR312 `airflow.operators.python.PythonVirtualenvOperator` is de 44 | ShortCircuitOperator(), 45 | ) | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.PythonVirtualenvOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonVirtualenvOperator` from `airflow.providers.standard.operators.python` instead. AIR312.py:44:5: AIR312 `airflow.operators.python.ShortCircuitOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -130,7 +130,7 @@ AIR312.py:44:5: AIR312 `airflow.operators.python.ShortCircuitOperator` is deprec 45 | ) 46 | BranchDayOfWeekOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.python.ShortCircuitOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `ShortCircuitOperator` from `airflow.providers.standard.operators.python` instead. AIR312.py:46:1: AIR312 `airflow.operators.weekday.BranchDayOfWeekOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -141,7 +141,7 @@ AIR312.py:46:1: AIR312 `airflow.operators.weekday.BranchDayOfWeekOperator` is de 47 | DateTimeSensor(), DateTimeSensorAsync() 48 | ExternalTaskMarker(), ExternalTaskSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.operators.weekday.BranchDayOfWeekOperator` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchDayOfWeekOperator` from `airflow.providers.standard.operators.weekday` instead. AIR312.py:47:1: AIR312 `airflow.sensors.date_time.DateTimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -152,7 +152,7 @@ AIR312.py:47:1: AIR312 `airflow.sensors.date_time.DateTimeSensor` is deprecated 48 | ExternalTaskMarker(), ExternalTaskSensor() 49 | FileSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.date_time.DateTimeSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DateTimeSensor` from `airflow.providers.standard.sensors.date_time` instead. AIR312.py:47:19: AIR312 `airflow.sensors.date_time.DateTimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -163,7 +163,7 @@ AIR312.py:47:19: AIR312 `airflow.sensors.date_time.DateTimeSensorAsync` is depre 48 | ExternalTaskMarker(), ExternalTaskSensor() 49 | FileSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.date_time.DateTimeSensorAsync` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DateTimeSensorAsync` from `airflow.providers.standard.sensors.date_time` instead. AIR312.py:48:1: AIR312 `airflow.sensors.external_task.ExternalTaskMarker` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -174,7 +174,7 @@ AIR312.py:48:1: AIR312 `airflow.sensors.external_task.ExternalTaskMarker` is dep 49 | FileSensor() 50 | TimeSensor(), TimeSensorAsync() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalTaskMarker` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskMarker` from `airflow.providers.standard.sensors.external_task` instead. AIR312.py:48:23: AIR312 `airflow.sensors.external_task.ExternalTaskSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -185,7 +185,7 @@ AIR312.py:48:23: AIR312 `airflow.sensors.external_task.ExternalTaskSensor` is de 49 | FileSensor() 50 | TimeSensor(), TimeSensorAsync() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.sensors.external_task.ExternalTaskSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensor` from `airflow.providers.standard.sensors.external_task` instead. AIR312.py:49:1: AIR312 `airflow.sensors.filesystem.FileSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -196,7 +196,7 @@ AIR312.py:49:1: AIR312 `airflow.sensors.filesystem.FileSensor` is deprecated and 50 | TimeSensor(), TimeSensorAsync() 51 | TimeDeltaSensor(), TimeDeltaSensorAsync() | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `airflow.providers.standard.sensors.filesystem.FileSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `FileSensor` from `airflow.providers.standard.sensors.filesystem` instead. AIR312.py:50:1: AIR312 `airflow.sensors.time_sensor.TimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -207,7 +207,7 @@ AIR312.py:50:1: AIR312 `airflow.sensors.time_sensor.TimeSensor` is deprecated an 51 | TimeDeltaSensor(), TimeDeltaSensorAsync() 52 | DayOfWeekSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.time.TimeSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeSensor` from `airflow.providers.standard.sensors.time` instead. AIR312.py:50:15: AIR312 `airflow.sensors.time_sensor.TimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -218,7 +218,7 @@ AIR312.py:50:15: AIR312 `airflow.sensors.time_sensor.TimeSensorAsync` is depreca 51 | TimeDeltaSensor(), TimeDeltaSensorAsync() 52 | DayOfWeekSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.time.TimeSensorAsync` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeSensorAsync` from `airflow.providers.standard.sensors.time` instead. AIR312.py:51:1: AIR312 `airflow.sensors.time_delta.TimeDeltaSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -229,7 +229,7 @@ AIR312.py:51:1: AIR312 `airflow.sensors.time_delta.TimeDeltaSensor` is deprecate 52 | DayOfWeekSensor() 53 | DagStateTrigger(), WorkflowTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.time_delta.TimeDeltaSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeDeltaSensor` from `airflow.providers.standard.sensors.time_delta` instead. AIR312.py:51:20: AIR312 `airflow.sensors.time_delta.TimeDeltaSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -240,7 +240,7 @@ AIR312.py:51:20: AIR312 `airflow.sensors.time_delta.TimeDeltaSensorAsync` is dep 52 | DayOfWeekSensor() 53 | DagStateTrigger(), WorkflowTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.time_delta.TimeDeltaSensorAsync` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeDeltaSensorAsync` from `airflow.providers.standard.sensors.time_delta` instead. AIR312.py:52:1: AIR312 `airflow.sensors.weekday.DayOfWeekSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -251,7 +251,7 @@ AIR312.py:52:1: AIR312 `airflow.sensors.weekday.DayOfWeekSensor` is deprecated a 53 | DagStateTrigger(), WorkflowTrigger() 54 | FileTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `airflow.providers.standard.sensors.weekday.DayOfWeekSensor` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DayOfWeekSensor` from `airflow.providers.standard.sensors.weekday` instead. AIR312.py:53:1: AIR312 `airflow.triggers.external_task.DagStateTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -262,7 +262,7 @@ AIR312.py:53:1: AIR312 `airflow.triggers.external_task.DagStateTrigger` is depre 54 | FileTrigger() 55 | DateTimeTrigger(), TimeDeltaTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.triggers.external_task.DagStateTrigger` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `DagStateTrigger` from `airflow.providers.standard.triggers.external_task` instead. AIR312.py:53:20: AIR312 `airflow.triggers.external_task.WorkflowTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -273,7 +273,7 @@ AIR312.py:53:20: AIR312 `airflow.triggers.external_task.WorkflowTrigger` is depr 54 | FileTrigger() 55 | DateTimeTrigger(), TimeDeltaTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.triggers.external_task.WorkflowTrigger` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `WorkflowTrigger` from `airflow.providers.standard.triggers.external_task` instead. AIR312.py:54:1: AIR312 `airflow.triggers.file.FileTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -283,7 +283,7 @@ AIR312.py:54:1: AIR312 `airflow.triggers.file.FileTrigger` is deprecated and mov | ^^^^^^^^^^^ AIR312 55 | DateTimeTrigger(), TimeDeltaTrigger() | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.triggers.file.FileTrigger` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `FileTrigger` from `airflow.providers.standard.triggers.file` instead. AIR312.py:55:1: AIR312 `airflow.triggers.temporal.DateTimeTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -292,7 +292,7 @@ AIR312.py:55:1: AIR312 `airflow.triggers.temporal.DateTimeTrigger` is deprecated 55 | DateTimeTrigger(), TimeDeltaTrigger() | ^^^^^^^^^^^^^^^ AIR312 | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.triggers.temporal.DateTimeTrigger` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `DateTimeTrigger` from `airflow.providers.standard.triggers.temporal` instead. AIR312.py:55:20: AIR312 `airflow.triggers.temporal.TimeDeltaTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | @@ -301,4 +301,4 @@ AIR312.py:55:20: AIR312 `airflow.triggers.temporal.TimeDeltaTrigger` is deprecat 55 | DateTimeTrigger(), TimeDeltaTrigger() | ^^^^^^^^^^^^^^^^ AIR312 | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `airflow.providers.standard.triggers.temporal.TimeDeltaTrigger` instead. + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `TimeDeltaTrigger` from `airflow.providers.standard.triggers.temporal` instead. From fbaf826a9d057af1ff996bd0db1d331b82684eb1 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 26 May 2025 05:33:51 -0400 Subject: [PATCH 232/487] Only enable `js` feature of `uuid` crate for wasm crates (#18152) --- Cargo.lock | 2 ++ Cargo.toml | 3 +-- crates/ruff_wasm/Cargo.toml | 2 ++ crates/ty_wasm/Cargo.toml | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44e8e2bf51ea48..bab299de643b44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3081,6 +3081,7 @@ dependencies = [ "ruff_workspace", "serde", "serde-wasm-bindgen", + "uuid", "wasm-bindgen", "wasm-bindgen-test", ] @@ -4071,6 +4072,7 @@ dependencies = [ "ty_ide", "ty_project", "ty_python_semantic", + "uuid", "wasm-bindgen", "wasm-bindgen-test", ] diff --git a/Cargo.toml b/Cargo.toml index f2a934387571a8..80f7825dc03484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,6 @@ uuid = { version = "1.6.1", features = [ "v4", "fast-rng", "macro-diagnostics", - "js", ] } walkdir = { version = "2.3.2" } wasm-bindgen = { version = "0.2.92" } @@ -188,7 +187,7 @@ wild = { version = "2" } zip = { version = "0.6.6", default-features = false } [workspace.metadata.cargo-shear] -ignored = ["getrandom", "ruff_options_metadata"] +ignored = ["getrandom", "ruff_options_metadata", "uuid"] [workspace.lints.rust] diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index b0c8fff39f416a..4325835cc688ae 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -41,6 +41,8 @@ getrandom = { workspace = true, features = ["wasm_js"] } serde = { workspace = true } serde-wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true } +# Not a direct dependency but required to compile for Wasm. +uuid = { workspace = true, features = ["js"] } [dev-dependencies] wasm-bindgen-test = { workspace = true } diff --git a/crates/ty_wasm/Cargo.toml b/crates/ty_wasm/Cargo.toml index 3c96fa77e812b2..42dfb6611801b0 100644 --- a/crates/ty_wasm/Cargo.toml +++ b/crates/ty_wasm/Cargo.toml @@ -40,6 +40,8 @@ log = { workspace = true } # See https://docs.rs/getrandom/latest/getrandom/#webassembly-support getrandom = { workspace = true, features = ["wasm_js"] } serde-wasm-bindgen = { workspace = true } +# Not a direct dependency but required to compile for Wasm. +uuid = { workspace = true, features = ["js"] } wasm-bindgen = { workspace = true } From 7eca6f96e3aee3ac71df92d5bda206c1b7e1cc29 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 26 May 2025 11:41:03 +0200 Subject: [PATCH 233/487] [ty] Fix attribute writes to unions/intersections including modules (#18313) ## Summary Fix a bug that involved writes to attributes on union/intersection types that included modules as elements. This is a prerequisite to avoid some ecosystem false positives in https://github.com/astral-sh/ruff/pull/18312 ## Test Plan Added regression test --- .../resources/mdtest/attributes.md | 30 ++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 39 +++++++++++-------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index e04c00f7294191..98ce6b5aa9ad42 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1698,6 +1698,36 @@ reveal_type(outer.nested.inner.Outer.Nested.Inner.attr) # revealed: int outer.nested.inner.Outer.Nested.Inner.attr = "a" ``` +### Unions of module attributes + +`mod1.py`: + +```py +global_symbol: str = "a" +``` + +`mod2.py`: + +```py +global_symbol: str = "a" +``` + +```py +import mod1 +import mod2 + +def _(flag: bool): + if flag: + mod = mod1 + else: + mod = mod2 + + mod.global_symbol = "b" + + # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `global_symbol` on type ` | `" + mod.global_symbol = 1 +``` + ## Literal types ### Function-literal attributes diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 9f3f48974525d8..614bfbb540dbed 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -3406,24 +3406,31 @@ impl<'db> TypeInferenceBuilder<'db> { Type::ModuleLiteral(module) => { if let Symbol::Type(attr_ty, _) = module.static_member(db, attribute) { let assignable = value_ty.is_assignable_to(db, attr_ty); - if !assignable { - report_invalid_attribute_assignment( - &self.context, - target.into(), - attr_ty, - value_ty, - attribute, - ); + if assignable { + true + } else { + if emit_diagnostics { + report_invalid_attribute_assignment( + &self.context, + target.into(), + attr_ty, + value_ty, + attribute, + ); + } + false } - - false } else { - if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) { - builder.into_diagnostic(format_args!( - "Unresolved attribute `{}` on type `{}`.", - attribute, - object_ty.display(db) - )); + if emit_diagnostics { + if let Some(builder) = + self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) + { + builder.into_diagnostic(format_args!( + "Unresolved attribute `{}` on type `{}`.", + attribute, + object_ty.display(db) + )); + } } false From d078ecff379b3303909df096534307d871e6aab0 Mon Sep 17 00:00:00 2001 From: Vasanth <76586907+Vasanth-96@users.noreply.github.com> Date: Mon, 26 May 2025 15:23:03 +0530 Subject: [PATCH 234/487] =?UTF-8?q?[flake8=5Fasync]=20Refactor=20argument?= =?UTF-8?q?=20name=20resolution=20for=20async=20sleep=20func=E2=80=A6=20(#?= =?UTF-8?q?18262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vasanth-96 Co-authored-by: Micha Reiser --- .../test/fixtures/flake8_async/ASYNC115.py | 20 +++++++++ .../test/fixtures/flake8_async/ASYNC116.py | 20 +++++++++ .../flake8_async/rules/async_zero_sleep.rs | 20 ++++++++- .../rules/long_sleep_not_forever.rs | 20 ++++++++- ...e8_async__tests__ASYNC115_ASYNC115.py.snap | 38 +++++++++++++++++ ...e8_async__tests__ASYNC116_ASYNC116.py.snap | 41 +++++++++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py index 235a64f053674c..b15b028048e108 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py @@ -145,3 +145,23 @@ async def main() -> None: sleep = 10 anyio.run(main) + + +async def test_anyio_async115_helpers(): + import anyio + + await anyio.sleep(delay=1) # OK + await anyio.sleep(seconds=1) # OK + + await anyio.sleep(delay=0) # ASYNC115 + await anyio.sleep(seconds=0) # OK + + +async def test_trio_async115_helpers(): + import trio + + await trio.sleep(seconds=1) # OK + await trio.sleep(delay=1) # OK + + await trio.sleep(seconds=0) # ASYNC115 + await trio.sleep(delay=0) # OK diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC116.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC116.py index 5cfab2eae142bf..4141f64e8d1842 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC116.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC116.py @@ -108,3 +108,23 @@ async def import_from_anyio(): # catch from import await sleep(86401) # error: 116, "async" + + +async def test_anyio_async116_helpers(): + import anyio + + await anyio.sleep(delay=1) # OK + await anyio.sleep(seconds=1) # OK + + await anyio.sleep(delay=86401) # ASYNC116 + await anyio.sleep(seconds=86401) # OK + + +async def test_trio_async116_helpers(): + import trio + + await trio.sleep(seconds=1) # OK + await trio.sleep(delay=1) # OK + + await trio.sleep(seconds=86401) # ASYNC116 + await trio.sleep(delay=86401) # OK diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index fa2ad2d3e05733..87a6ef841a2dae 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -62,7 +62,25 @@ pub(crate) fn async_zero_sleep(checker: &Checker, call: &ExprCall) { return; } - let Some(arg) = call.arguments.find_argument_value("seconds", 0) else { + let Some(qualified_name) = checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + else { + return; + }; + + let Some(module) = AsyncModule::try_from(&qualified_name) else { + return; + }; + + // Determine the correct argument name + let arg_name = match module { + AsyncModule::Trio => "seconds", + AsyncModule::AnyIo => "delay", + AsyncModule::AsyncIo => return, + }; + + let Some(arg) = call.arguments.find_argument_value(arg_name, 0) else { return; }; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs index 2d51087f2f84b6..3e31062672aa6c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs @@ -71,7 +71,25 @@ pub(crate) fn long_sleep_not_forever(checker: &Checker, call: &ExprCall) { return; } - let Some(arg) = call.arguments.find_argument_value("seconds", 0) else { + let Some(qualified_name) = checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + else { + return; + }; + + let Some(module) = AsyncModule::try_from(&qualified_name) else { + return; + }; + + // Determine the correct argument name + let arg_name = match module { + AsyncModule::Trio => "seconds", + AsyncModule::AnyIo => "delay", + AsyncModule::AsyncIo => return, + }; + + let Some(arg) = call.arguments.find_argument_value(arg_name, 0) else { return; }; diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap index c2ec9f1dbe25bf..5bd34508d71973 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap @@ -214,3 +214,41 @@ ASYNC115.py:128:15: ASYNC115 [*] Use `anyio.lowlevel.checkpoint()` instead of `a 129 129 | 130 130 | 131 131 | def func(): + +ASYNC115.py:156:11: ASYNC115 [*] Use `anyio.lowlevel.checkpoint()` instead of `anyio.sleep(0)` + | +154 | await anyio.sleep(seconds=1) # OK +155 | +156 | await anyio.sleep(delay=0) # ASYNC115 + | ^^^^^^^^^^^^^^^^^^^^ ASYNC115 +157 | await anyio.sleep(seconds=0) # OK + | + = help: Replace with `anyio.lowlevel.checkpoint()` + +ℹ Safe fix +153 153 | await anyio.sleep(delay=1) # OK +154 154 | await anyio.sleep(seconds=1) # OK +155 155 | +156 |- await anyio.sleep(delay=0) # ASYNC115 + 156 |+ await anyio.lowlevel.checkpoint() # ASYNC115 +157 157 | await anyio.sleep(seconds=0) # OK +158 158 | +159 159 | + +ASYNC115.py:166:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +164 | await trio.sleep(delay=1) # OK +165 | +166 | await trio.sleep(seconds=0) # ASYNC115 + | ^^^^^^^^^^^^^^^^^^^^^ ASYNC115 +167 | await trio.sleep(delay=0) # OK + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +163 163 | await trio.sleep(seconds=1) # OK +164 164 | await trio.sleep(delay=1) # OK +165 165 | +166 |- await trio.sleep(seconds=0) # ASYNC115 + 166 |+ await trio.lowlevel.checkpoint() # ASYNC115 +167 167 | await trio.sleep(delay=0) # OK diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC116_ASYNC116.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC116_ASYNC116.py.snap index 6904caf6291205..3e2e1aead7f384 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC116_ASYNC116.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC116_ASYNC116.py.snap @@ -289,3 +289,44 @@ ASYNC116.py:110:11: ASYNC116 [*] `anyio.sleep()` with >24 hour interval should u 109 110 | # catch from import 110 |- await sleep(86401) # error: 116, "async" 111 |+ await sleep_forever() # error: 116, "async" +111 112 | +112 113 | +113 114 | async def test_anyio_async116_helpers(): + +ASYNC116.py:119:11: ASYNC116 [*] `anyio.sleep()` with >24 hour interval should usually be `anyio.sleep_forever()` + | +117 | await anyio.sleep(seconds=1) # OK +118 | +119 | await anyio.sleep(delay=86401) # ASYNC116 + | ^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC116 +120 | await anyio.sleep(seconds=86401) # OK + | + = help: Replace with `anyio.sleep_forever()` + +ℹ Unsafe fix +116 116 | await anyio.sleep(delay=1) # OK +117 117 | await anyio.sleep(seconds=1) # OK +118 118 | +119 |- await anyio.sleep(delay=86401) # ASYNC116 + 119 |+ await anyio.sleep_forever() # ASYNC116 +120 120 | await anyio.sleep(seconds=86401) # OK +121 121 | +122 122 | + +ASYNC116.py:129:11: ASYNC116 [*] `trio.sleep()` with >24 hour interval should usually be `trio.sleep_forever()` + | +127 | await trio.sleep(delay=1) # OK +128 | +129 | await trio.sleep(seconds=86401) # ASYNC116 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC116 +130 | await trio.sleep(delay=86401) # OK + | + = help: Replace with `trio.sleep_forever()` + +ℹ Unsafe fix +126 126 | await trio.sleep(seconds=1) # OK +127 127 | await trio.sleep(delay=1) # OK +128 128 | +129 |- await trio.sleep(seconds=86401) # ASYNC116 + 129 |+ await trio.sleep_forever() # ASYNC116 +130 130 | await trio.sleep(delay=86401) # OK From 4ef2c223c9231d8b8a7091b8721215788bb9cc1b Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 26 May 2025 12:03:29 +0200 Subject: [PATCH 235/487] [ty] Respect `MRO_NO_OBJECT_FALLBACK` policy when looking up symbols on `type` instances (#18312) ## Summary This should address a problem that came up while working on https://github.com/astral-sh/ruff/pull/18280. When looking up an attribute (typically a dunder method) with the `MRO_NO_OBJECT_FALLBACK` policy, the attribute is first looked up on the meta type. If the meta type happens to be `type`, we go through the following branch in `find_name_in_mro_with_policy`: https://github.com/astral-sh/ruff/blob/97ff015c88354b5d45b52a9b1460c30978b5c29b/crates/ty_python_semantic/src/types.rs#L2565-L2573 The problem is that we now look up the attribute on `object` *directly* (instead of just having `object` in the MRO). In this case, `MRO_NO_OBJECT_FALLBACK` has no effect in `class_member_from_mro`: https://github.com/astral-sh/ruff/blob/c3feb8ce27350a4c7a560671f4668dd47761260e/crates/ty_python_semantic/src/types/class.rs#L1081-L1082 So instead, we need to explicitly respect the `MRO_NO_OBJECT_FALLBACK` policy here by returning `Symbol::Unbound`. ## Test Plan Added new Markdown tests that explain the ecosystem changes that we observe. --- .../resources/mdtest/annotations/callable.md | 33 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 10 ++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index dc27fdb7d4f742..a819d2c63038b2 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -305,4 +305,37 @@ def _(c: Callable[[int], int]): reveal_type(c.__call__) # revealed: (int, /) -> int ``` +Unlike other type checkers, we do _not_ allow attributes to be accessed that would only be available +on function-like callables: + +```py +def f_wrong(c: Callable[[], None]): + # error: [unresolved-attribute] "Type `() -> None` has no attribute `__qualname__`" + c.__qualname__ + + # error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`." + c.__qualname__ = "my_callable" +``` + +We do this, because at runtime, calls to `f_wrong` with a non-function callable would raise an +`AttributeError`: + +```py +class MyCallable: + def __call__(self) -> None: + pass + +f_wrong(MyCallable()) # raises `AttributeError` at runtime +``` + +If users want to write to attributes such as `__qualname__`, they need to check the existence of the +attribute first: + +```py +def f_okay(c: Callable[[], None]): + if hasattr(c, "__qualname__"): + c.__qualname__ # okay + c.__qualname__ = "my_callable" # also okay +``` + [gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c21144b719c754..25c8be75fb0065 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2567,9 +2567,13 @@ impl<'db> Type<'db> { // `Type::NominalInstance(type)` is equivalent to looking up the name in the // MRO of the class `object`. Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => { - KnownClass::Object - .to_class_literal(db) - .find_name_in_mro_with_policy(db, name, policy) + if policy.mro_no_object_fallback() { + Some(Symbol::Unbound.into()) + } else { + KnownClass::Object + .to_class_literal(db) + .find_name_in_mro_with_policy(db, name, policy) + } } Type::FunctionLiteral(_) From f885cb8a2fbf1f6cf778328f18d3715eb56a97a0 Mon Sep 17 00:00:00 2001 From: Felix Scherz Date: Mon, 26 May 2025 12:59:45 +0200 Subject: [PATCH 236/487] [ty] use `__getattribute__` to lookup unknown members on a type (#18280) ## Summary `Type::member_lookup_with_policy` now falls back to calling `__getattribute__` when a member cannot be found as a second fallback after `__getattr__`. closes https://github.com/astral-sh/ty/issues/441 ## Test Plan Added markdown tests. --------- Co-authored-by: Alex Waygood Co-authored-by: David Peter --- .../resources/mdtest/attributes.md | 59 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 29 ++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 98ce6b5aa9ad42..b1a1573ce48082 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1527,6 +1527,65 @@ def _(ns: argparse.Namespace): reveal_type(ns.whatever) # revealed: Any ``` +## Classes with custom `__getattribute__` methods + +If a type provides a custom `__getattribute__`, we use its return type as the type for unknown +attributes. Note that this behavior differs from runtime, where `__getattribute__` is called +unconditionally, even for known attributes. The rationale for doing this is that it allows users to +specify more precise types for specific attributes, such as `x: str` in the example below. This +behavior matches other type checkers such as mypy and pyright. + +```py +from typing import Any + +class Foo: + x: str + def __getattribute__(self, attr: str) -> Any: + return 42 + +reveal_type(Foo().x) # revealed: str +reveal_type(Foo().y) # revealed: Any +``` + +A standard library example for a class with a custom `__getattribute__` method is `SimpleNamespace`: + +```py +from types import SimpleNamespace + +sn = SimpleNamespace(a="a") + +reveal_type(sn.a) # revealed: Any +``` + +`__getattribute__` takes precedence over `__getattr__`: + +```py +class C: + def __getattribute__(self, name: str) -> int: + return 1 + + def __getattr__(self, name: str) -> str: + return "a" + +c = C() + +reveal_type(c.x) # revealed: int +``` + +Like all dunder methods, `__getattribute__` is not looked up on instances: + +```py +def external_getattribute(name) -> int: + return 1 + +class ThisFails: + def __init__(self): + self.__getattribute__ = external_getattribute + +# error: [unresolved-attribute] +ThisFails().x +``` + ## Classes with custom `__setattr__` methods ### Basic diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 25c8be75fb0065..e1a8f9debbab7b 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3200,6 +3200,29 @@ impl<'db> Type<'db> { .into() }; + let custom_getattribute_result = || { + // Avoid cycles when looking up `__getattribute__` + if "__getattribute__" == name.as_str() { + return Symbol::Unbound.into(); + } + + // Typeshed has a `__getattribute__` method defined on `builtins.object` so we + // explicitly hide it here using `MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK`. + self.try_call_dunder_with_policy( + db, + "__getattribute__", + &mut CallArgumentTypes::positional([Type::StringLiteral( + StringLiteralType::new(db, Box::from(name.as_str())), + )]), + MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK + | MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) + .map(|outcome| Symbol::bound(outcome.return_type(db))) + // TODO: Handle call errors here. + .unwrap_or(Symbol::Unbound) + .into() + }; + match result { member @ SymbolAndQualifiers { symbol: Symbol::Type(_, Boundness::Bound), @@ -3208,11 +3231,13 @@ impl<'db> Type<'db> { member @ SymbolAndQualifiers { symbol: Symbol::Type(_, Boundness::PossiblyUnbound), qualifiers: _, - } => member.or_fall_back_to(db, custom_getattr_result), + } => member + .or_fall_back_to(db, custom_getattribute_result) + .or_fall_back_to(db, custom_getattr_result), SymbolAndQualifiers { symbol: Symbol::Unbound, qualifiers: _, - } => custom_getattr_result(), + } => custom_getattribute_result().or_fall_back_to(db, custom_getattr_result), } } From e1b662bf5d5d38463e5303e30ef65c8d8682aa0c Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 26 May 2025 13:20:27 +0200 Subject: [PATCH 237/487] [ty] Always pass `NO_INSTANCE_FALLBACK` in `try_call_dunder_with_policy` (#18315) ## Summary The previous `try_call_dunder_with_policy` API was a bit of a footgun since you needed to pass `NO_INSTANCE_FALLBACK` in *addition* to other policies that you wanted for the member lookup. Implicit calls to dunder methods never access instance members though, so we can do this implicitly in `try_call_dunder_with_policy`. No functional changes. --- crates/ty_python_semantic/src/types.rs | 16 ++++++++++++---- crates/ty_python_semantic/src/types/infer.rs | 3 +-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index e1a8f9debbab7b..fcab31b7a7ff79 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3214,8 +3214,7 @@ impl<'db> Type<'db> { &mut CallArgumentTypes::positional([Type::StringLiteral( StringLiteralType::new(db, Box::from(name.as_str())), )]), - MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK - | MemberLookupPolicy::NO_INSTANCE_FALLBACK, + MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) .map(|outcome| Symbol::bound(outcome.return_type(db))) // TODO: Handle call errors here. @@ -4415,7 +4414,7 @@ impl<'db> Type<'db> { db, name, &mut argument_types, - MemberLookupPolicy::NO_INSTANCE_FALLBACK, + MemberLookupPolicy::default(), ) } @@ -4423,6 +4422,9 @@ impl<'db> Type<'db> { /// particular, this allows to specify `MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK` to avoid /// looking up dunder methods on `object`, which is needed for functions like `__init__`, /// `__new__`, or `__setattr__`. + /// + /// Note that `NO_INSTANCE_FALLBACK` is always added to the policy, since implicit calls to + /// dunder methods never access instance members. fn try_call_dunder_with_policy( self, db: &'db dyn Db, @@ -4430,8 +4432,14 @@ impl<'db> Type<'db> { argument_types: &mut CallArgumentTypes<'_, 'db>, policy: MemberLookupPolicy, ) -> Result, CallDunderError<'db>> { + // Implicit calls to dunder methods never access instance members, so we pass + // `NO_INSTANCE_FALLBACK` here in addition to other policies: match self - .member_lookup_with_policy(db, name.into(), policy) + .member_lookup_with_policy( + db, + name.into(), + policy | MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) .symbol { Symbol::Type(dunder_callable, boundness) => { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 614bfbb540dbed..bddbe9f8363745 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -3231,8 +3231,7 @@ impl<'db> TypeInferenceBuilder<'db> { )), value_ty, ]), - MemberLookupPolicy::NO_INSTANCE_FALLBACK - | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, + MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ); match result { From 5d93d619f37c89e7827b2ea7beb52e17be60323c Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 26 May 2025 13:55:11 +0200 Subject: [PATCH 238/487] Use git-commit as ty playground version instead of 0.0.0 (#18314) --- crates/ty_wasm/build.rs | 90 +++++++++++++++++++++++++++ crates/ty_wasm/src/lib.rs | 8 +++ playground/ruff/src/Editor/Chrome.tsx | 2 +- playground/shared/src/Header.tsx | 2 +- playground/shared/src/VersionTag.tsx | 1 + playground/ty/src/Playground.tsx | 5 +- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 crates/ty_wasm/build.rs diff --git a/crates/ty_wasm/build.rs b/crates/ty_wasm/build.rs new file mode 100644 index 00000000000000..5355a7c669d389 --- /dev/null +++ b/crates/ty_wasm/build.rs @@ -0,0 +1,90 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +fn main() { + // The workspace root directory is not available without walking up the tree + // https://github.com/rust-lang/cargo/issues/3946 + let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join(".."); + + commit_info(&workspace_root); +} + +/// Retrieve commit information from the Git repository. +fn commit_info(workspace_root: &Path) { + // If not in a git repository, do not attempt to retrieve commit information + let git_dir = workspace_root.join(".git"); + if !git_dir.exists() { + return; + } + + if let Some(git_head_path) = git_head(&git_dir) { + println!("cargo:rerun-if-changed={}", git_head_path.display()); + + let git_head_contents = fs::read_to_string(git_head_path); + if let Ok(git_head_contents) = git_head_contents { + // The contents are either a commit or a reference in the following formats + // - "" when the head is detached + // - "ref " when working on a branch + // If a commit, checking if the HEAD file has changed is sufficient + // If a ref, we need to add the head file for that ref to rebuild on commit + let mut git_ref_parts = git_head_contents.split_whitespace(); + git_ref_parts.next(); + if let Some(git_ref) = git_ref_parts.next() { + let git_ref_path = git_dir.join(git_ref); + println!("cargo:rerun-if-changed={}", git_ref_path.display()); + } + } + } + + let output = match Command::new("git") + .arg("log") + .arg("-1") + .arg("--date=short") + .arg("--abbrev=9") + .arg("--format=%H %h %cd %(describe:tags)") + .current_dir(workspace_root) + .output() + { + Ok(output) if output.status.success() => output, + _ => return, + }; + + let stdout = String::from_utf8(output.stdout).unwrap(); + + let mut parts = stdout.split_whitespace(); + let mut next = || parts.next().unwrap(); + let _commit_hash = next(); + println!("cargo::rustc-env=TY_WASM_COMMIT_SHORT_HASH={}", next()); +} + +fn git_head(git_dir: &Path) -> Option { + // The typical case is a standard git repository. + let git_head_path = git_dir.join("HEAD"); + if git_head_path.exists() { + return Some(git_head_path); + } + if !git_dir.is_file() { + return None; + } + // If `.git/HEAD` doesn't exist and `.git` is actually a file, + // then let's try to attempt to read it as a worktree. If it's + // a worktree, then its contents will look like this, e.g.: + // + // gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2 + // + // And the HEAD file we want to watch will be at: + // + // /home/andrew/astral/uv/main/.git/worktrees/pr2/HEAD + let contents = fs::read_to_string(git_dir).ok()?; + let (label, worktree_path) = contents.split_once(':')?; + if label != "gitdir" { + return None; + } + let worktree_path = worktree_path.trim(); + Some(PathBuf::from(worktree_path)) +} diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 809309edcbfda8..4ce30936e65de7 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -23,6 +23,14 @@ use ty_project::{Db, ProjectDatabase}; use ty_python_semantic::Program; use wasm_bindgen::prelude::*; +#[wasm_bindgen] +pub fn version() -> String { + option_env!("TY_WASM_COMMIT_SHORT_HASH") + .or_else(|| option_env!("CARGO_PKG_VERSION")) + .unwrap_or("unknown") + .to_string() +} + #[wasm_bindgen(start)] pub fn run() { use log::Level; diff --git a/playground/ruff/src/Editor/Chrome.tsx b/playground/ruff/src/Editor/Chrome.tsx index 43d798b0d021b7..3fc9ddca2a997a 100644 --- a/playground/ruff/src/Editor/Chrome.tsx +++ b/playground/ruff/src/Editor/Chrome.tsx @@ -88,7 +88,7 @@ export default function Chrome() { edit={revision} theme={theme} tool="ruff" - version={ruffVersion} + version={`v${ruffVersion}`} onChangeTheme={setTheme} onShare={handleShare} onReset={handleResetClicked} diff --git a/playground/shared/src/Header.tsx b/playground/shared/src/Header.tsx index 694ec20a25c9dc..128f8f0afdfb85 100644 --- a/playground/shared/src/Header.tsx +++ b/playground/shared/src/Header.tsx @@ -42,7 +42,7 @@ export default function Header({
{version ? (
- v{version} + {version}
) : null} diff --git a/playground/shared/src/VersionTag.tsx b/playground/shared/src/VersionTag.tsx index dd80a9fc7cde76..89a7e3c81fca14 100644 --- a/playground/shared/src/VersionTag.tsx +++ b/playground/shared/src/VersionTag.tsx @@ -4,6 +4,7 @@ import { ReactNode } from "react"; export default function VersionTag({ children }: { children: ReactNode }) { return (
("0.0.0"); + const [version, setVersion] = useState(null); const [error, setError] = useState(null); const workspacePromiseRef = useRef | null>(null); const [workspace, setWorkspace] = useState(null); @@ -453,6 +453,7 @@ export interface InitializedPlayground { async function startPlayground(): Promise { const ty = await import("../ty_wasm"); await ty.default(); + const version = ty.version(); const monaco = await loader.init(); setupMonaco(monaco, { @@ -466,7 +467,7 @@ async function startPlayground(): Promise { const workspace = restored ?? DEFAULT_WORKSPACE; return { - version: "0.0.0", + version, workspace, }; } From 66b082ff71979f9cc5e10f9522b4588dc9b452ee Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 26 May 2025 14:09:06 +0200 Subject: [PATCH 239/487] [ty] Abort process if worker thread panics (#18211) --- Cargo.lock | 1 + crates/ty_server/Cargo.toml | 1 + .../src/server/schedule/thread/pool.rs | 35 ++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bab299de643b44..206a24a6a21fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4004,6 +4004,7 @@ dependencies = [ "ruff_source_file", "ruff_text_size", "rustc-hash 2.1.1", + "salsa", "serde", "serde_json", "shellexpand", diff --git a/crates/ty_server/Cargo.toml b/crates/ty_server/Cargo.toml index 0bae064fda3741..0012420f59706d 100644 --- a/crates/ty_server/Cargo.toml +++ b/crates/ty_server/Cargo.toml @@ -26,6 +26,7 @@ jod-thread = { workspace = true } lsp-server = { workspace = true } lsp-types = { workspace = true } rustc-hash = { workspace = true } +salsa = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } shellexpand = { workspace = true } diff --git a/crates/ty_server/src/server/schedule/thread/pool.rs b/crates/ty_server/src/server/schedule/thread/pool.rs index 391676ad5becfc..88abbd8e42e0bc 100644 --- a/crates/ty_server/src/server/schedule/thread/pool.rs +++ b/crates/ty_server/src/server/schedule/thread/pool.rs @@ -13,6 +13,8 @@ //! The thread pool is implemented entirely using //! the threading utilities in [`crate::server::schedule::thread`]. +use crossbeam::channel::{Receiver, Sender}; +use std::panic::AssertUnwindSafe; use std::{ num::NonZeroUsize, sync::{ @@ -21,8 +23,6 @@ use std::{ }, }; -use crossbeam::channel::{Receiver, Sender}; - use super::{Builder, JoinHandle, ThreadPriority}; pub(crate) struct Pool { @@ -51,8 +51,7 @@ impl Pool { let threads = usize::from(threads); - // Channel buffer capacity is between 2 and 4, depending on the pool size. - let (job_sender, job_receiver) = crossbeam::channel::bounded(std::cmp::min(threads * 2, 4)); + let (job_sender, job_receiver) = crossbeam::channel::bounded(std::cmp::max(threads * 2, 4)); let extant_tasks = Arc::new(AtomicUsize::new(0)); let mut handles = Vec::with_capacity(threads); @@ -71,7 +70,33 @@ impl Pool { current_priority = job.requested_priority; } extant_tasks.fetch_add(1, Ordering::SeqCst); - (job.f)(); + + // SAFETY: it's safe to assume that `job.f` is unwind safe because we always + // abort the process if it panics. + // Panicking here ensures that we don't swallow errors and is the same as + // what rayon does. + // Any recovery should be implemented outside the thread pool (e.g. when + // dispatching requests/notifications etc). + if let Err(error) = std::panic::catch_unwind(AssertUnwindSafe(job.f)) { + if let Some(msg) = error.downcast_ref::() { + tracing::error!("Worker thread panicked with: {msg}; aborting"); + } else if let Some(msg) = error.downcast_ref::<&str>() { + tracing::error!("Worker thread panicked with: {msg}; aborting"); + } else if let Some(cancelled) = + error.downcast_ref::() + { + tracing::error!( + "Worker thread got cancelled: {cancelled}; aborting" + ); + } else { + tracing::error!( + "Worker thread panicked with: {error:?}; aborting" + ); + } + + std::process::abort(); + } + extant_tasks.fetch_sub(1, Ordering::SeqCst); } } From d51f6940febc54b69eb865ccef8abeb19721f218 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 26 May 2025 14:14:23 +0200 Subject: [PATCH 240/487] [ty] Playground: Better default settings (#18316) ## Summary The playground default settings set the `division-by-zero` rule severity to `error`. This slightly confusing because `division-by-zero` is now disabled by default. I am assuming that we have a `rules` section in there to make it easier for users to customize those settings (in addition to what the JSON schema gives us). Here, I'm proposing a different default rule-set (`"undefined-reveal": "ignore"`) that I would personally find more helpful for the playground, since we're using it so frequently for MREs that often involve some `reveal_type` calls. --- playground/ty/src/Playground.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/ty/src/Playground.tsx b/playground/ty/src/Playground.tsx index e501ab8c3717df..9c87ff6c3c5b29 100644 --- a/playground/ty/src/Playground.tsx +++ b/playground/ty/src/Playground.tsx @@ -198,7 +198,7 @@ export const DEFAULT_SETTINGS = JSON.stringify( "python-version": "3.13", }, rules: { - "division-by-zero": "error", + "undefined-reveal": "ignore", }, }, null, From d8216fa3282c89b0e5827b8da5f5f2aefd55ad4a Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 26 May 2025 14:37:49 +0200 Subject: [PATCH 241/487] [ty] Gracefully handle salsa cancellations and panics in background request handlers (#18254) --- crates/ruff_benchmark/benches/ty.rs | 10 +- crates/ruff_db/src/panic.rs | 12 +- crates/ty/src/lib.rs | 6 +- crates/ty/tests/file_watching.rs | 14 +- crates/ty_project/src/db.rs | 24 +-- crates/ty_server/src/server.rs | 11 +- crates/ty_server/src/server/api.rs | 152 +++++++++++++----- .../ty_server/src/server/api/diagnostics.rs | 8 +- crates/ty_wasm/src/lib.rs | 4 +- 9 files changed, 146 insertions(+), 95 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index bd5fdaa026b5b0..c36e1202740825 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -131,7 +131,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { fn setup() -> Case { let case = setup_tomllib_case(); - let result: Vec<_> = case.db.check().unwrap(); + let result: Vec<_> = case.db.check(); assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); @@ -159,7 +159,7 @@ fn benchmark_incremental(criterion: &mut Criterion) { None, ); - let result = db.check().unwrap(); + let result = db.check(); assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len()); } @@ -179,7 +179,7 @@ fn benchmark_cold(criterion: &mut Criterion) { setup_tomllib_case, |case| { let Case { db, .. } = case; - let result: Vec<_> = db.check().unwrap(); + let result: Vec<_> = db.check(); assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); }, @@ -293,7 +293,7 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) { }, |case| { let Case { db, .. } = case; - let result = db.check().unwrap(); + let result = db.check(); assert_eq!(result.len(), 0); }, BatchSize::SmallInput, @@ -339,7 +339,7 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) { }, |case| { let Case { db, .. } = case; - let result = db.check().unwrap(); + let result = db.check(); assert_eq!(result.len(), 0); }, BatchSize::SmallInput, diff --git a/crates/ruff_db/src/panic.rs b/crates/ruff_db/src/panic.rs index 67ebf940dc3b94..a6b67f1f1de0b9 100644 --- a/crates/ruff_db/src/panic.rs +++ b/crates/ruff_db/src/panic.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::backtrace::BacktraceStatus; use std::cell::Cell; use std::panic::Location; @@ -24,17 +25,25 @@ impl Payload { None } } + + pub fn downcast_ref(&self) -> Option<&R> { + self.0.downcast_ref::() + } } impl std::fmt::Display for PanicError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "panicked at")?; + write!(f, "panicked at")?; if let Some(location) = &self.location { write!(f, " {location}")?; } if let Some(payload) = self.payload.as_str() { write!(f, ":\n{payload}")?; } + if let Some(query_trace) = self.salsa_backtrace.as_ref() { + let _ = writeln!(f, "{query_trace}"); + } + if let Some(backtrace) = &self.backtrace { match backtrace.status() { BacktraceStatus::Disabled => { @@ -49,6 +58,7 @@ impl std::fmt::Display for PanicError { _ => {} } } + Ok(()) } } diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 443f61602eb368..ec66b60ca57d37 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -243,12 +243,14 @@ impl MainLoop { MainLoopMessage::CheckWorkspace => { let db = db.clone(); let sender = self.sender.clone(); - let mut reporter = R::default(); // Spawn a new task that checks the project. This needs to be done in a separate thread // to prevent blocking the main loop here. rayon::spawn(move || { - match db.check_with_reporter(&mut reporter) { + match salsa::Cancelled::catch(|| { + let mut reporter = R::default(); + db.check_with_reporter(&mut reporter) + }) { Ok(result) => { // Send the result back to the main loop for printing. sender diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index c8f3a186b00401..848117bf515833 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -1135,7 +1135,7 @@ print(sys.last_exc, os.getegid()) Ok(()) })?; - let diagnostics = case.db.check().context("Failed to check project.")?; + let diagnostics = case.db.check(); assert_eq!(diagnostics.len(), 2); assert_eq!( @@ -1160,7 +1160,7 @@ print(sys.last_exc, os.getegid()) }) .expect("Search path settings to be valid"); - let diagnostics = case.db.check().context("Failed to check project.")?; + let diagnostics = case.db.check(); assert!(diagnostics.is_empty()); Ok(()) @@ -1763,10 +1763,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> { let foo = case .system_file(case.project_path("foo.py")) .expect("foo.py to exist"); - let diagnostics = case - .db() - .check_file(foo) - .context("Failed to check project.")?; + let diagnostics = case.db().check_file(foo); assert!( diagnostics.is_empty(), @@ -1786,10 +1783,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> { case.apply_changes(changes); - let diagnostics = case - .db() - .check_file(foo) - .context("Failed to check project.")?; + let diagnostics = case.db().check_file(foo); assert!( diagnostics.len() == 1, diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index 14fb3ae24471af..9af4e7e50e80f0 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -8,8 +8,8 @@ use ruff_db::files::{File, Files}; use ruff_db::system::System; use ruff_db::vendored::VendoredFileSystem; use ruff_db::{Db as SourceDb, Upcast}; +use salsa::Event; use salsa::plumbing::ZalsaDatabase; -use salsa::{Cancelled, Event}; use ty_ide::Db as IdeDb; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::{Db as SemanticDb, Program}; @@ -76,24 +76,21 @@ impl ProjectDatabase { } /// Checks all open files in the project and its dependencies. - pub fn check(&self) -> Result, Cancelled> { + pub fn check(&self) -> Vec { let mut reporter = DummyReporter; let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter); - self.with_db(|db| db.project().check(db, reporter)) + self.project().check(self, reporter) } /// Checks all open files in the project and its dependencies, using the given reporter. - pub fn check_with_reporter( - &self, - reporter: &mut dyn Reporter, - ) -> Result, Cancelled> { + pub fn check_with_reporter(&self, reporter: &mut dyn Reporter) -> Vec { let reporter = AssertUnwindSafe(reporter); - self.with_db(|db| db.project().check(db, reporter)) + self.project().check(self, reporter) } #[tracing::instrument(level = "debug", skip(self))] - pub fn check_file(&self, file: File) -> Result, Cancelled> { - self.with_db(|db| self.project().check_file(db, file)) + pub fn check_file(&self, file: File) -> Vec { + self.project().check_file(self, file) } /// Returns a mutable reference to the system. @@ -107,13 +104,6 @@ impl ProjectDatabase { Arc::get_mut(&mut self.system) .expect("ref count should be 1 because `zalsa_mut` drops all other DB references.") } - - pub(crate) fn with_db(&self, f: F) -> Result - where - F: FnOnce(&ProjectDatabase) -> T + std::panic::UnwindSafe, - { - Cancelled::catch(|| f(self)) - } } impl Upcast for ProjectDatabase { diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 36694eb2dafb1b..bb030ad212fb3c 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -1,10 +1,5 @@ //! Scheduling, I/O, and API endpoints. -use std::num::NonZeroUsize; -// The new PanicInfoHook name requires MSRV >= 1.82 -#[expect(deprecated)] -use std::panic::PanicInfo; - use lsp_server::Message; use lsp_types::{ ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, @@ -13,6 +8,8 @@ use lsp_types::{ TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, Url, }; +use std::num::NonZeroUsize; +use std::panic::PanicHookInfo; use self::connection::{Connection, ConnectionInitializer}; use self::schedule::event_loop_thread; @@ -125,9 +122,7 @@ impl Server { } pub(crate) fn run(self) -> crate::Result<()> { - // The new PanicInfoHook name requires MSRV >= 1.82 - #[expect(deprecated)] - type PanicHook = Box) + 'static + Sync + Send>; + type PanicHook = Box) + 'static + Sync + Send>; struct RestorePanicHook { hook: Option, } diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index a9dd9da1d0272f..2b215004683bb6 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -3,42 +3,41 @@ use crate::session::Session; use crate::system::{AnySystemPath, url_to_any_system_path}; use anyhow::anyhow; use lsp_server as server; +use lsp_server::RequestId; use lsp_types::notification::Notification; +use ruff_db::panic::PanicError; +use std::panic::UnwindSafe; mod diagnostics; mod notifications; mod requests; mod traits; -use notifications as notification; -use requests as request; - use self::traits::{NotificationHandler, RequestHandler}; - use super::{Result, client::Responder, schedule::BackgroundSchedule}; pub(super) fn request<'a>(req: server::Request) -> Task<'a> { let id = req.id.clone(); match req.method.as_str() { - request::DocumentDiagnosticRequestHandler::METHOD => background_request_task::< - request::DocumentDiagnosticRequestHandler, + requests::DocumentDiagnosticRequestHandler::METHOD => background_request_task::< + requests::DocumentDiagnosticRequestHandler, >( req, BackgroundSchedule::Worker ), - request::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::< - request::GotoTypeDefinitionRequestHandler, + requests::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::< + requests::GotoTypeDefinitionRequestHandler, >( req, BackgroundSchedule::Worker ), - request::HoverRequestHandler::METHOD => { - background_request_task::(req, BackgroundSchedule::Worker) - } - request::InlayHintRequestHandler::METHOD => background_request_task::< - request::InlayHintRequestHandler, + requests::HoverRequestHandler::METHOD => background_request_task::< + requests::HoverRequestHandler, + >(req, BackgroundSchedule::Worker), + requests::InlayHintRequestHandler::METHOD => background_request_task::< + requests::InlayHintRequestHandler, >(req, BackgroundSchedule::Worker), - request::CompletionRequestHandler::METHOD => background_request_task::< - request::CompletionRequestHandler, + requests::CompletionRequestHandler::METHOD => background_request_task::< + requests::CompletionRequestHandler, >( req, BackgroundSchedule::LatencySensitive ), @@ -64,23 +63,23 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { match notif.method.as_str() { - notification::DidCloseTextDocumentHandler::METHOD => { - local_notification_task::(notif) + notifications::DidCloseTextDocumentHandler::METHOD => { + local_notification_task::(notif) } - notification::DidOpenTextDocumentHandler::METHOD => { - local_notification_task::(notif) + notifications::DidOpenTextDocumentHandler::METHOD => { + local_notification_task::(notif) } - notification::DidChangeTextDocumentHandler::METHOD => { - local_notification_task::(notif) + notifications::DidChangeTextDocumentHandler::METHOD => { + local_notification_task::(notif) } - notification::DidOpenNotebookHandler::METHOD => { - local_notification_task::(notif) + notifications::DidOpenNotebookHandler::METHOD => { + local_notification_task::(notif) } - notification::DidCloseNotebookHandler::METHOD => { - local_notification_task::(notif) + notifications::DidCloseNotebookHandler::METHOD => { + local_notification_task::(notif) } - notification::DidChangeWatchedFiles::METHOD => { - local_notification_task::(notif) + notifications::DidChangeWatchedFiles::METHOD => { + local_notification_task::(notif) } lsp_types::notification::SetTrace::METHOD => { tracing::trace!("Ignoring `setTrace` notification"); @@ -103,23 +102,25 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { fn _local_request_task<'a, R: traits::SyncRequestHandler>( req: server::Request, -) -> super::Result> { +) -> super::Result> +where + <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, +{ let (id, params) = cast_request::(req)?; Ok(Task::local(|session, notifier, requester, responder| { - let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered(); + let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); let result = R::run(session, notifier, requester, params); respond::(id, result, &responder); })) } -// TODO(micha): Calls to `db` could panic if the db gets mutated while this task is running. -// We should either wrap `R::run_with_snapshot` with a salsa catch cancellation handler or -// use `SemanticModel` instead of passing `db` which uses a Result for all it's methods -// that propagate cancellations. fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( req: server::Request, schedule: BackgroundSchedule, -) -> super::Result> { +) -> super::Result> +where + <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, +{ let (id, params) = cast_request::(req)?; Ok(Task::background(schedule, move |session: &Session| { let url = R::document_url(¶ms).into_owned(); @@ -128,6 +129,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( tracing::warn!("Ignoring request for invalid `{url}`"); return Box::new(|_, _| {}); }; + let db = match &path { AnySystemPath::System(path) => match session.project_db_for_path(path.as_std_path()) { Some(db) => db.clone(), @@ -142,19 +144,55 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( }; Box::new(move |notifier, responder| { - let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered(); - let result = R::run_with_snapshot(&db, snapshot, notifier, params); - respond::(id, result, &responder); + let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); + let result = ruff_db::panic::catch_unwind(|| { + R::run_with_snapshot(&db, snapshot, notifier, params) + }); + + if let Some(response) = request_result_to_response(&id, &responder, result) { + respond::(id, response, &responder); + } }) })) } +fn request_result_to_response( + id: &RequestId, + responder: &Responder, + result: std::result::Result, PanicError>, +) -> Option> { + match result { + Ok(response) => Some(response), + Err(error) => { + if error.payload.downcast_ref::().is_some() { + // Request was cancelled by Salsa. TODO: Retry + respond_silent_error( + id.clone(), + responder, + Error { + code: lsp_server::ErrorCode::ContentModified, + error: anyhow!("content modified"), + }, + ); + None + } else { + let message = format!("request handler {error}"); + + Some(Err(Error { + code: lsp_server::ErrorCode::InternalError, + error: anyhow!(message), + })) + } + } + } +} + fn local_notification_task<'a, N: traits::SyncNotificationHandler>( notif: server::Notification, ) -> super::Result> { let (id, params) = cast_notification::(notif)?; Ok(Task::local(move |session, notifier, requester, _| { - let _span = tracing::trace_span!("notification", method = N::METHOD).entered(); + let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); if let Err(err) = N::run(session, notifier, requester, params) { tracing::error!("An error occurred while running {id}: {err}"); show_err_msg!("ty encountered a problem. Check the logs for more details."); @@ -163,10 +201,15 @@ fn local_notification_task<'a, N: traits::SyncNotificationHandler>( } #[expect(dead_code)] -fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationHandler>( +fn background_notification_thread<'a, N>( req: server::Notification, schedule: BackgroundSchedule, -) -> super::Result> { +) -> super::Result> +where + N: traits::BackgroundDocumentNotificationHandler, + <::NotificationType as lsp_types::notification::Notification>::Params: + UnwindSafe, +{ let (id, params) = cast_notification::(req)?; Ok(Task::background(schedule, move |session: &Session| { let url = N::document_url(¶ms); @@ -177,8 +220,20 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH return Box::new(|_, _| {}); }; Box::new(move |notifier, _| { - let _span = tracing::trace_span!("notification", method = N::METHOD).entered(); - if let Err(err) = N::run_with_snapshot(snapshot, notifier, params) { + let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); + + let result = match ruff_db::panic::catch_unwind(|| { + N::run_with_snapshot(snapshot, notifier, params) + }) { + Ok(result) => result, + Err(panic) => { + tracing::error!("An error occurred while running {id}: {panic}"); + show_err_msg!("ty encountered a panic. Check the logs for more details."); + return; + } + }; + + if let Err(err) = result { tracing::error!("An error occurred while running {id}: {err}"); show_err_msg!("ty encountered a problem. Check the logs for more details."); } @@ -198,6 +253,7 @@ fn cast_request( )> where Req: traits::RequestHandler, + <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, { request .extract(Req::METHOD) @@ -232,6 +288,14 @@ fn respond( } } +/// Sends back an error response to the server using a [`Responder`] without showing a warning +/// to the user. +fn respond_silent_error(id: server::RequestId, responder: &Responder, error: Error) { + if let Err(err) = responder.respond::<()>(id, Err(error)) { + tracing::error!("Failed to send response: {err}"); + } +} + /// Tries to cast a serialized request from the server into /// a parameter type for a specific request handler. fn cast_notification( @@ -240,7 +304,9 @@ fn cast_notification( ( &'static str, <::NotificationType as lsp_types::notification::Notification>::Params, -)> where N: traits::NotificationHandler{ + )> where + N: traits::NotificationHandler, +{ Ok(( N::METHOD, notification diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index 367f65b96ee2a9..b0cdb9a7b7d3c0 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -41,13 +41,7 @@ pub(super) fn compute_diagnostics( return vec![]; }; - let diagnostics = match db.check_file(file) { - Ok(diagnostics) => diagnostics, - Err(cancelled) => { - tracing::info!("Diagnostics computation {cancelled}"); - return vec![]; - } - }; + let diagnostics = db.check_file(file); diagnostics .as_slice() diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 4ce30936e65de7..839442852644c3 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -187,14 +187,14 @@ impl Workspace { /// Checks a single file. #[wasm_bindgen(js_name = "checkFile")] pub fn check_file(&self, file_id: &FileHandle) -> Result, Error> { - let result = self.db.check_file(file_id.file).map_err(into_error)?; + let result = self.db.check_file(file_id.file); Ok(result.into_iter().map(Diagnostic::wrap).collect()) } /// Checks all open files pub fn check(&self) -> Result, Error> { - let result = self.db.check().map_err(into_error)?; + let result = self.db.check(); Ok(result.into_iter().map(Diagnostic::wrap).collect()) } From 175402aa75a57affbab84b3323a0b53263888b4f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 26 May 2025 14:44:43 +0200 Subject: [PATCH 242/487] [ty] Remove unnecessary lifetimes for `Task` (#18261) --- crates/ty_server/src/server/api.rs | 20 +++++------ crates/ty_server/src/server/client.rs | 18 +++++----- crates/ty_server/src/server/schedule.rs | 8 ++--- crates/ty_server/src/server/schedule/task.rs | 35 ++++++++++---------- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index 2b215004683bb6..ce3dd2c0b5ab58 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -16,7 +16,7 @@ mod traits; use self::traits::{NotificationHandler, RequestHandler}; use super::{Result, client::Responder, schedule::BackgroundSchedule}; -pub(super) fn request<'a>(req: server::Request) -> Task<'a> { +pub(super) fn request(req: server::Request) -> Task { let id = req.id.clone(); match req.method.as_str() { @@ -61,7 +61,7 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { }) } -pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { +pub(super) fn notification(notif: server::Notification) -> Task { match notif.method.as_str() { notifications::DidCloseTextDocumentHandler::METHOD => { local_notification_task::(notif) @@ -100,9 +100,7 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { }) } -fn _local_request_task<'a, R: traits::SyncRequestHandler>( - req: server::Request, -) -> super::Result> +fn _local_request_task(req: server::Request) -> super::Result where <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, { @@ -114,10 +112,10 @@ where })) } -fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( +fn background_request_task( req: server::Request, schedule: BackgroundSchedule, -) -> super::Result> +) -> super::Result where <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, { @@ -187,9 +185,9 @@ fn request_result_to_response( } } -fn local_notification_task<'a, N: traits::SyncNotificationHandler>( +fn local_notification_task( notif: server::Notification, -) -> super::Result> { +) -> super::Result { let (id, params) = cast_notification::(notif)?; Ok(Task::local(move |session, notifier, requester, _| { let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); @@ -201,10 +199,10 @@ fn local_notification_task<'a, N: traits::SyncNotificationHandler>( } #[expect(dead_code)] -fn background_notification_thread<'a, N>( +fn background_notification_thread( req: server::Notification, schedule: BackgroundSchedule, -) -> super::Result> +) -> super::Result where N: traits::BackgroundDocumentNotificationHandler, <::NotificationType as lsp_types::notification::Notification>::Params: diff --git a/crates/ty_server/src/server/client.rs b/crates/ty_server/src/server/client.rs index e136bc98d44b87..667c0f14283318 100644 --- a/crates/ty_server/src/server/client.rs +++ b/crates/ty_server/src/server/client.rs @@ -6,12 +6,12 @@ use serde_json::Value; use super::{ClientSender, schedule::Task}; -type ResponseBuilder<'s> = Box Task<'s>>; +type ResponseBuilder = Box Task>; -pub(crate) struct Client<'s> { +pub(crate) struct Client { notifier: Notifier, responder: Responder, - pub(super) requester: Requester<'s>, + pub(super) requester: Requester, } #[derive(Clone)] @@ -20,13 +20,13 @@ pub(crate) struct Notifier(ClientSender); #[derive(Clone)] pub(crate) struct Responder(ClientSender); -pub(crate) struct Requester<'s> { +pub(crate) struct Requester { sender: ClientSender, next_request_id: i32, - response_handlers: FxHashMap>, + response_handlers: FxHashMap, } -impl Client<'_> { +impl Client { pub(super) fn new(sender: ClientSender) -> Self { Self { notifier: Notifier(sender.clone()), @@ -91,14 +91,14 @@ impl Responder { } } -impl<'s> Requester<'s> { +impl Requester { /// Sends a request of kind `R` to the client, with associated parameters. /// The task provided by `response_handler` will be dispatched as soon as the response /// comes back from the client. pub(crate) fn request( &mut self, params: R::Params, - response_handler: impl Fn(R::Result) -> Task<'s> + 'static, + response_handler: impl Fn(R::Result) -> Task + 'static, ) -> crate::Result<()> where R: lsp_types::request::Request, @@ -155,7 +155,7 @@ impl<'s> Requester<'s> { Ok(()) } - pub(crate) fn pop_response_task(&mut self, response: lsp_server::Response) -> Task<'s> { + pub(crate) fn pop_response_task(&mut self, response: lsp_server::Response) -> Task { if let Some(handler) = self.response_handlers.remove(&response.id) { handler(response) } else { diff --git a/crates/ty_server/src/server/schedule.rs b/crates/ty_server/src/server/schedule.rs index 21c71683189755..b77ab22abe125c 100644 --- a/crates/ty_server/src/server/schedule.rs +++ b/crates/ty_server/src/server/schedule.rs @@ -34,7 +34,7 @@ pub(crate) fn event_loop_thread( pub(crate) struct Scheduler<'s> { session: &'s mut Session, - client: Client<'s>, + client: Client, fmt_pool: thread::Pool, background_pool: thread::Pool, } @@ -60,7 +60,7 @@ impl<'s> Scheduler<'s> { pub(super) fn request( &mut self, params: R::Params, - response_handler: impl Fn(R::Result) -> Task<'s> + 'static, + response_handler: impl Fn(R::Result) -> Task + 'static, ) -> crate::Result<()> where R: lsp_types::request::Request, @@ -69,13 +69,13 @@ impl<'s> Scheduler<'s> { } /// Creates a task to handle a response from the client. - pub(super) fn response(&mut self, response: lsp_server::Response) -> Task<'s> { + pub(super) fn response(&mut self, response: lsp_server::Response) -> Task { self.client.requester.pop_response_task(response) } /// Dispatches a `task` by either running it as a blocking function or /// executing it on a background thread pool. - pub(super) fn dispatch(&mut self, task: task::Task<'s>) { + pub(super) fn dispatch(&mut self, task: task::Task) { match task { Task::Sync(SyncTask { func }) => { let notifier = self.client.notifier(); diff --git a/crates/ty_server/src/server/schedule/task.rs b/crates/ty_server/src/server/schedule/task.rs index d269c1edb5a3b1..22c4f2ca9cff78 100644 --- a/crates/ty_server/src/server/schedule/task.rs +++ b/crates/ty_server/src/server/schedule/task.rs @@ -6,11 +6,11 @@ use crate::{ session::Session, }; -type LocalFn<'s> = Box; +type LocalFn = Box; type BackgroundFn = Box; -type BackgroundFnBuilder<'s> = Box BackgroundFn + 's>; +type BackgroundFnBuilder = Box BackgroundFn>; /// Describes how the task should be run. #[derive(Clone, Copy, Debug, Default)] @@ -36,9 +36,9 @@ pub(in crate::server) enum BackgroundSchedule { /// while local tasks have exclusive access and can modify it as they please. Keep in mind that /// local tasks will **block** the main event loop, so only use local tasks if you **need** /// mutable state access or you need the absolute lowest latency possible. -pub(in crate::server) enum Task<'s> { - Background(BackgroundTaskBuilder<'s>), - Sync(SyncTask<'s>), +pub(in crate::server) enum Task { + Background(BackgroundTaskBuilder), + Sync(SyncTask), } // The reason why this isn't just a 'static background closure @@ -49,30 +49,31 @@ pub(in crate::server) enum Task<'s> { // that the inner closure can capture. This builder closure has a lifetime linked to the scheduler. // When the task is dispatched, the scheduler runs the synchronous builder, which takes the session // as a reference, to create the inner 'static closure. That closure is then moved to a background task pool. -pub(in crate::server) struct BackgroundTaskBuilder<'s> { +pub(in crate::server) struct BackgroundTaskBuilder { pub(super) schedule: BackgroundSchedule, - pub(super) builder: BackgroundFnBuilder<'s>, + pub(super) builder: BackgroundFnBuilder, } -pub(in crate::server) struct SyncTask<'s> { - pub(super) func: LocalFn<'s>, +pub(in crate::server) struct SyncTask { + pub(super) func: LocalFn, } -impl<'s> Task<'s> { +impl Task { /// Creates a new background task. - pub(crate) fn background( - schedule: BackgroundSchedule, - func: impl FnOnce(&Session) -> Box + 's, - ) -> Self { + pub(crate) fn background(schedule: BackgroundSchedule, func: F) -> Self + where + F: FnOnce(&Session) -> Box + 'static, + { Self::Background(BackgroundTaskBuilder { schedule, builder: Box::new(func), }) } /// Creates a new local task. - pub(crate) fn local( - func: impl FnOnce(&mut Session, Notifier, &mut Requester, Responder) + 's, - ) -> Self { + pub(crate) fn local(func: F) -> Self + where + F: FnOnce(&mut Session, Notifier, &mut Requester, Responder) + 'static, + { Self::Sync(SyncTask { func: Box::new(func), }) From b25b6423717efa7a0197475253a421830850de15 Mon Sep 17 00:00:00 2001 From: Maddy Guthridge Date: Tue, 27 May 2025 00:35:20 +1000 Subject: [PATCH 243/487] Improve readability of rule status icons in documentation (#18297) Co-authored-by: Micha Reiser --- crates/ruff_dev/src/generate_rules_table.rs | 55 +++++++++------------ 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/crates/ruff_dev/src/generate_rules_table.rs b/crates/ruff_dev/src/generate_rules_table.rs index 73cdf65540ff35..21b6aec0de63cc 100644 --- a/crates/ruff_dev/src/generate_rules_table.rs +++ b/crates/ruff_dev/src/generate_rules_table.rs @@ -18,44 +18,43 @@ const FIX_SYMBOL: &str = "🛠️"; const PREVIEW_SYMBOL: &str = "🧪"; const REMOVED_SYMBOL: &str = "❌"; const WARNING_SYMBOL: &str = "⚠️"; -const STABLE_SYMBOL: &str = "✔️"; const SPACER: &str = "    "; +/// Style for the rule's fixability and status icons. +const SYMBOL_STYLE: &str = "style='width: 1em; display: inline-block;'"; +/// Style for the container wrapping the fixability and status icons. +const SYMBOLS_CONTAINER: &str = "style='display: flex; gap: 0.5rem; justify-content: end;'"; + fn generate_table(table_out: &mut String, rules: impl IntoIterator, linter: &Linter) { - table_out.push_str("| Code | Name | Message | |"); + table_out.push_str("| Code | Name | Message | |"); table_out.push('\n'); - table_out.push_str("| ---- | ---- | ------- | ------: |"); + table_out.push_str("| ---- | ---- | ------- | -: |"); table_out.push('\n'); for rule in rules { let status_token = match rule.group() { RuleGroup::Removed => { - format!("{REMOVED_SYMBOL}") + format!( + "{REMOVED_SYMBOL}" + ) } RuleGroup::Deprecated => { - format!("{WARNING_SYMBOL}") + format!( + "{WARNING_SYMBOL}" + ) } RuleGroup::Preview => { - format!("{PREVIEW_SYMBOL}") - } - RuleGroup::Stable => { - // A full opacity checkmark is a bit aggressive for indicating stable - format!("{STABLE_SYMBOL}") + format!("{PREVIEW_SYMBOL}") } + RuleGroup::Stable => format!(""), }; let fix_token = match rule.fixable() { FixAvailability::Always | FixAvailability::Sometimes => { - format!("{FIX_SYMBOL}") - } - FixAvailability::None => { - format!( - "" - ) + format!("{FIX_SYMBOL}") } + FixAvailability::None => format!(""), }; - let tokens = format!("{status_token} {fix_token}"); - let rule_name = rule.as_ref(); // If the message ends in a bracketed expression (like: "Use {replacement}"), escape the @@ -82,15 +81,14 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator, #[expect(clippy::or_fun_call)] let _ = write!( table_out, - "| {ss}{0}{1}{se} {{ #{0}{1} }} | {ss}{2}{se} | {ss}{3}{se} | {ss}{4}{se} |", - linter.common_prefix(), - linter.code_for_rule(rule).unwrap(), - rule.explanation() + "| {ss}{prefix}{code}{se} {{ #{prefix}{code} }} | {ss}{explanation}{se} | {ss}{message}{se} |
{status_token}{fix_token}
|", + prefix = linter.common_prefix(), + code = linter.code_for_rule(rule).unwrap(), + explanation = rule + .explanation() .is_some() .then_some(format_args!("[{rule_name}](rules/{rule_name}.md)")) .unwrap_or(format_args!("{rule_name}")), - message, - tokens, ); table_out.push('\n'); } @@ -104,12 +102,6 @@ pub(crate) fn generate() -> String { table_out.push_str("### Legend"); table_out.push('\n'); - let _ = write!( - &mut table_out, - "{SPACER}{STABLE_SYMBOL}{SPACER} The rule is stable." - ); - table_out.push_str("
"); - let _ = write!( &mut table_out, "{SPACER}{PREVIEW_SYMBOL}{SPACER} The rule is unstable and is in [\"preview\"](faq.md#what-is-preview)." @@ -132,7 +124,8 @@ pub(crate) fn generate() -> String { &mut table_out, "{SPACER}{FIX_SYMBOL}{SPACER} The rule is automatically fixable by the `--fix` command-line option." ); - table_out.push_str("
"); + table_out.push_str("\n\n"); + table_out.push_str("All rules not marked as preview, deprecated or removed are stable."); table_out.push('\n'); for linter in Linter::iter() { From 4e68dd96a61c4fe0411d4dff832d4c60084349fb Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 26 May 2025 17:08:52 +0200 Subject: [PATCH 244/487] [ty] Infer types for ty_extensions.Intersection[A, B] tuple expressions (#18321) ## Summary fixes astral-sh/ty#366 ## Test Plan * Added panic corpus regression tests * I also wrote a hover regression test (see below), but decided not to include it. The corpus tests are much more "effective" at finding these types of errors, since they exhaustively check all expressions for types.
```rs #[test] fn hover_regression_test_366() { let test = cursor_test( r#" from ty_extensions import Intersection class A: ... class B: ... def _(x: Intersection[A, B]): pass "#, ); assert_snapshot!(test.hover(), @r" A & B --------------------------------------------- ```text A & B ``` --------------------------------------------- info[hover]: Hovered content is --> main.py:7:31 | 5 | class B: ... 6 | 7 | def _(x: Intersection[A, B]): | ^^-^ | | | | | Cursor offset | source 8 | pass | "); } ```
--- .../resources/test/corpus/ty_extensions.py | 30 +++++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 9 ++++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 crates/ty_project/resources/test/corpus/ty_extensions.py diff --git a/crates/ty_project/resources/test/corpus/ty_extensions.py b/crates/ty_project/resources/test/corpus/ty_extensions.py new file mode 100644 index 00000000000000..64810a630764f4 --- /dev/null +++ b/crates/ty_project/resources/test/corpus/ty_extensions.py @@ -0,0 +1,30 @@ +""" +Make sure that types are inferred for all subexpressions of the following +annotations involving ty_extension `_SpecialForm`s. + +This is a regression test for https://github.com/astral-sh/ty/issues/366 +""" + +from ty_extensions import CallableTypeOf, Intersection, Not, TypeOf + + +class A: ... + + +class B: ... + + +def _(x: Not[A]): + pass + + +def _(x: Intersection[A], y: Intersection[A, B]): + pass + + +def _(x: TypeOf[1j]): + pass + + +def _(x: CallableTypeOf[str]): + pass diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index bddbe9f8363745..b60ac4c9ba094e 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -8648,11 +8648,16 @@ impl<'db> TypeInferenceBuilder<'db> { element => Either::Right(std::iter::once(element)), }; - elements + let ty = elements .fold(IntersectionBuilder::new(db), |builder, element| { builder.add_positive(self.infer_type_expression(element)) }) - .build() + .build(); + + if matches!(arguments_slice, ast::Expr::Tuple(_)) { + self.store_expression_type(arguments_slice, ty); + } + ty } KnownInstanceType::TypeOf => match arguments_slice { ast::Expr::Tuple(_) => { From 62ef96f51e42e3cb7f71d89454a25da007b42c12 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 26 May 2025 19:45:48 +0200 Subject: [PATCH 245/487] [ty] Move `respect-ignore-files` under `src` section (#18322) --- crates/ty/docs/configuration.md | 38 +++++++++++----------- crates/ty/src/args.rs | 8 +++-- crates/ty/tests/cli.rs | 10 +++--- crates/ty_project/src/metadata/options.rs | 28 ++++++++-------- crates/ty_project/src/metadata/settings.rs | 9 +++-- ty.schema.json | 14 ++++---- 6 files changed, 57 insertions(+), 50 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index b3ad865ee047b4..a2986eeb2709eb 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -1,25 +1,6 @@ # Configuration -#### `respect-ignore-files` - -Whether to automatically exclude files that are ignored by `.ignore`, -`.gitignore`, `.git/info/exclude`, and global `gitignore` files. -Enabled by default. - -**Default value**: `true` - -**Type**: `bool` - -**Example usage** (`pyproject.toml`): - -```toml -[tool.ty] -respect-ignore-files = false -``` - ---- - #### `rules` Configures the enabled rules and their severity. @@ -162,6 +143,25 @@ typeshed = "/path/to/custom/typeshed" ## `src` +#### `respect-ignore-files` + +Whether to automatically exclude files that are ignored by `.ignore`, +`.gitignore`, `.git/info/exclude`, and global `gitignore` files. +Enabled by default. + +**Default value**: `true` + +**Type**: `bool` + +**Example usage** (`pyproject.toml`): + +```toml +[tool.ty.src] +respect-ignore-files = false +``` + +--- + #### `root` The root of the project, used for finding first-party modules. diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index d4d49e45651f58..9a48b310ec0563 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -4,7 +4,7 @@ use clap::error::ErrorKind; use clap::{ArgAction, ArgMatches, Error, Parser}; use ruff_db::system::SystemPathBuf; use ty_project::combine::Combine; -use ty_project::metadata::options::{EnvironmentOptions, Options, TerminalOptions}; +use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions}; use ty_project::metadata::value::{RangedValue, RelativePathBuf, ValueSource}; use ty_python_semantic::lint; @@ -184,9 +184,11 @@ impl CheckCommand { .map(|output_format| RangedValue::cli(output_format.into())), error_on_warning: self.error_on_warning, }), + src: Some(SrcOptions { + respect_ignore_files, + ..SrcOptions::default() + }), rules, - respect_ignore_files, - ..Default::default() }; // Merge with options passed in via --config options.combine(self.config.into_options().unwrap_or_default()) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 6daa3d27d8fddf..5a7003052813a7 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -85,7 +85,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { "); // Test that we can set to false via config file - case.write_file("ty.toml", "respect-ignore-files = false")?; + case.write_file("ty.toml", "src.respect-ignore-files = false")?; assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 @@ -104,7 +104,7 @@ fn test_respect_ignore_files() -> anyhow::Result<()> { "); // Ensure CLI takes precedence - case.write_file("ty.toml", "respect-ignore-files = true")?; + case.write_file("ty.toml", "src.respect-ignore-files = true")?; assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" success: false exit_code: 1 @@ -1534,7 +1534,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { #[test] fn cli_config_args_invalid_option() -> anyhow::Result<()> { let case = TestCase::with_file("test.py", r"print(1)")?; - assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r" + assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r###" success: false exit_code: 2 ----- stdout ----- @@ -1544,13 +1544,13 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> { | 1 | bad-option=true | ^^^^^^^^^^ - unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `respect-ignore-files` + unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal` Usage: ty For more information, try '--help'. - "); + "###); Ok(()) } diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 46fd5eadd8ea7c..0581684faf1dcd 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -57,19 +57,6 @@ pub struct Options { #[serde(skip_serializing_if = "Option::is_none")] #[option_group] pub terminal: Option, - - /// Whether to automatically exclude files that are ignored by `.ignore`, - /// `.gitignore`, `.git/info/exclude`, and global `gitignore` files. - /// Enabled by default. - #[option( - default = r#"true"#, - value_type = r#"bool"#, - example = r#" - respect-ignore-files = false - "# - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub respect_ignore_files: Option, } impl Options { @@ -216,7 +203,7 @@ impl Options { pub(crate) fn to_settings(&self, db: &dyn Db) -> (Settings, Vec) { let (rules, diagnostics) = self.to_rule_selection(db); - let mut settings = Settings::new(rules, self.respect_ignore_files); + let mut settings = Settings::new(rules, self.src.as_ref()); if let Some(terminal) = self.terminal.as_ref() { settings.set_terminal(TerminalSettings { @@ -421,6 +408,19 @@ pub struct SrcOptions { "# )] pub root: Option, + + /// Whether to automatically exclude files that are ignored by `.ignore`, + /// `.gitignore`, `.git/info/exclude`, and global `gitignore` files. + /// Enabled by default. + #[option( + default = r#"true"#, + value_type = r#"bool"#, + example = r#" + respect-ignore-files = false + "# + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub respect_ignore_files: Option, } #[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)] diff --git a/crates/ty_project/src/metadata/settings.rs b/crates/ty_project/src/metadata/settings.rs index b635c5470de8de..0a48fc559cd3ff 100644 --- a/crates/ty_project/src/metadata/settings.rs +++ b/crates/ty_project/src/metadata/settings.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use crate::metadata::options::SrcOptions; use ruff_db::diagnostic::DiagnosticFormat; use ty_python_semantic::lint::RuleSelection; @@ -26,11 +27,15 @@ pub struct Settings { } impl Settings { - pub fn new(rules: RuleSelection, respect_ignore_files: Option) -> Self { + pub fn new(rules: RuleSelection, src_options: Option<&SrcOptions>) -> Self { + let respect_ignore_files = src_options + .and_then(|src| src.respect_ignore_files) + .unwrap_or(true); + Self { rules: Arc::new(rules), terminal: TerminalSettings::default(), - respect_ignore_files: respect_ignore_files.unwrap_or(true), + respect_ignore_files, } } diff --git a/ty.schema.json b/ty.schema.json index 0aed4e9558c71e..f4ae8d241babff 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -14,13 +14,6 @@ } ] }, - "respect-ignore-files": { - "description": "Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. Enabled by default.", - "type": [ - "boolean", - "null" - ] - }, "rules": { "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://ty.dev/rules) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", "anyOf": [ @@ -858,6 +851,13 @@ "SrcOptions": { "type": "object", "properties": { + "respect-ignore-files": { + "description": "Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. Enabled by default.", + "type": [ + "boolean", + "null" + ] + }, "root": { "description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `.//` directory exists, include `.` and `./` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), it will also be included in the first party search path.", "type": [ From 1d20cf9570a0ca5148e375401016c8c9255ac488 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Mon, 26 May 2025 21:34:47 +0200 Subject: [PATCH 246/487] [ty] Add hint if async context manager is used in non-async with statement (#18299) # Summary Adds a subdiagnostic hint in the following scenario where a synchronous `with` is used with an async context manager: ```py class Manager: async def __aenter__(self): ... async def __aexit__(self, *args): ... # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" # note: Objects of type `Manager` *can* be used as async context managers # note: Consider using `async with` here with Manager(): ... ``` closes https://github.com/astral-sh/ty/issues/508 ## Test Plan New MD snapshot tests --------- Co-authored-by: David Peter --- ...of_no\342\200\246_(b07503f9b773ea61).snap" | 85 +++++++++++++++++++ .../resources/mdtest/with/sync.md | 42 +++++++++ crates/ty_python_semantic/src/types.rs | 21 ++++- 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" new file mode 100644 index 00000000000000..b8243d669fa7ea --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" @@ -0,0 +1,85 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: sync.md - With statements - Accidental use of non-async `with` +mdtest path: crates/ty_python_semantic/resources/mdtest/with/sync.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | class Manager: + 2 | async def __aenter__(self): ... + 3 | async def __aexit__(self, *args): ... + 4 | + 5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" + 6 | with Manager(): + 7 | ... + 8 | class Manager: + 9 | async def __aenter__(self): ... +10 | async def __aexit__(self, typ: str, exc, traceback): ... +11 | +12 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" +13 | with Manager(): +14 | ... +15 | class Manager: +16 | async def __aenter__(self, wrong_extra_arg): ... +17 | async def __aexit__(self, typ, exc, traceback, wrong_extra_arg): ... +18 | +19 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" +20 | with Manager(): +21 | ... +``` + +# Diagnostics + +``` +error[invalid-context-manager]: Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__` + --> src/mdtest_snippet.py:6:6 + | +5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and... +6 | with Manager(): + | ^^^^^^^^^ +7 | ... +8 | class Manager: + | +info: Objects of type `Manager` can be used as async context managers +info: Consider using `async with` here +info: rule `invalid-context-manager` is enabled by default + +``` + +``` +error[invalid-context-manager]: Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__` + --> src/mdtest_snippet.py:13:6 + | +12 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` an... +13 | with Manager(): + | ^^^^^^^^^ +14 | ... +15 | class Manager: + | +info: Objects of type `Manager` can be used as async context managers +info: Consider using `async with` here +info: rule `invalid-context-manager` is enabled by default + +``` + +``` +error[invalid-context-manager]: Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__` + --> src/mdtest_snippet.py:20:6 + | +19 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` an... +20 | with Manager(): + | ^^^^^^^^^ +21 | ... + | +info: Objects of type `Manager` can be used as async context managers +info: Consider using `async with` here +info: rule `invalid-context-manager` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/with/sync.md b/crates/ty_python_semantic/resources/mdtest/with/sync.md index b180d6f55c4b93..89d0b281da36e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/with/sync.md +++ b/crates/ty_python_semantic/resources/mdtest/with/sync.md @@ -149,3 +149,45 @@ context_expr = Manager() with context_expr as f: reveal_type(f) # revealed: str ``` + +## Accidental use of non-async `with` + + + +If a synchronous `with` statement is used on a type with `__aenter__` and `__aexit__`, we show a +diagnostic hint that the user might have intended to use `asnyc with` instead. + +```py +class Manager: + async def __aenter__(self): ... + async def __aexit__(self, *args): ... + +# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" +with Manager(): + ... +``` + +The sub-diagnostic is also provided if the signatures of `__aenter__` and `__aexit__` do not match +the expected signatures for a context manager: + +```py +class Manager: + async def __aenter__(self): ... + async def __aexit__(self, typ: str, exc, traceback): ... + +# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" +with Manager(): + ... +``` + +Similarly, we also show the hint if the functions have the wrong number of arguments: + +```py +class Manager: + async def __aenter__(self, wrong_extra_arg): ... + async def __aexit__(self, typ, exc, traceback, wrong_extra_arg): ... + +# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" +with Manager(): + ... +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index fcab31b7a7ff79..5a0f3f9f1e6c92 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6133,12 +6133,31 @@ impl<'db> ContextManagerError<'db> { } => format_call_dunder_errors(enter_error, "__enter__", exit_error, "__exit__"), }; - builder.into_diagnostic( + let mut diag = builder.into_diagnostic( format_args!( "Object of type `{context_expression}` cannot be used with `with` because {formatted_errors}", context_expression = context_expression_type.display(db) ), ); + + // If `__aenter__` and `__aexit__` are available, the user may have intended to use `async with` instead of `with`: + if let ( + Ok(_) | Err(CallDunderError::CallError(..)), + Ok(_) | Err(CallDunderError::CallError(..)), + ) = ( + context_expression_type.try_call_dunder(db, "__aenter__", CallArgumentTypes::none()), + context_expression_type.try_call_dunder( + db, + "__aexit__", + CallArgumentTypes::positional([Type::unknown(), Type::unknown(), Type::unknown()]), + ), + ) { + diag.info(format_args!( + "Objects of type `{context_expression}` can be used as async context managers", + context_expression = context_expression_type.display(db) + )); + diag.info("Consider using `async with` here"); + } } } From 0a11baf29c2867005c3f4bb9d99a137c3c16d056 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 26 May 2025 20:40:16 +0100 Subject: [PATCH 247/487] [ty] Implement implicit inheritance from `Generic[]` for PEP-695 generic classes (#18283) --- .../mdtest/generics/legacy/classes.md | 2 +- .../mdtest/generics/pep695/classes.md | 35 +++++++ .../resources/mdtest/mro.md | 8 +- .../resources/mdtest/protocols.md | 6 +- crates/ty_python_semantic/src/types/class.rs | 11 ++- .../ty_python_semantic/src/types/generics.rs | 54 ++--------- crates/ty_python_semantic/src/types/infer.rs | 54 +++++++---- crates/ty_python_semantic/src/types/mro.rs | 97 +++++++++++++------ 8 files changed, 166 insertions(+), 101 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index d952ed9e86d734..ad1fe65958a2a5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -37,7 +37,7 @@ class RepeatedTypevar(Generic[T, T]): ... You can only specialize `typing.Generic` with typevars (TODO: or param specs or typevar tuples). ```py -# error: [invalid-argument-type] "`` is not a valid argument to `typing.Generic`" +# error: [invalid-argument-type] "`` is not a valid argument to `Generic`" class GenericOfType(Generic[int]): ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index c205d4661739ef..1d3fee229bace0 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -67,6 +67,41 @@ T = TypeVar("T") # error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables" class BothGenericSyntaxes[U](Generic[T]): ... + +reveal_type(BothGenericSyntaxes.__mro__) # revealed: tuple[, Unknown, ] + +# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables" +# error: [invalid-base] "Cannot inherit from plain `Generic`" +class DoublyInvalid[T](Generic): ... + +reveal_type(DoublyInvalid.__mro__) # revealed: tuple[, Unknown, ] +``` + +Generic classes implicitly inherit from `Generic`: + +```py +class Foo[T]: ... + +# revealed: tuple[, typing.Generic, ] +reveal_type(Foo.__mro__) +# revealed: tuple[, typing.Generic, ] +reveal_type(Foo[int].__mro__) + +class A: ... +class Bar[T](A): ... + +# revealed: tuple[, , typing.Generic, ] +reveal_type(Bar.__mro__) +# revealed: tuple[, , typing.Generic, ] +reveal_type(Bar[int].__mro__) + +class B: ... +class Baz[T](A, B): ... + +# revealed: tuple[, , , typing.Generic, ] +reveal_type(Baz.__mro__) +# revealed: tuple[, , , typing.Generic, ] +reveal_type(Baz[int].__mro__) ``` ## Specializing generic classes explicitly diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 47d607fcda9ae3..1a73deb594637c 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -644,14 +644,14 @@ reveal_type(C.__mro__) # revealed: tuple[, Unknown, class D(D.a): a: D -#reveal_type(D.__class__) # revealed: +reveal_type(D.__class__) # revealed: reveal_type(D.__mro__) # revealed: tuple[, Unknown, ] class E[T](E.a): ... -#reveal_type(E.__class__) # revealed: -reveal_type(E.__mro__) # revealed: tuple[, Unknown, ] +reveal_type(E.__class__) # revealed: +reveal_type(E.__mro__) # revealed: tuple[, Unknown, typing.Generic, ] class F[T](F(), F): ... # error: [cyclic-class-definition] -#reveal_type(F.__class__) # revealed: +reveal_type(F.__class__) # revealed: type[Unknown] reveal_type(F.__mro__) # revealed: tuple[, Unknown, ] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index a2116c8f972c4e..763694dacba82c 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -58,9 +58,13 @@ class Bar1(Protocol[T], Generic[T]): class Bar2[T](Protocol): x: T -# error: [invalid-generic-class] "Cannot both inherit from subscripted `typing.Protocol` and use PEP 695 type variables" +# error: [invalid-generic-class] "Cannot both inherit from subscripted `Protocol` and use PEP 695 type variables" class Bar3[T](Protocol[T]): x: T + +# Note that this class definition *will* actually succeed at runtime, +# unlike classes that combine PEP-695 type parameters with inheritance from `Generic[]` +reveal_type(Bar3.__mro__) # revealed: tuple[, typing.Protocol, typing.Generic, ] ``` It's an error to include both bare `Protocol` and subscripted `Protocol[]` in the bases list diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 83870384ee971f..0aa8e785fe71e4 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -223,8 +223,11 @@ impl<'db> ClassType<'db> { } } - pub(super) const fn is_generic(self) -> bool { - matches!(self, Self::Generic(_)) + pub(super) fn has_pep_695_type_params(self, db: &'db dyn Db) -> bool { + match self { + Self::NonGeneric(class) => class.has_pep_695_type_params(db), + Self::Generic(generic) => generic.origin(db).has_pep_695_type_params(db), + } } /// Returns the class literal and specialization for this class. For a non-generic class, this @@ -573,6 +576,10 @@ impl<'db> ClassLiteral<'db> { .or_else(|| self.inherited_legacy_generic_context(db)) } + pub(crate) fn has_pep_695_type_params(self, db: &'db dyn Db) -> bool { + self.pep695_generic_context(db).is_some() + } + #[salsa::tracked(cycle_fn=pep695_generic_context_cycle_recover, cycle_initial=pep695_generic_context_cycle_initial)] pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option> { let scope = self.body_scope(db); diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index c3d5a61fb7a0f8..4866b73efdabfe 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -27,7 +27,6 @@ use crate::{Db, FxOrderSet}; pub struct GenericContext<'db> { #[returns(ref)] pub(crate) variables: FxOrderSet>, - pub(crate) origin: GenericContextOrigin, } impl<'db> GenericContext<'db> { @@ -41,7 +40,7 @@ impl<'db> GenericContext<'db> { .iter() .filter_map(|type_param| Self::variable_from_type_param(db, index, type_param)) .collect(); - Self::new(db, variables, GenericContextOrigin::TypeParameterList) + Self::new(db, variables) } fn variable_from_type_param( @@ -87,11 +86,7 @@ impl<'db> GenericContext<'db> { if variables.is_empty() { return None; } - Some(Self::new( - db, - variables, - GenericContextOrigin::LegacyGenericFunction, - )) + Some(Self::new(db, variables)) } /// Creates a generic context from the legacy `TypeVar`s that appear in class's base class @@ -107,7 +102,7 @@ impl<'db> GenericContext<'db> { if variables.is_empty() { return None; } - Some(Self::new(db, variables, GenericContextOrigin::Inherited)) + Some(Self::new(db, variables)) } pub(crate) fn len(self, db: &'db dyn Db) -> usize { @@ -244,46 +239,21 @@ impl<'db> GenericContext<'db> { .iter() .map(|ty| ty.normalized(db)) .collect(); - Self::new(db, variables, self.origin(db)) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub enum GenericContextOrigin { - LegacyBase(LegacyGenericBase), - Inherited, - LegacyGenericFunction, - TypeParameterList, -} - -impl GenericContextOrigin { - pub(crate) const fn as_str(self) -> &'static str { - match self { - Self::LegacyBase(base) => base.as_str(), - Self::Inherited => "inherited", - Self::LegacyGenericFunction => "legacy generic function", - Self::TypeParameterList => "type parameter list", - } + Self::new(db, variables) } } -impl std::fmt::Display for GenericContextOrigin { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub enum LegacyGenericBase { +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum LegacyGenericBase { Generic, Protocol, } impl LegacyGenericBase { - pub(crate) const fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { - Self::Generic => "`typing.Generic`", - Self::Protocol => "subscripted `typing.Protocol`", + Self::Generic => "Generic", + Self::Protocol => "Protocol", } } } @@ -294,12 +264,6 @@ impl std::fmt::Display for LegacyGenericBase { } } -impl From for GenericContextOrigin { - fn from(base: LegacyGenericBase) -> Self { - Self::LegacyBase(base) - } -} - /// An assignment of a specific type to each type variable in a generic scope. /// /// TODO: Handle nested specializations better, with actual parent links to the specialization of diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b60ac4c9ba094e..d3b93fcab0c683 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -108,7 +108,7 @@ use super::diagnostic::{ report_runtime_check_against_non_runtime_checkable_protocol, report_slice_step_size_zero, report_unresolved_reference, }; -use super::generics::{GenericContextOrigin, LegacyGenericBase}; +use super::generics::LegacyGenericBase; use super::slots::check_class_slots; use super::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, @@ -856,6 +856,25 @@ impl<'db> TypeInferenceBuilder<'db> { } continue; } + // Note that unlike several of the other errors caught in this function, + // this does not lead to the class creation failing at runtime, + // but it is semantically invalid. + Type::KnownInstance(KnownInstanceType::Protocol(Some(_))) => { + if class_node.type_params.is_none() { + continue; + } + let Some(builder) = self + .context + .report_lint(&INVALID_GENERIC_CLASS, &class_node.bases()[i]) + else { + continue; + }; + builder.into_diagnostic( + "Cannot both inherit from subscripted `Protocol` \ + and use PEP 695 type variables", + ); + continue; + } Type::ClassLiteral(class) => class, // dynamic/unknown bases are never `@final` _ => continue, @@ -917,7 +936,7 @@ impl<'db> TypeInferenceBuilder<'db> { { builder.into_diagnostic(format_args!( "Cannot create a consistent method resolution order (MRO) \ - for class `{}` with bases list `[{}]`", + for class `{}` with bases list `[{}]`", class.name(self.db()), bases_list .iter() @@ -926,6 +945,16 @@ impl<'db> TypeInferenceBuilder<'db> { )); } } + MroErrorKind::Pep695ClassWithGenericInheritance => { + if let Some(builder) = + self.context.report_lint(&INVALID_GENERIC_CLASS, class_node) + { + builder.into_diagnostic( + "Cannot both inherit from `typing.Generic` \ + and use PEP 695 type variables", + ); + } + } MroErrorKind::InheritanceCycle => { if let Some(builder) = self .context @@ -1022,21 +1051,6 @@ impl<'db> TypeInferenceBuilder<'db> { } } - // (5) Check that a generic class does not have invalid or conflicting generic - // contexts. - if class.pep695_generic_context(self.db()).is_some() { - if let Some(legacy_context) = class.legacy_generic_context(self.db()) { - if let Some(builder) = - self.context.report_lint(&INVALID_GENERIC_CLASS, class_node) - { - builder.into_diagnostic(format_args!( - "Cannot both inherit from {} and use PEP 695 type variables", - legacy_context.origin(self.db()) - )); - } - } - } - if let (Some(legacy), Some(inherited)) = ( class.legacy_generic_context(self.db()), class.inherited_legacy_generic_context(self.db()), @@ -7628,7 +7642,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node) { builder.into_diagnostic(format_args!( - "`{}` is not a valid argument to {origin}", + "`{}` is not a valid argument to `{origin}`", typevar.display(self.db()), )); } @@ -7636,9 +7650,7 @@ impl<'db> TypeInferenceBuilder<'db> { } }) .collect(); - typevars.map(|typevars| { - GenericContext::new(self.db(), typevars, GenericContextOrigin::from(origin)) - }) + typevars.map(|typevars| GenericContext::new(self.db(), typevars)) } fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> { diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 72f3eceafa8466..44957fb929f732 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -67,10 +67,41 @@ impl<'db> Mro<'db> { fn of_class_impl( db: &'db dyn Db, class: ClassType<'db>, - bases: &[Type<'db>], + original_bases: &[Type<'db>], specialization: Option>, ) -> Result> { - match bases { + /// Possibly add `Generic` to the resolved bases list. + /// + /// This function is called in two cases: + /// - If we encounter a subscripted `Generic` in the original bases list + /// (`Generic[T]` or similar) + /// - If the class has PEP-695 type parameters, + /// `Generic` is [implicitly appended] to the bases list at runtime + /// + /// Whether or not `Generic` is added to the bases list depends on: + /// - Whether `Protocol` is present in the original bases list + /// - Whether any of the bases yet to be visited in the original bases list + /// is a generic alias (which would therefore have `Generic` in its MRO) + /// + /// This function emulates the behavior of `typing._GenericAlias.__mro_entries__` at + /// . + /// + /// [implicitly inherits]: https://docs.python.org/3/reference/compound_stmts.html#generic-classes + fn maybe_add_generic<'db>( + resolved_bases: &mut Vec>, + original_bases: &[Type<'db>], + remaining_bases: &[Type<'db>], + ) { + if original_bases.contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) { + return; + } + if remaining_bases.iter().any(Type::is_generic_alias) { + return; + } + resolved_bases.push(ClassBase::Generic); + } + + match original_bases { // `builtins.object` is the special case: // the only class in Python that has an MRO with length <2 [] if class.is_object(db) => Ok(Self::from([ @@ -93,7 +124,7 @@ impl<'db> Mro<'db> { // ``` [] => { // e.g. `class Foo[T]: ...` implicitly has `Generic` inserted into its bases - if class.is_generic() { + if class.has_pep_695_type_params(db) { Ok(Self::from([ ClassBase::Class(class), ClassBase::Generic, @@ -110,13 +141,14 @@ impl<'db> Mro<'db> { // but it's a common case (i.e., worth optimizing for), // and the `c3_merge` function requires lots of allocations. [single_base] - if !matches!( - single_base, - Type::GenericAlias(_) - | Type::KnownInstance( - KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) - ) - ) => + if !class.has_pep_695_type_params(db) + && !matches!( + single_base, + Type::GenericAlias(_) + | Type::KnownInstance( + KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) + ) + ) => { ClassBase::try_from_type(db, *single_base).map_or_else( || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), @@ -137,31 +169,21 @@ impl<'db> Mro<'db> { // We'll fallback to a full implementation of the C3-merge algorithm to determine // what MRO Python will give this class at runtime // (if an MRO is indeed resolvable at all!) - original_bases => { + _ => { let mut resolved_bases = vec![]; let mut invalid_bases = vec![]; for (i, base) in original_bases.iter().enumerate() { - // This emulates the behavior of `typing._GenericAlias.__mro_entries__` at - // . - // - // Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere + // Note that we emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere // (see `infer::TypeInferenceBuilder::check_class_definitions`), // which is why we only care about `KnownInstanceType::Generic(Some(_))`, // not `KnownInstanceType::Generic(None)`. if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base { - if original_bases - .contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) - { - continue; - } - if original_bases[i + 1..] - .iter() - .any(|b| b.is_generic_alias() && b != base) - { - continue; - } - resolved_bases.push(ClassBase::Generic); + maybe_add_generic( + &mut resolved_bases, + original_bases, + &original_bases[i + 1..], + ); } else { match ClassBase::try_from_type(db, *base) { Some(valid_base) => resolved_bases.push(valid_base), @@ -174,6 +196,12 @@ impl<'db> Mro<'db> { return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice())); } + // `Generic` is implicitly added to the bases list of a class that has PEP-695 type parameters + // (documented at https://docs.python.org/3/reference/compound_stmts.html#generic-classes) + if class.has_pep_695_type_params(db) { + maybe_add_generic(&mut resolved_bases, original_bases, &[]); + } + let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])]; for base in &resolved_bases { if base.has_cyclic_mro(db) { @@ -192,6 +220,18 @@ impl<'db> Mro<'db> { return Ok(mro); } + // We now know that the MRO is unresolvable through the C3-merge algorithm. + // The rest of this function is dedicated to figuring out the best error message + // to report to the user. + + if class.has_pep_695_type_params(db) + && original_bases.iter().any(|base| { + matches!(base, Type::KnownInstance(KnownInstanceType::Generic(_))) + }) + { + return Err(MroErrorKind::Pep695ClassWithGenericInheritance); + } + let mut duplicate_dynamic_bases = false; let duplicate_bases: Vec> = { @@ -416,6 +456,9 @@ pub(super) enum MroErrorKind<'db> { /// See [`DuplicateBaseError`] for more details. DuplicateBases(Box<[DuplicateBaseError<'db>]>), + /// The class uses PEP-695 parameters and also inherits from `Generic[]`. + Pep695ClassWithGenericInheritance, + /// A cycle was encountered resolving the class' bases. InheritanceCycle, From 6453ac9ea13a6d7c684fc7a8e02d69cebf4339ff Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 26 May 2025 21:44:43 +0100 Subject: [PATCH 248/487] [ty] Tell the user why we inferred a certain Python version when reporting version-specific syntax errors (#18295) --- crates/ty/docs/rules.md | 108 +++++++++--------- crates/ty/tests/cli.rs | 73 +++++++++++- crates/ty_project/src/lib.rs | 13 +-- crates/ty_python_semantic/src/lib.rs | 1 + crates/ty_python_semantic/src/types.rs | 1 + .../src/types/diagnostic.rs | 54 ++------- crates/ty_python_semantic/src/types/infer.rs | 6 +- .../src/util/diagnostics.rs | 51 +++++++++ crates/ty_python_semantic/src/util/mod.rs | 1 + 9 files changed, 201 insertions(+), 107 deletions(-) create mode 100644 crates/ty_python_semantic/src/util/diagnostics.rs diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 40a2e2fa13da97..731d08c6452ca7 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873) ## `invalid-type-variable-constraints` @@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897) ## `missing-argument` @@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) ## `no-matching-overload` @@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945) ## `non-subscriptable` @@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968) ## `not-iterable` @@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986) ## `parameter-already-assigned` @@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037) ## `raw-string-type-annotation` @@ -1231,7 +1231,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373) ## `subclass-of-final-class` @@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128) ## `too-many-positional-arguments` @@ -1285,7 +1285,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173) ## `type-assertion-failure` @@ -1312,7 +1312,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1356,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194) ## `unknown-argument` @@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) ## `unresolved-attribute` @@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) ## `unresolved-import` @@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) ## `unresolved-reference` @@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) ## `unsupported-bool-conversion` @@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006) ## `unsupported-operator` @@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332) ## `zero-stepsize-in-slice` @@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354) ## `invalid-ignore-comment` @@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058) ## `possibly-unbound-implicit-call` @@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111) ## `possibly-unbound-import` @@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) ## `redundant-cast` @@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425) ## `undefined-reveal` @@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233) ## `unknown-rule` @@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489) ## `division-by-zero` @@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240) ## `possibly-unresolved-reference` @@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106) ## `unused-ignore-comment` diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 5a7003052813a7..344c41a9ffdc34 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -242,7 +242,7 @@ fn config_override_python_platform() -> anyhow::Result<()> { } #[test] -fn config_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { +fn config_file_annotation_showing_where_python_version_set_typing_error() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", @@ -308,6 +308,77 @@ fn config_file_annotation_showing_where_python_version_set() -> anyhow::Result<( Ok(()) } +#[test] +fn config_file_annotation_showing_where_python_version_set_syntax_error() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [project] + requires-python = ">=3.8" + "#, + ), + ( + "test.py", + r#" + match object(): + case int(): + pass + case _: + pass + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:2:1 + | + 2 | match object(): + | ^^^^^ Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10) + 3 | case int(): + 4 | pass + | + info: Python 3.8 was assumed when parsing syntax + --> pyproject.toml:3:19 + | + 2 | [project] + 3 | requires-python = ">=3.8" + | ^^^^^^^ Python 3.8 assumed due to this configuration setting + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:2:1 + | + 2 | match object(): + | ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + 3 | case int(): + 4 | pass + | + info: Python 3.9 was assumed when parsing syntax because it was specified on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + /// Paths specified on the CLI are relative to the current working directory and not the project root. /// /// We test this by adding an extra search path from the CLI to the libs directory when diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index bbe84382af79eb..2c79edf9def262 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -23,8 +23,8 @@ use std::sync::Arc; use thiserror::Error; use tracing::error; use ty_python_semantic::lint::{LintRegistry, LintRegistryBuilder, RuleSelection}; -use ty_python_semantic::register_lints; use ty_python_semantic::types::check_types; +use ty_python_semantic::{add_inferred_python_version_hint_to_diagnostic, register_lints}; pub mod combine; @@ -460,12 +460,11 @@ fn check_file_impl(db: &dyn Db, file: File) -> Vec { .map(|error| create_parse_diagnostic(file, error)), ); - diagnostics.extend( - parsed - .unsupported_syntax_errors() - .iter() - .map(|error| create_unsupported_syntax_diagnostic(file, error)), - ); + diagnostics.extend(parsed.unsupported_syntax_errors().iter().map(|error| { + let mut error = create_unsupported_syntax_diagnostic(file, error); + add_inferred_python_version_hint_to_diagnostic(db.upcast(), &mut error, "parsing syntax"); + error + })); { let db = AssertUnwindSafe(db); diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index 8857a48341f772..11dfbae3a03aa0 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -14,6 +14,7 @@ pub use program::{ pub use python_platform::PythonPlatform; pub use semantic_model::{HasType, SemanticModel}; pub use site_packages::SysPrefixPathOrigin; +pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic; pub mod ast_node_ref; mod db; diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 5a0f3f9f1e6c92..d88f98481fa531 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -50,6 +50,7 @@ use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; use crate::types::signatures::{Parameter, ParameterForm, Parameters}; +pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic; use crate::{Db, FxOrderSet, Module, Program}; pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass}; use instance::Protocol; diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index c996be3d0bf109..a4dfbc7f7828d7 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1,8 +1,12 @@ use super::call::CallErrorKind; use super::context::InferContext; use super::mro::DuplicateBaseError; -use super::{CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass}; +use super::{ + CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass, + add_inferred_python_version_hint_to_diagnostic, +}; use crate::db::Db; +use crate::declare_lint; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; use crate::types::LintDiagnosticGuard; @@ -12,10 +16,8 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; -use crate::{Program, PythonVersionWithSource, declare_lint}; use itertools::Itertools; -use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; -use ruff_db::files::system_path_to_file; +use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_python_stdlib::builtins::version_builtin_was_added; use ruff_text_size::{Ranged, TextRange}; @@ -1762,44 +1764,6 @@ pub(super) fn report_possibly_unbound_attribute( )); } -pub(super) fn add_inferred_python_version_hint(db: &dyn Db, mut diagnostic: LintDiagnosticGuard) { - let program = Program::get(db); - let PythonVersionWithSource { version, source } = program.python_version_with_source(db); - - match source { - crate::PythonVersionSource::Cli => { - diagnostic.info(format_args!( - "Python {version} was assumed when resolving types because it was specified on the command line", - )); - } - crate::PythonVersionSource::File(path, range) => { - if let Ok(file) = system_path_to_file(db.upcast(), &**path) { - let mut sub_diagnostic = SubDiagnostic::new( - Severity::Info, - format_args!("Python {version} was assumed when resolving types"), - ); - sub_diagnostic.annotate( - Annotation::primary(Span::from(file).with_optional_range(*range)).message( - format_args!("Python {version} assumed due to this configuration setting"), - ), - ); - diagnostic.sub(sub_diagnostic); - } else { - diagnostic.info(format_args!( - "Python {version} was assumed when resolving types because of your configuration file(s)", - )); - } - } - crate::PythonVersionSource::Default => { - diagnostic.info(format_args!( - "Python {version} was assumed when resolving types \ - because it is the newest Python version supported by ty, \ - and neither a command-line argument nor a configuration setting was provided", - )); - } - } -} - pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) { let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else { return; @@ -1811,7 +1775,11 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node diagnostic.info(format_args!( "`{id}` was added as a builtin in Python 3.{version_added_to_builtins}" )); - add_inferred_python_version_hint(context.db(), diagnostic); + add_inferred_python_version_hint_to_diagnostic( + context.db(), + &mut diagnostic, + "resolving types", + ); } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index d3b93fcab0c683..950aba851d00af 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -114,7 +114,9 @@ use super::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, }; use super::subclass_of::SubclassOfInner; -use super::{BoundSuperError, BoundSuperType, ClassBase}; +use super::{ + BoundSuperError, BoundSuperType, ClassBase, add_inferred_python_version_hint_to_diagnostic, +}; /// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope. /// Use when checking a scope, or needing to provide a type for an arbitrary expression in the @@ -6029,7 +6031,7 @@ impl<'db> TypeInferenceBuilder<'db> { diag.info( "Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later", ); - diagnostic::add_inferred_python_version_hint(db, diag); + add_inferred_python_version_hint_to_diagnostic(db, &mut diag, "resolving types"); } } Type::unknown() diff --git a/crates/ty_python_semantic/src/util/diagnostics.rs b/crates/ty_python_semantic/src/util/diagnostics.rs new file mode 100644 index 00000000000000..c9949f0bddb044 --- /dev/null +++ b/crates/ty_python_semantic/src/util/diagnostics.rs @@ -0,0 +1,51 @@ +use crate::{Db, Program, PythonVersionWithSource}; +use ruff_db::{ + diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}, + files::system_path_to_file, +}; + +/// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred. +/// +/// ty can infer the Python version from various sources, such as command-line arguments, +/// configuration files, or defaults. +pub fn add_inferred_python_version_hint_to_diagnostic( + db: &dyn Db, + diagnostic: &mut Diagnostic, + action: &str, +) { + let program = Program::get(db); + let PythonVersionWithSource { version, source } = program.python_version_with_source(db); + + match source { + crate::PythonVersionSource::Cli => { + diagnostic.info(format_args!( + "Python {version} was assumed when {action} because it was specified on the command line", + )); + } + crate::PythonVersionSource::File(path, range) => { + if let Ok(file) = system_path_to_file(db.upcast(), &**path) { + let mut sub_diagnostic = SubDiagnostic::new( + Severity::Info, + format_args!("Python {version} was assumed when {action}"), + ); + sub_diagnostic.annotate( + Annotation::primary(Span::from(file).with_optional_range(*range)).message( + format_args!("Python {version} assumed due to this configuration setting"), + ), + ); + diagnostic.sub(sub_diagnostic); + } else { + diagnostic.info(format_args!( + "Python {version} was assumed when {action} because of your configuration file(s)", + )); + } + } + crate::PythonVersionSource::Default => { + diagnostic.info(format_args!( + "Python {version} was assumed when {action} \ + because it is the newest Python version supported by ty, \ + and neither a command-line argument nor a configuration setting was provided", + )); + } + } +} diff --git a/crates/ty_python_semantic/src/util/mod.rs b/crates/ty_python_semantic/src/util/mod.rs index 0f80fb9c6ef098..54555cd6173a61 100644 --- a/crates/ty_python_semantic/src/util/mod.rs +++ b/crates/ty_python_semantic/src/util/mod.rs @@ -1 +1,2 @@ +pub(crate) mod diagnostics; pub(crate) mod subscript; From 8d5655a7baa1ecc84a906cbfc0e2294cf173dee7 Mon Sep 17 00:00:00 2001 From: justin Date: Mon, 26 May 2025 23:00:38 -0700 Subject: [PATCH 249/487] [ty] Add --config-file CLI arg (#18083) --- crates/ruff_benchmark/benches/ty.rs | 4 +- crates/ty/Cargo.toml | 2 +- crates/ty/docs/cli.md | 4 +- crates/ty/src/args.rs | 6 + crates/ty/src/lib.rs | 31 ++-- crates/ty/tests/cli.rs | 57 ++++++++ crates/ty/tests/file_watching.rs | 132 ++++++++++++++---- crates/ty_project/src/db/changes.rs | 32 ++++- crates/ty_project/src/lib.rs | 2 +- crates/ty_project/src/metadata.rs | 49 +++++-- .../src/metadata/configuration_file.rs | 25 ++++ crates/ty_project/src/metadata/options.rs | 19 ++- 12 files changed, 300 insertions(+), 63 deletions(-) diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index c36e1202740825..fe974de25b47a7 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -78,7 +78,7 @@ fn setup_tomllib_case() -> Case { let src_root = SystemPath::new("/src"); let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap(); - metadata.apply_cli_options(Options { + metadata.apply_options(Options { environment: Some(EnvironmentOptions { python_version: Some(RangedValue::cli(PythonVersion::PY312)), ..EnvironmentOptions::default() @@ -224,7 +224,7 @@ fn setup_micro_case(code: &str) -> Case { let src_root = SystemPath::new("/src"); let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap(); - metadata.apply_cli_options(Options { + metadata.apply_options(Options { environment: Some(EnvironmentOptions { python_version: Some(RangedValue::cli(PythonVersion::PY312)), ..EnvironmentOptions::default() diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index cd287501fbbaf7..cd8f7b32ea5bc0 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -22,7 +22,7 @@ ty_server = { workspace = true } anyhow = { workspace = true } argfile = { workspace = true } -clap = { workspace = true, features = ["wrap_help", "string"] } +clap = { workspace = true, features = ["wrap_help", "string", "env"] } clap_complete_command = { workspace = true } colored = { workspace = true } countme = { workspace = true, features = ["enable"] } diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index 3df1548a31c1b3..c98543117e217e 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -47,7 +47,9 @@ ty check [OPTIONS] [PATH]... overriding a specific configuration option.

Overrides of individual settings using this option always take precedence over all configuration files.

-
--error rule

Treat the given rule as having severity 'error'. Can be specified multiple times.

+
--config-file path

The path to a ty.toml file to use for configuration.

+

While ty configuration can be included in a pyproject.toml file, it is not allowed in this context.

+

May also be set with the TY_CONFIG_FILE environment variable.

--error rule

Treat the given rule as having severity 'error'. Can be specified multiple times.

--error-on-warning

Use exit code 1 if there are any warning-level diagnostics

--exit-zero

Always use exit code 0, even when there are error-level diagnostics

--extra-search-path path

Additional path to use as a module-resolution source (can be passed multiple times)

diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index 9a48b310ec0563..d7f49f79f8e2b2 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -107,6 +107,12 @@ pub(crate) struct CheckCommand { #[clap(flatten)] pub(crate) config: ConfigsArg, + /// The path to a `ty.toml` file to use for configuration. + /// + /// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context. + #[arg(long, env = "TY_CONFIG_FILE", value_name = "PATH")] + pub(crate) config_file: Option, + /// The format to use for printing diagnostic messages. #[arg(long)] pub(crate) output_format: Option, diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index ec66b60ca57d37..91efddde605f7a 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -23,7 +23,7 @@ use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity}; use ruff_db::max_parallelism; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; use salsa::plumbing::ZalsaDatabase; -use ty_project::metadata::options::Options; +use ty_project::metadata::options::ProjectOptionsOverrides; use ty_project::watch::ProjectWatcher; use ty_project::{Db, DummyReporter, Reporter, watch}; use ty_project::{ProjectDatabase, ProjectMetadata}; @@ -102,13 +102,21 @@ fn run_check(args: CheckCommand) -> anyhow::Result { .map(|path| SystemPath::absolute(path, &cwd)) .collect(); - let system = OsSystem::new(cwd); + let system = OsSystem::new(&cwd); let watch = args.watch; let exit_zero = args.exit_zero; + let config_file = args + .config_file + .as_ref() + .map(|path| SystemPath::absolute(path, &cwd)); + + let mut project_metadata = match &config_file { + Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?, + None => ProjectMetadata::discover(&project_path, &system)?, + }; - let cli_options = args.into_options(); - let mut project_metadata = ProjectMetadata::discover(&project_path, &system)?; - project_metadata.apply_cli_options(cli_options.clone()); + let options = args.into_options(); + project_metadata.apply_options(options.clone()); project_metadata.apply_configuration_files(&system)?; let mut db = ProjectDatabase::new(project_metadata, system)?; @@ -117,7 +125,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result { db.project().set_included_paths(&mut db, check_paths); } - let (main_loop, main_loop_cancellation_token) = MainLoop::new(cli_options); + let project_options_overrides = ProjectOptionsOverrides::new(config_file, options); + let (main_loop, main_loop_cancellation_token) = MainLoop::new(project_options_overrides); // Listen to Ctrl+C and abort the watch mode. let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token)); @@ -178,11 +187,13 @@ struct MainLoop { /// The file system watcher, if running in watch mode. watcher: Option, - cli_options: Options, + project_options_overrides: ProjectOptionsOverrides, } impl MainLoop { - fn new(cli_options: Options) -> (Self, MainLoopCancellationToken) { + fn new( + project_options_overrides: ProjectOptionsOverrides, + ) -> (Self, MainLoopCancellationToken) { let (sender, receiver) = crossbeam_channel::bounded(10); ( @@ -190,7 +201,7 @@ impl MainLoop { sender: sender.clone(), receiver, watcher: None, - cli_options, + project_options_overrides, }, MainLoopCancellationToken { sender }, ) @@ -340,7 +351,7 @@ impl MainLoop { MainLoopMessage::ApplyChanges(changes) => { revision += 1; // Automatically cancels any pending queries and waits for them to complete. - db.apply_changes(changes, Some(&self.cli_options)); + db.apply_changes(changes, Some(&self.project_options_overrides)); if let Some(watcher) = self.watcher.as_mut() { watcher.update(db); } diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 344c41a9ffdc34..a2892e4f71ce0f 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -1700,6 +1700,63 @@ fn check_conda_prefix_var_to_resolve_path() -> anyhow::Result<()> { ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. "); + + Ok(()) +} + +#[test] +fn config_file_override() -> anyhow::Result<()> { + // Set `error-on-warning` to true in the configuration file + // Explicitly set `--warn unresolved-reference` to ensure the rule warns instead of errors + let case = TestCase::with_files(vec![ + ("test.py", r"print(x) # [unresolved-reference]"), + ( + "ty-override.toml", + r#" + [terminal] + error-on-warning = true + "#, + ), + ])?; + + // Ensure flag works via CLI arg + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config-file").arg("ty-override.toml"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Ensure the flag works via an environment variable + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").env("TY_CONFIG_FILE", "ty-override.toml"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) } diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index 848117bf515833..71c50ff6ebbe67 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -10,7 +10,7 @@ use ruff_db::system::{ }; use ruff_db::{Db as _, Upcast}; use ruff_python_ast::PythonVersion; -use ty_project::metadata::options::{EnvironmentOptions, Options}; +use ty_project::metadata::options::{EnvironmentOptions, Options, ProjectOptionsOverrides}; use ty_project::metadata::pyproject::{PyProject, Tool}; use ty_project::metadata::value::{RangedValue, RelativePathBuf}; use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher}; @@ -164,8 +164,12 @@ impl TestCase { Ok(all_events) } - fn apply_changes(&mut self, changes: Vec) { - self.db.apply_changes(changes, None); + fn apply_changes( + &mut self, + changes: Vec, + project_options_overrides: Option<&ProjectOptionsOverrides>, + ) { + self.db.apply_changes(changes, project_options_overrides); } fn update_options(&mut self, options: Options) -> anyhow::Result<()> { @@ -180,7 +184,7 @@ impl TestCase { .context("Failed to write configuration")?; let changes = self.take_watch_changes(event_for_file("pyproject.toml")); - self.apply_changes(changes); + self.apply_changes(changes, None); if let Some(watcher) = &mut self.watcher { watcher.update(&self.db); @@ -476,7 +480,7 @@ fn new_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); let foo = case.system_file(&foo_path).expect("foo.py to exist."); @@ -499,7 +503,7 @@ fn new_ignored_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(case.system_file(&foo_path).is_ok()); case.assert_indexed_project_files([bar_file]); @@ -535,7 +539,7 @@ fn new_non_project_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("black.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(case.system_file(&black_path).is_ok()); @@ -576,7 +580,7 @@ fn new_files_with_explicit_included_paths() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("test2.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); let sub_a_file = case.system_file(&sub_a_path).expect("sub/a.py to exist"); @@ -621,7 +625,7 @@ fn new_file_in_included_out_of_project_directory() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("script2.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); let src_a_file = case.system_file(&src_a).unwrap(); let outside_b_file = case.system_file(&outside_b_path).unwrap(); @@ -648,7 +652,7 @@ fn changed_file() -> anyhow::Result<()> { assert!(!changes.is_empty()); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 2')"); case.assert_indexed_project_files([foo]); @@ -671,7 +675,7 @@ fn deleted_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(!foo.exists(case.db())); case.assert_indexed_project_files([]); @@ -703,7 +707,7 @@ fn move_file_to_trash() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(!foo.exists(case.db())); case.assert_indexed_project_files([]); @@ -730,7 +734,7 @@ fn move_file_to_project() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); let foo_in_project = case.system_file(&foo_in_project)?; @@ -755,7 +759,7 @@ fn rename_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("bar.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(!foo.exists(case.db())); @@ -796,7 +800,7 @@ fn directory_moved_to_project() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("sub")); - case.apply_changes(changes); + case.apply_changes(changes, None); let init_file = case .system_file(sub_new_path.join("__init__.py")) @@ -853,7 +857,7 @@ fn directory_moved_to_trash() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("sub")); - case.apply_changes(changes); + case.apply_changes(changes, None); // `import sub.a` should no longer resolve assert!( @@ -916,7 +920,7 @@ fn directory_renamed() -> anyhow::Result<()> { // Linux and windows only emit an event for the newly created root directory, but not for every new component. let changes = case.stop_watch(event_for_file("sub")); - case.apply_changes(changes); + case.apply_changes(changes, None); // `import sub.a` should no longer resolve assert!( @@ -989,7 +993,7 @@ fn directory_deleted() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("sub")); - case.apply_changes(changes); + case.apply_changes(changes, None); // `import sub.a` should no longer resolve assert!( @@ -1035,7 +1039,7 @@ fn search_path() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("a.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(resolve_module(case.db().upcast(), &ModuleName::new_static("a").unwrap()).is_some()); case.assert_indexed_project_files([case.system_file(case.project_path("bar.py")).unwrap()]); @@ -1066,7 +1070,7 @@ fn add_search_path() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("a.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(resolve_module(case.db().upcast(), &ModuleName::new_static("a").unwrap()).is_some()); @@ -1213,7 +1217,7 @@ fn changed_versions_file() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("VERSIONS")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert!(resolve_module(case.db(), &ModuleName::new("os").unwrap()).is_some()); @@ -1267,7 +1271,7 @@ fn hard_links_in_project() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 2')"); @@ -1338,7 +1342,7 @@ fn hard_links_to_target_outside_project() -> anyhow::Result<()> { let changes = case.stop_watch(ChangeEvent::is_changed); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!(source_text(case.db(), bar).as_str(), "print('Version 2')"); @@ -1377,7 +1381,7 @@ mod unix { let changes = case.stop_watch(event_for_file("foo.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!( foo.permissions(case.db()), @@ -1460,7 +1464,7 @@ mod unix { let changes = case.take_watch_changes(event_for_file("baz.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!( source_text(case.db(), baz_file).as_str(), @@ -1473,7 +1477,7 @@ mod unix { let changes = case.stop_watch(event_for_file("baz.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!( source_text(case.db(), baz_file).as_str(), @@ -1544,7 +1548,7 @@ mod unix { let changes = case.stop_watch(event_for_file("baz.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); // The file watcher is guaranteed to emit one event for the changed file, but it isn't specified // if the event is emitted for the "original" or linked path because both paths are watched. @@ -1658,7 +1662,7 @@ mod unix { let changes = case.stop_watch(event_for_file("baz.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); assert_eq!( source_text(case.db(), baz_original_file).as_str(), @@ -1715,7 +1719,7 @@ fn nested_projects_delete_root() -> anyhow::Result<()> { let changes = case.stop_watch(ChangeEvent::is_deleted); - case.apply_changes(changes); + case.apply_changes(changes, None); // It should now pick up the outer project. assert_eq!(case.db().project().root(case.db()), case.root_path()); @@ -1781,7 +1785,73 @@ fn changes_to_user_configuration() -> anyhow::Result<()> { let changes = case.stop_watch(event_for_file("ty.toml")); - case.apply_changes(changes); + case.apply_changes(changes, None); + + let diagnostics = case.db().check_file(foo); + + assert!( + diagnostics.len() == 1, + "Expected exactly one diagnostic but got: {diagnostics:#?}" + ); + + Ok(()) +} + +#[test] +fn changes_to_config_file_override() -> anyhow::Result<()> { + let mut case = setup(|context: &mut SetupContext| { + std::fs::write( + context.join_project_path("pyproject.toml").as_std_path(), + r#" + [project] + name = "test" + "#, + )?; + + std::fs::write( + context.join_project_path("foo.py").as_std_path(), + "a = 10 / 0", + )?; + + std::fs::write( + context.join_project_path("ty-override.toml").as_std_path(), + r#" + [rules] + division-by-zero = "ignore" + "#, + )?; + + Ok(()) + })?; + + let foo = case + .system_file(case.project_path("foo.py")) + .expect("foo.py to exist"); + let diagnostics = case.db().check_file(foo); + + assert!( + diagnostics.is_empty(), + "Expected no diagnostics but got: {diagnostics:#?}" + ); + + // Enable division-by-zero in the explicitly specified configuration with warning severity + update_file( + case.project_path("ty-override.toml"), + r#" + [rules] + division-by-zero = "warn" + "#, + )?; + + let changes = case.stop_watch(event_for_file("ty-override.toml")); + + case.apply_changes( + changes, + Some(&ProjectOptionsOverrides::new( + Some(case.project_path("ty-override.toml")), + Options::default(), + )), + ); let diagnostics = case.db().check_file(foo); @@ -1855,7 +1925,7 @@ fn rename_files_casing_only() -> anyhow::Result<()> { } let changes = case.stop_watch(event_for_file("Lib.py")); - case.apply_changes(changes); + case.apply_changes(changes, None); // Resolving `lib` should now fail but `Lib` should now succeed assert_eq!( diff --git a/crates/ty_project/src/db/changes.rs b/crates/ty_project/src/db/changes.rs index cf36a1946a687f..f2f72ae14090f3 100644 --- a/crates/ty_project/src/db/changes.rs +++ b/crates/ty_project/src/db/changes.rs @@ -1,5 +1,5 @@ use crate::db::{Db, ProjectDatabase}; -use crate::metadata::options::Options; +use crate::metadata::options::ProjectOptionsOverrides; use crate::watch::{ChangeEvent, CreatedKind, DeletedKind}; use crate::{Project, ProjectMetadata}; use std::collections::BTreeSet; @@ -12,10 +12,18 @@ use rustc_hash::FxHashSet; use ty_python_semantic::Program; impl ProjectDatabase { - #[tracing::instrument(level = "debug", skip(self, changes, cli_options))] - pub fn apply_changes(&mut self, changes: Vec, cli_options: Option<&Options>) { + #[tracing::instrument(level = "debug", skip(self, changes, project_options_overrides))] + pub fn apply_changes( + &mut self, + changes: Vec, + project_options_overrides: Option<&ProjectOptionsOverrides>, + ) { let mut project = self.project(); let project_root = project.root(self).to_path_buf(); + let config_file_override = + project_options_overrides.and_then(|options| options.config_file_override.clone()); + let options = + project_options_overrides.map(|project_options| project_options.options.clone()); let program = Program::get(self); let custom_stdlib_versions_path = program .custom_stdlib_search_path(self) @@ -42,6 +50,14 @@ impl ProjectDatabase { tracing::trace!("Handle change: {:?}", change); if let Some(path) = change.system_path() { + if let Some(config_file) = &config_file_override { + if config_file.as_path() == path { + project_changed = true; + + continue; + } + } + if matches!( path.file_name(), Some(".gitignore" | ".ignore" | "ty.toml" | "pyproject.toml") @@ -170,10 +186,14 @@ impl ProjectDatabase { } if project_changed { - match ProjectMetadata::discover(&project_root, self.system()) { + let new_project_metadata = match config_file_override { + Some(config_file) => ProjectMetadata::from_config_file(config_file, self.system()), + None => ProjectMetadata::discover(&project_root, self.system()), + }; + match new_project_metadata { Ok(mut metadata) => { - if let Some(cli_options) = cli_options { - metadata.apply_cli_options(cli_options.clone()); + if let Some(cli_options) = options { + metadata.apply_options(cli_options); } if let Err(error) = metadata.apply_configuration_files(self.system()) { diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index 2c79edf9def262..6c7b95c256fe05 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -5,7 +5,7 @@ use crate::walk::{ProjectFilesFilter, ProjectFilesWalker}; pub use db::{Db, ProjectDatabase}; use files::{Index, Indexed, IndexedFiles}; use metadata::settings::Settings; -pub use metadata::{ProjectDiscoveryError, ProjectMetadata}; +pub use metadata::{ProjectMetadata, ProjectMetadataError}; use ruff_db::diagnostic::{ Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic, create_parse_diagnostic, create_unsupported_syntax_diagnostic, diff --git a/crates/ty_project/src/metadata.rs b/crates/ty_project/src/metadata.rs index 2d866114cab667..47896e503113da 100644 --- a/crates/ty_project/src/metadata.rs +++ b/crates/ty_project/src/metadata.rs @@ -48,6 +48,29 @@ impl ProjectMetadata { } } + pub fn from_config_file( + path: SystemPathBuf, + system: &dyn System, + ) -> Result { + tracing::debug!("Using overridden configuration file at '{path}'"); + + let config_file = ConfigurationFile::from_path(path.clone(), system).map_err(|error| { + ProjectMetadataError::ConfigurationFileError { + source: Box::new(error), + path: path.clone(), + } + })?; + + let options = config_file.into_options(); + + Ok(Self { + name: Name::new(system.current_directory().file_name().unwrap_or("root")), + root: system.current_directory().to_path_buf(), + options, + extra_configuration_paths: vec![path], + }) + } + /// Loads a project from a `pyproject.toml` file. pub(crate) fn from_pyproject( pyproject: PyProject, @@ -106,11 +129,11 @@ impl ProjectMetadata { pub fn discover( path: &SystemPath, system: &dyn System, - ) -> Result { + ) -> Result { tracing::debug!("Searching for a project in '{path}'"); if !system.is_directory(path) { - return Err(ProjectDiscoveryError::NotADirectory(path.to_path_buf())); + return Err(ProjectMetadataError::NotADirectory(path.to_path_buf())); } let mut closest_project: Option = None; @@ -125,7 +148,7 @@ impl ProjectMetadata { ) { Ok(pyproject) => Some(pyproject), Err(error) => { - return Err(ProjectDiscoveryError::InvalidPyProject { + return Err(ProjectMetadataError::InvalidPyProject { path: pyproject_path, source: Box::new(error), }); @@ -144,7 +167,7 @@ impl ProjectMetadata { ) { Ok(options) => options, Err(error) => { - return Err(ProjectDiscoveryError::InvalidTyToml { + return Err(ProjectMetadataError::InvalidTyToml { path: ty_toml_path, source: Box::new(error), }); @@ -171,7 +194,7 @@ impl ProjectMetadata { .and_then(|pyproject| pyproject.project.as_ref()), ) .map_err(|err| { - ProjectDiscoveryError::InvalidRequiresPythonConstraint { + ProjectMetadataError::InvalidRequiresPythonConstraint { source: err, path: pyproject_path, } @@ -185,7 +208,7 @@ impl ProjectMetadata { let metadata = ProjectMetadata::from_pyproject(pyproject, project_root.to_path_buf()) .map_err( - |err| ProjectDiscoveryError::InvalidRequiresPythonConstraint { + |err| ProjectMetadataError::InvalidRequiresPythonConstraint { source: err, path: pyproject_path, }, @@ -249,7 +272,7 @@ impl ProjectMetadata { } /// Combine the project options with the CLI options where the CLI options take precedence. - pub fn apply_cli_options(&mut self, options: Options) { + pub fn apply_options(&mut self, options: Options) { self.options = options.combine(std::mem::take(&mut self.options)); } @@ -282,7 +305,7 @@ impl ProjectMetadata { } #[derive(Debug, Error)] -pub enum ProjectDiscoveryError { +pub enum ProjectMetadataError { #[error("project path '{0}' is not a directory")] NotADirectory(SystemPathBuf), @@ -303,6 +326,12 @@ pub enum ProjectDiscoveryError { source: ResolveRequiresPythonError, path: SystemPathBuf, }, + + #[error("Error loading configuration file at {path}: {source}")] + ConfigurationFileError { + source: Box, + path: SystemPathBuf, + }, } #[cfg(test)] @@ -314,7 +343,7 @@ mod tests { use ruff_db::system::{SystemPathBuf, TestSystem}; use ruff_python_ast::PythonVersion; - use crate::{ProjectDiscoveryError, ProjectMetadata}; + use crate::{ProjectMetadata, ProjectMetadataError}; #[test] fn project_without_pyproject() -> anyhow::Result<()> { @@ -1076,7 +1105,7 @@ expected `.`, `]` } #[track_caller] - fn assert_error_eq(error: &ProjectDiscoveryError, message: &str) { + fn assert_error_eq(error: &ProjectMetadataError, message: &str) { assert_eq!(error.to_string().replace('\\', "/"), message); } diff --git a/crates/ty_project/src/metadata/configuration_file.rs b/crates/ty_project/src/metadata/configuration_file.rs index 4190d2cd8b8f65..ad985cdff5c706 100644 --- a/crates/ty_project/src/metadata/configuration_file.rs +++ b/crates/ty_project/src/metadata/configuration_file.rs @@ -14,6 +14,25 @@ pub(crate) struct ConfigurationFile { } impl ConfigurationFile { + pub(crate) fn from_path( + path: SystemPathBuf, + system: &dyn System, + ) -> Result { + let ty_toml_str = system.read_to_string(&path).map_err(|source| { + ConfigurationFileError::FileReadError { + source, + path: path.clone(), + } + })?; + + match Options::from_toml_str(&ty_toml_str, ValueSource::File(Arc::new(path.clone()))) { + Ok(options) => Ok(Self { path, options }), + Err(error) => Err(ConfigurationFileError::InvalidTyToml { + source: Box::new(error), + path, + }), + } + } /// Loads the user-level configuration file if it exists. /// /// Returns `None` if the file does not exist or if the concept of user-level configurations @@ -66,4 +85,10 @@ pub enum ConfigurationFileError { source: Box, path: SystemPathBuf, }, + #[error("Failed to read `{path}`: {source}")] + FileReadError { + #[source] + source: std::io::Error, + path: SystemPathBuf, + }, } diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 0581684faf1dcd..2e089d334f2fd4 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -2,7 +2,7 @@ use crate::Db; use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSourceGuard}; use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, Severity, Span}; use ruff_db::files::system_path_to_file; -use ruff_db::system::{System, SystemPath}; +use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_macros::{Combine, OptionsMetadata}; use ruff_python_ast::PythonVersion; use rustc_hash::FxHashMap; @@ -575,3 +575,20 @@ impl OptionDiagnostic { } } } + +/// This is a wrapper for options that actually get loaded from configuration files +/// and the CLI, which also includes a `config_file_override` option that overrides +/// default configuration discovery with an explicitly-provided path to a configuration file +pub struct ProjectOptionsOverrides { + pub config_file_override: Option, + pub options: Options, +} + +impl ProjectOptionsOverrides { + pub fn new(config_file_override: Option, options: Options) -> Self { + Self { + config_file_override, + options, + } + } +} From 9ec4a178a44be2592162a099e75c14e825383f53 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 27 May 2025 17:51:19 +0100 Subject: [PATCH 250/487] [ty] Move arviz off the list of selected primer projects (#18336) --- crates/ty_python_semantic/resources/primer/bad.txt | 1 + crates/ty_python_semantic/resources/primer/good.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index d54f0407e84202..43b465e959d247 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -1,6 +1,7 @@ Tanjun # hangs antidote # hangs / slow artigraph # cycle panics (value_type_) +arviz # too many iterations on versions of arviz newer than https://github.com/arviz-devs/arviz/commit/3205b82bb4d6097c31f7334d7ac51a6de37002d0 core # cycle panics (value_type_) cpython # access to field whilst being initialized, too many cycle iterations discord.py # some kind of hang, only when multi-threaded? diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index abaebc99b119e5..896083bda1ce16 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -12,7 +12,6 @@ alerta altair anyio apprise -arviz async-utils asynq attrs From e03e05d2b35259f759577fee9f3b7b554121df7d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 27 May 2025 19:08:59 +0100 Subject: [PATCH 251/487] [ty] Simplify `Type::normalized` slightly (#18339) --- crates/ty_python_semantic/src/types.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index d88f98481fa531..ec2aa92f9a546c 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1026,34 +1026,10 @@ impl<'db> Type<'db> { Type::BoundSuper(bound_super) => Type::BoundSuper(bound_super.normalized(db)), Type::GenericAlias(generic) => Type::GenericAlias(generic.normalized(db)), Type::SubclassOf(subclass_of) => Type::SubclassOf(subclass_of.normalized(db)), + Type::TypeVar(typevar) => Type::TypeVar(typevar.normalized(db)), Type::KnownInstance(known_instance) => { Type::KnownInstance(known_instance.normalized(db)) } - Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) { - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - Type::TypeVar(TypeVarInstance::new( - db, - typevar.name(db).clone(), - typevar.definition(db), - Some(TypeVarBoundOrConstraints::UpperBound(bound.normalized(db))), - typevar.variance(db), - typevar.default_ty(db), - typevar.kind(db), - )) - } - Some(TypeVarBoundOrConstraints::Constraints(union)) => { - Type::TypeVar(TypeVarInstance::new( - db, - typevar.name(db).clone(), - typevar.definition(db), - Some(TypeVarBoundOrConstraints::Constraints(union.normalized(db))), - typevar.variance(db), - typevar.default_ty(db), - typevar.kind(db), - )) - } - None => self, - }, Type::LiteralString | Type::AlwaysFalsy | Type::AlwaysTruthy From 743764d38435bb273f3bf09826a7d10df55ef680 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 27 May 2025 19:32:17 +0100 Subject: [PATCH 252/487] [ty] Simplify `Type::try_bool()` (#18342) ## Summary I don't think we're ever going to add any `KnownInstanceType` variants that evaluate to `False` in a boolean context; the `KnownInstanceType::bool()` method just seems like unnecessary complexity. ## Test Plan `cargo test -p ty_python_semantic` --- crates/ty_python_semantic/src/types.rs | 8 ++- .../src/types/known_instance.rs | 54 +------------------ 2 files changed, 4 insertions(+), 58 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ec2aa92f9a546c..5ee101e973d168 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3413,6 +3413,9 @@ impl<'db> Type<'db> { | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) + | Type::PropertyInstance(_) + | Type::BoundSuper(_) + | Type::KnownInstance(_) | Type::AlwaysTruthy => Truthiness::AlwaysTrue, Type::AlwaysFalsy => Truthiness::AlwaysFalse, @@ -3448,10 +3451,6 @@ impl<'db> Type<'db> { Type::ProtocolInstance(_) => try_dunder_bool()?, - Type::KnownInstance(known_instance) => known_instance.bool(), - - Type::PropertyInstance(_) => Truthiness::AlwaysTrue, - Type::Union(union) => try_union(*union)?, Type::Intersection(_) => { @@ -3464,7 +3463,6 @@ impl<'db> Type<'db> { Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()), Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()), Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()), - Type::BoundSuper(_) => Truthiness::AlwaysTrue, }; Ok(truthiness) diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index a310a9894187fa..afb60ddac885fb 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -11,7 +11,7 @@ use std::fmt::Display; use super::generics::GenericContext; -use super::{ClassType, Truthiness, Type, TypeAliasType, TypeVarInstance, class::KnownClass}; +use super::{ClassType, Type, TypeAliasType, TypeVarInstance, class::KnownClass}; use crate::db::Db; use crate::module_resolver::{KnownModule, file_to_module}; use ruff_db::files::File; @@ -101,58 +101,6 @@ pub enum KnownInstanceType<'db> { } impl<'db> KnownInstanceType<'db> { - /// Evaluate the known instance in boolean context - pub(crate) const fn bool(self) -> Truthiness { - match self { - Self::Annotated - | Self::Literal - | Self::LiteralString - | Self::Optional - // This is a legacy `TypeVar` _outside_ of any generic class or function, so it's - // AlwaysTrue. The truthiness of a typevar inside of a generic class or function - // depends on its bounds and constraints; but that's represented by `Type::TypeVar` and - // handled in elsewhere. - | Self::TypeVar(_) - | Self::Union - | Self::NoReturn - | Self::Never - | Self::Tuple - | Self::Type - | Self::TypingSelf - | Self::Final - | Self::ClassVar - | Self::Callable - | Self::Concatenate - | Self::Unpack - | Self::Required - | Self::NotRequired - | Self::TypeAlias - | Self::TypeGuard - | Self::TypedDict - | Self::TypeIs - | Self::List - | Self::Dict - | Self::DefaultDict - | Self::Set - | Self::FrozenSet - | Self::Counter - | Self::Deque - | Self::ChainMap - | Self::OrderedDict - | Self::Protocol(_) - | Self::Generic(_) - | Self::ReadOnly - | Self::TypeAliasType(_) - | Self::Unknown - | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::Not - | Self::Intersection - | Self::TypeOf - | Self::CallableTypeOf => Truthiness::AlwaysTrue, - } - } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { match self { Self::Annotated From 3e811fc3693260e7bb16c20419ca3cf321c6a922 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 27 May 2025 19:37:01 +0100 Subject: [PATCH 253/487] [ty] Derive `PartialOrd, Ord` for `KnownInstanceType` (#18340) --- .../src/types/known_instance.rs | 9 +- .../src/types/type_ordering.rs | 141 +----------------- 2 files changed, 10 insertions(+), 140 deletions(-) diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index afb60ddac885fb..e21f218b84641c 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -18,7 +18,14 @@ use ruff_db::files::File; /// Enumeration of specific runtime symbols that are special enough /// that they can each be considered to inhabit a unique type. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] +/// +/// # Ordering +/// +/// Ordering between variants is stable and should be the same between runs. +/// Ordering within variants (for variants that wrap associate data) +/// is based on the known-instance's salsa-assigned id and not on its values. +/// The id may change between runs, or when the type var instance was garbage collected and recreated. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, PartialOrd, Ord)] pub enum KnownInstanceType<'db> { /// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`) Annotated, diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index a52dda24af0cb1..cb2b309f63d193 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::db::Db; use super::{ - DynamicType, KnownInstanceType, SuperOwnerKind, TodoType, Type, class_base::ClassBase, + DynamicType, SuperOwnerKind, TodoType, Type, class_base::ClassBase, subclass_of::SubclassOfInner, }; @@ -180,144 +180,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (_, Type::BoundSuper(_)) => Ordering::Greater, (Type::KnownInstance(left_instance), Type::KnownInstance(right_instance)) => { - match (left_instance, right_instance) { - (KnownInstanceType::Tuple, _) => Ordering::Less, - (_, KnownInstanceType::Tuple) => Ordering::Greater, - - (KnownInstanceType::AlwaysFalsy, _) => Ordering::Less, - (_, KnownInstanceType::AlwaysFalsy) => Ordering::Greater, - - (KnownInstanceType::AlwaysTruthy, _) => Ordering::Less, - (_, KnownInstanceType::AlwaysTruthy) => Ordering::Greater, - - (KnownInstanceType::Annotated, _) => Ordering::Less, - (_, KnownInstanceType::Annotated) => Ordering::Greater, - - (KnownInstanceType::Callable, _) => Ordering::Less, - (_, KnownInstanceType::Callable) => Ordering::Greater, - - (KnownInstanceType::ChainMap, _) => Ordering::Less, - (_, KnownInstanceType::ChainMap) => Ordering::Greater, - - (KnownInstanceType::ClassVar, _) => Ordering::Less, - (_, KnownInstanceType::ClassVar) => Ordering::Greater, - - (KnownInstanceType::Concatenate, _) => Ordering::Less, - (_, KnownInstanceType::Concatenate) => Ordering::Greater, - - (KnownInstanceType::Counter, _) => Ordering::Less, - (_, KnownInstanceType::Counter) => Ordering::Greater, - - (KnownInstanceType::DefaultDict, _) => Ordering::Less, - (_, KnownInstanceType::DefaultDict) => Ordering::Greater, - - (KnownInstanceType::Deque, _) => Ordering::Less, - (_, KnownInstanceType::Deque) => Ordering::Greater, - - (KnownInstanceType::Dict, _) => Ordering::Less, - (_, KnownInstanceType::Dict) => Ordering::Greater, - - (KnownInstanceType::Final, _) => Ordering::Less, - (_, KnownInstanceType::Final) => Ordering::Greater, - - (KnownInstanceType::FrozenSet, _) => Ordering::Less, - (_, KnownInstanceType::FrozenSet) => Ordering::Greater, - - (KnownInstanceType::TypeGuard, _) => Ordering::Less, - (_, KnownInstanceType::TypeGuard) => Ordering::Greater, - - (KnownInstanceType::TypedDict, _) => Ordering::Less, - (_, KnownInstanceType::TypedDict) => Ordering::Greater, - - (KnownInstanceType::List, _) => Ordering::Less, - (_, KnownInstanceType::List) => Ordering::Greater, - - (KnownInstanceType::Literal, _) => Ordering::Less, - (_, KnownInstanceType::Literal) => Ordering::Greater, - - (KnownInstanceType::LiteralString, _) => Ordering::Less, - (_, KnownInstanceType::LiteralString) => Ordering::Greater, - - (KnownInstanceType::Optional, _) => Ordering::Less, - (_, KnownInstanceType::Optional) => Ordering::Greater, - - (KnownInstanceType::OrderedDict, _) => Ordering::Less, - (_, KnownInstanceType::OrderedDict) => Ordering::Greater, - - (KnownInstanceType::Generic(left), KnownInstanceType::Generic(right)) => { - left.cmp(right) - } - (KnownInstanceType::Generic(_), _) => Ordering::Less, - (_, KnownInstanceType::Generic(_)) => Ordering::Greater, - - (KnownInstanceType::Protocol(left), KnownInstanceType::Protocol(right)) => { - left.cmp(right) - } - (KnownInstanceType::Protocol(_), _) => Ordering::Less, - (_, KnownInstanceType::Protocol(_)) => Ordering::Greater, - - (KnownInstanceType::NoReturn, _) => Ordering::Less, - (_, KnownInstanceType::NoReturn) => Ordering::Greater, - - (KnownInstanceType::Never, _) => Ordering::Less, - (_, KnownInstanceType::Never) => Ordering::Greater, - - (KnownInstanceType::Set, _) => Ordering::Less, - (_, KnownInstanceType::Set) => Ordering::Greater, - - (KnownInstanceType::Type, _) => Ordering::Less, - (_, KnownInstanceType::Type) => Ordering::Greater, - - (KnownInstanceType::TypeAlias, _) => Ordering::Less, - (_, KnownInstanceType::TypeAlias) => Ordering::Greater, - - (KnownInstanceType::Unknown, _) => Ordering::Less, - (_, KnownInstanceType::Unknown) => Ordering::Greater, - - (KnownInstanceType::Not, _) => Ordering::Less, - (_, KnownInstanceType::Not) => Ordering::Greater, - - (KnownInstanceType::Intersection, _) => Ordering::Less, - (_, KnownInstanceType::Intersection) => Ordering::Greater, - - (KnownInstanceType::TypeOf, _) => Ordering::Less, - (_, KnownInstanceType::TypeOf) => Ordering::Greater, - - (KnownInstanceType::CallableTypeOf, _) => Ordering::Less, - (_, KnownInstanceType::CallableTypeOf) => Ordering::Greater, - - (KnownInstanceType::Unpack, _) => Ordering::Less, - (_, KnownInstanceType::Unpack) => Ordering::Greater, - - (KnownInstanceType::TypingSelf, _) => Ordering::Less, - (_, KnownInstanceType::TypingSelf) => Ordering::Greater, - - (KnownInstanceType::Required, _) => Ordering::Less, - (_, KnownInstanceType::Required) => Ordering::Greater, - - (KnownInstanceType::NotRequired, _) => Ordering::Less, - (_, KnownInstanceType::NotRequired) => Ordering::Greater, - - (KnownInstanceType::TypeIs, _) => Ordering::Less, - (_, KnownInstanceType::TypeIs) => Ordering::Greater, - - (KnownInstanceType::ReadOnly, _) => Ordering::Less, - (_, KnownInstanceType::ReadOnly) => Ordering::Greater, - - (KnownInstanceType::Union, _) => Ordering::Less, - (_, KnownInstanceType::Union) => Ordering::Greater, - - ( - KnownInstanceType::TypeAliasType(left), - KnownInstanceType::TypeAliasType(right), - ) => left.cmp(right), - (KnownInstanceType::TypeAliasType(_), _) => Ordering::Less, - (_, KnownInstanceType::TypeAliasType(_)) => Ordering::Greater, - - (KnownInstanceType::TypeVar(left), KnownInstanceType::TypeVar(right)) => { - left.cmp(right) - } - } + left_instance.cmp(right_instance) } (Type::KnownInstance(_), _) => Ordering::Less, From 3eada01153e719738515735a3adcec5b25b9a838 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 27 May 2025 12:16:41 -0700 Subject: [PATCH 254/487] put similar dunder-call tests next to each other (#18343) Follow-up from post-land review on https://github.com/astral-sh/ruff/pull/18260 --- .../resources/mdtest/call/dunder.md | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 8c2a70a2c4deb2..15cf01b7a1f41f 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -59,6 +59,8 @@ ClassWithNormalDunder[0] ## Operating on instances +### Attaching dunder methods to instances in methods + When invoking a dunder method on an instance of a class, it is looked up on the class: ```py @@ -116,6 +118,40 @@ def _(flag: bool): reveal_type(this_fails[0]) # revealed: Unknown | str ``` +### Dunder methods as class-level annotations with no value + +Class-level annotations with no value assigned are considered instance-only, and aren't available as +dunder methods: + +```py +from typing import Callable + +class C: + __call__: Callable[..., None] + +# error: [call-non-callable] +C()() + +# error: [invalid-assignment] +_: Callable[..., None] = C() +``` + +And of course the same is true if we have only an implicit assignment inside a method: + +```py +from typing import Callable + +class C: + def __init__(self): + self.__call__ = lambda *a, **kw: None + +# error: [call-non-callable] +C()() + +# error: [invalid-assignment] +_: Callable[..., None] = C() +``` + ## When the dunder is not a method A dunder can also be a non-method callable: @@ -239,37 +275,3 @@ def _(flag: bool): # error: [possibly-unbound-implicit-call] reveal_type(c[0]) # revealed: str ``` - -## Dunder methods cannot be looked up on instances - -Class-level annotations with no value assigned are considered instance-only, and aren't available as -dunder methods: - -```py -from typing import Callable - -class C: - __call__: Callable[..., None] - -# error: [call-non-callable] -C()() - -# error: [invalid-assignment] -_: Callable[..., None] = C() -``` - -And of course the same is true if we have only an implicit assignment inside a method: - -```py -from typing import Callable - -class C: - def __init__(self): - self.__call__ = lambda *a, **kw: None - -# error: [call-non-callable] -C()() - -# error: [invalid-assignment] -_: Callable[..., None] = C() -``` From 602dd5c039935286bcb4553827a18ba6ff3d4b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=B9=E0=A4=BF=E0=A4=AE=E0=A4=BE=E0=A4=82=E0=A4=B6?= =?UTF-8?q?=E0=A5=81?= Date: Wed, 28 May 2025 12:36:39 +0530 Subject: [PATCH 255/487] [pycodestyle] Make `E712` suggestion not assume a context (#18328) --- .../pycodestyle/rules/literal_comparisons.rs | 12 ++++------ ...les__pycodestyle__tests__E712_E712.py.snap | 24 +++++++++---------- ...pycodestyle__tests__constant_literals.snap | 4 ++-- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs index b700451fbdc7da..ef175cd81010f4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs @@ -136,22 +136,18 @@ impl AlwaysFixableViolation for TrueFalseComparison { let cond = cond.truncated_display(); match (value, op) { (true, EqCmpOp::Eq) => { - format!("Avoid equality comparisons to `True`; use `if {cond}:` for truth checks") + format!("Avoid equality comparisons to `True`; use `{cond}:` for truth checks") } (true, EqCmpOp::NotEq) => { format!( - "Avoid inequality comparisons to `True`; use `if not {cond}:` for false checks" + "Avoid inequality comparisons to `True`; use `not {cond}:` for false checks" ) } (false, EqCmpOp::Eq) => { - format!( - "Avoid equality comparisons to `False`; use `if not {cond}:` for false checks" - ) + format!("Avoid equality comparisons to `False`; use `not {cond}:` for false checks") } (false, EqCmpOp::NotEq) => { - format!( - "Avoid inequality comparisons to `False`; use `if {cond}:` for truth checks" - ) + format!("Avoid inequality comparisons to `False`; use `{cond}:` for truth checks") } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E712_E712.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E712_E712.py.snap index 70d56f46d7cea3..b540850623816f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E712_E712.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E712_E712.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E712.py:2:4: E712 [*] Avoid equality comparisons to `True`; use `if res:` for truth checks +E712.py:2:4: E712 [*] Avoid equality comparisons to `True`; use `res:` for truth checks | 1 | #: E712 2 | if res == True: @@ -19,7 +19,7 @@ E712.py:2:4: E712 [*] Avoid equality comparisons to `True`; use `if res:` for tr 4 4 | #: E712 5 5 | if res != False: -E712.py:5:4: E712 [*] Avoid inequality comparisons to `False`; use `if res:` for truth checks +E712.py:5:4: E712 [*] Avoid inequality comparisons to `False`; use `res:` for truth checks | 3 | pass 4 | #: E712 @@ -40,7 +40,7 @@ E712.py:5:4: E712 [*] Avoid inequality comparisons to `False`; use `if res:` for 7 7 | #: E712 8 8 | if True != res: -E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `if not res:` for false checks +E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `not res:` for false checks | 6 | pass 7 | #: E712 @@ -61,7 +61,7 @@ E712.py:8:4: E712 [*] Avoid inequality comparisons to `True`; use `if not res:` 10 10 | #: E712 11 11 | if False == res: -E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `if not res:` for false checks +E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `not res:` for false checks | 9 | pass 10 | #: E712 @@ -82,7 +82,7 @@ E712.py:11:4: E712 [*] Avoid equality comparisons to `False`; use `if not res:` 13 13 | #: E712 14 14 | if res[1] == True: -E712.py:14:4: E712 [*] Avoid equality comparisons to `True`; use `if res[1]:` for truth checks +E712.py:14:4: E712 [*] Avoid equality comparisons to `True`; use `res[1]:` for truth checks | 12 | pass 13 | #: E712 @@ -103,7 +103,7 @@ E712.py:14:4: E712 [*] Avoid equality comparisons to `True`; use `if res[1]:` fo 16 16 | #: E712 17 17 | if res[1] != False: -E712.py:17:4: E712 [*] Avoid inequality comparisons to `False`; use `if res[1]:` for truth checks +E712.py:17:4: E712 [*] Avoid inequality comparisons to `False`; use `res[1]:` for truth checks | 15 | pass 16 | #: E712 @@ -124,7 +124,7 @@ E712.py:17:4: E712 [*] Avoid inequality comparisons to `False`; use `if res[1]:` 19 19 | #: E712 20 20 | var = 1 if cond == True else -1 if cond == False else cond -E712.py:20:12: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for truth checks +E712.py:20:12: E712 [*] Avoid equality comparisons to `True`; use `cond:` for truth checks | 18 | pass 19 | #: E712 @@ -145,7 +145,7 @@ E712.py:20:12: E712 [*] Avoid equality comparisons to `True`; use `if cond:` for 22 22 | if (True) == TrueElement or x == TrueElement: 23 23 | pass -E712.py:20:36: E712 [*] Avoid equality comparisons to `False`; use `if not cond:` for false checks +E712.py:20:36: E712 [*] Avoid equality comparisons to `False`; use `not cond:` for false checks | 18 | pass 19 | #: E712 @@ -166,7 +166,7 @@ E712.py:20:36: E712 [*] Avoid equality comparisons to `False`; use `if not cond: 22 22 | if (True) == TrueElement or x == TrueElement: 23 23 | pass -E712.py:22:4: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement:` for truth checks +E712.py:22:4: E712 [*] Avoid equality comparisons to `True`; use `TrueElement:` for truth checks | 20 | var = 1 if cond == True else -1 if cond == False else cond 21 | #: E712 @@ -226,7 +226,7 @@ E712.py:25:4: E712 [*] Avoid equality comparisons to `True` or `False` 27 27 | 28 28 | if(True) == TrueElement or x == TrueElement: -E712.py:28:3: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement:` for truth checks +E712.py:28:3: E712 [*] Avoid equality comparisons to `True`; use `TrueElement:` for truth checks | 26 | pass 27 | @@ -246,7 +246,7 @@ E712.py:28:3: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement 30 30 | 31 31 | if (yield i) == True: -E712.py:31:4: E712 [*] Avoid equality comparisons to `True`; use `if yield i:` for truth checks +E712.py:31:4: E712 [*] Avoid equality comparisons to `True`; use `yield i:` for truth checks | 29 | pass 30 | @@ -266,7 +266,7 @@ E712.py:31:4: E712 [*] Avoid equality comparisons to `True`; use `if yield i:` f 33 33 | 34 34 | #: Okay -E712.py:58:4: E712 [*] Avoid equality comparisons to `True`; use `if True:` for truth checks +E712.py:58:4: E712 [*] Avoid equality comparisons to `True`; use `True:` for truth checks | 57 | # https://github.com/astral-sh/ruff/issues/17582 58 | if True == True: # No duplicated diagnostic diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__constant_literals.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__constant_literals.snap index 7adb6732e7bf38..89296988de3cb4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__constant_literals.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__constant_literals.snap @@ -106,7 +106,7 @@ constant_literals.py:12:4: F632 [*] Use `==` to compare constant literals 14 14 | if False == None: # E711, E712 (fix) 15 15 | pass -constant_literals.py:14:4: E712 [*] Avoid equality comparisons to `False`; use `if not None:` for false checks +constant_literals.py:14:4: E712 [*] Avoid equality comparisons to `False`; use `not None:` for false checks | 12 | if False is "abc": # F632 (fix, but leaves behind unfixable E712) 13 | pass @@ -168,7 +168,7 @@ constant_literals.py:16:4: E711 [*] Comparison to `None` should be `cond is None 18 18 | 19 19 | named_var = [] -constant_literals.py:16:4: E712 [*] Avoid equality comparisons to `False`; use `if not None:` for false checks +constant_literals.py:16:4: E712 [*] Avoid equality comparisons to `False`; use `not None:` for false checks | 14 | if False == None: # E711, E712 (fix) 15 | pass From 9ce83c215d2a25713a5c044965e9b573faa105b0 Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 28 May 2025 10:22:44 +0300 Subject: [PATCH 256/487] [`pyupgrade`]: new rule UP050 (`useless-class-metaclass-type`) (#18334) Co-authored-by: Micha Reiser --- .../test/fixtures/pyupgrade/UP050.py | 84 +++++++ .../src/checkers/ast/analyze/statement.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pyupgrade/mod.rs | 1 + .../src/rules/pyupgrade/rules/mod.rs | 2 + .../rules/useless_class_metaclass_type.rs | 79 ++++++ ...er__rules__pyupgrade__tests__UP050.py.snap | 237 ++++++++++++++++++ ruff.schema.json | 2 + 8 files changed, 409 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py create mode 100644 crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py new file mode 100644 index 00000000000000..6a04a4acc3cf6c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py @@ -0,0 +1,84 @@ +class A: + ... + + +class A(metaclass=type): + ... + + +class A( + metaclass=type +): + ... + + +class A( + metaclass=type + # +): + ... + + +class A( + # + metaclass=type +): + ... + + +class A( + metaclass=type, + # +): + ... + + +class A( + # + metaclass=type, + # +): + ... + + +class B(A, metaclass=type): + ... + + +class B( + A, + metaclass=type, +): + ... + + +class B( + A, + # comment + metaclass=type, +): + ... + + +def foo(): + class A(metaclass=type): + ... + + +class A( + metaclass=type # comment + , +): + ... + + +type = str + +class Foo(metaclass=type): + ... + + +import builtins + +class A(metaclass=builtins.type): + ... \ No newline at end of file diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 84563726a21ee0..059c831a171d37 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -439,6 +439,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::UselessObjectInheritance) { pyupgrade::rules::useless_object_inheritance(checker, class_def); } + if checker.enabled(Rule::UselessClassMetaclassType) { + pyupgrade::rules::useless_class_metaclass_type(checker, class_def); + } if checker.enabled(Rule::ReplaceStrEnum) { if checker.target_version() >= PythonVersion::PY311 { pyupgrade::rules::replace_str_enum(checker, class_def); diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 63f81714dcf66a..c96881fbaf0dc6 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -552,6 +552,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass), (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), (Pyupgrade, "049") => (RuleGroup::Preview, rules::pyupgrade::rules::PrivateTypeParameter), + (Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType), // pydocstyle (Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule), diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index dfaf0ece7a3123..3f0388787b7a3b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -111,6 +111,7 @@ mod tests { #[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047.py"))] #[test_case(Rule::PrivateTypeParameter, Path::new("UP049_0.py"))] #[test_case(Rule::PrivateTypeParameter, Path::new("UP049_1.py"))] + #[test_case(Rule::UselessClassMetaclassType, Path::new("UP050.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = path.to_string_lossy().to_string(); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs index c8f5f1b04b496b..aef1d1d16858e6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/mod.rs @@ -37,6 +37,7 @@ pub(crate) use unpacked_list_comprehension::*; pub(crate) use use_pep585_annotation::*; pub(crate) use use_pep604_annotation::*; pub(crate) use use_pep604_isinstance::*; +pub(crate) use useless_class_metaclass_type::*; pub(crate) use useless_metaclass_type::*; pub(crate) use useless_object_inheritance::*; pub(crate) use yield_in_for_loop::*; @@ -80,6 +81,7 @@ mod unpacked_list_comprehension; mod use_pep585_annotation; mod use_pep604_annotation; mod use_pep604_isinstance; +mod useless_class_metaclass_type; mod useless_metaclass_type; mod useless_object_inheritance; mod yield_in_for_loop; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs new file mode 100644 index 00000000000000..c4b26a2a84ea57 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs @@ -0,0 +1,79 @@ +use crate::checkers::ast::Checker; +use crate::fix::edits::{Parentheses, remove_argument}; +use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::StmtClassDef; +use ruff_text_size::Ranged; + +/// ## What it does +/// Checks for `metaclass=type` in class definitions. +/// +/// ## Why is this bad? +/// Since Python 3, the default metaclass is `type`, so specifying it explicitly is redundant. +/// +/// Even though `__prepare__` is not required, the default metaclass (`type`) implements it, +/// for the convenience of subclasses calling it via `super()`. +/// ## Example +/// +/// ```python +/// class Foo(metaclass=type): ... +/// ``` +/// +/// Use instead: +/// +/// ```python +/// class Foo: ... +/// ``` +/// +/// ## References +/// - [PEP 3115 – Metaclasses in Python 3000](https://peps.python.org/pep-3115/) +#[derive(ViolationMetadata)] +pub(crate) struct UselessClassMetaclassType { + name: String, +} + +impl Violation for UselessClassMetaclassType { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + let UselessClassMetaclassType { name } = self; + format!("Class `{name}` uses `metaclass=type`, which is redundant") + } + + fn fix_title(&self) -> Option { + Some("Remove `metaclass=type`".to_string()) + } +} + +/// UP050 +pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtClassDef) { + let Some(arguments) = class_def.arguments.as_deref() else { + return; + }; + + for keyword in &arguments.keywords { + if let (Some("metaclass"), expr) = (keyword.arg.as_deref(), &keyword.value) { + if checker.semantic().match_builtin_expr(expr, "type") { + let mut diagnostic = Diagnostic::new( + UselessClassMetaclassType { + name: class_def.name.to_string(), + }, + keyword.range(), + ); + + diagnostic.try_set_fix(|| { + remove_argument( + keyword, + arguments, + Parentheses::Remove, + checker.locator().contents(), + ) + .map(Fix::safe_edit) + }); + + checker.report_diagnostic(diagnostic); + } + } + } +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap new file mode 100644 index 00000000000000..73074b663ee318 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap @@ -0,0 +1,237 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP050.py:5:9: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +5 | class A(metaclass=type): + | ^^^^^^^^^^^^^^ UP050 +6 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +2 2 | ... +3 3 | +4 4 | +5 |-class A(metaclass=type): + 5 |+class A: +6 6 | ... +7 7 | +8 8 | + +UP050.py:10:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | + 9 | class A( +10 | metaclass=type + | ^^^^^^^^^^^^^^ UP050 +11 | ): +12 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +6 6 | ... +7 7 | +8 8 | +9 |-class A( +10 |- metaclass=type +11 |-): + 9 |+class A: +12 10 | ... +13 11 | +14 12 | + +UP050.py:16:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +15 | class A( +16 | metaclass=type + | ^^^^^^^^^^^^^^ UP050 +17 | # +18 | ): + | + = help: Remove `metaclass=type` + +ℹ Safe fix +12 12 | ... +13 13 | +14 14 | +15 |-class A( +16 |- metaclass=type +17 |- # +18 |-): + 15 |+class A: +19 16 | ... +20 17 | +21 18 | + +UP050.py:24:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +22 | class A( +23 | # +24 | metaclass=type + | ^^^^^^^^^^^^^^ UP050 +25 | ): +26 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +19 19 | ... +20 20 | +21 21 | +22 |-class A( +23 |- # +24 |- metaclass=type +25 |-): + 22 |+class A: +26 23 | ... +27 24 | +28 25 | + +UP050.py:30:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +29 | class A( +30 | metaclass=type, + | ^^^^^^^^^^^^^^ UP050 +31 | # +32 | ): + | + = help: Remove `metaclass=type` + +ℹ Safe fix +26 26 | ... +27 27 | +28 28 | +29 |-class A( +30 |- metaclass=type, +31 |- # +32 |-): + 29 |+class A: +33 30 | ... +34 31 | +35 32 | + +UP050.py:38:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +36 | class A( +37 | # +38 | metaclass=type, + | ^^^^^^^^^^^^^^ UP050 +39 | # +40 | ): + | + = help: Remove `metaclass=type` + +ℹ Safe fix +33 33 | ... +34 34 | +35 35 | +36 |-class A( +37 |- # +38 |- metaclass=type, +39 |- # +40 |-): + 36 |+class A: +41 37 | ... +42 38 | +43 39 | + +UP050.py:44:12: UP050 [*] Class `B` uses `metaclass=type`, which is redundant + | +44 | class B(A, metaclass=type): + | ^^^^^^^^^^^^^^ UP050 +45 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +41 41 | ... +42 42 | +43 43 | +44 |-class B(A, metaclass=type): + 44 |+class B(A): +45 45 | ... +46 46 | +47 47 | + +UP050.py:50:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant + | +48 | class B( +49 | A, +50 | metaclass=type, + | ^^^^^^^^^^^^^^ UP050 +51 | ): +52 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +47 47 | +48 48 | class B( +49 49 | A, +50 |- metaclass=type, +51 50 | ): +52 51 | ... +53 52 | + +UP050.py:58:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant + | +56 | A, +57 | # comment +58 | metaclass=type, + | ^^^^^^^^^^^^^^ UP050 +59 | ): +60 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +54 54 | +55 55 | class B( +56 56 | A, +57 |- # comment +58 |- metaclass=type, +59 57 | ): +60 58 | ... +61 59 | + +UP050.py:69:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +68 | class A( +69 | metaclass=type # comment + | ^^^^^^^^^^^^^^ UP050 +70 | , +71 | ): + | + = help: Remove `metaclass=type` + +ℹ Safe fix +65 65 | ... +66 66 | +67 67 | +68 |-class A( +69 |- metaclass=type # comment +70 |- , +71 |-): + 68 |+class A: +72 69 | ... +73 70 | +74 71 | + +UP050.py:83:9: UP050 [*] Class `A` uses `metaclass=type`, which is redundant + | +81 | import builtins +82 | +83 | class A(metaclass=builtins.type): + | ^^^^^^^^^^^^^^^^^^^^^^^ UP050 +84 | ... + | + = help: Remove `metaclass=type` + +ℹ Safe fix +80 80 | +81 81 | import builtins +82 82 | +83 |-class A(metaclass=builtins.type): + 83 |+class A: +84 84 | ... diff --git a/ruff.schema.json b/ruff.schema.json index 0059b7bb2fbaf9..e0f8ce55fbc831 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -4299,6 +4299,8 @@ "UP046", "UP047", "UP049", + "UP05", + "UP050", "W", "W1", "W19", From 6d210dd0c73260e9c1d570462e9771a66ebccd07 Mon Sep 17 00:00:00 2001 From: Max Mynter <32773644+maxmynter@users.noreply.github.com> Date: Wed, 28 May 2025 09:30:51 +0200 Subject: [PATCH 257/487] Add Autofix for ISC003 (#18256) Co-authored-by: Micha Reiser --- .../flake8_implicit_str_concat/ISC.py | 96 ++++++ .../src/checkers/ast/analyze/expression.rs | 7 +- .../rules/explicit.rs | 85 +++-- ...icit_str_concat__tests__ISC001_ISC.py.snap | 6 + ...icit_str_concat__tests__ISC003_ISC.py.snap | 314 +++++++++++++++++- ...oncat__tests__multiline_ISC001_ISC.py.snap | 6 + 6 files changed, 477 insertions(+), 37 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py index 68fc1e3d9c63ca..09fba4a9db940a 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py @@ -91,3 +91,99 @@ _ = "\12""8" # fix should be "\128" _ = "\12""foo" # fix should be "\12foo" _ = "\12" "" # fix should be "\12" + + +# Mixed literal + non-literal scenarios +_ = ( + "start" + + variable + + "end" +) + +_ = ( + f"format" + + func_call() + + "literal" +) + +_ = ( + rf"raw_f{x}" + + r"raw_normal" +) + + +# Different prefix combinations +_ = ( + u"unicode" + + r"raw" +) + +_ = ( + rb"raw_bytes" + + b"normal_bytes" +) + +_ = ( + b"bytes" + + b"with_bytes" +) + +# Repeated concatenation + +_ = ("a" + + "b" + + "c" + + "d" + "e" +) + +_ = ("a" + + "b" + + "c" + + "d" + + "e" +) + +_ = ( + "start" + + variable + # comment + "end" +) + +_ = ( + "start" + + variable + # leading comment + + "end" +) + +_ = ( + "first" + + "second" # extra spaces around + +) + +_ = ( + "first" + # trailing spaces before + + "second" +) + +_ = (( + "deep" + + "nesting" +)) + +_ = ( + "contains + plus" + + "another string" +) + +_ = ( + "start" + # leading comment + + "end" +) + +_ = ( + "start" + + # leading comment + "end" +) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 38bbf9874ca87b..6cfa73f8765c92 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1364,11 +1364,8 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { op: Operator::Add, .. }) => { if checker.enabled(Rule::ExplicitStringConcatenation) { - if let Some(diagnostic) = flake8_implicit_str_concat::rules::explicit( - expr, - checker.locator, - checker.settings, - ) { + if let Some(diagnostic) = flake8_implicit_str_concat::rules::explicit(expr, checker) + { checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs index 18690266cd1d61..5af474d665ebe1 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs @@ -1,11 +1,12 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::AlwaysFixableViolation; +use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; +use ruff_python_trivia::is_python_whitespace; use ruff_source_file::LineRanges; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use crate::Locator; -use crate::settings::LinterSettings; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for string literals that are explicitly concatenated (using the @@ -34,46 +35,76 @@ use crate::settings::LinterSettings; #[derive(ViolationMetadata)] pub(crate) struct ExplicitStringConcatenation; -impl Violation for ExplicitStringConcatenation { +impl AlwaysFixableViolation for ExplicitStringConcatenation { #[derive_message_formats] fn message(&self) -> String { "Explicitly concatenated string should be implicitly concatenated".to_string() } + + fn fix_title(&self) -> String { + "Remove redundant '+' operator to implicitly concatenate".to_string() + } } /// ISC003 -pub(crate) fn explicit( - expr: &Expr, - locator: &Locator, - settings: &LinterSettings, -) -> Option { +pub(crate) fn explicit(expr: &Expr, checker: &Checker) -> Option { // If the user sets `allow-multiline` to `false`, then we should allow explicitly concatenated // strings that span multiple lines even if this rule is enabled. Otherwise, there's no way // for the user to write multiline strings, and that setting is "more explicit" than this rule // being enabled. - if !settings.flake8_implicit_str_concat.allow_multiline { + if !checker.settings.flake8_implicit_str_concat.allow_multiline { return None; } - if let Expr::BinOp(ast::ExprBinOp { - left, - op, - right, - range, - }) = expr - { - if matches!(op, Operator::Add) { - if matches!( - left.as_ref(), - Expr::FString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) - ) && matches!( - right.as_ref(), - Expr::FString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) - ) && locator.contains_line_break(*range) + if let Expr::BinOp(bin_op) = expr { + if let ast::ExprBinOp { + left, + right, + op: Operator::Add, + .. + } = bin_op + { + let concatable = matches!( + (left.as_ref(), right.as_ref()), + ( + Expr::StringLiteral(_) | Expr::FString(_), + Expr::StringLiteral(_) | Expr::FString(_) + ) | (Expr::BytesLiteral(_), Expr::BytesLiteral(_)) + ); + if concatable + && checker + .locator() + .contains_line_break(TextRange::new(left.end(), right.start())) { - return Some(Diagnostic::new(ExplicitStringConcatenation, expr.range())); + let mut diagnostic = Diagnostic::new(ExplicitStringConcatenation, expr.range()); + diagnostic.set_fix(generate_fix(checker, bin_op)); + return Some(diagnostic); } } } None } + +fn generate_fix(checker: &Checker, expr_bin_op: &ast::ExprBinOp) -> Fix { + let ast::ExprBinOp { left, right, .. } = expr_bin_op; + + let between_operands_range = TextRange::new(left.end(), right.start()); + let between_operands = checker.locator().slice(between_operands_range); + let (before_plus, after_plus) = between_operands.split_once('+').unwrap(); + + let linebreak_before_operator = + before_plus.contains_line_break(TextRange::at(TextSize::new(0), before_plus.text_len())); + + // If removing `+` from first line trim trailing spaces + // Preserve indentation when removing `+` from second line + let before_plus = if linebreak_before_operator { + before_plus + } else { + before_plus.trim_end_matches(is_python_whitespace) + }; + + Fix::safe_edit(Edit::range_replacement( + format!("{before_plus}{after_plus}"), + between_operands_range, + )) +} diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap index 73d16d6ebd2b29..e6cfb14078d4bc 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap @@ -461,6 +461,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 91 |+_ = "\128" # fix should be "\128" 92 92 | _ = "\12""foo" # fix should be "\12foo" 93 93 | _ = "\12" "" # fix should be "\12" +94 94 | ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line | @@ -479,6 +480,8 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 92 |-_ = "\12""foo" # fix should be "\12foo" 92 |+_ = "\12foo" # fix should be "\12foo" 93 93 | _ = "\12" "" # fix should be "\12" +94 94 | +95 95 | ISC.py:93:5: ISC001 [*] Implicitly concatenated string literals on one line | @@ -495,3 +498,6 @@ ISC.py:93:5: ISC001 [*] Implicitly concatenated string literals on one line 92 92 | _ = "\12""foo" # fix should be "\12foo" 93 |-_ = "\12" "" # fix should be "\12" 93 |+_ = "\12" # fix should be "\12" +94 94 | +95 95 | +96 96 | # Mixed literal + non-literal scenarios diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap index dccaf146a2f4fe..6ce1e6eee6089a 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_implicit_str_concat/mod.rs --- -ISC.py:9:3: ISC003 Explicitly concatenated string should be implicitly concatenated +ISC.py:9:3: ISC003 [*] Explicitly concatenated string should be implicitly concatenated | 8 | _ = ( 9 | / "abc" + @@ -9,8 +9,19 @@ ISC.py:9:3: ISC003 Explicitly concatenated string should be implicitly concatena | |_______^ ISC003 11 | ) | + = help: Remove redundant '+' operator to implicitly concatenate -ISC.py:14:3: ISC003 Explicitly concatenated string should be implicitly concatenated +ℹ Safe fix +6 6 | "def" +7 7 | +8 8 | _ = ( +9 |- "abc" + + 9 |+ "abc" +10 10 | "def" +11 11 | ) +12 12 | + +ISC.py:14:3: ISC003 [*] Explicitly concatenated string should be implicitly concatenated | 13 | _ = ( 14 | / f"abc" + @@ -18,8 +29,19 @@ ISC.py:14:3: ISC003 Explicitly concatenated string should be implicitly concaten | |_______^ ISC003 16 | ) | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +11 11 | ) +12 12 | +13 13 | _ = ( +14 |- f"abc" + + 14 |+ f"abc" +15 15 | "def" +16 16 | ) +17 17 | -ISC.py:19:3: ISC003 Explicitly concatenated string should be implicitly concatenated +ISC.py:19:3: ISC003 [*] Explicitly concatenated string should be implicitly concatenated | 18 | _ = ( 19 | / b"abc" + @@ -27,8 +49,19 @@ ISC.py:19:3: ISC003 Explicitly concatenated string should be implicitly concaten | |________^ ISC003 21 | ) | + = help: Remove redundant '+' operator to implicitly concatenate -ISC.py:78:10: ISC003 Explicitly concatenated string should be implicitly concatenated +ℹ Safe fix +16 16 | ) +17 17 | +18 18 | _ = ( +19 |- b"abc" + + 19 |+ b"abc" +20 20 | b"def" +21 21 | ) +22 22 | + +ISC.py:78:10: ISC003 [*] Explicitly concatenated string should be implicitly concatenated | 77 | # Explicitly concatenated nested f-strings 78 | _ = f"a {f"first" @@ -38,8 +71,19 @@ ISC.py:78:10: ISC003 Explicitly concatenated string should be implicitly concate 80 | _ = f"a {f"first {f"middle"}" 81 | + f"second"} d" | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +76 76 | +77 77 | # Explicitly concatenated nested f-strings +78 78 | _ = f"a {f"first" +79 |- + f"second"} d" + 79 |+ f"second"} d" +80 80 | _ = f"a {f"first {f"middle"}" +81 81 | + f"second"} d" +82 82 | -ISC.py:80:10: ISC003 Explicitly concatenated string should be implicitly concatenated +ISC.py:80:10: ISC003 [*] Explicitly concatenated string should be implicitly concatenated | 78 | _ = f"a {f"first" 79 | + f"second"} d" @@ -50,3 +94,263 @@ ISC.py:80:10: ISC003 Explicitly concatenated string should be implicitly concate 82 | 83 | # See https://github.com/astral-sh/ruff/issues/12936 | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +78 78 | _ = f"a {f"first" +79 79 | + f"second"} d" +80 80 | _ = f"a {f"first {f"middle"}" +81 |- + f"second"} d" + 81 |+ f"second"} d" +82 82 | +83 83 | # See https://github.com/astral-sh/ruff/issues/12936 +84 84 | _ = "\12""0" # fix should be "\0120" + +ISC.py:110:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +109 | _ = ( +110 | / rf"raw_f{x}" + +111 | | r"raw_normal" + | |_________________^ ISC003 +112 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +107 107 | ) +108 108 | +109 109 | _ = ( +110 |- rf"raw_f{x}" + + 110 |+ rf"raw_f{x}" +111 111 | r"raw_normal" +112 112 | ) +113 113 | + +ISC.py:117:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +115 | # Different prefix combinations +116 | _ = ( +117 | / u"unicode" + +118 | | r"raw" + | |__________^ ISC003 +119 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +114 114 | +115 115 | # Different prefix combinations +116 116 | _ = ( +117 |- u"unicode" + + 117 |+ u"unicode" +118 118 | r"raw" +119 119 | ) +120 120 | + +ISC.py:122:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +121 | _ = ( +122 | / rb"raw_bytes" + +123 | | b"normal_bytes" + | |___________________^ ISC003 +124 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +119 119 | ) +120 120 | +121 121 | _ = ( +122 |- rb"raw_bytes" + + 122 |+ rb"raw_bytes" +123 123 | b"normal_bytes" +124 124 | ) +125 125 | + +ISC.py:127:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +126 | _ = ( +127 | / b"bytes" + +128 | | b"with_bytes" + | |_________________^ ISC003 +129 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +124 124 | ) +125 125 | +126 126 | _ = ( +127 |- b"bytes" + + 127 |+ b"bytes" +128 128 | b"with_bytes" +129 129 | ) +130 130 | + +ISC.py:133:6: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +131 | # Repeated concatenation +132 | +133 | _ = ("a" + + | ______^ +134 | | "b" + + | |_______^ ISC003 +135 | "c" + +136 | "d" + "e" + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +130 130 | +131 131 | # Repeated concatenation +132 132 | +133 |-_ = ("a" + + 133 |+_ = ("a" +134 134 | "b" + +135 135 | "c" + +136 136 | "d" + "e" + +ISC.py:139:6: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +137 | ) +138 | +139 | _ = ("a" + | ______^ +140 | | + "b" + | |_________^ ISC003 +141 | + "c" +142 | + "d" + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +137 137 | ) +138 138 | +139 139 | _ = ("a" +140 |- + "b" + 140 |+ "b" +141 141 | + "c" +142 142 | + "d" +143 143 | + "e" + +ISC.py:160:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +159 | _ = ( +160 | / "first" +161 | | + "second" # extra spaces around + + | |_________________^ ISC003 +162 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +158 158 | +159 159 | _ = ( +160 160 | "first" +161 |- + "second" # extra spaces around + + 161 |+ "second" # extra spaces around + +162 162 | ) +163 163 | +164 164 | _ = ( + +ISC.py:165:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +164 | _ = ( +165 | / "first" + # trailing spaces before + +166 | | "second" + | |____________^ ISC003 +167 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +162 162 | ) +163 163 | +164 164 | _ = ( +165 |- "first" + # trailing spaces before + + 165 |+ "first" # trailing spaces before + +166 166 | "second" +167 167 | ) +168 168 | + +ISC.py:170:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +169 | _ = (( +170 | / "deep" + +171 | | "nesting" + | |_____________^ ISC003 +172 | )) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +167 167 | ) +168 168 | +169 169 | _ = (( +170 |- "deep" + + 170 |+ "deep" +171 171 | "nesting" +172 172 | )) +173 173 | + +ISC.py:175:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +174 | _ = ( +175 | / "contains + plus" + +176 | | "another string" + | |____________________^ ISC003 +177 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +172 172 | )) +173 173 | +174 174 | _ = ( +175 |- "contains + plus" + + 175 |+ "contains + plus" +176 176 | "another string" +177 177 | ) +178 178 | + +ISC.py:180:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +179 | _ = ( +180 | / "start" +181 | | # leading comment +182 | | + "end" + | |___________^ ISC003 +183 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +179 179 | _ = ( +180 180 | "start" +181 181 | # leading comment +182 |- + "end" + 182 |+ "end" +183 183 | ) +184 184 | +185 185 | _ = ( + +ISC.py:186:5: ISC003 [*] Explicitly concatenated string should be implicitly concatenated + | +185 | _ = ( +186 | / "start" + +187 | | # leading comment +188 | | "end" + | |_________^ ISC003 +189 | ) + | + = help: Remove redundant '+' operator to implicitly concatenate + +ℹ Safe fix +183 183 | ) +184 184 | +185 185 | _ = ( +186 |- "start" + + 186 |+ "start" +187 187 | # leading comment +188 188 | "end" +189 189 | ) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap index 73d16d6ebd2b29..e6cfb14078d4bc 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap @@ -461,6 +461,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 91 |+_ = "\128" # fix should be "\128" 92 92 | _ = "\12""foo" # fix should be "\12foo" 93 93 | _ = "\12" "" # fix should be "\12" +94 94 | ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line | @@ -479,6 +480,8 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 92 |-_ = "\12""foo" # fix should be "\12foo" 92 |+_ = "\12foo" # fix should be "\12foo" 93 93 | _ = "\12" "" # fix should be "\12" +94 94 | +95 95 | ISC.py:93:5: ISC001 [*] Implicitly concatenated string literals on one line | @@ -495,3 +498,6 @@ ISC.py:93:5: ISC001 [*] Implicitly concatenated string literals on one line 92 92 | _ = "\12""foo" # fix should be "\12foo" 93 |-_ = "\12" "" # fix should be "\12" 93 |+_ = "\12" # fix should be "\12" +94 94 | +95 95 | +96 96 | # Mixed literal + non-literal scenarios From 48c425c15b5fa585b3cd9866c1e31cc40a3a986a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 28 May 2025 02:45:11 -0500 Subject: [PATCH 258/487] [ty] Support publishing diagnostics in the server (#18309) ## Summary This PR adds support for [publishing diagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics) from the ty language server. It only adds support for it for text documents and not notebook documents because the server doesn't have full notebook support yet. Closes: astral-sh/ty#79 ## Test Plan Testing this out in Helix and Zed since those are the two editors that I know of that doesn't support pull diagnostics: ### Helix https://github.com/user-attachments/assets/e193f804-0b32-4f7e-8b83-6f9307e3d2d4 ### Zed https://github.com/user-attachments/assets/93ec7169-ce2b-4521-b009-a82d8afb9eaa --- crates/ty_project/src/db/changes.rs | 48 +++++-- crates/ty_server/src/document/notebook.rs | 3 +- .../ty_server/src/server/api/diagnostics.rs | 136 ++++++++++++++++-- .../server/api/notifications/did_change.rs | 20 +-- .../notifications/did_change_watched_files.rs | 29 +++- .../src/server/api/notifications/did_open.rs | 15 +- .../src/server/api/requests/diagnostic.rs | 9 +- crates/ty_server/src/session.rs | 14 ++ crates/ty_server/src/session/capabilities.rs | 5 + crates/ty_server/src/session/index.rs | 3 +- crates/ty_server/src/system.rs | 11 +- 11 files changed, 234 insertions(+), 59 deletions(-) diff --git a/crates/ty_project/src/db/changes.rs b/crates/ty_project/src/db/changes.rs index f2f72ae14090f3..de9dcd1b421e24 100644 --- a/crates/ty_project/src/db/changes.rs +++ b/crates/ty_project/src/db/changes.rs @@ -11,13 +11,31 @@ use ruff_db::system::SystemPath; use rustc_hash::FxHashSet; use ty_python_semantic::Program; +/// Represents the result of applying changes to the project database. +pub struct ChangeResult { + project_changed: bool, + custom_stdlib_changed: bool, +} + +impl ChangeResult { + /// Returns `true` if the project structure has changed. + pub fn project_changed(&self) -> bool { + self.project_changed + } + + /// Returns `true` if the custom stdlib's VERSIONS file has changed. + pub fn custom_stdlib_changed(&self) -> bool { + self.custom_stdlib_changed + } +} + impl ProjectDatabase { #[tracing::instrument(level = "debug", skip(self, changes, project_options_overrides))] pub fn apply_changes( &mut self, changes: Vec, project_options_overrides: Option<&ProjectOptionsOverrides>, - ) { + ) -> ChangeResult { let mut project = self.project(); let project_root = project.root(self).to_path_buf(); let config_file_override = @@ -29,10 +47,10 @@ impl ProjectDatabase { .custom_stdlib_search_path(self) .map(|path| path.join("VERSIONS")); - // Are there structural changes to the project - let mut project_changed = false; - // Changes to a custom stdlib path's VERSIONS - let mut custom_stdlib_change = false; + let mut result = ChangeResult { + project_changed: false, + custom_stdlib_changed: false, + }; // Paths that were added let mut added_paths = FxHashSet::default(); @@ -52,7 +70,7 @@ impl ProjectDatabase { if let Some(path) = change.system_path() { if let Some(config_file) = &config_file_override { if config_file.as_path() == path { - project_changed = true; + result.project_changed = true; continue; } @@ -63,13 +81,13 @@ impl ProjectDatabase { Some(".gitignore" | ".ignore" | "ty.toml" | "pyproject.toml") ) { // Changes to ignore files or settings can change the project structure or add/remove files. - project_changed = true; + result.project_changed = true; continue; } if Some(path) == custom_stdlib_versions_path.as_deref() { - custom_stdlib_change = true; + result.custom_stdlib_changed = true; } } @@ -132,7 +150,7 @@ impl ProjectDatabase { .as_ref() .is_some_and(|versions_path| versions_path.starts_with(&path)) { - custom_stdlib_change = true; + result.custom_stdlib_changed = true; } if project.is_path_included(self, &path) || path == project_root { @@ -146,7 +164,7 @@ impl ProjectDatabase { // We may want to make this more clever in the future, to e.g. iterate over the // indexed files and remove the once that start with the same path, unless // the deleted path is the project configuration. - project_changed = true; + result.project_changed = true; } } } @@ -162,7 +180,7 @@ impl ProjectDatabase { } ChangeEvent::Rescan => { - project_changed = true; + result.project_changed = true; Files::sync_all(self); sync_recursively.clear(); break; @@ -185,7 +203,7 @@ impl ProjectDatabase { last = Some(path); } - if project_changed { + if result.project_changed { let new_project_metadata = match config_file_override { Some(config_file) => ProjectMetadata::from_config_file(config_file, self.system()), None => ProjectMetadata::discover(&project_root, self.system()), @@ -227,8 +245,8 @@ impl ProjectDatabase { } } - return; - } else if custom_stdlib_change { + return result; + } else if result.custom_stdlib_changed { let search_paths = project .metadata(self) .to_program_settings(self.system()) @@ -258,5 +276,7 @@ impl ProjectDatabase { // implement a `BTreeMap` or similar and only prune the diagnostics from paths that we've // re-scanned (or that were removed etc). project.replace_index_diagnostics(self, diagnostics); + + result } } diff --git a/crates/ty_server/src/document/notebook.rs b/crates/ty_server/src/document/notebook.rs index e25d031bdac485..2616cffd706114 100644 --- a/crates/ty_server/src/document/notebook.rs +++ b/crates/ty_server/src/document/notebook.rs @@ -199,7 +199,6 @@ impl NotebookDocument { } /// Get the URI for a cell by its index within the cell array. - #[expect(dead_code)] pub(crate) fn cell_uri_by_index(&self, index: CellId) -> Option<&lsp_types::Url> { self.cells.get(index).map(|cell| &cell.url) } @@ -212,7 +211,7 @@ impl NotebookDocument { } /// Returns a list of cell URIs in the order they appear in the array. - pub(crate) fn urls(&self) -> impl Iterator { + pub(crate) fn cell_urls(&self) -> impl Iterator { self.cells.iter().map(|cell| &cell.url) } diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index b0cdb9a7b7d3c0..3adb42ec1aaa74 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -1,23 +1,51 @@ use lsp_server::ErrorCode; use lsp_types::notification::PublishDiagnostics; use lsp_types::{ - Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, NumberOrString, - PublishDiagnosticsParams, Range, Url, + CodeDescription, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, + NumberOrString, PublishDiagnosticsParams, Range, Url, }; +use rustc_hash::FxHashMap; use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; use ruff_db::files::FileRange; use ruff_db::source::{line_index, source_text}; use ty_project::{Db, ProjectDatabase}; -use crate::DocumentSnapshot; -use crate::PositionEncoding; use crate::document::{FileRangeExt, ToRangeExt}; use crate::server::Result; use crate::server::client::Notifier; +use crate::system::url_to_any_system_path; +use crate::{DocumentSnapshot, PositionEncoding, Session}; use super::LSPResult; +/// Represents the diagnostics for a text document or a notebook document. +pub(super) enum Diagnostics { + TextDocument(Vec), + + /// A map of cell URLs to the diagnostics for that cell. + NotebookDocument(FxHashMap>), +} + +impl Diagnostics { + /// Returns the diagnostics for a text document. + /// + /// # Panics + /// + /// Panics if the diagnostics are for a notebook document. + pub(super) fn expect_text_document(self) -> Vec { + match self { + Diagnostics::TextDocument(diagnostics) => diagnostics, + Diagnostics::NotebookDocument(_) => { + panic!("Expected a text document diagnostics, but got notebook diagnostics") + } + } + } +} + +/// Clears the diagnostics for the document at `uri`. +/// +/// This is done by notifying the client with an empty list of diagnostics for the document. pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> { notifier .notify::(PublishDiagnosticsParams { @@ -29,25 +57,106 @@ pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> { Ok(()) } +/// Publishes the diagnostics for the given document snapshot using the [publish diagnostics +/// notification]. +/// +/// This function is a no-op if the client supports pull diagnostics. +/// +/// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics +pub(super) fn publish_diagnostics(session: &Session, url: Url, notifier: &Notifier) -> Result<()> { + if session.client_capabilities().pull_diagnostics { + return Ok(()); + } + + let Ok(path) = url_to_any_system_path(&url) else { + return Ok(()); + }; + + let snapshot = session + .take_snapshot(url.clone()) + .ok_or_else(|| anyhow::anyhow!("Unable to take snapshot for document with URL {url}")) + .with_failure_code(lsp_server::ErrorCode::InternalError)?; + + let db = session.project_db_or_default(&path); + + let Some(diagnostics) = compute_diagnostics(db, &snapshot) else { + return Ok(()); + }; + + // Sends a notification to the client with the diagnostics for the document. + let publish_diagnostics_notification = |uri: Url, diagnostics: Vec| { + notifier + .notify::(PublishDiagnosticsParams { + uri, + diagnostics, + version: Some(snapshot.query().version()), + }) + .with_failure_code(lsp_server::ErrorCode::InternalError) + }; + + match diagnostics { + Diagnostics::TextDocument(diagnostics) => { + publish_diagnostics_notification(url, diagnostics)?; + } + Diagnostics::NotebookDocument(cell_diagnostics) => { + for (cell_url, diagnostics) in cell_diagnostics { + publish_diagnostics_notification(cell_url, diagnostics)?; + } + } + } + + Ok(()) +} + pub(super) fn compute_diagnostics( db: &ProjectDatabase, snapshot: &DocumentSnapshot, -) -> Vec { +) -> Option { let Some(file) = snapshot.file(db) else { tracing::info!( "No file found for snapshot for `{}`", snapshot.query().file_url() ); - return vec![]; + return None; }; let diagnostics = db.check_file(file); - diagnostics - .as_slice() - .iter() - .map(|message| to_lsp_diagnostic(db, message, snapshot.encoding())) - .collect() + if let Some(notebook) = snapshot.query().as_notebook() { + let mut cell_diagnostics: FxHashMap> = FxHashMap::default(); + + // Populates all relevant URLs with an empty diagnostic list. This ensures that documents + // without diagnostics still get updated. + for cell_url in notebook.cell_urls() { + cell_diagnostics.entry(cell_url.clone()).or_default(); + } + + for (cell_index, diagnostic) in diagnostics.iter().map(|diagnostic| { + ( + // TODO: Use the cell index instead using `SourceKind` + usize::default(), + to_lsp_diagnostic(db, diagnostic, snapshot.encoding()), + ) + }) { + let Some(cell_uri) = notebook.cell_uri_by_index(cell_index) else { + tracing::warn!("Unable to find notebook cell at index {cell_index}"); + continue; + }; + cell_diagnostics + .entry(cell_uri.clone()) + .or_default() + .push(diagnostic); + } + + Some(Diagnostics::NotebookDocument(cell_diagnostics)) + } else { + Some(Diagnostics::TextDocument( + diagnostics + .iter() + .map(|diagnostic| to_lsp_diagnostic(db, diagnostic, snapshot.encoding())) + .collect(), + )) + } } /// Converts the tool specific [`Diagnostic`][ruff_db::diagnostic::Diagnostic] to an LSP @@ -91,9 +200,8 @@ fn to_lsp_diagnostic( .id() .is_lint() .then(|| { - Some(lsp_types::CodeDescription { - href: lsp_types::Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id())) - .ok()?, + Some(CodeDescription { + href: Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id())).ok()?, }) }) .flatten(); diff --git a/crates/ty_server/src/server/api/notifications/did_change.rs b/crates/ty_server/src/server/api/notifications/did_change.rs index c066725c9d7e1f..0641b967919c0a 100644 --- a/crates/ty_server/src/server/api/notifications/did_change.rs +++ b/crates/ty_server/src/server/api/notifications/did_change.rs @@ -1,11 +1,12 @@ use lsp_server::ErrorCode; -use lsp_types::DidChangeTextDocumentParams; use lsp_types::notification::DidChangeTextDocument; +use lsp_types::{DidChangeTextDocumentParams, VersionedTextDocumentIdentifier}; use ty_project::watch::ChangeEvent; use crate::server::Result; use crate::server::api::LSPResult; +use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::server::client::{Notifier, Requester}; use crate::session::Session; @@ -20,18 +21,23 @@ impl NotificationHandler for DidChangeTextDocumentHandler { impl SyncNotificationHandler for DidChangeTextDocumentHandler { fn run( session: &mut Session, - _notifier: Notifier, + notifier: Notifier, _requester: &mut Requester, params: DidChangeTextDocumentParams, ) -> Result<()> { - let Ok(path) = url_to_any_system_path(¶ms.text_document.uri) else { + let DidChangeTextDocumentParams { + text_document: VersionedTextDocumentIdentifier { uri, version }, + content_changes, + } = params; + + let Ok(path) = url_to_any_system_path(&uri) else { return Ok(()); }; - let key = session.key_from_url(params.text_document.uri); + let key = session.key_from_url(uri.clone()); session - .update_text_document(&key, params.content_changes, params.text_document.version) + .update_text_document(&key, content_changes, version) .with_failure_code(ErrorCode::InternalError)?; match path { @@ -48,8 +54,6 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler { } } - // TODO(dhruvmanila): Publish diagnostics if the client doesn't support pull diagnostics - - Ok(()) + publish_diagnostics(session, uri, ¬ifier) } } diff --git a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs index d380b5c06a065d..f2038e9abcfd7d 100644 --- a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs +++ b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs @@ -1,5 +1,6 @@ use crate::server::Result; use crate::server::api::LSPResult; +use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::server::client::{Notifier, Requester}; use crate::server::schedule::Task; @@ -20,7 +21,7 @@ impl NotificationHandler for DidChangeWatchedFiles { impl SyncNotificationHandler for DidChangeWatchedFiles { fn run( session: &mut Session, - _notifier: Notifier, + notifier: Notifier, requester: &mut Requester, params: types::DidChangeWatchedFilesParams, ) -> Result<()> { @@ -85,19 +86,35 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { return Ok(()); } + let mut project_changed = false; + for (root, changes) in events_by_db { tracing::debug!("Applying changes to `{root}`"); + + // SAFETY: Only paths that are part of the workspace are registered for file watching. + // So, virtual paths and paths that are outside of a workspace does not trigger this + // notification. let db = session.project_db_for_path_mut(&*root).unwrap(); - db.apply_changes(changes, None); + let result = db.apply_changes(changes, None); + + project_changed |= result.project_changed(); } let client_capabilities = session.client_capabilities(); - if client_capabilities.diagnostics_refresh { - requester - .request::((), |()| Task::nothing()) - .with_failure_code(lsp_server::ErrorCode::InternalError)?; + if project_changed { + if client_capabilities.diagnostics_refresh { + requester + .request::((), |()| Task::nothing()) + .with_failure_code(lsp_server::ErrorCode::InternalError)?; + } else { + for url in session.text_document_urls() { + publish_diagnostics(session, url.clone(), ¬ifier)?; + } + } + + // TODO: always publish diagnostics for notebook files (since they don't use pull diagnostics) } if client_capabilities.inlay_refresh { diff --git a/crates/ty_server/src/server/api/notifications/did_open.rs b/crates/ty_server/src/server/api/notifications/did_open.rs index 1b49222be8c9a7..3dea80892a6efd 100644 --- a/crates/ty_server/src/server/api/notifications/did_open.rs +++ b/crates/ty_server/src/server/api/notifications/did_open.rs @@ -6,6 +6,7 @@ use ty_project::watch::ChangeEvent; use crate::TextDocument; use crate::server::Result; +use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::server::client::{Notifier, Requester}; use crate::session::Session; @@ -20,7 +21,7 @@ impl NotificationHandler for DidOpenTextDocumentHandler { impl SyncNotificationHandler for DidOpenTextDocumentHandler { fn run( session: &mut Session, - _notifier: Notifier, + notifier: Notifier, _requester: &mut Requester, DidOpenTextDocumentParams { text_document: @@ -37,24 +38,22 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler { }; let document = TextDocument::new(text, version).with_language_id(&language_id); - session.open_text_document(uri, document); + session.open_text_document(uri.clone(), document); - match path { + match &path { AnySystemPath::System(path) => { let db = match session.project_db_for_path_mut(path.as_std_path()) { Some(db) => db, None => session.default_project_db_mut(), }; - db.apply_changes(vec![ChangeEvent::Opened(path)], None); + db.apply_changes(vec![ChangeEvent::Opened(path.clone())], None); } AnySystemPath::SystemVirtual(virtual_path) => { let db = session.default_project_db_mut(); - db.files().virtual_file(db, &virtual_path); + db.files().virtual_file(db, virtual_path); } } - // TODO(dhruvmanila): Publish diagnostics if the client doesn't support pull diagnostics - - Ok(()) + publish_diagnostics(session, uri, ¬ifier) } } diff --git a/crates/ty_server/src/server/api/requests/diagnostic.rs b/crates/ty_server/src/server/api/requests/diagnostic.rs index a6b90f9fa10e71..013799bd701737 100644 --- a/crates/ty_server/src/server/api/requests/diagnostic.rs +++ b/crates/ty_server/src/server/api/requests/diagnostic.rs @@ -6,7 +6,7 @@ use lsp_types::{ FullDocumentDiagnosticReport, RelatedFullDocumentDiagnosticReport, }; -use crate::server::api::diagnostics::compute_diagnostics; +use crate::server::api::diagnostics::{Diagnostics, compute_diagnostics}; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; use crate::server::{Result, client::Notifier}; use crate::session::DocumentSnapshot; @@ -29,14 +29,15 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { _notifier: Notifier, _params: DocumentDiagnosticParams, ) -> Result { - let diagnostics = compute_diagnostics(db, &snapshot); - Ok(DocumentDiagnosticReportResult::Report( DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport { related_documents: None, full_document_diagnostic_report: FullDocumentDiagnosticReport { result_id: None, - items: diagnostics, + // SAFETY: Pull diagnostic requests are only called for text documents, not for + // notebook documents. + items: compute_diagnostics(db, &snapshot) + .map_or_else(Vec::new, Diagnostics::expect_text_document), }, }), )) diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index 5d1cce4bd3f3e4..d66acc33eac7ab 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -46,6 +46,7 @@ pub struct Session { /// The global position encoding, negotiated during LSP initialization. position_encoding: PositionEncoding, + /// Tracks what LSP features the client supports and doesn't support. resolved_client_capabilities: Arc, } @@ -90,6 +91,14 @@ impl Session { // and `default_workspace_db_mut` but the borrow checker doesn't allow that. // https://github.com/astral-sh/ruff/pull/13041#discussion_r1726725437 + /// Returns a reference to the project's [`ProjectDatabase`] corresponding to the given path, + /// or the default project if no project is found for the path. + pub(crate) fn project_db_or_default(&self, path: &AnySystemPath) -> &ProjectDatabase { + path.as_system() + .and_then(|path| self.project_db_for_path(path.as_std_path())) + .unwrap_or_else(|| self.default_project_db()) + } + /// Returns a reference to the project's [`ProjectDatabase`] corresponding to the given path, if /// any. pub(crate) fn project_db_for_path(&self, path: impl AsRef) -> Option<&ProjectDatabase> { @@ -141,6 +150,11 @@ impl Session { }) } + /// Iterates over the LSP URLs for all open text documents. These URLs are valid file paths. + pub(super) fn text_document_urls(&self) -> impl Iterator + '_ { + self.index().text_document_urls() + } + /// Registers a notebook document at the provided `url`. /// If a document is already open here, it will be overwritten. pub fn open_notebook_document(&mut self, url: Url, document: NotebookDocument) { diff --git a/crates/ty_server/src/session/capabilities.rs b/crates/ty_server/src/session/capabilities.rs index adfe6cb077af18..16c7ba513a40dc 100644 --- a/crates/ty_server/src/session/capabilities.rs +++ b/crates/ty_server/src/session/capabilities.rs @@ -8,7 +8,12 @@ pub(crate) struct ResolvedClientCapabilities { pub(crate) document_changes: bool, pub(crate) diagnostics_refresh: bool, pub(crate) inlay_refresh: bool, + + /// Whether [pull diagnostics] is supported. + /// + /// [pull diagnostics]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics pub(crate) pull_diagnostics: bool, + /// Whether `textDocument.typeDefinition.linkSupport` is `true` pub(crate) type_definition_link_support: bool, diff --git a/crates/ty_server/src/session/index.rs b/crates/ty_server/src/session/index.rs index bcd2c60ab85fa0..da0b7570ef9754 100644 --- a/crates/ty_server/src/session/index.rs +++ b/crates/ty_server/src/session/index.rs @@ -34,7 +34,6 @@ impl Index { } } - #[expect(dead_code)] pub(super) fn text_document_urls(&self) -> impl Iterator + '_ { self.documents .iter() @@ -135,7 +134,7 @@ impl Index { } pub(super) fn open_notebook_document(&mut self, notebook_url: Url, document: NotebookDocument) { - for cell_url in document.urls() { + for cell_url in document.cell_urls() { self.notebook_cells .insert(cell_url.clone(), notebook_url.clone()); } diff --git a/crates/ty_server/src/system.rs b/crates/ty_server/src/system.rs index 58e9425ce384c6..553e3ccbb6660d 100644 --- a/crates/ty_server/src/system.rs +++ b/crates/ty_server/src/system.rs @@ -47,12 +47,21 @@ pub(crate) fn file_to_url(db: &dyn Db, file: File) -> Option { } /// Represents either a [`SystemPath`] or a [`SystemVirtualPath`]. -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) enum AnySystemPath { System(SystemPathBuf), SystemVirtual(SystemVirtualPathBuf), } +impl AnySystemPath { + pub(crate) const fn as_system(&self) -> Option<&SystemPathBuf> { + match self { + AnySystemPath::System(system_path_buf) => Some(system_path_buf), + AnySystemPath::SystemVirtual(_) => None, + } + } +} + #[derive(Debug)] pub(crate) struct LSPSystem { /// A read-only copy of the index where the server stores all the open documents and settings. From bbcd7e019659083c3582c9fb96ebada10f39e188 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 28 May 2025 10:00:56 +0200 Subject: [PATCH 259/487] [ty] Synthetic function-like callables (#18242) ## Summary We create `Callable` types for synthesized functions like the `__init__` method of a dataclass. These generated functions are real functions though, with descriptor-like behavior. That is, they can bind `self` when accessed on an instance. This was modeled incorrectly so far. ## Test Plan Updated tests --- .../resources/mdtest/dataclasses.md | 85 ++++++++++++++----- crates/ty_python_semantic/src/types.rs | 81 +++++++++++++++++- crates/ty_python_semantic/src/types/class.rs | 32 +++++-- crates/ty_python_semantic/src/types/infer.rs | 1 + 4 files changed, 167 insertions(+), 32 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses.md index fcbe3f6c1b1206..a74c125b5dbe65 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses.md @@ -56,8 +56,6 @@ Person(20, "Eve") ## Signature of `__init__` -TODO: All of the following tests are missing the `self` argument in the `__init__` signature. - Declarations in the class body are used to generate the signature of the `__init__` method. If the attributes are not just declarations, but also bindings, the type inferred from bindings is used as the default value. @@ -71,7 +69,7 @@ class D: y: str = "default" z: int | None = 1 + 2 -reveal_type(D.__init__) # revealed: (x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None ``` This also works if the declaration and binding are split: @@ -82,7 +80,7 @@ class D: x: int | None x = None -reveal_type(D.__init__) # revealed: (x: int | None = None) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int | None = None) -> None ``` Non-fully static types are handled correctly: @@ -96,7 +94,7 @@ class C: y: int | Any z: tuple[int, Any] -reveal_type(C.__init__) # revealed: (x: Any, y: int | Any, z: tuple[int, Any]) -> None +reveal_type(C.__init__) # revealed: (self: C, x: Any, y: int | Any, z: tuple[int, Any]) -> None ``` Variables without annotations are ignored: @@ -107,7 +105,7 @@ class D: x: int y = 1 -reveal_type(D.__init__) # revealed: (x: int) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int) -> None ``` If attributes without default values are declared after attributes with default values, a @@ -132,7 +130,7 @@ class D: y: ClassVar[str] = "default" z: bool -reveal_type(D.__init__) # revealed: (x: int, z: bool) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int, z: bool) -> None d = D(1, True) reveal_type(d.x) # revealed: int @@ -150,7 +148,7 @@ class D: def y(self) -> str: return "" -reveal_type(D.__init__) # revealed: (x: int) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int) -> None ``` And neither do nested class declarations: @@ -163,7 +161,7 @@ class D: class Nested: y: str -reveal_type(D.__init__) # revealed: (x: int) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int) -> None ``` But if there is a variable annotation with a function or class literal type, the signature of @@ -181,7 +179,7 @@ class D: class_literal: TypeOf[SomeClass] class_subtype_of: type[SomeClass] -# revealed: (function_literal: def some_function() -> None, class_literal: , class_subtype_of: type[SomeClass]) -> None +# revealed: (self: D, function_literal: def some_function() -> None, class_literal: , class_subtype_of: type[SomeClass]) -> None reveal_type(D.__init__) ``` @@ -194,7 +192,7 @@ from typing import Callable class D: c: Callable[[int], str] -reveal_type(D.__init__) # revealed: (c: (int, /) -> str) -> None +reveal_type(D.__init__) # revealed: (self: D, c: (int, /) -> str) -> None ``` Implicit instance attributes do not affect the signature of `__init__`: @@ -209,7 +207,7 @@ class D: reveal_type(D(1).y) # revealed: str -reveal_type(D.__init__) # revealed: (x: int) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int) -> None ``` Annotating expressions does not lead to an entry in `__annotations__` at runtime, and so it wouldn't @@ -222,7 +220,7 @@ class D: (x): int = 1 # TODO: should ideally not include a `x` parameter -reveal_type(D.__init__) # revealed: (x: int = Literal[1]) -> None +reveal_type(D.__init__) # revealed: (self: D, x: int = Literal[1]) -> None ``` ## `@dataclass` calls with arguments @@ -529,7 +527,7 @@ class C(Base): z: int = 10 x: int = 15 -reveal_type(C.__init__) # revealed: (x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None +reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None ``` ## Generic dataclasses @@ -582,7 +580,7 @@ class UppercaseString: class C: upper: UppercaseString = UppercaseString() -reveal_type(C.__init__) # revealed: (upper: str = str) -> None +reveal_type(C.__init__) # revealed: (self: C, upper: str = str) -> None c = C("abc") reveal_type(c.upper) # revealed: str @@ -628,7 +626,7 @@ class ConvertToLength: class C: converter: ConvertToLength = ConvertToLength() -reveal_type(C.__init__) # revealed: (converter: str = Literal[""]) -> None +reveal_type(C.__init__) # revealed: (self: C, converter: str = Literal[""]) -> None c = C("abc") reveal_type(c.converter) # revealed: int @@ -667,7 +665,7 @@ class AcceptsStrAndInt: class C: field: AcceptsStrAndInt = AcceptsStrAndInt() -reveal_type(C.__init__) # revealed: (field: str | int = int) -> None +reveal_type(C.__init__) # revealed: (self: C, field: str | int = int) -> None ``` ## `dataclasses.field` @@ -728,7 +726,7 @@ import dataclasses class C: x: str -reveal_type(C.__init__) # revealed: (x: str) -> None +reveal_type(C.__init__) # revealed: (self: C, x: str) -> None ``` ### Dataclass with custom `__init__` method @@ -821,10 +819,57 @@ reveal_type(Person.__mro__) # revealed: tuple[, None +reveal_type(Person.__init__) # revealed: (self: Person, name: str, age: int | None = None) -> None reveal_type(Person.__repr__) # revealed: def __repr__(self) -> str reveal_type(Person.__eq__) # revealed: def __eq__(self, value: object, /) -> bool ``` + +## Function-like behavior of synthesized methods + +Here, we make sure that the synthesized methods of dataclasses behave like proper functions. + +```toml +[environment] +python-version = "3.12" +``` + +```py +from dataclasses import dataclass +from typing import Callable +from types import FunctionType +from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to + +@dataclass +class C: + x: int + +reveal_type(C.__init__) # revealed: (self: C, x: int) -> None +reveal_type(type(C.__init__)) # revealed: + +# We can access attributes that are defined on functions: +reveal_type(type(C.__init__).__code__) # revealed: CodeType +reveal_type(C.__init__.__code__) # revealed: CodeType + +def equivalent_signature(self: C, x: int) -> None: + pass + +type DunderInitType = TypeOf[C.__init__] +type EquivalentPureCallableType = Callable[[C, int], None] +type EquivalentFunctionLikeCallableType = CallableTypeOf[equivalent_signature] + +static_assert(is_subtype_of(DunderInitType, EquivalentPureCallableType)) +static_assert(is_assignable_to(DunderInitType, EquivalentPureCallableType)) + +static_assert(not is_subtype_of(EquivalentPureCallableType, DunderInitType)) +static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType)) + +static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType)) +static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType)) + +static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType)) +static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType)) + +static_assert(is_subtype_of(DunderInitType, FunctionType)) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 5ee101e973d168..9b53980915ea48 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1235,6 +1235,14 @@ impl<'db> Type<'db> { ) => (self.literal_fallback_instance(db)) .is_some_and(|instance| instance.is_subtype_of(db, target)), + // Function-like callables are subtypes of `FunctionType` + (Type::Callable(callable), Type::NominalInstance(target)) + if callable.is_function_like(db) + && target.class.is_known(db, KnownClass::FunctionType) => + { + true + } + (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { self_function_literal .into_callable_type(db) @@ -2747,6 +2755,26 @@ impl<'db> Type<'db> { instance.display(db), owner.display(db) ); + match self { + Type::Callable(callable) if callable.is_function_like(db) => { + // For "function-like" callables, model the the behavior of `FunctionType.__get__`. + // + // It is a shortcut to model this in `try_call_dunder_get`. If we want to be really precise, + // we should instead return a new method-wrapper type variant for the synthesized `__get__` + // method of these synthesized functions. The method-wrapper would then be returned from + // `find_name_in_mro` when called on function-like `Callable`s. This would allow us to + // correctly model the behavior of *explicit* `SomeDataclass.__init__.__get__` calls. + return if instance.is_none(db) { + Some((self, AttributeKind::NormalOrNonDataDescriptor)) + } else { + Some(( + Type::Callable(callable.bind_self(db)), + AttributeKind::NormalOrNonDataDescriptor, + )) + }; + } + _ => {} + } let descr_get = self.class_member(db, "__get__".into()).symbol; @@ -3080,6 +3108,11 @@ impl<'db> Type<'db> { Type::Callable(_) | Type::DataclassTransformer(_) if name_str == "__call__" => { Symbol::bound(self).into() } + + Type::Callable(callable) if callable.is_function_like(db) => KnownClass::FunctionType + .to_instance(db) + .member_lookup_with_policy(db, name, policy), + Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object .to_instance(db) .member_lookup_with_policy(db, name, policy), @@ -5139,6 +5172,9 @@ impl<'db> Type<'db> { Type::MethodWrapper(_) => KnownClass::MethodWrapperType.to_class_literal(db), Type::WrapperDescriptor(_) => KnownClass::WrapperDescriptorType.to_class_literal(db), Type::DataclassDecorator(_) => KnownClass::FunctionType.to_class_literal(db), + Type::Callable(callable) if callable.is_function_like(db) => { + KnownClass::FunctionType.to_class_literal(db) + } Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), @@ -6936,6 +6972,7 @@ impl<'db> FunctionType<'db> { Type::Callable(CallableType::from_overloads( db, self.signature(db).overloads.iter().cloned(), + false, )) } @@ -7562,6 +7599,7 @@ impl<'db> BoundMethodType<'db> { .overloads .iter() .map(signatures::Signature::bind_self), + false, )) } @@ -7626,12 +7664,23 @@ impl<'db> BoundMethodType<'db> { pub struct CallableType<'db> { #[returns(deref)] signatures: Box<[Signature<'db>]>, + /// We use `CallableType` to represent function-like objects, like the synthesized methods + /// of dataclasses or NamedTuples. These callables act like real functions when accessed + /// as attributes on instances, i.e. they bind `self`. + is_function_like: bool, } impl<'db> CallableType<'db> { /// Create a non-overloaded callable type with a single signature. pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self { - CallableType::new(db, vec![signature].into_boxed_slice()) + CallableType::new(db, vec![signature].into_boxed_slice(), false) + } + + /// Create a non-overloaded, function-like callable type with a single signature. + /// + /// A function-like callable will bind `self` when accessed as an attribute on an instance. + pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Self { + CallableType::new(db, vec![signature].into_boxed_slice(), true) } /// Create an overloaded callable type with multiple signatures. @@ -7639,7 +7688,7 @@ impl<'db> CallableType<'db> { /// # Panics /// /// Panics if `overloads` is empty. - pub(crate) fn from_overloads(db: &'db dyn Db, overloads: I) -> Self + pub(crate) fn from_overloads(db: &'db dyn Db, overloads: I, is_function_like: bool) -> Self where I: IntoIterator>, { @@ -7648,7 +7697,7 @@ impl<'db> CallableType<'db> { !overloads.is_empty(), "CallableType must have at least one signature" ); - CallableType::new(db, overloads) + CallableType::new(db, overloads, is_function_like) } /// Create a callable type which accepts any parameters and returns an `Unknown` type. @@ -7659,6 +7708,14 @@ impl<'db> CallableType<'db> { ) } + pub(crate) fn bind_self(self, db: &'db dyn Db) -> Self { + CallableType::from_overloads( + db, + self.signatures(db).iter().map(Signature::bind_self), + false, + ) + } + /// Create a callable type which represents a fully-static "bottom" callable. /// /// Specifically, this represents a callable type with a single signature: @@ -7677,6 +7734,7 @@ impl<'db> CallableType<'db> { self.signatures(db) .iter() .map(|signature| signature.normalized(db)), + self.is_function_like(db), ) } @@ -7686,6 +7744,7 @@ impl<'db> CallableType<'db> { self.signatures(db) .iter() .map(|signature| signature.apply_type_mapping(db, type_mapping)), + self.is_function_like(db), ) } @@ -7734,6 +7793,13 @@ impl<'db> CallableType<'db> { where F: Fn(&Signature<'db>, &Signature<'db>) -> bool, { + let self_is_function_like = self.is_function_like(db); + let other_is_function_like = other.is_function_like(db); + + if !self_is_function_like && other_is_function_like { + return false; + } + match (self.signatures(db), other.signatures(db)) { ([self_signature], [other_signature]) => { // Base case: both callable types contain a single signature. @@ -7776,6 +7842,10 @@ impl<'db> CallableType<'db> { /// /// See [`Type::is_equivalent_to`] for more details. fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + if self.is_function_like(db) != other.is_function_like(db) { + return false; + } + match (self.signatures(db), other.signatures(db)) { ([self_signature], [other_signature]) => { // Common case: both callable types contain a single signature, use the custom @@ -7802,6 +7872,10 @@ impl<'db> CallableType<'db> { /// /// See [`Type::is_gradual_equivalent_to`] for more details. fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + if self.is_function_like(db) != other.is_function_like(db) { + return false; + } + match (self.signatures(db), other.signatures(db)) { ([self_signature], [other_signature]) => { self_signature.is_gradual_equivalent_to(db, other_signature) @@ -7821,6 +7895,7 @@ impl<'db> CallableType<'db> { .iter() .cloned() .map(|signature| signature.replace_self_reference(db, class)), + self.is_function_like(db), ) } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 0aa8e785fe71e4..1a89063878a794 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1267,7 +1267,7 @@ impl<'db> ClassLiteral<'db> { } let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); - Some(Type::Callable(CallableType::single(db, signature))) + Some(Type::Callable(CallableType::function_like(db, signature))) }; match (field_policy, name) { @@ -1281,7 +1281,13 @@ impl<'db> ClassLiteral<'db> { return None; } - signature_from_fields(vec![]) + let self_parameter = Parameter::positional_or_keyword(Name::new_static("self")) + // TODO: could be `Self`. + .with_annotated_type(Type::instance( + db, + self.apply_optional_specialization(db, specialization), + )); + signature_from_fields(vec![self_parameter]) } (CodeGeneratorKind::NamedTuple, "__new__") => { let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls")) @@ -1294,16 +1300,24 @@ impl<'db> ClassLiteral<'db> { } let signature = Signature::new( - Parameters::new([Parameter::positional_or_keyword(Name::new_static("other")) - // TODO: could be `Self`. - .with_annotated_type(Type::instance( - db, - self.apply_optional_specialization(db, specialization), - ))]), + Parameters::new([ + Parameter::positional_or_keyword(Name::new_static("self")) + // TODO: could be `Self`. + .with_annotated_type(Type::instance( + db, + self.apply_optional_specialization(db, specialization), + )), + Parameter::positional_or_keyword(Name::new_static("other")) + // TODO: could be `Self`. + .with_annotated_type(Type::instance( + db, + self.apply_optional_specialization(db, specialization), + )), + ]), Some(KnownClass::Bool.to_instance(db)), ); - Some(Type::Callable(CallableType::single(db, signature))) + Some(Type::Callable(CallableType::function_like(db, signature))) } (CodeGeneratorKind::NamedTuple, name) if name != "__init__" => { KnownClass::NamedTupleFallback diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 950aba851d00af..325d0e00db8ee1 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -8737,6 +8737,7 @@ impl<'db> TypeInferenceBuilder<'db> { Type::Callable(CallableType::from_overloads( db, std::iter::once(signature).chain(signature_iter), + false, )) } }, From 66ba1d87751eac470475156e5060e0d983978966 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 28 May 2025 10:59:29 +0200 Subject: [PATCH 260/487] [ty] Support cancellation and retry in the server (#18273) --- crates/ty_server/src/lib.rs | 20 +- crates/ty_server/src/message.rs | 54 ---- crates/ty_server/src/server.rs | 161 +++-------- crates/ty_server/src/server/api.rs | 213 +++++++++----- .../ty_server/src/server/api/diagnostics.rs | 17 +- .../ty_server/src/server/api/notifications.rs | 2 + .../src/server/api/notifications/cancel.rs | 27 ++ .../server/api/notifications/did_change.rs | 10 +- .../notifications/did_change_watched_files.rs | 20 +- .../src/server/api/notifications/did_close.rs | 16 +- .../api/notifications/did_close_notebook.rs | 8 +- .../src/server/api/notifications/did_open.rs | 12 +- .../api/notifications/did_open_notebook.rs | 5 +- .../src/server/api/requests/completion.rs | 6 +- .../src/server/api/requests/diagnostic.rs | 16 +- .../api/requests/goto_type_definition.rs | 4 +- .../src/server/api/requests/hover.rs | 4 +- .../src/server/api/requests/inlay_hints.rs | 4 +- crates/ty_server/src/server/api/traits.rs | 23 +- crates/ty_server/src/server/client.rs | 169 ------------ crates/ty_server/src/server/connection.rs | 69 +---- crates/ty_server/src/server/main_loop.rs | 212 ++++++++++++++ crates/ty_server/src/server/schedule.rs | 58 +--- crates/ty_server/src/server/schedule/task.rs | 20 +- crates/ty_server/src/session.rs | 28 +- crates/ty_server/src/session/client.rs | 260 ++++++++++++++++++ crates/ty_server/src/session/request_queue.rs | 197 +++++++++++++ crates/ty_server/src/session/settings.rs | 1 - 28 files changed, 1014 insertions(+), 622 deletions(-) delete mode 100644 crates/ty_server/src/message.rs create mode 100644 crates/ty_server/src/server/api/notifications/cancel.rs delete mode 100644 crates/ty_server/src/server/client.rs create mode 100644 crates/ty_server/src/server/main_loop.rs create mode 100644 crates/ty_server/src/session/client.rs create mode 100644 crates/ty_server/src/session/request_queue.rs diff --git a/crates/ty_server/src/lib.rs b/crates/ty_server/src/lib.rs index efc27184c72192..0cf68dfdf988cd 100644 --- a/crates/ty_server/src/lib.rs +++ b/crates/ty_server/src/lib.rs @@ -1,12 +1,9 @@ -use crate::server::Server; +use crate::server::{ConnectionInitializer, Server}; use anyhow::Context; pub use document::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument}; pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session}; use std::num::NonZeroUsize; -#[macro_use] -mod message; - mod document; mod logging; mod server; @@ -32,9 +29,18 @@ pub fn run_server() -> anyhow::Result<()> { .unwrap_or(four) .min(four); - Server::new(worker_threads) + let (connection, io_threads) = ConnectionInitializer::stdio(); + + let server_result = Server::new(worker_threads, connection) .context("Failed to start server")? - .run()?; + .run(); + + let io_result = io_threads.join(); - Ok(()) + match (server_result, io_result) { + (Ok(()), Ok(())) => Ok(()), + (Err(server), Err(io)) => Err(server).context(format!("IO thread error: {io}")), + (Err(server), _) => Err(server), + (_, Err(io)) => Err(io).context("IO thread error"), + } } diff --git a/crates/ty_server/src/message.rs b/crates/ty_server/src/message.rs deleted file mode 100644 index 8b21fcf52a58ad..00000000000000 --- a/crates/ty_server/src/message.rs +++ /dev/null @@ -1,54 +0,0 @@ -use anyhow::Context; -use lsp_types::notification::Notification; -use std::sync::OnceLock; - -use crate::server::ClientSender; - -static MESSENGER: OnceLock = OnceLock::new(); - -pub(crate) fn init_messenger(client_sender: ClientSender) { - MESSENGER - .set(client_sender) - .expect("messenger should only be initialized once"); -} - -pub(crate) fn show_message(message: String, message_type: lsp_types::MessageType) { - try_show_message(message, message_type).unwrap(); -} - -pub(super) fn try_show_message( - message: String, - message_type: lsp_types::MessageType, -) -> crate::Result<()> { - MESSENGER - .get() - .ok_or_else(|| anyhow::anyhow!("messenger not initialized"))? - .send(lsp_server::Message::Notification( - lsp_server::Notification { - method: lsp_types::notification::ShowMessage::METHOD.into(), - params: serde_json::to_value(lsp_types::ShowMessageParams { - typ: message_type, - message, - })?, - }, - )) - .context("Failed to send message")?; - - Ok(()) -} - -/// Sends an error to the client with a formatted message. The error is sent in a -/// `window/showMessage` notification. -macro_rules! show_err_msg { - ($msg:expr$(, $($arg:tt)*)?) => { - crate::message::show_message(::core::format_args!($msg$(, $($arg)*)?).to_string(), lsp_types::MessageType::ERROR) - }; -} - -/// Sends a request to display a warning to the client with a formatted message. The warning is -/// sent in a `window/showMessage` notification. -macro_rules! show_warn_msg { - ($msg:expr$(, $($arg:tt)*)?) => { - crate::message::show_message(::core::format_args!($msg$(, $($arg)*)?).to_string(), lsp_types::MessageType::WARNING) - }; -} diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index bb030ad212fb3c..3ceb5402021eaf 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -1,9 +1,7 @@ //! Scheduling, I/O, and API endpoints. -use lsp_server::Message; use lsp_types::{ - ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, - DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher, HoverProviderCapability, + ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability, InlayHintOptions, InlayHintServerCapabilities, MessageType, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, Url, @@ -11,19 +9,20 @@ use lsp_types::{ use std::num::NonZeroUsize; use std::panic::PanicHookInfo; -use self::connection::{Connection, ConnectionInitializer}; -use self::schedule::event_loop_thread; +use self::connection::Connection; +use self::schedule::spawn_main_loop; use crate::PositionEncoding; use crate::session::{AllSettings, ClientSettings, Experimental, Session}; mod api; -mod client; mod connection; +mod main_loop; mod schedule; -use crate::message::try_show_message; -use crate::server::schedule::Task; -pub(crate) use connection::ClientSender; +use crate::session::client::Client; +pub(crate) use api::Error; +pub(crate) use connection::{ConnectionInitializer, ConnectionSender}; +pub(crate) use main_loop::{Action, Event, MainLoopReceiver, MainLoopSender}; pub(crate) type Result = std::result::Result; @@ -31,13 +30,16 @@ pub(crate) struct Server { connection: Connection, client_capabilities: ClientCapabilities, worker_threads: NonZeroUsize, + main_loop_receiver: MainLoopReceiver, + main_loop_sender: MainLoopSender, session: Session, } impl Server { - pub(crate) fn new(worker_threads: NonZeroUsize) -> crate::Result { - let connection = ConnectionInitializer::stdio(); - + pub(crate) fn new( + worker_threads: NonZeroUsize, + connection: ConnectionInitializer, + ) -> crate::Result { let (id, init_params) = connection.initialize_start()?; let AllSettings { @@ -61,7 +63,11 @@ impl Server { crate::version(), )?; - crate::message::init_messenger(connection.make_sender()); + // The number 32 was chosen arbitrarily. The main goal was to have enough capacity to queue + // some responses before blocking. + let (main_loop_sender, main_loop_receiver) = crossbeam::channel::bounded(32); + let client = Client::new(main_loop_sender.clone(), connection.sender()); + crate::logging::init_logging( global_settings.tracing.log_level.unwrap_or_default(), global_settings.tracing.log_file.as_deref(), @@ -99,10 +105,10 @@ impl Server { "Multiple workspaces are not yet supported, using the first workspace: {}", &first_workspace.0 ); - show_warn_msg!( + client.show_warning_message(format_args!( "Multiple workspaces are not yet supported, using the first workspace: {}", - &first_workspace.0 - ); + &first_workspace.0, + )); vec![first_workspace] } else { workspaces @@ -111,6 +117,8 @@ impl Server { Ok(Self { connection, worker_threads, + main_loop_receiver, + main_loop_sender, session: Session::new( &client_capabilities, position_encoding, @@ -121,7 +129,7 @@ impl Server { }) } - pub(crate) fn run(self) -> crate::Result<()> { + pub(crate) fn run(mut self) -> crate::Result<()> { type PanicHook = Box) + 'static + Sync + Send>; struct RestorePanicHook { hook: Option, @@ -141,6 +149,8 @@ impl Server { hook: Some(std::panic::take_hook()), }; + let client = Client::new(self.main_loop_sender.clone(), self.connection.sender()); + // When we panic, try to notify the client. std::panic::set_hook(Box::new(move |panic_info| { use std::io::Write; @@ -154,118 +164,15 @@ impl Server { let mut stderr = std::io::stderr().lock(); writeln!(stderr, "{panic_info}\n{backtrace}").ok(); - try_show_message( - "The ty language server exited with a panic. See the logs for more details." - .to_string(), - MessageType::ERROR, - ) - .ok(); + client + .show_message( + "The ty language server exited with a panic. See the logs for more details.", + MessageType::ERROR, + ) + .ok(); })); - event_loop_thread(move || { - Self::event_loop( - &self.connection, - &self.client_capabilities, - self.session, - self.worker_threads, - )?; - self.connection.close()?; - Ok(()) - })? - .join() - } - - fn event_loop( - connection: &Connection, - client_capabilities: &ClientCapabilities, - mut session: Session, - worker_threads: NonZeroUsize, - ) -> crate::Result<()> { - let mut scheduler = - schedule::Scheduler::new(&mut session, worker_threads, connection.make_sender()); - - let fs_watcher = client_capabilities - .workspace - .as_ref() - .and_then(|workspace| workspace.did_change_watched_files?.dynamic_registration) - .unwrap_or_default(); - - if fs_watcher { - let registration = lsp_types::Registration { - id: "workspace/didChangeWatchedFiles".to_owned(), - method: "workspace/didChangeWatchedFiles".to_owned(), - register_options: Some( - serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { - watchers: vec![ - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String("**/ty.toml".into()), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String( - "**/.gitignore".into(), - ), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String("**/.ignore".into()), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String( - "**/pyproject.toml".into(), - ), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String("**/*.py".into()), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String("**/*.pyi".into()), - kind: None, - }, - FileSystemWatcher { - glob_pattern: lsp_types::GlobPattern::String("**/*.ipynb".into()), - kind: None, - }, - ], - }) - .unwrap(), - ), - }; - let response_handler = |()| { - tracing::info!("File watcher successfully registered"); - Task::nothing() - }; - - if let Err(err) = scheduler.request::( - lsp_types::RegistrationParams { - registrations: vec![registration], - }, - response_handler, - ) { - tracing::error!( - "An error occurred when trying to register the configuration file watcher: {err}" - ); - } - } else { - tracing::warn!("The client does not support file system watching."); - } - - for msg in connection.incoming() { - if connection.handle_shutdown(&msg)? { - break; - } - let task = match msg { - Message::Request(req) => api::request(req), - Message::Notification(notification) => api::notification(notification), - Message::Response(response) => scheduler.response(response), - }; - scheduler.dispatch(task); - } - - Ok(()) + spawn_main_loop(move || self.main_loop())?.join() } fn find_best_position_encoding(client_capabilities: &ClientCapabilities) -> PositionEncoding { diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index ce3dd2c0b5ab58..d6a934c9489fbf 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -5,7 +5,7 @@ use anyhow::anyhow; use lsp_server as server; use lsp_server::RequestId; use lsp_types::notification::Notification; -use ruff_db::panic::PanicError; +use lsp_types::request::Request; use std::panic::UnwindSafe; mod diagnostics; @@ -14,8 +14,16 @@ mod requests; mod traits; use self::traits::{NotificationHandler, RequestHandler}; -use super::{Result, client::Responder, schedule::BackgroundSchedule}; +use super::{Result, schedule::BackgroundSchedule}; +use crate::session::client::Client; +use ruff_db::panic::PanicError; +/// Processes a request from the client to the server. +/// +/// The LSP specification requires that each request has exactly one response. Therefore, +/// it's crucial that all paths in this method call [`Client::respond`] exactly once. +/// The only exception to this is requests that were cancelled by the client. In this case, +/// the response was already sent by the [`notification::CancelNotificationHandler`]. pub(super) fn request(req: server::Request) -> Task { let id = req.id.clone(); @@ -45,7 +53,7 @@ pub(super) fn request(req: server::Request) -> Task { method => { tracing::warn!("Received request {method} which does not have a handler"); let result: Result<()> = Err(Error::new( - anyhow!("Unknown request"), + anyhow!("Unknown request: {method}"), server::ErrorCode::MethodNotFound, )); return Task::immediate(id, result); @@ -53,11 +61,21 @@ pub(super) fn request(req: server::Request) -> Task { } .unwrap_or_else(|err| { tracing::error!("Encountered error when routing request with ID {id}: {err}"); - show_err_msg!( - "ty failed to handle a request from the editor. Check the logs for more details." - ); - let result: Result<()> = Err(err); - Task::immediate(id, result) + + Task::local(move |_session, client| { + client.show_error_message( + "ty failed to handle a request from the editor. Check the logs for more details.", + ); + respond_silent_error( + id, + client, + lsp_server::ResponseError { + code: err.code as i32, + message: err.to_string(), + data: None, + }, + ); + }) }) } @@ -81,6 +99,9 @@ pub(super) fn notification(notif: server::Notification) -> Task { notifications::DidChangeWatchedFiles::METHOD => { local_notification_task::(notif) } + lsp_types::notification::Cancel::METHOD => { + local_notification_task::(notif) + } lsp_types::notification::SetTrace::METHOD => { tracing::trace!("Ignoring `setTrace` notification"); return Task::nothing(); @@ -91,41 +112,50 @@ pub(super) fn notification(notif: server::Notification) -> Task { return Task::nothing(); } } - .unwrap_or_else(|err| { - tracing::error!("Encountered error when routing notification: {err}"); - show_err_msg!( - "ty failed to handle a notification from the editor. Check the logs for more details." - ); - Task::nothing() - }) + .unwrap_or_else(|err| { + tracing::error!("Encountered error when routing notification: {err}"); + Task::local(|_session, client| { + client.show_error_message( + "ty failed to handle a notification from the editor. Check the logs for more details." + ); + }) + }) } -fn _local_request_task(req: server::Request) -> super::Result +fn _local_request_task(req: server::Request) -> Result where - <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, + <::RequestType as Request>::Params: UnwindSafe, { let (id, params) = cast_request::(req)?; - Ok(Task::local(|session, notifier, requester, responder| { + Ok(Task::local(move |session, client: &Client| { let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); - let result = R::run(session, notifier, requester, params); - respond::(id, result, &responder); + let result = R::run(session, client, params); + respond::(&id, result, client); })) } fn background_request_task( req: server::Request, schedule: BackgroundSchedule, -) -> super::Result +) -> Result where - <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, + <::RequestType as Request>::Params: UnwindSafe, { + let retry = R::RETRY_ON_CANCELLATION.then(|| req.clone()); let (id, params) = cast_request::(req)?; + Ok(Task::background(schedule, move |session: &Session| { + let cancellation_token = session + .request_queue() + .incoming() + .cancellation_token(&id) + .expect("request should have been tested for cancellation before scheduling"); + let url = R::document_url(¶ms).into_owned(); let Ok(path) = url_to_any_system_path(&url) else { tracing::warn!("Ignoring request for invalid `{url}`"); - return Box::new(|_, _| {}); + return Box::new(|_| {}); }; let db = match &path { @@ -138,17 +168,31 @@ where let Some(snapshot) = session.take_snapshot(url) else { tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist."); - return Box::new(|_, _| {}); + return Box::new(|_| {}); }; - Box::new(move |notifier, responder| { + Box::new(move |client| { let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); + + // Test again if the request was cancelled since it was scheduled on the background task + // and, if so, return early + if cancellation_token.is_cancelled() { + tracing::trace!( + "Ignoring request id={id} method={} because it was cancelled", + R::METHOD + ); + + // We don't need to send a response here because the `cancel` notification + // handler already responded with a message. + return; + } + let result = ruff_db::panic::catch_unwind(|| { - R::run_with_snapshot(&db, snapshot, notifier, params) + R::run_with_snapshot(&db, snapshot, client, params) }); - if let Some(response) = request_result_to_response(&id, &responder, result) { - respond::(id, response, &responder); + if let Some(response) = request_result_to_response::(&id, client, result, retry) { + respond::(&id, response, client); } }) })) @@ -156,29 +200,45 @@ where fn request_result_to_response( id: &RequestId, - responder: &Responder, - result: std::result::Result, PanicError>, -) -> Option> { + client: &Client, + result: std::result::Result< + Result<<::RequestType as Request>::Result>, + PanicError, + >, + request: Option, +) -> Option::RequestType as Request>::Result>> +where + R: traits::BackgroundDocumentRequestHandler, +{ match result { Ok(response) => Some(response), Err(error) => { + // Check if the request was canceled due to some modifications to the salsa database. if error.payload.downcast_ref::().is_some() { - // Request was cancelled by Salsa. TODO: Retry - respond_silent_error( - id.clone(), - responder, - Error { - code: lsp_server::ErrorCode::ContentModified, - error: anyhow!("content modified"), - }, + // If the query supports retry, re-queue the request. + // The query is still likely to succeed if the user modified any other document. + if let Some(request) = request { + tracing::trace!( + "request id={} method={} was cancelled by salsa, re-queueing for retry", + request.id, + request.method + ); + if client.retry(request).is_ok() { + return None; + } + } + + tracing::trace!( + "request id={} was cancelled by salsa, sending content modified", + id ); + + respond_silent_error(id.clone(), client, R::salsa_cancellation_error()); None } else { - let message = format!("request handler {error}"); - Some(Err(Error { code: lsp_server::ErrorCode::InternalError, - error: anyhow!(message), + error: anyhow!("request handler {error}"), })) } } @@ -187,13 +247,13 @@ fn request_result_to_response( fn local_notification_task( notif: server::Notification, -) -> super::Result { +) -> Result { let (id, params) = cast_notification::(notif)?; - Ok(Task::local(move |session, notifier, requester, _| { + Ok(Task::local(move |session, client| { let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); - if let Err(err) = N::run(session, notifier, requester, params) { + if let Err(err) = N::run(session, client, params) { tracing::error!("An error occurred while running {id}: {err}"); - show_err_msg!("ty encountered a problem. Check the logs for more details."); + client.show_error_message("ty encountered a problem. Check the logs for more details."); } })) } @@ -202,11 +262,10 @@ fn local_notification_task( fn background_notification_thread( req: server::Notification, schedule: BackgroundSchedule, -) -> super::Result +) -> Result where N: traits::BackgroundDocumentNotificationHandler, - <::NotificationType as lsp_types::notification::Notification>::Params: - UnwindSafe, + <::NotificationType as Notification>::Params: UnwindSafe, { let (id, params) = cast_notification::(req)?; Ok(Task::background(schedule, move |session: &Session| { @@ -215,25 +274,29 @@ where tracing::debug!( "Ignoring notification because snapshot for url `{url}` doesn't exist." ); - return Box::new(|_, _| {}); + return Box::new(|_| {}); }; - Box::new(move |notifier, _| { + Box::new(move |client| { let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); let result = match ruff_db::panic::catch_unwind(|| { - N::run_with_snapshot(snapshot, notifier, params) + N::run_with_snapshot(snapshot, client, params) }) { Ok(result) => result, Err(panic) => { tracing::error!("An error occurred while running {id}: {panic}"); - show_err_msg!("ty encountered a panic. Check the logs for more details."); + client.show_error_message( + "ty encountered a panic. Check the logs for more details.", + ); return; } }; if let Err(err) = result { tracing::error!("An error occurred while running {id}: {err}"); - show_err_msg!("ty encountered a problem. Check the logs for more details."); + client.show_error_message( + "ty encountered a problem. Check the logs for more details.", + ); } }) })) @@ -245,13 +308,13 @@ where /// implementation. fn cast_request( request: server::Request, -) -> super::Result<( - server::RequestId, - <::RequestType as lsp_types::request::Request>::Params, +) -> Result<( + RequestId, + <::RequestType as Request>::Params, )> where - Req: traits::RequestHandler, - <::RequestType as lsp_types::request::Request>::Params: UnwindSafe, + Req: RequestHandler, + <::RequestType as Request>::Params: UnwindSafe, { request .extract(Req::METHOD) @@ -269,27 +332,25 @@ where /// Sends back a response to the server using a [`Responder`]. fn respond( - id: server::RequestId, - result: crate::server::Result< - <::RequestType as lsp_types::request::Request>::Result, - >, - responder: &Responder, + id: &RequestId, + result: Result<<::RequestType as Request>::Result>, + client: &Client, ) where - Req: traits::RequestHandler, + Req: RequestHandler, { if let Err(err) = &result { tracing::error!("An error occurred with request ID {id}: {err}"); - show_err_msg!("ty encountered a problem. Check the logs for more details."); + client.show_error_message("ty encountered a problem. Check the logs for more details."); } - if let Err(err) = responder.respond(id, result) { + if let Err(err) = client.respond(id, result) { tracing::error!("Failed to send response: {err}"); } } -/// Sends back an error response to the server using a [`Responder`] without showing a warning +/// Sends back an error response to the server using a [`Client`] without showing a warning /// to the user. -fn respond_silent_error(id: server::RequestId, responder: &Responder, error: Error) { - if let Err(err) = responder.respond::<()>(id, Err(error)) { +fn respond_silent_error(id: RequestId, client: &Client, error: lsp_server::ResponseError) { + if let Err(err) = client.respond_err(id, error) { tracing::error!("Failed to send response: {err}"); } } @@ -298,12 +359,12 @@ fn respond_silent_error(id: server::RequestId, responder: &Responder, error: Err /// a parameter type for a specific request handler. fn cast_notification( notification: server::Notification, -) -> super::Result< - ( - &'static str, - <::NotificationType as lsp_types::notification::Notification>::Params, - )> where - N: traits::NotificationHandler, +) -> Result<( + &'static str, + <::NotificationType as Notification>::Params, +)> +where + N: NotificationHandler, { Ok(( N::METHOD, diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index 3adb42ec1aaa74..57ffbd6650dd50 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -11,14 +11,13 @@ use ruff_db::files::FileRange; use ruff_db::source::{line_index, source_text}; use ty_project::{Db, ProjectDatabase}; +use super::LSPResult; use crate::document::{FileRangeExt, ToRangeExt}; use crate::server::Result; -use crate::server::client::Notifier; +use crate::session::client::Client; use crate::system::url_to_any_system_path; use crate::{DocumentSnapshot, PositionEncoding, Session}; -use super::LSPResult; - /// Represents the diagnostics for a text document or a notebook document. pub(super) enum Diagnostics { TextDocument(Vec), @@ -46,9 +45,9 @@ impl Diagnostics { /// Clears the diagnostics for the document at `uri`. /// /// This is done by notifying the client with an empty list of diagnostics for the document. -pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> { - notifier - .notify::(PublishDiagnosticsParams { +pub(super) fn clear_diagnostics(uri: &Url, client: &Client) -> Result<()> { + client + .send_notification::(PublishDiagnosticsParams { uri: uri.clone(), diagnostics: vec![], version: None, @@ -63,7 +62,7 @@ pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> { /// This function is a no-op if the client supports pull diagnostics. /// /// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics -pub(super) fn publish_diagnostics(session: &Session, url: Url, notifier: &Notifier) -> Result<()> { +pub(super) fn publish_diagnostics(session: &Session, url: Url, client: &Client) -> Result<()> { if session.client_capabilities().pull_diagnostics { return Ok(()); } @@ -85,8 +84,8 @@ pub(super) fn publish_diagnostics(session: &Session, url: Url, notifier: &Notifi // Sends a notification to the client with the diagnostics for the document. let publish_diagnostics_notification = |uri: Url, diagnostics: Vec| { - notifier - .notify::(PublishDiagnosticsParams { + client + .send_notification::(PublishDiagnosticsParams { uri, diagnostics, version: Some(snapshot.query().version()), diff --git a/crates/ty_server/src/server/api/notifications.rs b/crates/ty_server/src/server/api/notifications.rs index 5e4f54eb942d0f..baeb85e8a0e2d9 100644 --- a/crates/ty_server/src/server/api/notifications.rs +++ b/crates/ty_server/src/server/api/notifications.rs @@ -1,3 +1,4 @@ +mod cancel; mod did_change; mod did_change_watched_files; mod did_close; @@ -5,6 +6,7 @@ mod did_close_notebook; mod did_open; mod did_open_notebook; +pub(super) use cancel::CancelNotificationHandler; pub(super) use did_change::DidChangeTextDocumentHandler; pub(super) use did_change_watched_files::DidChangeWatchedFiles; pub(super) use did_close::DidCloseTextDocumentHandler; diff --git a/crates/ty_server/src/server/api/notifications/cancel.rs b/crates/ty_server/src/server/api/notifications/cancel.rs new file mode 100644 index 00000000000000..c5bcf7429e4fd5 --- /dev/null +++ b/crates/ty_server/src/server/api/notifications/cancel.rs @@ -0,0 +1,27 @@ +use lsp_server::RequestId; +use lsp_types::CancelParams; +use lsp_types::notification::Cancel; + +use crate::server::Result; +use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; +use crate::session::Session; +use crate::session::client::Client; + +pub(crate) struct CancelNotificationHandler; + +impl NotificationHandler for CancelNotificationHandler { + type NotificationType = Cancel; +} + +impl SyncNotificationHandler for CancelNotificationHandler { + fn run(session: &mut Session, client: &Client, params: CancelParams) -> Result<()> { + let id: RequestId = match params.id { + lsp_types::NumberOrString::Number(id) => id.into(), + lsp_types::NumberOrString::String(id) => id.into(), + }; + + let _ = client.cancel(session, id); + + Ok(()) + } +} diff --git a/crates/ty_server/src/server/api/notifications/did_change.rs b/crates/ty_server/src/server/api/notifications/did_change.rs index 0641b967919c0a..43b8f88c7d49d3 100644 --- a/crates/ty_server/src/server/api/notifications/did_change.rs +++ b/crates/ty_server/src/server/api/notifications/did_change.rs @@ -2,15 +2,14 @@ use lsp_server::ErrorCode; use lsp_types::notification::DidChangeTextDocument; use lsp_types::{DidChangeTextDocumentParams, VersionedTextDocumentIdentifier}; -use ty_project::watch::ChangeEvent; - use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; +use ty_project::watch::ChangeEvent; pub(crate) struct DidChangeTextDocumentHandler; @@ -21,8 +20,7 @@ impl NotificationHandler for DidChangeTextDocumentHandler { impl SyncNotificationHandler for DidChangeTextDocumentHandler { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, params: DidChangeTextDocumentParams, ) -> Result<()> { let DidChangeTextDocumentParams { @@ -54,6 +52,6 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler { } } - publish_diagnostics(session, uri, ¬ifier) + publish_diagnostics(session, uri, client) } } diff --git a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs index f2038e9abcfd7d..2d27495cfe3e73 100644 --- a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs +++ b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs @@ -2,9 +2,8 @@ use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; -use crate::server::schedule::Task; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; use lsp_types as types; use lsp_types::{FileChangeType, notification as notif}; @@ -21,8 +20,7 @@ impl NotificationHandler for DidChangeWatchedFiles { impl SyncNotificationHandler for DidChangeWatchedFiles { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: types::DidChangeWatchedFilesParams, ) -> Result<()> { let mut events_by_db: FxHashMap<_, Vec> = FxHashMap::default(); @@ -105,12 +103,16 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { if project_changed { if client_capabilities.diagnostics_refresh { - requester - .request::((), |()| Task::nothing()) + client + .send_request::( + session, + (), + |_, ()| {}, + ) .with_failure_code(lsp_server::ErrorCode::InternalError)?; } else { for url in session.text_document_urls() { - publish_diagnostics(session, url.clone(), ¬ifier)?; + publish_diagnostics(session, url.clone(), client)?; } } @@ -118,8 +120,8 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { } if client_capabilities.inlay_refresh { - requester - .request::((), |()| Task::nothing()) + client + .send_request::(session, (), |_, ()| {}) .with_failure_code(lsp_server::ErrorCode::InternalError)?; } diff --git a/crates/ty_server/src/server/api/notifications/did_close.rs b/crates/ty_server/src/server/api/notifications/did_close.rs index 0daaa645a28ce8..649546595e166d 100644 --- a/crates/ty_server/src/server/api/notifications/did_close.rs +++ b/crates/ty_server/src/server/api/notifications/did_close.rs @@ -1,15 +1,14 @@ -use lsp_server::ErrorCode; -use lsp_types::DidCloseTextDocumentParams; -use lsp_types::notification::DidCloseTextDocument; -use ty_project::watch::ChangeEvent; - use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::clear_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; +use lsp_server::ErrorCode; +use lsp_types::DidCloseTextDocumentParams; +use lsp_types::notification::DidCloseTextDocument; +use ty_project::watch::ChangeEvent; pub(crate) struct DidCloseTextDocumentHandler; @@ -20,8 +19,7 @@ impl NotificationHandler for DidCloseTextDocumentHandler { impl SyncNotificationHandler for DidCloseTextDocumentHandler { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, params: DidCloseTextDocumentParams, ) -> Result<()> { let Ok(path) = url_to_any_system_path(¶ms.text_document.uri) else { @@ -38,7 +36,7 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler { db.apply_changes(vec![ChangeEvent::DeletedVirtual(virtual_path)], None); } - clear_diagnostics(key.url(), ¬ifier)?; + clear_diagnostics(key.url(), client)?; Ok(()) } diff --git a/crates/ty_server/src/server/api/notifications/did_close_notebook.rs b/crates/ty_server/src/server/api/notifications/did_close_notebook.rs index 07f0c31b488e91..627e5415e3923f 100644 --- a/crates/ty_server/src/server/api/notifications/did_close_notebook.rs +++ b/crates/ty_server/src/server/api/notifications/did_close_notebook.rs @@ -1,14 +1,13 @@ use lsp_types::DidCloseNotebookDocumentParams; use lsp_types::notification::DidCloseNotebookDocument; -use ty_project::watch::ChangeEvent; - use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; +use ty_project::watch::ChangeEvent; pub(crate) struct DidCloseNotebookHandler; @@ -19,8 +18,7 @@ impl NotificationHandler for DidCloseNotebookHandler { impl SyncNotificationHandler for DidCloseNotebookHandler { fn run( session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, + _client: &Client, params: DidCloseNotebookDocumentParams, ) -> Result<()> { let Ok(path) = url_to_any_system_path(¶ms.notebook_document.uri) else { diff --git a/crates/ty_server/src/server/api/notifications/did_open.rs b/crates/ty_server/src/server/api/notifications/did_open.rs index 3dea80892a6efd..9f2dea3691233f 100644 --- a/crates/ty_server/src/server/api/notifications/did_open.rs +++ b/crates/ty_server/src/server/api/notifications/did_open.rs @@ -1,16 +1,15 @@ use lsp_types::notification::DidOpenTextDocument; use lsp_types::{DidOpenTextDocumentParams, TextDocumentItem}; -use ruff_db::Db; -use ty_project::watch::ChangeEvent; - use crate::TextDocument; use crate::server::Result; use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; +use ruff_db::Db; +use ty_project::watch::ChangeEvent; pub(crate) struct DidOpenTextDocumentHandler; @@ -21,8 +20,7 @@ impl NotificationHandler for DidOpenTextDocumentHandler { impl SyncNotificationHandler for DidOpenTextDocumentHandler { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, DidOpenTextDocumentParams { text_document: TextDocumentItem { @@ -54,6 +52,6 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler { } } - publish_diagnostics(session, uri, ¬ifier) + publish_diagnostics(session, uri, client) } } diff --git a/crates/ty_server/src/server/api/notifications/did_open_notebook.rs b/crates/ty_server/src/server/api/notifications/did_open_notebook.rs index d2ee3e9558036c..9a8d24bb9ce504 100644 --- a/crates/ty_server/src/server/api/notifications/did_open_notebook.rs +++ b/crates/ty_server/src/server/api/notifications/did_open_notebook.rs @@ -9,8 +9,8 @@ use crate::document::NotebookDocument; use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; -use crate::server::client::{Notifier, Requester}; use crate::session::Session; +use crate::session::client::Client; use crate::system::{AnySystemPath, url_to_any_system_path}; pub(crate) struct DidOpenNotebookHandler; @@ -22,8 +22,7 @@ impl NotificationHandler for DidOpenNotebookHandler { impl SyncNotificationHandler for DidOpenNotebookHandler { fn run( session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, + _client: &Client, params: DidOpenNotebookDocumentParams, ) -> Result<()> { let Ok(path) = url_to_any_system_path(¶ms.notebook_document.uri) else { diff --git a/crates/ty_server/src/server/api/requests/completion.rs b/crates/ty_server/src/server/api/requests/completion.rs index e0089a43a1bd70..11d61b1450e584 100644 --- a/crates/ty_server/src/server/api/requests/completion.rs +++ b/crates/ty_server/src/server/api/requests/completion.rs @@ -9,7 +9,7 @@ use ty_project::ProjectDatabase; use crate::DocumentSnapshot; use crate::document::PositionExt; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; -use crate::server::client::Notifier; +use crate::session::client::Client; pub(crate) struct CompletionRequestHandler; @@ -18,6 +18,8 @@ impl RequestHandler for CompletionRequestHandler { } impl BackgroundDocumentRequestHandler for CompletionRequestHandler { + const RETRY_ON_CANCELLATION: bool = true; + fn document_url(params: &CompletionParams) -> Cow { Cow::Borrowed(¶ms.text_document_position.text_document.uri) } @@ -25,7 +27,7 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: CompletionParams, ) -> crate::server::Result> { let Some(file) = snapshot.file(db) else { diff --git a/crates/ty_server/src/server/api/requests/diagnostic.rs b/crates/ty_server/src/server/api/requests/diagnostic.rs index 013799bd701737..115aa878e30985 100644 --- a/crates/ty_server/src/server/api/requests/diagnostic.rs +++ b/crates/ty_server/src/server/api/requests/diagnostic.rs @@ -6,10 +6,11 @@ use lsp_types::{ FullDocumentDiagnosticReport, RelatedFullDocumentDiagnosticReport, }; +use crate::server::Result; use crate::server::api::diagnostics::{Diagnostics, compute_diagnostics}; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; -use crate::server::{Result, client::Notifier}; use crate::session::DocumentSnapshot; +use crate::session::client::Client; use ty_project::ProjectDatabase; pub(crate) struct DocumentDiagnosticRequestHandler; @@ -26,7 +27,7 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, _params: DocumentDiagnosticParams, ) -> Result { Ok(DocumentDiagnosticReportResult::Report( @@ -42,4 +43,15 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { }), )) } + + fn salsa_cancellation_error() -> lsp_server::ResponseError { + lsp_server::ResponseError { + code: lsp_server::ErrorCode::ServerCancelled as i32, + message: "server cancelled the request".to_owned(), + data: serde_json::to_value(lsp_types::DiagnosticServerCancellationData { + retrigger_request: true, + }) + .ok(), + } + } } diff --git a/crates/ty_server/src/server/api/requests/goto_type_definition.rs b/crates/ty_server/src/server/api/requests/goto_type_definition.rs index 8e7824769151b5..197e61dd0696a3 100644 --- a/crates/ty_server/src/server/api/requests/goto_type_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_type_definition.rs @@ -9,7 +9,7 @@ use ty_project::ProjectDatabase; use crate::DocumentSnapshot; use crate::document::{PositionExt, ToLink}; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; -use crate::server::client::Notifier; +use crate::session::client::Client; pub(crate) struct GotoTypeDefinitionRequestHandler; @@ -25,7 +25,7 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: GotoTypeDefinitionParams, ) -> crate::server::Result> { let Some(file) = snapshot.file(db) else { diff --git a/crates/ty_server/src/server/api/requests/hover.rs b/crates/ty_server/src/server/api/requests/hover.rs index 8b58059d1af248..f244cc81a3f6f5 100644 --- a/crates/ty_server/src/server/api/requests/hover.rs +++ b/crates/ty_server/src/server/api/requests/hover.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::DocumentSnapshot; use crate::document::{PositionExt, ToRangeExt}; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; -use crate::server::client::Notifier; +use crate::session::client::Client; use lsp_types::request::HoverRequest; use lsp_types::{HoverContents, HoverParams, MarkupContent, Url}; use ruff_db::source::{line_index, source_text}; @@ -25,7 +25,7 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: HoverParams, ) -> crate::server::Result> { let Some(file) = snapshot.file(db) else { diff --git a/crates/ty_server/src/server/api/requests/inlay_hints.rs b/crates/ty_server/src/server/api/requests/inlay_hints.rs index 2e7c3c6bd0429e..bba7cd6ba26c86 100644 --- a/crates/ty_server/src/server/api/requests/inlay_hints.rs +++ b/crates/ty_server/src/server/api/requests/inlay_hints.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::DocumentSnapshot; use crate::document::{RangeExt, TextSizeExt}; use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; -use crate::server::client::Notifier; +use crate::session::client::Client; use lsp_types::request::InlayHintRequest; use lsp_types::{InlayHintParams, Url}; use ruff_db::source::{line_index, source_text}; @@ -24,7 +24,7 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: InlayHintParams, ) -> crate::server::Result>> { let Some(file) = snapshot.file(db) else { diff --git a/crates/ty_server/src/server/api/traits.rs b/crates/ty_server/src/server/api/traits.rs index 7301259c10d047..7132251bf07af0 100644 --- a/crates/ty_server/src/server/api/traits.rs +++ b/crates/ty_server/src/server/api/traits.rs @@ -1,6 +1,6 @@ //! A stateful LSP implementation that calls into the ty API. -use crate::server::client::{Notifier, Requester}; +use crate::session::client::Client; use crate::session::{DocumentSnapshot, Session}; use lsp_types::notification::Notification as LSPNotification; @@ -21,14 +21,16 @@ pub(super) trait RequestHandler { pub(super) trait SyncRequestHandler: RequestHandler { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: <::RequestType as Request>::Params, ) -> super::Result<<::RequestType as Request>::Result>; } /// A request handler that can be run on a background thread. pub(super) trait BackgroundDocumentRequestHandler: RequestHandler { + /// Whether this request be retried if it was cancelled due to a modification to the Salsa database. + const RETRY_ON_CANCELLATION: bool = false; + fn document_url( params: &<::RequestType as Request>::Params, ) -> std::borrow::Cow; @@ -36,9 +38,17 @@ pub(super) trait BackgroundDocumentRequestHandler: RequestHandler { fn run_with_snapshot( db: &ProjectDatabase, snapshot: DocumentSnapshot, - notifier: Notifier, + client: &Client, params: <::RequestType as Request>::Params, ) -> super::Result<<::RequestType as Request>::Result>; + + fn salsa_cancellation_error() -> lsp_server::ResponseError { + lsp_server::ResponseError { + code: lsp_server::ErrorCode::ContentModified as i32, + message: "content modified".to_string(), + data: None, + } + } } /// A supertrait for any server notification handler. @@ -55,8 +65,7 @@ pub(super) trait NotificationHandler { pub(super) trait SyncNotificationHandler: NotificationHandler { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: <::NotificationType as LSPNotification>::Params, ) -> super::Result<()>; } @@ -72,7 +81,7 @@ pub(super) trait BackgroundDocumentNotificationHandler: NotificationHandler { fn run_with_snapshot( snapshot: DocumentSnapshot, - notifier: Notifier, + client: &Client, params: <::NotificationType as LSPNotification>::Params, ) -> super::Result<()>; } diff --git a/crates/ty_server/src/server/client.rs b/crates/ty_server/src/server/client.rs deleted file mode 100644 index 667c0f14283318..00000000000000 --- a/crates/ty_server/src/server/client.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::any::TypeId; - -use lsp_server::{Notification, RequestId}; -use rustc_hash::FxHashMap; -use serde_json::Value; - -use super::{ClientSender, schedule::Task}; - -type ResponseBuilder = Box Task>; - -pub(crate) struct Client { - notifier: Notifier, - responder: Responder, - pub(super) requester: Requester, -} - -#[derive(Clone)] -pub(crate) struct Notifier(ClientSender); - -#[derive(Clone)] -pub(crate) struct Responder(ClientSender); - -pub(crate) struct Requester { - sender: ClientSender, - next_request_id: i32, - response_handlers: FxHashMap, -} - -impl Client { - pub(super) fn new(sender: ClientSender) -> Self { - Self { - notifier: Notifier(sender.clone()), - responder: Responder(sender.clone()), - requester: Requester { - sender, - next_request_id: 1, - response_handlers: FxHashMap::default(), - }, - } - } - - pub(super) fn notifier(&self) -> Notifier { - self.notifier.clone() - } - - pub(super) fn responder(&self) -> Responder { - self.responder.clone() - } -} - -#[expect(dead_code)] // we'll need to use `Notifier` in the future -impl Notifier { - pub(crate) fn notify(&self, params: N::Params) -> crate::Result<()> - where - N: lsp_types::notification::Notification, - { - let method = N::METHOD.to_string(); - - let message = lsp_server::Message::Notification(Notification::new(method, params)); - - self.0.send(message) - } - - pub(crate) fn notify_method(&self, method: String) -> crate::Result<()> { - self.0 - .send(lsp_server::Message::Notification(Notification::new( - method, - Value::Null, - ))) - } -} - -impl Responder { - pub(crate) fn respond( - &self, - id: RequestId, - result: crate::server::Result, - ) -> crate::Result<()> - where - R: serde::Serialize, - { - self.0.send( - match result { - Ok(res) => lsp_server::Response::new_ok(id, res), - Err(crate::server::api::Error { code, error }) => { - lsp_server::Response::new_err(id, code as i32, format!("{error}")) - } - } - .into(), - ) - } -} - -impl Requester { - /// Sends a request of kind `R` to the client, with associated parameters. - /// The task provided by `response_handler` will be dispatched as soon as the response - /// comes back from the client. - pub(crate) fn request( - &mut self, - params: R::Params, - response_handler: impl Fn(R::Result) -> Task + 'static, - ) -> crate::Result<()> - where - R: lsp_types::request::Request, - { - let serialized_params = serde_json::to_value(params)?; - - self.response_handlers.insert( - self.next_request_id.into(), - Box::new(move |response: lsp_server::Response| { - match (response.error, response.result) { - (Some(err), _) => { - tracing::error!( - "Got an error from the client (code {}): {}", - err.code, - err.message - ); - Task::nothing() - } - (None, Some(response)) => match serde_json::from_value(response) { - Ok(response) => response_handler(response), - Err(error) => { - tracing::error!("Failed to deserialize response from server: {error}"); - Task::nothing() - } - }, - (None, None) => { - if TypeId::of::() == TypeId::of::<()>() { - // We can't call `response_handler(())` directly here, but - // since we _know_ the type expected is `()`, we can use - // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, - // so this branch works in the general case but we'll only - // hit it if the concrete type is `()`, so the `unwrap()` is safe here. - response_handler(serde_json::from_value(Value::Null).unwrap()); - } else { - tracing::error!( - "Server response was invalid: did not contain a result or error" - ); - } - Task::nothing() - } - } - }), - ); - - self.sender - .send(lsp_server::Message::Request(lsp_server::Request { - id: self.next_request_id.into(), - method: R::METHOD.into(), - params: serialized_params, - }))?; - - self.next_request_id += 1; - - Ok(()) - } - - pub(crate) fn pop_response_task(&mut self, response: lsp_server::Response) -> Task { - if let Some(handler) = self.response_handlers.remove(&response.id) { - handler(response) - } else { - tracing::error!( - "Received a response with ID {}, which was not expected", - response.id - ); - Task::nothing() - } - } -} diff --git a/crates/ty_server/src/server/connection.rs b/crates/ty_server/src/server/connection.rs index 029a34c931f59f..ded1fcee7b257b 100644 --- a/crates/ty_server/src/server/connection.rs +++ b/crates/ty_server/src/server/connection.rs @@ -1,31 +1,25 @@ use lsp_server as lsp; use lsp_types::{notification::Notification, request::Request}; -use std::sync::{Arc, Weak}; -type ConnectionSender = crossbeam::channel::Sender; +pub(crate) type ConnectionSender = crossbeam::channel::Sender; type ConnectionReceiver = crossbeam::channel::Receiver; /// A builder for `Connection` that handles LSP initialization. pub(crate) struct ConnectionInitializer { connection: lsp::Connection, - threads: lsp::IoThreads, } /// Handles inbound and outbound messages with the client. pub(crate) struct Connection { - sender: Arc, + sender: ConnectionSender, receiver: ConnectionReceiver, - threads: lsp::IoThreads, } impl ConnectionInitializer { /// Create a new LSP server connection over stdin/stdout. - pub(super) fn stdio() -> Self { + pub(crate) fn stdio() -> (Self, lsp::IoThreads) { let (connection, threads) = lsp::Connection::stdio(); - Self { - connection, - threads, - } + (Self { connection }, threads) } /// Starts the initialization process with the client by listening for an initialization request. @@ -59,27 +53,25 @@ impl ConnectionInitializer { )?; let Self { connection: lsp::Connection { sender, receiver }, - threads, } = self; - Ok(Connection { - sender: Arc::new(sender), - receiver, - threads, - }) + Ok(Connection { sender, receiver }) } } impl Connection { /// Make a new `ClientSender` for sending messages to the client. - pub(super) fn make_sender(&self) -> ClientSender { - ClientSender { - weak_sender: Arc::downgrade(&self.sender), - } + pub(super) fn sender(&self) -> ConnectionSender { + self.sender.clone() + } + + pub(super) fn send(&self, msg: lsp::Message) -> crate::Result<()> { + self.sender.send(msg)?; + Ok(()) } /// An iterator over incoming messages from the client. - pub(super) fn incoming(&self) -> crossbeam::channel::Iter { - self.receiver.iter() + pub(super) fn incoming(&self) -> &crossbeam::channel::Receiver { + &self.receiver } /// Check and respond to any incoming shutdown requests; returns`true` if the server should be shutdown. @@ -131,37 +123,4 @@ impl Connection { _ => Ok(false), } } - - /// Join the I/O threads that underpin this connection. - /// This is guaranteed to be nearly immediate since - /// we close the only active channels to these threads prior - /// to joining them. - pub(super) fn close(self) -> crate::Result<()> { - std::mem::drop( - Arc::into_inner(self.sender) - .expect("the client sender shouldn't have more than one strong reference"), - ); - std::mem::drop(self.receiver); - self.threads.join()?; - Ok(()) - } -} - -/// A weak reference to an underlying sender channel, used for communication with the client. -/// If the `Connection` that created this `ClientSender` is dropped, any `send` calls will throw -/// an error. -#[derive(Clone, Debug)] -pub(crate) struct ClientSender { - weak_sender: Weak, -} - -// note: additional wrapper functions for senders may be implemented as needed. -impl ClientSender { - pub(crate) fn send(&self, msg: lsp::Message) -> crate::Result<()> { - let Some(sender) = self.weak_sender.upgrade() else { - anyhow::bail!("The connection with the client has been closed"); - }; - - Ok(sender.send(msg)?) - } } diff --git a/crates/ty_server/src/server/main_loop.rs b/crates/ty_server/src/server/main_loop.rs new file mode 100644 index 00000000000000..9f0aefaf5b41d0 --- /dev/null +++ b/crates/ty_server/src/server/main_loop.rs @@ -0,0 +1,212 @@ +use crate::Session; +use crate::server::schedule::Scheduler; +use crate::server::{Server, api}; +use crate::session::client::Client; +use crossbeam::select; +use lsp_server::Message; +use lsp_types::{DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher}; + +pub(crate) type MainLoopSender = crossbeam::channel::Sender; +pub(crate) type MainLoopReceiver = crossbeam::channel::Receiver; + +impl Server { + pub(super) fn main_loop(&mut self) -> crate::Result<()> { + self.initialize(&Client::new( + self.main_loop_sender.clone(), + self.connection.sender(), + )); + + let mut scheduler = Scheduler::new(self.worker_threads); + + while let Ok(next_event) = self.next_event() { + let Some(next_event) = next_event else { + anyhow::bail!("client exited without proper shutdown sequence"); + }; + + match next_event { + Event::Message(msg) => { + if self.connection.handle_shutdown(&msg)? { + break; + } + let task = match msg { + Message::Request(req) => { + self.session + .request_queue_mut() + .incoming_mut() + .register(req.id.clone(), req.method.clone()); + + api::request(req) + } + Message::Notification(notification) => api::notification(notification), + + // Handle the response from the client to a server request + Message::Response(response) => { + if let Some(handler) = self + .session + .request_queue_mut() + .outgoing_mut() + .complete(&response.id) + { + handler(&self.session, response); + } else { + tracing::error!( + "Received a response with ID {}, which was not expected", + response.id + ); + } + + continue; + } + }; + + let client = + Client::new(self.main_loop_sender.clone(), self.connection.sender()); + scheduler.dispatch(task, &mut self.session, client); + } + Event::Action(action) => match action { + Action::SendResponse(response) => { + // Filter out responses for already canceled requests. + if let Some((start_time, method)) = self + .session + .request_queue_mut() + .incoming_mut() + .complete(&response.id) + { + let duration = start_time.elapsed(); + tracing::trace!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration)); + + self.connection.send(Message::Response(response))?; + } else { + tracing::trace!( + "Ignoring response for canceled request id={}", + response.id + ); + } + } + + Action::RetryRequest(request) => { + // Never retry canceled requests. + if self + .session + .request_queue() + .incoming() + .is_pending(&request.id) + { + api::request(request); + } else { + tracing::debug!( + "Request {}/{} was cancelled, not retrying", + request.method, + request.id + ); + } + } + }, + } + } + + Ok(()) + } + + /// Waits for the next message from the client or action. + /// + /// Returns `Ok(None)` if the client connection is closed. + fn next_event(&self) -> Result, crossbeam::channel::RecvError> { + let next = select!( + recv(self.connection.incoming()) -> msg => msg.map(Event::Message), + recv(self.main_loop_receiver) -> event => return Ok(event.ok()), + ); + + next.map(Some) + } + + fn initialize(&mut self, client: &Client) { + let fs_watcher = self + .client_capabilities + .workspace + .as_ref() + .and_then(|workspace| workspace.did_change_watched_files?.dynamic_registration) + .unwrap_or_default(); + + if fs_watcher { + let registration = lsp_types::Registration { + id: "workspace/didChangeWatchedFiles".to_owned(), + method: "workspace/didChangeWatchedFiles".to_owned(), + register_options: Some( + serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { + watchers: vec![ + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String("**/ty.toml".into()), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String( + "**/.gitignore".into(), + ), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String("**/.ignore".into()), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String( + "**/pyproject.toml".into(), + ), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String("**/*.py".into()), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String("**/*.pyi".into()), + kind: None, + }, + FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String("**/*.ipynb".into()), + kind: None, + }, + ], + }) + .unwrap(), + ), + }; + let response_handler = move |_session: &Session, ()| { + tracing::info!("File watcher successfully registered"); + }; + + if let Err(err) = client.send_request::( + &self.session, + lsp_types::RegistrationParams { + registrations: vec![registration], + }, + response_handler, + ) { + tracing::error!( + "An error occurred when trying to register the configuration file watcher: {err}" + ); + } + } else { + tracing::warn!("The client does not support file system watching."); + } + } +} + +/// An action that should be performed on the main loop. +#[derive(Debug)] +pub(crate) enum Action { + /// Send a response to the client + SendResponse(lsp_server::Response), + + /// Retry a request that previously failed due to a salsa cancellation. + RetryRequest(lsp_server::Request), +} + +#[derive(Debug)] +pub(crate) enum Event { + /// An incoming message from the LSP client. + Message(lsp_server::Message), + + Action(Action), +} diff --git a/crates/ty_server/src/server/schedule.rs b/crates/ty_server/src/server/schedule.rs index b77ab22abe125c..5473900f2c4c7b 100644 --- a/crates/ty_server/src/server/schedule.rs +++ b/crates/ty_server/src/server/schedule.rs @@ -5,20 +5,18 @@ use crate::session::Session; mod task; mod thread; -pub(super) use task::{BackgroundSchedule, Task}; - use self::{ task::{BackgroundTaskBuilder, SyncTask}, thread::ThreadPriority, }; - -use super::{ClientSender, client::Client}; +use crate::session::client::Client; +pub(super) use task::{BackgroundSchedule, Task}; /// The event loop thread is actually a secondary thread that we spawn from the /// _actual_ main thread. This secondary thread has a larger stack size /// than some OS defaults (Windows, for example) and is also designated as /// high-priority. -pub(crate) fn event_loop_thread( +pub(crate) fn spawn_main_loop( func: impl FnOnce() -> crate::Result<()> + Send + 'static, ) -> crate::Result>> { // Override OS defaults to avoid stack overflows on platforms with low stack size defaults. @@ -32,69 +30,33 @@ pub(crate) fn event_loop_thread( ) } -pub(crate) struct Scheduler<'s> { - session: &'s mut Session, - client: Client, +pub(crate) struct Scheduler { fmt_pool: thread::Pool, background_pool: thread::Pool, } -impl<'s> Scheduler<'s> { - pub(super) fn new( - session: &'s mut Session, - worker_threads: NonZeroUsize, - sender: ClientSender, - ) -> Self { +impl Scheduler { + pub(super) fn new(worker_threads: NonZeroUsize) -> Self { const FMT_THREADS: usize = 1; Self { - session, fmt_pool: thread::Pool::new(NonZeroUsize::try_from(FMT_THREADS).unwrap()), background_pool: thread::Pool::new(worker_threads), - client: Client::new(sender), } } - /// Immediately sends a request of kind `R` to the client, with associated parameters. - /// The task provided by `response_handler` will be dispatched as soon as the response - /// comes back from the client. - pub(super) fn request( - &mut self, - params: R::Params, - response_handler: impl Fn(R::Result) -> Task + 'static, - ) -> crate::Result<()> - where - R: lsp_types::request::Request, - { - self.client.requester.request::(params, response_handler) - } - - /// Creates a task to handle a response from the client. - pub(super) fn response(&mut self, response: lsp_server::Response) -> Task { - self.client.requester.pop_response_task(response) - } - /// Dispatches a `task` by either running it as a blocking function or /// executing it on a background thread pool. - pub(super) fn dispatch(&mut self, task: task::Task) { + pub(super) fn dispatch(&mut self, task: task::Task, session: &mut Session, client: Client) { match task { Task::Sync(SyncTask { func }) => { - let notifier = self.client.notifier(); - let responder = self.client.responder(); - func( - self.session, - notifier, - &mut self.client.requester, - responder, - ); + func(session, &client); } Task::Background(BackgroundTaskBuilder { schedule, builder: func, }) => { - let static_func = func(self.session); - let notifier = self.client.notifier(); - let responder = self.client.responder(); - let task = move || static_func(notifier, responder); + let static_func = func(session); + let task = move || static_func(&client); match schedule { BackgroundSchedule::Worker => { self.background_pool.spawn(ThreadPriority::Worker, task); diff --git a/crates/ty_server/src/server/schedule/task.rs b/crates/ty_server/src/server/schedule/task.rs index 22c4f2ca9cff78..e781ffcec700a0 100644 --- a/crates/ty_server/src/server/schedule/task.rs +++ b/crates/ty_server/src/server/schedule/task.rs @@ -1,14 +1,12 @@ use lsp_server::RequestId; use serde::Serialize; -use crate::{ - server::client::{Notifier, Requester, Responder}, - session::Session, -}; +use crate::session::Session; +use crate::session::client::Client; -type LocalFn = Box; +type LocalFn = Box; -type BackgroundFn = Box; +type BackgroundFn = Box; type BackgroundFnBuilder = Box BackgroundFn>; @@ -62,7 +60,7 @@ impl Task { /// Creates a new background task. pub(crate) fn background(schedule: BackgroundSchedule, func: F) -> Self where - F: FnOnce(&Session) -> Box + 'static, + F: FnOnce(&Session) -> Box + 'static, { Self::Background(BackgroundTaskBuilder { schedule, @@ -72,7 +70,7 @@ impl Task { /// Creates a new local task. pub(crate) fn local(func: F) -> Self where - F: FnOnce(&mut Session, Notifier, &mut Requester, Responder) + 'static, + F: FnOnce(&mut Session, &Client) + 'static, { Self::Sync(SyncTask { func: Box::new(func), @@ -84,8 +82,8 @@ impl Task { where R: Serialize + Send + 'static, { - Self::local(move |_, _, _, responder| { - if let Err(err) = responder.respond(id, result) { + Self::local(move |_, client| { + if let Err(err) = client.respond(&id, result) { tracing::error!("Unable to send immediate response: {err}"); } }) @@ -93,6 +91,6 @@ impl Task { /// Creates a local task that does nothing. pub(crate) fn nothing() -> Self { - Self::local(move |_, _, _, _| {}) + Self::local(move |_, _| {}) } } diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index d66acc33eac7ab..f995b2f19f69f0 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -7,29 +7,27 @@ use std::sync::Arc; use anyhow::anyhow; use lsp_types::{ClientCapabilities, TextDocumentContentChangeEvent, Url}; - use ruff_db::Db; use ruff_db::files::{File, system_path_to_file}; use ruff_db::system::SystemPath; use ty_project::{ProjectDatabase, ProjectMetadata}; -use crate::document::{DocumentKey, DocumentVersion, NotebookDocument}; -use crate::system::{AnySystemPath, LSPSystem, url_to_any_system_path}; -use crate::{PositionEncoding, TextDocument}; - pub(crate) use self::capabilities::ResolvedClientCapabilities; pub use self::index::DocumentQuery; pub(crate) use self::settings::AllSettings; pub use self::settings::ClientSettings; pub(crate) use self::settings::Experimental; +use crate::document::{DocumentKey, DocumentVersion, NotebookDocument}; +use crate::session::request_queue::RequestQueue; +use crate::system::{AnySystemPath, LSPSystem, url_to_any_system_path}; +use crate::{PositionEncoding, TextDocument}; mod capabilities; +pub(crate) mod client; pub(crate) mod index; +mod request_queue; mod settings; -// TODO(dhruvmanila): In general, the server shouldn't use any salsa queries directly and instead -// should use methods on `ProjectDatabase`. - /// The global state for the LSP pub struct Session { /// Used to retrieve information about open documents and settings. @@ -49,10 +47,13 @@ pub struct Session { /// Tracks what LSP features the client supports and doesn't support. resolved_client_capabilities: Arc, + + /// Tracks the pending requests between client and server. + request_queue: RequestQueue, } impl Session { - pub fn new( + pub(crate) fn new( client_capabilities: &ClientCapabilities, position_encoding: PositionEncoding, global_settings: ClientSettings, @@ -84,9 +85,18 @@ impl Session { resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new( client_capabilities, )), + request_queue: RequestQueue::new(), }) } + pub(crate) fn request_queue(&self) -> &RequestQueue { + &self.request_queue + } + + pub(crate) fn request_queue_mut(&mut self) -> &mut RequestQueue { + &mut self.request_queue + } + // TODO(dhruvmanila): Ideally, we should have a single method for `workspace_db_for_path_mut` // and `default_workspace_db_mut` but the borrow checker doesn't allow that. // https://github.com/astral-sh/ruff/pull/13041#discussion_r1726725437 diff --git a/crates/ty_server/src/session/client.rs b/crates/ty_server/src/session/client.rs new file mode 100644 index 00000000000000..4d54c0fd63769f --- /dev/null +++ b/crates/ty_server/src/session/client.rs @@ -0,0 +1,260 @@ +use crate::Session; +use crate::server::{Action, ConnectionSender}; +use crate::server::{Event, MainLoopSender}; +use anyhow::{Context, anyhow}; +use lsp_server::{ErrorCode, Message, Notification, RequestId, ResponseError}; +use serde_json::Value; +use std::any::TypeId; +use std::fmt::Display; + +pub(crate) type ClientResponseHandler = Box; + +#[derive(Debug)] +pub(crate) struct Client { + /// Channel to send messages back to the main loop. + main_loop_sender: MainLoopSender, + /// Channel to send messages directly to the LSP client without going through the main loop. + /// + /// This is generally preferred because it reduces pressure on the main loop but it may not always be + /// possible if access to data on [`Session`] is required, which background tasks don't have. + client_sender: ConnectionSender, +} + +impl Client { + pub(crate) fn new(main_loop_sender: MainLoopSender, client_sender: ConnectionSender) -> Self { + Self { + main_loop_sender, + client_sender, + } + } + + /// Sends a request of kind `R` to the client, with associated parameters. + /// + /// The request is sent immediately. + /// The `response_handler` will be dispatched as soon as the client response + /// is processed on the main-loop. The handler always runs on the main-loop thread. + /// + /// # Note + /// This method takes a `session` so that we can register the pending-request + /// and send the response directly to the client. If this ever becomes too limiting (because we + /// need to send a request from somewhere where we don't have access to session), consider introducing + /// a new `send_deferred_request` method that doesn't take a session and instead sends + /// an `Action` to the main loop to send the request (the main loop has always access to session). + pub(crate) fn send_request( + &self, + session: &Session, + params: R::Params, + response_handler: impl FnOnce(&Session, R::Result) + Send + 'static, + ) -> crate::Result<()> + where + R: lsp_types::request::Request, + { + let response_handler = Box::new( + move |session: &Session, response: lsp_server::Response| { + let _span = + tracing::debug_span!("client_response", id=%response.id, method = R::METHOD) + .entered(); + + match (response.error, response.result) { + (Some(err), _) => { + tracing::error!( + "Got an error from the client (code {code}, method {method}): {message}", + code = err.code, + message = err.message, + method = R::METHOD + ); + } + (None, Some(response)) => match serde_json::from_value(response) { + Ok(response) => response_handler(session, response), + Err(error) => { + tracing::error!( + "Failed to deserialize client response (method={method}): {error}", + method = R::METHOD + ); + } + }, + (None, None) => { + if TypeId::of::() == TypeId::of::<()>() { + // We can't call `response_handler(())` directly here, but + // since we _know_ the type expected is `()`, we can use + // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, + // so this branch works in the general case but we'll only + // hit it if the concrete type is `()`, so the `unwrap()` is safe here. + response_handler(session, serde_json::from_value(Value::Null).unwrap()); + } else { + tracing::error!( + "Invalid client response: did not contain a result or error (method={method})", + method = R::METHOD + ); + } + } + } + }, + ); + + let id = session + .request_queue() + .outgoing() + .register(response_handler); + + self.client_sender + .send(Message::Request(lsp_server::Request { + id, + method: R::METHOD.to_string(), + params: serde_json::to_value(params).context("Failed to serialize params")?, + })) + .with_context(|| { + format!("Failed to send request method={method}", method = R::METHOD) + })?; + + Ok(()) + } + + /// Sends a notification to the client. + pub(crate) fn send_notification(&self, params: N::Params) -> crate::Result<()> + where + N: lsp_types::notification::Notification, + { + let method = N::METHOD.to_string(); + + self.client_sender + .send(lsp_server::Message::Notification(Notification::new( + method, params, + ))) + .map_err(|error| { + anyhow!( + "Failed to send notification (method={method}): {error}", + method = N::METHOD + ) + }) + } + + /// Sends a notification without any parameters to the client. + /// + /// This is useful for notifications that don't require any data. + #[expect(dead_code)] + pub(crate) fn send_notification_no_params(&self, method: &str) -> crate::Result<()> { + self.client_sender + .send(lsp_server::Message::Notification(Notification::new( + method.to_string(), + Value::Null, + ))) + .map_err(|error| anyhow!("Failed to send notification (method={method}): {error}",)) + } + + /// Sends a response to the client for a given request ID. + /// + /// The response isn't sent immediately. Instead, it's queued up in the main loop + /// and checked for cancellation (each request must have exactly one response). + pub(crate) fn respond( + &self, + id: &RequestId, + result: crate::server::Result, + ) -> crate::Result<()> + where + R: serde::Serialize, + { + let response = match result { + Ok(res) => lsp_server::Response::new_ok(id.clone(), res), + Err(crate::server::Error { code, error }) => { + lsp_server::Response::new_err(id.clone(), code as i32, error.to_string()) + } + }; + + self.main_loop_sender + .send(Event::Action(Action::SendResponse(response))) + .map_err(|error| anyhow!("Failed to send response for request {id}: {error}")) + } + + /// Sends an error response to the client for a given request ID. + /// + /// The response isn't sent immediately. Instead, it's queued up in the main loop. + pub(crate) fn respond_err( + &self, + id: RequestId, + error: lsp_server::ResponseError, + ) -> crate::Result<()> { + let response = lsp_server::Response { + id, + result: None, + error: Some(error), + }; + + self.main_loop_sender + .send(Event::Action(Action::SendResponse(response))) + .map_err(|error| anyhow!("Failed to send response: {error}")) + } + + /// Shows a message to the user. + /// + /// This opens a pop up in VS Code showing `message`. + pub(crate) fn show_message( + &self, + message: impl Display, + message_type: lsp_types::MessageType, + ) -> crate::Result<()> { + self.send_notification::( + lsp_types::ShowMessageParams { + typ: message_type, + message: message.to_string(), + }, + ) + } + + /// Sends a request to display a warning to the client with a formatted message. The warning is + /// sent in a `window/showMessage` notification. + /// + /// Logs an error if the message could not be sent. + pub(crate) fn show_warning_message(&self, message: impl Display) { + let result = self.show_message(message, lsp_types::MessageType::WARNING); + + if let Err(err) = result { + tracing::error!("Failed to send warning message to the client: {err}"); + } + } + + /// Sends a request to display an error to the client with a formatted message. The error is + /// sent in a `window/showMessage` notification. + /// + /// Logs an error if the message could not be sent. + pub(crate) fn show_error_message(&self, message: impl Display) { + let result = self.show_message(message, lsp_types::MessageType::ERROR); + + if let Err(err) = result { + tracing::error!("Failed to send error message to the client: {err}"); + } + } + + /// Re-queues this request after a salsa cancellation for a retry. + /// + /// The main loop will skip the retry if the client cancelled the request in the meantime. + pub(crate) fn retry(&self, request: lsp_server::Request) -> crate::Result<()> { + self.main_loop_sender + .send(Event::Action(Action::RetryRequest(request))) + .map_err(|error| anyhow!("Failed to send retry request: {error}")) + } + + pub(crate) fn cancel(&self, session: &mut Session, id: RequestId) -> crate::Result<()> { + let method_name = session.request_queue_mut().incoming_mut().cancel(&id); + + if let Some(method_name) = method_name { + tracing::debug!("Cancelled request id={id} method={method_name}"); + let error = ResponseError { + code: ErrorCode::RequestCanceled as i32, + message: "request was cancelled by client".to_owned(), + data: None, + }; + + // Use `client_sender` here instead of `respond_err` because + // `respond_err` filters out responses for canceled requests (which we just did!). + self.client_sender + .send(Message::Response(lsp_server::Response { + id, + result: None, + error: Some(error), + }))?; + } + + Ok(()) + } +} diff --git a/crates/ty_server/src/session/request_queue.rs b/crates/ty_server/src/session/request_queue.rs new file mode 100644 index 00000000000000..60c601ae92b15a --- /dev/null +++ b/crates/ty_server/src/session/request_queue.rs @@ -0,0 +1,197 @@ +use crate::session::client::ClientResponseHandler; +use lsp_server::RequestId; +use rustc_hash::FxHashMap; +use std::cell::{Cell, OnceCell, RefCell}; +use std::fmt::Formatter; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::time::Instant; + +/// Tracks the pending requests between client and server. +pub(crate) struct RequestQueue { + incoming: Incoming, + outgoing: Outgoing, +} + +impl RequestQueue { + pub(super) fn new() -> Self { + Self { + incoming: Incoming::default(), + outgoing: Outgoing::default(), + } + } + + pub(crate) fn outgoing_mut(&mut self) -> &mut Outgoing { + &mut self.outgoing + } + + /// Returns the server to client request queue. + pub(crate) fn outgoing(&self) -> &Outgoing { + &self.outgoing + } + + /// Returns the client to server request queue. + pub(crate) fn incoming(&self) -> &Incoming { + &self.incoming + } + + pub(crate) fn incoming_mut(&mut self) -> &mut Incoming { + &mut self.incoming + } +} + +/// Requests from client -> server. +/// +/// Tracks which requests are pending. Requests that aren't registered are considered completed. +/// +/// A request is pending if: +/// +/// * it has been registered +/// * it hasn't been cancelled +/// * it hasn't been completed +/// +/// Tracking whether a request is pending is required to ensure that the server sends exactly +/// one response for every request as required by the LSP specification. +#[derive(Default, Debug)] +pub(crate) struct Incoming { + pending: FxHashMap, +} + +impl Incoming { + /// Registers a new pending request. + pub(crate) fn register(&mut self, request_id: RequestId, method: String) { + self.pending.insert(request_id, PendingRequest::new(method)); + } + + /// Cancels the pending request with the given id. + /// + /// Returns the method name if the request was still pending, `None` if it was already completed. + pub(super) fn cancel(&mut self, request_id: &RequestId) -> Option { + self.pending.remove(request_id).map(|mut pending| { + if let Some(cancellation_token) = pending.cancellation_token.take() { + cancellation_token.cancel(); + } + pending.method + }) + } + + /// Returns `true` if the request with the given id is still pending. + pub(crate) fn is_pending(&self, request_id: &RequestId) -> bool { + self.pending.contains_key(request_id) + } + + /// Returns the cancellation token for the given request id if the request is still pending. + pub(crate) fn cancellation_token( + &self, + request_id: &RequestId, + ) -> Option { + let pending = self.pending.get(request_id)?; + + Some(RequestCancellationToken::clone( + pending + .cancellation_token + .get_or_init(RequestCancellationToken::default), + )) + } + + /// Marks the request as completed. + /// + /// Returns the time when the request was registered and the request method name, or `None` if the request was not pending. + pub(crate) fn complete(&mut self, request_id: &RequestId) -> Option<(Instant, String)> { + self.pending + .remove(request_id) + .map(|pending| (pending.start_time, pending.method)) + } +} + +/// A request from the client to the server that hasn't been responded yet. +#[derive(Debug)] +struct PendingRequest { + /// The time when the request was registered. + /// + /// This does not include the time the request was queued in the main loop before it was registered. + start_time: Instant, + + /// The method name of the request. + method: String, + + /// A cancellation token to cancel this request. + /// + /// This is only initialized for background requests. Local tasks don't support cancellation (unless retried) + /// as they're processed immediately after receiving the request; Making it impossible for a + /// cancellation message to be processed before the task is completed. + cancellation_token: OnceCell, +} + +impl PendingRequest { + fn new(method: String) -> Self { + Self { + start_time: Instant::now(), + method, + cancellation_token: OnceCell::new(), + } + } +} + +/// Token to cancel a specific request. +/// +/// Can be shared between threads to check for cancellation *after* a request has been scheduled. +#[derive(Debug, Default)] +pub(crate) struct RequestCancellationToken(Arc); + +impl RequestCancellationToken { + /// Returns true if the request was cancelled. + pub(crate) fn is_cancelled(&self) -> bool { + self.0.load(std::sync::atomic::Ordering::Relaxed) + } + + /// Signals that the request should not be processed because it was cancelled. + fn cancel(&self) { + self.0.store(true, std::sync::atomic::Ordering::Relaxed); + } + + fn clone(this: &Self) -> Self { + RequestCancellationToken(this.0.clone()) + } +} + +/// Requests from server -> client. +#[derive(Default)] +pub(crate) struct Outgoing { + /// The id of the next request sent from the server to the client. + next_request_id: Cell, + + /// A map of request ids to the handlers that process the client-response. + response_handlers: RefCell>, +} + +impl Outgoing { + /// Registers a handler, returns the id for the request. + #[must_use] + pub(crate) fn register(&self, handler: ClientResponseHandler) -> RequestId { + let id = self.next_request_id.get(); + self.next_request_id.set(id + 1); + + self.response_handlers + .borrow_mut() + .insert(id.into(), handler); + id.into() + } + + /// Marks the request with the given id as complete and returns the handler to process the response. + /// + /// Returns `None` if the request was not found. + #[must_use] + pub(crate) fn complete(&mut self, request_id: &RequestId) -> Option { + self.response_handlers.get_mut().remove(request_id) + } +} + +impl std::fmt::Debug for Outgoing { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Outgoing") + .field("next_request_id", &self.next_request_id) + .field("response_handlers", &"") + .finish() + } +} diff --git a/crates/ty_server/src/session/settings.rs b/crates/ty_server/src/session/settings.rs index 64f6fe04a900f4..bbf2363f4afd66 100644 --- a/crates/ty_server/src/session/settings.rs +++ b/crates/ty_server/src/session/settings.rs @@ -99,7 +99,6 @@ impl AllSettings { serde_json::from_value(options) .map_err(|err| { tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); - show_err_msg!("ty received invalid client settings - falling back to default client settings."); }) .unwrap_or_default(), ) From b60ba75d096bcae7137db5c8ac7e943f24f91b3c Mon Sep 17 00:00:00 2001 From: Viktor Merkurev <6966509+fennr@users.noreply.github.com> Date: Wed, 28 May 2025 15:39:05 +0500 Subject: [PATCH 261/487] [flake8_use_pathlib]: Replace os.symlink with Path.symlink_to (PTH211) (#18337) Co-authored-by: Micha Reiser --- .../fixtures/flake8_use_pathlib/PTH211.py | 15 ++++++ .../src/checkers/ast/analyze/expression.rs | 1 + crates/ruff_linter/src/codes.rs | 1 + .../src/rules/flake8_use_pathlib/mod.rs | 1 + .../rules/replaceable_by_pathlib.rs | 49 +++++++++++++------ ..._use_pathlib__tests__PTH211_PTH211.py.snap | 36 ++++++++++++++ .../rules/flake8_use_pathlib/violations.rs | 42 ++++++++++++++++ ruff.schema.json | 1 + 8 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH211.py create mode 100644 crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH211_PTH211.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH211.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH211.py new file mode 100644 index 00000000000000..5acf2febe27b18 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH211.py @@ -0,0 +1,15 @@ +import os +from pathlib import Path + + +os.symlink("usr/bin/python", "tmp/python") +os.symlink(b"usr/bin/python", b"tmp/python") +Path("tmp/python").symlink_to("usr/bin/python") # Ok + +os.symlink("usr/bin/python", "tmp/python", target_is_directory=True) +os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True) +Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok + +fd = os.open(".", os.O_RDONLY) +os.symlink("source.txt", "link.txt", dir_fd=fd) # Ok: dir_fd is not supported by pathlib +os.close(fd) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 6cfa73f8765c92..de027ff99e3fd7 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1041,6 +1041,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { Rule::OsPathGetctime, Rule::Glob, Rule::OsListdir, + Rule::OsSymlink, ]) { flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c96881fbaf0dc6..2ab35f2f16c141 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -934,6 +934,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob), (Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir), (Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix), + (Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsSymlink), // flake8-logging-format (Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat), diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs index fcb05b3f0923f4..7d02e85e9ebda8 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs @@ -66,6 +66,7 @@ mod tests { #[test_case(Rule::OsListdir, Path::new("PTH208.py"))] #[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210.py"))] #[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210_1.py"))] + #[test_case(Rule::OsSymlink, Path::new("PTH211.py"))] fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 22c62c06904f2c..243729c70fd53e 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -13,7 +13,7 @@ use crate::rules::flake8_use_pathlib::violations::{ BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath, OsPathBasename, OsPathDirname, OsPathExists, OsPathExpanduser, OsPathIsabs, OsPathIsdir, OsPathIsfile, OsPathIslink, OsPathJoin, OsPathSamefile, OsPathSplitext, OsReadlink, OsRemove, - OsRename, OsReplace, OsRmdir, OsStat, OsUnlink, PyPath, + OsRename, OsReplace, OsRmdir, OsStat, OsSymlink, OsUnlink, PyPath, }; use ruff_python_ast::PythonVersion; @@ -38,7 +38,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { .arguments .find_argument_value("path", 0) .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) - || is_argument_non_default(&call.arguments, "dir_fd", 2) + || is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } @@ -54,7 +54,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 2 // os.mkdir(path, mode=0o777, *, dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 2) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } Diagnostic::new(OsMkdir, range) @@ -68,8 +68,8 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 2 3 // os.rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "src_dir_fd", 2) - || is_argument_non_default(&call.arguments, "dst_dir_fd", 3) + if is_keyword_only_argument_non_default(&call.arguments, "src_dir_fd") + || is_keyword_only_argument_non_default(&call.arguments, "dst_dir_fd") { return; } @@ -84,8 +84,8 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 2 3 // os.replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "src_dir_fd", 2) - || is_argument_non_default(&call.arguments, "dst_dir_fd", 3) + if is_keyword_only_argument_non_default(&call.arguments, "src_dir_fd") + || is_keyword_only_argument_non_default(&call.arguments, "dst_dir_fd") { return; } @@ -99,7 +99,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 // os.rmdir(path, *, dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 1) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } Diagnostic::new(OsRmdir, range) @@ -112,7 +112,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 // os.remove(path, *, dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 1) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } Diagnostic::new(OsRemove, range) @@ -125,7 +125,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 // os.unlink(path, *, dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 1) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } Diagnostic::new(OsUnlink, range) @@ -155,7 +155,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { .arguments .find_argument_value("path", 0) .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) - || is_argument_non_default(&call.arguments, "dir_fd", 1) + || is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } @@ -202,6 +202,20 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { ["os", "path", "getmtime"] => Diagnostic::new(OsPathGetmtime, range), // PTH205 ["os", "path", "getctime"] => Diagnostic::new(OsPathGetctime, range), + // PTH211 + ["os", "symlink"] => { + // `dir_fd` is not supported by pathlib, so check if there are non-default values. + // Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.symlink) + // ```text + // 0 1 2 3 + // os.symlink(src, dst, target_is_directory=False, *, dir_fd=None) + // ``` + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { + return; + } + Diagnostic::new(OsSymlink, range) + } + // PTH123 ["" | "builtins", "open"] => { // `closefd` and `opener` are not supported by pathlib, so check if they are @@ -248,7 +262,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 2 3 4 // glob.glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 2) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } @@ -267,7 +281,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 2 3 4 // glob.iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 2) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } @@ -287,7 +301,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // 0 1 // os.readlink(path, *, dir_fd=None) // ``` - if is_argument_non_default(&call.arguments, "dir_fd", 1) { + if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } Diagnostic::new(OsReadlink, range) @@ -303,6 +317,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { } Diagnostic::new(OsListdir, range) } + _ => return, }; @@ -348,3 +363,9 @@ fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usi .find_argument_value(name, position) .is_some_and(|expr| !expr.is_none_literal_expr()) } + +fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool { + arguments + .find_keyword(name) + .is_some_and(|keyword| !keyword.value.is_none_literal_expr()) +} diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH211_PTH211.py.snap b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH211_PTH211.py.snap new file mode 100644 index 00000000000000..76ac48db2cd476 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__PTH211_PTH211.py.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs +--- +PTH211.py:5:1: PTH211 `os.symlink` should be replaced by `Path.symlink_to` + | +5 | os.symlink("usr/bin/python", "tmp/python") + | ^^^^^^^^^^ PTH211 +6 | os.symlink(b"usr/bin/python", b"tmp/python") +7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok + | + +PTH211.py:6:1: PTH211 `os.symlink` should be replaced by `Path.symlink_to` + | +5 | os.symlink("usr/bin/python", "tmp/python") +6 | os.symlink(b"usr/bin/python", b"tmp/python") + | ^^^^^^^^^^ PTH211 +7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok + | + +PTH211.py:9:1: PTH211 `os.symlink` should be replaced by `Path.symlink_to` + | + 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok + 8 | + 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True) + | ^^^^^^^^^^ PTH211 +10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True) +11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok + | + +PTH211.py:10:1: PTH211 `os.symlink` should be replaced by `Path.symlink_to` + | + 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True) +10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True) + | ^^^^^^^^^^ PTH211 +11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok + | diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs index 22935ea28dc78c..1bed2ba4bf5f70 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs @@ -1215,3 +1215,45 @@ impl Violation for OsListdir { "Use `pathlib.Path.iterdir()` instead.".to_string() } } + +/// ## What it does +/// Checks for uses of `os.symlink`. +/// +/// ## Why is this bad? +/// `pathlib` offers a high-level API for path manipulation, as compared to +/// the lower-level API offered by `os.symlink`. +/// +/// ## Example +/// ```python +/// import os +/// +/// os.symlink("usr/bin/python", "tmp/python", target_is_directory=False) +/// ``` +/// +/// Use instead: +/// ```python +/// from pathlib import Path +/// +/// Path("tmp/python").symlink_to("usr/bin/python") +/// ``` +/// +/// ## Known issues +/// While using `pathlib` can improve the readability and type safety of your code, +/// it can be less performant than the lower-level alternatives that work directly with strings, +/// especially on older versions of Python. +/// +/// ## References +/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to) +/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/) +/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module) +/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/) +/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/) +#[derive(ViolationMetadata)] +pub(crate) struct OsSymlink; + +impl Violation for OsSymlink { + #[derive_message_formats] + fn message(&self) -> String { + "`os.symlink` should be replaced by `Path.symlink_to`".to_string() + } +} diff --git a/ruff.schema.json b/ruff.schema.json index e0f8ce55fbc831..37fc8102c7eb68 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3889,6 +3889,7 @@ "PTH208", "PTH21", "PTH210", + "PTH211", "PYI", "PYI0", "PYI00", From a3ee6bb3b5f4f375e606d3bc12094549bb46a98b Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Wed, 28 May 2025 07:41:31 -0400 Subject: [PATCH 262/487] Return `DiagnosticGuard` from `Checker::report_diagnostic` (#18232) Summary -- This PR adds a `DiagnosticGuard` type to ruff that is adapted from the `DiagnosticGuard` and `LintDiagnosticGuard` types from ty. This guard is returned by `Checker::report_diagnostic` and derefs to a `ruff_diagnostics::Diagnostic` (`OldDiagnostic`), allowing methods like `OldDiagnostic::set_fix` to be called on the result. On `Drop` the `DiagnosticGuard` pushes its contained `OldDiagnostic` to the `Checker`. The main motivation for this is to make a following PR adding a `SourceFile` to each diagnostic easier. For every rule where a `Checker` is available, this will now only require modifying `Checker::report_diagnostic` rather than all the rules. In the few cases where we need to create a diagnostic before we know if we actually want to emit it, there is a `DiagnosticGuard::defuse` method, which consumes the guard without emitting the diagnostic. I was able to restructure about half of the rules that naively called this to avoid calling it, but a handful of rules still need it. One of the fairly common patterns where `defuse` was needed initially was something like ```rust let diagnostic = Diagnostic::new(DiagnosticKind, range); if !checker.enabled(diagnostic.rule()) { return; } ``` So I also added a `Checker::checked_report_diagnostic` method that handles this check internally. That helped to avoid some additional `defuse` calls. The name is a bit repetitive, so I'm definitely open to suggestions there. I included a warning against using it in the docs since, as we've seen, the conversion from a diagnostic to a rule is actually pretty expensive. Test Plan -- Existing tests --- .../src/checkers/ast/analyze/bindings.rs | 82 ++--- .../checkers/ast/analyze/deferred_scopes.rs | 18 +- .../checkers/ast/analyze/except_handler.rs | 9 +- .../src/checkers/ast/analyze/expression.rs | 30 +- .../src/checkers/ast/analyze/statement.rs | 334 +++++++----------- .../ast/analyze/unresolved_references.rs | 9 +- crates/ruff_linter/src/checkers/ast/mod.rs | 121 ++++++- .../airflow/rules/dag_schedule_argument.rs | 5 +- .../airflow/rules/moved_to_provider_in_3.rs | 55 +-- .../src/rules/airflow/rules/removal_in_3.rs | 88 ++--- .../suggested_to_move_to_provider_in_3.rs | 54 +-- .../airflow/rules/suggested_to_update_3_0.rs | 62 ++-- .../rules/airflow/rules/task_variable_name.rs | 5 +- .../rules/fastapi_non_annotated_dependency.rs | 6 +- .../rules/fastapi_redundant_response_model.rs | 5 +- .../rules/fastapi_unused_path_parameter.rs | 5 +- .../src/rules/flake8_2020/rules/compare.rs | 20 +- .../flake8_2020/rules/name_or_attribute.rs | 4 +- .../src/rules/flake8_2020/rules/subscript.rs | 10 +- .../flake8_annotations/rules/definition.rs | 49 ++- .../flake8_async/rules/async_busy_wait.rs | 6 +- .../rules/async_function_with_timeout.rs | 7 +- .../flake8_async/rules/async_zero_sleep.rs | 5 +- .../flake8_async/rules/blocking_http_call.rs | 7 +- .../flake8_async/rules/blocking_open_call.rs | 7 +- .../rules/blocking_process_invocation.rs | 19 +- .../flake8_async/rules/blocking_sleep.rs | 7 +- .../rules/cancel_scope_no_checkpoint.rs | 7 +- .../rules/long_sleep_not_forever.rs | 5 +- .../src/rules/flake8_async/rules/sync_call.rs | 5 +- .../rules/flake8_bandit/rules/assert_used.rs | 8 +- .../rules/bad_file_permissions.rs | 10 +- .../rules/flake8_bandit/rules/django_extra.rs | 4 +- .../flake8_bandit/rules/django_raw_sql.rs | 4 +- .../rules/flake8_bandit/rules/exec_used.rs | 4 +- .../flake8_bandit/rules/flask_debug_true.rs | 4 +- .../rules/hardcoded_bind_all_interfaces.rs | 16 +- .../rules/hardcoded_password_default.rs | 18 +- .../rules/hardcoded_password_func_arg.rs | 6 +- .../rules/hardcoded_password_string.rs | 10 +- .../rules/hardcoded_sql_expression.rs | 4 +- .../rules/hardcoded_tmp_directory.rs | 6 +- .../rules/hashlib_insecure_hash_functions.rs | 14 +- .../rules/jinja2_autoescape_false.rs | 19 +- .../rules/logging_config_insecure_listen.rs | 7 +- .../flake8_bandit/rules/mako_templates.rs | 4 +- .../flake8_bandit/rules/paramiko_calls.rs | 4 +- .../rules/request_with_no_cert_validation.rs | 6 +- .../rules/request_without_timeout.rs | 10 +- .../flake8_bandit/rules/shell_injection.rs | 30 +- .../rules/snmp_insecure_version.rs | 4 +- .../rules/snmp_weak_cryptography.rs | 4 +- .../rules/ssh_no_host_key_verification.rs | 7 +- .../rules/ssl_insecure_version.rs | 10 +- .../rules/ssl_with_bad_defaults.rs | 10 +- .../rules/ssl_with_no_version.rs | 4 +- .../rules/suspicious_function_call.rs | 63 ++-- .../flake8_bandit/rules/suspicious_imports.rs | 171 +++++---- .../rules/tarfile_unsafe_members.rs | 3 +- .../rules/try_except_continue.rs | 4 +- .../flake8_bandit/rules/try_except_pass.rs | 4 +- .../flake8_bandit/rules/unsafe_markup_use.rs | 6 +- .../flake8_bandit/rules/unsafe_yaml_load.rs | 12 +- .../rules/weak_cryptographic_key.rs | 7 +- .../flake8_blind_except/rules/blind_except.rs | 6 +- ...olean_default_value_positional_argument.rs | 7 +- .../rules/boolean_positional_value_in_call.rs | 4 +- .../boolean_type_hint_positional_argument.rs | 6 +- .../rules/abstract_base_class.rs | 10 +- .../flake8_bugbear/rules/assert_false.rs | 5 +- .../rules/assert_raises_exception.rs | 7 +- .../rules/assignment_to_os_environ.rs | 4 +- .../rules/batched_without_explicit_strict.rs | 5 +- .../rules/cached_instance_method.rs | 4 +- .../rules/class_as_data_structure.rs | 4 +- .../rules/duplicate_exceptions.rs | 9 +- .../flake8_bugbear/rules/duplicate_value.rs | 6 +- .../rules/except_with_empty_tuple.rs | 7 +- .../except_with_non_exception_classes.rs | 7 +- .../rules/f_string_docstring.rs | 4 +- .../function_call_in_argument_default.rs | 5 +- .../rules/function_uses_loop_variable.rs | 6 +- .../rules/getattr_with_constant.rs | 5 +- .../rules/jump_statement_in_finally.rs | 6 +- .../rules/loop_iterator_mutation.rs | 3 +- .../rules/loop_variable_overrides_iterator.rs | 6 +- .../rules/mutable_argument_default.rs | 5 +- .../rules/mutable_contextvar_default.rs | 4 +- .../rules/no_explicit_stacklevel.rs | 6 +- .../flake8_bugbear/rules/raise_literal.rs | 4 +- .../rules/raise_without_from_inside_except.rs | 7 +- .../rules/re_sub_positional_args.rs | 7 +- .../redundant_tuple_in_exception_handler.rs | 5 +- .../rules/return_in_generator.rs | 3 +- .../rules/reuse_of_groupby_generator.rs | 4 +- .../rules/setattr_with_constant.rs | 5 +- .../star_arg_unpacking_after_keyword_arg.rs | 7 +- .../rules/static_key_dict_comprehension.rs | 6 +- .../rules/strip_with_multi_characters.rs | 4 +- .../rules/unary_prefix_increment_decrement.rs | 10 +- .../rules/unintentional_type_annotation.rs | 10 +- .../rules/unreliable_callable_check.rs | 5 +- .../rules/unused_loop_control_variable.rs | 5 +- .../rules/useless_comparison.rs | 10 +- .../rules/useless_contextlib_suppress.rs | 4 +- .../rules/useless_expression.rs | 10 +- .../rules/zip_without_explicit_strict.rs | 10 +- .../rules/builtin_argument_shadowing.rs | 5 +- .../rules/builtin_attribute_shadowing.rs | 5 +- .../rules/builtin_import_shadowing.rs | 6 +- .../builtin_lambda_argument_shadowing.rs | 6 +- .../rules/builtin_variable_shadowing.rs | 5 +- .../rules/unnecessary_call_around_sorted.rs | 5 +- .../rules/unnecessary_collection_call.rs | 6 +- .../rules/unnecessary_comprehension.rs | 5 +- .../unnecessary_comprehension_in_call.rs | 7 +- ...cessary_dict_comprehension_for_iterable.rs | 6 +- .../unnecessary_double_cast_or_process.rs | 5 +- .../rules/unnecessary_generator_dict.rs | 5 +- .../rules/unnecessary_generator_list.rs | 21 +- .../rules/unnecessary_generator_set.rs | 9 +- .../rules/unnecessary_list_call.rs | 5 +- .../unnecessary_list_comprehension_dict.rs | 5 +- .../unnecessary_list_comprehension_set.rs | 6 +- .../rules/unnecessary_literal_dict.rs | 6 +- .../rules/unnecessary_literal_set.rs | 6 +- .../unnecessary_literal_within_dict_call.rs | 6 +- .../unnecessary_literal_within_list_call.rs | 6 +- .../unnecessary_literal_within_tuple_call.rs | 8 +- .../rules/unnecessary_map.rs | 5 +- .../rules/unnecessary_subscript_reversal.rs | 6 +- .../rules/call_date_fromtimestamp.rs | 4 +- .../flake8_datetimez/rules/call_date_today.rs | 4 +- .../rules/call_datetime_fromtimestamp.rs | 7 +- .../rules/call_datetime_now_without_tzinfo.rs | 7 +- .../call_datetime_strptime_without_zone.rs | 7 +- .../rules/call_datetime_today.rs | 4 +- .../rules/call_datetime_utcfromtimestamp.rs | 4 +- .../rules/call_datetime_utcnow.rs | 4 +- .../rules/call_datetime_without_tzinfo.rs | 7 +- .../rules/datetime_min_max.rs | 4 +- .../rules/flake8_debugger/rules/debugger.rs | 15 +- .../rules/all_with_model_form.rs | 12 +- .../rules/exclude_with_model_form.rs | 7 +- .../rules/locals_in_render_function.rs | 7 +- .../rules/model_without_dunder_str.rs | 7 +- .../rules/non_leading_receiver_decorator.rs | 7 +- .../rules/nullable_model_string_field.rs | 6 +- .../rules/unordered_body_content_in_model.rs | 5 +- .../rules/string_in_exception.rs | 12 +- .../rules/future_required_type_annotation.rs | 6 +- .../future_rewritable_type_annotation.rs | 7 +- .../rules/f_string_in_gettext_func_call.rs | 4 +- .../rules/format_in_gettext_func_call.rs | 7 +- .../rules/printf_in_gettext_func_call.rs | 5 +- .../rules/explicit.rs | 13 +- .../rules/banned_import_alias.rs | 11 +- .../rules/banned_import_from.rs | 12 +- .../rules/unconventional_import_alias.rs | 17 +- .../rules/direct_logger_instantiation.rs | 6 +- .../rules/exc_info_outside_except_handler.rs | 6 +- .../rules/exception_without_exc_info.rs | 4 +- .../rules/invalid_get_logger_argument.rs | 5 +- .../log_exception_outside_except_handler.rs | 6 +- .../flake8_logging/rules/root_logger_call.rs | 6 +- .../flake8_logging/rules/undocumented_warn.rs | 5 +- .../rules/logging_call.rs | 29 +- .../rules/duplicate_class_field_definition.rs | 4 +- .../rules/multiple_starts_ends_with.rs | 5 +- .../flake8_pie/rules/non_unique_enums.rs | 4 +- .../rules/reimplemented_container_builtin.rs | 6 +- .../rules/unnecessary_dict_kwargs.rs | 11 +- .../rules/unnecessary_placeholder.rs | 18 +- .../rules/unnecessary_range_start.rs | 4 +- .../flake8_pie/rules/unnecessary_spread.rs | 5 +- .../rules/flake8_print/rules/print_call.rs | 15 +- .../flake8_pyi/rules/any_eq_ne_annotation.rs | 5 +- .../rules/bad_generator_return_type.rs | 6 +- .../rules/bad_version_info_comparison.rs | 6 +- .../flake8_pyi/rules/bytestring_usage.rs | 9 +- .../rules/collections_named_tuple.rs | 4 +- .../rules/complex_assignment_in_stub.rs | 4 +- .../rules/complex_if_statement_in_stub.rs | 8 +- .../rules/custom_type_var_for_self.rs | 47 +-- .../flake8_pyi/rules/docstring_in_stubs.rs | 8 +- .../rules/duplicate_literal_member.rs | 10 +- .../rules/duplicate_union_member.rs | 11 +- .../rules/ellipsis_in_non_empty_class_body.rs | 6 +- .../flake8_pyi/rules/exit_annotations.rs | 26 +- .../rules/future_annotations_in_stub.rs | 6 +- .../rules/generic_not_last_base_class.rs | 6 +- .../rules/iter_method_return_iterable.rs | 7 +- .../rules/no_return_argument_annotation.rs | 6 +- .../flake8_pyi/rules/non_empty_stub_body.rs | 5 +- .../flake8_pyi/rules/non_self_return_type.rs | 6 +- .../rules/numeric_literal_too_long.rs | 5 +- .../flake8_pyi/rules/pass_in_class_body.rs | 5 +- .../rules/pass_statement_stub_body.rs | 5 +- .../rules/pre_pep570_positional_argument.rs | 7 +- .../flake8_pyi/rules/prefix_type_params.rs | 4 +- .../rules/quoted_annotation_in_stub.rs | 5 +- .../rules/redundant_final_literal.rs | 6 +- .../rules/redundant_literal_union.rs | 6 +- .../rules/redundant_none_literal.rs | 5 +- .../rules/redundant_numeric_union.rs | 7 +- .../rules/flake8_pyi/rules/simple_defaults.rs | 29 +- .../rules/str_or_repr_defined_in_stub.rs | 5 +- .../rules/string_or_bytes_too_long.rs | 5 +- .../rules/stub_body_multiple_statements.rs | 7 +- .../flake8_pyi/rules/type_alias_naming.rs | 10 +- .../unaliased_collections_abc_set_import.rs | 17 +- .../rules/unnecessary_literal_union.rs | 6 +- .../rules/unnecessary_type_union.rs | 6 +- .../flake8_pyi/rules/unrecognized_platform.rs | 10 +- .../rules/unrecognized_version_info.rs | 17 +- .../rules/unsupported_method_call_on_all.rs | 6 +- .../rules/unused_private_type_definition.rs | 43 ++- .../flake8_pytest_style/rules/assertion.rs | 56 ++- .../rules/flake8_pytest_style/rules/fail.rs | 4 +- .../flake8_pytest_style/rules/fixture.rs | 37 +- .../flake8_pytest_style/rules/imports.rs | 27 +- .../rules/flake8_pytest_style/rules/marks.rs | 9 +- .../flake8_pytest_style/rules/parametrize.rs | 38 +- .../rules/flake8_pytest_style/rules/patch.rs | 35 +- .../rules/flake8_pytest_style/rules/raises.rs | 16 +- .../rules/test_functions.rs | 7 +- .../rules/flake8_pytest_style/rules/warns.rs | 16 +- .../rules/avoidable_escaped_quote.rs | 58 ++- .../rules/check_string_quotes.rs | 23 +- .../rules/unnecessary_escaped_quote.rs | 67 ++-- .../unnecessary_paren_on_raise_exception.rs | 7 +- .../src/rules/flake8_return/rules/function.rs | 115 ++---- .../rules/private_member_access.rs | 6 +- .../flake8_simplify/rules/ast_bool_op.rs | 20 +- .../rules/flake8_simplify/rules/ast_expr.rs | 12 +- .../rules/flake8_simplify/rules/ast_ifexp.rs | 11 +- .../flake8_simplify/rules/ast_unary_op.rs | 11 +- .../rules/flake8_simplify/rules/ast_with.rs | 5 +- .../flake8_simplify/rules/collapsible_if.rs | 5 +- .../rules/enumerate_for_loop.rs | 5 +- .../if_else_block_instead_of_dict_get.rs | 8 +- .../if_else_block_instead_of_dict_lookup.rs | 7 +- .../rules/if_else_block_instead_of_if_exp.rs | 5 +- .../rules/if_with_same_arms.rs | 6 +- .../flake8_simplify/rules/key_in_dict.rs | 5 +- .../flake8_simplify/rules/needless_bool.rs | 5 +- .../rules/open_file_with_context_handler.rs | 7 +- .../rules/reimplemented_builtin.rs | 8 +- .../rules/return_in_try_except_finally.rs | 7 +- .../rules/split_static_string.rs | 5 +- .../rules/suppressible_exception.rs | 5 +- .../flake8_simplify/rules/yoda_conditions.rs | 10 +- .../rules/zip_dict_keys_and_values.rs | 5 +- .../rules/no_slots_in_namedtuple_subclass.rs | 6 +- .../rules/no_slots_in_str_subclass.rs | 4 +- .../rules/no_slots_in_tuple_subclass.rs | 4 +- .../flake8_tidy_imports/rules/banned_api.rs | 10 +- .../rules/banned_module_level_imports.rs | 6 +- .../rules/relative_imports.rs | 10 +- .../rules/empty_type_checking_block.rs | 5 +- .../rules/runtime_cast_value.rs | 5 +- .../runtime_import_in_type_checking_block.rs | 11 +- .../rules/runtime_string_union.rs | 4 +- .../rules/type_alias_quotes.rs | 13 +- .../rules/typing_only_runtime_import.rs | 35 +- .../rules/unused_arguments.rs | 23 +- .../rules/invalid_pathlib_with_suffix.rs | 7 +- .../flake8_use_pathlib/rules/os_sep_split.rs | 4 +- .../path_constructor_current_directory.rs | 6 +- .../rules/replaceable_by_pathlib.rs | 80 ++--- .../flynt/rules/static_join_to_fstring.rs | 5 +- .../mccabe/rules/function_is_too_complex.rs | 13 +- .../rules/numpy/rules/deprecated_function.rs | 5 +- .../numpy/rules/deprecated_type_alias.rs | 5 +- .../src/rules/numpy/rules/legacy_random.rs | 6 +- .../numpy/rules/numpy_2_0_deprecation.rs | 5 +- .../pandas_vet/rules/assignment_to_df.rs | 14 +- .../src/rules/pandas_vet/rules/attr.rs | 4 +- .../src/rules/pandas_vet/rules/call.rs | 31 +- .../pandas_vet/rules/inplace_argument.rs | 7 +- .../rules/nunique_constant_series_check.rs | 6 +- .../src/rules/pandas_vet/rules/pd_merge.rs | 4 +- .../src/rules/pandas_vet/rules/read_table.rs | 5 +- .../src/rules/pandas_vet/rules/subscript.rs | 29 +- .../rules/camelcase_imported_as_acronym.rs | 12 +- .../rules/camelcase_imported_as_constant.rs | 16 +- .../rules/camelcase_imported_as_lowercase.rs | 12 +- .../constant_imported_as_non_constant.rs | 16 +- .../pep8_naming/rules/dunder_function_name.rs | 16 +- .../rules/error_suffix_on_exception_name.rs | 17 +- .../rules/invalid_argument_name.rs | 6 +- .../pep8_naming/rules/invalid_class_name.rs | 14 +- .../rules/invalid_first_argument_name.rs | 21 +- .../rules/invalid_function_name.rs | 16 +- .../lowercase_imported_as_non_lowercase.rs | 13 +- .../mixed_case_variable_in_class_scope.rs | 6 +- .../mixed_case_variable_in_global_scope.rs | 6 +- .../non_lowercase_variable_in_function.rs | 6 +- .../perflint/rules/incorrect_dict_iterator.rs | 8 +- .../rules/manual_dict_comprehension.rs | 10 +- .../rules/manual_list_comprehension.rs | 6 +- .../rules/perflint/rules/manual_list_copy.rs | 4 +- .../perflint/rules/try_except_in_loop.rs | 4 +- .../perflint/rules/unnecessary_list_cast.rs | 8 +- .../pycodestyle/rules/ambiguous_class_name.rs | 13 +- .../rules/ambiguous_function_name.rs | 13 +- .../rules/ambiguous_variable_name.rs | 7 +- .../rules/pycodestyle/rules/bare_except.rs | 15 +- .../rules/invalid_escape_sequence.rs | 9 +- .../pycodestyle/rules/lambda_assignment.rs | 24 +- .../pycodestyle/rules/literal_comparisons.rs | 28 +- .../rules/module_import_not_at_top_of_file.rs | 6 +- .../rules/multiple_imports_on_one_line.rs | 5 +- .../src/rules/pycodestyle/rules/not_tests.rs | 8 +- .../pycodestyle/rules/type_comparison.rs | 4 +- .../rules/whitespace_after_decorator.rs | 5 +- .../rules/pydoclint/rules/check_docstring.rs | 34 +- .../src/rules/pydocstyle/rules/backslashes.rs | 6 +- .../pydocstyle/rules/blank_after_summary.rs | 5 +- .../rules/blank_before_after_class.rs | 17 +- .../rules/blank_before_after_function.rs | 8 +- .../src/rules/pydocstyle/rules/capitalized.rs | 6 +- .../pydocstyle/rules/ends_with_period.rs | 6 +- .../pydocstyle/rules/ends_with_punctuation.rs | 6 +- .../src/rules/pydocstyle/rules/if_needed.rs | 7 +- .../src/rules/pydocstyle/rules/indent.rs | 13 +- .../rules/multi_line_summary_start.rs | 10 +- .../rules/newline_after_last_paragraph.rs | 5 +- .../rules/pydocstyle/rules/no_signature.rs | 4 +- .../rules/no_surrounding_whitespace.rs | 5 +- .../pydocstyle/rules/non_imperative_mood.rs | 6 +- .../src/rules/pydocstyle/rules/not_empty.rs | 4 +- .../src/rules/pydocstyle/rules/not_missing.rs | 47 +-- .../src/rules/pydocstyle/rules/one_liner.rs | 7 +- .../src/rules/pydocstyle/rules/sections.rs | 76 ++-- .../pydocstyle/rules/starts_with_this.rs | 4 +- .../rules/pydocstyle/rules/triple_quotes.rs | 14 +- .../src/rules/pyflakes/rules/assert_tuple.rs | 4 +- .../pyflakes/rules/break_outside_loop.rs | 11 +- .../pyflakes/rules/continue_outside_loop.rs | 11 +- .../pyflakes/rules/default_except_not_last.rs | 13 +- .../rules/f_string_missing_placeholders.rs | 6 +- .../rules/future_feature_not_defined.rs | 6 +- .../src/rules/pyflakes/rules/if_tuple.rs | 4 +- .../rules/invalid_literal_comparisons.rs | 6 +- .../pyflakes/rules/invalid_print_syntax.rs | 4 +- .../pyflakes/rules/raise_not_implemented.rs | 5 +- .../src/rules/pyflakes/rules/repeated_keys.rs | 8 +- .../pyflakes/rules/starred_expressions.rs | 14 +- .../src/rules/pyflakes/rules/strings.rs | 44 +-- .../rules/pyflakes/rules/undefined_local.rs | 6 +- .../rules/pyflakes/rules/unused_annotation.rs | 4 +- .../src/rules/pyflakes/rules/unused_import.rs | 8 +- .../rules/pyflakes/rules/unused_variable.rs | 5 +- .../pygrep_hooks/rules/invalid_mock_access.rs | 10 +- .../pylint/rules/assert_on_string_literal.rs | 15 +- .../pylint/rules/bad_dunder_method_name.rs | 6 +- .../src/rules/pylint/rules/bad_open_mode.rs | 6 +- .../pylint/rules/bad_staticmethod_argument.rs | 6 +- .../rules/pylint/rules/bad_str_strip_call.rs | 6 +- .../rules/bad_string_format_character.rs | 15 +- .../pylint/rules/bad_string_format_type.rs | 4 +- .../rules/pylint/rules/binary_op_exception.rs | 7 +- .../rules/boolean_chained_comparison.rs | 173 +++++---- .../rules/pylint/rules/collapsible_else_if.rs | 5 +- .../pylint/rules/compare_to_empty_string.rs | 10 +- .../pylint/rules/comparison_of_constant.rs | 6 +- .../pylint/rules/comparison_with_itself.rs | 10 +- .../rules/pylint/rules/continue_in_finally.rs | 4 +- .../pylint/rules/dict_index_missing_items.rs | 5 +- .../pylint/rules/dict_iter_missing_items.rs | 5 +- .../src/rules/pylint/rules/duplicate_bases.rs | 5 +- .../src/rules/pylint/rules/eq_without_hash.rs | 5 +- .../pylint/rules/global_at_module_level.rs | 4 +- .../rules/pylint/rules/global_statement.rs | 6 +- .../src/rules/pylint/rules/if_stmt_min_max.rs | 6 +- .../pylint/rules/import_outside_top_level.rs | 4 +- .../rules/pylint/rules/import_private_name.rs | 7 +- .../src/rules/pylint/rules/import_self.rs | 34 +- .../rules/pylint/rules/invalid_all_format.rs | 10 +- .../rules/pylint/rules/invalid_all_object.rs | 10 +- .../rules/pylint/rules/invalid_bool_return.rs | 11 +- .../pylint/rules/invalid_bytes_return.rs | 11 +- .../pylint/rules/invalid_envvar_default.rs | 4 +- .../pylint/rules/invalid_envvar_value.rs | 4 +- .../rules/pylint/rules/invalid_hash_return.rs | 11 +- .../pylint/rules/invalid_index_return.rs | 11 +- .../pylint/rules/invalid_length_return.rs | 11 +- .../rules/pylint/rules/invalid_str_return.rs | 11 +- .../rules/pylint/rules/iteration_over_set.rs | 6 +- .../src/rules/pylint/rules/len_test.rs | 11 +- .../rules/pylint/rules/literal_membership.rs | 6 +- .../src/rules/pylint/rules/logging.rs | 6 +- .../pylint/rules/magic_value_comparison.rs | 6 +- .../rules/pylint/rules/manual_import_from.rs | 5 +- .../pylint/rules/misplaced_bare_raise.rs | 4 +- .../pylint/rules/modified_iterating_set.rs | 5 +- .../rules/named_expr_without_context.rs | 4 +- .../src/rules/pylint/rules/nan_comparison.rs | 17 +- .../src/rules/pylint/rules/nested_min_max.rs | 6 +- .../rules/pylint/rules/no_method_decorator.rs | 9 +- .../src/rules/pylint/rules/no_self_use.rs | 6 +- .../pylint/rules/non_ascii_module_import.rs | 10 +- .../src/rules/pylint/rules/non_ascii_name.rs | 15 +- .../pylint/rules/non_augmented_assignment.rs | 11 +- .../rules/pylint/rules/non_slot_assignment.rs | 6 +- .../rules/pylint/rules/nonlocal_and_global.rs | 6 +- .../pylint/rules/potential_index_error.rs | 4 +- .../pylint/rules/property_with_parameters.rs | 4 +- .../pylint/rules/redeclared_assigned_name.rs | 6 +- .../rules/pylint/rules/redefined_loop_name.rs | 6 +- .../rules/redefined_slots_in_subclass.rs | 6 +- .../rules/repeated_equality_comparison.rs | 6 +- .../pylint/rules/repeated_keyword_argument.rs | 10 +- .../src/rules/pylint/rules/return_in_init.rs | 4 +- .../pylint/rules/self_assigning_variable.rs | 6 +- .../pylint/rules/self_or_cls_assignment.rs | 7 +- .../pylint/rules/shallow_copy_environ.rs | 5 +- .../rules/pylint/rules/single_string_slots.rs | 12 +- .../pylint/rules/singledispatch_method.rs | 5 +- .../rules/singledispatchmethod_function.rs | 6 +- .../rules/subprocess_popen_preexec_fn.rs | 4 +- .../rules/subprocess_run_without_check.rs | 6 +- .../pylint/rules/super_without_brackets.rs | 6 +- .../src/rules/pylint/rules/sys_exit_alias.rs | 6 +- .../rules/pylint/rules/too_many_arguments.rs | 6 +- .../rules/too_many_boolean_expressions.rs | 10 +- .../rules/pylint/rules/too_many_branches.rs | 13 +- .../src/rules/pylint/rules/too_many_locals.rs | 6 +- .../pylint/rules/too_many_nested_blocks.rs | 6 +- .../rules/too_many_positional_arguments.rs | 6 +- .../pylint/rules/too_many_public_methods.rs | 6 +- .../rules/too_many_return_statements.rs | 13 +- .../rules/pylint/rules/too_many_statements.rs | 13 +- .../src/rules/pylint/rules/type_bivariance.rs | 6 +- .../rules/type_name_incorrect_variance.rs | 6 +- .../pylint/rules/type_param_name_mismatch.rs | 6 +- .../unexpected_special_method_signature.rs | 6 +- .../rules/unnecessary_dict_index_lookup.rs | 8 +- .../rules/unnecessary_direct_lambda_call.rs | 4 +- .../pylint/rules/unnecessary_dunder_call.rs | 6 +- .../rules/pylint/rules/unnecessary_lambda.rs | 5 +- .../rules/unnecessary_list_index_lookup.rs | 8 +- .../src/rules/pylint/rules/unreachable.rs | 6 +- .../pylint/rules/unspecified_encoding.rs | 5 +- .../pylint/rules/useless_else_on_loop.rs | 5 +- .../rules/useless_exception_statement.rs | 5 +- .../pylint/rules/useless_import_alias.rs | 10 +- .../src/rules/pylint/rules/useless_return.rs | 5 +- .../rules/pylint/rules/useless_with_lock.rs | 4 +- .../rules/yield_from_in_async_function.rs | 4 +- .../src/rules/pylint/rules/yield_in_init.rs | 4 +- ...convert_named_tuple_functional_to_class.rs | 5 +- .../convert_typed_dict_functional_to_class.rs | 5 +- .../pyupgrade/rules/datetime_utc_alias.rs | 5 +- .../rules/deprecated_c_element_tree.rs | 5 +- .../pyupgrade/rules/deprecated_import.rs | 8 +- .../pyupgrade/rules/deprecated_mock_import.rs | 11 +- .../rules/deprecated_unittest_alias.rs | 5 +- .../src/rules/pyupgrade/rules/f_strings.rs | 5 +- .../rules/pyupgrade/rules/format_literals.rs | 5 +- .../rules/lru_cache_with_maxsize_none.rs | 5 +- .../rules/lru_cache_without_parameters.rs | 5 +- .../rules/pyupgrade/rules/native_literals.rs | 13 +- .../pyupgrade/rules/non_pep646_unpack.rs | 5 +- .../src/rules/pyupgrade/rules/open_alias.rs | 5 +- .../rules/pyupgrade/rules/os_error_alias.rs | 8 +- .../pyupgrade/rules/outdated_version_block.rs | 27 +- .../rules/pep695/non_pep695_generic_class.rs | 8 +- .../pep695/non_pep695_generic_function.rs | 11 +- .../rules/pep695/non_pep695_type_alias.rs | 29 +- .../rules/pep695/private_type_parameter.rs | 24 +- .../rules/printf_string_formatting.rs | 12 +- .../pyupgrade/rules/quoted_annotation.rs | 8 +- .../pyupgrade/rules/redundant_open_modes.rs | 15 +- .../pyupgrade/rules/replace_stdout_stderr.rs | 5 +- .../rules/pyupgrade/rules/replace_str_enum.rs | 6 +- .../rules/replace_universal_newlines.rs | 5 +- .../rules/super_call_with_parameters.rs | 5 +- .../pyupgrade/rules/timeout_error_alias.rs | 8 +- .../pyupgrade/rules/type_of_primitive.rs | 5 +- .../pyupgrade/rules/typing_text_str_alias.rs | 5 +- .../pyupgrade/rules/unicode_kind_prefix.rs | 5 +- .../rules/unnecessary_builtin_import.rs | 5 +- .../rules/unnecessary_class_parentheses.rs | 5 +- .../rules/unnecessary_default_type_args.rs | 5 +- .../rules/unnecessary_encode_utf8.rs | 17 +- .../rules/unnecessary_future_import.rs | 5 +- .../pyupgrade/rules/use_pep585_annotation.rs | 5 +- .../pyupgrade/rules/use_pep604_annotation.rs | 22 +- .../pyupgrade/rules/use_pep604_isinstance.rs | 16 +- .../rules/useless_class_metaclass_type.rs | 6 +- .../pyupgrade/rules/useless_metaclass_type.rs | 5 +- .../rules/useless_object_inheritance.rs | 5 +- .../pyupgrade/rules/yield_in_for_loop.rs | 6 +- .../src/rules/refurb/rules/bit_count.rs | 6 +- .../refurb/rules/check_and_remove_from_set.rs | 5 +- .../rules/refurb/rules/delete_full_slice.rs | 6 +- .../refurb/rules/for_loop_set_mutations.rs | 20 +- .../src/rules/refurb/rules/for_loop_writes.rs | 77 ++-- .../refurb/rules/fromisoformat_replace_z.rs | 7 +- .../refurb/rules/fstring_number_format.rs | 6 +- .../refurb/rules/hardcoded_string_charset.rs | 5 +- .../rules/refurb/rules/hashlib_digest_hex.rs | 5 +- .../rules/if_exp_instead_of_or_operator.rs | 6 +- .../src/rules/refurb/rules/if_expr_min_max.rs | 6 +- .../src/rules/refurb/rules/implicit_cwd.rs | 6 +- .../rules/refurb/rules/int_on_sliced_str.rs | 5 +- .../refurb/rules/isinstance_type_none.rs | 8 +- .../rules/refurb/rules/list_reverse_copy.rs | 11 +- .../src/rules/refurb/rules/math_constant.rs | 5 +- .../rules/refurb/rules/metaclass_abcmeta.rs | 6 +- .../rules/refurb/rules/print_empty_string.rs | 15 +- .../src/rules/refurb/rules/read_whole_file.rs | 6 +- .../rules/refurb/rules/readlines_in_for.rs | 5 +- .../rules/refurb/rules/redundant_log_base.rs | 5 +- .../rules/refurb/rules/regex_flag_alias.rs | 5 +- .../refurb/rules/reimplemented_operator.rs | 5 +- .../refurb/rules/reimplemented_starmap.rs | 5 +- .../src/rules/refurb/rules/repeated_append.rs | 6 +- .../src/rules/refurb/rules/repeated_global.rs | 36 +- .../rules/single_item_membership_test.rs | 8 +- .../src/rules/refurb/rules/slice_copy.rs | 5 +- .../rules/slice_to_remove_prefix_or_suffix.rs | 8 +- .../src/rules/refurb/rules/sorted_min_max.rs | 5 +- .../rules/refurb/rules/subclass_builtin.rs | 5 +- .../refurb/rules/type_none_comparison.rs | 8 +- .../refurb/rules/unnecessary_enumerate.rs | 9 +- .../refurb/rules/unnecessary_from_float.rs | 6 +- .../rules/verbose_decimal_constructor.rs | 20 +- .../rules/refurb/rules/write_whole_file.rs | 6 +- .../ruff/rules/ambiguous_unicode_character.rs | 87 +++-- .../ruff/rules/assert_with_print_message.rs | 5 +- .../rules/ruff/rules/assignment_in_assert.rs | 20 +- .../rules/ruff/rules/asyncio_dangling_task.rs | 30 +- .../ruff/rules/class_with_mixed_type_vars.rs | 6 +- .../rules/collection_literal_concatenation.rs | 5 +- .../src/rules/ruff/rules/dataclass_enum.rs | 6 +- .../ruff/rules/decimal_from_float_literal.rs | 14 +- .../rules/ruff/rules/default_factory_kwarg.rs | 5 +- .../explicit_f_string_type_conversion.rs | 6 +- .../ruff/rules/falsy_dict_get_fallback.rs | 6 +- .../function_call_in_dataclass_default.rs | 6 +- .../rules/ruff/rules/if_key_in_dict_del.rs | 8 +- .../rules/implicit_classvar_in_dataclass.rs | 6 +- .../src/rules/ruff/rules/implicit_optional.rs | 8 +- .../rules/ruff/rules/in_empty_collection.rs | 4 +- ...rectly_parenthesized_tuple_in_subscript.rs | 9 +- ...invalid_assert_message_literal_argument.rs | 7 +- .../invalid_formatter_suppression_comment.rs | 9 +- .../rules/ruff/rules/invalid_index_type.rs | 18 +- .../ruff/rules/map_int_version_parsing.rs | 4 +- .../ruff/rules/missing_fstring_syntax.rs | 8 +- .../rules/ruff/rules/mutable_class_default.rs | 6 +- .../ruff/rules/mutable_dataclass_default.rs | 6 +- .../ruff/rules/mutable_fromkeys_value.rs | 5 +- .../src/rules/ruff/rules/needless_else.rs | 8 +- .../src/rules/ruff/rules/never_union.rs | 11 +- .../ruff/rules/none_not_at_end_of_union.rs | 4 +- .../rules/parenthesize_chained_operators.rs | 9 +- .../src/rules/ruff/rules/post_init_default.rs | 6 +- .../rules/pytest_raises_ambiguous_pattern.rs | 6 +- .../ruff/rules/quadratic_list_summation.rs | 5 +- .../ruff/rules/redundant_bool_literal.rs | 6 +- .../src/rules/ruff/rules/sort_dunder_all.rs | 6 +- .../src/rules/ruff/rules/sort_dunder_slots.rs | 42 ++- .../src/rules/ruff/rules/starmap_zip.rs | 6 +- .../ruff/rules/unnecessary_cast_to_int.rs | 8 +- ...y_iterable_allocation_for_first_element.rs | 6 +- .../rules/ruff/rules/unnecessary_key_check.rs | 5 +- .../unnecessary_literal_within_deque_call.rs | 6 +- .../ruff/rules/unnecessary_nested_literal.rs | 6 +- .../rules/unnecessary_regular_expression.rs | 6 +- .../src/rules/ruff/rules/unnecessary_round.rs | 8 +- .../src/rules/ruff/rules/unraw_re_pattern.rs | 9 +- .../src/rules/ruff/rules/unused_async.rs | 6 +- .../ruff/rules/unused_unpacked_variable.rs | 5 +- .../rules/ruff/rules/used_dummy_variable.rs | 31 +- .../src/rules/ruff/rules/useless_if_else.rs | 4 +- .../ruff/rules/zip_instead_of_pairwise.rs | 6 +- .../rules/error_instead_of_exception.rs | 7 +- .../tryceratops/rules/raise_vanilla_args.rs | 4 +- .../tryceratops/rules/raise_vanilla_class.rs | 4 +- .../tryceratops/rules/raise_within_try.rs | 4 +- .../tryceratops/rules/try_consider_else.rs | 4 +- .../rules/type_check_without_type_error.rs | 4 +- .../tryceratops/rules/useless_try_except.rs | 4 +- .../tryceratops/rules/verbose_log_message.rs | 4 +- .../rules/tryceratops/rules/verbose_raise.rs | 6 +- crates/ruff_macros/src/violation_metadata.rs | 4 +- 590 files changed, 2962 insertions(+), 3769 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs index 1356bb6288fd87..f2f8147cc9255f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix}; +use ruff_diagnostics::Fix; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -38,92 +38,64 @@ pub(crate) fn bindings(checker: &Checker) { .dummy_variable_rgx .is_match(binding.name(checker.source())) { - let mut diagnostic = Diagnostic::new( - pyflakes::rules::UnusedVariable { - name: binding.name(checker.source()).to_string(), - }, - binding.range(), - ); - diagnostic.try_set_fix(|| { - pyflakes::fixes::remove_exception_handler_assignment(binding, checker.locator) + checker + .report_diagnostic( + pyflakes::rules::UnusedVariable { + name: binding.name(checker.source()).to_string(), + }, + binding.range(), + ) + .try_set_fix(|| { + pyflakes::fixes::remove_exception_handler_assignment( + binding, + checker.locator, + ) .map(Fix::safe_edit) - }); - checker.report_diagnostic(diagnostic); + }); } } if checker.enabled(Rule::InvalidAllFormat) { - if let Some(diagnostic) = pylint::rules::invalid_all_format(binding) { - checker.report_diagnostic(diagnostic); - } + pylint::rules::invalid_all_format(checker, binding); } if checker.enabled(Rule::InvalidAllObject) { - if let Some(diagnostic) = pylint::rules::invalid_all_object(binding) { - checker.report_diagnostic(diagnostic); - } + pylint::rules::invalid_all_object(checker, binding); } if checker.enabled(Rule::NonAsciiName) { - if let Some(diagnostic) = pylint::rules::non_ascii_name(binding, checker.locator) { - checker.report_diagnostic(diagnostic); - } + pylint::rules::non_ascii_name(checker, binding); } if checker.enabled(Rule::UnconventionalImportAlias) { - if let Some(diagnostic) = flake8_import_conventions::rules::unconventional_import_alias( + flake8_import_conventions::rules::unconventional_import_alias( checker, binding, &checker.settings.flake8_import_conventions.aliases, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) { - if let Some(diagnostic) = - flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding) - { - checker.report_diagnostic(diagnostic); - } + flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding); } if !checker.source_type.is_stub() && checker.enabled(Rule::UnquotedTypeAlias) { flake8_type_checking::rules::unquoted_type_alias(checker, binding); } if checker.enabled(Rule::UnsortedDunderSlots) { - if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) { - checker.report_diagnostic(diagnostic); - } + ruff::rules::sort_dunder_slots(checker, binding); } if checker.enabled(Rule::UsedDummyVariable) { - if let Some(diagnostic) = ruff::rules::used_dummy_variable(checker, binding, binding_id) - { - checker.report_diagnostic(diagnostic); - } + ruff::rules::used_dummy_variable(checker, binding, binding_id); } if checker.enabled(Rule::AssignmentInAssert) { - if let Some(diagnostic) = ruff::rules::assignment_in_assert(checker, binding) { - checker.report_diagnostic(diagnostic); - } + ruff::rules::assignment_in_assert(checker, binding); } if checker.enabled(Rule::PytestUnittestRaisesAssertion) { - if let Some(diagnostic) = - flake8_pytest_style::rules::unittest_raises_assertion_binding(checker, binding) - { - checker.report_diagnostic(diagnostic); - } + flake8_pytest_style::rules::unittest_raises_assertion_binding(checker, binding); } if checker.enabled(Rule::ForLoopWrites) { - if let Some(diagnostic) = refurb::rules::for_loop_writes_binding(checker, binding) { - checker.report_diagnostic(diagnostic); - } + refurb::rules::for_loop_writes_binding(checker, binding); } if checker.enabled(Rule::CustomTypeVarForSelf) { - if let Some(diagnostic) = - flake8_pyi::rules::custom_type_var_instead_of_self(checker, binding) - { - checker.report_diagnostic(diagnostic); - } + flake8_pyi::rules::custom_type_var_instead_of_self(checker, binding); } if checker.enabled(Rule::PrivateTypeParameter) { - if let Some(diagnostic) = pyupgrade::rules::private_type_parameter(checker, binding) { - checker.report_diagnostic(diagnostic); - } + pyupgrade::rules::private_type_parameter(checker, binding); } } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 36a8af20fa7187..72ef6bde280f68 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix}; +use ruff_diagnostics::Fix; use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind}; use ruff_text_size::Ranged; @@ -112,12 +112,12 @@ pub(crate) fn deferred_scopes(checker: &Checker) { .map(|id| checker.semantic.reference(*id)) .all(ResolvedReference::is_load) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pylint::rules::GlobalVariableNotAssigned { name: (*name).to_string(), }, binding.range(), - )); + ); } } } @@ -146,12 +146,12 @@ pub(crate) fn deferred_scopes(checker: &Checker) { if scope.kind.is_generator() { continue; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pylint::rules::RedefinedArgumentFromLocal { name: name.to_string(), }, binding.range(), - )); + ); } } } @@ -186,13 +186,13 @@ pub(crate) fn deferred_scopes(checker: &Checker) { continue; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::ImportShadowedByLoopVar { name: name.to_string(), row: checker.compute_source_row(shadowed.start()), }, binding.range(), - )); + ); } } } @@ -331,7 +331,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) { // Create diagnostics for each statement. for (source, entries) in &redefinitions { for (shadowed, binding) in entries { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( pyflakes::rules::RedefinedWhileUnused { name: binding.name(checker.source()).to_string(), row: checker.compute_source_row(shadowed.start()), @@ -346,8 +346,6 @@ pub(crate) fn deferred_scopes(checker: &Checker) { if let Some(fix) = source.as_ref().and_then(|source| fixes.get(source)) { diagnostic.set_fix(fix.clone()); } - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs b/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs index 9a68550216776b..7ef76d938274e8 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs @@ -17,14 +17,7 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker) range: _, }) => { if checker.enabled(Rule::BareExcept) { - if let Some(diagnostic) = pycodestyle::rules::bare_except( - type_.as_deref(), - body, - except_handler, - checker.locator, - ) { - checker.report_diagnostic(diagnostic); - } + pycodestyle::rules::bare_except(checker, type_.as_deref(), body, except_handler); } if checker.enabled(Rule::RaiseWithoutFromInsideExcept) { flake8_bugbear::rules::raise_without_from_inside_except( diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index de027ff99e3fd7..e6de35139836be 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1,8 +1,6 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Operator}; use ruff_python_literal::cformat::{CFormatError, CFormatErrorType}; -use ruff_diagnostics::Diagnostic; - use ruff_python_ast::types::Node; use ruff_python_semantic::ScopeKind; use ruff_python_semantic::analyze::typing; @@ -195,14 +193,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { let check_too_many_expressions = checker.enabled(Rule::ExpressionsInStarAssignment); let check_two_starred_expressions = checker.enabled(Rule::MultipleStarredExpressions); - if let Some(diagnostic) = pyflakes::rules::starred_expressions( + pyflakes::rules::starred_expressions( + checker, elts, check_too_many_expressions, check_two_starred_expressions, expr.range(), - ) { - checker.report_diagnostic(diagnostic); - } + ); } } Expr::Name(ast::ExprName { id, ctx, range }) => { @@ -527,12 +524,12 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { match pyflakes::format::FormatSummary::try_from(string_value.to_str()) { Err(e) => { if checker.enabled(Rule::StringDotFormatInvalidFormat) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::StringDotFormatInvalidFormat { message: pyflakes::format::error_to_string(&e), }, location, - )); + ); } } Ok(summary) => { @@ -936,9 +933,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { pylint::rules::repeated_keyword_argument(checker, call); } if checker.enabled(Rule::PytestPatchWithLambda) { - if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(call) { - checker.report_diagnostic(diagnostic); - } + flake8_pytest_style::rules::patch_with_lambda(checker, call); } if checker.any_enabled(&[ Rule::PytestParametrizeNamesWrongType, @@ -1286,22 +1281,22 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { .. }) => { if checker.enabled(Rule::PercentFormatUnsupportedFormatCharacter) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::PercentFormatUnsupportedFormatCharacter { char: c, }, location, - )); + ); } } Err(e) => { if checker.enabled(Rule::PercentFormatInvalidFormat) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::PercentFormatInvalidFormat { message: e.to_string(), }, location, - )); + ); } } Ok(summary) => { @@ -1365,10 +1360,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { op: Operator::Add, .. }) => { if checker.enabled(Rule::ExplicitStringConcatenation) { - if let Some(diagnostic) = flake8_implicit_str_concat::rules::explicit(expr, checker) - { - checker.report_diagnostic(diagnostic); - } + flake8_implicit_str_concat::rules::explicit(checker, expr); } if checker.enabled(Rule::CollectionLiteralConcatenation) { ruff::rules::collection_literal_concatenation(checker, expr); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 059c831a171d37..69f7dfd8d8dc4f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_python_ast::helpers; use ruff_python_ast::types::Node; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -39,12 +38,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if !checker.semantic.scope_id.is_global() { for name in names { if checker.semantic.nonlocal(name).is_none() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pylint::rules::NonlocalWithoutBinding { name: name.to_string(), }, name.range(), - )); + ); } } } @@ -55,22 +54,20 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } Stmt::Break(_) => { if checker.enabled(Rule::BreakOutsideLoop) { - if let Some(diagnostic) = pyflakes::rules::break_outside_loop( + pyflakes::rules::break_outside_loop( + checker, stmt, &mut checker.semantic.current_statements().skip(1), - ) { - checker.report_diagnostic(diagnostic); - } + ); } } Stmt::Continue(_) => { if checker.enabled(Rule::ContinueOutsideLoop) { - if let Some(diagnostic) = pyflakes::rules::continue_outside_loop( + pyflakes::rules::continue_outside_loop( + checker, stmt, &mut checker.semantic.current_statements().skip(1), - ) { - checker.report_diagnostic(diagnostic); - } + ); } } Stmt::FunctionDef( @@ -98,9 +95,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { fastapi::rules::fastapi_unused_path_parameter(checker, function_def); } if checker.enabled(Rule::AmbiguousFunctionName) { - if let Some(diagnostic) = pycodestyle::rules::ambiguous_function_name(name) { - checker.report_diagnostic(diagnostic); - } + pycodestyle::rules::ambiguous_function_name(checker, name); } if checker.enabled(Rule::InvalidBoolReturnType) { pylint::rules::invalid_bool_return(checker, function_def); @@ -121,15 +116,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::invalid_str_return(checker, function_def); } if checker.enabled(Rule::InvalidFunctionName) { - if let Some(diagnostic) = pep8_naming::rules::invalid_function_name( + pep8_naming::rules::invalid_function_name( + checker, stmt, name, decorator_list, &checker.settings.pep8_naming.ignore_names, &checker.semantic, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.source_type.is_stub() { if checker.enabled(Rule::PassStatementStubBody) { @@ -179,14 +173,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { flake8_pyi::rules::pep_484_positional_parameter(checker, function_def); } if checker.enabled(Rule::DunderFunctionName) { - if let Some(diagnostic) = pep8_naming::rules::dunder_function_name( + pep8_naming::rules::dunder_function_name( + checker, checker.semantic.current_scope(), stmt, name, &checker.settings.pep8_naming.ignore_names, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::GlobalStatement) { pylint::rules::global_statement(checker, name); @@ -231,14 +224,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { ); } if checker.enabled(Rule::ComplexStructure) { - if let Some(diagnostic) = mccabe::rules::function_is_too_complex( + mccabe::rules::function_is_too_complex( + checker, stmt, name, body, checker.settings.mccabe.max_complexity, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::HardcodedPasswordDefault) { flake8_bandit::rules::hardcoded_password_default(checker, parameters); @@ -258,31 +250,28 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::too_many_positional_arguments(checker, function_def); } if checker.enabled(Rule::TooManyReturnStatements) { - if let Some(diagnostic) = pylint::rules::too_many_return_statements( + pylint::rules::too_many_return_statements( + checker, stmt, body, checker.settings.pylint.max_returns, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::TooManyBranches) { - if let Some(diagnostic) = pylint::rules::too_many_branches( + pylint::rules::too_many_branches( + checker, stmt, body, checker.settings.pylint.max_branches, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::TooManyStatements) { - if let Some(diagnostic) = pylint::rules::too_many_statements( + pylint::rules::too_many_statements( + checker, stmt, body, checker.settings.pylint.max_statements, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.any_enabled(&[ Rule::PytestFixtureIncorrectParenthesesStyle, @@ -451,28 +440,24 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pyupgrade::rules::unnecessary_class_parentheses(checker, class_def); } if checker.enabled(Rule::AmbiguousClassName) { - if let Some(diagnostic) = pycodestyle::rules::ambiguous_class_name(name) { - checker.report_diagnostic(diagnostic); - } + pycodestyle::rules::ambiguous_class_name(checker, name); } if checker.enabled(Rule::InvalidClassName) { - if let Some(diagnostic) = pep8_naming::rules::invalid_class_name( + pep8_naming::rules::invalid_class_name( + checker, stmt, name, &checker.settings.pep8_naming.ignore_names, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::ErrorSuffixOnExceptionName) { - if let Some(diagnostic) = pep8_naming::rules::error_suffix_on_exception_name( + pep8_naming::rules::error_suffix_on_exception_name( + checker, stmt, arguments.as_deref(), name, &checker.settings.pep8_naming.ignore_names, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if !checker.source_type.is_stub() { if checker.any_enabled(&[ @@ -611,11 +596,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } if checker.enabled(Rule::Debugger) { - if let Some(diagnostic) = - flake8_debugger::rules::debugger_import(stmt, None, &alias.name) - { - checker.report_diagnostic(diagnostic); - } + flake8_debugger::rules::debugger_import(checker, stmt, None, &alias.name); } if checker.enabled(Rule::BannedApi) { flake8_tidy_imports::rules::banned_api( @@ -638,94 +619,74 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::manual_from_import(checker, stmt, alias, names); } if checker.enabled(Rule::ImportSelf) { - if let Some(diagnostic) = - pylint::rules::import_self(alias, checker.module.qualified_name()) - { - checker.report_diagnostic(diagnostic); - } + pylint::rules::import_self(checker, alias, checker.module.qualified_name()); } if let Some(asname) = &alias.asname { let name = alias.name.split('.').next_back().unwrap(); if checker.enabled(Rule::ConstantImportedAsNonConstant) { - if let Some(diagnostic) = - pep8_naming::rules::constant_imported_as_non_constant( - name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::constant_imported_as_non_constant( + checker, + name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::LowercaseImportedAsNonLowercase) { - if let Some(diagnostic) = - pep8_naming::rules::lowercase_imported_as_non_lowercase( - name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::lowercase_imported_as_non_lowercase( + checker, + name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::CamelcaseImportedAsLowercase) { - if let Some(diagnostic) = - pep8_naming::rules::camelcase_imported_as_lowercase( - name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::camelcase_imported_as_lowercase( + checker, + name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::CamelcaseImportedAsConstant) { - if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_constant( + pep8_naming::rules::camelcase_imported_as_constant( + checker, name, asname, alias, stmt, &checker.settings.pep8_naming.ignore_names, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::CamelcaseImportedAsAcronym) { - if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_acronym( + pep8_naming::rules::camelcase_imported_as_acronym( name, asname, alias, stmt, checker, - ) { - checker.report_diagnostic(diagnostic); - } + ); } } if checker.enabled(Rule::BannedImportAlias) { if let Some(asname) = &alias.asname { - if let Some(diagnostic) = - flake8_import_conventions::rules::banned_import_alias( - stmt, - &alias.name, - asname, - &checker.settings.flake8_import_conventions.banned_aliases, - ) - { - checker.report_diagnostic(diagnostic); - } + flake8_import_conventions::rules::banned_import_alias( + checker, + stmt, + &alias.name, + asname, + &checker.settings.flake8_import_conventions.banned_aliases, + ); } } if checker.enabled(Rule::PytestIncorrectPytestImport) { - if let Some(diagnostic) = flake8_pytest_style::rules::import( + flake8_pytest_style::rules::import( + checker, stmt, &alias.name, alias.asname.as_deref(), - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::BuiltinImportShadowing) { flake8_builtins::rules::builtin_import_shadowing(checker, alias); @@ -837,11 +798,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } if checker.enabled(Rule::PytestIncorrectPytestImport) { - if let Some(diagnostic) = - flake8_pytest_style::rules::import_from(stmt, module, level) - { - checker.report_diagnostic(diagnostic); - } + flake8_pytest_style::rules::import_from(checker, stmt, module, level); } if checker.source_type.is_stub() { if checker.enabled(Rule::FutureAnnotationsInStub) { @@ -856,119 +813,98 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } else if &alias.name == "*" { if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) { if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::UndefinedLocalWithNestedImportStarUsage { name: helpers::format_import_from(level, module).to_string(), }, stmt.range(), - )); + ); } } if checker.enabled(Rule::UndefinedLocalWithImportStar) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::UndefinedLocalWithImportStar { name: helpers::format_import_from(level, module).to_string(), }, stmt.range(), - )); + ); } } if checker.enabled(Rule::RelativeImports) { - if let Some(diagnostic) = flake8_tidy_imports::rules::banned_relative_import( + flake8_tidy_imports::rules::banned_relative_import( checker, stmt, level, module, checker.module.qualified_name(), checker.settings.flake8_tidy_imports.ban_relative_imports, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::Debugger) { - if let Some(diagnostic) = - flake8_debugger::rules::debugger_import(stmt, module, &alias.name) - { - checker.report_diagnostic(diagnostic); - } + flake8_debugger::rules::debugger_import(checker, stmt, module, &alias.name); } if checker.enabled(Rule::BannedImportAlias) { if let Some(asname) = &alias.asname { let qualified_name = helpers::format_import_from_member(level, module, &alias.name); - if let Some(diagnostic) = - flake8_import_conventions::rules::banned_import_alias( - stmt, - &qualified_name, - asname, - &checker.settings.flake8_import_conventions.banned_aliases, - ) - { - checker.report_diagnostic(diagnostic); - } + flake8_import_conventions::rules::banned_import_alias( + checker, + stmt, + &qualified_name, + asname, + &checker.settings.flake8_import_conventions.banned_aliases, + ); } } if let Some(asname) = &alias.asname { if checker.enabled(Rule::ConstantImportedAsNonConstant) { - if let Some(diagnostic) = - pep8_naming::rules::constant_imported_as_non_constant( - &alias.name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::constant_imported_as_non_constant( + checker, + &alias.name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::LowercaseImportedAsNonLowercase) { - if let Some(diagnostic) = - pep8_naming::rules::lowercase_imported_as_non_lowercase( - &alias.name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::lowercase_imported_as_non_lowercase( + checker, + &alias.name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::CamelcaseImportedAsLowercase) { - if let Some(diagnostic) = - pep8_naming::rules::camelcase_imported_as_lowercase( - &alias.name, - asname, - alias, - stmt, - &checker.settings.pep8_naming.ignore_names, - ) - { - checker.report_diagnostic(diagnostic); - } + pep8_naming::rules::camelcase_imported_as_lowercase( + checker, + &alias.name, + asname, + alias, + stmt, + &checker.settings.pep8_naming.ignore_names, + ); } if checker.enabled(Rule::CamelcaseImportedAsConstant) { - if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_constant( + pep8_naming::rules::camelcase_imported_as_constant( + checker, &alias.name, asname, alias, stmt, &checker.settings.pep8_naming.ignore_names, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::CamelcaseImportedAsAcronym) { - if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_acronym( + pep8_naming::rules::camelcase_imported_as_acronym( &alias.name, asname, alias, stmt, checker, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if !checker.source_type.is_stub() { if checker.enabled(Rule::UselessImportAlias) { @@ -981,23 +917,21 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } if checker.enabled(Rule::ImportSelf) { - if let Some(diagnostic) = pylint::rules::import_from_self( + pylint::rules::import_from_self( + checker, level, module, names, checker.module.qualified_name(), - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::BannedImportFrom) { - if let Some(diagnostic) = flake8_import_conventions::rules::banned_import_from( + flake8_import_conventions::rules::banned_import_from( + checker, stmt, &helpers::format_import_from(level, module), &checker.settings.flake8_import_conventions.banned_from, - ) { - checker.report_diagnostic(diagnostic); - } + ); } if checker.enabled(Rule::ByteStringUsage) { flake8_pyi::rules::bytestring_import(checker, import_from); @@ -1212,7 +1146,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { ) => { if !checker.semantic.in_type_checking_block() { if checker.enabled(Rule::Assert) { - checker.report_diagnostic(flake8_bandit::rules::assert_used(stmt)); + flake8_bandit::rules::assert_used(checker, stmt); } } if checker.enabled(Rule::AssertTuple) { @@ -1409,11 +1343,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } if checker.enabled(Rule::DefaultExceptNotLast) { - if let Some(diagnostic) = - pyflakes::rules::default_except_not_last(handlers, checker.locator) - { - checker.report_diagnostic(diagnostic); - } + pyflakes::rules::default_except_not_last(checker, handlers, checker.locator); } if checker.any_enabled(&[ Rule::DuplicateHandlerException, @@ -1508,9 +1438,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { ); } if checker.enabled(Rule::PandasDfVariableName) { - if let Some(diagnostic) = pandas_vet::rules::assignment_to_df(targets) { - checker.report_diagnostic(diagnostic); - } + pandas_vet::rules::assignment_to_df(checker, targets); } if checker .settings @@ -1704,11 +1632,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::named_expr_without_context(checker, value); } if checker.enabled(Rule::AsyncioDanglingTask) { - if let Some(diagnostic) = - ruff::rules::asyncio_dangling_task(value, checker.semantic()) - { - checker.report_diagnostic(diagnostic); - } + ruff::rules::asyncio_dangling_task(checker, value, checker.semantic()); } if checker.enabled(Rule::RepeatedAppend) { refurb::rules::repeated_append(checker, stmt); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs b/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs index dbe2c540ef4d8d..3060e07f636519 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_python_semantic::Exceptions; use ruff_python_stdlib::builtins::version_builtin_was_added; @@ -15,12 +14,12 @@ pub(crate) fn unresolved_references(checker: &Checker) { for reference in checker.semantic.unresolved_references() { if reference.is_wildcard_import() { if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::UndefinedLocalWithImportStarUsage { name: reference.name(checker.source()).to_string(), }, reference.range(), - )); + ); } } else { if checker.enabled(Rule::UndefinedName) { @@ -42,13 +41,13 @@ pub(crate) fn unresolved_references(checker: &Checker) { let symbol_name = reference.name(checker.source()); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( pyflakes::rules::UndefinedName { name: symbol_name.to_string(), minor_version_builtin_added: version_builtin_was_added(symbol_name), }, reference.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 694ff08a87e61a..92783663c03904 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -31,7 +31,7 @@ use ruff_python_parser::semantic_errors::{ }; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Edit, IsolationLevel}; +use ruff_diagnostics::{Diagnostic, Edit, IsolationLevel, Violation}; use ruff_notebook::{CellOffsets, NotebookIndex}; use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path}; use ruff_python_ast::identifier::Identifier; @@ -66,7 +66,7 @@ use crate::importer::{ImportRequest, Importer, ResolutionError}; use crate::noqa::NoqaMapping; use crate::package::PackageRoot; use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled}; -use crate::registry::Rule; +use crate::registry::{AsRule, Rule}; use crate::rules::pyflakes::rules::{ LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction, }; @@ -379,10 +379,40 @@ impl<'a> Checker<'a> { self.indexer.comment_ranges() } - /// Push a new [`Diagnostic`] to the collection in the [`Checker`] - pub(crate) fn report_diagnostic(&self, diagnostic: Diagnostic) { - let mut diagnostics = self.diagnostics.borrow_mut(); - diagnostics.push(diagnostic); + /// Return a [`DiagnosticGuard`] for reporting a diagnostic. + /// + /// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic + /// before it is added to the collection in the checker on `Drop`. + pub(crate) fn report_diagnostic<'chk, T: Violation>( + &'chk self, + kind: T, + range: TextRange, + ) -> DiagnosticGuard<'chk, 'a> { + DiagnosticGuard { + checker: self, + diagnostic: Some(Diagnostic::new(kind, range)), + } + } + + /// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is + /// enabled. + /// + /// Prefer [`Checker::report_diagnostic`] in general because the conversion from a `Diagnostic` + /// to a `Rule` is somewhat expensive. + pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>( + &'chk self, + kind: T, + range: TextRange, + ) -> Option> { + let diagnostic = Diagnostic::new(kind, range); + if self.enabled(diagnostic.rule()) { + Some(DiagnosticGuard { + checker: self, + diagnostic: Some(diagnostic), + }) + } else { + None + } } /// Adds a [`TextRange`] to the set of ranges of variable names @@ -508,9 +538,9 @@ impl<'a> Checker<'a> { } /// Push `diagnostic` if the checker is not in a `@no_type_check` context. - pub(crate) fn report_type_diagnostic(&self, diagnostic: Diagnostic) { + pub(crate) fn report_type_diagnostic(&self, kind: T, range: TextRange) { if !self.semantic.in_no_type_check() { - self.report_diagnostic(diagnostic); + self.report_diagnostic(kind, range); } } @@ -595,7 +625,7 @@ impl SemanticSyntaxContext for Checker<'_> { match error.kind { SemanticSyntaxErrorKind::LateFutureImport => { if self.settings.rules.enabled(Rule::LateFutureImport) { - self.report_diagnostic(Diagnostic::new(LateFutureImport, error.range)); + self.report_diagnostic(LateFutureImport, error.range); } } SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { name, start } => { @@ -604,31 +634,28 @@ impl SemanticSyntaxContext for Checker<'_> { .rules .enabled(Rule::LoadBeforeGlobalDeclaration) { - self.report_diagnostic(Diagnostic::new( + self.report_diagnostic( LoadBeforeGlobalDeclaration { name, row: self.compute_source_row(start), }, error.range, - )); + ); } } SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => { if self.settings.rules.enabled(Rule::YieldOutsideFunction) { - self.report_diagnostic(Diagnostic::new( - YieldOutsideFunction::new(kind), - error.range, - )); + self.report_diagnostic(YieldOutsideFunction::new(kind), error.range); } } SemanticSyntaxErrorKind::ReturnOutsideFunction => { if self.settings.rules.enabled(Rule::ReturnOutsideFunction) { - self.report_diagnostic(Diagnostic::new(ReturnOutsideFunction, error.range)); + self.report_diagnostic(ReturnOutsideFunction, error.range); } } SemanticSyntaxErrorKind::AwaitOutsideAsyncFunction(_) => { if self.settings.rules.enabled(Rule::AwaitOutsideAsync) { - self.report_diagnostic(Diagnostic::new(AwaitOutsideAsync, error.range)); + self.report_diagnostic(AwaitOutsideAsync, error.range); } } SemanticSyntaxErrorKind::ReboundComprehensionVariable @@ -2717,12 +2744,12 @@ impl<'a> Checker<'a> { self.semantic.restore(snapshot); if self.enabled(Rule::ForwardAnnotationSyntaxError) { - self.report_type_diagnostic(Diagnostic::new( + self.report_type_diagnostic( pyflakes::rules::ForwardAnnotationSyntaxError { parse_error: parse_error.error.to_string(), }, string_expr.range(), - )); + ); } } } @@ -3020,3 +3047,59 @@ pub(crate) fn check_ast( (diagnostics.into_inner(), semantic_errors.into_inner()) } + +/// An abstraction for mutating a diagnostic. +/// +/// Callers can build this guard by starting with `Checker::report_diagnostic`. +/// +/// The primary function of this guard is to add the underlying diagnostic to the `Checker`'s list +/// of diagnostics on `Drop`, while dereferencing to the underlying diagnostic for mutations like +/// adding fixes or parent ranges. +pub(crate) struct DiagnosticGuard<'a, 'b> { + /// The parent checker that will receive the diagnostic on `Drop`. + checker: &'a Checker<'b>, + /// The diagnostic that we want to report. + /// + /// This is always `Some` until the `Drop` (or `defuse`) call. + diagnostic: Option, +} + +impl DiagnosticGuard<'_, '_> { + /// Consume the underlying `Diagnostic` without emitting it. + /// + /// In general you should avoid constructing diagnostics that may not be emitted, but this + /// method can be used where this is unavoidable. + pub(crate) fn defuse(mut self) { + self.diagnostic = None; + } +} + +impl std::ops::Deref for DiagnosticGuard<'_, '_> { + type Target = Diagnostic; + + fn deref(&self) -> &Diagnostic { + // OK because `self.diagnostic` is only `None` within `Drop`. + self.diagnostic.as_ref().unwrap() + } +} + +/// Return a mutable borrow of the diagnostic in this guard. +impl std::ops::DerefMut for DiagnosticGuard<'_, '_> { + fn deref_mut(&mut self) -> &mut Diagnostic { + // OK because `self.diagnostic` is only `None` within `Drop`. + self.diagnostic.as_mut().unwrap() + } +} + +impl Drop for DiagnosticGuard<'_, '_> { + fn drop(&mut self) { + if std::thread::panicking() { + // Don't submit diagnostics when panicking because they might be incomplete. + return; + } + + if let Some(diagnostic) = self.diagnostic.take() { + self.checker.diagnostics.borrow_mut().push(diagnostic); + } + } +} diff --git a/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs b/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs index 5263ed963bb231..3413544dedc545 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::{self as ast}; @@ -86,6 +86,5 @@ pub(crate) fn dag_no_schedule_argument(checker: &Checker, expr: &Expr) { } // Produce a diagnostic when the `schedule` keyword argument is not found. - let diagnostic = Diagnostic::new(AirflowDagNoScheduleArgument, expr.range()); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(AirflowDagNoScheduleArgument, expr.range()); } diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index 1a9c7433d6bb82..81841674314f2e 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -1,7 +1,8 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -28,12 +29,12 @@ use crate::checkers::ast::Checker; /// from airflow.providers.fab.auth_manager.fab_auth_manage import FabAuthManager /// ``` #[derive(ViolationMetadata)] -pub(crate) struct Airflow3MovedToProvider { - deprecated: String, +pub(crate) struct Airflow3MovedToProvider<'a> { + deprecated: QualifiedName<'a>, replacement: ProviderReplacement, } -impl Violation for Airflow3MovedToProvider { +impl Violation for Airflow3MovedToProvider<'_> { const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] @@ -1197,34 +1198,42 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan _ => return, }; - let mut diagnostic = Diagnostic::new( - Airflow3MovedToProvider { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - ranged.range(), - ); - - let semantic = checker.semantic(); - if let Some((module, name)) = match &replacement { - ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)), + let (module, name) = match &replacement { + ProviderReplacement::AutoImport { module, name, .. } => (module, *name), ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { - Some((module, name.as_str())) + (module, name.as_str()) } - ProviderReplacement::None => None, - } { - if is_guarded_by_try_except(expr, module, name, semantic) { + ProviderReplacement::None => { + checker.report_diagnostic( + Airflow3MovedToProvider { + deprecated: qualified_name, + replacement, + }, + ranged, + ); return; } - diagnostic.try_set_fix(|| { + }; + + if is_guarded_by_try_except(expr, module, name, checker.semantic()) { + return; + } + + checker + .report_diagnostic( + Airflow3MovedToProvider { + deprecated: qualified_name, + replacement: replacement.clone(), + }, + ranged, + ) + .try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), expr.start(), checker.semantic(), )?; - let replacement_edit = Edit::range_replacement(binding, ranged.range()); + let replacement_edit = Edit::range_replacement(binding, ranged); Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); - } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index 7a77228221cd5c..3c551f4a1d9961 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -3,7 +3,7 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, }; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{ @@ -166,13 +166,13 @@ fn check_function_parameters(checker: &Checker, function_def: &StmtFunctionDef) for param in function_def.parameters.iter_non_variadic_params() { let param_name = param.name(); if REMOVED_CONTEXT_KEYS.contains(¶m_name.as_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Airflow3Removal { deprecated: param_name.to_string(), replacement: Replacement::None, }, param_name.range(), - )); + ); } } } @@ -200,7 +200,7 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum segments => { if is_airflow_auth_manager(segments) { if !arguments.is_empty() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Airflow3Removal { deprecated: String::from("appbuilder"), replacement: Replacement::Message( @@ -208,7 +208,7 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum ), }, arguments.range(), - )); + ); } } else if is_airflow_task_handler(segments) { diagnostic_for_argument(checker, arguments, "filename_template", None); @@ -305,7 +305,7 @@ fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) { } else { None }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( Airflow3Removal { deprecated: attr.to_string(), replacement, @@ -315,7 +315,6 @@ fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) { if let Some(fix) = fix { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } /// Checks whether an Airflow 3.0–removed context key is used in a function decorated with `@task`. @@ -382,13 +381,13 @@ fn check_context_key_usage_in_call(checker: &Checker, call_expr: &ExprCall) { continue; }; if value == removed_key { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Airflow3Removal { deprecated: removed_key.to_string(), replacement: Replacement::None, }, *range, - )); + ); } } } @@ -423,13 +422,13 @@ fn check_context_key_usage_in_subscript(checker: &Checker, subscript: &ExprSubsc } if REMOVED_CONTEXT_KEYS.contains(&key.to_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Airflow3Removal { deprecated: key.to_string(), replacement: Replacement::None, }, slice.range(), - )); + ); } } @@ -537,7 +536,7 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) { None }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( Airflow3Removal { deprecated: attr.to_string(), replacement, @@ -547,7 +546,6 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) { if let Some(fix) = fix { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } /// Check whether a removed Airflow name is used. @@ -966,26 +964,36 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { _ => return, }; - let mut diagnostic = Diagnostic::new( - Airflow3Removal { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - range, - ); - let semantic = checker.semantic(); - if let Some((module, name)) = match &replacement { - Replacement::AutoImport { module, name } => Some((module, *name)), - Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())), - _ => None, - } { - if is_guarded_by_try_except(expr, module, name, semantic) { + let (module, name) = match &replacement { + Replacement::AutoImport { module, name } => (module, *name), + Replacement::SourceModuleMoved { module, name } => (module, name.as_str()), + _ => { + checker.report_diagnostic( + Airflow3Removal { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ); return; } + }; - let import_target = name.split('.').next().unwrap_or(name); + if is_guarded_by_try_except(expr, module, name, checker.semantic()) { + return; + } + + let import_target = name.split('.').next().unwrap_or(name); - diagnostic.try_set_fix(|| { + checker + .report_diagnostic( + Airflow3Removal { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ) + .try_set_fix(|| { let (import_edit, _) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, import_target), expr.start(), @@ -994,9 +1002,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { let replacement_edit = Edit::range_replacement(name.to_string(), range); Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); - } - - checker.report_diagnostic(diagnostic); } /// Check whether a customized Airflow plugin contains removed extensions. @@ -1028,7 +1033,7 @@ fn check_airflow_plugin_extension( ) }) }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Airflow3Removal { deprecated: name.to_string(), replacement: Replacement::Message( @@ -1036,7 +1041,7 @@ fn check_airflow_plugin_extension( ), }, expr.range(), - )); + ); } } } @@ -1052,7 +1057,11 @@ fn diagnostic_for_argument( let Some(keyword) = arguments.find_keyword(deprecated) else { return; }; - let mut diagnostic = Diagnostic::new( + let range = keyword + .arg + .as_ref() + .map_or_else(|| keyword.range(), Ranged::range); + let mut diagnostic = checker.report_diagnostic( Airflow3Removal { deprecated: deprecated.to_string(), replacement: match replacement { @@ -1060,20 +1069,15 @@ fn diagnostic_for_argument( None => Replacement::None, }, }, - keyword - .arg - .as_ref() - .map_or_else(|| keyword.range(), Ranged::range), + range, ); if let Some(replacement) = replacement { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( replacement.to_string(), - diagnostic.range, + range, ))); } - - checker.report_diagnostic(diagnostic); } /// Check whether the symbol is coming from the `secrets` builtin or provider module which ends diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index b5db71486e22f6..a6815fa5a93540 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -1,8 +1,9 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -30,12 +31,12 @@ use crate::checkers::ast::Checker; /// from airflow.providers.standard.operators.python import PythonOperator /// ``` #[derive(ViolationMetadata)] -pub(crate) struct Airflow3SuggestedToMoveToProvider { - deprecated: String, +pub(crate) struct Airflow3SuggestedToMoveToProvider<'a> { + deprecated: QualifiedName<'a>, replacement: ProviderReplacement, } -impl Violation for Airflow3SuggestedToMoveToProvider { +impl Violation for Airflow3SuggestedToMoveToProvider<'_> { const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] fn message(&self) -> String { @@ -281,26 +282,36 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan _ => return, }; - let mut diagnostic = Diagnostic::new( - Airflow3SuggestedToMoveToProvider { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - ranged.range(), - ); - - let semantic = checker.semantic(); - if let Some((module, name)) = match &replacement { - ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)), + let (module, name) = match &replacement { + ProviderReplacement::AutoImport { module, name, .. } => (module, *name), ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => { - Some((module, name.as_str())) + (module, name.as_str()) } - ProviderReplacement::None => None, - } { - if is_guarded_by_try_except(expr, module, name, semantic) { + ProviderReplacement::None => { + checker.report_diagnostic( + Airflow3SuggestedToMoveToProvider { + deprecated: qualified_name, + replacement: replacement.clone(), + }, + ranged.range(), + ); return; } - diagnostic.try_set_fix(|| { + }; + + if is_guarded_by_try_except(expr, module, name, checker.semantic()) { + return; + } + + checker + .report_diagnostic( + Airflow3SuggestedToMoveToProvider { + deprecated: qualified_name, + replacement: replacement.clone(), + }, + ranged.range(), + ) + .try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), expr.start(), @@ -309,7 +320,4 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan let replacement_edit = Edit::range_replacement(binding, ranged.range()); Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); - } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index 0bc310f3fe27d3..f3286a83506e97 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -3,7 +3,7 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, }; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, Expr, ExprAttribute, ExprCall, ExprName, name::QualifiedName}; use ruff_python_semantic::Modules; @@ -120,7 +120,11 @@ fn diagnostic_for_argument( let Some(keyword) = arguments.find_keyword(deprecated) else { return; }; - let mut diagnostic = Diagnostic::new( + let range = keyword + .arg + .as_ref() + .map_or_else(|| keyword.range(), Ranged::range); + let mut diagnostic = checker.report_diagnostic( Airflow3SuggestedUpdate { deprecated: deprecated.to_string(), replacement: match replacement { @@ -128,20 +132,15 @@ fn diagnostic_for_argument( None => Replacement::None, }, }, - keyword - .arg - .as_ref() - .map_or_else(|| keyword.range(), Ranged::range), + range, ); if let Some(replacement) = replacement { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( replacement.to_string(), - diagnostic.range, + range, ))); } - - checker.report_diagnostic(diagnostic); } /// Check whether a removed Airflow argument is passed. /// @@ -284,24 +283,34 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { _ => return, }; - let mut diagnostic = Diagnostic::new( - Airflow3SuggestedUpdate { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - range, - ); - - let semantic = checker.semantic(); - if let Some((module, name)) = match &replacement { - Replacement::AutoImport { module, name } => Some((module, *name)), - Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())), - _ => None, - } { - if is_guarded_by_try_except(expr, module, name, semantic) { + let (module, name) = match &replacement { + Replacement::AutoImport { module, name } => (module, *name), + Replacement::SourceModuleMoved { module, name } => (module, name.as_str()), + _ => { + checker.report_diagnostic( + Airflow3SuggestedUpdate { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ); return; } - diagnostic.try_set_fix(|| { + }; + + if is_guarded_by_try_except(expr, module, name, checker.semantic()) { + return; + } + + checker + .report_diagnostic( + Airflow3SuggestedUpdate { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ) + .try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(module, name), expr.start(), @@ -310,7 +319,4 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { let replacement_edit = Edit::range_replacement(binding, range); Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); - } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs index 72e62d15bc2c6a..5470014187d9e4 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -110,11 +110,10 @@ pub(crate) fn variable_name_task_id(checker: &Checker, targets: &[Expr], value: return; } - let diagnostic = Diagnostic::new( + checker.report_diagnostic( AirflowVariableNameTaskIdMismatch { task_id: task_id.to_string(), }, target.range(), ); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs index 2ef280bd2e11ef..b2221cfbd541f0 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_callable; @@ -237,7 +237,7 @@ fn create_diagnostic( return seen_default; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( FastApiNonAnnotatedDependency { py_version: checker.target_version(), }, @@ -308,7 +308,5 @@ fn create_diagnostic( } diagnostic.try_set_optional_fix(|| fix); - checker.report_diagnostic(diagnostic); - seen_default } diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs index ab38db93aedf87..96a663eaea58e2 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Decorator, Expr, ExprCall, Keyword, StmtFunctionDef}; use ruff_python_semantic::{Modules, SemanticModel}; @@ -85,7 +85,7 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def: continue; }; let mut diagnostic = - Diagnostic::new(FastApiRedundantResponseModel, response_model_arg.range()); + checker.report_diagnostic(FastApiRedundantResponseModel, response_model_arg.range()); diagnostic.try_set_fix(|| { remove_argument( response_model_arg, @@ -95,7 +95,6 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def: ) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs index 286617ccdf6823..42220b3dc8909e 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs @@ -3,7 +3,7 @@ use std::ops::Range; use std::str::CharIndices; use ruff_diagnostics::Fix; -use ruff_diagnostics::{Diagnostic, FixAvailability, Violation}; +use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault}; @@ -186,7 +186,7 @@ pub(crate) fn fastapi_unused_path_parameter( .iter() .any(|param| param.name() == path_param); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( FastApiUnusedPathParameter { arg_name: path_param.to_string(), function_name: function_def.name.to_string(), @@ -204,7 +204,6 @@ pub(crate) fn fastapi_unused_path_parameter( checker.locator().contents(), ))); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs index 359be0dbcb7949..a83fe0c288edbb 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_text_size::Ranged; @@ -254,12 +254,12 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators ) = (ops, comparators) { if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SysVersionInfo0Eq3 { eq: matches!(*operator, CmpOp::Eq), }, left.range(), - )); + ); } } } else if *i == 1 { @@ -274,10 +274,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators ) = (ops, comparators) { if checker.enabled(Rule::SysVersionInfo1CmpInt) { - checker.report_diagnostic(Diagnostic::new( - SysVersionInfo1CmpInt, - left.range(), - )); + checker.report_diagnostic(SysVersionInfo1CmpInt, left.range()); } } } @@ -298,10 +295,7 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators ) = (ops, comparators) { if checker.enabled(Rule::SysVersionInfoMinorCmpInt) { - checker.report_diagnostic(Diagnostic::new( - SysVersionInfoMinorCmpInt, - left.range(), - )); + checker.report_diagnostic(SysVersionInfoMinorCmpInt, left.range()); } } } @@ -317,10 +311,10 @@ pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators { if value.len() == 1 { if checker.enabled(Rule::SysVersionCmpStr10) { - checker.report_diagnostic(Diagnostic::new(SysVersionCmpStr10, left.range())); + checker.report_diagnostic(SysVersionCmpStr10, left.range()); } } else if checker.enabled(Rule::SysVersionCmpStr3) { - checker.report_diagnostic(Diagnostic::new(SysVersionCmpStr3, left.range())); + checker.report_diagnostic(SysVersionCmpStr3, left.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs index bcf610d3552514..fdf240834add03 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -56,6 +56,6 @@ pub(crate) fn name_or_attribute(checker: &Checker, expr: &Expr) { .resolve_qualified_name(expr) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["six", "PY3"])) { - checker.report_diagnostic(Diagnostic::new(SixPY3, expr.range())); + checker.report_diagnostic(SixPY3, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs index 0ed2feb2481791..e9f90c85d44f0e 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -183,9 +183,9 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, slice: &Expr) { }) = upper.as_ref() { if *i == 1 && checker.enabled(Rule::SysVersionSlice1) { - checker.report_diagnostic(Diagnostic::new(SysVersionSlice1, value.range())); + checker.report_diagnostic(SysVersionSlice1, value.range()); } else if *i == 3 && checker.enabled(Rule::SysVersionSlice3) { - checker.report_diagnostic(Diagnostic::new(SysVersionSlice3, value.range())); + checker.report_diagnostic(SysVersionSlice3, value.range()); } } } @@ -195,9 +195,9 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, slice: &Expr) { .. }) => { if *i == 2 && checker.enabled(Rule::SysVersion2) { - checker.report_diagnostic(Diagnostic::new(SysVersion2, value.range())); + checker.report_diagnostic(SysVersion2, value.range()); } else if *i == 0 && checker.enabled(Rule::SysVersion0) { - checker.report_diagnostic(Diagnostic::new(SysVersion0, value.range())); + checker.report_diagnostic(SysVersion0, value.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs index 1f75ec28a1ea68..333845c7e292fc 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,7 +9,7 @@ use ruff_python_semantic::analyze::visibility; use ruff_python_stdlib::typing::simple_magic_return_type; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::registry::Rule; use crate::rules::flake8_annotations::helpers::auto_return_type; use crate::rules::ruff::typing::type_hint_resolves_to_any; @@ -529,11 +529,11 @@ fn is_none_returning(body: &[Stmt]) -> bool { } /// ANN401 -fn check_dynamically_typed( - checker: &Checker, +fn check_dynamically_typed<'a, 'b, F>( + checker: &'a Checker<'b>, annotation: &Expr, func: F, - diagnostics: &mut Vec, + diagnostics: &mut Vec>, ) where F: FnOnce() -> String, { @@ -545,18 +545,14 @@ fn check_dynamically_typed( checker, checker.target_version(), ) { - diagnostics.push(Diagnostic::new( - AnyType { name: func() }, - annotation.range(), - )); + diagnostics + .push(checker.report_diagnostic(AnyType { name: func() }, annotation.range())); } } } else { if type_hint_resolves_to_any(annotation, checker, checker.target_version()) { - diagnostics.push(Diagnostic::new( - AnyType { name: func() }, - annotation.range(), - )); + diagnostics + .push(checker.report_diagnostic(AnyType { name: func() }, annotation.range())); } } } @@ -655,7 +651,7 @@ pub(crate) fn definition( .is_match(parameter.name())) { if checker.enabled(Rule::MissingTypeFunctionArgument) { - diagnostics.push(Diagnostic::new( + diagnostics.push(checker.report_diagnostic( MissingTypeFunctionArgument { name: parameter.name().to_string(), }, @@ -681,7 +677,7 @@ pub(crate) fn definition( && checker.settings.dummy_variable_rgx.is_match(&arg.name)) { if checker.enabled(Rule::MissingTypeArgs) { - diagnostics.push(Diagnostic::new( + diagnostics.push(checker.report_diagnostic( MissingTypeArgs { name: arg.name.to_string(), }, @@ -712,7 +708,7 @@ pub(crate) fn definition( && checker.settings.dummy_variable_rgx.is_match(&arg.name)) { if checker.enabled(Rule::MissingTypeKwargs) { - diagnostics.push(Diagnostic::new( + diagnostics.push(checker.report_diagnostic( MissingTypeKwargs { name: arg.name.to_string(), }, @@ -745,7 +741,7 @@ pub(crate) fn definition( }) .map(|(return_type, edits)| (checker.generator().expr(&return_type), edits)) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypeClassMethod { name: name.to_string(), annotation: return_type.clone().map(|(return_type, ..)| return_type), @@ -771,7 +767,7 @@ pub(crate) fn definition( }) .map(|(return_type, edits)| (checker.generator().expr(&return_type), edits)) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypeStaticMethod { name: name.to_string(), annotation: return_type.clone().map(|(return_type, ..)| return_type), @@ -791,7 +787,7 @@ pub(crate) fn definition( // least one argument is typed. if checker.enabled(Rule::MissingReturnTypeSpecialMethod) { if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypeSpecialMethod { name: name.to_string(), annotation: Some("None".to_string()), @@ -808,7 +804,7 @@ pub(crate) fn definition( } else if is_method && visibility::is_magic(name) { if checker.enabled(Rule::MissingReturnTypeSpecialMethod) { let return_type = simple_magic_return_type(name); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypeSpecialMethod { name: name.to_string(), annotation: return_type.map(ToString::to_string), @@ -839,7 +835,7 @@ pub(crate) fn definition( (checker.generator().expr(&return_type), edits) }) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypeUndocumentedPublicFunction { name: name.to_string(), annotation: return_type @@ -874,7 +870,7 @@ pub(crate) fn definition( (checker.generator().expr(&return_type), edits) }) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingReturnTypePrivateFunction { name: name.to_string(), annotation: return_type @@ -901,7 +897,7 @@ pub(crate) fn definition( // If settings say so, don't report any of the // diagnostics gathered here if there were no type annotations at all. - if !checker.settings.flake8_annotations.ignore_fully_untyped + let diagnostics_enabled = !checker.settings.flake8_annotations.ignore_fully_untyped || has_any_typed_arg || has_typed_return || (is_method @@ -910,10 +906,11 @@ pub(crate) fn definition( .posonlyargs .first() .or_else(|| parameters.args.first()) - .is_some_and(|first_param| first_param.annotation().is_some())) - { + .is_some_and(|first_param| first_param.annotation().is_some())); + + if !diagnostics_enabled { for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); + diagnostic.defuse(); } } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs index 60ab11c874bec7..9d5980c54852bb 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::Ranged; @@ -74,11 +74,11 @@ pub(crate) fn async_busy_wait(checker: &Checker, while_stmt: &ast::StmtWhile) { qualified_name.segments(), ["trio" | "anyio", "sleep" | "sleep_until"] | ["asyncio", "sleep"] ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( AsyncBusyWait { module: AsyncModule::try_from(&qualified_name).unwrap(), }, while_stmt.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs index 009aa42c77cb1b..3703ded95a27fa 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -112,8 +112,5 @@ pub(crate) fn async_function_with_timeout(checker: &Checker, function_def: &ast: return; } - checker.report_diagnostic(Diagnostic::new( - AsyncFunctionWithTimeout { module }, - timeout.range(), - )); + checker.report_diagnostic(AsyncFunctionWithTimeout { module }, timeout.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index 87a6ef841a2dae..890cadd0e4d477 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; use ruff_python_semantic::Modules; @@ -109,7 +109,7 @@ pub(crate) fn async_zero_sleep(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(AsyncZeroSleep { module }, call.range()); + let mut diagnostic = checker.report_diagnostic(AsyncZeroSleep { module }, call.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from(&module.to_string(), "lowlevel"), @@ -121,6 +121,5 @@ pub(crate) fn async_zero_sleep(checker: &Checker, call: &ExprCall) { let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index 747913352c01ab..ba99ea539ddd68 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -1,6 +1,6 @@ use ruff_python_ast::ExprCall; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; @@ -70,10 +70,7 @@ pub(crate) fn blocking_http_call(checker: &Checker, call: &ExprCall) { .as_ref() .is_some_and(is_blocking_http_call) { - checker.report_diagnostic(Diagnostic::new( - BlockingHttpCallInAsyncFunction, - call.func.range(), - )); + checker.report_diagnostic(BlockingHttpCallInAsyncFunction, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index 621f87cec5125f..99c9d23bb613a2 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{SemanticModel, analyze}; @@ -52,10 +52,7 @@ pub(crate) fn blocking_open_call(checker: &Checker, call: &ast::ExprCall) { if is_open_call(&call.func, checker.semantic()) || is_open_call_from_pathlib(call.func.as_ref(), checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( - BlockingOpenCallInAsyncFunction, - call.func.range(), - )); + checker.report_diagnostic(BlockingOpenCallInAsyncFunction, call.func.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index e9d6cb7ff2439a..fffd7d69139399 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -6,7 +6,6 @@ use ruff_python_semantic::analyze::typing::find_assigned_value; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::registry::AsRule; /// ## What it does /// Checks that async functions do not create subprocesses with blocking methods. @@ -125,17 +124,17 @@ pub(crate) fn blocking_process_invocation(checker: &Checker, call: &ast::ExprCal }; let range = call.func.range(); - let diagnostic = match qualified_name.segments() { + match qualified_name.segments() { ["subprocess", "Popen"] | ["os", "popen"] => { - Diagnostic::new(CreateSubprocessInAsyncFunction, range) + checker.report_diagnostic_if_enabled(CreateSubprocessInAsyncFunction, range) } ["os", "system" | "posix_spawn" | "posix_spawnp"] | [ "subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput" | "getstatusoutput", - ] => Diagnostic::new(RunProcessInAsyncFunction, range), + ] => checker.report_diagnostic_if_enabled(RunProcessInAsyncFunction, range), ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] => { - Diagnostic::new(WaitForProcessInAsyncFunction, range) + checker.report_diagnostic_if_enabled(WaitForProcessInAsyncFunction, range) } [ "os", @@ -143,17 +142,13 @@ pub(crate) fn blocking_process_invocation(checker: &Checker, call: &ast::ExprCal | "spawnvpe", ] => { if is_p_wait(call, checker.semantic()) { - Diagnostic::new(RunProcessInAsyncFunction, range) + checker.report_diagnostic_if_enabled(RunProcessInAsyncFunction, range) } else { - Diagnostic::new(CreateSubprocessInAsyncFunction, range) + checker.report_diagnostic_if_enabled(CreateSubprocessInAsyncFunction, range) } } _ => return, }; - - if checker.enabled(diagnostic.rule()) { - checker.report_diagnostic(diagnostic); - } } fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs index c625318796a9b7..693ec58d3c8079 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs @@ -1,6 +1,6 @@ use ruff_python_ast::ExprCall; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; @@ -51,10 +51,7 @@ pub(crate) fn blocking_sleep(checker: &Checker, call: &ExprCall) { .as_ref() .is_some_and(is_blocking_sleep) { - checker.report_diagnostic(Diagnostic::new( - BlockingSleepInAsyncFunction, - call.func.range(), - )); + checker.report_diagnostic(BlockingSleepInAsyncFunction, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs index 9ebf0ae2e2994b..80c65c2265f687 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{AwaitVisitor, any_over_body}; use ruff_python_ast::visitor::Visitor; @@ -100,8 +100,5 @@ pub(crate) fn cancel_scope_no_checkpoint( return; } - checker.report_diagnostic(Diagnostic::new( - CancelScopeNoCheckpoint { method_name }, - with_stmt.range, - )); + checker.report_diagnostic(CancelScopeNoCheckpoint { method_name }, with_stmt.range); } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs index 3e31062672aa6c..e91ec3615efd70 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, ExprNumberLiteral, Number}; use ruff_python_semantic::Modules; @@ -137,7 +137,7 @@ pub(crate) fn long_sleep_not_forever(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(LongSleepNotForever { module }, call.range()); + let mut diagnostic = checker.report_diagnostic(LongSleepNotForever { module }, call.range()); let replacement_function = "sleep_forever"; diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( @@ -149,5 +149,4 @@ pub(crate) fn long_sleep_not_forever(checker: &Checker, call: &ExprCall) { let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); Ok(Fix::unsafe_edits(import_edit, [reference_edit, arg_edit])) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs index fc93b806bcf963..ee0c301a4bb576 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall}; use ruff_python_semantic::Modules; @@ -80,7 +80,7 @@ pub(crate) fn sync_call(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(TrioSyncCall { method_name }, call.range); + let mut diagnostic = checker.report_diagnostic(TrioSyncCall { method_name }, call.range); if checker.semantic().in_async_context() { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( pad( @@ -91,5 +91,4 @@ pub(crate) fn sync_call(checker: &Checker, call: &ExprCall) { call.func.start(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs index 2001ef772712b0..6ea3b04fa9a630 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs @@ -1,10 +1,12 @@ use ruff_python_ast::Stmt; use ruff_text_size::{TextLen, TextRange}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for uses of the `assert` keyword. /// @@ -41,6 +43,6 @@ impl Violation for Assert { } /// S101 -pub(crate) fn assert_used(stmt: &Stmt) -> Diagnostic { - Diagnostic::new(Assert, TextRange::at(stmt.start(), "assert".text_len())) +pub(crate) fn assert_used(checker: &Checker, stmt: &Stmt) { + checker.report_diagnostic(Assert, TextRange::at(stmt.start(), "assert".text_len())); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs index 0033504e2d385e..c99774341fc6f1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr, Operator}; @@ -78,22 +78,22 @@ pub(crate) fn bad_file_permissions(checker: &Checker, call: &ast::ExprCall) { // The mask is a valid integer value -- check for overly permissive permissions. Ok(Some(mask)) => { if (mask & WRITE_WORLD > 0) || (mask & EXECUTE_GROUP > 0) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadFilePermissions { reason: Reason::Permissive(mask), }, mode_arg.range(), - )); + ); } } // The mask is an invalid integer value (i.e., it's out of range). Err(_) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadFilePermissions { reason: Reason::Invalid, }, mode_arg.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs index 41686ce957b3ff..9051f9db47bf91 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute}; use ruff_text_size::Ranged; @@ -54,7 +54,7 @@ pub(crate) fn django_extra(checker: &Checker, call: &ast::ExprCall) { } if is_call_insecure(call) { - checker.report_diagnostic(Diagnostic::new(DjangoExtra, call.arguments.range())); + checker.report_diagnostic(DjangoExtra, call.arguments.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs index c4ddc9decd9a69..7876dca2c93be3 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; @@ -55,7 +55,7 @@ pub(crate) fn django_raw_sql(checker: &Checker, call: &ast::ExprCall) { .find_argument_value("sql", 0) .is_some_and(Expr::is_string_literal_expr) { - checker.report_diagnostic(Diagnostic::new(DjangoRawSql, call.func.range())); + checker.report_diagnostic(DjangoRawSql, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs index ee23e38be2a2b6..a495231702ce74 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -34,6 +34,6 @@ impl Violation for ExecBuiltin { /// S102 pub(crate) fn exec_used(checker: &Checker, func: &Expr) { if checker.semantic().match_builtin_expr(func, "exec") { - checker.report_diagnostic(Diagnostic::new(ExecBuiltin, func.range())); + checker.report_diagnostic(ExecBuiltin, func.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs index 01a3f63ae2e9fa..6ab094b6778d4a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; @@ -67,6 +67,6 @@ pub(crate) fn flask_debug_true(checker: &Checker, call: &ExprCall) { if typing::resolve_assignment(value, checker.semantic()) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["flask", "Flask"])) { - checker.report_diagnostic(Diagnostic::new(FlaskDebugTrue, debug_argument.range())); + checker.report_diagnostic(FlaskDebugTrue, debug_argument.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs index 2b8c743fea6e92..6a448047488170 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::Ranged; @@ -41,8 +41,7 @@ pub(crate) fn hardcoded_bind_all_interfaces(checker: &Checker, string: StringLik match string { StringLike::String(ast::ExprStringLiteral { value, .. }) => { if value == "0.0.0.0" { - checker - .report_diagnostic(Diagnostic::new(HardcodedBindAllInterfaces, string.range())); + checker.report_diagnostic(HardcodedBindAllInterfaces, string.range()); } } StringLike::FString(ast::ExprFString { value, .. }) => { @@ -50,19 +49,14 @@ pub(crate) fn hardcoded_bind_all_interfaces(checker: &Checker, string: StringLik match part { ast::FStringPart::Literal(literal) => { if &**literal == "0.0.0.0" { - checker.report_diagnostic(Diagnostic::new( - HardcodedBindAllInterfaces, - literal.range(), - )); + checker.report_diagnostic(HardcodedBindAllInterfaces, literal.range()); } } ast::FStringPart::FString(f_string) => { for literal in f_string.elements.literals() { if &**literal == "0.0.0.0" { - checker.report_diagnostic(Diagnostic::new( - HardcodedBindAllInterfaces, - literal.range(), - )); + checker + .report_diagnostic(HardcodedBindAllInterfaces, literal.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs index 08070b92220dc7..9cc404fba8f8d4 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, Parameter, Parameters}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -54,18 +54,20 @@ impl Violation for HardcodedPasswordDefault { } } -fn check_password_kwarg(parameter: &Parameter, default: &Expr) -> Option { - string_literal(default).filter(|string| !string.is_empty())?; +fn check_password_kwarg(checker: &Checker, parameter: &Parameter, default: &Expr) { + if string_literal(default).is_none_or(str::is_empty) { + return; + } let kwarg_name = ¶meter.name; if !matches_password_name(kwarg_name) { - return None; + return; } - Some(Diagnostic::new( + checker.report_diagnostic( HardcodedPasswordDefault { name: kwarg_name.to_string(), }, default.range(), - )) + ); } /// S107 @@ -74,8 +76,6 @@ pub(crate) fn hardcoded_password_default(checker: &Checker, parameters: &Paramet let Some(default) = parameter.default() else { continue; }; - if let Some(diagnostic) = check_password_kwarg(¶meter.parameter, default) { - checker.report_diagnostic(diagnostic); - } + check_password_kwarg(checker, ¶meter.parameter, default); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs index c242d36832ec78..a6aacb3c5ec964 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Keyword; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -62,11 +62,11 @@ pub(crate) fn hardcoded_password_func_arg(checker: &Checker, keywords: &[Keyword if !matches_password_name(arg) { continue; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HardcodedPasswordFuncArg { name: arg.to_string(), }, keyword.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs index 6ea4ab32be82d6..317dcad0c598bf 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -83,12 +83,12 @@ pub(crate) fn compare_to_hardcoded_password_string( let Some(name) = password_target(left) else { continue; }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HardcodedPasswordString { name: name.to_string(), }, comp.range(), - )); + ); } } @@ -100,12 +100,12 @@ pub(crate) fn assign_hardcoded_password_string(checker: &Checker, value: &Expr, { for target in targets { if let Some(name) = password_target(target) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HardcodedPasswordString { name: name.to_string(), }, value.range(), - )); + ); return; } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index 70470fd977a69f..8fa2cbf8dc037c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -2,7 +2,7 @@ use std::sync::LazyLock; use regex::Regex; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::raw_contents; use ruff_python_ast::{self as ast, Expr, Operator}; @@ -113,7 +113,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) { }; if SQL_REGEX.is_match(&content) { - checker.report_diagnostic(Diagnostic::new(HardcodedSQLExpression, expr.range())); + checker.report_diagnostic(HardcodedSQLExpression, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs index 35b35c850f6e8d..88086ca8bf7975 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr, StringLike}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -102,10 +102,10 @@ fn check(checker: &Checker, value: &str, range: TextRange) { } } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HardcodedTempFile { string: value.to_string(), }, range, - )); + ); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs index 8fdfdd702f8f6f..035bb4c89288b6 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_false; use ruff_python_ast::{self as ast, Arguments}; @@ -141,23 +141,23 @@ fn detect_insecure_hashlib_calls( hash_func_name.to_ascii_lowercase().as_str(), "md4" | "md5" | "sha" | "sha1" ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HashlibInsecureHashFunction { library: "hashlib".to_string(), string: hash_func_name.to_string(), }, name_arg.range(), - )); + ); } } HashlibCall::WeakHash(func_name) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HashlibInsecureHashFunction { library: "hashlib".to_string(), string: (*func_name).to_string(), }, call.func.range(), - )); + ); } } } @@ -186,13 +186,13 @@ fn detect_insecure_crypt_calls(checker: &Checker, call: &ast::ExprCall) { qualified_name.segments(), ["crypt", "METHOD_CRYPT" | "METHOD_MD5" | "METHOD_BLOWFISH"] ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( HashlibInsecureHashFunction { library: "crypt".to_string(), string: qualified_name.to_string(), }, method.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs index 100a15d40ea31a..14f96de490b83b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -70,23 +70,20 @@ pub(crate) fn jinja2_autoescape_false(checker: &Checker, call: &ast::ExprCall) { Expr::Call(ast::ExprCall { func, .. }) => { if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() { if id != "select_autoescape" { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( Jinja2AutoescapeFalse { value: true }, keyword.range(), - )); + ); } } } - _ => checker.report_diagnostic(Diagnostic::new( - Jinja2AutoescapeFalse { value: true }, - keyword.range(), - )), + _ => { + checker + .report_diagnostic(Jinja2AutoescapeFalse { value: true }, keyword.range()); + } } } else { - checker.report_diagnostic(Diagnostic::new( - Jinja2AutoescapeFalse { value: false }, - call.func.range(), - )); + checker.report_diagnostic(Jinja2AutoescapeFalse { value: false }, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs index 95e92641953faa..48aab788dd0e29 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; @@ -51,9 +51,6 @@ pub(crate) fn logging_config_insecure_listen(checker: &Checker, call: &ast::Expr return; } - checker.report_diagnostic(Diagnostic::new( - LoggingConfigInsecureListen, - call.func.range(), - )); + checker.report_diagnostic(LoggingConfigInsecureListen, call.func.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs index ecf38b92ddc24a..8ca83f4abb417b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -50,6 +50,6 @@ pub(crate) fn mako_templates(checker: &Checker, call: &ast::ExprCall) { matches!(qualified_name.segments(), ["mako", "template", "Template"]) }) { - checker.report_diagnostic(Diagnostic::new(MakoTemplates, call.func.range())); + checker.report_diagnostic(MakoTemplates, call.func.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs index bc76d74ebc0123..45f553d694ab7b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -45,6 +45,6 @@ pub(crate) fn paramiko_call(checker: &Checker, func: &Expr) { matches!(qualified_name.segments(), ["paramiko", "exec_command"]) }) { - checker.report_diagnostic(Diagnostic::new(ParamikoCall, func.range())); + checker.report_diagnostic(ParamikoCall, func.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs index abb0f034b278ae..c6745ebb8b0153 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::is_const_false; @@ -65,12 +65,12 @@ pub(crate) fn request_with_no_cert_validation(checker: &Checker, call: &ast::Exp { if let Some(keyword) = call.arguments.find_keyword("verify") { if is_const_false(&keyword.value) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RequestWithNoCertValidation { string: target.to_string(), }, keyword.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs index 12a553d9b578c7..7a27c805a24707 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -70,22 +70,22 @@ pub(crate) fn request_without_timeout(checker: &Checker, call: &ast::ExprCall) { { if let Some(keyword) = call.arguments.find_keyword("timeout") { if keyword.value.is_none_literal_expr() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RequestWithoutTimeout { implicit: false, module: module.to_string(), }, keyword.range(), - )); + ); } } else if module == "requests" { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RequestWithoutTimeout { implicit: true, module: module.to_string(), }, call.func.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs index 89972bca1a780b..5b578a8e5cbd9c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs @@ -1,7 +1,7 @@ //! Checks relating to shell injection. use crate::preview::is_shell_injection_only_trusted_input_enabled; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -314,13 +314,13 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy), }) => { if checker.enabled(Rule::SubprocessPopenWithShellEqualsTrue) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SubprocessPopenWithShellEqualsTrue { safety: Safety::from(arg), is_exact: matches!(truthiness, Truthiness::True), }, call.func.range(), - )); + ); } } // S603 @@ -329,10 +329,10 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { || !is_shell_injection_only_trusted_input_enabled(checker.settings) { if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SubprocessWithoutShellEqualsTrue, call.func.range(), - )); + ); } } } @@ -344,12 +344,12 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { { // S604 if checker.enabled(Rule::CallWithShellEqualsTrue) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( CallWithShellEqualsTrue { is_exact: matches!(truthiness, Truthiness::True), }, call.func.range(), - )); + ); } } @@ -357,12 +357,12 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { if checker.enabled(Rule::StartProcessWithAShell) { if matches!(call_kind, Some(CallKind::Shell)) { if let Some(arg) = call.arguments.args.first() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( StartProcessWithAShell { safety: Safety::from(arg), }, call.func.range(), - )); + ); } } } @@ -370,7 +370,7 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { // S606 if checker.enabled(Rule::StartProcessWithNoShell) { if matches!(call_kind, Some(CallKind::NoShell)) { - checker.report_diagnostic(Diagnostic::new(StartProcessWithNoShell, call.func.range())); + checker.report_diagnostic(StartProcessWithNoShell, call.func.range()); } } @@ -379,10 +379,7 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { if call_kind.is_some() { if let Some(arg) = call.arguments.args.first() { if is_partial_path(arg) { - checker.report_diagnostic(Diagnostic::new( - StartProcessWithPartialPath, - arg.range(), - )); + checker.report_diagnostic(StartProcessWithPartialPath, arg.range()); } } } @@ -403,10 +400,7 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { { if let Some(arg) = call.arguments.args.first() { if is_wildcard_command(arg) { - checker.report_diagnostic(Diagnostic::new( - UnixCommandWildcardInjection, - arg.range(), - )); + checker.report_diagnostic(UnixCommandWildcardInjection, arg.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs index 82365a74bf929f..28164d334c4e98 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Int}; use ruff_text_size::Ranged; @@ -60,7 +60,7 @@ pub(crate) fn snmp_insecure_version(checker: &Checker, call: &ast::ExprCall) { .. }) ) { - checker.report_diagnostic(Diagnostic::new(SnmpInsecureVersion, keyword.range())); + checker.report_diagnostic(SnmpInsecureVersion, keyword.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs index f33fdbc8fce532..e89b2f0d606df7 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -52,7 +52,7 @@ pub(crate) fn snmp_weak_cryptography(checker: &Checker, call: &ast::ExprCall) { ) }) { - checker.report_diagnostic(Diagnostic::new(SnmpWeakCryptography, call.func.range())); + checker.report_diagnostic(SnmpWeakCryptography, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs index 7956608f397ff9..c109f7c09e285e 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; @@ -78,9 +78,6 @@ pub(crate) fn ssh_no_host_key_verification(checker: &Checker, call: &ExprCall) { ["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"] ) }) { - checker.report_diagnostic(Diagnostic::new( - SSHNoHostKeyVerification, - policy_argument.range(), - )); + checker.report_diagnostic(SSHNoHostKeyVerification, policy_argument.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs index 70bddf995a968e..886733befdd118 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_text_size::Ranged; @@ -68,22 +68,22 @@ pub(crate) fn ssl_insecure_version(checker: &Checker, call: &ExprCall) { match &keyword.value { Expr::Name(ast::ExprName { id, .. }) => { if is_insecure_protocol(id) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SslInsecureVersion { protocol: id.to_string(), }, keyword.range(), - )); + ); } } Expr::Attribute(ast::ExprAttribute { attr, .. }) => { if is_insecure_protocol(attr) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SslInsecureVersion { protocol: attr.to_string(), }, keyword.range(), - )); + ); } } _ => {} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs index ab8367a7d2c5aa..82c6362b7b2478 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, StmtFunctionDef}; @@ -57,22 +57,22 @@ pub(crate) fn ssl_with_bad_defaults(checker: &Checker, function_def: &StmtFuncti match default { Expr::Name(ast::ExprName { id, range, .. }) => { if is_insecure_protocol(id.as_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SslWithBadDefaults { protocol: id.to_string(), }, *range, - )); + ); } } Expr::Attribute(ast::ExprAttribute { attr, range, .. }) => { if is_insecure_protocol(attr.as_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SslWithBadDefaults { protocol: attr.to_string(), }, *range, - )); + ); } } _ => {} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs index 854c8f19cc8049..9bedb2eeb635fe 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_text_size::Ranged; @@ -43,7 +43,7 @@ pub(crate) fn ssl_with_no_version(checker: &Checker, call: &ExprCall) { .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["ssl", "wrap_socket"])) { if call.arguments.find_keyword("ssl_version").is_none() { - checker.report_diagnostic(Diagnostic::new(SslWithNoVersion, call.range())); + checker.report_diagnostic(SslWithNoVersion, call.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index c7886e8bcf1130..6302b767c2eada 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -2,14 +2,13 @@ //! //! See: use itertools::Either; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::preview::is_suspicious_function_reference_enabled; -use crate::registry::AsRule; /// ## What it does /// Checks for calls to `pickle` functions or modules that wrap them. @@ -1035,16 +1034,20 @@ fn suspicious_function( return; }; - let diagnostic = match qualified_name.segments() { + match qualified_name.segments() { // Pickle ["pickle" | "dill", "load" | "loads" | "Unpickler"] | ["shelve", "open" | "DbfilenameShelf"] | ["jsonpickle", "decode"] | ["jsonpickle", "unpickler", "decode"] - | ["pandas", "read_pickle"] => Diagnostic::new(SuspiciousPickleUsage, range), + | ["pandas", "read_pickle"] => { + checker.report_diagnostic_if_enabled(SuspiciousPickleUsage, range) + } // Marshal - ["marshal", "load" | "loads"] => Diagnostic::new(SuspiciousMarshalUsage, range), + ["marshal", "load" | "loads"] => { + checker.report_diagnostic_if_enabled(SuspiciousMarshalUsage, range) + } // InsecureHash [ @@ -1059,7 +1062,7 @@ fn suspicious_function( "primitives", "hashes", "SHA1" | "MD5", - ] => Diagnostic::new(SuspiciousInsecureHashUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousInsecureHashUsage, range), // InsecureCipher [ @@ -1075,7 +1078,7 @@ fn suspicious_function( "ciphers", "algorithms", "ARC4" | "Blowfish" | "IDEA", - ] => Diagnostic::new(SuspiciousInsecureCipherUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherUsage, range), // InsecureCipherMode [ @@ -1085,13 +1088,17 @@ fn suspicious_function( "ciphers", "modes", "ECB", - ] => Diagnostic::new(SuspiciousInsecureCipherModeUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherModeUsage, range), // Mktemp - ["tempfile", "mktemp"] => Diagnostic::new(SuspiciousMktempUsage, range), + ["tempfile", "mktemp"] => { + checker.report_diagnostic_if_enabled(SuspiciousMktempUsage, range) + } // Eval - ["" | "builtins", "eval"] => Diagnostic::new(SuspiciousEvalUsage, range), + ["" | "builtins", "eval"] => { + checker.report_diagnostic_if_enabled(SuspiciousEvalUsage, range) + } // MarkSafe ["django", "utils", "safestring" | "html", "mark_safe"] => { @@ -1102,7 +1109,7 @@ fn suspicious_function( } } } - Diagnostic::new(SuspiciousMarkSafeUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousMarkSafeUsage, range) } // URLOpen (`Request`) @@ -1124,7 +1131,7 @@ fn suspicious_function( } } } - Diagnostic::new(SuspiciousURLOpenUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousURLOpenUsage, range) } // URLOpen (`urlopen`, `urlretrieve`) @@ -1176,7 +1183,7 @@ fn suspicious_function( } } } - Diagnostic::new(SuspiciousURLOpenUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousURLOpenUsage, range) } // URLOpen (`URLopener`, `FancyURLopener`) @@ -1187,18 +1194,18 @@ fn suspicious_function( "urllib", "request", "URLopener" | "FancyURLopener", - ] => Diagnostic::new(SuspiciousURLOpenUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousURLOpenUsage, range), // NonCryptographicRandom [ "random", "Random" | "random" | "randrange" | "randint" | "choice" | "choices" | "uniform" | "triangular" | "randbytes", - ] => Diagnostic::new(SuspiciousNonCryptographicRandomUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousNonCryptographicRandomUsage, range), // UnverifiedContext ["ssl", "_create_unverified_context"] => { - Diagnostic::new(SuspiciousUnverifiedContextUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousUnverifiedContextUsage, range) } // XMLCElementTree @@ -1207,7 +1214,7 @@ fn suspicious_function( "etree", "cElementTree", "parse" | "iterparse" | "fromstring" | "XMLParser", - ] => Diagnostic::new(SuspiciousXMLCElementTreeUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousXMLCElementTreeUsage, range), // XMLElementTree [ @@ -1215,31 +1222,31 @@ fn suspicious_function( "etree", "ElementTree", "parse" | "iterparse" | "fromstring" | "XMLParser", - ] => Diagnostic::new(SuspiciousXMLElementTreeUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousXMLElementTreeUsage, range), // XMLExpatReader ["xml", "sax", "expatreader", "create_parser"] => { - Diagnostic::new(SuspiciousXMLExpatReaderUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousXMLExpatReaderUsage, range) } // XMLExpatBuilder ["xml", "dom", "expatbuilder", "parse" | "parseString"] => { - Diagnostic::new(SuspiciousXMLExpatBuilderUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousXMLExpatBuilderUsage, range) } // XMLSax ["xml", "sax", "parse" | "parseString" | "make_parser"] => { - Diagnostic::new(SuspiciousXMLSaxUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousXMLSaxUsage, range) } // XMLMiniDOM ["xml", "dom", "minidom", "parse" | "parseString"] => { - Diagnostic::new(SuspiciousXMLMiniDOMUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousXMLMiniDOMUsage, range) } // XMLPullDOM ["xml", "dom", "pulldom", "parse" | "parseString"] => { - Diagnostic::new(SuspiciousXMLPullDOMUsage, range) + checker.report_diagnostic_if_enabled(SuspiciousXMLPullDOMUsage, range) } // XMLETree @@ -1248,20 +1255,16 @@ fn suspicious_function( "etree", "parse" | "fromstring" | "RestrictedElement" | "GlobalParserTLS" | "getDefaultParser" | "check_docinfo", - ] => Diagnostic::new(SuspiciousXMLETreeUsage, range), + ] => checker.report_diagnostic_if_enabled(SuspiciousXMLETreeUsage, range), // Telnet - ["telnetlib", ..] => Diagnostic::new(SuspiciousTelnetUsage, range), + ["telnetlib", ..] => checker.report_diagnostic_if_enabled(SuspiciousTelnetUsage, range), // FTPLib - ["ftplib", ..] => Diagnostic::new(SuspiciousFTPLibUsage, range), + ["ftplib", ..] => checker.report_diagnostic_if_enabled(SuspiciousFTPLibUsage, range), _ => return, }; - - if checker.enabled(diagnostic.rule()) { - checker.report_diagnostic(diagnostic); - } } /// S308 diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs index 5b3522c56ed07b..1951141db0f7a9 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs @@ -1,13 +1,12 @@ //! Check for imports of or from suspicious modules. //! //! See: -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::registry::AsRule; /// ## What it does /// Checks for imports of the `telnetlib` module. @@ -362,42 +361,47 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { for name in names { match name.name.as_str() { "telnetlib" => { - check_and_push_diagnostic(checker, SuspiciousTelnetlibImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousTelnetlibImport, name.range); } "ftplib" => { - check_and_push_diagnostic(checker, SuspiciousFtplibImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousFtplibImport, name.range); } "pickle" | "cPickle" | "dill" | "shelve" => { - check_and_push_diagnostic(checker, SuspiciousPickleImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousPickleImport, name.range); } "subprocess" => { - check_and_push_diagnostic(checker, SuspiciousSubprocessImport, name.range); + checker + .report_diagnostic_if_enabled(SuspiciousSubprocessImport, name.range); } "xml.etree.cElementTree" | "xml.etree.ElementTree" => { - check_and_push_diagnostic(checker, SuspiciousXmlEtreeImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousXmlEtreeImport, name.range); } "xml.sax" => { - check_and_push_diagnostic(checker, SuspiciousXmlSaxImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousXmlSaxImport, name.range); } "xml.dom.expatbuilder" => { - check_and_push_diagnostic(checker, SuspiciousXmlExpatImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousXmlExpatImport, name.range); } "xml.dom.minidom" => { - check_and_push_diagnostic(checker, SuspiciousXmlMinidomImport, name.range); + checker + .report_diagnostic_if_enabled(SuspiciousXmlMinidomImport, name.range); } "xml.dom.pulldom" => { - check_and_push_diagnostic(checker, SuspiciousXmlPulldomImport, name.range); + checker + .report_diagnostic_if_enabled(SuspiciousXmlPulldomImport, name.range); + } + "lxml" => { + checker.report_diagnostic_if_enabled(SuspiciousLxmlImport, name.range); } - "lxml" => check_and_push_diagnostic(checker, SuspiciousLxmlImport, name.range), "xmlrpc" => { - check_and_push_diagnostic(checker, SuspiciousXmlrpcImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousXmlrpcImport, name.range); } "Crypto.Cipher" | "Crypto.Hash" | "Crypto.IO" | "Crypto.Protocol" | "Crypto.PublicKey" | "Crypto.Random" | "Crypto.Signature" | "Crypto.Util" => { - check_and_push_diagnostic(checker, SuspiciousPycryptoImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousPycryptoImport, name.range); } "pyghmi" => { - check_and_push_diagnostic(checker, SuspiciousPyghmiImport, name.range); + checker.report_diagnostic_if_enabled(SuspiciousPyghmiImport, name.range); } _ => {} } @@ -406,27 +410,30 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => { let Some(identifier) = module else { return }; match identifier.as_str() { - "telnetlib" => check_and_push_diagnostic( - checker, - SuspiciousTelnetlibImport, - identifier.range(), - ), + "telnetlib" => { + checker.report_diagnostic_if_enabled( + SuspiciousTelnetlibImport, + identifier.range(), + ); + } "ftplib" => { - check_and_push_diagnostic(checker, SuspiciousFtplibImport, identifier.range()); + checker + .report_diagnostic_if_enabled(SuspiciousFtplibImport, identifier.range()); } "pickle" | "cPickle" | "dill" | "shelve" => { - check_and_push_diagnostic(checker, SuspiciousPickleImport, identifier.range()); + checker + .report_diagnostic_if_enabled(SuspiciousPickleImport, identifier.range()); + } + "subprocess" => { + checker.report_diagnostic_if_enabled( + SuspiciousSubprocessImport, + identifier.range(), + ); } - "subprocess" => check_and_push_diagnostic( - checker, - SuspiciousSubprocessImport, - identifier.range(), - ), "xml.etree" => { for name in names { if matches!(name.name.as_str(), "cElementTree" | "ElementTree") { - check_and_push_diagnostic( - checker, + checker.report_diagnostic_if_enabled( SuspiciousXmlEtreeImport, identifier.range(), ); @@ -434,17 +441,13 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { } } "xml.etree.cElementTree" | "xml.etree.ElementTree" => { - check_and_push_diagnostic( - checker, - SuspiciousXmlEtreeImport, - identifier.range(), - ); + checker + .report_diagnostic_if_enabled(SuspiciousXmlEtreeImport, identifier.range()); } "xml" => { for name in names { if name.name.as_str() == "sax" { - check_and_push_diagnostic( - checker, + checker.report_diagnostic_if_enabled( SuspiciousXmlSaxImport, identifier.range(), ); @@ -452,58 +455,61 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { } } "xml.sax" => { - check_and_push_diagnostic(checker, SuspiciousXmlSaxImport, identifier.range()); + checker + .report_diagnostic_if_enabled(SuspiciousXmlSaxImport, identifier.range()); } "xml.dom" => { for name in names { match name.name.as_str() { - "expatbuilder" => check_and_push_diagnostic( - checker, - SuspiciousXmlExpatImport, - identifier.range(), - ), - "minidom" => check_and_push_diagnostic( - checker, - SuspiciousXmlMinidomImport, - identifier.range(), - ), - "pulldom" => check_and_push_diagnostic( - checker, - SuspiciousXmlPulldomImport, - identifier.range(), - ), - _ => (), + "expatbuilder" => { + checker.report_diagnostic_if_enabled( + SuspiciousXmlExpatImport, + identifier.range(), + ); + } + "minidom" => { + checker.report_diagnostic_if_enabled( + SuspiciousXmlMinidomImport, + identifier.range(), + ); + } + "pulldom" => { + checker.report_diagnostic_if_enabled( + SuspiciousXmlPulldomImport, + identifier.range(), + ); + } + _ => {} } } } "xml.dom.expatbuilder" => { - check_and_push_diagnostic( - checker, - SuspiciousXmlExpatImport, + checker + .report_diagnostic_if_enabled(SuspiciousXmlExpatImport, identifier.range()); + } + "xml.dom.minidom" => { + checker.report_diagnostic_if_enabled( + SuspiciousXmlMinidomImport, + identifier.range(), + ); + } + "xml.dom.pulldom" => { + checker.report_diagnostic_if_enabled( + SuspiciousXmlPulldomImport, identifier.range(), ); } - "xml.dom.minidom" => check_and_push_diagnostic( - checker, - SuspiciousXmlMinidomImport, - identifier.range(), - ), - "xml.dom.pulldom" => check_and_push_diagnostic( - checker, - SuspiciousXmlPulldomImport, - identifier.range(), - ), "lxml" => { - check_and_push_diagnostic(checker, SuspiciousLxmlImport, identifier.range()); + checker.report_diagnostic_if_enabled(SuspiciousLxmlImport, identifier.range()); } "xmlrpc" => { - check_and_push_diagnostic(checker, SuspiciousXmlrpcImport, identifier.range()); + checker + .report_diagnostic_if_enabled(SuspiciousXmlrpcImport, identifier.range()); } "wsgiref.handlers" => { for name in names { if name.name.as_str() == "CGIHandler" { - check_and_push_diagnostic( - checker, + checker.report_diagnostic_if_enabled( SuspiciousHttpoxyImport, identifier.range(), ); @@ -513,8 +519,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { "twisted.web.twcgi" => { for name in names { if name.name.as_str() == "CGIScript" { - check_and_push_diagnostic( - checker, + checker.report_diagnostic_if_enabled( SuspiciousHttpoxyImport, identifier.range(), ); @@ -534,8 +539,7 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { | "Signature" | "Util" ) { - check_and_push_diagnostic( - checker, + checker.report_diagnostic_if_enabled( SuspiciousPycryptoImport, identifier.range(), ); @@ -544,14 +548,12 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { } "Crypto.Cipher" | "Crypto.Hash" | "Crypto.IO" | "Crypto.Protocol" | "Crypto.PublicKey" | "Crypto.Random" | "Crypto.Signature" | "Crypto.Util" => { - check_and_push_diagnostic( - checker, - SuspiciousPycryptoImport, - identifier.range(), - ); + checker + .report_diagnostic_if_enabled(SuspiciousPycryptoImport, identifier.range()); } "pyghmi" => { - check_and_push_diagnostic(checker, SuspiciousPyghmiImport, identifier.range()); + checker + .report_diagnostic_if_enabled(SuspiciousPyghmiImport, identifier.range()); } _ => {} } @@ -559,10 +561,3 @@ pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) { _ => panic!("Expected Stmt::Import | Stmt::ImportFrom"), } } - -fn check_and_push_diagnostic(checker: &Checker, diagnostic: T, range: TextRange) { - let diagnostic = Diagnostic::new(diagnostic, range); - if checker.enabled(diagnostic.rule()) { - checker.report_diagnostic(diagnostic); - } -} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs index f4d938097151f9..f1f058706487e4 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs @@ -1,5 +1,4 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; @@ -70,5 +69,5 @@ pub(crate) fn tarfile_unsafe_members(checker: &Checker, call: &ast::ExprCall) { return; } - checker.report_diagnostic(Diagnostic::new(TarfileUnsafeMembers, call.func.range())); + checker.report_diagnostic(TarfileUnsafeMembers, call.func.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs index 193c797a09a103..ac7e7ebaf037b7 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -64,7 +64,7 @@ pub(crate) fn try_except_continue( ) { if matches!(body, [Stmt::Continue(_)]) { if check_typed_exception || is_untyped_exception(type_, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new(TryExceptContinue, except_handler.range())); + checker.report_diagnostic(TryExceptContinue, except_handler.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs index ff48968c985b4f..3fdf1bf37cf44b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -60,7 +60,7 @@ pub(crate) fn try_except_pass( ) { if matches!(body, [Stmt::Pass(_)]) { if check_typed_exception || is_untyped_exception(type_, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new(TryExceptPass, except_handler.range())); + checker.report_diagnostic(TryExceptPass, except_handler.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs index 7c9cac7921bb3c..f82d6fbc09b6e2 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, ExprCall}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{Modules, SemanticModel}; @@ -112,12 +112,12 @@ pub(crate) fn unsafe_markup_call(checker: &Checker, call: &ExprCall) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnsafeMarkupUse { name: qualified_name.to_string(), }, call.range(), - )); + ); } fn is_markup_call(qualified_name: &QualifiedName, settings: &LinterSettings) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs index 00a3df81a29296..7c6510f585a43c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -82,16 +82,10 @@ pub(crate) fn unsafe_yaml_load(checker: &Checker, call: &ast::ExprCall) { Expr::Name(ast::ExprName { id, .. }) => Some(id.to_string()), _ => None, }; - checker.report_diagnostic(Diagnostic::new( - UnsafeYAMLLoad { loader }, - loader_arg.range(), - )); + checker.report_diagnostic(UnsafeYAMLLoad { loader }, loader_arg.range()); } } else { - checker.report_diagnostic(Diagnostic::new( - UnsafeYAMLLoad { loader: None }, - call.func.range(), - )); + checker.report_diagnostic(UnsafeYAMLLoad { loader: None }, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs index 47cc09a9ddf9f3..c89d8a7b3a5f81 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; use ruff_text_size::{Ranged, TextRange}; @@ -55,10 +55,7 @@ pub(crate) fn weak_cryptographic_key(checker: &Checker, call: &ExprCall) { }; if cryptographic_key.is_vulnerable() { - checker.report_diagnostic(Diagnostic::new( - WeakCryptographicKey { cryptographic_key }, - range, - )); + checker.report_diagnostic(WeakCryptographicKey { cryptographic_key }, range); } } diff --git a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs index 5680966fe943ae..c9119014f737ec 100644 --- a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs +++ b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; @@ -107,12 +107,12 @@ pub(crate) fn blind_except( return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BlindExcept { name: builtin_exception_type.to_string(), }, type_.range(), - )); + ); } /// A visitor to detect whether the exception with the given name was re-raised. diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs index 5a27f40e57617c..f4f352a2acfd4d 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::name::UnqualifiedName; @@ -131,10 +131,7 @@ pub(crate) fn boolean_default_value_positional_argument( return; } - checker.report_diagnostic(Diagnostic::new( - BooleanDefaultValuePositionalArgument, - param.identifier(), - )); + checker.report_diagnostic(BooleanDefaultValuePositionalArgument, param.identifier()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs index 82dfca34bc6df5..260d4e566302ff 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -61,6 +61,6 @@ pub(crate) fn boolean_positional_value_in_call(checker: &Checker, call: &ast::Ex .iter() .filter(|arg| arg.is_boolean_literal_expr()) { - checker.report_diagnostic(Diagnostic::new(BooleanPositionalValueInCall, arg.range())); + checker.report_diagnostic(BooleanPositionalValueInCall, arg.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index 4be057891346a7..d06a8f7d426230 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; @@ -158,10 +157,7 @@ pub(crate) fn boolean_type_hint_positional_argument( return; } - checker.report_diagnostic(Diagnostic::new( - BooleanTypeHintPositionalArgument, - parameter.identifier(), - )); + checker.report_diagnostic(BooleanTypeHintPositionalArgument, parameter.identifier()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs index e019e6a6909b7e..b1aa5bf9d17966 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::SemanticModel; @@ -196,22 +196,22 @@ pub(crate) fn abstract_base_class( && is_empty_body(body) && !is_overload(decorator_list, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( EmptyMethodWithoutAbstractDecorator { name: format!("{name}.{method_name}"), }, stmt.range(), - )); + ); } } if checker.enabled(Rule::AbstractBaseClassWithoutAbstractMethod) { if !has_abstract_method { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( AbstractBaseClassWithoutAbstractMethod { name: name.to_string(), }, stmt.identifier(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs index 5be68147712789..6ec957f8fee4ff 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_false; @@ -79,10 +79,9 @@ pub(crate) fn assert_false(checker: &Checker, stmt: &Stmt, test: &Expr, msg: Opt return; } - let mut diagnostic = Diagnostic::new(AssertFalse, test.range()); + let mut diagnostic = checker.report_diagnostic(AssertFalse, test.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().stmt(&assertion_error(msg)), stmt.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs index e233a7c0db0f0a..0b9743aba5c197 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, WithItem}; use ruff_text_size::Ranged; @@ -99,9 +99,6 @@ pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) { continue; } - checker.report_diagnostic(Diagnostic::new( - AssertRaisesException { exception }, - item.range(), - )); + checker.report_diagnostic(AssertRaisesException { exception }, item.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs index 722f676137044d..d708793c1dde25 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -66,5 +66,5 @@ pub(crate) fn assignment_to_os_environ(checker: &Checker, targets: &[Expr]) { if id != "os" { return; } - checker.report_diagnostic(Diagnostic::new(AssignmentToOsEnviron, target.range())); + checker.report_diagnostic(AssignmentToOsEnviron, target.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs index fafc2fa96613f7..8afefcd940484e 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_bugbear::rules::is_infinite_iterable; -use ruff_diagnostics::{Diagnostic, FixAvailability, Violation}; +use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_python_ast::PythonVersion; @@ -86,6 +86,5 @@ pub(crate) fn batched_without_explicit_strict(checker: &Checker, call: &ExprCall return; } - let diagnostic = Diagnostic::new(BatchedWithoutExplicitStrict, call.range); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(BatchedWithoutExplicitStrict, call.range); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 30175823c68071..1c19a160acd587 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{self as ast, Expr}; @@ -102,7 +102,7 @@ pub(crate) fn cached_instance_method(checker: &Checker, function_def: &ast::Stmt return; } - checker.report_diagnostic(Diagnostic::new(CachedInstanceMethod, decorator.range())); + checker.report_diagnostic(CachedInstanceMethod, decorator.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs index 39ab5a9ae24386..bf7994b990995e 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::analyze::visibility::{self, Visibility::Public}; @@ -105,7 +105,7 @@ pub(crate) fn class_as_data_structure(checker: &Checker, class_def: &ast::StmtCl } if has_dunder_init && public_methods == 1 { - checker.report_diagnostic(Diagnostic::new(ClassAsDataStructure, class_def.range())); + checker.report_diagnostic(ClassAsDataStructure, class_def.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index 51d14bd11453a2..dcda3372b55550 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; @@ -141,7 +141,7 @@ fn duplicate_handler_exceptions<'a>( if checker.enabled(Rule::DuplicateHandlerException) { // TODO(charlie): Handle "BaseException" and redundant exception aliases. if !duplicates.is_empty() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DuplicateHandlerException { names: duplicates .into_iter() @@ -167,7 +167,6 @@ fn duplicate_handler_exceptions<'a>( }, expr.range(), ))); - checker.report_diagnostic(diagnostic); } } @@ -217,13 +216,13 @@ pub(crate) fn duplicate_exceptions(checker: &Checker, handlers: &[ExceptHandler] .current_statement() .as_try_stmt() .is_some_and(|try_stmt| try_stmt.is_star); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( DuplicateTryBlockException { name: name.segments().join("."), is_star, }, expr.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs index 7a59df6266bc81..346a1ae064167b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -60,7 +60,7 @@ pub(crate) fn duplicate_value(checker: &Checker, set: &ast::ExprSet) { for (index, value) in set.iter().enumerate() { if value.is_literal_expr() { if let Some(existing) = seen_values.insert(HashableExpr::from(value), value) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DuplicateValue { value: checker.generator().expr(value), existing: checker.generator().expr(existing), @@ -71,8 +71,6 @@ pub(crate) fn duplicate_value(checker: &Checker, set: &ast::ExprSet) { diagnostic.try_set_fix(|| { remove_member(set, index, checker.locator().contents()).map(Fix::safe_edit) }); - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs index cc512fee938c5a..52375d2d74ddc5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast}; use ruff_python_ast::{ExceptHandler, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -66,9 +66,6 @@ pub(crate) fn except_with_empty_tuple(checker: &Checker, except_handler: &Except .current_statement() .as_try_stmt() .is_some_and(|try_stmt| try_stmt.is_star); - checker.report_diagnostic(Diagnostic::new( - ExceptWithEmptyTuple { is_star }, - except_handler.range(), - )); + checker.report_diagnostic(ExceptWithEmptyTuple { is_star }, except_handler.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs index 25e4da777f5ee4..ca3b0874463da5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; use ruff_python_ast::{self as ast, ExceptHandler, Expr, Operator}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -69,10 +69,7 @@ pub(crate) fn except_with_non_exception_classes(checker: &Checker, except_handle .current_statement() .as_try_stmt() .is_some_and(|try_stmt| try_stmt.is_star); - checker.report_diagnostic(Diagnostic::new( - ExceptWithNonExceptionClasses { is_star }, - expr.range(), - )); + checker.report_diagnostic(ExceptWithNonExceptionClasses { is_star }, expr.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs index d5def15e96c1c2..d17b380c446084 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; @@ -51,5 +51,5 @@ pub(crate) fn f_string_docstring(checker: &Checker, body: &[Stmt]) { if !value.is_f_string_expr() { return; } - checker.report_diagnostic(Diagnostic::new(FStringDocstring, stmt.identifier())); + checker.report_diagnostic(FStringDocstring, stmt.identifier()); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs index e6efec2bf7cddc..05a24fe0212146 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs @@ -1,7 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Parameters}; use ruff_text_size::Ranged; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; @@ -113,12 +112,12 @@ impl Visitor<'_> for ArgumentDefaultVisitor<'_, '_> { ) }) { - self.checker.report_diagnostic(Diagnostic::new( + self.checker.report_diagnostic( FunctionCallInDefaultArgument { name: UnqualifiedName::from_expr(func).map(|name| name.to_string()), }, expr.range(), - )); + ); } visitor::walk_expr(self, expr); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs index f40692781c93e4..bef045a9b4b463 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::types::Node; use ruff_python_ast::visitor; @@ -305,12 +305,12 @@ pub(crate) fn function_uses_loop_variable(checker: &Checker, node: &Node) { for name in suspicious_variables { if reassigned_in_loop.contains(&name.id.as_str()) { if checker.insert_flake8_bugbear_range(name.range()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( FunctionUsesLoopVariable { name: name.id.to_string(), }, name.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs index cdf6841774facc..3eee042415c5f5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; @@ -68,7 +68,7 @@ pub(crate) fn getattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr, return; } - let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range()); + let mut diagnostic = checker.report_diagnostic(GetAttrWithConstant, expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad( if matches!( @@ -88,5 +88,4 @@ pub(crate) fn getattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr, ), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs index e835a84dc445eb..b4c5d7f9ec923e 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -56,7 +56,7 @@ impl Violation for JumpStatementInFinally { fn walk_stmt(checker: &Checker, body: &[Stmt], f: fn(&Stmt) -> bool) { for stmt in body { if f(stmt) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( JumpStatementInFinally { name: match stmt { Stmt::Break(_) => "break", @@ -67,7 +67,7 @@ fn walk_stmt(checker: &Checker, body: &[Stmt], f: fn(&Stmt) -> bool) { .to_owned(), }, stmt.range(), - )); + ); } match stmt { Stmt::While(ast::StmtWhile { body, .. }) | Stmt::For(ast::StmtFor { body, .. }) => { diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs index 5b0e52543ebe3c..854c27466408f2 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::fmt::Debug; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; @@ -110,7 +109,7 @@ pub(crate) fn loop_iterator_mutation(checker: &Checker, stmt_for: &StmtFor) { let name = UnqualifiedName::from_expr(iter) .map(|name| name.to_string()) .map(SourceCodeSnippet::new); - checker.report_diagnostic(Diagnostic::new(LoopIteratorMutation { name }, *mutation)); + checker.report_diagnostic(LoopIteratorMutation { name }, *mutation); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs index 51105419a85125..647bf675a234b7 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; @@ -64,12 +64,12 @@ pub(crate) fn loop_variable_overrides_iterator(checker: &Checker, target: &Expr, for (name, expr) in target_names { if iter_names.contains_key(name) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( LoopVariableOverridesIterator { name: name.to_string(), }, expr.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index 01abdeacfa9ddb..7a4e462ad7b684 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -1,6 +1,6 @@ use std::fmt::Write; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::name::QualifiedName; @@ -111,7 +111,7 @@ pub(crate) fn mutable_argument_default(checker: &Checker, function_def: &ast::St is_immutable_annotation(expr, checker.semantic(), extend_immutable_calls.as_slice()) }) { - let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range()); + let mut diagnostic = checker.report_diagnostic(MutableArgumentDefault, default.range()); // If the function body is on the same line as the function def, do not fix if let Some(fix) = move_initialization( @@ -126,7 +126,6 @@ pub(crate) fn mutable_argument_default(checker: &Checker, function_def: &ast::St ) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs index 1923fcaa25aa5f..1188467478bab1 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::name::QualifiedName; @@ -102,6 +102,6 @@ pub(crate) fn mutable_contextvar_default(checker: &Checker, call: &ast::ExprCall matches!(qualified_name.segments(), ["contextvars", "ContextVar"]) }) { - checker.report_diagnostic(Diagnostic::new(MutableContextvarDefault, default.range())); + checker.report_diagnostic(MutableContextvarDefault, default.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index 261246d63e9b9d..30529639b92c88 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -85,7 +85,7 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { { return; } - let mut diagnostic = Diagnostic::new(NoExplicitStacklevel, call.func.range()); + let mut diagnostic = checker.report_diagnostic(NoExplicitStacklevel, call.func.range()); let edit = add_argument( "stacklevel=2", @@ -95,8 +95,6 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { ); diagnostic.set_fix(Fix::unsafe_edit(edit)); - - checker.report_diagnostic(diagnostic); } /// Returns `true` if `skip_file_prefixes` is set to its non-default value. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs index 4c78d34598aa06..980fde34227917 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -39,6 +39,6 @@ impl Violation for RaiseLiteral { /// B016 pub(crate) fn raise_literal(checker: &Checker, expr: &Expr) { if expr.is_literal_expr() { - checker.report_diagnostic(Diagnostic::new(RaiseLiteral, expr.range())); + checker.report_diagnostic(RaiseLiteral, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs index 7e61499676227d..4b9797e327309d 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs @@ -1,7 +1,7 @@ use ruff_python_ast as ast; use ruff_python_ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::RaiseStatementVisitor; use ruff_python_ast::statement_visitor::StatementVisitor; @@ -106,10 +106,7 @@ pub(crate) fn raise_without_from_inside_except( .as_try_stmt() .is_some_and(|try_stmt| try_stmt.is_star); - checker.report_diagnostic(Diagnostic::new( - RaiseWithoutFromInsideExcept { is_star }, - range, - )); + checker.report_diagnostic(RaiseWithoutFromInsideExcept { is_star }, range); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs index 93ac7167535284..f046d331d12fb3 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs @@ -2,7 +2,7 @@ use std::fmt; use ruff_python_ast::{self as ast}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -75,10 +75,7 @@ pub(crate) fn re_sub_positional_args(checker: &Checker, call: &ast::ExprCall) { }; if call.arguments.args.len() > method.num_args() { - checker.report_diagnostic(Diagnostic::new( - ReSubPositionalArgs { method }, - call.range(), - )); + checker.report_diagnostic(ReSubPositionalArgs { method }, call.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs index 5fc41a1144e6ca..9af207f8b5fbf8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, ExceptHandler, Expr}; use ruff_text_size::Ranged; @@ -79,7 +79,7 @@ pub(crate) fn redundant_tuple_in_exception_handler(checker: &Checker, handlers: if elt.is_starred_expr() { continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RedundantTupleInExceptionHandler { name: checker.generator().expr(elt), }, @@ -100,6 +100,5 @@ pub(crate) fn redundant_tuple_in_exception_handler(checker: &Checker, handlers: ), type_.range(), ))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs index 6c77f895229acb..bfffb34494177e 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor; @@ -101,7 +100,7 @@ pub(crate) fn return_in_generator(checker: &Checker, function_def: &StmtFunction if visitor.has_yield { if let Some(return_) = visitor.return_ { - checker.report_diagnostic(Diagnostic::new(ReturnInGenerator, return_)); + checker.report_diagnostic(ReturnInGenerator, return_); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs index d140cc2078792e..460dab103147d2 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Comprehension, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{self, Visitor}; use ruff_text_size::Ranged; @@ -339,6 +339,6 @@ pub(crate) fn reuse_of_groupby_generator( finder.visit_stmt(stmt); } for expr in finder.exprs { - checker.report_diagnostic(Diagnostic::new(ReuseOfGroupbyGenerator, expr.range())); + checker.report_diagnostic(ReuseOfGroupbyGenerator, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index 4694c07df64a99..8ed5452a10b471 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr, ExprContext, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_codegen::Generator; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; @@ -90,12 +90,11 @@ pub(crate) fn setattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr, }) = checker.semantic().current_statement() { if expr == child.as_ref() { - let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range()); + let mut diagnostic = checker.report_diagnostic(SetAttrWithConstant, expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( assignment(obj, name.to_str(), value, checker.generator()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs index dc295cb21f1263..c857b3be38a426 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, Keyword}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -71,9 +71,6 @@ pub(crate) fn star_arg_unpacking_after_keyword_arg( if arg.start() <= keyword.start() { continue; } - checker.report_diagnostic(Diagnostic::new( - StarArgUnpackingAfterKeywordArg, - arg.range(), - )); + checker.report_diagnostic(StarArgUnpackingAfterKeywordArg, arg.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs index fdd937ade210c8..5852ef71dc539f 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::StoredNameFinder; use ruff_python_ast::visitor::Visitor; @@ -58,12 +58,12 @@ pub(crate) fn static_key_dict_comprehension(checker: &Checker, dict_comp: &ast:: }; if is_constant(&dict_comp.key, &names) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( StaticKeyDictComprehension { key: SourceCodeSnippet::from_str(checker.locator().slice(dict_comp.key.as_ref())), }, dict_comp.key.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs index 7eb149edd9d86a..3f982e5592b321 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -73,6 +73,6 @@ pub(crate) fn strip_with_multi_characters( }; if value.chars().count() > 1 && !value.chars().all_unique() { - checker.report_diagnostic(Diagnostic::new(StripWithMultiCharacters, expr.range())); + checker.report_diagnostic(StripWithMultiCharacters, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs index d7b6c193c0424c..1fbfc42d5a7ae3 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, UnaryOp}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -61,20 +61,20 @@ pub(crate) fn unary_prefix_increment_decrement( }; match (op, nested_op) { (UnaryOp::UAdd, UnaryOp::UAdd) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnaryPrefixIncrementDecrement { operator: UnaryPrefixOperatorType::Increment, }, expr.range(), - )); + ); } (UnaryOp::USub, UnaryOp::USub) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnaryPrefixIncrementDecrement { operator: UnaryPrefixOperatorType::Decrement, }, expr.range(), - )); + ); } _ => {} } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs index 2c2b1442fef579..e9230d664b0490 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -46,17 +46,13 @@ pub(crate) fn unintentional_type_annotation( match target { Expr::Subscript(ast::ExprSubscript { value, .. }) => { if value.is_name_expr() { - checker - .report_diagnostic(Diagnostic::new(UnintentionalTypeAnnotation, stmt.range())); + checker.report_diagnostic(UnintentionalTypeAnnotation, stmt.range()); } } Expr::Attribute(ast::ExprAttribute { value, .. }) => { if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() { if id != "self" { - checker.report_diagnostic(Diagnostic::new( - UnintentionalTypeAnnotation, - stmt.range(), - )); + checker.report_diagnostic(UnintentionalTypeAnnotation, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs index 632fd5d00f4deb..8d0934505ccb3a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -72,7 +72,7 @@ pub(crate) fn unreliable_callable_check( return; } - let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnreliableCallableCheck, expr.range()); if builtins_function == "hasattr" { diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol( @@ -87,5 +87,4 @@ pub(crate) fn unreliable_callable_check( Ok(Fix::safe_edits(binding_edit, import_edit)) }); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 01d01ad8532df7..233d8e3c8cd407 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers; @@ -121,7 +121,7 @@ pub(crate) fn unused_loop_control_variable(checker: &Checker, stmt_for: &ast::St .is_match(rename.as_str()) .then_some(rename); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnusedLoopControlVariable { name: name.to_string(), rename: rename.clone(), @@ -147,7 +147,6 @@ pub(crate) fn unused_loop_control_variable(checker: &Checker, stmt_for: &ast::St } } } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs index 2c1434106c02e7..274beba9ae80a9 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Stmt}; use ruff_python_semantic::ScopeKind; @@ -78,22 +78,22 @@ pub(crate) fn useless_comparison(checker: &Checker, expr: &Expr) { .and_then(Stmt::as_expr_stmt) .is_some_and(|last_stmt| &*last_stmt.value == expr) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UselessComparison { at: ComparisonLocationAt::EndOfFunction, }, expr.range(), - )); + ); return; } } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UselessComparison { at: ComparisonLocationAt::MiddleBody, }, expr.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs index 350681548ce6d3..866ad761b64c3d 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -63,6 +63,6 @@ pub(crate) fn useless_contextlib_suppress( matches!(qualified_name.segments(), ["contextlib", "suppress"]) }) { - checker.report_diagnostic(Diagnostic::new(UselessContextlibSuppress, expr.range())); + checker.report_diagnostic(UselessContextlibSuppress, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs index b711dcfdb60e9c..39f01c9ed088d5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::helpers::contains_effect; @@ -100,22 +100,22 @@ pub(crate) fn useless_expression(checker: &Checker, value: &Expr) { // Flag attributes as useless expressions, even if they're attached to calls or other // expressions. if value.is_attribute_expr() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UselessExpression { kind: Kind::Attribute, }, value.range(), - )); + ); } return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UselessExpression { kind: Kind::Expression, }, value.range(), - )); + ); } #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index 88e25540003144..2dfb56826f63f8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -63,8 +63,9 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal .iter() .any(|arg| is_infinite_iterable(arg, semantic)) { - checker.report_diagnostic( - Diagnostic::new(ZipWithoutExplicitStrict, call.range()).with_fix(Fix::applicable_edit( + checker + .report_diagnostic(ZipWithoutExplicitStrict, call.range()) + .set_fix(Fix::applicable_edit( add_argument( "strict=False", &call.arguments, @@ -82,8 +83,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal } else { Applicability::Safe }, - )), - ); + )); } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index e2b24dd3503144..76175c7a7ba730 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Parameter}; @@ -92,11 +91,11 @@ pub(crate) fn builtin_argument_shadowing(checker: &Checker, parameter: &Paramete return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BuiltinArgumentShadowing { name: parameter.name.to_string(), }, parameter.name.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index d75b1225f2a979..678e7ad4e03750 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -135,14 +134,14 @@ pub(crate) fn builtin_attribute_shadowing( == Some(scope_id) }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BuiltinAttributeShadowing { kind, name: name.to_string(), row: checker.compute_source_row(binding.start()), }, reference.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs index a426615f71c9d0..c1ce98c31777cc 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Alias; @@ -63,11 +63,11 @@ pub(crate) fn builtin_import_shadowing(checker: &Checker, alias: &Alias) { &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BuiltinImportShadowing { name: name.to_string(), }, name.range, - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs index ff637cdb2daeb1..20d895047eb9e7 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; @@ -46,12 +46,12 @@ pub(crate) fn builtin_lambda_argument_shadowing(checker: &Checker, lambda: &Expr &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BuiltinLambdaArgumentShadowing { name: name.to_string(), }, name.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 157f8d6a8f32da..23b622fc47cb8b 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::TextRange; @@ -74,11 +73,11 @@ pub(crate) fn builtin_variable_shadowing(checker: &Checker, name: &str, range: T &checker.settings.flake8_builtins.ignorelist, checker.target_version(), ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BuiltinVariableShadowing { name: name.to_string(), }, range, - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs index 444732b3fb089c..716ef5b6fa1da9 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -79,7 +79,7 @@ pub(crate) fn unnecessary_call_around_sorted( if !semantic.match_builtin_expr(inner_func, "sorted") { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryCallAroundSorted { func: unnecessary_function, }, @@ -94,7 +94,6 @@ pub(crate) fn unnecessary_call_around_sorted( }; Ok(Fix::applicable_edit(edit, applicability)) }); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs index f19dd138b3013e..7c8d5b73867ae8 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextSize}; @@ -89,7 +89,7 @@ pub(crate) fn unnecessary_collection_call( }; let mut diagnostic = - Diagnostic::new(UnnecessaryCollectionCall { kind: collection }, call.range()); + checker.report_diagnostic(UnnecessaryCollectionCall { kind: collection }, call.range()); // Convert `dict()` to `{}`. if call.arguments.keywords.is_empty() { @@ -128,8 +128,6 @@ pub(crate) fn unnecessary_collection_call( fixes::fix_unnecessary_collection_call(call, checker).map(Fix::unsafe_edit) }); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs index 9b90fe5009fef3..01d08a2664a0ac 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Comprehension, Expr}; use ruff_python_semantic::analyze::typing; @@ -85,7 +85,7 @@ fn add_diagnostic(checker: &Checker, expr: &Expr) { { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryComprehension { kind: comprehension_kind, }, @@ -95,7 +95,6 @@ fn add_diagnostic(checker: &Checker, expr: &Expr) { fixes::fix_unnecessary_comprehension(expr, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } /// C416 diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs index cc9785d331ccdf..c503777cb58d14 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, FixAvailability}; +use ruff_diagnostics::FixAvailability; use ruff_diagnostics::{Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; @@ -136,13 +136,13 @@ pub(crate) fn unnecessary_comprehension_in_call( } let mut diagnostic = match (arg, builtin_function.duplication_variance()) { - (Expr::ListComp(_), _) => Diagnostic::new( + (Expr::ListComp(_), _) => checker.report_diagnostic( UnnecessaryComprehensionInCall { comprehension_kind: ComprehensionKind::List, }, arg.range(), ), - (Expr::SetComp(_), DuplicationVariance::Invariant) => Diagnostic::new( + (Expr::SetComp(_), DuplicationVariance::Invariant) => checker.report_diagnostic( UnnecessaryComprehensionInCall { comprehension_kind: ComprehensionKind::Set, }, @@ -175,7 +175,6 @@ pub(crate) fn unnecessary_comprehension_in_call( diagnostic.set_fix(Fix::unsafe_edits(collection_start, [collection_end])); } - checker.report_diagnostic(diagnostic); } /// Return `true` if the [`Expr`] contains an `await` expression. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs index e2662ca1302191..1705b3b0ecf32c 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -1,5 +1,5 @@ use ast::ExprName; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::any_over_expr; @@ -113,7 +113,7 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryDictComprehensionForIterable { is_value_none_literal: dict_comp.value.is_none_literal_expr(), }, @@ -131,8 +131,6 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( dict_comp.range(), ))); } - - checker.report_diagnostic(diagnostic); } /// Returns `true` if the expression can be shared across multiple values. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs index 07a9daf1189b00..e8988e66612159 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableKeyword; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; @@ -125,7 +125,7 @@ pub(crate) fn unnecessary_double_cast_or_process( | ("set", "set") | ("list" | "tuple", "list" | "tuple") ) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryDoubleCastOrProcess { inner: inner_func_name.to_string(), outer: outer_func_name.to_string(), @@ -140,6 +140,5 @@ pub(crate) fn unnecessary_double_cast_or_process( ) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs index 644d5123a0501c..7602cd1e350237 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; @@ -70,8 +70,7 @@ pub(crate) fn unnecessary_generator_dict( if tuple.iter().any(Expr::is_starred_expr) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryGeneratorDict, expr.range()); diagnostic .try_set_fix(|| fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::unsafe_edit)); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs index ecf69a07e002a6..1627d769740ad5 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ExprGenerator; @@ -94,22 +94,23 @@ pub(crate) fn unnecessary_generator_list(checker: &Checker, call: &ast::ExprCall if let [generator] = generators.as_slice() { if generator.ifs.is_empty() && !generator.is_async { if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) { - let diagnostic = Diagnostic::new( - UnnecessaryGeneratorList { - short_circuit: true, - }, - call.range(), - ); let iterator = format!("list({})", checker.locator().slice(generator.iter.range())); let fix = Fix::unsafe_edit(Edit::range_replacement(iterator, call.range())); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic( + UnnecessaryGeneratorList { + short_circuit: true, + }, + call.range(), + ) + .set_fix(fix); return; } } } // Convert `list(f(x) for x in y)` to `[f(x) for x in y]`. - let diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryGeneratorList { short_circuit: false, }, @@ -158,5 +159,5 @@ pub(crate) fn unnecessary_generator_list(checker: &Checker, call: &ast::ExprCall Fix::unsafe_edits(call_start, [call_end]) } }; - checker.report_diagnostic(diagnostic.with_fix(fix)); + diagnostic.set_fix(fix); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs index 1f2227087f991d..0db3228cbb8b31 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ExprGenerator; @@ -94,7 +94,7 @@ pub(crate) fn unnecessary_generator_set(checker: &Checker, call: &ast::ExprCall) if let [generator] = generators.as_slice() { if generator.ifs.is_empty() && !generator.is_async { if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryGeneratorSet { short_circuit: true, }, @@ -105,14 +105,13 @@ pub(crate) fn unnecessary_generator_set(checker: &Checker, call: &ast::ExprCall) iterator, call.range(), ))); - checker.report_diagnostic(diagnostic); return; } } } // Convert `set(f(x) for x in y)` to `{f(x) for x in y}`. - let diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryGeneratorSet { short_circuit: false, }, @@ -165,5 +164,5 @@ pub(crate) fn unnecessary_generator_set(checker: &Checker, call: &ast::ExprCall) Fix::unsafe_edits(call_start, [call_end]) } }; - checker.report_diagnostic(diagnostic.with_fix(fix)); + diagnostic.set_fix(fix); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs index 62a7f5e11b38a4..784105fda70ff2 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Arguments, Expr, ExprCall}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -74,10 +74,9 @@ pub(crate) fn unnecessary_list_call(checker: &Checker, expr: &Expr, call: &ExprC if !checker.semantic().has_builtin_binding("list") { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryListCall, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListCall, expr.range()); diagnostic.try_set_fix(|| { fixes::fix_unnecessary_list_call(expr, checker.locator(), checker.stylist()) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs index 6ea0f907a9a479..eeae7b838ce5f9 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; @@ -68,9 +68,8 @@ pub(crate) fn unnecessary_list_comprehension_dict( if !checker.semantic().has_builtin_binding("dict") { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListComprehensionDict, expr.range()); diagnostic.try_set_fix(|| { fixes::fix_unnecessary_list_comprehension_dict(expr, checker).map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs index c0f8b29643ff44..b1f1755a1f093f 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parenthesized_range; @@ -60,7 +60,7 @@ pub(crate) fn unnecessary_list_comprehension_set(checker: &Checker, call: &ast:: if !argument.is_list_comp_expr() { return; } - let diagnostic = Diagnostic::new(UnnecessaryListComprehensionSet, call.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListComprehensionSet, call.range()); let one = TextSize::from(1); // Replace `set(` with `{`. @@ -100,5 +100,5 @@ pub(crate) fn unnecessary_list_comprehension_set(checker: &Checker, call: &ast:: let replacement = Edit::range_replacement(checker.source()[span].to_string(), replacement_range); let fix = Fix::unsafe_edits(call_start, [call_end, replacement]); - checker.report_diagnostic(diagnostic.with_fix(fix)); + diagnostic.set_fix(fix); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs index 111a5f8e5feefb..235e6e8c1996a7 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; @@ -78,10 +78,10 @@ pub(crate) fn unnecessary_literal_dict( if !checker.semantic().has_builtin_binding("dict") { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryLiteralDict { obj_type: kind }, expr.range()); + let mut diagnostic = + checker.report_diagnostic(UnnecessaryLiteralDict { obj_type: kind }, expr.range()); diagnostic .try_set_fix(|| fixes::fix_unnecessary_literal_dict(expr, checker).map(Fix::unsafe_edit)); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs index 2f9ee5bc08256d..f5b0f311b684c6 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextSize}; @@ -67,7 +67,7 @@ pub(crate) fn unnecessary_literal_set(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryLiteralSet { kind }, call.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryLiteralSet { kind }, call.range()); // Convert `set((1, 2))` to `{1, 2}`. diagnostic.set_fix({ @@ -124,8 +124,6 @@ pub(crate) fn unnecessary_literal_set(checker: &Checker, call: &ast::ExprCall) { } } }); - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs index 710e790e34ff97..d80ad19733ff04 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs @@ -2,7 +2,7 @@ use std::fmt; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -71,7 +71,7 @@ pub(crate) fn unnecessary_literal_within_dict_call(checker: &Checker, call: &ast return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryLiteralWithinDictCall { kind: argument_kind, }, @@ -88,8 +88,6 @@ pub(crate) fn unnecessary_literal_within_dict_call(checker: &Checker, call: &ast Fix::unsafe_edits(call_start, [call_end]) }); - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs index 61df6104048031..e5b8fcfd3c0146 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextSize}; @@ -82,7 +82,7 @@ pub(crate) fn unnecessary_literal_within_list_call(checker: &Checker, call: &ast return; } - let diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryLiteralWithinListCall { kind: argument_kind, }, @@ -119,7 +119,7 @@ pub(crate) fn unnecessary_literal_within_list_call(checker: &Checker, call: &ast } }; - checker.report_diagnostic(diagnostic.with_fix(fix)); + diagnostic.set_fix(fix); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs index eae745afd9f968..7dd8231e7cd443 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Expr}; @@ -110,7 +110,7 @@ pub(crate) fn unnecessary_literal_within_tuple_call( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryLiteralWithinTupleCall { literal_kind: argument_kind, }, @@ -165,10 +165,8 @@ pub(crate) fn unnecessary_literal_within_tuple_call( }); } - _ => return, + _ => (), } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs index 36b286b86ed1a0..bcd0090fcaa6ff 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Fix}; +use ruff_diagnostics::Fix; use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; @@ -138,7 +138,7 @@ pub(crate) fn unnecessary_map(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryMap { object_type }, call.range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryMap { object_type }, call.range); diagnostic.try_set_fix(|| { fixes::fix_unnecessary_map( call, @@ -149,7 +149,6 @@ pub(crate) fn unnecessary_map(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } fn is_list_set_or_dict(func: &Expr, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs index 2c26077bf46a5d..d2e0d960fa635f 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, UnaryOp}; use ruff_text_size::Ranged; @@ -86,10 +86,10 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &Checker, call: &ast::Expr if !matches!(function_name, "reversed" | "set" | "sorted") { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnnecessarySubscriptReversal { func: function_name.to_string(), }, call.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs index 111916a5eedc7b..12ab428186b59e 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -73,6 +73,6 @@ pub(crate) fn call_date_fromtimestamp(checker: &Checker, func: &Expr, location: ) }) { - checker.report_diagnostic(Diagnostic::new(CallDateFromtimestamp, location)); + checker.report_diagnostic(CallDateFromtimestamp, location); } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs index 721073076ffce8..b182c840cae7b6 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -69,6 +69,6 @@ pub(crate) fn call_date_today(checker: &Checker, func: &Expr, location: TextRang matches!(qualified_name.segments(), ["datetime", "date", "today"]) }) { - checker.report_diagnostic(Diagnostic::new(CallDateToday, location)); + checker.report_diagnostic(CallDateToday, location); } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index 523254619bc1ee..6781be630ca2be 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; @@ -97,8 +97,5 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &Checker, call: &ast::ExprCal None => DatetimeModuleAntipattern::NoTzArgumentPassed, }; - checker.report_diagnostic(Diagnostic::new( - CallDatetimeFromtimestamp(antipattern), - call.range, - )); + checker.report_diagnostic(CallDatetimeFromtimestamp(antipattern), call.range); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index cb02443b3eb91b..693f15ddce90ff 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -92,8 +92,5 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &Checker, call: &ast::Ex None => DatetimeModuleAntipattern::NoTzArgumentPassed, }; - checker.report_diagnostic(Diagnostic::new( - CallDatetimeNowWithoutTzinfo(antipattern), - call.range, - )); + checker.report_diagnostic(CallDatetimeNowWithoutTzinfo(antipattern), call.range); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index 4c89488b8375d3..43a265e1eede10 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; @@ -139,10 +139,7 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &Checker, call: &ast: semantic.current_expression_grandparent(), semantic.current_expression_parent(), ) { - checker.report_diagnostic(Diagnostic::new( - CallDatetimeStrptimeWithoutZone(antipattern), - call.range, - )); + checker.report_diagnostic(CallDatetimeStrptimeWithoutZone(antipattern), call.range); } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs index 9ba0332eead67e..1f22d130873eb6 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -75,5 +75,5 @@ pub(crate) fn call_datetime_today(checker: &Checker, func: &Expr, location: Text return; } - checker.report_diagnostic(Diagnostic::new(CallDatetimeToday, location)); + checker.report_diagnostic(CallDatetimeToday, location); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs index 4febc8f8f89aa8..272ce7161b0436 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -82,5 +82,5 @@ pub(crate) fn call_datetime_utcfromtimestamp(checker: &Checker, func: &Expr, loc return; } - checker.report_diagnostic(Diagnostic::new(CallDatetimeUtcfromtimestamp, location)); + checker.report_diagnostic(CallDatetimeUtcfromtimestamp, location); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs index f54452ab6eb215..8a7068b05051c5 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -82,5 +82,5 @@ pub(crate) fn call_datetime_utcnow(checker: &Checker, func: &Expr, location: Tex return; } - checker.report_diagnostic(Diagnostic::new(CallDatetimeUtcnow, location)); + checker.report_diagnostic(CallDatetimeUtcnow, location); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index 2e46890d5347f2..c59c321918b19e 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -86,8 +86,5 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &Checker, call: &ast::ExprCa None => DatetimeModuleAntipattern::NoTzArgumentPassed, }; - checker.report_diagnostic(Diagnostic::new( - CallDatetimeWithoutTzinfo(antipattern), - call.range, - )); + checker.report_diagnostic(CallDatetimeWithoutTzinfo(antipattern), call.range); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs index 5f6a94ce184ce7..83b9fcb8a1efe4 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::{Modules, SemanticModel}; @@ -74,7 +74,7 @@ pub(crate) fn datetime_min_max(checker: &Checker, expr: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new(DatetimeMinMax { min_max }, expr.range())); + checker.report_diagnostic(DatetimeMinMax { min_max }, expr.range()); } /// Check if the current expression has the pattern `foo.replace(tzinfo=bar)` or `foo.time()`. diff --git a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs index 9ac6c79bbbfac3..807cb90981011c 100644 --- a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs +++ b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; @@ -60,36 +60,35 @@ pub(crate) fn debugger_call(checker: &Checker, expr: &Expr, func: &Expr) { } }) { - checker.report_diagnostic(Diagnostic::new(Debugger { using_type }, expr.range())); + checker.report_diagnostic(Debugger { using_type }, expr.range()); } } /// Checks for the presence of a debugger import. -pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option { +pub(crate) fn debugger_import(checker: &Checker, stmt: &Stmt, module: Option<&str>, name: &str) { if let Some(module) = module { let qualified_name = QualifiedName::user_defined(module).append_member(name); if is_debugger_call(&qualified_name) { - return Some(Diagnostic::new( + checker.report_diagnostic( Debugger { using_type: DebuggerUsingType::Import(qualified_name.to_string()), }, stmt.range(), - )); + ); } } else { let qualified_name = QualifiedName::user_defined(name); if is_debugger_import(&qualified_name) { - return Some(Diagnostic::new( + checker.report_diagnostic( Debugger { using_type: DebuggerUsingType::Import(name.to_string()), }, stmt.range(), - )); + ); } } - None } fn is_debugger_call(qualified_name: &QualifiedName) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs index 794ae075d0ae47..c9ccd941ed7bfa 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::Modules; @@ -78,19 +78,13 @@ pub(crate) fn all_with_model_form(checker: &Checker, class_def: &ast::StmtClassD match value.as_ref() { Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { if value == "__all__" { - checker.report_diagnostic(Diagnostic::new( - DjangoAllWithModelForm, - element.range(), - )); + checker.report_diagnostic(DjangoAllWithModelForm, element.range()); return; } } Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => { if value == "__all__".as_bytes() { - checker.report_diagnostic(Diagnostic::new( - DjangoAllWithModelForm, - element.range(), - )); + checker.report_diagnostic(DjangoAllWithModelForm, element.range()); return; } } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs index c92c0fc0475229..0de9346f4e3437 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::Modules; @@ -71,10 +71,7 @@ pub(crate) fn exclude_with_model_form(checker: &Checker, class_def: &ast::StmtCl continue; }; if id == "exclude" { - checker.report_diagnostic(Diagnostic::new( - DjangoExcludeWithModelForm, - target.range(), - )); + checker.report_diagnostic(DjangoExcludeWithModelForm, target.range()); return; } } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs index 9dbf1ae2f70754..8328d7d24bac92 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{Modules, SemanticModel}; @@ -61,10 +61,7 @@ pub(crate) fn locals_in_render_function(checker: &Checker, call: &ast::ExprCall) if let Some(argument) = call.arguments.find_argument_value("context", 2) { if is_locals_call(argument, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( - DjangoLocalsInRenderFunction, - argument.range(), - )); + checker.report_diagnostic(DjangoLocalsInRenderFunction, argument.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs index 9ea374cca68091..acb63289c0750f 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::identifier::Identifier; @@ -64,10 +64,7 @@ pub(crate) fn model_without_dunder_str(checker: &Checker, class_def: &ast::StmtC return; } - checker.report_diagnostic(Diagnostic::new( - DjangoModelWithoutDunderStr, - class_def.identifier(), - )); + checker.report_diagnostic(DjangoModelWithoutDunderStr, class_def.identifier()); } /// Returns `true` if the class has `__str__` method. diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs index 74ef6caf804cee..a4744674a9e26d 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Decorator; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -70,10 +70,7 @@ pub(crate) fn non_leading_receiver_decorator(checker: &Checker, decorator_list: }) }); if i > 0 && is_receiver && !seen_receiver { - checker.report_diagnostic(Diagnostic::new( - DjangoNonLeadingReceiverDecorator, - decorator.range(), - )); + checker.report_diagnostic(DjangoNonLeadingReceiverDecorator, decorator.range()); } if !is_receiver && seen_receiver { seen_receiver = false; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs index d01f35154176fb..6840d466418ad6 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_semantic::{Modules, SemanticModel}; @@ -64,12 +64,12 @@ pub(crate) fn nullable_model_string_field(checker: &Checker, body: &[Stmt]) { continue; }; if let Some(field_name) = is_nullable_field(value, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( DjangoNullableModelStringField { field_name: field_name.to_string(), }, value.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs index fdc0b806b0dedc..85f385be465cea 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -112,14 +112,13 @@ pub(crate) fn unordered_body_content_in_model(checker: &Checker, class_def: &ast .iter() .find(|&&prev_element_type| prev_element_type > element_type) { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( DjangoUnorderedBodyContentInModel { element_type, prev_element_type, }, element.range(), ); - checker.report_diagnostic(diagnostic); } else { element_types.push(element_type); } diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 85767714069ca0..560fbfd34a804a 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::whitespace; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; @@ -188,7 +188,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { if checker.enabled(Rule::RawStringInException) { if string.len() >= checker.settings.flake8_errmsg.max_string_length { let mut diagnostic = - Diagnostic::new(RawStringInException, first.range()); + checker.report_diagnostic(RawStringInException, first.range()); if let Some(indentation) = whitespace::indentation(checker.source(), stmt) { @@ -200,14 +200,14 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { checker.locator(), )); } - checker.report_diagnostic(diagnostic); } } } // Check for f-strings. Expr::FString(_) => { if checker.enabled(Rule::FStringInException) { - let mut diagnostic = Diagnostic::new(FStringInException, first.range()); + let mut diagnostic = + checker.report_diagnostic(FStringInException, first.range()); if let Some(indentation) = whitespace::indentation(checker.source(), stmt) { diagnostic.set_fix(generate_fix( stmt, @@ -217,7 +217,6 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { checker.locator(), )); } - checker.report_diagnostic(diagnostic); } } // Check for .format() calls. @@ -228,7 +227,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { { if attr == "format" && value.is_literal_expr() { let mut diagnostic = - Diagnostic::new(DotFormatInException, first.range()); + checker.report_diagnostic(DotFormatInException, first.range()); if let Some(indentation) = whitespace::indentation(checker.source(), stmt) { @@ -240,7 +239,6 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { checker.locator(), )); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs index 30207f02d73558..7475d238fe25c0 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::{MemberNameImport, NameImport}; @@ -85,7 +85,8 @@ impl AlwaysFixableViolation for FutureRequiredTypeAnnotation { /// FA102 pub(crate) fn future_required_type_annotation(checker: &Checker, expr: &Expr, reason: Reason) { - let mut diagnostic = Diagnostic::new(FutureRequiredTypeAnnotation { reason }, expr.range()); + let mut diagnostic = + checker.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range()); let required_import = NameImport::ImportFrom(MemberNameImport::member( "__future__".to_string(), "annotations".to_string(), @@ -95,5 +96,4 @@ pub(crate) fn future_required_type_annotation(checker: &Checker, expr: &Expr, re .importer() .add_import(&required_import, TextSize::default()), )); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs index 654ed1acd5b016..12c67d0f9a42dd 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -84,9 +84,6 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr) .map(|binding| binding.to_string()); if let Some(name) = name { - checker.report_diagnostic(Diagnostic::new( - FutureRewritableTypeAnnotation { name }, - expr.range(), - )); + checker.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs index f9cbac5cabcdf0..d165cbe48b9e37 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -54,7 +54,7 @@ impl Violation for FStringInGetTextFuncCall { pub(crate) fn f_string_in_gettext_func_call(checker: &Checker, args: &[Expr]) { if let Some(first) = args.first() { if first.is_f_string_expr() { - checker.report_diagnostic(Diagnostic::new(FStringInGetTextFuncCall {}, first.range())); + checker.report_diagnostic(FStringInGetTextFuncCall {}, first.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs index 818d54e027d1ed..79e242d0363237 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -56,10 +56,7 @@ pub(crate) fn format_in_gettext_func_call(checker: &Checker, args: &[Expr]) { if let Expr::Call(ast::ExprCall { func, .. }) = &first { if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() { if attr == "format" { - checker.report_diagnostic(Diagnostic::new( - FormatInGetTextFuncCall {}, - first.range(), - )); + checker.report_diagnostic(FormatInGetTextFuncCall {}, first.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs index 993f9a7cbee1f1..668ae71e2e51fe 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr, Operator}; use crate::checkers::ast::Checker; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -60,8 +60,7 @@ pub(crate) fn printf_in_gettext_func_call(checker: &Checker, args: &[Expr]) { }) = &first { if left.is_string_literal_expr() { - checker - .report_diagnostic(Diagnostic::new(PrintfInGetTextFuncCall {}, first.range())); + checker.report_diagnostic(PrintfInGetTextFuncCall {}, first.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs index 5af474d665ebe1..173c05b8a9afa1 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs @@ -1,5 +1,5 @@ use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_python_trivia::is_python_whitespace; @@ -47,13 +47,13 @@ impl AlwaysFixableViolation for ExplicitStringConcatenation { } /// ISC003 -pub(crate) fn explicit(expr: &Expr, checker: &Checker) -> Option { +pub(crate) fn explicit(checker: &Checker, expr: &Expr) { // If the user sets `allow-multiline` to `false`, then we should allow explicitly concatenated // strings that span multiple lines even if this rule is enabled. Otherwise, there's no way // for the user to write multiline strings, and that setting is "more explicit" than this rule // being enabled. if !checker.settings.flake8_implicit_str_concat.allow_multiline { - return None; + return; } if let Expr::BinOp(bin_op) = expr { @@ -76,13 +76,12 @@ pub(crate) fn explicit(expr: &Expr, checker: &Checker) -> Option { .locator() .contains_line_break(TextRange::new(left.end(), right.start())) { - let mut diagnostic = Diagnostic::new(ExplicitStringConcatenation, expr.range()); - diagnostic.set_fix(generate_fix(checker, bin_op)); - return Some(diagnostic); + checker + .report_diagnostic(ExplicitStringConcatenation, expr.range()) + .set_fix(generate_fix(checker, bin_op)); } } } - None } fn generate_fix(checker: &Checker, expr_bin_op: &ast::ExprBinOp) -> Fix { diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs index d0a9039a140954..412fd2fba12ea2 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs @@ -1,10 +1,11 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; use crate::rules::flake8_import_conventions::settings::BannedAliases; /// ## What it does @@ -48,24 +49,24 @@ impl Violation for BannedImportAlias { /// ICN002 pub(crate) fn banned_import_alias( + checker: &Checker, stmt: &Stmt, name: &str, asname: &str, banned_conventions: &FxHashMap, -) -> Option { +) { if let Some(banned_aliases) = banned_conventions.get(name) { if banned_aliases .iter() .any(|banned_alias| banned_alias == asname) { - return Some(Diagnostic::new( + checker.report_diagnostic( BannedImportAlias { name: name.to_string(), asname: asname.to_string(), }, stmt.range(), - )); + ); } } - None } diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs index afbfc8df4936cb..da96e127cd9ce5 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs @@ -1,10 +1,12 @@ use ruff_python_ast::Stmt; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for member imports that should instead be accessed by importing the /// module. @@ -46,17 +48,17 @@ impl Violation for BannedImportFrom { /// ICN003 pub(crate) fn banned_import_from( + checker: &Checker, stmt: &Stmt, name: &str, banned_conventions: &FxHashSet, -) -> Option { +) { if banned_conventions.contains(name) { - return Some(Diagnostic::new( + checker.report_diagnostic( BannedImportFrom { name: name.to_string(), }, stmt.range(), - )); + ); } - None } diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs index 815582df8fa1ca..ccf58de5f3f105 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, Imported}; use ruff_text_size::Ranged; @@ -60,17 +60,21 @@ pub(crate) fn unconventional_import_alias( checker: &Checker, binding: &Binding, conventions: &FxHashMap, -) -> Option { - let import = binding.as_any_import()?; +) { + let Some(import) = binding.as_any_import() else { + return; + }; let qualified_name = import.qualified_name().to_string(); - let expected_alias = conventions.get(qualified_name.as_str())?; + let Some(expected_alias) = conventions.get(qualified_name.as_str()) else { + return; + }; let name = binding.name(checker.source()); if name == expected_alias { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnconventionalImportAlias { name: qualified_name, asname: expected_alias.to_string(), @@ -92,5 +96,4 @@ pub(crate) fn unconventional_import_alias( }); } } - Some(diagnostic) } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs index 83dae3049ca5b2..d372a8aab8e06e 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -64,7 +64,8 @@ pub(crate) fn direct_logger_instantiation(checker: &Checker, call: &ast::ExprCal .resolve_qualified_name(call.func.as_ref()) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["logging", "Logger"])) { - let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range()); + let mut diagnostic = + checker.report_diagnostic(DirectLoggerInstantiation, call.func.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("logging", "getLogger"), @@ -74,6 +75,5 @@ pub(crate) fn direct_logger_instantiation(checker: &Checker, call: &ast::ExprCal let reference_edit = Edit::range_replacement(binding, call.func.range()); Ok(Fix::unsafe_edits(import_edit, [reference_edit])) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs index ec74ffdc48466e..b016bc0ca9267a 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; @@ -107,12 +107,10 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall let arguments = &call.arguments; let source = checker.source(); - let mut diagnostic = Diagnostic::new(ExcInfoOutsideExceptHandler, exc_info.range); + let mut diagnostic = checker.report_diagnostic(ExcInfoOutsideExceptHandler, exc_info.range); diagnostic.try_set_fix(|| { let edit = remove_argument(exc_info, arguments, Parentheses::Preserve, source)?; Ok(Fix::unsafe_edit(edit)) }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs index f8c36980d2dd97..8329823b468bec 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{self as ast, Expr, ExprCall}; @@ -74,7 +74,7 @@ pub(crate) fn exception_without_exc_info(checker: &Checker, call: &ExprCall) { } if exc_info_arg_is_falsey(call, checker) { - checker.report_diagnostic(Diagnostic::new(ExceptionWithoutExcInfo, call.range())); + checker.report_diagnostic(ExceptionWithoutExcInfo, call.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs index f2a30ee900bdd0..68c5144a27a3cd 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; @@ -84,12 +84,11 @@ pub(crate) fn invalid_get_logger_argument(checker: &Checker, call: &ast::ExprCal return; } - let mut diagnostic = Diagnostic::new(InvalidGetLoggerArgument, expr.range()); + let mut diagnostic = checker.report_diagnostic(InvalidGetLoggerArgument, expr.range()); if checker.semantic().has_builtin_binding("__name__") { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( "__name__".to_string(), expr.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs index 5122f9cb0010a8..d92a88ff93c051 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::analyze::logging; @@ -99,11 +99,9 @@ pub(crate) fn log_exception_outside_except_handler(checker: &Checker, call: &Exp _ => return, }; - let mut diagnostic = Diagnostic::new(LogExceptionOutsideExceptHandler, call.range); + let mut diagnostic = checker.report_diagnostic(LogExceptionOutsideExceptHandler, call.range); if let Some(fix) = fix { diagnostic.set_fix(fix); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs index 167eab4b3a2fec..a98850d7f743ac 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_python_semantic::Modules; @@ -63,9 +63,7 @@ pub(crate) fn root_logger_call(checker: &Checker, call: &ExprCall) { let kind = RootLoggerCall { attr: (*attr).to_string(), }; - let diagnostic = Diagnostic::new(kind, call.range); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(kind, call.range); } #[inline] diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs index 8ed90df6d07cc1..a71f372d65fdc2 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -59,7 +59,7 @@ pub(crate) fn undocumented_warn(checker: &Checker, expr: &Expr) { .resolve_qualified_name(expr) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["logging", "WARN"])) { - let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range()); + let mut diagnostic = checker.report_diagnostic(UndocumentedWarn, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("logging", "WARNING"), @@ -69,6 +69,5 @@ pub(crate) fn undocumented_warn(checker: &Checker, expr: &Expr) { let reference_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(import_edit, [reference_edit])) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs index a053b4514fb12b..7780b72dc7349d 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator}; use ruff_python_semantic::analyze::logging; use ruff_python_stdlib::logging::LoggingLevel; @@ -48,12 +48,12 @@ fn check_msg(checker: &Checker, msg: &Expr) { Expr::BinOp(ast::ExprBinOp { op, .. }) => match op { Operator::Add => { if checker.enabled(Rule::LoggingStringConcat) { - checker.report_diagnostic(Diagnostic::new(LoggingStringConcat, msg.range())); + checker.report_diagnostic(LoggingStringConcat, msg.range()); } } Operator::Mod => { if checker.enabled(Rule::LoggingPercentFormat) { - checker.report_diagnostic(Diagnostic::new(LoggingPercentFormat, msg.range())); + checker.report_diagnostic(LoggingPercentFormat, msg.range()); } } _ => {} @@ -61,7 +61,7 @@ fn check_msg(checker: &Checker, msg: &Expr) { // Check for f-strings. Expr::FString(_) => { if checker.enabled(Rule::LoggingFString) { - checker.report_diagnostic(Diagnostic::new(LoggingFString, msg.range())); + checker.report_diagnostic(LoggingFString, msg.range()); } } // Check for .format() calls. @@ -69,8 +69,7 @@ fn check_msg(checker: &Checker, msg: &Expr) { if checker.enabled(Rule::LoggingStringFormat) { if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() { if attr == "format" && value.is_literal_expr() { - checker - .report_diagnostic(Diagnostic::new(LoggingStringFormat, msg.range())); + checker.report_diagnostic(LoggingStringFormat, msg.range()); } } } @@ -91,10 +90,10 @@ fn check_log_record_attr_clash(checker: &Checker, extra: &Keyword) { None } }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( LoggingExtraAttrClash(invalid_key.value.to_string()), invalid_key.range(), - )); + ); } } Expr::Call(ast::ExprCall { @@ -106,10 +105,10 @@ fn check_log_record_attr_clash(checker: &Checker, extra: &Keyword) { for keyword in keywords { if let Some(attr) = &keyword.arg { if is_reserved_attr(attr) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( LoggingExtraAttrClash(attr.to_string()), keyword.range(), - )); + ); } } } @@ -184,12 +183,11 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) { logging_call_type, LoggingCallType::LevelCall(LoggingLevel::Warn) ) { - let mut diagnostic = Diagnostic::new(LoggingWarn, range); + let mut diagnostic = checker.report_diagnostic(LoggingWarn, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "warning".to_string(), range, ))); - checker.report_diagnostic(diagnostic); } } @@ -212,15 +210,12 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) { match logging_level { LoggingLevel::Error => { if checker.enabled(Rule::LoggingExcInfo) { - checker.report_diagnostic(Diagnostic::new(LoggingExcInfo, range)); + checker.report_diagnostic(LoggingExcInfo, range); } } LoggingLevel::Exception => { if checker.enabled(Rule::LoggingRedundantExcInfo) { - checker.report_diagnostic(Diagnostic::new( - LoggingRedundantExcInfo, - exc_info.range(), - )); + checker.report_diagnostic(LoggingRedundantExcInfo, exc_info.range()); } } _ => {} diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs index 5474c528fdae00..c1dc4783419c0b 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs @@ -1,6 +1,5 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; @@ -94,7 +93,7 @@ pub(crate) fn duplicate_class_field_definition(checker: &Checker, body: &[Stmt]) } if !seen_targets.insert(target.id.as_str()) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DuplicateClassFieldDefinition { name: target.id.to_string(), }, @@ -105,7 +104,6 @@ pub(crate) fn duplicate_class_field_definition(checker: &Checker, body: &[Stmt]) diagnostic.set_fix(Fix::unsafe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs index 62c75c9c1876a8..a15ff261414ea9 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs @@ -9,7 +9,7 @@ use ruff_text_size::{Ranged, TextRange}; use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr, ExprContext, Identifier}; use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -128,7 +128,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { // Generate a `Diagnostic` for each duplicate. for ((attr_name, arg_name), indices) in duplicates { if indices.len() > 1 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MultipleStartsEndsWith { attr: attr_name.to_string(), }, @@ -219,7 +219,6 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { checker.generator().expr(&bool_op), expr.range(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs index d54919bc3cfbac..44260082982847 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs @@ -1,7 +1,6 @@ use ruff_python_semantic::SemanticModel; use rustc_hash::FxHashSet; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; @@ -91,13 +90,12 @@ pub(crate) fn non_unique_enums(checker: &Checker, parent: &Stmt, body: &[Stmt]) let comparable = ComparableExpr::from(value); if !seen_targets.insert(comparable) { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( NonUniqueEnums { value: checker.generator().expr(value), }, stmt.range(), ); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs index 0f99c813e8729d..e78083888e02cd 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, ExprLambda}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -74,7 +74,8 @@ pub(crate) fn reimplemented_container_builtin(checker: &Checker, expr: &ExprLamb Expr::Dict(dict) if dict.is_empty() => Container::Dict, _ => return, }; - let mut diagnostic = Diagnostic::new(ReimplementedContainerBuiltin { container }, expr.range()); + let mut diagnostic = + checker.report_diagnostic(ReimplementedContainerBuiltin { container }, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol( container.as_str(), @@ -84,7 +85,6 @@ pub(crate) fn reimplemented_container_builtin(checker: &Checker, expr: &ExprLamb let binding_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(binding_edit, import_edit)) }); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs index d8bba791e07cb8..6164c549ed9c96 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr}; @@ -92,12 +92,13 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) { // Ex) `foo(**{**bar})` if let [ast::DictItem { key: None, value }] = dict.items.as_slice() { - let diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); let edit = Edit::range_replacement( format!("**{}", checker.locator().slice(value)), keyword.range(), ); - checker.report_diagnostic(diagnostic.with_fix(Fix::safe_edit(edit))); + checker + .report_diagnostic(UnnecessaryDictKwargs, keyword.range()) + .set_fix(Fix::safe_edit(edit)); continue; } @@ -111,7 +112,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) { continue; } - let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryDictKwargs, keyword.range()); if dict.is_empty() { diagnostic.try_set_fix(|| { @@ -168,8 +169,6 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) { } } } - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs index e704243b350d3c..8e2dbd34fdf2a7 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs @@ -1,5 +1,5 @@ use ruff_diagnostics::{AlwaysFixableViolation, Applicability}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::whitespace::trailing_comment_start_offset; @@ -138,14 +138,14 @@ fn add_diagnostic( let isolation_level = Checker::isolation(checker.semantic().current_statement_id()); let fix = Fix::applicable_edit(edit, applicability).isolate(isolation_level); - let diagnostic = Diagnostic::new( - UnnecessaryPlaceholder { - kind: placeholder_kind, - }, - stmt.range(), - ); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic( + UnnecessaryPlaceholder { + kind: placeholder_kind, + }, + stmt.range(), + ) + .set_fix(fix); } #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs index b046df61d0f798..3540b534f914cf 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; @@ -70,7 +69,7 @@ pub(crate) fn unnecessary_range_start(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryRangeStart, start.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryRangeStart, start.range()); diagnostic.try_set_fix(|| { remove_argument( &start, @@ -80,5 +79,4 @@ pub(crate) fn unnecessary_range_start(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs index e1d3eed5b9db22..353b83bf143859 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_parser::{TokenKind, Tokens}; @@ -52,11 +52,10 @@ pub(crate) fn unnecessary_spread(checker: &Checker, dict: &ast::ExprDict) { // We only care about when the key is None which indicates a spread `**` // inside a dict. if let Expr::Dict(inner) = value { - let mut diagnostic = Diagnostic::new(UnnecessarySpread, value.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessarySpread, value.range()); if let Some(fix) = unnecessary_spread_fix(inner, prev_end, checker.tokens()) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } } prev_end = value.end(); diff --git a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs index 41e4cde22e0163..ed7f4ce866f9b5 100644 --- a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs @@ -1,11 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::delete_stmt; -use crate::registry::AsRule; /// ## What it does /// Checks for `print` statements. @@ -121,7 +120,7 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { return; }; - let mut diagnostic = match qualified_name.segments() { + let diagnostic = match qualified_name.segments() { ["" | "builtins", "print"] => { // If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`, // or `"sys.stderr"`), don't trigger T201. @@ -136,15 +135,15 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { } } } - Diagnostic::new(Print, call.func.range()) + checker.report_diagnostic_if_enabled(Print, call.func.range()) } - ["pprint", "pprint"] => Diagnostic::new(PPrint, call.func.range()), + ["pprint", "pprint"] => checker.report_diagnostic_if_enabled(PPrint, call.func.range()), _ => return, }; - if !checker.enabled(diagnostic.rule()) { + let Some(mut diagnostic) = diagnostic else { return; - } + }; // Remove the `print`, if it's a standalone statement. if semantic.current_expression_parent().is_none() { @@ -156,6 +155,4 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { .isolate(Checker::isolation(semantic.current_statement_parent_id())), ); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs index 653229819889b3..db61dcff95a5e3 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Parameters; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -84,7 +84,7 @@ pub(crate) fn any_eq_ne_annotation(checker: &Checker, name: &str, parameters: &P return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( AnyEqNeAnnotation { method_name: name.to_string(), }, @@ -100,5 +100,4 @@ pub(crate) fn any_eq_ne_annotation(checker: &Checker, name: &str, parameters: &P let binding_edit = Edit::range_replacement(binding, annotation.range()); Ok(Fix::safe_edits(binding_edit, import_edit)) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs index d3fbb436bfe52c..b6033ebab1ab20 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_subscript; @@ -210,7 +210,7 @@ pub(crate) fn bad_generator_return_type(function_def: &ast::StmtFunctionDef, che } } } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( GeneratorReturnFromIterMethod { return_type: member.to_iter(), method, @@ -228,8 +228,6 @@ pub(crate) fn bad_generator_return_type(function_def: &ast::StmtFunctionDef, che checker, ) }); - - checker.report_diagnostic(diagnostic); } /// Returns `true` if the [`ast::Expr`] is a `None` literal or a `typing.Any` expression. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs index 1feef776c207cf..4d4a21d84fe1f5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -144,12 +144,12 @@ pub(crate) fn bad_version_info_comparison(checker: &Checker, test: &Expr, has_el && (checker.source_type.is_stub() || is_bad_version_info_in_non_stub_enabled(checker.settings)) { if has_else_clause { - checker.report_diagnostic(Diagnostic::new(BadVersionInfoOrder, test.range())); + checker.report_diagnostic(BadVersionInfoOrder, test.range()); } } } else { if checker.enabled(Rule::BadVersionInfoComparison) { - checker.report_diagnostic(Diagnostic::new(BadVersionInfoComparison, test.range())); + checker.report_diagnostic(BadVersionInfoComparison, test.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs index 7b8ff0ce6d7cda..3267c08e5efbce 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, FixAvailability, Violation}; +use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; @@ -74,10 +74,7 @@ pub(crate) fn bytestring_attribute(checker: &Checker, attribute: &Expr) { ["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc, _ => return, }; - checker.report_diagnostic(Diagnostic::new( - ByteStringUsage { origin }, - attribute.range(), - )); + checker.report_diagnostic(ByteStringUsage { origin }, attribute.range()); } /// PYI057 @@ -97,7 +94,7 @@ pub(crate) fn bytestring_import(checker: &Checker, import_from: &ast::StmtImport for name in names { if name.name.as_str() == "ByteString" { - checker.report_diagnostic(Diagnostic::new(ByteStringUsage { origin }, name.range())); + checker.report_diagnostic(ByteStringUsage { origin }, name.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs index 9e01fb1420b576..ea122dfdaa2ce1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -62,6 +62,6 @@ pub(crate) fn collections_named_tuple(checker: &Checker, expr: &Expr) { matches!(qualified_name.segments(), ["collections", "namedtuple"]) }) { - checker.report_diagnostic(Diagnostic::new(CollectionsNamedTuple, expr.range())); + checker.report_diagnostic(CollectionsNamedTuple, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs index 08d7b95f761c45..dda0042d59d363 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, StmtAssign}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -56,5 +56,5 @@ pub(crate) fn complex_assignment_in_stub(checker: &Checker, stmt: &StmtAssign) { if matches!(stmt.targets.as_slice(), [Expr::Name(_)]) { return; } - checker.report_diagnostic(Diagnostic::new(ComplexAssignmentInStub, stmt.range)); + checker.report_diagnostic(ComplexAssignmentInStub, stmt.range); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs index c6001371432b98..a2525a8dfc559c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -48,12 +48,12 @@ pub(crate) fn complex_if_statement_in_stub(checker: &Checker, test: &Expr) { left, comparators, .. }) = test else { - checker.report_diagnostic(Diagnostic::new(ComplexIfStatementInStub, test.range())); + checker.report_diagnostic(ComplexIfStatementInStub, test.range()); return; }; if comparators.len() != 1 { - checker.report_diagnostic(Diagnostic::new(ComplexIfStatementInStub, test.range())); + checker.report_diagnostic(ComplexIfStatementInStub, test.range()); return; } @@ -74,5 +74,5 @@ pub(crate) fn complex_if_statement_in_stub(checker: &Checker, test: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new(ComplexIfStatementInStub, test.range())); + checker.report_diagnostic(ComplexIfStatementInStub, test.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs index c3655eebdaf1e9..24e39f267a5a5a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs @@ -1,7 +1,7 @@ use anyhow::{Context, bail}; use itertools::Itertools; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::analyze::class::is_metaclass; @@ -112,14 +112,18 @@ impl Violation for CustomTypeVarForSelf { } /// PYI019 -pub(crate) fn custom_type_var_instead_of_self( - checker: &Checker, - binding: &Binding, -) -> Option { +pub(crate) fn custom_type_var_instead_of_self(checker: &Checker, binding: &Binding) { let semantic = checker.semantic(); let current_scope = &semantic.scopes[binding.scope]; - let function_def = binding.statement(semantic)?.as_function_def_stmt()?; - let importer = checker.typing_importer("Self", PythonVersion::PY311)?; + let Some(function_def) = binding + .statement(semantic) + .and_then(|stmt| stmt.as_function_def_stmt()) + else { + return; + }; + let Some(importer) = checker.typing_importer("Self", PythonVersion::PY311) else { + return; + }; let ast::StmtFunctionDef { name: function_name, @@ -133,14 +137,17 @@ pub(crate) fn custom_type_var_instead_of_self( let type_params = type_params.as_deref(); // Given, e.g., `def foo(self: _S, arg: bytes)`, extract `_S`. - let self_or_cls_parameter = parameters - .posonlyargs - .iter() - .chain(¶meters.args) - .next()?; + let Some(self_or_cls_parameter) = parameters.posonlyargs.iter().chain(¶meters.args).next() + else { + return; + }; - let self_or_cls_annotation = self_or_cls_parameter.annotation()?; - let parent_class = current_scope.kind.as_class()?; + let Some(self_or_cls_annotation) = self_or_cls_parameter.annotation() else { + return; + }; + let Some(parent_class) = current_scope.kind.as_class() else { + return; + }; // Skip any abstract/static/overloaded methods, // and any methods in metaclasses @@ -148,7 +155,7 @@ pub(crate) fn custom_type_var_instead_of_self( || is_overload(decorator_list, semantic) || is_metaclass(parent_class, semantic).is_yes() { - return None; + return; } let function_kind = function_type::classify( @@ -169,17 +176,19 @@ pub(crate) fn custom_type_var_instead_of_self( self_annotation: self_or_cls_annotation, type_params, }), - FunctionType::Function | FunctionType::StaticMethod => return None, + FunctionType::Function | FunctionType::StaticMethod => return, }; - let custom_typevar = method.custom_typevar(semantic, binding.scope)?; + let Some(custom_typevar) = method.custom_typevar(semantic, binding.scope) else { + return; + }; let function_header_end = returns .as_deref() .map(Ranged::end) .unwrap_or_else(|| parameters.end()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CustomTypeVarForSelf { typevar_name: custom_typevar.name(checker.source()).to_string(), }, @@ -196,8 +205,6 @@ pub(crate) fn custom_type_var_instead_of_self( self_or_cls_annotation, ) }); - - Some(diagnostic) } #[derive(Debug)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs index 140df88d42d8fb..f74b7046e75881 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs @@ -1,6 +1,6 @@ use ruff_python_ast::ExprStringLiteral; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -63,8 +63,6 @@ pub(crate) fn docstring_in_stubs( Edit::range_deletion(docstring_range) }; - let fix = Fix::unsafe_edit(edit); - let diagnostic = Diagnostic::new(DocstringInStub, docstring_range).with_fix(fix); - - checker.report_diagnostic(diagnostic); + let mut diagnostic = checker.report_diagnostic(DocstringInStub, docstring_range); + diagnostic.set_fix(Fix::unsafe_edit(edit)); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs index e2f831d0a12baf..6a8cdc0a21e53c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -55,7 +55,7 @@ impl AlwaysFixableViolation for DuplicateLiteralMember { pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) { let mut seen_nodes: HashSet, _> = FxHashSet::default(); let mut unique_nodes: Vec<&Expr> = Vec::new(); - let mut diagnostics: Vec = Vec::new(); + let mut diagnostics = Vec::new(); // Adds a member to `literal_exprs` if it is a `Literal` annotation let mut check_for_duplicate_members = |expr: &'a Expr, _: &'a Expr| { @@ -63,7 +63,7 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) { if seen_nodes.insert(expr.into()) { unique_nodes.push(expr); } else { - diagnostics.push(Diagnostic::new( + diagnostics.push(checker.report_diagnostic( DuplicateLiteralMember { duplicate_name: checker.generator().expr(expr), }, @@ -108,8 +108,4 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) { diagnostic.set_fix(fix.clone()); } } - - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); - } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 6ed4436c443960..81eb281244500f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion}; @@ -62,7 +62,7 @@ impl Violation for DuplicateUnionMember { pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { let mut seen_nodes: HashSet, _> = FxHashSet::default(); let mut unique_nodes: Vec<&Expr> = Vec::new(); - let mut diagnostics: Vec = Vec::new(); + let mut diagnostics = Vec::new(); let mut union_type = UnionKind::TypingUnion; // Adds a member to `literal_exprs` if it is a `Literal` annotation @@ -75,7 +75,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { if seen_nodes.insert(expr.into()) { unique_nodes.push(expr); } else { - diagnostics.push(Diagnostic::new( + diagnostics.push(checker.report_diagnostic( DuplicateUnionMember { duplicate_name: checker.generator().expr(expr), }, @@ -137,11 +137,6 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { diagnostic.set_fix(fix.clone()); } } - - // Add all diagnostics to the checker - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); - } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs index b358ad801893ea..028ab7c5a6255e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Stmt, StmtExpr}; use ruff_text_size::Ranged; @@ -55,13 +55,13 @@ pub(crate) fn ellipsis_in_non_empty_class_body(checker: &Checker, body: &[Stmt]) }; if value.is_ellipsis_literal_expr() { - let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range()); + let mut diagnostic = + checker.report_diagnostic(EllipsisInNonEmptyClassBody, stmt.range()); let edit = fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs index 1829f4930b66e2..bb1f636bbd42b8 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs @@ -6,7 +6,7 @@ use ruff_python_ast::{ }; use smallvec::SmallVec; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{SemanticModel, analyze::visibility::is_overload}; @@ -181,13 +181,13 @@ pub(crate) fn bad_exit_annotation(checker: &Checker, function: &StmtFunctionDef) .skip(3) .filter(|parameter| parameter.default.is_none()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadExitAnnotation { func_kind, error_kind: ErrorKind::ArgsAfterFirstFourMustHaveDefault, }, parameter.range(), - )); + ); } // ...as should all keyword-only arguments. @@ -196,13 +196,13 @@ pub(crate) fn bad_exit_annotation(checker: &Checker, function: &StmtFunctionDef) .iter() .filter(|arg| arg.default.is_none()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadExitAnnotation { func_kind, error_kind: ErrorKind::AllKwargsMustHaveDefault, }, parameter.range(), - )); + ); } check_positional_args_for_non_overloaded_method(checker, &non_self_positional_args, func_kind); @@ -216,7 +216,7 @@ fn check_short_args_list(checker: &Checker, parameters: &Parameters, func_kind: .annotation() .filter(|ann| !is_object_or_unused(ann, checker.semantic())) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BadExitAnnotation { func_kind, error_kind: ErrorKind::StarArgsNotAnnotated, @@ -233,17 +233,15 @@ fn check_short_args_list(checker: &Checker, parameters: &Parameters, func_kind: let binding_edit = Edit::range_replacement(binding, annotation.range()); Ok(Fix::safe_edits(binding_edit, import_edit)) }); - - checker.report_diagnostic(diagnostic); } } else { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadExitAnnotation { func_kind, error_kind: ErrorKind::MissingArgs, }, parameters.range(), - )); + ); } } @@ -284,13 +282,13 @@ fn check_positional_args_for_non_overloaded_method( continue; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadExitAnnotation { func_kind: kind, error_kind: error_info, }, annotation.range(), - )); + ); } } @@ -423,13 +421,13 @@ fn check_positional_args_for_overloaded_method( } // Okay, neither of them match... - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadExitAnnotation { func_kind: kind, error_kind: ErrorKind::UnrecognizedExitOverload, }, parameters_range, - )); + ); } /// Return the non-`None` annotation element of a PEP 604-style union or `Optional` annotation. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 7f8d85871cf249..28ae4ef7014620 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -1,6 +1,6 @@ use ruff_python_ast::StmtImportFrom; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::{checkers::ast::Checker, fix, preview::is_fix_future_annotations_in_stub_enabled}; @@ -53,7 +53,7 @@ pub(crate) fn from_future_import(checker: &Checker, target: &StmtImportFrom) { return; } - let mut diagnostic = Diagnostic::new(FutureAnnotationsInStub, *range); + let mut diagnostic = checker.report_diagnostic(FutureAnnotationsInStub, *range); if is_fix_future_annotations_in_stub_enabled(checker.settings) { let stmt = checker.semantic().current_statement(); @@ -71,6 +71,4 @@ pub(crate) fn from_future_import(checker: &Checker, target: &StmtImportFrom) { Ok(Fix::safe_edit(edit)) }); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs index f4af4d5fefc1bb..5aa851fb8d0148 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, helpers::map_subscript}; use ruff_text_size::Ranged; @@ -92,14 +92,12 @@ pub(crate) fn generic_not_last_base_class(checker: &Checker, class_def: &ast::St return; } - let mut diagnostic = Diagnostic::new(GenericNotLastBaseClass, bases.range()); + let mut diagnostic = checker.report_diagnostic(GenericNotLastBaseClass, bases.range()); // No fix if multiple `Generic[]`s are seen in the class bases. if generic_base_iter.next().is_none() { diagnostic.try_set_fix(|| generate_fix(generic_base, bases, checker)); } - - checker.report_diagnostic(diagnostic); } fn generate_fix( diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs index bcaa46086ff9fe..d2a25aa0297385 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_text_size::Ranged; @@ -125,9 +125,6 @@ pub(crate) fn iter_method_return_iterable(checker: &Checker, definition: &Defini } }) { - checker.report_diagnostic(Diagnostic::new( - IterMethodReturnIterable { is_async }, - returns.range(), - )); + checker.report_diagnostic(IterMethodReturnIterable { is_async }, returns.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs index c657dce97e35d1..87cd4ff8c6073b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -65,7 +65,7 @@ pub(crate) fn no_return_argument_annotation(checker: &Checker, parameters: &ast: .filter_map(ast::AnyParameterRef::annotation) { if is_no_return(annotation, checker) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NoReturnArgumentAnnotationInStub { module: if checker.target_version() >= PythonVersion::PY311 { TypingModule::Typing @@ -74,7 +74,7 @@ pub(crate) fn no_return_argument_annotation(checker: &Checker, parameters: &ast: }, }, annotation.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs index f6ec5b567d47f6..02b5a61b0d65b6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::{self as ast, Stmt}; @@ -65,10 +65,9 @@ pub(crate) fn non_empty_stub_body(checker: &Checker, body: &[Stmt]) { } } - let mut diagnostic = Diagnostic::new(NonEmptyStubBody, stmt.range()); + let mut diagnostic = checker.report_diagnostic(NonEmptyStubBody, stmt.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), stmt.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs index 1329cabaac653d..ae2bf52d7fc5c6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::{Checker, TypingImporter}; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::PythonVersion; @@ -208,7 +208,7 @@ fn add_diagnostic( return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NonSelfReturnType { class_name: class_def.name.to_string(), method_name: method_name.to_string(), @@ -219,8 +219,6 @@ fn add_diagnostic( diagnostic.try_set_fix(|| { replace_with_self_fix(checker.semantic(), &importer, stmt, returns, class_def) }); - - checker.report_diagnostic(diagnostic); } fn replace_with_self_fix( diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs index fc258077a68d10..a9f805501d663e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_text_size::{Ranged, TextSize}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -50,10 +50,9 @@ pub(crate) fn numeric_literal_too_long(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new(NumericLiteralTooLong, expr.range()); + let mut diagnostic = checker.report_diagnostic(NumericLiteralTooLong, expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs index 2ba5d60dcd5fb6..830107855e9bb3 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -52,11 +52,10 @@ pub(crate) fn pass_in_class_body(checker: &Checker, class_def: &ast::StmtClassDe continue; } - let mut diagnostic = Diagnostic::new(PassInClassBody, stmt.range()); + let mut diagnostic = checker.report_diagnostic(PassInClassBody, stmt.range()); let edit = fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(), ))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs index c3ea8bccca77e2..8ecd9f23f834e7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; @@ -44,10 +44,9 @@ pub(crate) fn pass_statement_stub_body(checker: &Checker, body: &[Stmt]) { return; }; - let mut diagnostic = Diagnostic::new(PassStatementStubBody, pass.range()); + let mut diagnostic = checker.report_diagnostic(PassStatementStubBody, pass.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), pass.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs index f078e74cdd3d3f..344cb2274487c2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, ParameterWithDefault}; @@ -87,10 +87,7 @@ pub(crate) fn pep_484_positional_parameter(checker: &Checker, function_def: &ast if let Some(arg) = function_def.parameters.args.get(skip) { if is_old_style_positional_only(arg) { - checker.report_diagnostic(Diagnostic::new( - Pep484StylePositionalOnlyParameter, - arg.identifier(), - )); + checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs index 73c452fe23507d..fd1d872a613892 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -106,5 +106,5 @@ pub(crate) fn prefix_type_params(checker: &Checker, value: &Expr, targets: &[Exp return; }; - checker.report_diagnostic(Diagnostic::new(UnprefixedTypeParam { kind }, value.range())); + checker.report_diagnostic(UnprefixedTypeParam { kind }, value.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs index 6b00c41d921ffc..529e1e06746822 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs @@ -1,6 +1,6 @@ use ruff_text_size::TextRange; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -44,10 +44,9 @@ impl AlwaysFixableViolation for QuotedAnnotationInStub { /// PYI020 pub(crate) fn quoted_annotation_in_stub(checker: &Checker, annotation: &str, range: TextRange) { - let mut diagnostic = Diagnostic::new(QuotedAnnotationInStub, range); + let mut diagnostic = checker.report_diagnostic(QuotedAnnotationInStub, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( annotation.to_string(), range, ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs index 1d5761db77c6b0..50c34c48209447 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, comparable::ComparableExpr}; use ruff_text_size::{Ranged, TextSize}; @@ -97,7 +97,7 @@ pub(crate) fn redundant_final_literal(checker: &Checker, ann_assign: &ast::StmtA return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RedundantFinalLiteral { literal: SourceCodeSnippet::from_str(checker.locator().slice(literal.range())), }, @@ -113,8 +113,6 @@ pub(crate) fn redundant_final_literal(checker: &Checker, ann_assign: &ast::StmtA } else { diagnostic.set_fix(generate_fix(annotation, Some(literal), checker.locator())); } - - checker.report_diagnostic(diagnostic); } /// Generate a fix to convert a `Final[Literal[...]]` annotation to a `Final` annotation. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index fdb545bedfa061..7eb69aa53a6838 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -2,7 +2,7 @@ use std::fmt; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef}; use ruff_python_semantic::SemanticModel; @@ -90,7 +90,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { }; if builtin_types_in_union.contains(&literal_type) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RedundantLiteralUnion { literal: SourceCodeSnippet::from_str( checker.locator().slice(typing_literal_expr), @@ -98,7 +98,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { builtin_type: literal_type, }, typing_literal_expr.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs index a9f1b50226cd23..6d32946ca5485d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprNoneLiteral, Operator, PythonVersion, @@ -120,7 +120,7 @@ pub(crate) fn redundant_none_literal<'a>(checker: &Checker, literal_expr: &'a Ex // N.B. Applying the fix can leave an unused import to be fixed by the `unused-import` rule. for none_expr in none_exprs { let mut diagnostic = - Diagnostic::new(RedundantNoneLiteral { union_kind }, none_expr.range()); + checker.report_diagnostic(RedundantNoneLiteral { union_kind }, none_expr.range()); diagnostic.try_set_optional_fix(|| { create_fix( checker, @@ -136,7 +136,6 @@ pub(crate) fn redundant_none_literal<'a>(checker: &Checker, literal_expr: &'a Ex fix.map(|fix| fix.isolate(Checker::isolation(semantic.current_statement_id()))) }) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 23f7cb4a031810..3e5a47e60cb62b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -1,6 +1,6 @@ use bitflags::bitflags; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyParameterRef, Expr, ExprBinOp, Operator, Parameters, PythonVersion}; use ruff_python_semantic::analyze::typing::traverse_union; @@ -129,7 +129,8 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) { // Traverse the union a second time to construct a [`Fix`]. traverse_union(&mut remove_numeric_type, checker.semantic(), annotation); - let mut diagnostic = Diagnostic::new(RedundantNumericUnion { redundancy }, annotation.range()); + let mut diagnostic = + checker.report_diagnostic(RedundantNumericUnion { redundancy }, annotation.range()); // Mark [`Fix`] as unsafe when comments are in range. let applicability = if checker.comment_ranges().intersects(annotation.range()) { @@ -173,8 +174,6 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) { if let Some(fix) = fix { diagnostic.set_fix(fix); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, Eq, PartialEq)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index c67b1c78157507..bc3025d0f031d5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr, Operator, Parameters, Stmt, UnaryOp}; @@ -509,14 +509,13 @@ pub(crate) fn typed_argument_simple_defaults(checker: &Checker, parameters: &Par checker.locator(), checker.semantic(), ) { - let mut diagnostic = Diagnostic::new(TypedArgumentDefaultInStub, default.range()); + let mut diagnostic = + checker.report_diagnostic(TypedArgumentDefaultInStub, default.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), default.range(), ))); - - checker.report_diagnostic(diagnostic); } } } @@ -535,14 +534,13 @@ pub(crate) fn argument_simple_defaults(checker: &Checker, parameters: &Parameter checker.locator(), checker.semantic(), ) { - let mut diagnostic = Diagnostic::new(ArgumentDefaultInStub, default.range()); + let mut diagnostic = + checker.report_diagnostic(ArgumentDefaultInStub, default.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), default.range(), ))); - - checker.report_diagnostic(diagnostic); } } } @@ -569,12 +567,11 @@ pub(crate) fn assignment_default_in_stub(checker: &Checker, targets: &[Expr], va return; } - let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range()); + let mut diagnostic = checker.report_diagnostic(AssignmentDefaultInStub, value.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), value.range(), ))); - checker.report_diagnostic(diagnostic); } /// PYI015 @@ -603,12 +600,11 @@ pub(crate) fn annotated_assignment_default_in_stub( return; } - let mut diagnostic = Diagnostic::new(AssignmentDefaultInStub, value.range()); + let mut diagnostic = checker.report_diagnostic(AssignmentDefaultInStub, value.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), value.range(), ))); - checker.report_diagnostic(diagnostic); } /// PYI052 @@ -638,12 +634,12 @@ pub(crate) fn unannotated_assignment_in_stub(checker: &Checker, targets: &[Expr] return; } } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnannotatedAssignmentInStub { name: id.to_string(), }, value.range(), - )); + ); } /// PYI035 @@ -656,12 +652,12 @@ pub(crate) fn unassigned_special_variable_in_stub(checker: &Checker, target: &Ex return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnassignedSpecialVariableInStub { name: id.to_string(), }, stmt.range(), - )); + ); } /// PYI026 @@ -688,7 +684,7 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( TypeAliasWithoutAnnotation { module, name: id.to_string(), @@ -703,5 +699,4 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar [import_edit], )) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs index 5d0870b5540a5e..670d2022ec430d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs @@ -1,7 +1,7 @@ use ruff_python_ast as ast; use ruff_python_ast::Stmt; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_abstract; @@ -82,7 +82,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &Checker, stmt: &Stmt) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( StrOrReprDefinedInStub { name: name.to_string(), }, @@ -94,5 +94,4 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &Checker, stmt: &Stmt) { diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_parent_id(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index e5098c4ca34189..a9d60412f4a263 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::{self as ast, StringLike}; @@ -72,12 +72,11 @@ pub(crate) fn string_or_bytes_too_long(checker: &Checker, string: StringLike) { return; } - let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, string.range()); + let mut diagnostic = checker.report_diagnostic(StringOrBytesTooLong, string.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "...".to_string(), string.range(), ))); - checker.report_diagnostic(diagnostic); } /// Count the number of visible characters in an f-string. This accounts for diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs index 0d7f7728da3b58..e29aac789f2e0c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_python_ast::identifier::Identifier; @@ -41,9 +41,6 @@ impl Violation for StubBodyMultipleStatements { /// PYI048 pub(crate) fn stub_body_multiple_statements(checker: &Checker, stmt: &Stmt, body: &[Stmt]) { if body.len() > 1 { - checker.report_diagnostic(Diagnostic::new( - StubBodyMultipleStatements, - stmt.identifier(), - )); + checker.report_diagnostic(StubBodyMultipleStatements, stmt.identifier()); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs index 0454fe94911b8f..d2c27f8f938efc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -109,12 +109,12 @@ pub(crate) fn snake_case_type_alias(checker: &Checker, target: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SnakeCaseTypeAlias { name: id.to_string(), }, *range, - )); + ); } } @@ -125,11 +125,11 @@ pub(crate) fn t_suffixed_type_alias(checker: &Checker, target: &Expr) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TSuffixedTypeAlias { name: id.to_string(), }, *range, - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs index 244f600abfa1fc..d0bc616c095f19 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Imported; use ruff_python_semantic::{Binding, BindingKind, Scope}; @@ -57,26 +57,24 @@ impl Violation for UnaliasedCollectionsAbcSetImport { } /// PYI025 -pub(crate) fn unaliased_collections_abc_set_import( - checker: &Checker, - binding: &Binding, -) -> Option { +pub(crate) fn unaliased_collections_abc_set_import(checker: &Checker, binding: &Binding) { let BindingKind::FromImport(import) = &binding.kind else { - return None; + return; }; if !matches!( import.qualified_name().segments(), ["collections", "abc", "Set"] ) { - return None; + return; } let name = binding.name(checker.source()); if name == "AbstractSet" { - return None; + return; } - let mut diagnostic = Diagnostic::new(UnaliasedCollectionsAbcSetImport, binding.range()); + let mut diagnostic = + checker.report_diagnostic(UnaliasedCollectionsAbcSetImport, binding.range()); if checker.semantic().is_available("AbstractSet") { diagnostic.try_set_fix(|| { let semantic = checker.semantic(); @@ -87,7 +85,6 @@ pub(crate) fn unaliased_collections_abc_set_import( Ok(Fix::applicable_edits(edit, rest, applicability)) }); } - Some(diagnostic) } fn determine_applicability(binding: &Binding, scope: &Scope, checker: &Checker) -> Applicability { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index e3374dbe864a10..5283220dc1858a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::pep_604_union; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -124,7 +124,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryLiteralUnion { members: literal_exprs .iter() @@ -180,6 +180,4 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) { Fix::safe_edit(edit) } }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs index 3a4daa8e1626a8..cf6505fa6c4d00 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs @@ -1,5 +1,5 @@ use ast::ExprContext; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::pep_604_union; use ruff_python_ast::name::Name; @@ -116,7 +116,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { .map(|type_expr| Name::new(checker.locator().slice(type_expr))) .collect(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryTypeUnion { members: type_members.clone(), union_kind, @@ -218,8 +218,6 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { applicability, )); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs index 34675e1dce76cc..97914b05360a41 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -115,7 +115,7 @@ pub(crate) fn unrecognized_platform(checker: &Checker, test: &Expr) { // "in" might also make sense but we don't currently have one. if !matches!(op, CmpOp::Eq | CmpOp::NotEq) { if checker.enabled(Rule::UnrecognizedPlatformCheck) { - checker.report_diagnostic(Diagnostic::new(UnrecognizedPlatformCheck, test.range())); + checker.report_diagnostic(UnrecognizedPlatformCheck, test.range()); } return; } @@ -125,17 +125,17 @@ pub(crate) fn unrecognized_platform(checker: &Checker, test: &Expr) { // This protects against typos. if checker.enabled(Rule::UnrecognizedPlatformName) { if !matches!(value.to_str(), "linux" | "win32" | "cygwin" | "darwin") { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnrecognizedPlatformName { platform: value.to_string(), }, right.range(), - )); + ); } } } else { if checker.enabled(Rule::UnrecognizedPlatformCheck) { - checker.report_diagnostic(Diagnostic::new(UnrecognizedPlatformCheck, test.range())); + checker.report_diagnostic(UnrecognizedPlatformCheck, test.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs index 7b474858d888ac..cccf71d0734884 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::{self as ast, CmpOp, Expr, Int}; @@ -148,7 +148,7 @@ pub(crate) fn unrecognized_version_info(checker: &Checker, test: &Expr) { version_check(checker, expected, test, *op, comparator); } else { if checker.enabled(Rule::UnrecognizedVersionInfoCheck) { - checker.report_diagnostic(Diagnostic::new(UnrecognizedVersionInfoCheck, test.range())); + checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range()); } } } @@ -164,8 +164,7 @@ fn version_check( if expected == ExpectedComparator::MajorDigit { if !is_int_constant(comparator) { if checker.enabled(Rule::UnrecognizedVersionInfoCheck) { - checker - .report_diagnostic(Diagnostic::new(UnrecognizedVersionInfoCheck, test.range())); + checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range()); } } return; @@ -174,7 +173,7 @@ fn version_check( // Tuple comparison, e.g., `sys.version_info == (3, 4)`. let Expr::Tuple(tuple) = comparator else { if checker.enabled(Rule::UnrecognizedVersionInfoCheck) { - checker.report_diagnostic(Diagnostic::new(UnrecognizedVersionInfoCheck, test.range())); + checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range()); } return; }; @@ -183,13 +182,13 @@ fn version_check( // All tuple elements must be integers, e.g., `sys.version_info == (3, 4)` instead of // `sys.version_info == (3.0, 4)`. if checker.enabled(Rule::UnrecognizedVersionInfoCheck) { - checker.report_diagnostic(Diagnostic::new(UnrecognizedVersionInfoCheck, test.range())); + checker.report_diagnostic(UnrecognizedVersionInfoCheck, test.range()); } } else if tuple.len() > 2 { // Must compare against major and minor version only, e.g., `sys.version_info == (3, 4)` // instead of `sys.version_info == (3, 4, 0)`. if checker.enabled(Rule::PatchVersionComparison) { - checker.report_diagnostic(Diagnostic::new(PatchVersionComparison, test.range())); + checker.report_diagnostic(PatchVersionComparison, test.range()); } } @@ -202,10 +201,10 @@ fn version_check( }; if tuple.len() != expected_length { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( WrongTupleLengthVersionComparison { expected_length }, test.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs index f00b918155c618..082cf4bdd8349b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -69,12 +69,12 @@ pub(crate) fn unsupported_method_call_on_all(checker: &Checker, func: &Expr) { if !is_unsupported_method(attr.as_str()) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnsupportedMethodCallOnAll { name: attr.to_string(), }, func.range(), - )); + ); } fn is_unsupported_method(name: &str) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs index 03178a3a0a6f6b..5e481a724a251f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -224,21 +224,20 @@ pub(crate) fn unused_private_type_var(checker: &Checker, scope: &Scope) { continue; }; - let diagnostic = Diagnostic::new( - UnusedPrivateTypeVar { - type_var_like_name: id.to_string(), - type_var_like_kind: type_var_like_kind.to_string(), - }, - binding.range(), - ) - .with_fix(Fix::unsafe_edit(fix::edits::delete_stmt( - stmt, - None, - checker.locator(), - checker.indexer(), - ))); - - checker.report_diagnostic(diagnostic); + checker + .report_diagnostic( + UnusedPrivateTypeVar { + type_var_like_name: id.to_string(), + type_var_like_kind: type_var_like_kind.to_string(), + }, + binding.range(), + ) + .set_fix(Fix::unsafe_edit(fix::edits::delete_stmt( + stmt, + None, + checker.locator(), + checker.indexer(), + ))); } } @@ -271,12 +270,12 @@ pub(crate) fn unused_private_protocol(checker: &Checker, scope: &Scope) { continue; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnusedPrivateProtocol { name: class_def.name.to_string(), }, binding.range(), - )); + ); } } @@ -303,12 +302,12 @@ pub(crate) fn unused_private_type_alias(checker: &Checker, scope: &Scope) { continue; }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnusedPrivateTypeAlias { name: alias_name.to_string(), }, binding.range(), - )); + ); } } @@ -358,12 +357,12 @@ pub(crate) fn unused_private_typed_dict(checker: &Checker, scope: &Scope) { continue; }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnusedPrivateTypedDict { name: class_name.to_string(), }, binding.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs index 0d5200dbeec3df..71ba1e1de4abb8 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs @@ -8,7 +8,7 @@ use libcst_native::{ SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace, }; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::parenthesize::parenthesized_range; @@ -243,12 +243,12 @@ impl<'a> Visitor<'a> for ExceptionHandlerVisitor<'a, '_> { Expr::Name(ast::ExprName { id, .. }) => { if let Some(current_assert) = self.current_assert { if id.as_str() == self.exception_name { - self.checker.report_diagnostic(Diagnostic::new( + self.checker.report_diagnostic( PytestAssertInExcept { name: id.to_string(), }, current_assert.range(), - )); + ); } } } @@ -281,7 +281,7 @@ pub(crate) fn unittest_assertion( return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestUnittestAssertion { assertion: unittest_assert.to_string(), }, @@ -307,8 +307,6 @@ pub(crate) fn unittest_assertion( ))); } } - - checker.report_diagnostic(diagnostic); } /// ## What it does @@ -378,28 +376,23 @@ pub(crate) fn unittest_raises_assertion_call(checker: &Checker, call: &ast::Expr } } - if let Some(diagnostic) = unittest_raises_assertion(call, vec![], checker) { - checker.report_diagnostic(diagnostic); - } + unittest_raises_assertion(call, vec![], checker); } /// PT027 -pub(crate) fn unittest_raises_assertion_binding( - checker: &Checker, - binding: &Binding, -) -> Option { +pub(crate) fn unittest_raises_assertion_binding(checker: &Checker, binding: &Binding) { if !matches!(binding.kind, BindingKind::WithItemVar) { - return None; + return; } let semantic = checker.semantic(); - let Stmt::With(with) = binding.statement(semantic)? else { - return None; + let Some(Stmt::With(with)) = binding.statement(semantic) else { + return; }; - let Expr::Call(call) = corresponding_context_expr(binding, with)? else { - return None; + let Some(Expr::Call(call)) = corresponding_context_expr(binding, with) else { + return; }; let mut edits = vec![]; @@ -418,11 +411,13 @@ pub(crate) fn unittest_raises_assertion_binding( // ``` for reference_id in binding.references() { let reference = semantic.reference(reference_id); - let node_id = reference.expression_id()?; + let Some(node_id) = reference.expression_id() else { + return; + }; let mut ancestors = semantic.expressions(node_id).skip(1); - let Expr::Attribute(ast::ExprAttribute { attr, .. }) = ancestors.next()? else { + let Some(Expr::Attribute(ast::ExprAttribute { attr, .. })) = ancestors.next() else { continue; }; @@ -431,7 +426,7 @@ pub(crate) fn unittest_raises_assertion_binding( } } - unittest_raises_assertion(call, edits, checker) + unittest_raises_assertion(call, edits, checker); } fn corresponding_context_expr<'a>(binding: &Binding, with: &'a ast::StmtWith) -> Option<&'a Expr> { @@ -452,23 +447,19 @@ fn corresponding_context_expr<'a>(binding: &Binding, with: &'a ast::StmtWith) -> }) } -fn unittest_raises_assertion( - call: &ast::ExprCall, - extra_edits: Vec, - checker: &Checker, -) -> Option { +fn unittest_raises_assertion(call: &ast::ExprCall, extra_edits: Vec, checker: &Checker) { let Expr::Attribute(ast::ExprAttribute { attr, .. }) = call.func.as_ref() else { - return None; + return; }; if !matches!( attr.as_str(), "assertRaises" | "failUnlessRaises" | "assertRaisesRegex" | "assertRaisesRegexp" ) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestUnittestRaisesAssertion { assertion: attr.to_string(), }, @@ -496,8 +487,6 @@ fn unittest_raises_assertion( }); } } - - Some(diagnostic) } fn to_pytest_raises_args<'a>( @@ -589,7 +578,7 @@ fn to_pytest_raises_args<'a>( pub(crate) fn assert_falsy(checker: &Checker, stmt: &Stmt, test: &Expr) { let truthiness = Truthiness::from_expr(test, |id| checker.semantic().has_builtin_binding(id)); if truthiness.into_bool() == Some(false) { - checker.report_diagnostic(Diagnostic::new(PytestAssertAlwaysFalse, stmt.range())); + checker.report_diagnostic(PytestAssertAlwaysFalse, stmt.range()); } } @@ -824,7 +813,7 @@ fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> pub(crate) fn composite_condition(checker: &Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) { let composite = is_composite_condition(test); if matches!(composite, CompositionKind::Simple | CompositionKind::Mixed) { - let mut diagnostic = Diagnostic::new(PytestCompositeAssertion, stmt.range()); + let mut diagnostic = checker.report_diagnostic(PytestCompositeAssertion, stmt.range()); if matches!(composite, CompositionKind::Simple) && msg.is_none() && !checker.comment_ranges().intersects(stmt.range()) @@ -837,6 +826,5 @@ pub(crate) fn composite_condition(checker: &Checker, stmt: &Stmt, test: &Expr, m .map(Fix::unsafe_edit) }); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs index 7435e121840c6a..3c38e08c2b10e9 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -65,7 +65,7 @@ pub(crate) fn fail_call(checker: &Checker, call: &ast::ExprCall) { .or_else(|| call.arguments.find_argument_value("msg", 0)) .is_none_or(is_empty_or_null_string) { - checker.report_diagnostic(Diagnostic::new(PytestFailWithoutMessage, call.func.range())); + checker.report_diagnostic(PytestFailWithoutMessage, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index 3803100689a83c..c3b5e0cb998db6 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -1,5 +1,5 @@ use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Decorator; use ruff_python_ast::helpers::map_callable; @@ -672,12 +672,11 @@ fn pytest_fixture_parentheses( expected: Parentheses, actual: Parentheses, ) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestFixtureIncorrectParenthesesStyle { expected, actual }, decorator.range(), ); diagnostic.set_fix(fix); - checker.report_diagnostic(diagnostic); } /// PT001, PT002, PT003 @@ -706,20 +705,20 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor if checker.enabled(Rule::PytestFixturePositionalArgs) { if !arguments.args.is_empty() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PytestFixturePositionalArgs { function: func_name.to_string(), }, decorator.range(), - )); + ); } } if checker.enabled(Rule::PytestExtraneousScopeFunction) { if let Some(keyword) = arguments.find_keyword("scope") { if keyword_is_literal(keyword, "function") { - let mut diagnostic = - Diagnostic::new(PytestExtraneousScopeFunction, keyword.range()); + let mut diagnostic = checker + .report_diagnostic(PytestExtraneousScopeFunction, keyword.range()); diagnostic.try_set_fix(|| { edits::remove_argument( keyword, @@ -729,7 +728,6 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor ) .map(Fix::unsafe_edit) }); - checker.report_diagnostic(diagnostic); } } } @@ -775,7 +773,7 @@ fn check_fixture_returns(checker: &Checker, name: &str, body: &[Stmt], returns: if visitor.yield_statements.len() != 1 { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestUselessYieldFixture { name: name.to_string(), }, @@ -804,7 +802,6 @@ fn check_fixture_returns(checker: &Checker, name: &str, body: &[Stmt], returns: } else { diagnostic.set_fix(Fix::safe_edit(yield_edit)); } - checker.report_diagnostic(diagnostic); } } @@ -854,12 +851,12 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato for parameter in parameters.iter_non_variadic_params() { let name = parameter.name(); if name.starts_with('_') && !named_parametrize.contains(name.as_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PytestFixtureParamWithoutValue { name: name.to_string(), }, parameter.range(), - )); + ); } } } @@ -867,10 +864,7 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato /// PT020 fn check_fixture_decorator_name(checker: &Checker, decorator: &Decorator) { if is_pytest_yield_fixture(decorator, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( - PytestDeprecatedYieldFixture, - decorator.range(), - )); + checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range()); } } @@ -887,10 +881,7 @@ fn check_fixture_addfinalizer(checker: &Checker, parameters: &Parameters, body: } if let Some(addfinalizer) = visitor.addfinalizer_call { - checker.report_diagnostic(Diagnostic::new( - PytestFixtureFinalizerCallback, - addfinalizer.range(), - )); + checker.report_diagnostic(PytestFixtureFinalizerCallback, addfinalizer.range()); } } @@ -900,20 +891,18 @@ fn check_fixture_marks(checker: &Checker, decorators: &[Decorator]) { if checker.enabled(Rule::PytestUnnecessaryAsyncioMarkOnFixture) { if marker == "asyncio" { let mut diagnostic = - Diagnostic::new(PytestUnnecessaryAsyncioMarkOnFixture, expr.range()); + checker.report_diagnostic(PytestUnnecessaryAsyncioMarkOnFixture, expr.range()); let range = checker.locator().full_lines_range(expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); - checker.report_diagnostic(diagnostic); } } if checker.enabled(Rule::PytestErroneousUseFixturesOnFixture) { if marker == "usefixtures" { let mut diagnostic = - Diagnostic::new(PytestErroneousUseFixturesOnFixture, expr.range()); + checker.report_diagnostic(PytestErroneousUseFixturesOnFixture, expr.range()); let line_range = checker.locator().full_lines_range(expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(line_range))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs index 1b99c5c25dc3c8..e92b0f1fdc1f2c 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs @@ -1,9 +1,11 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for incorrect import of pytest. /// @@ -36,39 +38,26 @@ fn is_pytest_or_subpackage(imported_name: &str) -> bool { } /// PT013 -pub(crate) fn import(import_from: &Stmt, name: &str, asname: Option<&str>) -> Option { +pub(crate) fn import(checker: &Checker, import_from: &Stmt, name: &str, asname: Option<&str>) { if is_pytest_or_subpackage(name) { if let Some(alias) = asname { if alias != name { - return Some(Diagnostic::new( - PytestIncorrectPytestImport, - import_from.range(), - )); + checker.report_diagnostic(PytestIncorrectPytestImport, import_from.range()); } } } - None } /// PT013 -pub(crate) fn import_from( - import_from: &Stmt, - module: Option<&str>, - level: u32, -) -> Option { +pub(crate) fn import_from(checker: &Checker, import_from: &Stmt, module: Option<&str>, level: u32) { // If level is not zero or module is none, return if level != 0 { - return None; + return; } if let Some(module) = module { if is_pytest_or_subpackage(module) { - return Some(Diagnostic::new( - PytestIncorrectPytestImport, - import_from.range(), - )); + checker.report_diagnostic(PytestIncorrectPytestImport, import_from.range()); } } - - None } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs index 458aa5d846fdff..513398827d3a73 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -126,7 +126,7 @@ fn pytest_mark_parentheses( preferred: Parentheses, actual: Parentheses, ) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestIncorrectMarkParenthesesStyle { mark_name: marker.to_string(), expected_parens: preferred, @@ -135,7 +135,6 @@ fn pytest_mark_parentheses( decorator.range(), ); diagnostic.set_fix(fix); - checker.report_diagnostic(diagnostic); } fn check_mark_parentheses(checker: &Checker, decorator: &Decorator, marker: &str) { @@ -204,9 +203,9 @@ fn check_useless_usefixtures(checker: &Checker, decorator: &Decorator, marker: & _ => return, } - let mut diagnostic = Diagnostic::new(PytestUseFixturesWithoutParameters, decorator.range()); + let mut diagnostic = + checker.report_diagnostic(PytestUseFixturesWithoutParameters, decorator.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(decorator.range()))); - checker.report_diagnostic(diagnostic); } pub(crate) fn marks(checker: &Checker, decorators: &[Decorator]) { diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs index cfe9351ab4243e..a0f1d8f9bc9529 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -1,6 +1,6 @@ use rustc_hash::{FxBuildHasher, FxHashMap}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::parenthesize::parenthesized_range; @@ -349,7 +349,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr checker.locator().contents(), ) .unwrap_or(expr.range()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -375,7 +375,6 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr format!("({})", checker.generator().expr(&node)), name_range, ))); - checker.report_diagnostic(diagnostic); } types::ParametrizeNameType::List => { let name_range = get_parametrize_name_range( @@ -385,7 +384,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr checker.locator().contents(), ) .unwrap_or(expr.range()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -410,7 +409,6 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr checker.generator().expr(&node), name_range, ))); - checker.report_diagnostic(diagnostic); } types::ParametrizeNameType::Csv => {} } @@ -423,7 +421,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr match names_type { types::ParametrizeNameType::Tuple => {} types::ParametrizeNameType::List => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -439,10 +437,9 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr checker.generator().expr(&node), expr.range(), ))); - checker.report_diagnostic(diagnostic); } types::ParametrizeNameType::Csv => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -457,7 +454,6 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr expr.range(), ))); } - checker.report_diagnostic(diagnostic); } } } @@ -469,7 +465,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr match names_type { types::ParametrizeNameType::List => {} types::ParametrizeNameType::Tuple => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -486,10 +482,9 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr format!("({})", checker.generator().expr(&node)), expr.range(), ))); - checker.report_diagnostic(diagnostic); } types::ParametrizeNameType::Csv => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: false, expected: names_type, @@ -504,7 +499,6 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr expr.range(), ))); } - checker.report_diagnostic(diagnostic); } } } @@ -531,7 +525,7 @@ fn check_values(checker: &Checker, names: &Expr, values: &Expr) { match values { Expr::List(ast::ExprList { elts, .. }) => { if values_type != types::ParametrizeValuesType::List { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeValuesWrongType { values: values_type, row: values_row_type, @@ -570,7 +564,6 @@ fn check_values(checker: &Checker, names: &Expr, values: &Expr) { ); Fix::unsafe_edits(values_start, [values_end]) }); - checker.report_diagnostic(diagnostic); } if is_multi_named { @@ -579,7 +572,7 @@ fn check_values(checker: &Checker, names: &Expr, values: &Expr) { } Expr::Tuple(ast::ExprTuple { elts, .. }) => { if values_type != types::ParametrizeValuesType::Tuple { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeValuesWrongType { values: values_type, row: values_row_type, @@ -618,7 +611,6 @@ fn check_values(checker: &Checker, names: &Expr, values: &Expr) { Fix::unsafe_edits(values_start, [values_end]) }); - checker.report_diagnostic(diagnostic); } if is_multi_named { @@ -664,7 +656,7 @@ fn check_duplicates(checker: &Checker, values: &Expr) { let expr = ComparableExpr::from(element); seen.entry(expr) .and_modify(|index| { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestDuplicateParametrizeTestCases { index: *index }, element.range(), ); @@ -679,7 +671,6 @@ fn check_duplicates(checker: &Checker, values: &Expr) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(deletion_range))); } } - checker.report_diagnostic(diagnostic); }) .or_insert(index); prev = Some(element); @@ -687,7 +678,7 @@ fn check_duplicates(checker: &Checker, values: &Expr) { } fn handle_single_name(checker: &Checker, argnames: &Expr, value: &Expr, argvalues: &Expr) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeNamesWrongType { single_argument: true, expected: types::ParametrizeNameType::Csv, @@ -730,7 +721,6 @@ fn handle_single_name(checker: &Checker, argnames: &Expr, value: &Expr, argvalue Fix::safe_edits(argnames_edit, argvalues_edits) }; diagnostic.set_fix(fix); - checker.report_diagnostic(diagnostic); } /// Generate [`Edit`]s to unpack single-element lists or tuples in the given [`Expr`]. @@ -775,7 +765,7 @@ fn handle_value_rows( match elt { Expr::Tuple(ast::ExprTuple { elts, .. }) => { if values_row_type != types::ParametrizeValuesRowType::Tuple { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeValuesWrongType { values: values_type, row: values_row_type, @@ -813,12 +803,11 @@ fn handle_value_rows( let elt_end = Edit::replacement("]".into(), start, elt.end()); Fix::unsafe_edits(elt_start, [elt_end]) }); - checker.report_diagnostic(diagnostic); } } Expr::List(ast::ExprList { elts, .. }) => { if values_row_type != types::ParametrizeValuesRowType::List { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PytestParametrizeValuesWrongType { values: values_type, row: values_row_type, @@ -857,7 +846,6 @@ fn handle_value_rows( ); Fix::unsafe_edits(elt_start, [elt_end]) }); - checker.report_diagnostic(diagnostic); } } _ => {} diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs index 21876daa195ff0..2954531a9cb39b 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::visitor; @@ -6,6 +6,8 @@ use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, Parameters}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for mocked calls that use a dummy `lambda` function instead of /// `return_value`. @@ -73,19 +75,22 @@ impl<'a> Visitor<'a> for LambdaBodyVisitor<'a> { } } -fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option { +fn check_patch_call(checker: &Checker, call: &ast::ExprCall, index: usize) { if call.arguments.find_keyword("return_value").is_some() { - return None; + return; } - let ast::ExprLambda { + let Some(ast::ExprLambda { parameters, body, range: _, - } = call + }) = call .arguments - .find_argument_value("new", index)? - .as_lambda_expr()?; + .find_argument_value("new", index) + .and_then(|expr| expr.as_lambda_expr()) + else { + return; + }; // Walk the lambda body. If the lambda uses the arguments, then it's valid. if let Some(parameters) = parameters { @@ -95,16 +100,18 @@ fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option { }; visitor.visit_expr(body); if visitor.uses_args { - return None; + return; } } - Some(Diagnostic::new(PytestPatchWithLambda, call.func.range())) + checker.report_diagnostic(PytestPatchWithLambda, call.func.range()); } /// PT008 -pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option { - let name = UnqualifiedName::from_expr(&call.func)?; +pub(crate) fn patch_with_lambda(checker: &Checker, call: &ast::ExprCall) { + let Some(name) = UnqualifiedName::from_expr(&call.func) else { + return; + }; if matches!( name.segments(), @@ -118,7 +125,7 @@ pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option { "patch" ] | ["unittest", "mock", "patch"] ) { - check_patch_call(call, 1) + check_patch_call(checker, call, 1); } else if matches!( name.segments(), [ @@ -132,8 +139,6 @@ pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option { "object" ] | ["unittest", "mock", "patch", "object"] ) { - check_patch_call(call, 2) - } else { - None + check_patch_call(checker, call, 2); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs index 791843e2719bb7..4fc0d62833a2da 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_compound_statement; use ruff_python_ast::{self as ast, Expr, Stmt, WithItem}; @@ -178,10 +178,7 @@ pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) { .find_argument("expected_exception", 0) .is_none() { - checker.report_diagnostic(Diagnostic::new( - PytestRaisesWithoutException, - call.func.range(), - )); + checker.report_diagnostic(PytestRaisesWithoutException, call.func.range()); } } @@ -234,10 +231,7 @@ pub(crate) fn complex_raises(checker: &Checker, stmt: &Stmt, items: &[WithItem], }; if is_too_complex { - checker.report_diagnostic(Diagnostic::new( - PytestRaisesWithMultipleStatements, - stmt.range(), - )); + checker.report_diagnostic(PytestRaisesWithMultipleStatements, stmt.range()); } } } @@ -264,11 +258,11 @@ fn exception_needs_match(checker: &Checker, exception: &Expr) { .then_some(qualified_name) }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PytestRaisesTooBroad { exception: qualified_name, }, exception.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs index d6d7c4e6cdb747..1da6464166b85c 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_pytest_style::rules::helpers::is_likely_pytest_test; -use ruff_diagnostics::{Diagnostic, Edit, Fix, Violation}; +use ruff_diagnostics::{Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtFunctionDef; use ruff_text_size::Ranged; @@ -61,9 +61,10 @@ pub(crate) fn parameter_with_default_argument(checker: &Checker, function_def: & }; let parameter_name = parameter.name().to_string(); let kind = PytestParameterWithDefaultArgument { parameter_name }; - let diagnostic = Diagnostic::new(kind, default.range()); let edit = Edit::deletion(parameter.parameter.end(), parameter.end()); let fix = Fix::display_only_edit(edit); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(kind, default.range()) + .set_fix(fix); } } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs index a0ccb4a52d6731..ceadb3ba08189c 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_compound_statement; use ruff_python_ast::{self as ast, Expr, Stmt, WithItem}; @@ -174,10 +174,7 @@ pub(crate) fn warns_call(checker: &Checker, call: &ast::ExprCall) { if is_pytest_warns(&call.func, checker.semantic()) { if checker.enabled(Rule::PytestWarnsWithoutWarning) { if call.arguments.is_empty() { - checker.report_diagnostic(Diagnostic::new( - PytestWarnsWithoutWarning, - call.func.range(), - )); + checker.report_diagnostic(PytestWarnsWithoutWarning, call.func.range()); } } @@ -222,10 +219,7 @@ pub(crate) fn complex_warns(checker: &Checker, stmt: &Stmt, items: &[WithItem], }; if is_too_complex { - checker.report_diagnostic(Diagnostic::new( - PytestWarnsWithMultipleStatements, - stmt.range(), - )); + checker.report_diagnostic(PytestWarnsWithMultipleStatements, stmt.range()); } } } @@ -253,11 +247,11 @@ fn warning_needs_match(checker: &Checker, warning: &Expr) { .then_some(qualified_name) }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PytestWarnsTooBroad { warning: qualified_name, }, warning.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs index f1fc4c5a50abd8..64b1b83de25cd5 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs @@ -1,12 +1,11 @@ use flake8_quotes::helpers::{contains_escaped_quote, raw_contents, unescape_string}; use flake8_quotes::settings::Quote; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{Visitor, walk_f_string}; use ruff_python_ast::{self as ast, AnyStringFlags, PythonVersion, StringFlags, StringLike}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::flake8_quotes; @@ -94,25 +93,21 @@ impl<'a, 'b> AvoidableEscapedQuoteChecker<'a, 'b> { impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> { fn visit_string_literal(&mut self, string_literal: &ast::StringLiteral) { - if let Some(diagnostic) = check_string_or_bytes( - self.checker.locator(), + check_string_or_bytes( + self.checker, self.quotes_settings, string_literal.range(), AnyStringFlags::from(string_literal.flags), - ) { - self.checker.report_diagnostic(diagnostic); - } + ); } fn visit_bytes_literal(&mut self, bytes_literal: &ast::BytesLiteral) { - if let Some(diagnostic) = check_string_or_bytes( - self.checker.locator(), + check_string_or_bytes( + self.checker, self.quotes_settings, bytes_literal.range(), AnyStringFlags::from(bytes_literal.flags), - ) { - self.checker.report_diagnostic(diagnostic); - } + ); } fn visit_f_string(&mut self, f_string: &'_ ast::FString) { @@ -184,11 +179,7 @@ impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> { .literals() .any(|literal| contains_quote(literal, opposite_quote_char)) { - if let Some(diagnostic) = - check_f_string(self.checker.locator(), self.quotes_settings, f_string) - { - self.checker.report_diagnostic(diagnostic); - } + check_f_string(self.checker, self.quotes_settings, f_string); } walk_f_string(self, f_string); @@ -201,20 +192,22 @@ impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> { /// /// If the string kind is an f-string. fn check_string_or_bytes( - locator: &Locator, + checker: &Checker, quotes_settings: &flake8_quotes::settings::Settings, range: TextRange, flags: AnyStringFlags, -) -> Option { +) { assert!(!flags.is_f_string()); + let locator = checker.locator(); + if flags.is_triple_quoted() || flags.is_raw_string() { - return None; + return; } // Check if we're using the preferred quotation style. if Quote::from(flags.quote_style()) != quotes_settings.inline_quotes { - return None; + return; } let contents = raw_contents(locator.slice(range), flags); @@ -222,10 +215,10 @@ fn check_string_or_bytes( if !contains_escaped_quote(contents, quotes_settings.inline_quotes.as_char()) || contains_quote(contents, quotes_settings.inline_quotes.opposite().as_char()) { - return None; + return; } - let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, range); + let mut diagnostic = checker.report_diagnostic(AvoidableEscapedQuote, range); let fixed_contents = format!( "{prefix}{quote}{value}{quote}", prefix = flags.prefix(), @@ -236,24 +229,25 @@ fn check_string_or_bytes( fixed_contents, range, ))); - Some(diagnostic) } /// Checks for unnecessary escaped quotes in an f-string. fn check_f_string( - locator: &Locator, + checker: &Checker, quotes_settings: &flake8_quotes::settings::Settings, f_string: &ast::FString, -) -> Option { +) { + let locator = checker.locator(); + let ast::FString { flags, range, .. } = f_string; if flags.is_triple_quoted() || flags.prefix().is_raw() { - return None; + return; } // Check if we're using the preferred quotation style. if Quote::from(flags.quote_style()) != quotes_settings.inline_quotes { - return None; + return; } let quote_char = quotes_settings.inline_quotes.as_char(); @@ -272,7 +266,7 @@ fn check_f_string( } if edits.is_empty() { - return None; + return; } // Replacement for the f-string opening quote. We don't perform the check for raw and @@ -303,9 +297,9 @@ fn check_f_string( ), )); - Some( - Diagnostic::new(AvoidableEscapedQuote, *range).with_fix(Fix::safe_edits(start_edit, edits)), - ) + checker + .report_diagnostic(AvoidableEscapedQuote, *range) + .set_fix(Fix::safe_edits(start_edit, edits)); } #[derive(Debug, Default)] diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index 128bc327148b64..8ad5310f90d99f 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StringLike; use ruff_text_size::{Ranged, TextRange}; @@ -261,12 +261,12 @@ fn docstring(checker: &Checker, range: TextRange) { { // Fixing this would result in a one-sided multi-line docstring, which would // introduce a syntax error. - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadQuotesDocstring { preferred_quote: quotes_settings.docstring_quotes, }, range, - )); + ); return; } @@ -277,7 +277,7 @@ fn docstring(checker: &Checker, range: TextRange) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BadQuotesDocstring { preferred_quote: quotes_settings.docstring_quotes, }, @@ -298,7 +298,6 @@ fn docstring(checker: &Checker, range: TextRange) { fixed_contents, range, ))); - checker.report_diagnostic(diagnostic); } /// Q000, Q001 @@ -353,7 +352,7 @@ fn strings(checker: &Checker, sequence: &[TextRange]) { continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BadQuotesMultilineString { preferred_quote: quotes_settings.multiline_quotes, }, @@ -373,7 +372,6 @@ fn strings(checker: &Checker, sequence: &[TextRange]) { fixed_contents, *range, ))); - checker.report_diagnostic(diagnostic); } else if trivia.last_quote_char != quotes_settings.inline_quotes.as_char() // If we're not using the preferred type, only allow use to avoid escapes. && !relax_quote @@ -391,12 +389,12 @@ fn strings(checker: &Checker, sequence: &[TextRange]) { // ```python // ''"assert" ' SAM macro definitions ''' // ``` - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadQuotesInlineString { preferred_quote: quotes_settings.inline_quotes, }, *range, - )); + ); continue; } @@ -406,16 +404,16 @@ fn strings(checker: &Checker, sequence: &[TextRange]) { // ```python // ''"assert" ' SAM macro definitions ''' // ``` - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadQuotesInlineString { preferred_quote: quotes_settings.inline_quotes, }, *range, - )); + ); continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BadQuotesInlineString { preferred_quote: quotes_settings.inline_quotes, }, @@ -433,7 +431,6 @@ fn strings(checker: &Checker, sequence: &[TextRange]) { fixed_contents, *range, ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs index df7aed1032af70..8a58fccf00adc2 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs @@ -1,9 +1,8 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyStringFlags, StringFlags, StringLike}; use ruff_text_size::{Ranged, TextRange}; -use crate::Locator; use crate::checkers::ast::Checker; use super::super::helpers::{contains_escaped_quote, raw_contents, unescape_string}; @@ -51,33 +50,19 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi return; } - let locator = checker.locator(); - for part in string_like.parts() { match part { - ast::StringLikePart::String(string_literal) => { - if let Some(diagnostic) = check_string_or_bytes( - locator, - string_literal.range(), - AnyStringFlags::from(string_literal.flags), - ) { - checker.report_diagnostic(diagnostic); - } - } - ast::StringLikePart::Bytes(bytes_literal) => { - if let Some(diagnostic) = check_string_or_bytes( - locator, - bytes_literal.range(), - AnyStringFlags::from(bytes_literal.flags), - ) { - checker.report_diagnostic(diagnostic); - } - } - ast::StringLikePart::FString(f_string) => { - if let Some(diagnostic) = check_f_string(locator, f_string) { - checker.report_diagnostic(diagnostic); - } - } + ast::StringLikePart::String(string_literal) => check_string_or_bytes( + checker, + string_literal.range(), + AnyStringFlags::from(string_literal.flags), + ), + ast::StringLikePart::Bytes(bytes_literal) => check_string_or_bytes( + checker, + bytes_literal.range(), + AnyStringFlags::from(bytes_literal.flags), + ), + ast::StringLikePart::FString(f_string) => check_f_string(checker, f_string), } } } @@ -87,47 +72,42 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi /// # Panics /// /// If the string kind is an f-string. -fn check_string_or_bytes( - locator: &Locator, - range: TextRange, - flags: AnyStringFlags, -) -> Option { +fn check_string_or_bytes(checker: &Checker, range: TextRange, flags: AnyStringFlags) { assert!(!flags.is_f_string()); if flags.is_triple_quoted() || flags.is_raw_string() { - return None; + return; } - let contents = raw_contents(locator.slice(range), flags); + let contents = raw_contents(checker.locator().slice(range), flags); let quote = flags.quote_style(); let opposite_quote_char = quote.opposite().as_char(); if !contains_escaped_quote(contents, opposite_quote_char) { - return None; + return; } - let mut diagnostic = Diagnostic::new(UnnecessaryEscapedQuote, range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryEscapedQuote, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( flags .display_contents(&unescape_string(contents, opposite_quote_char)) .to_string(), range, ))); - Some(diagnostic) } /// Checks for unnecessary escaped quotes in an f-string. -fn check_f_string(locator: &Locator, f_string: &ast::FString) -> Option { +fn check_f_string(checker: &Checker, f_string: &ast::FString) { let ast::FString { flags, range, .. } = f_string; if flags.is_triple_quoted() || flags.prefix().is_raw() { - return None; + return; } let opposite_quote_char = flags.quote_style().opposite().as_char(); let mut edits = vec![]; for literal in f_string.elements.literals() { - let content = locator.slice(literal); + let content = checker.locator().slice(literal); if !contains_escaped_quote(content, opposite_quote_char) { continue; } @@ -138,9 +118,10 @@ fn check_f_string(locator: &Locator, f_string: &ast::FString) -> Option bool { } fn add_return_none(checker: &Checker, stmt: &Stmt, range: TextRange) { - let mut diagnostic = Diagnostic::new(ImplicitReturn, range); + let mut diagnostic = checker.report_diagnostic(ImplicitReturn, range); if let Some(indent) = indentation(checker.source(), stmt) { let mut content = String::new(); content.push_str(checker.stylist().line_ending().as_str()); @@ -466,7 +461,6 @@ fn add_return_none(checker: &Checker, stmt: &Stmt, range: TextRange) { end_of_last_statement(stmt, checker.locator()), ))); } - checker.report_diagnostic(diagnostic); } /// Returns a list of all implicit returns in the given statement. @@ -607,7 +601,7 @@ fn unnecessary_assign(checker: &Checker, stack: &Stack) { continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryAssign { name: assigned_id.to_string(), }, @@ -652,7 +646,6 @@ fn unnecessary_assign(checker: &Checker, stack: &Stack) { Ok(Fix::unsafe_edits(replace_assign, [delete_return])) }); - checker.report_diagnostic(diagnostic); } } @@ -667,83 +660,24 @@ fn superfluous_else_node( } else { Branch::Else }; + let range = elif_else_range(elif_else, checker.locator().contents()) + .unwrap_or_else(|| elif_else.range()); for child in if_elif_body { - if child.is_return_stmt() { - let mut diagnostic = Diagnostic::new( - SuperfluousElseReturn { branch }, - elif_else_range(elif_else, checker.locator().contents()) - .unwrap_or_else(|| elif_else.range()), - ); - if checker.enabled(diagnostic.rule()) { - diagnostic.try_set_fix(|| { - remove_else( - elif_else, - checker.locator(), - checker.indexer(), - checker.stylist(), - ) - }); - checker.report_diagnostic(diagnostic); - } - return true; + let diagnostic = if child.is_return_stmt() { + checker.report_diagnostic_if_enabled(SuperfluousElseReturn { branch }, range) } else if child.is_break_stmt() { - let mut diagnostic = Diagnostic::new( - SuperfluousElseBreak { branch }, - elif_else_range(elif_else, checker.locator().contents()) - .unwrap_or_else(|| elif_else.range()), - ); - if checker.enabled(diagnostic.rule()) { - diagnostic.try_set_fix(|| { - remove_else( - elif_else, - checker.locator(), - checker.indexer(), - checker.stylist(), - ) - }); - - checker.report_diagnostic(diagnostic); - } - return true; + checker.report_diagnostic_if_enabled(SuperfluousElseBreak { branch }, range) } else if child.is_raise_stmt() { - let mut diagnostic = Diagnostic::new( - SuperfluousElseRaise { branch }, - elif_else_range(elif_else, checker.locator().contents()) - .unwrap_or_else(|| elif_else.range()), - ); - if checker.enabled(diagnostic.rule()) { - diagnostic.try_set_fix(|| { - remove_else( - elif_else, - checker.locator(), - checker.indexer(), - checker.stylist(), - ) - }); - - checker.report_diagnostic(diagnostic); - } - return true; + checker.report_diagnostic_if_enabled(SuperfluousElseRaise { branch }, range) } else if child.is_continue_stmt() { - let mut diagnostic = Diagnostic::new( - SuperfluousElseContinue { branch }, - elif_else_range(elif_else, checker.locator().contents()) - .unwrap_or_else(|| elif_else.range()), - ); - if checker.enabled(diagnostic.rule()) { - diagnostic.try_set_fix(|| { - remove_else( - elif_else, - checker.locator(), - checker.indexer(), - checker.stylist(), - ) - }); - - checker.report_diagnostic(diagnostic); - } - return true; + checker.report_diagnostic_if_enabled(SuperfluousElseContinue { branch }, range) + } else { + continue; + }; + if let Some(mut d) = diagnostic { + d.try_set_fix(|| remove_else(checker, elif_else)); } + return true; } false } @@ -826,12 +760,11 @@ pub(crate) fn function(checker: &Checker, function_def: &ast::StmtFunctionDef) { } /// Generate a [`Fix`] to remove an `else` or `elif` clause. -fn remove_else( - elif_else: &ElifElseClause, - locator: &Locator, - indexer: &Indexer, - stylist: &Stylist, -) -> Result { +fn remove_else(checker: &Checker, elif_else: &ElifElseClause) -> Result { + let locator = checker.locator(); + let indexer = checker.indexer(); + let stylist = checker.stylist(); + if elif_else.test.is_some() { // Ex) `elif` -> `if` Ok(Fix::safe_edit(Edit::deletion( diff --git a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs index 5c3872d0fff67b..7bb4ff3b21ae55 100644 --- a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs +++ b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{is_dunder, is_sunder}; use ruff_python_ast::name::UnqualifiedName; @@ -144,12 +144,12 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) { } } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PrivateMemberAccess { access: attr.to_string(), }, expr.range(), - )); + ); } /// Check for the following cases: diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs index 76181cc76bddb1..07f2bcb20def58 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Arguments, BoolOp, CmpOp, Expr, ExprContext, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{Truthiness, contains_effect}; @@ -374,7 +374,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { } else { unreachable!("Indices should only contain `isinstance` calls") }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DuplicateIsinstanceCall { name: if let Expr::Name(ast::ExprName { id, .. }) = target { Some(id.to_string()) @@ -462,7 +462,6 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { expr.range(), ))); } - checker.report_diagnostic(diagnostic); } } } @@ -557,7 +556,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) { range: TextRange::default(), }; let in_expr = node2.into(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CompareWithTuple { replacement: checker.generator().expr(&in_expr), }, @@ -584,7 +583,6 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) { checker.generator().expr(&in_expr), expr.range(), ))); - checker.report_diagnostic(diagnostic); } } @@ -629,7 +627,7 @@ pub(crate) fn expr_and_not_expr(checker: &Checker, expr: &Expr) { for negate_expr in negated_expr { for non_negate_expr in &non_negated_expr { if let Some(id) = is_same_expr(negate_expr, non_negate_expr) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ExprAndNotExpr { name: id.to_string(), }, @@ -639,7 +637,6 @@ pub(crate) fn expr_and_not_expr(checker: &Checker, expr: &Expr) { "False".to_string(), expr.range(), ))); - checker.report_diagnostic(diagnostic); } } } @@ -686,7 +683,7 @@ pub(crate) fn expr_or_not_expr(checker: &Checker, expr: &Expr) { for negate_expr in negated_expr { for non_negate_expr in &non_negated_expr { if let Some(id) = is_same_expr(negate_expr, non_negate_expr) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ExprOrNotExpr { name: id.to_string(), }, @@ -696,7 +693,6 @@ pub(crate) fn expr_or_not_expr(checker: &Checker, expr: &Expr) { "True".to_string(), expr.range(), ))); - checker.report_diagnostic(diagnostic); } } } @@ -838,7 +834,7 @@ pub(crate) fn expr_or_true(checker: &Checker, expr: &Expr) { } if let Some((edit, remove)) = is_short_circuit(expr, BoolOp::Or, checker) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ExprOrTrue { expr: edit.content().unwrap_or_default().to_string(), remove, @@ -846,7 +842,6 @@ pub(crate) fn expr_or_true(checker: &Checker, expr: &Expr) { edit.range(), ); diagnostic.set_fix(Fix::unsafe_edit(edit)); - checker.report_diagnostic(diagnostic); } } @@ -857,7 +852,7 @@ pub(crate) fn expr_and_false(checker: &Checker, expr: &Expr) { } if let Some((edit, remove)) = is_short_circuit(expr, BoolOp::And, checker) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ExprAndFalse { expr: edit.content().unwrap_or_default().to_string(), remove, @@ -865,6 +860,5 @@ pub(crate) fn expr_and_false(checker: &Checker, expr: &Expr) { edit.range(), ); diagnostic.set_fix(Fix::unsafe_edit(edit)); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index ab832240bd4f8b..8a8b5268fd2b09 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, str_prefix::StringLiteralPre use ruff_text_size::{Ranged, TextRange}; use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::typing::is_dict; @@ -175,13 +175,13 @@ pub(crate) fn use_capital_environment_variables(checker: &Checker, expr: &Expr) return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UncapitalizedEnvironmentVariables { expected: SourceCodeSnippet::new(capital_env_var), actual: SourceCodeSnippet::new(env_var.to_string()), }, arg.range(), - )); + ); } fn check_os_environ_subscript(checker: &Checker, expr: &Expr) { @@ -215,7 +215,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UncapitalizedEnvironmentVariables { expected: SourceCodeSnippet::new(capital_env_var.clone()), actual: SourceCodeSnippet::new(env_var.to_string()), @@ -238,7 +238,6 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) { checker.generator().expr(&new_env_var), slice.range(), ))); - checker.report_diagnostic(diagnostic); } /// SIM910 @@ -298,7 +297,7 @@ pub(crate) fn dict_get_with_none_default(checker: &Checker, expr: &Expr) { ); let actual = checker.locator().slice(expr); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DictGetWithNoneDefault { expected: SourceCodeSnippet::new(expected.clone()), actual: SourceCodeSnippet::from_str(actual), @@ -309,5 +308,4 @@ pub(crate) fn dict_get_with_none_default(checker: &Checker, expr: &Expr) { expected, expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs index 10746d0588c873..8207ec4f3ff3cb 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{is_const_false, is_const_true}; use ruff_python_ast::name::Name; @@ -157,7 +157,7 @@ pub(crate) fn if_expr_with_true_false( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfExprWithTrueFalse { is_compare: test.is_compare_expr(), }, @@ -203,7 +203,6 @@ pub(crate) fn if_expr_with_true_false( expr.range(), ))); } - checker.report_diagnostic(diagnostic); } /// SIM211 @@ -218,7 +217,7 @@ pub(crate) fn if_expr_with_false_true( return; } - let mut diagnostic = Diagnostic::new(IfExprWithFalseTrue, expr.range()); + let mut diagnostic = checker.report_diagnostic(IfExprWithFalseTrue, expr.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr( &ast::ExprUnaryOp { @@ -230,7 +229,6 @@ pub(crate) fn if_expr_with_false_true( ), expr.range(), ))); - checker.report_diagnostic(diagnostic); } /// SIM212 @@ -264,7 +262,7 @@ pub(crate) fn twisted_arms_in_ifexpr( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfExprWithTwistedArms { expr_body: checker.generator().expr(body), expr_else: checker.generator().expr(orelse), @@ -284,5 +282,4 @@ pub(crate) fn twisted_arms_in_ifexpr( checker.generator().expr(&node3.into()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs index 9667ebf2bfac13..b615c4d9080ab8 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, CmpOp, Expr, ExprContext, Stmt, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_semantic::ScopeKind; @@ -173,7 +173,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp } } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NegateEqualOp { left: checker.generator().expr(left), right: checker.generator().expr(&comparators[0]), @@ -190,7 +190,6 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp checker.generator().expr(&node.into()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } /// SIM202 @@ -228,7 +227,7 @@ pub(crate) fn negation_with_not_equal_op( } } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NegateNotEqualOp { left: checker.generator().expr(left), right: checker.generator().expr(&comparators[0]), @@ -245,7 +244,6 @@ pub(crate) fn negation_with_not_equal_op( checker.generator().expr(&node.into()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } /// SIM208 @@ -265,7 +263,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DoubleNegation { expr: checker.generator().expr(operand), }, @@ -296,5 +294,4 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera expr.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs index 0dca0aed22c2fb..06cd73d100d0b6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs @@ -1,7 +1,7 @@ use anyhow::bail; use ast::Expr; -use ruff_diagnostics::{Diagnostic, Fix}; +use ruff_diagnostics::Fix; use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt, WithItem}; @@ -171,7 +171,7 @@ pub(crate) fn multiple_with_statements( return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MultipleWithStatements, TextRange::new(with_stmt.start(), colon.end()), ); @@ -208,6 +208,5 @@ pub(crate) fn multiple_with_statements( } }); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs index 3ba8c627759608..fc590eaf58c816 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use anyhow::{Result, bail}; use libcst_native::ParenthesizedNode; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{self as ast, ElifElseClause, Expr, Stmt, whitespace}; @@ -107,7 +107,7 @@ pub(crate) fn nested_if_statements( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CollapsibleIf, TextRange::new(nested_if.start(), colon.end()), ); @@ -138,7 +138,6 @@ pub(crate) fn nested_if_statements( } }); } - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy)] diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs index 07e45e6e82f36d..e3c66683b87d7c 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast, Expr, Int, Number, Operator, Stmt}; @@ -139,13 +139,12 @@ pub(crate) fn enumerate_for_loop(checker: &Checker, for_stmt: &ast::StmtFor) { continue; } - let diagnostic = Diagnostic::new( + checker.report_diagnostic( EnumerateForLoop { index: index.id.to_string(), }, stmt.range(), ); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs index d65085aae951d6..35b40b17d9ed23 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -216,7 +216,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfElseBlockInsteadOfDictGet { contents: contents.clone(), }, @@ -231,7 +231,6 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast stmt_if.range(), ))); } - checker.report_diagnostic(diagnostic); } /// SIM401 @@ -305,7 +304,7 @@ pub(crate) fn if_exp_instead_of_dict_get( let contents = checker.generator().expr(&fixed_node.into()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfElseBlockInsteadOfDictGet { contents: contents.clone(), }, @@ -320,5 +319,4 @@ pub(crate) fn if_exp_instead_of_dict_get( expr.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs index b1b4a384f456d4..55a2baee3ea981 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableLiteral; use ruff_python_ast::helpers::contains_effect; @@ -156,8 +156,5 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & return; } - checker.report_diagnostic(Diagnostic::new( - IfElseBlockInsteadOfDictLookup, - stmt_if.range(), - )); + checker.report_diagnostic(IfElseBlockInsteadOfDictLookup, stmt_if.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index f8d8e7cd6d095a..278f7e9b2640f7 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -233,7 +233,7 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfElseBlockInsteadOfIfExp { contents: contents.clone(), kind: assignment_kind, @@ -249,7 +249,6 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: stmt_if.range(), ))); } - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs index ab59e87955443a..76fa1d5ef029dd 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableStmt; use ruff_python_ast::parenthesize::parenthesized_range; @@ -87,7 +87,7 @@ pub(crate) fn if_with_same_arms(checker: &Checker, stmt_if: &ast::StmtIf) { continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfWithSameArms, TextRange::new(current_branch.start(), following_branch.end()), ); @@ -101,8 +101,6 @@ pub(crate) fn if_with_same_arms(checker: &Checker, stmt_if: &ast::StmtIf) { checker.comment_ranges(), ) }); - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs index 9a2b32e7eaa21b..5a21b7a9da47e6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_diagnostics::{Applicability, Edit}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::AnyNodeRef; @@ -103,7 +103,7 @@ fn key_in_dict(checker: &Checker, left: &Expr, right: &Expr, operator: CmpOp, pa ) .unwrap_or(right.range()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( InDictKeys { operator: operator.as_str().to_string(), }, @@ -158,7 +158,6 @@ fn key_in_dict(checker: &Checker, left: &Expr, right: &Expr, operator: CmpOp, pa )); } } - checker.report_diagnostic(diagnostic); } /// SIM118 in a `for` loop. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 427561c767c427..40c5cc9f5a85f3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::traversal; @@ -305,7 +305,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { .as_ref() .map(|expr| checker.generator().expr(expr)); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NeedlessBool { condition: condition.map(SourceCodeSnippet::new), negate: inverted, @@ -318,7 +318,6 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { range, ))); } - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index e46086f1fc479a..3c664654e62fc4 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_text_size::Ranged; @@ -265,8 +265,5 @@ pub(crate) fn open_file_with_context_handler(checker: &Checker, call: &ast::Expr } } - checker.report_diagnostic(Diagnostic::new( - OpenFileWithContextHandler, - call.func.range(), - )); + checker.report_diagnostic(OpenFileWithContextHandler, call.func.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index 4530751a69eb28..c021a8ca0c1d75 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::name::Name; @@ -111,7 +111,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ReimplementedBuiltin { replacement: contents.to_string(), }, @@ -124,7 +124,6 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { terminal.stmt.end(), ))); } - checker.report_diagnostic(diagnostic); } // Replace with `all`. (false, true) => { @@ -203,7 +202,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ReimplementedBuiltin { replacement: contents.to_string(), }, @@ -216,7 +215,6 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { terminal.stmt.end(), ))); } - checker.report_diagnostic(diagnostic); } _ => {} } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs index b050859cec6a7d..707e9b3ad92475 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -69,10 +69,7 @@ pub(crate) fn return_in_try_except_finally( if try_has_return || except_has_return { if let Some(finally_return) = find_return(finalbody) { - checker.report_diagnostic(Diagnostic::new( - ReturnInTryExceptFinally, - finally_return.range(), - )); + checker.report_diagnostic(ReturnInTryExceptFinally, finally_return.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs index 1e1d9aefca5a7e..780d985b47614c 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral, StringLiteralFlags, @@ -102,7 +102,7 @@ pub(crate) fn split_static_string( split_default(str_value, maxsplit_value, direction) }; - let mut diagnostic = Diagnostic::new(SplitStaticString, call.range()); + let mut diagnostic = checker.report_diagnostic(SplitStaticString, call.range()); if let Some(ref replacement_expr) = split_replacement { diagnostic.set_fix(Fix::applicable_edit( Edit::range_replacement(checker.generator().expr(replacement_expr), call.range()), @@ -114,7 +114,6 @@ pub(crate) fn split_static_string( }, )); } - checker.report_diagnostic(diagnostic); } fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr { diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs index c0a59746fd4d6f..03d01cc898264b 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers; use ruff_python_ast::name::UnqualifiedName; @@ -120,7 +120,7 @@ pub(crate) fn suppressible_exception( handler_names.join(", ") }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( SuppressibleException { exception: exception.clone(), }, @@ -147,5 +147,4 @@ pub(crate) fn suppressible_exception( )) }); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs index 7f0760073d31bc..f741bbeb148a74 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs @@ -3,7 +3,7 @@ use std::cmp; use anyhow::Result; use libcst_native::CompOp; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr, UnaryOp}; use ruff_python_codegen::Stylist; @@ -225,7 +225,7 @@ pub(crate) fn yoda_conditions( } if let Ok(suggestion) = reverse_comparison(expr, checker.locator(), checker.stylist()) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( YodaConditions { suggestion: Some(SourceCodeSnippet::new(suggestion.clone())), }, @@ -235,11 +235,7 @@ pub(crate) fn yoda_conditions( pad(suggestion, expr.range(), checker.locator()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } else { - checker.report_diagnostic(Diagnostic::new( - YodaConditions { suggestion: None }, - expr.range(), - )); + checker.report_diagnostic(YodaConditions { suggestion: None }, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs index 375276306a2e73..31dbcf72663d20 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs @@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_text_size::Ranged; use crate::{checkers::ast::Checker, fix::snippet::SourceCodeSnippet}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_python_semantic::analyze::typing::is_dict; /// ## What it does @@ -104,7 +104,7 @@ pub(crate) fn zip_dict_keys_and_values(checker: &Checker, expr: &ast::ExprCall) let expected = format!("{}.items()", checker.locator().slice(var1)); let actual = checker.locator().slice(expr); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ZipDictKeysAndValues { expected: SourceCodeSnippet::new(expected.clone()), actual: SourceCodeSnippet::from_str(actual), @@ -115,7 +115,6 @@ pub(crate) fn zip_dict_keys_and_values(checker: &Checker, expr: &ast::ExprCall) expected, expr.range(), ))); - checker.report_diagnostic(diagnostic); } fn get_var_attr(expr: &Expr) -> Option<(&ExprName, &Identifier)> { diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs index 1433439578286e..b10f8473d1ad49 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt, StmtClassDef, identifier::Identifier}; use ruff_python_semantic::SemanticModel; @@ -88,10 +88,10 @@ pub(crate) fn no_slots_in_namedtuple_subclass( if let Some(namedtuple_kind) = namedtuple_base(bases, checker.semantic()) { if !has_slots(&class.body) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NoSlotsInNamedtupleSubclass(namedtuple_kind), stmt.identifier(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs index c0338506488201..3603db1709ed85 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{Arguments, Expr, Stmt, StmtClassDef}; @@ -73,7 +73,7 @@ pub(crate) fn no_slots_in_str_subclass(checker: &Checker, stmt: &Stmt, class: &S return; } - checker.report_diagnostic(Diagnostic::new(NoSlotsInStrSubclass, stmt.identifier())); + checker.report_diagnostic(NoSlotsInStrSubclass, stmt.identifier()); } /// Return `true` if the class is a subclass of `str`. diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs index 3c8b5618439952..c95db4be111191 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Arguments, Stmt, StmtClassDef}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::identifier::Identifier; @@ -66,7 +66,7 @@ pub(crate) fn no_slots_in_tuple_subclass(checker: &Checker, stmt: &Stmt, class: semantic.match_builtin_expr(base, "tuple") || semantic.match_typing_expr(base, "Tuple") }) { if !has_slots(&class.body) { - checker.report_diagnostic(Diagnostic::new(NoSlotsInTupleSubclass, stmt.identifier())); + checker.report_diagnostic(NoSlotsInTupleSubclass, stmt.identifier()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs index 05cc14b992dfa3..9c65b36a83a261 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; @@ -43,13 +43,13 @@ pub(crate) fn banned_api(checker: &Checker, policy: &NameMatchPolicy, let banned_api = &checker.settings.flake8_tidy_imports.banned_api; if let Some(banned_module) = policy.find(banned_api.keys().map(AsRef::as_ref)) { if let Some(reason) = banned_api.get(&banned_module) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BannedApi { name: banned_module, message: reason.msg.to_string(), }, node.range(), - )); + ); } } } @@ -71,12 +71,12 @@ pub(crate) fn banned_attribute_access(checker: &Checker, expr: &Expr) { }) }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BannedApi { name: banned_path.to_string(), message: ban.msg.to_string(), }, expr.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs index cc6d47776d0c5d..dee32f183171ee 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_python_ast::{Alias, AnyNodeRef, Stmt, StmtImport, StmtImportFrom}; @@ -68,12 +68,12 @@ pub(crate) fn banned_module_level_imports(checker: &Checker, stmt: &Stmt) { .flake8_tidy_imports .banned_module_level_imports(), ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BannedModuleLevelImports { name: banned_module, }, node.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs index de8dfa69fc3c9b..f181a894c707b3 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_python_codegen::Generator; @@ -117,20 +117,18 @@ pub(crate) fn banned_relative_import( module: Option<&str>, module_path: Option<&[String]>, strictness: Strictness, -) -> Option { +) { let strictness_level = match strictness { Strictness::All => 0, Strictness::Parents => 1, }; if level > strictness_level { - let mut diagnostic = Diagnostic::new(RelativeImports { strictness }, stmt.range()); + let mut diagnostic = + checker.report_diagnostic(RelativeImports { strictness }, stmt.range()); if let Some(fix) = fix_banned_relative_import(stmt, level, module, module_path, checker.generator()) { diagnostic.set_fix(fix); } - Some(diagnostic) - } else { - None } } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs index 54da378b8ad170..11f11825a67841 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs @@ -1,6 +1,6 @@ use ruff_python_ast as ast; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; @@ -63,7 +63,7 @@ pub(crate) fn empty_type_checking_block(checker: &Checker, stmt: &ast::StmtIf) { return; } - let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range()); + let mut diagnostic = checker.report_diagnostic(EmptyTypeCheckingBlock, stmt.range()); // Delete the entire type-checking block. let stmt = checker.semantic().current_statement(); let parent = checker.semantic().current_statement_parent(); @@ -71,5 +71,4 @@ pub(crate) fn empty_type_checking_block(checker: &Checker, stmt: &ast::StmtIf) { diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_parent_id(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs index ec6ffee598b65c..5dbb249f7c2f94 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -62,7 +62,7 @@ pub(crate) fn runtime_cast_value(checker: &Checker, type_expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new(RuntimeCastValue, type_expr.range()); + let mut diagnostic = checker.report_diagnostic(RuntimeCastValue, type_expr.range()); let edit = quote_type_expression( type_expr, checker.semantic(), @@ -75,5 +75,4 @@ pub(crate) fn runtime_cast_value(checker: &Checker, type_expr: &Expr) { } else { diagnostic.set_fix(Fix::safe_edit(edit)); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs index dc3b337984665a..1d9cb39e0841f1 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use anyhow::Result; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Imported, NodeId, Scope, ScopeId}; use ruff_text_size::Ranged; @@ -198,7 +198,7 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S .. } in imports { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RuntimeImportInTypeCheckingBlock { qualified_name: import.qualified_name().to_string(), strategy: Strategy::MoveImport, @@ -211,7 +211,6 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S if let Some(fix) = fix.as_ref() { diagnostic.set_fix(fix.clone()); } - checker.report_diagnostic(diagnostic); } } @@ -227,7 +226,7 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S .. } in imports { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RuntimeImportInTypeCheckingBlock { qualified_name: import.qualified_name().to_string(), strategy: Strategy::QuoteUsages, @@ -238,7 +237,6 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S diagnostic.set_parent(range.start()); } diagnostic.set_fix(fix.clone()); - checker.report_diagnostic(diagnostic); } } @@ -252,7 +250,7 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S .. } in imports { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RuntimeImportInTypeCheckingBlock { qualified_name: import.qualified_name().to_string(), strategy: Strategy::MoveImport, @@ -262,7 +260,6 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S if let Some(range) = parent_range { diagnostic.set_parent(range.start()); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs index d5142b6dbeb38e..42ac5c1b917fa6 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Expr, Operator}; @@ -66,7 +66,7 @@ pub(crate) fn runtime_string_union(checker: &Checker, expr: &Expr) { traverse_op(expr, &mut strings); for string in strings { - checker.report_diagnostic(Diagnostic::new(RuntimeStringUnion, string.range())); + checker.report_diagnostic(RuntimeStringUnion, string.range()); } } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs index 3f0572aa63755e..f478fda4c990f7 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs @@ -1,5 +1,5 @@ use ast::{ExprContext, Operator}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Expr, Stmt}; @@ -179,11 +179,9 @@ pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) { checker.default_string_flags(), ); for name in names { - checker.report_diagnostic( - Diagnostic::new(UnquotedTypeAlias, name.range()) - .with_parent(parent) - .with_fix(Fix::unsafe_edit(edit.clone())), - ); + let mut diagnostic = checker.report_diagnostic(UnquotedTypeAlias, name.range()); + diagnostic.set_parent(parent); + diagnostic.set_fix(Fix::unsafe_edit(edit.clone())); } } @@ -288,14 +286,13 @@ pub(crate) fn quoted_type_alias( } let range = annotation_expr.range(); - let mut diagnostic = Diagnostic::new(QuotedTypeAlias, range); + let mut diagnostic = checker.report_diagnostic(QuotedTypeAlias, range); let edit = Edit::range_replacement(annotation_expr.value.to_string(), range); if checker.comment_ranges().intersects(range) { diagnostic.set_fix(Fix::unsafe_edit(edit)); } else { diagnostic.set_fix(Fix::safe_edit(edit)); } - checker.report_diagnostic(diagnostic); } /// Traverses the type expression and checks if the expression can safely diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index 61aaa0f344a1bf..d4a183e9055808 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -3,12 +3,12 @@ use std::borrow::Cow; use anyhow::Result; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, Imported, NodeId, Scope}; use ruff_text_size::{Ranged, TextRange}; -use crate::checkers::ast::Checker; +use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::codes::Rule; use crate::fix; use crate::importer::ImportedMembers; @@ -393,15 +393,18 @@ pub(crate) fn typing_only_runtime_import( .. } in imports { - let mut diagnostic = - diagnostic_for(import_type, import.qualified_name().to_string(), range); + let mut diagnostic = diagnostic_for( + checker, + import_type, + import.qualified_name().to_string(), + range, + ); if let Some(range) = parent_range { diagnostic.set_parent(range.start()); } if let Some(fix) = fix.as_ref() { diagnostic.set_fix(fix.clone()); } - checker.report_diagnostic(diagnostic); } } @@ -415,12 +418,15 @@ pub(crate) fn typing_only_runtime_import( .. } in imports { - let mut diagnostic = - diagnostic_for(import_type, import.qualified_name().to_string(), range); + let mut diagnostic = diagnostic_for( + checker, + import_type, + import.qualified_name().to_string(), + range, + ); if let Some(range) = parent_range { diagnostic.set_parent(range.start()); } - checker.report_diagnostic(diagnostic); } } } @@ -436,16 +442,21 @@ fn rule_for(import_type: ImportType) -> Rule { } /// Return the [`Diagnostic`] for the given import type. -fn diagnostic_for(import_type: ImportType, qualified_name: String, range: TextRange) -> Diagnostic { +fn diagnostic_for<'a, 'b>( + checker: &'a Checker<'b>, + import_type: ImportType, + qualified_name: String, + range: TextRange, +) -> DiagnosticGuard<'a, 'b> { match import_type { ImportType::StandardLibrary => { - Diagnostic::new(TypingOnlyStandardLibraryImport { qualified_name }, range) + checker.report_diagnostic(TypingOnlyStandardLibraryImport { qualified_name }, range) } ImportType::ThirdParty => { - Diagnostic::new(TypingOnlyThirdPartyImport { qualified_name }, range) + checker.report_diagnostic(TypingOnlyThirdPartyImport { qualified_name }, range) } ImportType::FirstParty => { - Diagnostic::new(TypingOnlyFirstPartyImport { qualified_name }, range) + checker.report_diagnostic(TypingOnlyFirstPartyImport { qualified_name }, range) } _ => unreachable!("Unexpected import type"), } diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs index d1216ba670d4db..b6ef9c1549fde6 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs @@ -1,7 +1,7 @@ use ruff_python_ast as ast; use ruff_python_ast::{Parameter, Parameters, Stmt, StmtExpr, StmtFunctionDef, StmtRaise}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::{function_type, visibility}; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; @@ -222,14 +222,18 @@ enum Argumentable { } impl Argumentable { - fn check_for(self, name: String, range: TextRange) -> Diagnostic { + fn check_for(self, checker: &Checker, name: String, range: TextRange) { match self { - Self::Function => Diagnostic::new(UnusedFunctionArgument { name }, range), - Self::Method => Diagnostic::new(UnusedMethodArgument { name }, range), - Self::ClassMethod => Diagnostic::new(UnusedClassMethodArgument { name }, range), - Self::StaticMethod => Diagnostic::new(UnusedStaticMethodArgument { name }, range), - Self::Lambda => Diagnostic::new(UnusedLambdaArgument { name }, range), - } + Self::Function => checker.report_diagnostic(UnusedFunctionArgument { name }, range), + Self::Method => checker.report_diagnostic(UnusedMethodArgument { name }, range), + Self::ClassMethod => { + checker.report_diagnostic(UnusedClassMethodArgument { name }, range) + } + Self::StaticMethod => { + checker.report_diagnostic(UnusedStaticMethodArgument { name }, range) + } + Self::Lambda => checker.report_diagnostic(UnusedLambdaArgument { name }, range), + }; } const fn rule_code(self) -> Rule { @@ -315,8 +319,7 @@ fn call<'a>( && binding.is_unused() && !dummy_variable_rgx.is_match(arg.name()) { - checker - .report_diagnostic(argumentable.check_for(arg.name.to_string(), binding.range())); + argumentable.check_for(checker, arg.name.to_string(), binding.range()); } } } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs index a3afa9374c1daf..167700d37e9483 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, StringFlags}; use ruff_python_semantic::SemanticModel; @@ -108,7 +108,8 @@ pub(crate) fn invalid_pathlib_with_suffix(checker: &Checker, call: &ast::ExprCal }; let single_dot = string_value == "."; - let mut diagnostic = Diagnostic::new(InvalidPathlibWithSuffix { single_dot }, call.range); + let mut diagnostic = + checker.report_diagnostic(InvalidPathlibWithSuffix { single_dot }, call.range); if !single_dot { let after_leading_quote = string.start() + first_part.flags.opener_len(); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( @@ -116,8 +117,6 @@ pub(crate) fn invalid_pathlib_with_suffix(checker: &Checker, call: &ast::ExprCal after_leading_quote, ))); } - - checker.report_diagnostic(diagnostic); } fn is_path_with_suffix_call(semantic: &SemanticModel, func: &ast::Expr) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs index 27d74c80cbb8ce..a35ca49b537173 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute}; use ruff_python_semantic::Modules; @@ -94,5 +94,5 @@ pub(crate) fn os_sep_split(checker: &Checker, call: &ast::ExprCall) { return; } - checker.report_diagnostic(Diagnostic::new(OsSepSplit, attr.range())); + checker.report_diagnostic(OsSepSplit, attr.range()); } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs index e7c3a75fd4c299..a9475bf6bf7ad1 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{Expr, ExprBinOp, ExprCall, Operator}; @@ -83,7 +83,7 @@ pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprC return; } - let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, arg.range()); + let mut diagnostic = checker.report_diagnostic(PathConstructorCurrentDirectory, arg.range()); match parent_and_next_path_fragment_range( checker.semantic(), @@ -111,8 +111,6 @@ pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprC Ok(Fix::applicable_edit(edit, applicability(call.range()))) }), } - - checker.report_diagnostic(diagnostic); } fn parent_and_next_path_fragment_range( diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 243729c70fd53e..ec64457faeb50b 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -1,11 +1,9 @@ -use ruff_diagnostics::Diagnostic; use ruff_python_ast::{self as ast, Expr, ExprBooleanLiteral, ExprCall}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::registry::AsRule; use crate::rules::flake8_use_pathlib::rules::{ Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize, }; @@ -23,9 +21,9 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { }; let range = call.func.range(); - let diagnostic = match qualified_name.segments() { + match qualified_name.segments() { // PTH100 - ["os", "path", "abspath"] => Diagnostic::new(OsPathAbspath, range), + ["os", "path", "abspath"] => checker.report_diagnostic_if_enabled(OsPathAbspath, range), // PTH101 ["os", "chmod"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -42,10 +40,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(OsChmod, range) + checker.report_diagnostic_if_enabled(OsChmod, range) } // PTH102 - ["os", "makedirs"] => Diagnostic::new(OsMakedirs, range), + ["os", "makedirs"] => checker.report_diagnostic_if_enabled(OsMakedirs, range), // PTH103 ["os", "mkdir"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -57,7 +55,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsMkdir, range) + checker.report_diagnostic_if_enabled(OsMkdir, range) } // PTH104 ["os", "rename"] => { @@ -73,7 +71,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(OsRename, range) + checker.report_diagnostic_if_enabled(OsRename, range) } // PTH105 ["os", "replace"] => { @@ -89,7 +87,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(OsReplace, range) + checker.report_diagnostic_if_enabled(OsReplace, range) } // PTH106 ["os", "rmdir"] => { @@ -102,7 +100,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsRmdir, range) + checker.report_diagnostic_if_enabled(OsRmdir, range) } // PTH107 ["os", "remove"] => { @@ -115,7 +113,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsRemove, range) + checker.report_diagnostic_if_enabled(OsRemove, range) } // PTH108 ["os", "unlink"] => { @@ -128,21 +126,23 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsUnlink, range) + checker.report_diagnostic_if_enabled(OsUnlink, range) } // PTH109 - ["os", "getcwd"] => Diagnostic::new(OsGetcwd, range), - ["os", "getcwdb"] => Diagnostic::new(OsGetcwd, range), + ["os", "getcwd"] => checker.report_diagnostic_if_enabled(OsGetcwd, range), + ["os", "getcwdb"] => checker.report_diagnostic_if_enabled(OsGetcwd, range), // PTH110 - ["os", "path", "exists"] => Diagnostic::new(OsPathExists, range), + ["os", "path", "exists"] => checker.report_diagnostic_if_enabled(OsPathExists, range), // PTH111 - ["os", "path", "expanduser"] => Diagnostic::new(OsPathExpanduser, range), + ["os", "path", "expanduser"] => { + checker.report_diagnostic_if_enabled(OsPathExpanduser, range) + } // PTH112 - ["os", "path", "isdir"] => Diagnostic::new(OsPathIsdir, range), + ["os", "path", "isdir"] => checker.report_diagnostic_if_enabled(OsPathIsdir, range), // PTH113 - ["os", "path", "isfile"] => Diagnostic::new(OsPathIsfile, range), + ["os", "path", "isfile"] => checker.report_diagnostic_if_enabled(OsPathIsfile, range), // PTH114 - ["os", "path", "islink"] => Diagnostic::new(OsPathIslink, range), + ["os", "path", "islink"] => checker.report_diagnostic_if_enabled(OsPathIslink, range), // PTH116 ["os", "stat"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -159,12 +159,12 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(OsStat, range) + checker.report_diagnostic_if_enabled(OsStat, range) } // PTH117 - ["os", "path", "isabs"] => Diagnostic::new(OsPathIsabs, range), + ["os", "path", "isabs"] => checker.report_diagnostic_if_enabled(OsPathIsabs, range), // PTH118 - ["os", "path", "join"] => Diagnostic::new( + ["os", "path", "join"] => checker.report_diagnostic_if_enabled( OsPathJoin { module: "path".to_string(), joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { @@ -175,7 +175,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { }, range, ), - ["os", "sep", "join"] => Diagnostic::new( + ["os", "sep", "join"] => checker.report_diagnostic_if_enabled( OsPathJoin { module: "sep".to_string(), joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { @@ -187,21 +187,21 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { range, ), // PTH119 - ["os", "path", "basename"] => Diagnostic::new(OsPathBasename, range), + ["os", "path", "basename"] => checker.report_diagnostic_if_enabled(OsPathBasename, range), // PTH120 - ["os", "path", "dirname"] => Diagnostic::new(OsPathDirname, range), + ["os", "path", "dirname"] => checker.report_diagnostic_if_enabled(OsPathDirname, range), // PTH121 - ["os", "path", "samefile"] => Diagnostic::new(OsPathSamefile, range), + ["os", "path", "samefile"] => checker.report_diagnostic_if_enabled(OsPathSamefile, range), // PTH122 - ["os", "path", "splitext"] => Diagnostic::new(OsPathSplitext, range), + ["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range), // PTH202 - ["os", "path", "getsize"] => Diagnostic::new(OsPathGetsize, range), + ["os", "path", "getsize"] => checker.report_diagnostic_if_enabled(OsPathGetsize, range), // PTH203 - ["os", "path", "getatime"] => Diagnostic::new(OsPathGetatime, range), + ["os", "path", "getatime"] => checker.report_diagnostic_if_enabled(OsPathGetatime, range), // PTH204 - ["os", "path", "getmtime"] => Diagnostic::new(OsPathGetmtime, range), + ["os", "path", "getmtime"] => checker.report_diagnostic_if_enabled(OsPathGetmtime, range), // PTH205 - ["os", "path", "getctime"] => Diagnostic::new(OsPathGetctime, range), + ["os", "path", "getctime"] => checker.report_diagnostic_if_enabled(OsPathGetctime, range), // PTH211 ["os", "symlink"] => { // `dir_fd` is not supported by pathlib, so check if there are non-default values. @@ -213,7 +213,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsSymlink, range) + checker.report_diagnostic_if_enabled(OsSymlink, range) } // PTH123 @@ -250,10 +250,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(BuiltinOpen, range) + checker.report_diagnostic_if_enabled(BuiltinOpen, range) } // PTH124 - ["py", "path", "local"] => Diagnostic::new(PyPath, range), + ["py", "path", "local"] => checker.report_diagnostic_if_enabled(PyPath, range), // PTH207 ["glob", "glob"] => { // `dir_fd` is not supported by pathlib, so check if it's set to non-default values. @@ -266,7 +266,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { return; } - Diagnostic::new( + checker.report_diagnostic_if_enabled( Glob { function: "glob".to_string(), }, @@ -285,7 +285,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { return; } - Diagnostic::new( + checker.report_diagnostic_if_enabled( Glob { function: "iglob".to_string(), }, @@ -304,7 +304,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") { return; } - Diagnostic::new(OsReadlink, range) + checker.report_diagnostic_if_enabled(OsReadlink, range) } // PTH208 ["os", "listdir"] => { @@ -315,15 +315,11 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { { return; } - Diagnostic::new(OsListdir, range) + checker.report_diagnostic_if_enabled(OsListdir, range) } _ => return, }; - - if checker.enabled(diagnostic.rule()) { - checker.report_diagnostic(diagnostic); - } } /// Returns `true` if the given expression looks like a file descriptor, i.e., if it is an integer. diff --git a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs index 28f98da0b6c214..1af7fc68d9fa59 100644 --- a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs @@ -2,7 +2,7 @@ use ast::FStringFlags; use itertools::Itertools; use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_text_size::{Ranged, TextRange}; @@ -152,7 +152,7 @@ pub(crate) fn static_join_to_fstring(checker: &Checker, expr: &Expr, joiner: &st let contents = checker.generator().expr(&new_expr); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( StaticJoinToFString { expression: SourceCodeSnippet::new(contents.clone()), }, @@ -162,5 +162,4 @@ pub(crate) fn static_join_to_fstring(checker: &Checker, expr: &Expr, joiner: &st pad(contents, expr.range(), checker.locator()), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs b/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs index ec5f371db74c84..b5c7ddd46aea9f 100644 --- a/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs +++ b/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for functions with a high `McCabe` complexity. /// @@ -153,23 +155,22 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize { } pub(crate) fn function_is_too_complex( + checker: &Checker, stmt: &Stmt, name: &str, body: &[Stmt], max_complexity: usize, -) -> Option { +) { let complexity = get_complexity_number(body) + 1; if complexity > max_complexity { - Some(Diagnostic::new( + checker.report_diagnostic( ComplexStructure { name: name.to_string(), complexity, max_complexity, }, stmt.identifier(), - )) - } else { - None + ); } } diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs index 70a37fa4cc2925..03343ae61eabdc 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -73,7 +73,7 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) { _ => None, }) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NumpyDeprecatedFunction { existing: existing.to_string(), replacement: replacement.to_string(), @@ -89,6 +89,5 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) { let replacement_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(import_edit, [replacement_edit])) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs index f1b527fa5c8f91..1b4225d185a27c 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -74,7 +74,7 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) { } }) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NumpyDeprecatedTypeAlias { type_name: type_name.to_string(), }, @@ -93,6 +93,5 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) { let binding_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(binding_edit, import_edit)) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs index f32f617c72e33d..5317c3c2c79aed 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -137,11 +137,11 @@ pub(crate) fn legacy_random(checker: &Checker, expr: &Expr) { } }) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NumpyLegacyRandom { method_name: method_name.to_string(), }, expr.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs index 6b74f97d720ffc..254fdba2643194 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs @@ -1,5 +1,5 @@ use crate::rules::numpy::helpers::{AttributeSearcher, ImportSearcher}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedNameBuilder; use ruff_python_ast::statement_visitor::StatementVisitor; @@ -667,7 +667,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( Numpy2Deprecation { existing: replacement.existing.to_string(), migration_guide: replacement.details.guideline(), @@ -701,7 +701,6 @@ pub(crate) fn numpy_2_0_deprecation(checker: &Checker, expr: &Expr) { )), Details::Manual { guideline: _ } => {} } - checker.report_diagnostic(diagnostic); } /// Ignore attempts to access a `numpy` member via its deprecated name diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs index 2d8e63f3c4fcb5..c179fcb392d234 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for assignments to the variable `df`. /// @@ -38,15 +40,15 @@ impl Violation for PandasDfVariableName { } /// PD901 -pub(crate) fn assignment_to_df(targets: &[Expr]) -> Option { +pub(crate) fn assignment_to_df(checker: &Checker, targets: &[Expr]) { let [target] = targets else { - return None; + return; }; let Expr::Name(ast::ExprName { id, .. }) = target else { - return None; + return; }; if id != "df" { - return None; + return; } - Some(Diagnostic::new(PandasDfVariableName, target.range())) + checker.report_diagnostic(PandasDfVariableName, target.range()); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs index dc63d78a322af8..7c95a0ca562a0e 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; @@ -77,5 +77,5 @@ pub(crate) fn attr(checker: &Checker, attribute: &ast::ExprAttribute) { return; } - checker.report_diagnostic(Diagnostic::new(PandasUseOfDotValues, attribute.range())); + checker.report_diagnostic(PandasUseOfDotValues, attribute.range()); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs index 894fdf2b2c2211..86c5e600c9c70b 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs @@ -1,6 +1,5 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -172,13 +171,21 @@ pub(crate) fn call(checker: &Checker, func: &Expr) { return; }; + // Ignore irrelevant bindings (like imports). + if !matches!( + test_expression(value, checker.semantic()), + Resolution::RelevantLocal | Resolution::PandasModule + ) { + return; + } + let range = func.range(); - let diagnostic = match attr.as_str() { + match attr.as_str() { "isnull" if checker.settings.rules.enabled(Rule::PandasUseOfDotIsNull) => { - Diagnostic::new(PandasUseOfDotIsNull, range) + checker.report_diagnostic(PandasUseOfDotIsNull, range); } "notnull" if checker.settings.rules.enabled(Rule::PandasUseOfDotNotNull) => { - Diagnostic::new(PandasUseOfDotNotNull, range) + checker.report_diagnostic(PandasUseOfDotNotNull, range); } "pivot" | "unstack" if checker @@ -186,21 +193,11 @@ pub(crate) fn call(checker: &Checker, func: &Expr) { .rules .enabled(Rule::PandasUseOfDotPivotOrUnstack) => { - Diagnostic::new(PandasUseOfDotPivotOrUnstack, range) + checker.report_diagnostic(PandasUseOfDotPivotOrUnstack, range); } "stack" if checker.settings.rules.enabled(Rule::PandasUseOfDotStack) => { - Diagnostic::new(PandasUseOfDotStack, range) + checker.report_diagnostic(PandasUseOfDotStack, range); } - _ => return, - }; - - // Ignore irrelevant bindings (like imports). - if !matches!( - test_expression(value, checker.semantic()), - Resolution::RelevantLocal | Resolution::PandasModule - ) { - return; + _ => {} } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs index 9afc5818a98395..46f74c79e884b4 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::parenthesize::parenthesized_range; @@ -78,7 +78,8 @@ pub(crate) fn inplace_argument(checker: &Checker, call: &ast::ExprCall) { }; if arg == "inplace" { if is_const_true(&keyword.value) { - let mut diagnostic = Diagnostic::new(PandasUseOfInplaceArgument, keyword.range()); + let mut diagnostic = + checker.report_diagnostic(PandasUseOfInplaceArgument, keyword.range()); // Avoid applying the fix if: // 1. The keyword argument is followed by a star argument (we can't be certain that // the star argument _doesn't_ contain an override). @@ -99,8 +100,6 @@ pub(crate) fn inplace_argument(checker: &Checker, call: &ast::ExprCall) { diagnostic.set_fix(fix); } } - - checker.report_diagnostic(diagnostic); } // Duplicate keywords is a syntax error, so we can stop here. diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs index fefe0ed4a7e195..57e4ea7efbcf8a 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr, Int}; @@ -114,8 +113,5 @@ pub(crate) fn nunique_constant_series_check( return; } - checker.report_diagnostic(Diagnostic::new( - PandasNuniqueConstantSeriesCheck, - expr.range(), - )); + checker.report_diagnostic(PandasNuniqueConstantSeriesCheck, expr.range()); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs index e7cf969c383395..569378888dac96 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr}; use crate::checkers::ast::Checker; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -63,7 +63,7 @@ pub(crate) fn use_of_pd_merge(checker: &Checker, func: &Expr) { if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func { if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() { if id == "pd" && attr == "merge" { - checker.report_diagnostic(Diagnostic::new(PandasUseOfPdMerge, func.range())); + checker.report_diagnostic(PandasUseOfPdMerge, func.range()); } } } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs index 29c1c9bef28f12..4353b58ae04500 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -62,8 +62,7 @@ pub(crate) fn use_of_read_table(checker: &Checker, call: &ast::ExprCall) { .map(|keyword| &keyword.value) { if value == "," { - checker - .report_diagnostic(Diagnostic::new(PandasUseOfDotReadTable, call.func.range())); + checker.report_diagnostic(PandasUseOfDotReadTable, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs index 11fa079ade00a7..efef6e806ca99a 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs @@ -1,6 +1,5 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; @@ -152,29 +151,27 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, expr: &Expr) { return; }; + // Avoid flagging on non-DataFrames (e.g., `{"a": 1}.at[0]`), and on irrelevant bindings + // (like imports). + if !matches!( + test_expression(value, checker.semantic()), + Resolution::RelevantLocal + ) { + return; + } + let range = expr.range(); - let diagnostic = match attr.as_str() { + match attr.as_str() { "ix" if checker.settings.rules.enabled(Rule::PandasUseOfDotIx) => { - Diagnostic::new(PandasUseOfDotIx, range) + checker.report_diagnostic(PandasUseOfDotIx, range) } "at" if checker.settings.rules.enabled(Rule::PandasUseOfDotAt) => { - Diagnostic::new(PandasUseOfDotAt, range) + checker.report_diagnostic(PandasUseOfDotAt, range) } "iat" if checker.settings.rules.enabled(Rule::PandasUseOfDotIat) => { - Diagnostic::new(PandasUseOfDotIat, range) + checker.report_diagnostic(PandasUseOfDotIat, range) } _ => return, }; - - // Avoid flagging on non-DataFrames (e.g., `{"a": 1}.at[0]`), and on irrelevant bindings - // (like imports). - if !matches!( - test_expression(value, checker.semantic()), - Resolution::RelevantLocal - ) { - return; - } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs index 6029af4f920205..fe34a67ef347b7 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str::{self}; @@ -62,7 +62,7 @@ pub(crate) fn camelcase_imported_as_acronym( alias: &Alias, stmt: &Stmt, checker: &Checker, -) -> Option { +) { if helpers::is_camelcase(name) && !str::is_cased_lowercase(asname) && str::is_cased_uppercase(asname) @@ -72,15 +72,15 @@ pub(crate) fn camelcase_imported_as_acronym( // Ignore any explicitly-allowed names. if ignore_names.matches(name) || ignore_names.matches(asname) { - return None; + return; } // Ignore names that follow a community-agreed import convention. if is_ignored_because_of_import_convention(asname, stmt, alias, checker) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CamelcaseImportedAsAcronym { name: name.to_string(), asname: asname.to_string(), @@ -88,9 +88,7 @@ pub(crate) fn camelcase_imported_as_acronym( alias.range(), ); diagnostic.set_parent(stmt.start()); - return Some(diagnostic); } - None } fn is_ignored_because_of_import_convention( diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs index afdfbfc30706c7..0f6eff642587b1 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs @@ -1,10 +1,11 @@ use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::str::{self}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; use crate::rules::pep8_naming::settings::IgnoreNames; @@ -65,14 +66,17 @@ impl Violation for CamelcaseImportedAsConstant { /// N814 pub(crate) fn camelcase_imported_as_constant( + checker: &Checker, name: &str, asname: &str, alias: &Alias, stmt: &Stmt, ignore_names: &IgnoreNames, -) -> Option { +) { // Single-character names are ambiguous. It could be a class or a constant. - asname.chars().nth(1)?; + if asname.chars().nth(1).is_none() { + return; + } if helpers::is_camelcase(name) && !str::is_cased_lowercase(asname) @@ -81,9 +85,9 @@ pub(crate) fn camelcase_imported_as_constant( { // Ignore any explicitly-allowed names. if ignore_names.matches(name) || ignore_names.matches(asname) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CamelcaseImportedAsConstant { name: name.to_string(), asname: asname.to_string(), @@ -91,7 +95,5 @@ pub(crate) fn camelcase_imported_as_constant( alias.range(), ); diagnostic.set_parent(stmt.start()); - return Some(diagnostic); } - None } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs index f91418315765a0..289e22755f40b4 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs @@ -1,9 +1,10 @@ use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; use crate::rules::pep8_naming::settings::IgnoreNames; @@ -50,18 +51,19 @@ impl Violation for CamelcaseImportedAsLowercase { /// N813 pub(crate) fn camelcase_imported_as_lowercase( + checker: &Checker, name: &str, asname: &str, alias: &Alias, stmt: &Stmt, ignore_names: &IgnoreNames, -) -> Option { +) { if helpers::is_camelcase(name) && ruff_python_stdlib::str::is_cased_lowercase(asname) { // Ignore any explicitly-allowed names. if ignore_names.matches(name) || ignore_names.matches(asname) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CamelcaseImportedAsLowercase { name: name.to_string(), asname: asname.to_string(), @@ -69,7 +71,5 @@ pub(crate) fn camelcase_imported_as_lowercase( alias.range(), ); diagnostic.set_parent(stmt.start()); - return Some(diagnostic); } - None } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs index d4eb7ee32f8352..df667f3ef5a323 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs @@ -1,10 +1,13 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; -use crate::rules::pep8_naming::{helpers, settings::IgnoreNames}; +use crate::{ + checkers::ast::Checker, + rules::pep8_naming::{helpers, settings::IgnoreNames}, +}; /// ## What it does /// Checks for constant imports that are aliased to non-constant-style @@ -63,12 +66,13 @@ impl Violation for ConstantImportedAsNonConstant { /// N811 pub(crate) fn constant_imported_as_non_constant( + checker: &Checker, name: &str, asname: &str, alias: &Alias, stmt: &Stmt, ignore_names: &IgnoreNames, -) -> Option { +) { if str::is_cased_uppercase(name) && !(str::is_cased_uppercase(asname) // Single-character names are ambiguous. @@ -78,9 +82,9 @@ pub(crate) fn constant_imported_as_non_constant( { // Ignore any explicitly-allowed names. if ignore_names.matches(name) || ignore_names.matches(asname) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ConstantImportedAsNonConstant { name: name.to_string(), asname: asname.to_string(), @@ -88,7 +92,5 @@ pub(crate) fn constant_imported_as_non_constant( alias.range(), ); diagnostic.set_parent(stmt.start()); - return Some(diagnostic); } - None } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs index 37748188fd8953..6ee2bc8af07ae2 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -1,11 +1,12 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Scope, ScopeKind}; +use crate::checkers::ast::Checker; use crate::rules::pep8_naming::settings::IgnoreNames; /// ## What it does @@ -48,24 +49,25 @@ impl Violation for DunderFunctionName { /// N807 pub(crate) fn dunder_function_name( + checker: &Checker, scope: &Scope, stmt: &Stmt, name: &str, ignore_names: &IgnoreNames, -) -> Option { +) { if matches!(scope.kind, ScopeKind::Class(_)) { - return None; + return; } if !visibility::is_magic(name) { - return None; + return; } // Allowed under PEP 562 (https://peps.python.org/pep-0562/). if matches!(scope.kind, ScopeKind::Module) && (name == "__getattr__" || name == "__dir__") { - return None; + return; } // Ignore any explicitly-allowed names. if ignore_names.matches(name) { - return None; + return; } - Some(Diagnostic::new(DunderFunctionName, stmt.identifier())) + checker.report_diagnostic(DunderFunctionName, stmt.identifier()); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs index 52b46b46363f77..2d8c4669e21e96 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; -use crate::rules::pep8_naming::settings::IgnoreNames; +use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; /// ## What it does /// Checks for custom exception definitions that omit the `Error` suffix. @@ -48,13 +48,14 @@ impl Violation for ErrorSuffixOnExceptionName { /// N818 pub(crate) fn error_suffix_on_exception_name( + checker: &Checker, class_def: &Stmt, arguments: Option<&Arguments>, name: &str, ignore_names: &IgnoreNames, -) -> Option { +) { if name.ends_with("Error") { - return None; + return; } if !arguments.is_some_and(|arguments| { @@ -66,18 +67,18 @@ pub(crate) fn error_suffix_on_exception_name( } }) }) { - return None; + return; } // Ignore any explicitly-allowed names. if ignore_names.matches(name) { - return None; + return; } - Some(Diagnostic::new( + checker.report_diagnostic( ErrorSuffixOnExceptionName { name: name.to_string(), }, class_def.identifier(), - )) + ); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs index 31b5fe71952409..604234982ae784 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ExprLambda, Parameters, StmtFunctionDef}; use ruff_python_semantic::ScopeKind; @@ -94,13 +94,11 @@ fn invalid_argument_name(checker: &Checker, parameters: &Parameters) { continue; } - let diagnostic = Diagnostic::new( + checker.report_diagnostic( InvalidArgumentName { name: name.to_string(), }, parameter.range(), ); - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs index 5db5bf0942a4cb..61d7ff398879b8 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; -use crate::rules::pep8_naming::settings::IgnoreNames; +use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; /// ## What it does /// Checks for class names that do not follow the `CamelCase` convention. @@ -54,22 +54,22 @@ impl Violation for InvalidClassName { /// N801 pub(crate) fn invalid_class_name( + checker: &Checker, class_def: &Stmt, name: &str, ignore_names: &IgnoreNames, -) -> Option { +) { let stripped = name.trim_start_matches('_'); if !stripped.chars().next().is_some_and(char::is_uppercase) || stripped.contains('_') { // Ignore any explicitly-allowed names. if ignore_names.matches(name) { - return None; + return; } - return Some(Diagnostic::new( + checker.report_diagnostic( InvalidClassName { name: name.to_string(), }, class_def.identifier(), - )); + ); } - None } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index e8f42a91903e1c..10a622de1e8bdc 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Fix, Violation}; +use ruff_diagnostics::{Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; @@ -10,7 +10,7 @@ use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; -use crate::checkers::ast::Checker; +use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::registry::Rule; use crate::renamer::Renamer; @@ -167,12 +167,16 @@ enum FunctionType { } impl FunctionType { - fn diagnostic_kind(self, argument_name: String, range: TextRange) -> Diagnostic { + fn diagnostic_kind<'a, 'b>( + self, + checker: &'a Checker<'b>, + argument_name: String, + range: TextRange, + ) -> DiagnosticGuard<'a, 'b> { match self { - Self::Method => { - Diagnostic::new(InvalidFirstArgumentNameForMethod { argument_name }, range) - } - Self::ClassMethod => Diagnostic::new( + Self::Method => checker + .report_diagnostic(InvalidFirstArgumentNameForMethod { argument_name }, range), + Self::ClassMethod => checker.report_diagnostic( InvalidFirstArgumentNameForClassMethod { argument_name, is_new: false, @@ -267,7 +271,7 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { } let mut diagnostic = - function_type.diagnostic_kind(self_or_cls.name.to_string(), self_or_cls.range()); + function_type.diagnostic_kind(checker, self_or_cls.name.to_string(), self_or_cls.range()); diagnostic.try_set_optional_fix(|| { rename_parameter( scope, @@ -278,7 +282,6 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { checker.stylist(), ) }); - checker.report_diagnostic(diagnostic); } /// Rename the first parameter to `self` or `cls`, if no other parameter has the target name. diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs index d50b9866349ed5..6618cbe3c8fdbf 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs @@ -1,12 +1,13 @@ use ruff_python_ast::{Decorator, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::visibility; use ruff_python_stdlib::str; +use crate::checkers::ast::Checker; use crate::rules::pep8_naming::settings::IgnoreNames; /// ## What it does @@ -57,15 +58,16 @@ impl Violation for InvalidFunctionName { /// N802 pub(crate) fn invalid_function_name( + checker: &Checker, stmt: &Stmt, name: &str, decorator_list: &[Decorator], ignore_names: &IgnoreNames, semantic: &SemanticModel, -) -> Option { +) { // Ignore any function names that are already lowercase. if str::is_lowercase(name) { - return None; + return; } // Ignore any functions that are explicitly `@override` or `@overload`. @@ -74,18 +76,18 @@ pub(crate) fn invalid_function_name( if visibility::is_override(decorator_list, semantic) || visibility::is_overload(decorator_list, semantic) { - return None; + return; } // Ignore any explicitly-allowed names. if ignore_names.matches(name) { - return None; + return; } - Some(Diagnostic::new( + checker.report_diagnostic( InvalidFunctionName { name: name.to_string(), }, stmt.identifier(), - )) + ); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs index d1372dfe0329e8..ad838b0bc58662 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; -use crate::rules::pep8_naming::settings::IgnoreNames; +use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; /// ## What it does /// Checks for lowercase imports that are aliased to non-lowercase names. @@ -49,19 +49,20 @@ impl Violation for LowercaseImportedAsNonLowercase { /// N812 pub(crate) fn lowercase_imported_as_non_lowercase( + checker: &Checker, name: &str, asname: &str, alias: &Alias, stmt: &Stmt, ignore_names: &IgnoreNames, -) -> Option { +) { if !str::is_cased_uppercase(name) && str::is_cased_lowercase(name) && !str::is_lowercase(asname) { // Ignore any explicitly-allowed names. if ignore_names.matches(name) || ignore_names.matches(asname) { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( LowercaseImportedAsNonLowercase { name: name.to_string(), asname: asname.to_string(), @@ -69,7 +70,5 @@ pub(crate) fn lowercase_imported_as_non_lowercase( alias.range(), ); diagnostic.set_parent(stmt.start()); - return Some(diagnostic); } - None } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs index a1ebfac6161798..8380762b5d966e 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -76,10 +76,10 @@ pub(crate) fn mixed_case_variable_in_class_scope( return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( MixedCaseVariableInClassScope { name: name.to_string(), }, expr.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs index fa2ee853ad7229..670e3fadbc8121 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -79,10 +79,10 @@ pub(crate) fn mixed_case_variable_in_global_scope(checker: &Checker, expr: &Expr return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( MixedCaseVariableInGlobalScope { name: name.to_string(), }, expr.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs index a0c495be275d3e..efcf590837af7c 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; @@ -81,10 +81,10 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &Checker, expr: &Expr, return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonLowercaseVariableInFunction { name: name.to_string(), }, expr.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs b/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs index 5343a0416bb6f2..ed7710ffb6bae4 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Arguments, Expr}; @@ -99,7 +99,7 @@ pub(crate) fn incorrect_dict_iterator(checker: &Checker, stmt_for: &ast::StmtFor } (true, false) => { // The key is unused, so replace with `dict.values()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IncorrectDictIterator { subset: DictSubset::Values, }, @@ -115,11 +115,10 @@ pub(crate) fn incorrect_dict_iterator(checker: &Checker, stmt_for: &ast::StmtFor stmt_for.target.range(), ); diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target])); - checker.report_diagnostic(diagnostic); } (false, true) => { // The value is unused, so replace with `dict.keys()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IncorrectDictIterator { subset: DictSubset::Keys, }, @@ -135,7 +134,6 @@ pub(crate) fn incorrect_dict_iterator(checker: &Checker, stmt_for: &ast::StmtFor stmt_for.target.range(), ); diagnostic.set_fix(Fix::unsafe_edits(replace_attribute, [replace_target])); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs index ed0a9fd101a2c2..c3b17b2d16b766 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ self as ast, Expr, Stmt, comparable::ComparableExpr, helpers::any_over_expr, @@ -296,7 +296,7 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF DictComprehensionType::Update }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ManualDictComprehension { fix_type, is_async: for_stmt.is_async, @@ -314,16 +314,14 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF checker, )) }); - - checker.report_diagnostic(diagnostic); } else { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( ManualDictComprehension { fix_type: DictComprehensionType::Comprehension, is_async: for_stmt.is_async, }, *range, - )); + ); } } diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs index 590628c39ff452..461092bb972594 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs @@ -5,7 +5,7 @@ use crate::{ rules::perflint::helpers::statement_deletion_range, }; use anyhow::{Result, anyhow}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use crate::rules::perflint::helpers::comment_strings_in_range; use ruff_macros::{ViolationMetadata, derive_message_formats}; @@ -331,7 +331,7 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF ComprehensionType::Extend }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ManualListComprehension { is_async: for_stmt.is_async, comprehension_type: Some(comprehension_type), @@ -352,8 +352,6 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF ) }); } - - checker.report_diagnostic(diagnostic); } fn convert_to_list_extend( diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs index d42d78d0f61f82..dc8fe2a6464d4e 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; @@ -119,5 +119,5 @@ pub(crate) fn manual_list_copy(checker: &Checker, for_stmt: &ast::StmtFor) { return; } - checker.report_diagnostic(Diagnostic::new(ManualListCopy, *range)); + checker.report_diagnostic(ManualListCopy, *range); } diff --git a/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs b/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs index 49c4a29c635a03..ee1fae0b145938 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast, Stmt}; @@ -107,7 +107,7 @@ pub(crate) fn try_except_in_loop(checker: &Checker, body: &[Stmt]) { return; } - checker.report_diagnostic(Diagnostic::new(TryExceptInLoop, handler.range())); + checker.report_diagnostic(TryExceptInLoop, handler.range()); } /// Returns `true` if a `break` or `continue` statement is present in `body`. diff --git a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs index 19b2c0b08ad39f..72a6d753034b8f 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; @@ -86,9 +86,8 @@ pub(crate) fn unnecessary_list_cast(checker: &Checker, iter: &Expr, body: &[Stmt range: iterable_range, .. }) => { - let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListCast, *list_range); diagnostic.set_fix(remove_cast(*list_range, *iterable_range)); - checker.report_diagnostic(diagnostic); } Expr::Name(ast::ExprName { id, @@ -114,9 +113,8 @@ pub(crate) fn unnecessary_list_cast(checker: &Checker, iter: &Expr, body: &[Stmt return; } - let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListCast, *list_range); diagnostic.set_fix(remove_cast(*list_range, *iterable_range)); - checker.report_diagnostic(diagnostic); } } _ => {} diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs index b7648a50e4d333..11af69e0aae11d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Identifier; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::rules::pycodestyle::helpers::is_ambiguous_name; +use crate::{checkers::ast::Checker, rules::pycodestyle::helpers::is_ambiguous_name}; /// ## What it does /// Checks for the use of the characters 'l', 'O', or 'I' as class names. @@ -36,13 +36,8 @@ impl Violation for AmbiguousClassName { } /// E742 -pub(crate) fn ambiguous_class_name(name: &Identifier) -> Option { +pub(crate) fn ambiguous_class_name(checker: &Checker, name: &Identifier) { if is_ambiguous_name(name) { - Some(Diagnostic::new( - AmbiguousClassName(name.to_string()), - name.range(), - )) - } else { - None + checker.report_diagnostic(AmbiguousClassName(name.to_string()), name.range()); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs index 15f276d9fcda9a..6d74c79d39ffb0 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Identifier; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::rules::pycodestyle::helpers::is_ambiguous_name; +use crate::{checkers::ast::Checker, rules::pycodestyle::helpers::is_ambiguous_name}; /// ## What it does /// Checks for the use of the characters 'l', 'O', or 'I' as function names. @@ -36,13 +36,8 @@ impl Violation for AmbiguousFunctionName { } /// E743 -pub(crate) fn ambiguous_function_name(name: &Identifier) -> Option { +pub(crate) fn ambiguous_function_name(checker: &Checker, name: &Identifier) { if is_ambiguous_name(name) { - Some(Diagnostic::new( - AmbiguousFunctionName(name.to_string()), - name.range(), - )) - } else { - None + checker.report_diagnostic(AmbiguousFunctionName(name.to_string()), name.range()); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs index 553fed76de8c75..1f4a6f56831d2b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs @@ -1,6 +1,6 @@ use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -50,9 +50,6 @@ pub(crate) fn ambiguous_variable_name(checker: &Checker, name: &str, range: Text return; } if is_ambiguous_name(name) { - checker.report_diagnostic(Diagnostic::new( - AmbiguousVariableName(name.to_string()), - range, - )); + checker.report_diagnostic(AmbiguousVariableName(name.to_string()), range); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs index a7f92225b344b9..571d327ccf1964 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::except; use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; -use crate::Locator; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for bare `except` catches in `try`-`except` statements. @@ -56,21 +56,16 @@ impl Violation for BareExcept { /// E722 pub(crate) fn bare_except( + checker: &Checker, type_: Option<&Expr>, body: &[Stmt], handler: &ExceptHandler, - locator: &Locator, -) -> Option { +) { if type_.is_none() && !body .iter() .any(|stmt| matches!(stmt, Stmt::Raise(ast::StmtRaise { exc: None, .. }))) { - Some(Diagnostic::new( - BareExcept, - except(handler, locator.contents()), - )) - } else { - None + checker.report_diagnostic(BareExcept, except(handler, checker.locator().contents())); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs index 5ee72ffae14d7c..8968a32827a367 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs @@ -1,6 +1,6 @@ use memchr::memchr_iter; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyStringFlags, FStringElement, StringLike, StringLikePart}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -252,7 +252,7 @@ fn check( if contains_valid_escape_sequence { // Escape with backslash. for invalid_escape_char in &invalid_escape_chars { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( InvalidEscapeSequence { ch: invalid_escape_char.ch, fix_title: FixTitle::AddBackslash, @@ -263,12 +263,11 @@ fn check( r"\".to_string(), invalid_escape_char.start() + TextSize::from(1), ))); - checker.report_diagnostic(diagnostic); } } else { // Turn into raw string. for invalid_escape_char in &invalid_escape_chars { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( InvalidEscapeSequence { ch: invalid_escape_char.ch, fix_title: FixTitle::UseRawStringLiteral, @@ -295,8 +294,6 @@ fn check( )), ); } - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 0d252c1bc0d0ef..eb8f15374164e5 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{ @@ -70,7 +70,16 @@ pub(crate) fn lambda_assignment( return; }; - let mut diagnostic = Diagnostic::new( + // If the assignment is a class attribute (with an annotation), ignore it. + // + // This is most common for, e.g., dataclasses and Pydantic models. Those libraries will + // treat the lambda as an assignable field, and the use of a lambda is almost certainly + // intentional. + if annotation.is_some() && checker.semantic().current_scope().kind.is_class() { + return; + } + + let mut diagnostic = checker.report_diagnostic( LambdaAssignment { name: id.to_string(), }, @@ -96,15 +105,6 @@ pub(crate) fn lambda_assignment( } } - // If the assignment is a class attribute (with an annotation), ignore it. - // - // This is most common for, e.g., dataclasses and Pydantic models. Those libraries will - // treat the lambda as an assignable field, and the use of a lambda is almost certainly - // intentional. - if annotation.is_some() && checker.semantic().current_scope().kind.is_class() { - return; - } - // Otherwise, if the assignment is in a class body, flag it, but use a display-only fix. // Rewriting safely would require making this a static method. // @@ -129,8 +129,6 @@ pub(crate) fn lambda_assignment( ))); } } - - checker.report_diagnostic(diagnostic); } /// Extract the argument types and return type from a `Callable` annotation. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs index ef175cd81010f4..7e261841386146 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs @@ -1,7 +1,7 @@ use ruff_python_ast::parenthesize::parenthesized_range; use rustc_hash::FxHashMap; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{self, generate_comparison}; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -209,7 +209,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) // then replace the entire expression at the end with one "real" fix, to // avoid conflicts. let mut bad_ops: FxHashMap = FxHashMap::default(); - let mut diagnostics: Vec = vec![]; + let mut diagnostics = vec![]; // Check `left`. let mut comparator = compare.left.as_ref(); @@ -225,12 +225,14 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) if checker.enabled(Rule::NoneComparison) && comparator.is_none_literal_expr() { match op { EqCmpOp::Eq => { - let diagnostic = Diagnostic::new(NoneComparison(op), comparator.range()); + let diagnostic = + checker.report_diagnostic(NoneComparison(op), comparator.range()); bad_ops.insert(0, CmpOp::Is); diagnostics.push(diagnostic); } EqCmpOp::NotEq => { - let diagnostic = Diagnostic::new(NoneComparison(op), comparator.range()); + let diagnostic = + checker.report_diagnostic(NoneComparison(op), comparator.range()); bad_ops.insert(0, CmpOp::IsNot); diagnostics.push(diagnostic); } @@ -246,7 +248,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) } else { None }; - let diagnostic = Diagnostic::new( + let diagnostic = checker.report_diagnostic( TrueFalseComparison { value: *value, op, @@ -263,7 +265,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) } else { None }; - let diagnostic = Diagnostic::new( + let diagnostic = checker.report_diagnostic( TrueFalseComparison { value: *value, op, @@ -291,12 +293,14 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) if checker.enabled(Rule::NoneComparison) && next.is_none_literal_expr() { match op { EqCmpOp::Eq => { - let diagnostic = Diagnostic::new(NoneComparison(op), next.range()); + let diagnostic = + checker.report_diagnostic(NoneComparison(op), next.range()); bad_ops.insert(index, CmpOp::Is); diagnostics.push(diagnostic); } EqCmpOp::NotEq => { - let diagnostic = Diagnostic::new(NoneComparison(op), next.range()); + let diagnostic = + checker.report_diagnostic(NoneComparison(op), next.range()); bad_ops.insert(index, CmpOp::IsNot); diagnostics.push(diagnostic); } @@ -324,7 +328,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) } else { None }; - let diagnostic = Diagnostic::new( + let diagnostic = checker.report_diagnostic( TrueFalseComparison { value: *value, op, @@ -343,7 +347,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) } else { None }; - let diagnostic = Diagnostic::new( + let diagnostic = checker.report_diagnostic( TrueFalseComparison { value: *value, op, @@ -426,8 +430,4 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare) ))); } } - - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); - } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs index 17d7a830aeac9f..307549e3f170f4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{PySourceType, Stmt}; use ruff_text_size::Ranged; @@ -58,11 +58,11 @@ impl Violation for ModuleImportNotAtTopOfFile { /// E402 pub(crate) fn module_import_not_at_top_of_file(checker: &Checker, stmt: &Stmt) { if checker.semantic().seen_import_boundary() && checker.semantic().at_top_level() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( ModuleImportNotAtTopOfFile { source_type: checker.source_type, }, stmt.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs index 625b082e8b56f6..fa9bfe59490ad8 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_codegen::Stylist; @@ -49,7 +49,7 @@ impl Violation for MultipleImportsOnOneLine { /// E401 pub(crate) fn multiple_imports_on_one_line(checker: &Checker, stmt: &Stmt, names: &[Alias]) { if names.len() > 1 { - let mut diagnostic = Diagnostic::new(MultipleImportsOnOneLine, stmt.range()); + let mut diagnostic = checker.report_diagnostic(MultipleImportsOnOneLine, stmt.range()); diagnostic.set_fix(split_imports( stmt, names, @@ -57,7 +57,6 @@ pub(crate) fn multiple_imports_on_one_line(checker: &Checker, stmt: &Stmt, names checker.indexer(), checker.stylist(), )); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs index 60668db0019522..90eb83f9ec719f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs @@ -1,5 +1,5 @@ use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::generate_comparison; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -96,7 +96,7 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) { match &**ops { [CmpOp::In] => { if checker.enabled(Rule::NotInTest) { - let mut diagnostic = Diagnostic::new(NotInTest, unary_op.operand.range()); + let mut diagnostic = checker.report_diagnostic(NotInTest, unary_op.operand.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad( generate_comparison( @@ -112,12 +112,11 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) { ), unary_op.range(), ))); - checker.report_diagnostic(diagnostic); } } [CmpOp::Is] => { if checker.enabled(Rule::NotIsTest) { - let mut diagnostic = Diagnostic::new(NotIsTest, unary_op.operand.range()); + let mut diagnostic = checker.report_diagnostic(NotIsTest, unary_op.operand.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( pad( generate_comparison( @@ -133,7 +132,6 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) { ), unary_op.range(), ))); - checker.report_diagnostic(diagnostic); } } _ => {} diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs index 97440863b796c4..296271d6c8e162 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -76,7 +76,7 @@ pub(crate) fn type_comparison(checker: &Checker, compare: &ast::ExprCompare) { } // Disallow the comparison. - checker.report_diagnostic(Diagnostic::new(TypeComparison, compare.range())); + checker.report_diagnostic(TypeComparison, compare.range()); } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs index efb3ed22dcf8f2..b142253f77b742 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Decorator; use ruff_python_trivia::is_python_whitespace; @@ -62,9 +62,8 @@ pub(crate) fn whitespace_after_decorator(checker: &Checker, decorator_list: &[De let end = start + TextSize::try_from(end).unwrap(); let range = TextRange::new(start, end); - let mut diagnostic = Diagnostic::new(WhitespaceAfterDecorator, range); + let mut diagnostic = checker.report_diagnostic(WhitespaceAfterDecorator, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs index 83acd9cde145ef..4bb9865a17fbf3 100644 --- a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs +++ b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs @@ -1,5 +1,4 @@ use itertools::Itertools; -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; @@ -925,10 +924,8 @@ pub(crate) fn check_docstring( semantic, ) { - checker.report_diagnostic(Diagnostic::new( - DocstringMissingReturns, - docstring.range(), - )); + checker + .report_diagnostic(DocstringMissingReturns, docstring.range()); } } None if body_entries @@ -936,10 +933,7 @@ pub(crate) fn check_docstring( .iter() .any(|entry| !entry.is_none_return()) => { - checker.report_diagnostic(Diagnostic::new( - DocstringMissingReturns, - docstring.range(), - )); + checker.report_diagnostic(DocstringMissingReturns, docstring.range()); } _ => {} } @@ -958,16 +952,10 @@ pub(crate) fn check_docstring( |arguments| arguments.first().is_none_or(Expr::is_none_literal_expr), ) => { - checker.report_diagnostic(Diagnostic::new( - DocstringMissingYields, - docstring.range(), - )); + checker.report_diagnostic(DocstringMissingYields, docstring.range()); } None if body_entries.yields.iter().any(|entry| !entry.is_none_yield) => { - checker.report_diagnostic(Diagnostic::new( - DocstringMissingYields, - docstring.range(), - )); + checker.report_diagnostic(DocstringMissingYields, docstring.range()); } _ => {} } @@ -994,13 +982,12 @@ pub(crate) fn check_docstring( .ends_with(exception.segments()) }) }) { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( DocstringMissingException { id: (*name).to_string(), }, docstring.range(), ); - checker.report_diagnostic(diagnostic); } } } @@ -1014,8 +1001,7 @@ pub(crate) fn check_docstring( if body_entries.returns.is_empty() || body_entries.returns.iter().all(ReturnEntry::is_implicit) { - let diagnostic = Diagnostic::new(DocstringExtraneousReturns, docstring.range()); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(DocstringExtraneousReturns, docstring.range()); } } } @@ -1024,8 +1010,7 @@ pub(crate) fn check_docstring( if checker.enabled(Rule::DocstringExtraneousYields) { if docstring_sections.yields.is_some() { if body_entries.yields.is_empty() { - let diagnostic = Diagnostic::new(DocstringExtraneousYields, docstring.range()); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(DocstringExtraneousYields, docstring.range()); } } } @@ -1045,13 +1030,12 @@ pub(crate) fn check_docstring( } } if !extraneous_exceptions.is_empty() { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( DocstringExtraneousException { ids: extraneous_exceptions, }, docstring.range(), ); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs index 9c322699a101ee..fb8e75f2e13d22 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -96,7 +96,8 @@ pub(crate) fn backslashes(checker: &Checker, docstring: &Docstring) { // Only allow continuations (backslashes followed by newlines) and Unicode escapes. if !matches!(*escaped_char, '\r' | '\n' | 'u' | 'U' | 'N') { - let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(EscapeSequenceInDocstring, docstring.range()); if !docstring.is_u_string() { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( @@ -105,7 +106,6 @@ pub(crate) fn backslashes(checker: &Checker, docstring: &Docstring) { ))); } - checker.report_diagnostic(diagnostic); break; } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs index c39977a46ebf87..371d0a7ed7ea06 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -84,7 +84,7 @@ pub(crate) fn blank_after_summary(checker: &Checker, docstring: &Docstring) { } } if lines_count > 1 && blanks_count != 1 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingBlankLineAfterSummary { num_lines: blanks_count, }, @@ -118,6 +118,5 @@ pub(crate) fn blank_after_summary(checker: &Checker, docstring: &Docstring) { blank_end, ))); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs index b2a1d5ac45c3f4..6e31de56553d91 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::{PythonWhitespace, indentation_at_offset}; use ruff_source_file::{Line, LineRanges, UniversalNewlineIterator}; @@ -193,26 +193,25 @@ pub(crate) fn blank_before_after_class(checker: &Checker, docstring: &Docstring) if checker.enabled(Rule::BlankLineBeforeClass) { if blank_lines_before != 0 { - let mut diagnostic = Diagnostic::new(BlankLineBeforeClass, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(BlankLineBeforeClass, docstring.range()); // Delete the blank line before the class. diagnostic.set_fix(Fix::safe_edit(Edit::deletion( blank_lines_start, docstring.line_start(), ))); - checker.report_diagnostic(diagnostic); } } if checker.enabled(Rule::IncorrectBlankLineBeforeClass) { if blank_lines_before != 1 { let mut diagnostic = - Diagnostic::new(IncorrectBlankLineBeforeClass, docstring.range()); + checker.report_diagnostic(IncorrectBlankLineBeforeClass, docstring.range()); // Insert one blank line before the class. diagnostic.set_fix(Fix::safe_edit(Edit::replacement( checker.stylist().line_ending().to_string(), blank_lines_start, docstring.line_start(), ))); - checker.report_diagnostic(diagnostic); } } } @@ -245,7 +244,7 @@ pub(crate) fn blank_before_after_class(checker: &Checker, docstring: &Docstring) let indentation = indentation_at_offset(docstring.start(), checker.source()) .expect("Own line docstring must have indentation"); let mut diagnostic = - Diagnostic::new(IncorrectBlankLineAfterClass, docstring.range()); + checker.report_diagnostic(IncorrectBlankLineAfterClass, docstring.range()); let line_ending = checker.stylist().line_ending().as_str(); // We have to trim the whitespace twice, once before the semicolon above and // once after the semicolon here, or we get invalid indents: @@ -259,7 +258,7 @@ pub(crate) fn blank_before_after_class(checker: &Checker, docstring: &Docstring) replacement_start, first_line.end(), ))); - checker.report_diagnostic(diagnostic); + return; } else if trailing.starts_with('#') { // Keep the end-of-line comment, start counting empty lines after it @@ -280,14 +279,14 @@ pub(crate) fn blank_before_after_class(checker: &Checker, docstring: &Docstring) } if blank_lines_after != 1 { - let mut diagnostic = Diagnostic::new(IncorrectBlankLineAfterClass, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(IncorrectBlankLineAfterClass, docstring.range()); // Insert a blank line before the class (replacing any existing lines). diagnostic.set_fix(Fix::safe_edit(Edit::replacement( checker.stylist().line_ending().to_string(), replacement_start, blank_lines_end, ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs index a5f0d10cbb1f60..0d3ae7a3fd34a9 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs @@ -1,7 +1,7 @@ use regex::Regex; use std::sync::LazyLock; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::PythonWhitespace; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; @@ -126,7 +126,7 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri } if blank_lines_before != 0 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLineBeforeFunction { num_lines: blank_lines_before, }, @@ -137,7 +137,6 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri blank_lines_start, docstring.line_start(), ))); - checker.report_diagnostic(diagnostic); } } @@ -180,7 +179,7 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri } if blank_lines_after != 0 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLineAfterFunction { num_lines: blank_lines_after, }, @@ -191,7 +190,6 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri first_line_end, blank_lines_end, ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs index b8144df8c5a619..5448705c0629e8 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; @@ -90,7 +90,7 @@ pub(crate) fn capitalized(checker: &Checker, docstring: &Docstring) { let leading_whitespace_len = body.text_len() - trim_start_body.text_len(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( FirstWordUncapitalized { first_word: first_word.to_string(), capitalized_word: capitalized_word.to_string(), @@ -102,6 +102,4 @@ pub(crate) fn capitalized(checker: &Checker, docstring: &Docstring) { capitalized_word, TextRange::at(body.start() + leading_whitespace_len, first_word.text_len()), ))); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs index 8e7ab735dfd830..173ad2552d26bc 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs @@ -1,7 +1,7 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -106,7 +106,8 @@ pub(crate) fn ends_with_period(checker: &Checker, docstring: &Docstring) { } if !trimmed.ends_with('.') { - let mut diagnostic = Diagnostic::new(MissingTrailingPeriod, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(MissingTrailingPeriod, docstring.range()); // Best-effort fix: avoid adding a period after other punctuation marks. if !trimmed.ends_with([':', ';', '?', '!']) { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( @@ -114,7 +115,6 @@ pub(crate) fn ends_with_period(checker: &Checker, docstring: &Docstring) { line.start() + trimmed.text_len(), ))); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs index 2ed5734c450564..070bd61103fe9f 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs @@ -1,7 +1,7 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -105,7 +105,8 @@ pub(crate) fn ends_with_punctuation(checker: &Checker, docstring: &Docstring) { } if !trimmed.ends_with(['.', '!', '?']) { - let mut diagnostic = Diagnostic::new(MissingTerminalPunctuation, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(MissingTerminalPunctuation, docstring.range()); // Best-effort fix: avoid adding a period after other punctuation marks. if !trimmed.ends_with([':', ';']) { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( @@ -113,7 +114,6 @@ pub(crate) fn ends_with_punctuation(checker: &Checker, docstring: &Docstring) { line.start() + trimmed.text_len(), ))); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs index 2fb93a1f207248..d9f528fe142033 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_overload; @@ -82,9 +82,6 @@ pub(crate) fn if_needed(checker: &Checker, docstring: &Docstring) { return; }; if is_overload(&function.decorator_list, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( - OverloadWithDocstring, - function.identifier(), - )); + checker.report_diagnostic(OverloadWithDocstring, function.identifier()); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs index c2796b71d72f11..a6f044b2c55d13 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs @@ -1,5 +1,5 @@ use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_source_file::{Line, NewlineWithTrailingNewline}; @@ -225,12 +225,11 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { // fix. if (is_last || !is_blank) && line_indent_size < docstring_indent_size { let mut diagnostic = - Diagnostic::new(UnderIndentation, TextRange::empty(line.start())); + checker.report_diagnostic(UnderIndentation, TextRange::empty(line.start())); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( clean_space(docstring_indentation), TextRange::at(line.start(), line_indent.text_len()), ))); - checker.report_diagnostic(diagnostic); } } @@ -267,7 +266,7 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { if checker.enabled(Rule::DocstringTabIndentation) { if has_seen_tab { - checker.report_diagnostic(Diagnostic::new(DocstringTabIndentation, docstring.range())); + checker.report_diagnostic(DocstringTabIndentation, docstring.range()); } } @@ -281,7 +280,7 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { // We report over-indentation on every line. This isn't great, but // enables the fix capability. let mut diagnostic = - Diagnostic::new(OverIndentation, TextRange::empty(line.start())); + checker.report_diagnostic(OverIndentation, TextRange::empty(line.start())); let edit = if indent.is_empty() { // Delete the entire indent. @@ -311,7 +310,6 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { Edit::range_replacement(indent, range) }; diagnostic.set_fix(Fix::safe_edit(edit)); - checker.report_diagnostic(diagnostic); } } @@ -324,7 +322,7 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { let is_indent_only = line_indent.len() == last.len(); if last_line_over_indent > 0 && is_indent_only { let mut diagnostic = - Diagnostic::new(OverIndentation, TextRange::empty(last.start())); + checker.report_diagnostic(OverIndentation, TextRange::empty(last.start())); let indent = clean_space(docstring_indentation); let range = TextRange::at(last.start(), line_indent.text_len()); let edit = if indent.is_empty() { @@ -333,7 +331,6 @@ pub(crate) fn indent(checker: &Checker, docstring: &Docstring) { Edit::range_replacement(indent, range) }; diagnostic.set_fix(Fix::safe_edit(edit)); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs index 764fa3682706f1..5d2e1270106fe4 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::is_triple_quote; use ruff_python_semantic::Definition; @@ -156,7 +156,8 @@ pub(crate) fn multi_line_summary_start(checker: &Checker, docstring: &Docstring) if is_triple_quote(&first_line) { if checker.enabled(Rule::MultiLineSummaryFirstLine) { - let mut diagnostic = Diagnostic::new(MultiLineSummaryFirstLine, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(MultiLineSummaryFirstLine, docstring.range()); // Delete until first non-whitespace char. for line in content_lines { if let Some(end_column) = line.find(|c: char| !c.is_whitespace()) { @@ -167,7 +168,6 @@ pub(crate) fn multi_line_summary_start(checker: &Checker, docstring: &Docstring) break; } } - checker.report_diagnostic(diagnostic); } } else if first_line.as_str().ends_with('\\') { // Ignore the edge case whether a single quoted string is multiple lines through an @@ -180,7 +180,8 @@ pub(crate) fn multi_line_summary_start(checker: &Checker, docstring: &Docstring) return; } else { if checker.enabled(Rule::MultiLineSummarySecondLine) { - let mut diagnostic = Diagnostic::new(MultiLineSummarySecondLine, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(MultiLineSummarySecondLine, docstring.range()); let mut indentation = Cow::Borrowed(docstring.compute_indentation()); let mut fixable = true; if !indentation.chars().all(char::is_whitespace) { @@ -223,7 +224,6 @@ pub(crate) fn multi_line_summary_start(checker: &Checker, docstring: &Docstring) first_line.end(), ))); } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs index 1da0e6b83c32e6..da56dbe1ae7283 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs @@ -1,6 +1,6 @@ use ruff_text_size::{TextLen, TextSize}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::clean_space; use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlines}; @@ -79,7 +79,7 @@ pub(crate) fn newline_after_last_paragraph(checker: &Checker, docstring: &Docstr { if last_line != "\"\"\"" && last_line != "'''" { let mut diagnostic = - Diagnostic::new(NewLineAfterLastParagraph, docstring.range()); + checker.report_diagnostic(NewLineAfterLastParagraph, docstring.range()); // Insert a newline just before the end-quote(s). let num_trailing_quotes = "'''".text_len(); let num_trailing_spaces: TextSize = last_line @@ -99,7 +99,6 @@ pub(crate) fn newline_after_last_paragraph(checker: &Checker, docstring: &Docstr docstring.end() - num_trailing_quotes - num_trailing_spaces, docstring.end() - num_trailing_quotes, ))); - checker.report_diagnostic(diagnostic); } } return; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs index 87d6c96d006dd5..2ca5cd7cab79aa 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; @@ -86,6 +86,6 @@ pub(crate) fn no_signature(checker: &Checker, docstring: &Docstring) { true }) { - checker.report_diagnostic(Diagnostic::new(SignatureInDocstring, docstring.range())); + checker.report_diagnostic(SignatureInDocstring, docstring.range()); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs index 91d24db52a1ca0..be8da37b62edf7 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::NewlineWithTrailingNewline; use ruff_text_size::Ranged; @@ -62,7 +62,7 @@ pub(crate) fn no_surrounding_whitespace(checker: &Checker, docstring: &Docstring if line == trimmed { return; } - let mut diagnostic = Diagnostic::new(SurroundingWhitespace, docstring.range()); + let mut diagnostic = checker.report_diagnostic(SurroundingWhitespace, docstring.range()); let quote = docstring.quote_style().as_char(); // If removing whitespace would lead to an invalid string of quote // characters, avoid applying the fix. @@ -72,5 +72,4 @@ pub(crate) fn no_surrounding_whitespace(checker: &Checker, docstring: &Docstring TextRange::at(body.start(), line.text_len()), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs index e5c88bfb36dc71..6a138f49b0d0ee 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs @@ -2,7 +2,7 @@ use std::sync::LazyLock; use imperative::Mood; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::visibility::{is_property, is_test}; use ruff_source_file::UniversalNewlines; @@ -99,11 +99,11 @@ pub(crate) fn non_imperative_mood(checker: &Checker, docstring: &Docstring, sett } if matches!(MOOD.is_imperative(&first_word_norm), Some(false)) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonImperativeMood { first_line: first_line.to_string(), }, docstring.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs index 05dd97fac5d203..ef92f1ede4ce4a 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -46,7 +46,7 @@ pub(crate) fn not_empty(checker: &Checker, docstring: &Docstring) -> bool { } if checker.enabled(Rule::EmptyDocstring) { - checker.report_diagnostic(Diagnostic::new(EmptyDocstring, docstring.range())); + checker.report_diagnostic(EmptyDocstring, docstring.range()); } false } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs index 5d941ab82a2642..e243e946dacd00 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::{ @@ -552,10 +552,7 @@ pub(crate) fn not_missing( return true; } if checker.enabled(Rule::UndocumentedPublicModule) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicModule, - TextRange::default(), - )); + checker.report_diagnostic(UndocumentedPublicModule, TextRange::default()); } false } @@ -564,10 +561,7 @@ pub(crate) fn not_missing( .. }) => { if checker.enabled(Rule::UndocumentedPublicPackage) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicPackage, - TextRange::default(), - )); + checker.report_diagnostic(UndocumentedPublicPackage, TextRange::default()); } false } @@ -576,10 +570,7 @@ pub(crate) fn not_missing( .. }) => { if checker.enabled(Rule::UndocumentedPublicClass) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicClass, - class.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicClass, class.identifier()); } false } @@ -588,10 +579,7 @@ pub(crate) fn not_missing( .. }) => { if checker.enabled(Rule::UndocumentedPublicNestedClass) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicNestedClass, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicNestedClass, function.identifier()); } false } @@ -603,10 +591,7 @@ pub(crate) fn not_missing( true } else { if checker.enabled(Rule::UndocumentedPublicFunction) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicFunction, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicFunction, function.identifier()); } false } @@ -621,34 +606,22 @@ pub(crate) fn not_missing( true } else if is_init(&function.name) { if checker.enabled(Rule::UndocumentedPublicInit) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicInit, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicInit, function.identifier()); } true } else if is_new(&function.name) || is_call(&function.name) { if checker.enabled(Rule::UndocumentedPublicMethod) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicMethod, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicMethod, function.identifier()); } true } else if is_magic(&function.name) { if checker.enabled(Rule::UndocumentedMagicMethod) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedMagicMethod, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedMagicMethod, function.identifier()); } true } else { if checker.enabled(Rule::UndocumentedPublicMethod) { - checker.report_diagnostic(Diagnostic::new( - UndocumentedPublicMethod, - function.identifier(), - )); + checker.report_diagnostic(UndocumentedPublicMethod, function.identifier()); } true } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs index ebfb81b73de7d8..1dafa14cac5a8b 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::NewlineWithTrailingNewline; use ruff_text_size::Ranged; @@ -67,7 +67,8 @@ pub(crate) fn one_liner(checker: &Checker, docstring: &Docstring) { } if non_empty_line_count == 1 && line_count > 1 { - let mut diagnostic = Diagnostic::new(UnnecessaryMultilineDocstring, docstring.range()); + let mut diagnostic = + checker.report_diagnostic(UnnecessaryMultilineDocstring, docstring.range()); // If removing whitespace would lead to an invalid string of quote // characters, avoid applying the fix. @@ -87,7 +88,5 @@ pub(crate) fn one_liner(checker: &Checker, docstring: &Docstring) { docstring.range(), ))); } - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index 050a455f5dd523..b59492c483943b 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashSet; use std::sync::LazyLock; use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_python_ast::identifier::Identifier; @@ -1362,7 +1362,7 @@ fn blanks_and_section_underline( if let Some(dashed_line) = find_underline(&non_blank_line, '-') { if num_blank_lines_after_header > 0 { if checker.enabled(Rule::MissingSectionUnderlineAfterName) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingSectionUnderlineAfterName { name: context.section_name().to_string(), }, @@ -1374,14 +1374,12 @@ fn blanks_and_section_underline( context.following_range().start(), blank_lines_end, ))); - - checker.report_diagnostic(diagnostic); } } if dashed_line.len().to_usize() != context.section_name().len() { if checker.enabled(Rule::MismatchedSectionUnderlineLength) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MismatchedSectionUnderlineLength { name: context.section_name().to_string(), }, @@ -1393,8 +1391,6 @@ fn blanks_and_section_underline( "-".repeat(context.section_name().len()), dashed_line, ))); - - checker.report_diagnostic(diagnostic); } } @@ -1402,7 +1398,7 @@ fn blanks_and_section_underline( let leading_space = leading_space(&non_blank_line); let docstring_indentation = docstring.compute_indentation(); if leading_space.len() > docstring_indentation.len() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( OverindentedSectionUnderline { name: context.section_name().to_string(), }, @@ -1420,8 +1416,6 @@ fn blanks_and_section_underline( } else { Edit::range_replacement(contents, range) })); - - checker.report_diagnostic(diagnostic); } } @@ -1441,12 +1435,12 @@ fn blanks_and_section_underline( if following_lines.peek().is_none() { if checker.enabled(Rule::EmptyDocstringSection) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( EmptyDocstringSection { name: context.section_name().to_string(), }, context.section_name_range(), - )); + ); } } else if checker.enabled(Rule::BlankLinesBetweenHeaderAndContent) { // If the section is followed by exactly one line, and then a @@ -1472,7 +1466,7 @@ fn blanks_and_section_underline( if is_sphinx { if num_blank_lines_dashes_end > 1 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, @@ -1484,10 +1478,9 @@ fn blanks_and_section_underline( line_after_dashes.start(), blank_lines_after_dashes_end, ))); - checker.report_diagnostic(diagnostic); } } else { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, @@ -1498,24 +1491,23 @@ fn blanks_and_section_underline( line_after_dashes.start(), blank_lines_after_dashes_end, ))); - checker.report_diagnostic(diagnostic); } } } } else { if checker.enabled(Rule::EmptyDocstringSection) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( EmptyDocstringSection { name: context.section_name().to_string(), }, context.section_name_range(), - )); + ); } } } else { if style.is_numpy() && checker.enabled(Rule::MissingDashedUnderlineAfterSection) { if let Some(equal_line) = find_underline(&non_blank_line, '=') { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingDashedUnderlineAfterSection { name: context.section_name().to_string(), }, @@ -1528,10 +1520,8 @@ fn blanks_and_section_underline( "-".repeat(context.section_name().len()), equal_line, ))); - - checker.report_diagnostic(diagnostic); } else { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingDashedUnderlineAfterSection { name: context.section_name().to_string(), }, @@ -1549,8 +1539,6 @@ fn blanks_and_section_underline( content, context.summary_range().end(), ))); - - checker.report_diagnostic(diagnostic); } } if num_blank_lines_after_header > 0 { @@ -1577,7 +1565,7 @@ fn blanks_and_section_underline( if is_sphinx { if num_blank_lines_after_header > 1 { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, @@ -1590,10 +1578,9 @@ fn blanks_and_section_underline( context.following_range().start(), blank_lines_end, ))); - checker.report_diagnostic(diagnostic); } } else { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, @@ -1604,7 +1591,6 @@ fn blanks_and_section_underline( TextRange::new(context.following_range().start(), blank_lines_end); // Delete any blank lines between the header and content. diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); - checker.report_diagnostic(diagnostic); } } } @@ -1612,7 +1598,7 @@ fn blanks_and_section_underline( } else { // Nothing but blank lines after the section header. if style.is_numpy() && checker.enabled(Rule::MissingDashedUnderlineAfterSection) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingDashedUnderlineAfterSection { name: context.section_name().to_string(), }, @@ -1630,16 +1616,14 @@ fn blanks_and_section_underline( content, context.summary_range().end(), ))); - - checker.report_diagnostic(diagnostic); } if checker.enabled(Rule::EmptyDocstringSection) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( EmptyDocstringSection { name: context.section_name().to_string(), }, context.section_name_range(), - )); + ); } } } @@ -1655,7 +1639,7 @@ fn common_section( let capitalized_section_name = context.kind().as_str(); if context.section_name() != capitalized_section_name { let section_range = context.section_name_range(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NonCapitalizedSectionName { name: context.section_name().to_string(), }, @@ -1667,7 +1651,6 @@ fn common_section( capitalized_section_name.to_string(), section_range, ))); - checker.report_diagnostic(diagnostic); } } @@ -1676,7 +1659,7 @@ fn common_section( let docstring_indentation = docstring.compute_indentation(); if leading_space.len() > docstring_indentation.len() { let section_range = context.section_name_range(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( OverindentedSection { name: context.section_name().to_string(), }, @@ -1691,7 +1674,6 @@ fn common_section( } else { Edit::range_replacement(content, fix_range) })); - checker.report_diagnostic(diagnostic); } } @@ -1706,7 +1688,7 @@ fn common_section( .count(); if num_blank_lines < 2 { let section_range = context.section_name_range(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NoBlankLineAfterSection { name: context.section_name().to_string(), }, @@ -1717,7 +1699,6 @@ fn common_section( line_end.to_string(), next.start(), ))); - checker.report_diagnostic(diagnostic); } } } else { @@ -1748,14 +1729,13 @@ fn common_section( ); let section_range = context.section_name_range(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingBlankLineAfterLastSection { name: context.section_name().to_string(), }, section_range, ); diagnostic.set_fix(Fix::safe_edit(edit)); - checker.report_diagnostic(diagnostic); } } } @@ -1766,7 +1746,7 @@ fn common_section( .is_some_and(|line| line.trim().is_empty()) { let section_range = context.section_name_range(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NoBlankLineBeforeSection { name: context.section_name().to_string(), }, @@ -1777,7 +1757,6 @@ fn common_section( line_end.to_string(), context.start(), ))); - checker.report_diagnostic(diagnostic); } } @@ -1835,13 +1814,13 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa if !missing_arg_names.is_empty() { if let Some(definition) = docstring.definition.name() { let names = missing_arg_names.into_iter().sorted().collect(); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UndocumentedParam { definition: definition.to_string(), names, }, function.identifier(), - )); + ); } } } @@ -1955,7 +1934,7 @@ fn numpy_section( let suffix = context.summary_after_section_name(); if !suffix.is_empty() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingNewLineAfterSectionName { name: context.section_name().to_string(), }, @@ -1966,8 +1945,6 @@ fn numpy_section( section_range.end(), suffix.text_len(), )))); - - checker.report_diagnostic(diagnostic); } } @@ -1989,7 +1966,7 @@ fn google_section( if checker.enabled(Rule::MissingSectionNameColon) { let suffix = context.summary_after_section_name(); if suffix != ":" { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MissingSectionNameColon { name: context.section_name().to_string(), }, @@ -2001,7 +1978,6 @@ fn google_section( ":".to_string(), TextRange::at(section_name_range.end(), suffix.text_len()), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs index 47274f78884b92..a405786043afa0 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -64,5 +64,5 @@ pub(crate) fn starts_with_this(checker: &Checker, docstring: &Docstring) { if normalize_word(first_word) != "this" { return; } - checker.report_diagnostic(Diagnostic::new(DocstringStartsWithThis, docstring.range())); + checker.report_diagnostic(DocstringStartsWithThis, docstring.range()); } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs index 8bd77b9b73ac52..ef428f8e01d98b 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::Quote; use ruff_text_size::Ranged; @@ -79,8 +79,8 @@ pub(crate) fn triple_quotes(checker: &Checker, docstring: &Docstring) { match expected_quote { Quote::Single => { if !opener.ends_with("'''") { - let mut diagnostic = - Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range()); + let mut diagnostic = checker + .report_diagnostic(TripleSingleQuotes { expected_quote }, docstring.range()); let body = docstring.body().as_str(); if !body.ends_with('\'') { @@ -89,14 +89,12 @@ pub(crate) fn triple_quotes(checker: &Checker, docstring: &Docstring) { docstring.range(), ))); } - - checker.report_diagnostic(diagnostic); } } Quote::Double => { if !opener.ends_with("\"\"\"") { - let mut diagnostic = - Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range()); + let mut diagnostic = checker + .report_diagnostic(TripleSingleQuotes { expected_quote }, docstring.range()); let body = docstring.body().as_str(); if !body.ends_with('"') { @@ -105,8 +103,6 @@ pub(crate) fn triple_quotes(checker: &Checker, docstring: &Docstring) { docstring.range(), ))); } - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs b/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs index a25322fb0f9881..d1cd8a7404e9ac 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -41,7 +41,7 @@ impl Violation for AssertTuple { pub(crate) fn assert_tuple(checker: &Checker, stmt: &Stmt, test: &Expr) { if let Expr::Tuple(tuple) = &test { if !tuple.is_empty() { - checker.report_diagnostic(Diagnostic::new(AssertTuple, stmt.range())); + checker.report_diagnostic(AssertTuple, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs b/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs index ba0e7b99ab4795..dc77d7bd072571 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for `break` statements outside of loops. /// @@ -31,15 +33,16 @@ impl Violation for BreakOutsideLoop { /// F701 pub(crate) fn break_outside_loop<'a>( + checker: &Checker, stmt: &'a Stmt, parents: &mut impl Iterator, -) -> Option { +) { let mut child = stmt; for parent in parents { match parent { Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => { if !orelse.contains(child) { - return None; + return; } } Stmt::FunctionDef(_) | Stmt::ClassDef(_) => { @@ -50,5 +53,5 @@ pub(crate) fn break_outside_loop<'a>( child = parent; } - Some(Diagnostic::new(BreakOutsideLoop, stmt.range())) + checker.report_diagnostic(BreakOutsideLoop, stmt.range()); } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs b/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs index dc4b8422411213..d91863daf695f0 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for `continue` statements outside of loops. /// @@ -31,15 +33,16 @@ impl Violation for ContinueOutsideLoop { /// F702 pub(crate) fn continue_outside_loop<'a>( + checker: &Checker, stmt: &'a Stmt, parents: &mut impl Iterator, -) -> Option { +) { let mut child = stmt; for parent in parents { match parent { Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => { if !orelse.contains(child) { - return None; + return; } } Stmt::FunctionDef(_) | Stmt::ClassDef(_) => { @@ -50,5 +53,5 @@ pub(crate) fn continue_outside_loop<'a>( child = parent; } - Some(Diagnostic::new(ContinueOutsideLoop, stmt.range())) + checker.report_diagnostic(ContinueOutsideLoop, stmt.range()); } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs index a9bf5919e40991..fe97acddc00527 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs @@ -1,9 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::except; use ruff_python_ast::{self as ast, ExceptHandler}; use crate::Locator; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for `except` blocks that handle all exceptions, but are not the last @@ -55,18 +56,14 @@ impl Violation for DefaultExceptNotLast { /// F707 pub(crate) fn default_except_not_last( + checker: &Checker, handlers: &[ExceptHandler], locator: &Locator, -) -> Option { +) { for (idx, handler) in handlers.iter().enumerate() { let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) = handler; if type_.is_none() && idx < handlers.len() - 1 { - return Some(Diagnostic::new( - DefaultExceptNotLast, - except(handler, locator.contents()), - )); + checker.report_diagnostic(DefaultExceptNotLast, except(handler, locator.contents())); } } - - None } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs index df0a6d3b11c17e..f15694b4276f73 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -91,13 +91,13 @@ pub(crate) fn f_string_missing_placeholders(checker: &Checker, expr: &ast::ExprF TextSize::new(1), ); - let mut diagnostic = Diagnostic::new(FStringMissingPlaceholders, f_string.range()); + let mut diagnostic = + checker.report_diagnostic(FStringMissingPlaceholders, f_string.range()); diagnostic.set_fix(convert_f_string_to_regular_string( prefix_range, f_string.range(), checker.locator(), )); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs b/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs index 7c4adce1d42557..3589e787461c31 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::future::is_feature_name; use ruff_text_size::Ranged; @@ -35,10 +35,10 @@ pub(crate) fn future_feature_not_defined(checker: &Checker, alias: &Alias) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( FutureFeatureNotDefined { name: alias.name.to_string(), }, alias.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs b/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs index c6c74f978d095f..f0f874bb390bc4 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, StmtIf}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::stmt_if::if_elif_branches; use ruff_text_size::Ranged; @@ -47,6 +47,6 @@ pub(crate) fn if_tuple(checker: &Checker, stmt_if: &StmtIf) { if tuple.is_empty() { continue; } - checker.report_diagnostic(Diagnostic::new(IfTuple, branch.test.range())); + checker.report_diagnostic(IfTuple, branch.test.range()); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs index 5a0417a1574b8c..15b6c974676bf4 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs @@ -1,6 +1,6 @@ use anyhow::{Error, bail}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers; use ruff_python_ast::{CmpOp, Expr}; @@ -90,7 +90,8 @@ pub(crate) fn invalid_literal_comparison( || helpers::is_mutable_iterable_initializer(left) || helpers::is_mutable_iterable_initializer(right)) { - let mut diagnostic = Diagnostic::new(IsLiteral { cmp_op: op.into() }, expr.range()); + let mut diagnostic = + checker.report_diagnostic(IsLiteral { cmp_op: op.into() }, expr.range()); if lazy_located.is_none() { lazy_located = Some(locate_cmp_ops(expr, checker.tokens())); } @@ -117,7 +118,6 @@ pub(crate) fn invalid_literal_comparison( bail!("Failed to fix invalid comparison due to missing op") } }); - checker.report_diagnostic(diagnostic); } left = right; } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs index ea5e0d3e794ac8..ef2faac12bf41a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_text_size::Ranged; @@ -59,6 +59,6 @@ impl Violation for InvalidPrintSyntax { /// F633 pub(crate) fn invalid_print_syntax(checker: &Checker, left: &Expr) { if checker.semantic().match_builtin_expr(left, "print") { - checker.report_diagnostic(Diagnostic::new(InvalidPrintSyntax, left.range())); + checker.report_diagnostic(InvalidPrintSyntax, left.range()); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs b/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs index 84fd99decc9a98..a66ebda0035e59 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -74,7 +74,7 @@ pub(crate) fn raise_not_implemented(checker: &Checker, expr: &Expr) { let Some(expr) = match_not_implemented(expr) else { return; }; - let mut diagnostic = Diagnostic::new(RaiseNotImplemented, expr.range()); + let mut diagnostic = checker.report_diagnostic(RaiseNotImplemented, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol( "NotImplementedError", @@ -86,5 +86,4 @@ pub(crate) fn raise_not_implemented(checker: &Checker, expr: &Expr) { import_edit, )) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs index 0beeb7293fcc99..a0e22fcc152f39 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs @@ -1,7 +1,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use std::collections::hash_map::Entry; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::{ComparableExpr, HashableExpr}; use ruff_python_ast::parenthesize::parenthesized_range; @@ -177,7 +177,7 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) { | Expr::Tuple(_) | Expr::FString(_) => { if checker.enabled(Rule::MultiValueRepeatedKeyLiteral) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MultiValueRepeatedKeyLiteral { name: SourceCodeSnippet::from_str(checker.locator().slice(key)), existing: SourceCodeSnippet::from_str( @@ -206,12 +206,11 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) { .end(), ))); } - checker.report_diagnostic(diagnostic); } } Expr::Name(_) => { if checker.enabled(Rule::MultiValueRepeatedKeyVariable) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MultiValueRepeatedKeyVariable { name: SourceCodeSnippet::from_str(checker.locator().slice(key)), }, @@ -238,7 +237,6 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) { .end(), ))); } - checker.report_diagnostic(diagnostic); } } _ => {} diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs b/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs index fb7e04f20b0da4..d9db11fc8fec68 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs @@ -1,9 +1,11 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for the use of too many expressions in starred assignment statements. /// @@ -54,17 +56,19 @@ impl Violation for MultipleStarredExpressions { /// F621, F622 pub(crate) fn starred_expressions( + checker: &Checker, elts: &[Expr], check_too_many_expressions: bool, check_two_starred_expressions: bool, location: TextRange, -) -> Option { +) { let mut has_starred: bool = false; let mut starred_index: Option = None; for (index, elt) in elts.iter().enumerate() { if elt.is_starred_expr() { if has_starred && check_two_starred_expressions { - return Some(Diagnostic::new(MultipleStarredExpressions, location)); + checker.report_diagnostic(MultipleStarredExpressions, location); + return; } has_starred = true; starred_index = Some(index); @@ -74,10 +78,8 @@ pub(crate) fn starred_expressions( if check_too_many_expressions { if let Some(starred_index) = starred_index { if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 { - return Some(Diagnostic::new(ExpressionsInStarAssignment, location)); + checker.report_diagnostic(ExpressionsInStarAssignment, location); } } } - - None } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs index 4dde25b7031292..ad50e058ec95b0 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs @@ -2,7 +2,7 @@ use std::string::ToString; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr, Keyword}; @@ -539,7 +539,7 @@ pub(crate) fn percent_format_expected_mapping( | Expr::ListComp(_) | Expr::SetComp(_) | Expr::Generator(_) => { - checker.report_diagnostic(Diagnostic::new(PercentFormatExpectedMapping, location)); + checker.report_diagnostic(PercentFormatExpectedMapping, location); } _ => {} } @@ -554,7 +554,7 @@ pub(crate) fn percent_format_expected_sequence( location: TextRange, ) { if summary.num_positional > 1 && matches!(right, Expr::Dict(_) | Expr::DictComp(_)) { - checker.report_diagnostic(Diagnostic::new(PercentFormatExpectedSequence, location)); + checker.report_diagnostic(PercentFormatExpectedSequence, location); } } @@ -599,7 +599,7 @@ pub(crate) fn percent_format_extra_named_arguments( .iter() .map(|(_, name)| (*name).to_string()) .collect(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PercentFormatExtraNamedArguments { missing: names }, location, ); @@ -613,7 +613,6 @@ pub(crate) fn percent_format_extra_named_arguments( )?; Ok(Fix::safe_edit(edit)) }); - checker.report_diagnostic(diagnostic); } /// F505 @@ -654,12 +653,12 @@ pub(crate) fn percent_format_missing_arguments( .collect(); if !missing.is_empty() { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PercentFormatMissingArgument { missing: missing.iter().map(|&s| s.clone()).collect(), }, location, - )); + ); } } @@ -670,10 +669,7 @@ pub(crate) fn percent_format_mixed_positional_and_named( location: TextRange, ) { if !(summary.num_positional == 0 || summary.keywords.is_empty()) { - checker.report_diagnostic(Diagnostic::new( - PercentFormatMixedPositionalAndNamed, - location, - )); + checker.report_diagnostic(PercentFormatMixedPositionalAndNamed, location); } } @@ -698,13 +694,13 @@ pub(crate) fn percent_format_positional_count_mismatch( } if found != summary.num_positional { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( PercentFormatPositionalCountMismatch { wanted: summary.num_positional, got: found, }, location, - )); + ); } } } @@ -718,8 +714,9 @@ pub(crate) fn percent_format_star_requires_sequence( ) { if summary.starred { match right { - Expr::Dict(_) | Expr::DictComp(_) => checker - .report_diagnostic(Diagnostic::new(PercentFormatStarRequiresSequence, location)), + Expr::Dict(_) | Expr::DictComp(_) => { + checker.report_diagnostic(PercentFormatStarRequiresSequence, location); + } _ => {} } } @@ -757,7 +754,7 @@ pub(crate) fn string_dot_format_extra_named_arguments( } let names: Vec = missing.iter().map(|(_, name)| (*name).clone()).collect(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( StringDotFormatExtraNamedArguments { missing: names }, call.range(), ); @@ -771,7 +768,6 @@ pub(crate) fn string_dot_format_extra_named_arguments( )?; Ok(Fix::safe_edit(edit)) }); - checker.report_diagnostic(diagnostic); } /// F523 @@ -819,7 +815,7 @@ pub(crate) fn string_dot_format_extra_positional_arguments( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( StringDotFormatExtraPositionalArguments { missing: missing .iter() @@ -840,8 +836,6 @@ pub(crate) fn string_dot_format_extra_positional_arguments( Ok(Fix::safe_edit(edit)) }); } - - checker.report_diagnostic(diagnostic); } /// F524 @@ -880,10 +874,7 @@ pub(crate) fn string_dot_format_missing_argument( .collect(); if !missing.is_empty() { - checker.report_diagnostic(Diagnostic::new( - StringDotFormatMissingArguments { missing }, - call.range(), - )); + checker.report_diagnostic(StringDotFormatMissingArguments { missing }, call.range()); } } @@ -894,9 +885,6 @@ pub(crate) fn string_dot_format_mixing_automatic( summary: &FormatSummary, ) { if !(summary.autos.is_empty() || summary.indices.is_empty()) { - checker.report_diagnostic(Diagnostic::new( - StringDotFormatMixingAutomatic, - call.range(), - )); + checker.report_diagnostic(StringDotFormatMixingAutomatic, call.range()); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs index 7a01716e90dea8..9b37ba4b8fe782 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs @@ -1,6 +1,6 @@ use std::string::ToString; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Scope, ScopeId}; use ruff_text_size::Ranged; @@ -62,12 +62,12 @@ pub(crate) fn undefined_local(checker: &Checker, scope_id: ScopeId, scope: &Scop } }) { // Then it's probably an error. - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UndefinedLocal { name: name.to_string(), }, range, - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs index 1494650747316a..b37175a02c4354 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Scope; use ruff_text_size::Ranged; @@ -46,6 +46,6 @@ pub(crate) fn unused_annotation(checker: &Checker, scope: &Scope) { None } }) { - checker.report_diagnostic(Diagnostic::new(UnusedAnnotation { name }, range)); + checker.report_diagnostic(UnusedAnnotation { name }, range); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index d996fdedc6ed02..ff0c15a2af86aa 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -4,7 +4,7 @@ use std::iter; use anyhow::{Result, anyhow, bail}; use std::collections::BTreeMap; -use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Stmt}; @@ -425,7 +425,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) { iter::zip(to_remove, iter::repeat(fix_remove)), iter::zip(to_reexport, iter::repeat(fix_reexport)), ) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnusedImport { name: binding.import.qualified_name().to_string(), module: binding.import.member_name().to_string(), @@ -444,14 +444,13 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) { diagnostic.set_fix(fix.clone()); } } - checker.report_diagnostic(diagnostic); } } // Separately, generate a diagnostic for every _ignored_ import, to ensure that the // suppression comments aren't marked as unused. for binding in ignored.into_values().flatten() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnusedImport { name: binding.import.qualified_name().to_string(), module: binding.import.member_name().to_string(), @@ -465,7 +464,6 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) { if let Some(range) = binding.parent_range { diagnostic.set_parent(range.start()); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs index 2305eaf043b1f6..16c1c69b1100e5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::parenthesize::parenthesized_range; @@ -256,7 +256,7 @@ pub(crate) fn unused_variable(checker: &Checker, name: &str, binding: &Binding) return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnusedVariable { name: name.to_string(), }, @@ -265,5 +265,4 @@ pub(crate) fn unused_variable(checker: &Checker, name: &str, binding: &Binding) if let Some(fix) = remove_unused_variable(binding, checker) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs index 79527c586fee27..ca1ec153c40782 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -61,12 +61,12 @@ pub(crate) fn uncalled_mock_method(checker: &Checker, expr: &Expr) { | "assert_has_calls" | "assert_not_called" ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidMockAccess { reason: Reason::UncalledMethod(attr.to_string()), }, expr.range(), - )); + ); } } } @@ -90,11 +90,11 @@ pub(crate) fn non_existent_mock_method(checker: &Checker, test: &Expr) { | "has_calls" | "not_called" ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidMockAccess { reason: Reason::NonExistentMethod(attr.to_string()), }, test.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs index 2673a57bae0897..648d535f9d0d95 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -49,7 +49,7 @@ impl Violation for AssertOnStringLiteral { pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { match test { Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( AssertOnStringLiteral { kind: if value.is_empty() { Kind::Empty @@ -58,10 +58,10 @@ pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { }, }, test.range(), - )); + ); } Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( AssertOnStringLiteral { kind: if value.is_empty() { Kind::Empty @@ -70,7 +70,7 @@ pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { }, }, test.range(), - )); + ); } Expr::FString(ast::ExprFString { value, .. }) => { let kind = if value.iter().all(|f_string_part| match f_string_part { @@ -100,10 +100,7 @@ pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { } else { Kind::Unknown }; - checker.report_diagnostic(Diagnostic::new( - AssertOnStringLiteral { kind }, - test.range(), - )); + checker.report_diagnostic(AssertOnStringLiteral { kind }, test.range()); } _ => {} } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs index e833be1c7d03e3..1c9c613caddbdb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -82,10 +82,10 @@ pub(crate) fn bad_dunder_method_name(checker: &Checker, method: &ast::StmtFuncti return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadDunderMethodName { name: method.name.to_string(), }, method.identifier(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs index d9900cea2a696a..75d0e47fd51879 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -66,12 +66,12 @@ pub(crate) fn bad_open_mode(checker: &Checker, call: &ast::ExprCall) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadOpenMode { mode: value.to_string(), }, mode.range(), - )); + ); } #[derive(Debug, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs index 937b5846eaafaf..f2abd808ff5ff3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; @@ -101,10 +101,10 @@ pub(crate) fn bad_staticmethod_argument(checker: &Checker, scope: &Scope) { _ => return, } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadStaticmethodArgument { argument_name: self_or_cls.name.to_string(), }, self_or_cls.range(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs index 94e700223ec180..7ce68b0af3e798 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs @@ -3,7 +3,7 @@ use std::fmt; use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::typing; @@ -211,7 +211,5 @@ pub(crate) fn bad_str_strip_call(checker: &Checker, call: &ast::ExprCall) { None }; - let diagnostic = Diagnostic::new(BadStrStripCall { strip, removal }, arg.range()); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(BadStrStripCall { strip, removal }, arg.range()); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs index 6d853115ca9579..a31731b4f9352f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprStringLiteral, StringFlags, StringLiteral}; use ruff_python_literal::{ @@ -50,7 +50,7 @@ pub(crate) fn call(checker: &Checker, string: &str, range: TextRange) { match FormatSpec::parse(format_spec) { Err(FormatSpecError::InvalidFormatType) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadStringFormatCharacter { // The format type character is always the last one. // More info in the official spec: @@ -58,7 +58,7 @@ pub(crate) fn call(checker: &Checker, string: &str, range: TextRange) { format_char: format_spec.chars().last().unwrap(), }, range, - )); + ); } Err(_) => {} Ok(FormatSpec::Static(_)) => {} @@ -70,7 +70,7 @@ pub(crate) fn call(checker: &Checker, string: &str, range: TextRange) { if let Err(FormatSpecError::InvalidFormatType) = FormatSpec::parse(&format_spec) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( BadStringFormatCharacter { // The format type character is always the last one. // More info in the official spec: @@ -78,7 +78,7 @@ pub(crate) fn call(checker: &Checker, string: &str, range: TextRange) { format_char: format_spec.chars().last().unwrap(), }, range, - )); + ); } } } @@ -103,10 +103,7 @@ pub(crate) fn percent(checker: &Checker, expr: &Expr, format_string: &ExprString // Parse the format string (e.g. `"%s"`) into a list of `PercentFormat`. if let Err(format_error) = CFormatString::from_str(string) { if let CFormatErrorType::UnsupportedFormatChar(format_char) = format_error.typ { - checker.report_diagnostic(Diagnostic::new( - BadStringFormatCharacter { format_char }, - expr.range(), - )); + checker.report_diagnostic(BadStringFormatCharacter { format_char }, expr.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs index e11c9e7f4f4497..e61c7969456c41 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs @@ -5,7 +5,7 @@ use ruff_python_literal::cformat::{CFormatPart, CFormatSpec, CFormatStrOrBytes, use ruff_text_size::Ranged; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; @@ -240,6 +240,6 @@ pub(crate) fn bad_string_format_type( _ => is_valid_constant(&format_strings, &bin_op.right), }; if !is_valid { - checker.report_diagnostic(Diagnostic::new(BadStringFormatType, bin_op.range())); + checker.report_diagnostic(BadStringFormatType, bin_op.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs b/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs index 1b1ad85c100f89..f7b074c9a88ead 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -76,8 +76,5 @@ pub(crate) fn binary_op_exception(checker: &Checker, except_handler: &ExceptHand return; }; - checker.report_diagnostic(Diagnostic::new( - BinaryOpException { op: op.into() }, - type_.range(), - )); + checker.report_diagnostic(BinaryOpException { op: op.into() }, type_.range()); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs index f3affe97f75b58..ca6ae6ab943b85 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ BoolOp, CmpOp, Expr, ExprBoolOp, ExprCompare, @@ -69,97 +69,92 @@ pub(crate) fn boolean_chained_comparison(checker: &Checker, expr_bool_op: &ExprB .iter() .map(|expr| expr.as_compare_expr().unwrap()); - let diagnostics = compare_expressions - .tuple_windows() - .filter(|(left_compare, right_compare)| { - are_compare_expr_simplifiable(left_compare, right_compare) - }) - .filter_map(|(left_compare, right_compare)| { - let Expr::Name(left_compare_right) = left_compare.comparators.last()? else { - return None; - }; - - let Expr::Name(right_compare_left) = &*right_compare.left else { - return None; - }; - - if left_compare_right.id() != right_compare_left.id() { - return None; + for (left_compare, right_compare) in + compare_expressions + .tuple_windows() + .filter(|(left_compare, right_compare)| { + are_compare_expr_simplifiable(left_compare, right_compare) + }) + { + let Some(Expr::Name(left_compare_right)) = left_compare.comparators.last() else { + continue; + }; + + let Expr::Name(right_compare_left) = &*right_compare.left else { + continue; + }; + + if left_compare_right.id() != right_compare_left.id() { + continue; + } + + let left_paren_count = parentheses_iterator( + left_compare.into(), + Some(expr_bool_op.into()), + comment_ranges, + locator.contents(), + ) + .count(); + + let right_paren_count = parentheses_iterator( + right_compare.into(), + Some(expr_bool_op.into()), + comment_ranges, + locator.contents(), + ) + .count(); + + // Create the edit that removes the comparison operator + + // In `a<(b) and ((b)) { + let balance_parens_edit = Edit::insertion( + "(".repeat(right_paren_count - left_paren_count), + left_compare.start(), + ); + Fix::safe_edits(edit, [balance_parens_edit]) } + std::cmp::Ordering::Equal => Fix::safe_edit(edit), + std::cmp::Ordering::Greater => { + let balance_parens_edit = Edit::insertion( + ")".repeat(left_paren_count - right_paren_count), + right_compare.end(), + ); + Fix::safe_edits(edit, [balance_parens_edit]) + } + }; - let left_paren_count = parentheses_iterator( - left_compare.into(), - Some(expr_bool_op.into()), - comment_ranges, - locator.contents(), - ) - .count(); - - let right_paren_count = parentheses_iterator( - right_compare.into(), - Some(expr_bool_op.into()), - comment_ranges, - locator.contents(), - ) - .count(); - - // Create the edit that removes the comparison operator + let mut diagnostic = checker.report_diagnostic( + BooleanChainedComparison, + TextRange::new(left_compare.start(), right_compare.end()), + ); - // In `a<(b) and ((b)) { - let balance_parens_edit = Edit::insertion( - "(".repeat(right_paren_count - left_paren_count), - left_compare.start(), - ); - Fix::safe_edits(edit, [balance_parens_edit]) - } - std::cmp::Ordering::Equal => Fix::safe_edit(edit), - std::cmp::Ordering::Greater => { - let balance_parens_edit = Edit::insertion( - ")".repeat(left_paren_count - right_paren_count), - right_compare.end(), - ); - Fix::safe_edits(edit, [balance_parens_edit]) - } - }; - - let mut diagnostic = Diagnostic::new( - BooleanChainedComparison, - TextRange::new(left_compare.start(), right_compare.end()), - ); - - diagnostic.set_fix(fix); - - Some(diagnostic) - }); - - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); + diagnostic.set_fix(fix); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs index aab8ae7935e529..2a356816a57087 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs @@ -1,7 +1,7 @@ use anyhow::Result; use ast::whitespace::indentation; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, ElifElseClause, Stmt}; use ruff_python_codegen::Stylist; @@ -82,7 +82,7 @@ pub(crate) fn collapsible_else_if(checker: &Checker, stmt: &Stmt) { return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CollapsibleElseIf, TextRange::new(else_clause.start(), first.start()), ); @@ -95,7 +95,6 @@ pub(crate) fn collapsible_else_if(checker: &Checker, stmt: &Stmt) { checker.stylist(), ) }); - checker.report_diagnostic(diagnostic); } /// Generate [`Fix`] to convert an `else` block to an `elif` block. diff --git a/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs index 2dc495ff90511a..d880f8033a8f68 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs @@ -2,7 +2,7 @@ use anyhow::bail; use itertools::Itertools; use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -89,13 +89,13 @@ pub(crate) fn compare_to_empty_string( let expr = checker.generator().expr(rhs); let existing = format!("{literal} {op} {expr}"); let replacement = format!("{}{expr}", op.into_unary()); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( CompareToEmptyString { existing, replacement, }, lhs.range(), - )); + ); } } } @@ -107,13 +107,13 @@ pub(crate) fn compare_to_empty_string( let literal = checker.generator().expr(rhs); let existing = format!("{expr} {op} {literal}"); let replacement = format!("{}{expr}", op.into_unary()); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( CompareToEmptyString { existing, replacement, }, rhs.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs b/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs index 2d3662e04a6330..7d01f9ba8cc7f4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{CmpOp, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -62,7 +62,7 @@ pub(crate) fn comparison_of_constant( .zip(ops) { if left.is_literal_expr() && right.is_literal_expr() { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( ComparisonOfConstant { left_constant: checker.generator().expr(left), op: *op, @@ -70,8 +70,6 @@ pub(crate) fn comparison_of_constant( }, left.range(), ); - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs b/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs index 24538dcd516599..a5b3f01f03421c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{CmpOp, Expr}; use ruff_text_size::Ranged; @@ -67,12 +67,12 @@ pub(crate) fn comparison_with_itself( op, checker.locator().slice(right) ); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( ComparisonWithItself { actual: SourceCodeSnippet::new(actual), }, left_name.range(), - )); + ); } // Ex) `id(foo) == id(foo)` (Expr::Call(left_call), Expr::Call(right_call)) => { @@ -115,12 +115,12 @@ pub(crate) fn comparison_with_itself( op, checker.locator().slice(right) ); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( ComparisonWithItself { actual: SourceCodeSnippet::new(actual), }, left_call.range(), - )); + ); } } _ => {} diff --git a/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs b/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs index 67637df202c6a0..f71970f7db165e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -49,7 +49,7 @@ impl Violation for ContinueInFinally { fn traverse_body(checker: &Checker, body: &[Stmt]) { for stmt in body { if stmt.is_continue_stmt() { - checker.report_diagnostic(Diagnostic::new(ContinueInFinally, stmt.range())); + checker.report_diagnostic(ContinueInFinally, stmt.range()); } match stmt { diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs index 0f6eb1150eba5a..54039e19c40dc6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{ @@ -86,8 +86,7 @@ pub(crate) fn dict_index_missing_items(checker: &Checker, stmt_for: &ast::StmtFo }; if has_violation { - let diagnostic = Diagnostic::new(DictIndexMissingItems, stmt_for.range()); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(DictIndexMissingItems, stmt_for.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs index ce1deda04205b2..4ea558f94b8833 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::is_dict; use ruff_python_semantic::{Binding, SemanticModel}; @@ -94,12 +94,11 @@ pub(crate) fn dict_iter_missing_items(checker: &Checker, target: &Expr, iter: &E return; } - let mut diagnostic = Diagnostic::new(DictIterMissingItems, iter.range()); + let mut diagnostic = checker.report_diagnostic(DictIterMissingItems, iter.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( format!("{}.items()", name.id), iter.range(), ))); - checker.report_diagnostic(diagnostic); } /// Returns true if the binding is a dictionary where each key is a tuple with two elements. diff --git a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs index 22f456723eba9a..721be4d36ea68f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr}; use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -67,7 +67,7 @@ pub(crate) fn duplicate_bases(checker: &Checker, name: &str, arguments: Option<& for base in bases { if let Expr::Name(ast::ExprName { id, .. }) = base { if !seen.insert(id) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DuplicateBases { base: id.to_string(), class: name.to_string(), @@ -83,7 +83,6 @@ pub(crate) fn duplicate_bases(checker: &Checker, name: &str, arguments: Option<& ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs index 48627534e8152f..4a50be69c2fae2 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprName, Identifier, StmtAnnAssign, StmtAssign, StmtClassDef, StmtFunctionDef, @@ -122,8 +122,7 @@ pub(crate) fn object_without_hash_method(checker: &Checker, class: &StmtClassDef hash: HasMethod::No } ) { - let diagnostic = Diagnostic::new(EqWithoutHash, class.name.range()); - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(EqWithoutHash, class.name.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs b/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs index 0a897c16f07da3..4c3d6304b6cd84 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; @@ -27,6 +27,6 @@ impl Violation for GlobalAtModuleLevel { /// PLW0604 pub(crate) fn global_at_module_level(checker: &Checker, stmt: &Stmt) { if checker.semantic().current_scope().kind.is_module() { - checker.report_diagnostic(Diagnostic::new(GlobalAtModuleLevel, stmt.range())); + checker.report_diagnostic(GlobalAtModuleLevel, stmt.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs index 11d7d5e41efda8..6ef6a8c97abecf 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -55,13 +55,13 @@ impl Violation for GlobalStatement { /// PLW0603 pub(crate) fn global_statement(checker: &Checker, name: &str) { if let Some(range) = checker.semantic().global(name) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( GlobalStatement { name: name.to_string(), }, // Match Pylint's behavior by reporting on the `global` statement`, rather // than the variable usage. range, - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs index a47be005e0319b..dae0f80732e580 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::parenthesize::parenthesized_range; @@ -176,7 +176,7 @@ pub(crate) fn if_stmt_min_max(checker: &Checker, stmt_if: &ast::StmtIf) { checker.locator().slice(arg2), ); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfStmtMinMax { min_max, replacement: SourceCodeSnippet::from_str(replacement.as_str()), @@ -197,8 +197,6 @@ pub(crate) fn if_stmt_min_max(checker: &Checker, stmt_if: &ast::StmtIf) { applicability, )); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs b/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs index a5dbffb4204dab..f0d96e724f23b3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; @@ -84,7 +84,7 @@ pub(crate) fn import_outside_top_level(checker: &Checker, stmt: &Stmt) { } // Emit the diagnostic - checker.report_diagnostic(Diagnostic::new(ImportOutsideTopLevel, stmt.range())); + checker.report_diagnostic(ImportOutsideTopLevel, stmt.range()); } fn is_banned_module_level_import(policy: &NameMatchPolicy, checker: &Checker) -> bool { diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs b/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs index b465fdd54b4fe8..f70d744c1bd80f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{FromImport, Import, Imported, ResolvedReference, Scope}; @@ -136,10 +136,7 @@ pub(crate) fn import_private_name(checker: &Checker, scope: &Scope) { } else { None }; - checker.report_diagnostic(Diagnostic::new( - ImportPrivateName { name, module }, - binding.range(), - )); + checker.report_diagnostic(ImportPrivateName { name, module }, binding.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_self.rs b/crates/ruff_linter/src/rules/pylint/rules/import_self.rs index c20b16c20c64df..1897f237f4f05b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_self.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_self.rs @@ -1,10 +1,12 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for import statements that import the current module. /// @@ -35,30 +37,36 @@ impl Violation for ImportSelf { } /// PLW0406 -pub(crate) fn import_self(alias: &Alias, module_path: Option<&[String]>) -> Option { - let module_path = module_path?; +pub(crate) fn import_self(checker: &Checker, alias: &Alias, module_path: Option<&[String]>) { + let Some(module_path) = module_path else { + return; + }; if alias.name.split('.').eq(module_path) { - return Some(Diagnostic::new( + checker.report_diagnostic( ImportSelf { name: alias.name.to_string(), }, alias.range(), - )); + ); } - - None } /// PLW0406 pub(crate) fn import_from_self( + checker: &Checker, level: u32, module: Option<&str>, names: &[Alias], module_path: Option<&[String]>, -) -> Option { - let module_path = module_path?; - let imported_module_path = resolve_imported_module_path(level, module, Some(module_path))?; +) { + let Some(module_path) = module_path else { + return; + }; + let Some(imported_module_path) = resolve_imported_module_path(level, module, Some(module_path)) + else { + return; + }; if imported_module_path .split('.') @@ -68,14 +76,12 @@ pub(crate) fn import_from_self( .iter() .find(|alias| alias.name == module_path[module_path.len() - 1]) { - return Some(Diagnostic::new( + checker.report_diagnostic( ImportSelf { name: format!("{}.{}", imported_module_path, alias.name), }, alias.range(), - )); + ); } } - - None } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs index a5a364f8ac3817..78a1095fdd51c3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs @@ -1,8 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for invalid assignments to `__all__`. /// @@ -36,10 +38,8 @@ impl Violation for InvalidAllFormat { } /// PLE0605 -pub(crate) fn invalid_all_format(binding: &Binding) -> Option { +pub(crate) fn invalid_all_format(checker: &Checker, binding: &Binding) { if binding.is_invalid_all_format() { - Some(Diagnostic::new(InvalidAllFormat, binding.range())) - } else { - None + checker.report_diagnostic(InvalidAllFormat, binding.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs index 40536e26029861..3e77db99564de5 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs @@ -1,8 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for the inclusion of invalid objects in `__all__`. /// @@ -36,10 +38,8 @@ impl Violation for InvalidAllObject { } /// PLE0604 -pub(crate) fn invalid_all_object(binding: &Binding) -> Option { +pub(crate) fn invalid_all_object(checker: &Checker, binding: &Binding) { if binding.is_invalid_all_object() { - Some(Diagnostic::new(InvalidAllObject, binding.range())) - } else { - None + checker.report_diagnostic(InvalidAllObject, binding.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs index 4f0d0442eb5fca..967c41394ace56 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -68,10 +68,7 @@ pub(crate) fn invalid_bool_return(checker: &Checker, function_def: &ast::StmtFun // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidBoolReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidBoolReturnType, function_def.identifier()); return; } @@ -88,11 +85,11 @@ pub(crate) fn invalid_bool_return(checker: &Checker, function_def: &ast::StmtFun ResolvedPythonType::Unknown | ResolvedPythonType::Atom(PythonType::Number(NumberLike::Bool)) ) { - checker.report_diagnostic(Diagnostic::new(InvalidBoolReturnType, value.range())); + checker.report_diagnostic(InvalidBoolReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidBoolReturnType, stmt.range())); + checker.report_diagnostic(InvalidBoolReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs index deb5d58cdc1872..2b57f30983dcdb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -68,10 +68,7 @@ pub(crate) fn invalid_bytes_return(checker: &Checker, function_def: &ast::StmtFu // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidBytesReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidBytesReturnType, function_def.identifier()); return; } @@ -87,11 +84,11 @@ pub(crate) fn invalid_bytes_return(checker: &Checker, function_def: &ast::StmtFu ResolvedPythonType::from(value), ResolvedPythonType::Unknown | ResolvedPythonType::Atom(PythonType::Bytes) ) { - checker.report_diagnostic(Diagnostic::new(InvalidBytesReturnType, value.range())); + checker.report_diagnostic(InvalidBytesReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidBytesReturnType, stmt.range())); + checker.report_diagnostic(InvalidBytesReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs index a6984b817ff828..66ab20d2b54465 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -70,6 +70,6 @@ pub(crate) fn invalid_envvar_default(checker: &Checker, call: &ast::ExprCall) { ) { return; } - checker.report_diagnostic(Diagnostic::new(InvalidEnvvarDefault, expr.range())); + checker.report_diagnostic(InvalidEnvvarDefault, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs index 68d29992704420..52dfa3365d19ae 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -58,6 +58,6 @@ pub(crate) fn invalid_envvar_value(checker: &Checker, call: &ast::ExprCall) { return; } - checker.report_diagnostic(Diagnostic::new(InvalidEnvvarValue, expr.range())); + checker.report_diagnostic(InvalidEnvvarValue, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs index 89fff4d2bbd062..ca88a11b673c86 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -72,10 +72,7 @@ pub(crate) fn invalid_hash_return(checker: &Checker, function_def: &ast::StmtFun // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidHashReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidHashReturnType, function_def.identifier()); return; } @@ -92,11 +89,11 @@ pub(crate) fn invalid_hash_return(checker: &Checker, function_def: &ast::StmtFun ResolvedPythonType::Unknown | ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) ) { - checker.report_diagnostic(Diagnostic::new(InvalidHashReturnType, value.range())); + checker.report_diagnostic(InvalidHashReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidHashReturnType, stmt.range())); + checker.report_diagnostic(InvalidHashReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs index cd3f64a16b0b85..edc3ebd1664cb2 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -74,10 +74,7 @@ pub(crate) fn invalid_index_return(checker: &Checker, function_def: &ast::StmtFu // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidIndexReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidIndexReturnType, function_def.identifier()); return; } @@ -94,11 +91,11 @@ pub(crate) fn invalid_index_return(checker: &Checker, function_def: &ast::StmtFu ResolvedPythonType::Unknown | ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) ) { - checker.report_diagnostic(Diagnostic::new(InvalidIndexReturnType, value.range())); + checker.report_diagnostic(InvalidIndexReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidIndexReturnType, stmt.range())); + checker.report_diagnostic(InvalidIndexReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs index bfe11d45e6efc7..cc5ca4c4028d89 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -73,10 +73,7 @@ pub(crate) fn invalid_length_return(checker: &Checker, function_def: &ast::StmtF // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidLengthReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidLengthReturnType, function_def.identifier()); return; } @@ -95,11 +92,11 @@ pub(crate) fn invalid_length_return(checker: &Checker, function_def: &ast::StmtF | ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) ) { - checker.report_diagnostic(Diagnostic::new(InvalidLengthReturnType, value.range())); + checker.report_diagnostic(InvalidLengthReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidLengthReturnType, stmt.range())); + checker.report_diagnostic(InvalidLengthReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs index 5e07f2bfd702af..f102d5305b61ba 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -68,10 +68,7 @@ pub(crate) fn invalid_str_return(checker: &Checker, function_def: &ast::StmtFunc // If there are no return statements, add a diagnostic. if terminal == Terminal::Implicit { - checker.report_diagnostic(Diagnostic::new( - InvalidStrReturnType, - function_def.identifier(), - )); + checker.report_diagnostic(InvalidStrReturnType, function_def.identifier()); return; } @@ -87,11 +84,11 @@ pub(crate) fn invalid_str_return(checker: &Checker, function_def: &ast::StmtFunc ResolvedPythonType::from(value), ResolvedPythonType::Unknown | ResolvedPythonType::Atom(PythonType::String) ) { - checker.report_diagnostic(Diagnostic::new(InvalidStrReturnType, value.range())); + checker.report_diagnostic(InvalidStrReturnType, value.range()); } } else { // Disallow implicit `None`. - checker.report_diagnostic(Diagnostic::new(InvalidStrReturnType, stmt.range())); + checker.report_diagnostic(InvalidStrReturnType, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs b/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs index f3511e25b1bdb1..96a8ff20407a44 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs @@ -1,6 +1,6 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::comparable::HashableExpr; @@ -63,7 +63,7 @@ pub(crate) fn iteration_over_set(checker: &Checker, expr: &Expr) { } } - let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range()); + let mut diagnostic = checker.report_diagnostic(IterationOverSet, expr.range()); let tuple = if let [elt] = set.elts.as_slice() { let elt = checker.locator().slice(elt); @@ -73,6 +73,4 @@ pub(crate) fn iteration_over_set(checker: &Checker, expr: &Expr) { format!("({})", &set[1..set.len() - 1]) }; diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(tuple, expr.range()))); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs index 9aaa1177deb95a..f1e586b8a6fccf 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; @@ -90,18 +90,17 @@ pub(crate) fn len_test(checker: &Checker, call: &ExprCall) { let replacement = checker.locator().slice(argument.range()).to_string(); - checker.report_diagnostic( - Diagnostic::new( + checker + .report_diagnostic( LenTest { expression: SourceCodeSnippet::new(replacement.clone()), }, call.range(), ) - .with_fix(Fix::safe_edit(Edit::range_replacement( + .set_fix(Fix::safe_edit(Edit::range_replacement( replacement, call.range(), - ))), - ); + ))); } fn is_indirect_sequence(expr: &Expr, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs b/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs index 10777dbd129507..9d398694da0a7a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::analyze::typing; @@ -101,7 +101,7 @@ pub(crate) fn literal_membership(checker: &Checker, compare: &ast::ExprCompare) return; } - let mut diagnostic = Diagnostic::new(LiteralMembership, right.range()); + let mut diagnostic = checker.report_diagnostic(LiteralMembership, right.range()); let literal = checker.locator().slice(right); let set = format!("{{{}}}", &literal[1..literal.len() - 1]); @@ -109,6 +109,4 @@ pub(crate) fn literal_membership(checker: &Checker, compare: &ast::ExprCompare) set, right.range(), ))); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/logging.rs b/crates/ruff_linter/src/rules/pylint/rules/logging.rs index 80ba1fdddd5644..326ca4a073ac7d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/logging.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/logging.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::analyze::logging; @@ -154,13 +154,13 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) { if checker.enabled(Rule::LoggingTooManyArgs) { if summary.num_positional < num_message_args { - checker.report_diagnostic(Diagnostic::new(LoggingTooManyArgs, call.func.range())); + checker.report_diagnostic(LoggingTooManyArgs, call.func.range()); } } if checker.enabled(Rule::LoggingTooFewArgs) { if num_message_args > 0 && num_keywords == 0 && summary.num_positional > num_message_args { - checker.report_diagnostic(Diagnostic::new(LoggingTooFewArgs, call.func.range())); + checker.report_diagnostic(LoggingTooFewArgs, call.func.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs index 96cfe2d3d3ae8d..258455bdb094be 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, UnaryOp}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -111,12 +111,12 @@ pub(crate) fn magic_value_comparison(checker: &Checker, left: &Expr, comparators for comparison_expr in std::iter::once(left).chain(comparators) { if let Some(value) = as_literal(comparison_expr) { if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( MagicValueComparison { value: checker.locator().slice(comparison_expr).to_string(), }, comparison_expr.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs index 0d4db4219a7c4c..0b8659aaee45a8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Alias, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -58,7 +58,7 @@ pub(crate) fn manual_from_import(checker: &Checker, stmt: &Stmt, alias: &Alias, return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ManualFromImport { module: module.to_string(), name: name.to_string(), @@ -81,5 +81,4 @@ pub(crate) fn manual_from_import(checker: &Checker, stmt: &Stmt, alias: &Alias, stmt.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs b/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs index 7191e4149cb1d0..99bc0483bc3f1f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -64,5 +64,5 @@ pub(crate) fn misplaced_bare_raise(checker: &Checker, raise: &ast::StmtRaise) { return; } - checker.report_diagnostic(Diagnostic::new(MisplacedBareRaise, raise.range())); + checker.report_diagnostic(MisplacedBareRaise, raise.range()); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs index a2647678cae30a..10e1ad9dbb069e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_body; use ruff_python_ast::name::Name; @@ -97,7 +97,7 @@ pub(crate) fn modified_iterating_set(checker: &Checker, for_stmt: &StmtFor) { }); if is_modified { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ModifiedIteratingSet { name: name.id.clone(), }, @@ -107,7 +107,6 @@ pub(crate) fn modified_iterating_set(checker: &Checker, for_stmt: &StmtFor) { format!("{}.copy()", checker.locator().slice(name)), name.range(), ))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs b/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs index df784953ae70cf..0c6a0361b77611 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -37,6 +37,6 @@ impl Violation for NamedExprWithoutContext { /// PLW0131 pub(crate) fn named_expr_without_context(checker: &Checker, value: &Expr) { if let Expr::Named(ast::ExprNamed { range, .. }) = value { - checker.report_diagnostic(Diagnostic::new(NamedExprWithoutContext, *range)); + checker.report_diagnostic(NamedExprWithoutContext, *range); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs index 31f2dceeb6e0b2..e645097a05fb04 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -66,26 +66,17 @@ fn nan_comparison_impl<'a>(checker: &Checker, comparators: impl Iterator { - checker.report_diagnostic(Diagnostic::new( - NanComparison { nan: Nan::NumPy }, - expr.range(), - )); + checker.report_diagnostic(NanComparison { nan: Nan::NumPy }, expr.range()); } ["math", "nan"] => { - checker.report_diagnostic(Diagnostic::new( - NanComparison { nan: Nan::Math }, - expr.range(), - )); + checker.report_diagnostic(NanComparison { nan: Nan::Math }, expr.range()); } _ => continue, } } if is_nan_float(expr, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new( - NanComparison { nan: Nan::Math }, - expr.range(), - )); + checker.report_diagnostic(NanComparison { nan: Nan::Math }, expr.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index b4e11ca8337147..e2f6f9d28ee550 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::SemanticModel; @@ -169,7 +169,8 @@ pub(crate) fn nested_min_max( }; MinMax::try_from_call(func.as_ref(), keywords.as_ref(), checker.semantic()) == Some(min_max) }) { - let mut diagnostic = Diagnostic::new(NestedMinMax { func: min_max }, expr.range()); + let mut diagnostic = + checker.report_diagnostic(NestedMinMax { func: min_max }, expr.range()); if !checker .comment_ranges() .has_comments(expr, checker.source()) @@ -188,6 +189,5 @@ pub(crate) fn nested_min_max( expr.range(), ))); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs index 6fae8631fe90a3..bca6480e7a51ea 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -172,8 +172,10 @@ fn get_undecorated_methods(checker: &Checker, class_stmt: &Stmt, method_type: &M let range = TextRange::new(stmt.range().start(), stmt.range().start()); let mut diagnostic = match method_type { - MethodType::Classmethod => Diagnostic::new(NoClassmethodDecorator, range), - MethodType::Staticmethod => Diagnostic::new(NoStaticmethodDecorator, range), + MethodType::Classmethod => checker.report_diagnostic(NoClassmethodDecorator, range), + MethodType::Staticmethod => { + checker.report_diagnostic(NoStaticmethodDecorator, range) + } }; let indentation = indentation_at_offset(stmt.range().start(), checker.source()); @@ -192,7 +194,6 @@ fn get_undecorated_methods(checker: &Checker, class_stmt: &Stmt, method_type: &M checker.indexer(), )], )); - checker.report_diagnostic(diagnostic); } None => { continue; diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index e81857a6fe934d..c18bfd1c02dd33 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -126,11 +126,11 @@ pub(crate) fn no_self_use(checker: &Checker, scope_id: ScopeId, scope: &Scope) { .map(|binding_id| semantic.binding(binding_id)) .is_some_and(|binding| binding.kind.is_argument() && binding.is_unused()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NoSelfUse { method_name: name.to_string(), }, func.identifier(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs index cfb983e90d4cce..33e164d9505a37 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -69,24 +69,24 @@ pub(crate) fn non_ascii_module_import(checker: &Checker, alias: &Alias) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonAsciiImportName { name: asname.to_string(), kind: Kind::Aliased, }, asname.range(), - )); + ); } else { if alias.name.as_str().is_ascii() { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonAsciiImportName { name: alias.name.to_string(), kind: Kind::Unaliased, }, alias.name.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs index fbc6f2b14cf9f5..d1f2fd19ab650d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs @@ -1,11 +1,11 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, BindingKind}; use ruff_text_size::Ranged; -use crate::Locator; +use crate::checkers::ast::Checker; /// ## What it does /// Checks for the use of non-ASCII characters in variable names. @@ -44,10 +44,11 @@ impl Violation for NonAsciiName { } /// PLC2401 -pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option { +pub(crate) fn non_ascii_name(checker: &Checker, binding: &Binding) { + let locator = checker.locator(); let name = binding.name(locator.contents()); if name.is_ascii() { - return None; + return; } let kind = match binding.kind { @@ -73,17 +74,17 @@ pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option { - return None; + return; } }; - Some(Diagnostic::new( + checker.report_diagnostic( NonAsciiName { name: name.to_string(), kind, }, binding.range(), - )) + ); } #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs index 567a5b509eddf8..d96461609e693b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs @@ -1,5 +1,5 @@ use ast::Expr; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::comparable::ComparableExpr; @@ -101,7 +101,8 @@ pub(crate) fn non_augmented_assignment(checker: &Checker, assign: &ast::StmtAssi // Match, e.g., `x = x + 1`. if ComparableExpr::from(target) == ComparableExpr::from(&value.left) { - let mut diagnostic = Diagnostic::new(NonAugmentedAssignment { operator }, assign.range()); + let mut diagnostic = + checker.report_diagnostic(NonAugmentedAssignment { operator }, assign.range()); diagnostic.set_fix(Fix::unsafe_edit(augmented_assignment( checker, target, @@ -110,7 +111,7 @@ pub(crate) fn non_augmented_assignment(checker: &Checker, assign: &ast::StmtAssi value, assign.range, ))); - checker.report_diagnostic(diagnostic); + return; } @@ -120,7 +121,8 @@ pub(crate) fn non_augmented_assignment(checker: &Checker, assign: &ast::StmtAssi && (value.left.is_number_literal_expr() || value.left.is_boolean_literal_expr()) && ComparableExpr::from(target) == ComparableExpr::from(&value.right) { - let mut diagnostic = Diagnostic::new(NonAugmentedAssignment { operator }, assign.range()); + let mut diagnostic = + checker.report_diagnostic(NonAugmentedAssignment { operator }, assign.range()); diagnostic.set_fix(Fix::unsafe_edit(augmented_assignment( checker, target, @@ -129,7 +131,6 @@ pub(crate) fn non_augmented_assignment(checker: &Checker, assign: &ast::StmtAssi value, assign.range, ))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs index 76a831efdf8409..ec75d836d3621b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; @@ -74,12 +74,12 @@ pub(crate) fn non_slot_assignment(checker: &Checker, class_def: &ast::StmtClassD } for attribute in is_attributes_not_in_slots(&class_def.body) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonSlotAssignment { name: attribute.name.to_string(), }, attribute.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs index 737c1fd092459e..060d1715e4c4dd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -59,12 +59,12 @@ pub(crate) fn nonlocal_and_global(checker: &Checker, nonlocal: &ast::StmtNonloca // `global`. for name in &nonlocal.names { if let Some(global) = checker.semantic().global(name) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( NonlocalAndGlobal { name: name.to_string(), }, global, - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index 816fa6e5ca9027..060e3bce455ad9 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -66,6 +66,6 @@ pub(crate) fn potential_index_error(checker: &Checker, value: &Expr, slice: &Exp // Emit a diagnostic if the index is out of bounds. If the index can't be represented as an // `i64`, but the length _can_, then the index is definitely out of bounds. if index.is_none_or(|index| index >= length || index < -length) { - checker.report_diagnostic(Diagnostic::new(PotentialIndexError, slice.range())); + checker.report_diagnostic(PotentialIndexError, slice.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs index 262fea2342aad1..cb1d5890641cb8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Decorator, Parameters, Stmt, identifier::Identifier}; use ruff_python_semantic::analyze::visibility::is_property; @@ -57,6 +57,6 @@ pub(crate) fn property_with_parameters( let semantic = checker.semantic(); let extra_property_decorators = checker.settings.pydocstyle.property_decorators(); if is_property(decorator_list, extra_property_decorators, semantic) { - checker.report_diagnostic(Diagnostic::new(PropertyWithParameters, stmt.identifier())); + checker.report_diagnostic(PropertyWithParameters, stmt.identifier()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs index d63b1a58065e62..64a5e1823a9b99 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_text_size::Ranged; @@ -63,12 +63,12 @@ fn check_expr(checker: &Checker, expr: &Expr, names: &mut Vec) { return; } if names.contains(id) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RedeclaredAssignedName { name: id.to_string(), }, expr.range(), - )); + ); } names.push(id.clone()); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs index 3d46e8b768d61b..1a55ecc21cee5f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs @@ -3,7 +3,7 @@ use std::{fmt, iter}; use regex::Regex; use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Stmt, WithItem}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; @@ -385,14 +385,14 @@ pub(crate) fn redefined_loop_name(checker: &Checker, stmt: &Stmt) { if ComparableExpr::from(outer_assignment_target.expr) .eq(&(ComparableExpr::from(inner_assignment_target.expr))) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RedefinedLoopName { name: checker.generator().expr(outer_assignment_target.expr), outer_kind: outer_assignment_target.binding_kind, inner_kind: inner_assignment_target.binding_kind, }, inner_assignment_target.expr.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs index 675045a73db93e..b0273d2a4da15a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use ruff_python_semantic::analyze::class::iter_super_class; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; @@ -104,13 +104,13 @@ impl Ranged for Slot<'_> { fn check_super_slots(checker: &Checker, class_def: &ast::StmtClassDef, slot: &Slot) { for super_class in iter_super_class(class_def, checker.semantic()).skip(1) { if slots_members(&super_class.body).contains(slot) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RedefinedSlotsInSubclass { base: super_class.name.to_string(), slot_name: slot.name.to_string(), }, slot.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index 3a7f4c78053901..61371a360793f8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use rustc_hash::{FxBuildHasher, FxHashMap}; use ast::ExprContext; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{any_over_expr, contains_effect}; @@ -139,7 +139,7 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp .iter() .all(|comparator| comparator.is_literal_expr()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RepeatedEqualityComparison { expression: SourceCodeSnippet::new(merged_membership_test( expr, @@ -193,8 +193,6 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp })), bool_op.range(), ))); - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs index 80fb83c9113c7d..434b30c19c6e31 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs @@ -1,6 +1,6 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, ExprStringLiteral}; use ruff_text_size::Ranged; @@ -44,24 +44,24 @@ pub(crate) fn repeated_keyword_argument(checker: &Checker, call: &ExprCall) { if let Some(id) = &keyword.arg { // Ex) `func(a=1, a=2)` if !seen.insert(id.as_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RepeatedKeywordArgument { duplicate_keyword: id.to_string(), }, keyword.range(), - )); + ); } } else if let Expr::Dict(dict) = &keyword.value { // Ex) `func(**{"a": 1, "a": 2})` for key in dict.iter_keys().flatten() { if let Expr::StringLiteral(ExprStringLiteral { value, .. }) = key { if !seen.insert(value.to_str()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( RepeatedKeywordArgument { duplicate_keyword: value.to_string(), }, key.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs index 11f05ca9f37c71..ee3398dfa4127b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -59,6 +59,6 @@ pub(crate) fn return_in_init(checker: &Checker, stmt: &Stmt) { } if in_dunder_method("__init__", checker.semantic(), checker.settings) { - checker.report_diagnostic(Diagnostic::new(ReturnInInit, stmt.range())); + checker.report_diagnostic(ReturnInInit, stmt.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs b/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs index d5d9117bb8a59f..9cedeae300ed72 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -79,12 +79,12 @@ fn visit_assignments(checker: &Checker, left: &Expr, right: &Expr) { Expr::Name(ast::ExprName { id: lhs_name, .. }), Expr::Name(ast::ExprName { id: rhs_name, .. }), ) if lhs_name == rhs_name => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( SelfAssigningVariable { name: lhs_name.to_string(), }, left.range(), - )); + ); } _ => {} } diff --git a/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs index aead8c0939a6ff..df4db639e9e766 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::ScopeKind; @@ -117,10 +117,7 @@ fn check_expr(checker: &Checker, target: &Expr, method_type: MethodType) { Expr::Name(_) => { if let Expr::Name(ast::ExprName { id, .. }) = target { if id.as_str() == method_type.arg_name() { - checker.report_diagnostic(Diagnostic::new( - SelfOrClsAssignment { method_type }, - target.range(), - )); + checker.report_diagnostic(SelfOrClsAssignment { method_type }, target.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs index 46c3b821c8b5e9..7517b651eceff8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; @@ -88,10 +88,9 @@ pub(crate) fn shallow_copy_environ(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(ShallowCopyEnviron, call.range()); + let mut diagnostic = checker.report_diagnostic(ShallowCopyEnviron, call.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( format!("{}.copy()", checker.locator().slice(arg)), call.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs b/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs index 83c28bc4526745..73265be1e07575 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt, StmtClassDef}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; @@ -66,10 +66,7 @@ pub(crate) fn single_string_slots(checker: &Checker, class: &StmtClassDef) { if let Expr::Name(ast::ExprName { id, .. }) = target { if id.as_str() == "__slots__" { if matches!(value.as_ref(), Expr::StringLiteral(_) | Expr::FString(_)) { - checker.report_diagnostic(Diagnostic::new( - SingleStringSlots, - stmt.identifier(), - )); + checker.report_diagnostic(SingleStringSlots, stmt.identifier()); } } } @@ -83,10 +80,7 @@ pub(crate) fn single_string_slots(checker: &Checker, class: &StmtClassDef) { if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { if id.as_str() == "__slots__" { if matches!(value.as_ref(), Expr::StringLiteral(_) | Expr::FString(_)) { - checker.report_diagnostic(Diagnostic::new( - SingleStringSlots, - stmt.identifier(), - )); + checker.report_diagnostic(SingleStringSlots, stmt.identifier()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs index adf728ec82951f..33d02279375651 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Scope; @@ -99,7 +99,7 @@ pub(crate) fn singledispatch_method(checker: &Checker, scope: &Scope) { matches!(qualified_name.segments(), ["functools", "singledispatch"]) }) { - let mut diagnostic = Diagnostic::new(SingledispatchMethod, decorator.range()); + let mut diagnostic = checker.report_diagnostic(SingledispatchMethod, decorator.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("functools", "singledispatchmethod"), @@ -111,7 +111,6 @@ pub(crate) fn singledispatch_method(checker: &Checker, scope: &Scope) { [import_edit], )) }); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs index c8cacdf7e55410..0e740b9148491b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Scope; @@ -95,7 +95,8 @@ pub(crate) fn singledispatchmethod_function(checker: &Checker, scope: &Scope) { ) }) { - let mut diagnostic = Diagnostic::new(SingledispatchmethodFunction, decorator.range()); + let mut diagnostic = + checker.report_diagnostic(SingledispatchmethodFunction, decorator.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("functools", "singledispatch"), @@ -107,7 +108,6 @@ pub(crate) fn singledispatchmethod_function(checker: &Checker, scope: &Scope) { [import_edit], )) }); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs index 3a06bac852fc5d..f3a0a0bd505cdd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; @@ -65,7 +65,7 @@ pub(crate) fn subprocess_popen_preexec_fn(checker: &Checker, call: &ast::ExprCal .find_keyword("preexec_fn") .filter(|keyword| !keyword.value.is_none_literal_expr()) { - checker.report_diagnostic(Diagnostic::new(SubprocessPopenPreexecFn, keyword.range())); + checker.report_diagnostic(SubprocessPopenPreexecFn, keyword.range()); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs index e3ec7b6e0a8a78..22359975330f31 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -71,7 +71,8 @@ pub(crate) fn subprocess_run_without_check(checker: &Checker, call: &ast::ExprCa .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["subprocess", "run"])) { if call.arguments.find_keyword("check").is_none() { - let mut diagnostic = Diagnostic::new(SubprocessRunWithoutCheck, call.func.range()); + let mut diagnostic = + checker.report_diagnostic(SubprocessRunWithoutCheck, call.func.range()); diagnostic.set_fix(Fix::applicable_edit( add_argument( "check=False", @@ -91,7 +92,6 @@ pub(crate) fn subprocess_run_without_check(checker: &Checker, call: &ast::ExprCa Applicability::Safe }, )); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs index e8ca774a345e61..9bd206c4b8a957 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{ScopeKind, analyze::function_type}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -108,12 +108,10 @@ pub(crate) fn super_without_brackets(checker: &Checker, func: &Expr) { return; } - let mut diagnostic = Diagnostic::new(SuperWithoutBrackets, value.range()); + let mut diagnostic = checker.report_diagnostic(SuperWithoutBrackets, value.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "super()".to_string(), value.range(), ))); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs b/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs index 997faa53149646..93e6ee00ddf367 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; @@ -77,7 +77,7 @@ pub(crate) fn sys_exit_alias(checker: &Checker, call: &ExprCall) { if !matches!(builtin, "exit" | "quit") { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( SysExitAlias { name: builtin.to_string(), }, @@ -91,7 +91,6 @@ pub(crate) fn sys_exit_alias(checker: &Checker, call: &ExprCall) { .any(|kwarg| kwarg.arg.is_none()); // only one optional argument allowed, and we can't convert **kwargs if call.arguments.len() > 1 || has_star_kwargs { - checker.report_diagnostic(diagnostic); return; } @@ -111,5 +110,4 @@ pub(crate) fn sys_exit_alias(checker: &Checker, call: &ExprCall) { } Ok(Fix::unsafe_edits(import_edit, edits)) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs index 8c368935616c6a..58d4fd938e8b92 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -108,11 +108,11 @@ pub(crate) fn too_many_arguments(checker: &Checker, function_def: &ast::StmtFunc return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyArguments { c_args: num_arguments, max_args: checker.settings.pylint.max_args, }, function_def.identifier(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs index 225d53aee6fdbf..1780b99159b98b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs @@ -1,5 +1,5 @@ use ast::{Expr, StmtIf}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -47,13 +47,13 @@ pub(crate) fn too_many_boolean_expressions(checker: &Checker, stmt: &StmtIf) { if let Some(bool_op) = stmt.test.as_bool_op_expr() { let expressions = count_bools(bool_op); if expressions > checker.settings.pylint.max_bool_expr { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyBooleanExpressions { expressions, max_expressions: checker.settings.pylint.max_bool_expr, }, bool_op.range(), - )); + ); } } @@ -61,13 +61,13 @@ pub(crate) fn too_many_boolean_expressions(checker: &Checker, stmt: &StmtIf) { if let Some(bool_op) = elif.test.as_ref().and_then(Expr::as_bool_op_expr) { let expressions = count_bools(bool_op); if expressions > checker.settings.pylint.max_bool_expr { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyBooleanExpressions { expressions, max_expressions: checker.settings.pylint.max_bool_expr, }, bool_op.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs index 73cddc644c096f..529e6fda2c7340 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for functions or methods with too many branches, including (nested) /// `if`, `elif`, and `else` branches, `for` loops, `try`-`except` clauses, and @@ -233,21 +235,20 @@ fn num_branches(stmts: &[Stmt]) -> usize { /// PLR0912 pub(crate) fn too_many_branches( + checker: &Checker, stmt: &Stmt, body: &[Stmt], max_branches: usize, -) -> Option { +) { let branches = num_branches(body); if branches > max_branches { - Some(Diagnostic::new( + checker.report_diagnostic( TooManyBranches { branches, max_branches, }, stmt.identifier(), - )) - } else { - None + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs index c0bdcaffff3e5d..43079c837571fa 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::{Scope, ScopeKind}; @@ -47,13 +47,13 @@ pub(crate) fn too_many_locals(checker: &Checker, scope: &Scope) { .count(); if num_locals > checker.settings.pylint.max_locals { if let ScopeKind::Function(func) = scope.kind { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyLocals { current_amount: num_locals, max_amount: checker.settings.pylint.max_locals, }, func.identifier(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs index e5546784620d9a..80900f84f80a10 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs @@ -1,5 +1,5 @@ use ast::ExceptHandler; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; @@ -74,13 +74,13 @@ pub(crate) fn too_many_nested_blocks(checker: &Checker, stmt: &Stmt) { return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyNestedBlocks { nested_blocks: count, max_nested_blocks, }, checker.semantic().statement(root_id).range(), - )); + ); } /// Returns `true` if the given statement is a nested block. diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs index 79e05dae553f99..39b0e9dfbff3f6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, identifier::Identifier}; use ruff_python_semantic::analyze::{function_type, visibility}; @@ -112,11 +112,11 @@ pub(crate) fn too_many_positional_arguments( return; } - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyPositionalArguments { c_pos: num_positional_args, max_pos: checker.settings.pylint.max_positional_args, }, function_def.identifier(), - )); + ); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs index 5853ad0264c378..0e6a58db944f2e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::analyze::visibility::{self, Visibility::Public}; @@ -121,12 +121,12 @@ pub(crate) fn too_many_public_methods( .count(); if methods > max_methods { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TooManyPublicMethods { methods, max_methods, }, class_def.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs index 7c7f85d417b2c6..14e29b82a6e8d2 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs @@ -1,11 +1,13 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor::Visitor; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for functions or methods with too many return statements. /// @@ -77,21 +79,20 @@ fn num_returns(body: &[Stmt]) -> usize { /// PLR0911 pub(crate) fn too_many_return_statements( + checker: &Checker, stmt: &Stmt, body: &[Stmt], max_returns: usize, -) -> Option { +) { let returns = num_returns(body); if returns > max_returns { - Some(Diagnostic::new( + checker.report_diagnostic( TooManyReturnStatements { returns, max_returns, }, stmt.identifier(), - )) - } else { - None + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs index 2ec7c7b3d3cb3d..c0b033741818f8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs @@ -1,9 +1,11 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for functions or methods with too many statements. /// @@ -137,21 +139,20 @@ fn num_statements(stmts: &[Stmt]) -> usize { /// PLR0915 pub(crate) fn too_many_statements( + checker: &Checker, stmt: &Stmt, body: &[Stmt], max_statements: usize, -) -> Option { +) { let statements = num_statements(body); if statements > max_statements { - Some(Diagnostic::new( + checker.report_diagnostic( TooManyStatements { statements, max_statements, }, stmt.identifier(), - )) - } else { - None + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs b/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs index 5801e866887661..2a92cb98932d63 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{self as ast, Expr}; @@ -124,13 +124,13 @@ pub(crate) fn type_bivariance(checker: &Checker, value: &Expr) { return; }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TypeBivariance { kind, param_name: type_param_name(arguments).map(ToString::to_string), }, func.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs b/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs index 08efd64b7de6b0..96c63658cdda0a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{self as ast, Expr}; @@ -128,7 +128,7 @@ pub(crate) fn type_name_incorrect_variance(checker: &Checker, value: &Expr) { VarVariance::Invariance => name_root.to_string(), }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TypeNameIncorrectVariance { kind, param_name: param_name.to_string(), @@ -136,7 +136,7 @@ pub(crate) fn type_name_incorrect_variance(checker: &Checker, value: &Expr) { replacement_name, }, func.range(), - )); + ); } /// Returns `true` if the parameter name does not match its type variance. diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs b/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs index 722590a517f6f2..5dfe9f22c4b933 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -119,14 +119,14 @@ pub(crate) fn type_param_name_mismatch(checker: &Checker, value: &Expr, targets: return; }; - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( TypeParamNameMismatch { kind, var_name: var_name.to_string(), param_name: param_name.to_string(), }, value.range(), - )); + ); } #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs b/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs index 0dfb437aeec537..ba1db43a1cdb72 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use ruff_python_ast::{Decorator, Parameters, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_staticmethod; @@ -186,13 +186,13 @@ pub(crate) fn unexpected_special_method_signature( }; if !valid_signature { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnexpectedSpecialMethodSignature { method_name: name.to_owned(), expected_params, actual_params, }, stmt.identifier(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs index 46e2485c652647..aa610bb280376c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, StmtFor}; @@ -59,12 +59,11 @@ pub(crate) fn unnecessary_dict_index_lookup(checker: &Checker, stmt_for: &StmtFo }; for range in ranges { - let mut diagnostic = Diagnostic::new(UnnecessaryDictIndexLookup, range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryDictIndexLookup, range); diagnostic.set_fix(Fix::safe_edits( Edit::range_replacement(value_name.id.to_string(), range), [noop(index_name), noop(value_name)], )); - checker.report_diagnostic(diagnostic); } } @@ -104,12 +103,11 @@ pub(crate) fn unnecessary_dict_index_lookup_comprehension(checker: &Checker, exp }; for range in ranges { - let mut diagnostic = Diagnostic::new(UnnecessaryDictIndexLookup, range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryDictIndexLookup, range); diagnostic.set_fix(Fix::safe_edits( Edit::range_replacement(value_name.id.to_string(), range), [noop(index_name), noop(value_name)], )); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs index bf4cb4afc141e8..b932c5634a70bf 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -38,6 +38,6 @@ impl Violation for UnnecessaryDirectLambdaCall { /// PLC3002 pub(crate) fn unnecessary_direct_lambda_call(checker: &Checker, expr: &Expr, func: &Expr) { if let Expr::Lambda(_) = func { - checker.report_diagnostic(Diagnostic::new(UnnecessaryDirectLambdaCall, expr.range())); + checker.report_diagnostic(UnnecessaryDirectLambdaCall, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index 8295e7d6af39d8..6d4440a4ace799 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, OperatorPrecedence, Stmt}; use ruff_python_semantic::SemanticModel; @@ -202,7 +202,7 @@ pub(crate) fn unnecessary_dunder_call(checker: &Checker, call: &ast::ExprCall) { } } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryDunderCall { method: attr.to_string(), replacement: title, @@ -237,8 +237,6 @@ pub(crate) fn unnecessary_dunder_call(checker: &Checker, call: &ast::ExprCall) { call.range(), ))); } - - checker.report_diagnostic(diagnostic); } /// Return `true` if this is a dunder method that is allowed to be called explicitly. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs index f677f7a80f2533..9ecfc0801ee685 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, ExprLambda, Parameter, ParameterWithDefault, visitor}; @@ -207,7 +207,7 @@ pub(crate) fn unnecessary_lambda(checker: &Checker, lambda: &ExprLambda) { } } - let mut diagnostic = Diagnostic::new(UnnecessaryLambda, lambda.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryLambda, lambda.range()); diagnostic.set_fix(Fix::applicable_edit( Edit::range_replacement( checker.locator().slice(func.as_ref()).to_string(), @@ -215,7 +215,6 @@ pub(crate) fn unnecessary_lambda(checker: &Checker, lambda: &ExprLambda) { ), Applicability::Unsafe, )); - checker.report_diagnostic(diagnostic); } /// Identify all `Expr::Name` nodes in an AST. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs index 493a648b2eaf85..414a30e820ea14 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, Int, Number, StmtFor}; @@ -61,12 +61,11 @@ pub(crate) fn unnecessary_list_index_lookup(checker: &Checker, stmt_for: &StmtFo }; for range in ranges { - let mut diagnostic = Diagnostic::new(UnnecessaryListIndexLookup, range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListIndexLookup, range); diagnostic.set_fix(Fix::safe_edits( Edit::range_replacement(value_name.id.to_string(), range), [noop(index_name), noop(value_name)], )); - checker.report_diagnostic(diagnostic); } } @@ -105,12 +104,11 @@ pub(crate) fn unnecessary_list_index_lookup_comprehension(checker: &Checker, exp }; for range in ranges { - let mut diagnostic = Diagnostic::new(UnnecessaryListIndexLookup, range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryListIndexLookup, range); diagnostic.set_fix(Fix::safe_edits( Edit::range_replacement(value_name.id.to_string(), range), [noop(index_name), noop(value_name)], )); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs index a93fec919de7bf..b81c43d8c035d0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{Identifier, Stmt}; use ruff_python_semantic::cfg::graph::{BlockId, Condition, ControlFlowGraph, build_cfg}; use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -64,12 +64,12 @@ pub(crate) fn in_function(checker: &Checker, name: &Identifier, body: &[Stmt]) { let start = cfg.range(start_block).start(); let end = cfg.range(end_block).end(); - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnreachableCode { name: name.to_string(), }, TextRange::new(start, end), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 5a114bed48ba22..fea61bc453f8ab 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr}; @@ -91,7 +91,7 @@ pub(crate) fn unspecified_encoding(checker: &Checker, call: &ast::ExprCall) { return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnspecifiedEncoding { function_name, mode, @@ -99,7 +99,6 @@ pub(crate) fn unspecified_encoding(checker: &Checker, call: &ast::ExprCall) { call.func.range(), ); diagnostic.set_fix(generate_keyword_fix(checker, call)); - checker.report_diagnostic(diagnostic); } /// Represents the path of the function or method being called. diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs index c2df2dfc6179e7..573b3688e6d3ec 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs @@ -1,7 +1,7 @@ use anyhow::Result; use ast::whitespace::indentation; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier; use ruff_python_ast::{self as ast, ExceptHandler, MatchCase, Stmt}; @@ -70,7 +70,7 @@ pub(crate) fn useless_else_on_loop(checker: &Checker, stmt: &Stmt, body: &[Stmt] let else_range = identifier::else_(stmt, checker.locator().contents()).expect("else clause"); - let mut diagnostic = Diagnostic::new(UselessElseOnLoop, else_range); + let mut diagnostic = checker.report_diagnostic(UselessElseOnLoop, else_range); diagnostic.try_set_fix(|| { remove_else( stmt, @@ -81,7 +81,6 @@ pub(crate) fn useless_else_on_loop(checker: &Checker, stmt: &Stmt, body: &[Stmt] checker.stylist(), ) }); - checker.report_diagnostic(diagnostic); } /// Returns `true` if the given body contains a `break` statement. diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs index 389628325ffca8..e1b27dd4da8128 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -56,12 +56,11 @@ pub(crate) fn useless_exception_statement(checker: &Checker, expr: &ast::StmtExp }; if is_builtin_exception(func, checker.semantic(), checker.target_version()) { - let mut diagnostic = Diagnostic::new(UselessExceptionStatement, expr.range()); + let mut diagnostic = checker.report_diagnostic(UselessExceptionStatement, expr.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( "raise ".to_string(), expr.start(), ))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs index 683682ba4b5c6f..b2ea23054c33c5 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -73,7 +73,7 @@ pub(crate) fn useless_import_alias(checker: &Checker, alias: &Alias) { .settings .isort .requires_module_import(alias.name.to_string(), Some(asname.to_string())); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UselessImportAlias { required_import_conflict, }, @@ -85,8 +85,6 @@ pub(crate) fn useless_import_alias(checker: &Checker, alias: &Alias) { alias.range(), ))); } - - checker.report_diagnostic(diagnostic); } /// PLC0414 @@ -110,7 +108,7 @@ pub(crate) fn useless_import_from_alias( Some(asname.to_string()), level, ); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UselessImportAlias { required_import_conflict, }, @@ -123,6 +121,4 @@ pub(crate) fn useless_import_from_alias( alias.range(), ))); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs index da0479e7deb3b7..5bec98ebf4d85c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::visitor::Visitor; @@ -94,10 +94,9 @@ pub(crate) fn useless_return( return; } - let mut diagnostic = Diagnostic::new(UselessReturn, last_stmt.range()); + let mut diagnostic = checker.report_diagnostic(UselessReturn, last_stmt.range()); let edit = fix::edits::delete_stmt(last_stmt, Some(stmt), checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_id(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs index 98db5eb04a9f75..fcd4d8126e144b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -80,6 +80,6 @@ pub(crate) fn useless_with_lock(checker: &Checker, with: &ast::StmtWith) { return; } - checker.report_diagnostic(Diagnostic::new(UselessWithLock, call.range())); + checker.report_diagnostic(UselessWithLock, call.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs b/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs index ab76bb65ec53eb..fa71e3796d679e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::ScopeKind; @@ -43,6 +43,6 @@ pub(crate) fn yield_from_in_async_function(checker: &Checker, expr: &ast::ExprYi checker.semantic().current_scope().kind, ScopeKind::Function(ast::StmtFunctionDef { is_async: true, .. }) ) { - checker.report_diagnostic(Diagnostic::new(YieldFromInAsyncFunction, expr.range())); + checker.report_diagnostic(YieldFromInAsyncFunction, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs b/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs index a6979c769cc11e..f303324704b959 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -41,6 +41,6 @@ impl Violation for YieldInInit { /// PLE0100 pub(crate) fn yield_in_init(checker: &Checker, expr: &Expr) { if in_dunder_method("__init__", checker.semantic(), checker.settings) { - checker.report_diagnostic(Diagnostic::new(YieldInInit, expr.range())); + checker.report_diagnostic(YieldInInit, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index 806caa561f57ec..90e526a3b71fb0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -1,6 +1,6 @@ use log::debug; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::name::Name; @@ -113,7 +113,7 @@ pub(crate) fn convert_named_tuple_functional_to_class( } }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ConvertNamedTupleFunctionalToClass { name: typename.to_string(), }, @@ -130,7 +130,6 @@ pub(crate) fn convert_named_tuple_functional_to_class( checker.comment_ranges(), )); } - checker.report_diagnostic(diagnostic); } /// Return the typename, args, keywords, and base class. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index 5341d4d67ccd3b..73ca1f3a4d4f45 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Keyword, Stmt}; use ruff_python_codegen::Generator; @@ -97,7 +97,7 @@ pub(crate) fn convert_typed_dict_functional_to_class( return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ConvertTypedDictFunctionalToClass { name: class_name.to_string(), }, @@ -115,7 +115,6 @@ pub(crate) fn convert_typed_dict_functional_to_class( checker.comment_ranges(), )); } - checker.report_diagnostic(diagnostic); } /// Return the class name, arguments, keywords and base class for a `TypedDict` diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs index 18706eb0fe5e08..daf1129dc822d2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -58,7 +58,7 @@ pub(crate) fn datetime_utc_alias(checker: &Checker, expr: &Expr) { matches!(qualified_name.segments(), ["datetime", "timezone", "utc"]) }) { - let mut diagnostic = Diagnostic::new(DatetimeTimezoneUTC, expr.range()); + let mut diagnostic = checker.report_diagnostic(DatetimeTimezoneUTC, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import_from("datetime", "UTC"), @@ -68,6 +68,5 @@ pub(crate) fn datetime_utc_alias(checker: &Checker, expr: &Expr) { let reference_edit = Edit::range_replacement(binding, expr.range()); Ok(Fix::safe_edits(import_edit, [reference_edit])) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs index 8d20eb816b14d5..4dcba36f8272de 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; @@ -42,13 +42,12 @@ fn add_check_for_node(checker: &Checker, node: &T) where T: Ranged, { - let mut diagnostic = Diagnostic::new(DeprecatedCElementTree, node.range()); + let mut diagnostic = checker.report_diagnostic(DeprecatedCElementTree, node.range()); let contents = checker.locator().slice(node); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( contents.replacen("cElementTree", "ElementTree", 1), node.range(), ))); - checker.report_diagnostic(diagnostic); } /// UP023 diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 4f3f0075299f13..2c49b252cfe33f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{Alias, StmtImportFrom}; @@ -726,7 +726,7 @@ pub(crate) fn deprecated_import(checker: &Checker, import_from_stmt: &StmtImport ); for (operation, fix) in fixer.without_renames() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DeprecatedImport { deprecation: Deprecation::WithoutRename(operation), }, @@ -738,16 +738,14 @@ pub(crate) fn deprecated_import(checker: &Checker, import_from_stmt: &StmtImport import_from_stmt.range(), ))); } - checker.report_diagnostic(diagnostic); } for operation in fixer.with_renames() { - let diagnostic = Diagnostic::new( + checker.report_diagnostic( DeprecatedImport { deprecation: Deprecation::WithRename(operation), }, import_from_stmt.range(), ); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index 0a2a81b80acedc..612b10da544e44 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -5,7 +5,7 @@ use libcst_native::{ }; use log::debug; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::whitespace::indentation; @@ -258,7 +258,7 @@ pub(crate) fn deprecated_mock_attribute(checker: &Checker, attribute: &ast::Expr if UnqualifiedName::from_expr(&attribute.value) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["mock", "mock"])) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DeprecatedMockImport { reference_type: MockReference::Attribute, }, @@ -268,7 +268,6 @@ pub(crate) fn deprecated_mock_attribute(checker: &Checker, attribute: &ast::Expr "mock".to_string(), attribute.value.range(), ))); - checker.report_diagnostic(diagnostic); } } @@ -297,7 +296,7 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) { // Add a `Diagnostic` for each `mock` import. for name in names { if &name.name == "mock" || &name.name == "mock.mock" { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DeprecatedMockImport { reference_type: MockReference::Import, }, @@ -309,7 +308,6 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) { stmt.range(), ))); } - checker.report_diagnostic(diagnostic); } } } @@ -324,7 +322,7 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) { } if module == "mock" { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DeprecatedMockImport { reference_type: MockReference::Import, }, @@ -337,7 +335,6 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) { .map(Fix::safe_edit) }); } - checker.report_diagnostic(diagnostic); } } _ => (), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs index 294f155c19a97d..00871001343760 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashMap; use std::sync::LazyLock; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -91,7 +91,7 @@ pub(crate) fn deprecated_unittest_alias(checker: &Checker, expr: &Expr) { if id != "self" { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DeprecatedUnittestAlias { alias: attr.to_string(), target: (*target).to_string(), @@ -102,5 +102,4 @@ pub(crate) fn deprecated_unittest_alias(checker: &Checker, expr: &Expr) { format!("self.{target}"), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index 413672ce576948..e5025566c997e7 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use anyhow::{Context, Result}; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::str::{leading_quote, trailing_quote}; @@ -504,7 +504,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma return; } - let mut diagnostic = Diagnostic::new(FString, call.range()); + let mut diagnostic = checker.report_diagnostic(FString, call.range()); // Avoid fix if there are comments within the call: // ``` @@ -528,5 +528,4 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma ))); } } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs index ed9536786eefec..6a3f66ab0dedf2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow}; use libcst_native::{Arg, Expression}; use regex::Regex; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Stylist; @@ -112,12 +112,11 @@ pub(crate) fn format_literals(checker: &Checker, call: &ast::ExprCall, summary: Arguments::Reorder(&summary.indices) }; - let mut diagnostic = Diagnostic::new(FormatLiterals, call.range()); + let mut diagnostic = checker.report_diagnostic(FormatLiterals, call.range()); diagnostic.try_set_fix(|| { generate_call(call, arguments, checker.locator(), checker.stylist()) .map(|suggestion| Fix::unsafe_edit(Edit::range_replacement(suggestion, call.range()))) }); - checker.report_diagnostic(diagnostic); } /// Returns true if the indices are sequential. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs index 42531ff4c5f31c..b3f176e8eb815c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Keyword}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -87,7 +87,7 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &Checker, decorator_list: &[D range: _, } = &keywords[0]; if arg.as_ref().is_some_and(|arg| arg == "maxsize") && value.is_none_literal_expr() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( LRUCacheWithMaxsizeNone, TextRange::new(func.end(), decorator.end()), ); @@ -101,7 +101,6 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &Checker, decorator_list: &[D Edit::range_replacement(binding, decorator.expression.range()); Ok(Fix::safe_edits(import_edit, [reference_edit])) }); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index 6fe2319a3c36df..70c5da0e94234c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_text_size::{Ranged, TextRange}; @@ -74,12 +74,11 @@ pub(crate) fn lru_cache_without_parameters(checker: &Checker, decorator_list: &[ matches!(qualified_name.segments(), ["functools", "lru_cache"]) }) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( LRUCacheWithoutParameters, TextRange::new(func.end(), decorator.end()), ); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(arguments.range()))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs index fbca70791fc197..00a69daf46a660 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs @@ -1,7 +1,7 @@ use std::fmt; use std::str::FromStr; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, OperatorPrecedence, UnaryOp}; use ruff_source_file::find_newline; @@ -193,21 +193,21 @@ pub(crate) fn native_literals( match args.first() { None => { - let mut diagnostic = Diagnostic::new(NativeLiterals { literal_type }, call.range()); - // Do not suggest fix for attribute access on an int like `int().attribute` // Ex) `int().denominator` is valid but `0.denominator` is not if literal_type == LiteralType::Int && matches!(parent_expr, Some(Expr::Attribute(_))) { return; } + let mut diagnostic = + checker.report_diagnostic(NativeLiterals { literal_type }, call.range()); + let expr = literal_type.as_zero_value_expr(checker); let content = checker.generator().expr(&expr); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( content, call.range(), ))); - checker.report_diagnostic(diagnostic); } Some(arg) => { let (has_unary_op, literal_expr) = if let Some(literal_expr) = arg.as_literal_expr() { @@ -291,8 +291,9 @@ pub(crate) fn native_literals( let edit = Edit::range_replacement(content, call.range()); let fix = Fix::applicable_edit(edit, applicability); - let diagnostic = Diagnostic::new(NativeLiterals { literal_type }, call.range()); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(NativeLiterals { literal_type }, call.range()) + .set_fix(fix); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs index de022c51ccd441..f913170a1bdd9e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprSubscript, PythonVersion}; use ruff_python_semantic::SemanticModel; @@ -88,12 +88,11 @@ pub(crate) fn use_pep646_unpack(checker: &Checker, expr: &ExprSubscript) { return; } - let mut diagnostic = Diagnostic::new(NonPEP646Unpack, *range); + let mut diagnostic = checker.report_diagnostic(NonPEP646Unpack, *range); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( format!("*{}", checker.locator().slice(slice.as_ref())), *range, ))); - checker.report_diagnostic(diagnostic); } /// Determine whether the [`ExprSubscript`] is in a subscript index (e.g., `Generic[Unpack[int]]`). diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs index f67a777d247cd8..45f997c04e9045 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -52,7 +52,7 @@ pub(crate) fn open_alias(checker: &Checker, expr: &Expr, func: &Expr) { .resolve_qualified_name(func) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["io", "open"])) { - let mut diagnostic = Diagnostic::new(OpenAlias, expr.range()); + let mut diagnostic = checker.report_diagnostic(OpenAlias, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol( "open", @@ -64,6 +64,5 @@ pub(crate) fn open_alias(checker: &Checker, expr: &Expr, func: &Expr) { import_edit, )) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs index bd862507584394..070a13f470fc68 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{Name, UnqualifiedName}; use ruff_python_semantic::SemanticModel; @@ -71,7 +71,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool { /// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`]. fn atom_diagnostic(checker: &Checker, target: &Expr) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( OSErrorAlias { name: UnqualifiedName::from_expr(target).map(|name| name.to_string()), }, @@ -88,12 +88,11 @@ fn atom_diagnostic(checker: &Checker, target: &Expr) { import_edit, )) }); - checker.report_diagnostic(diagnostic); } /// Create a [`Diagnostic`] for a tuple of expressions. fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) { - let mut diagnostic = Diagnostic::new(OSErrorAlias { name: None }, tuple.range()); + let mut diagnostic = checker.report_diagnostic(OSErrorAlias { name: None }, tuple.range()); let semantic = checker.semantic(); if semantic.has_builtin_binding("OSError") { // Filter out any `OSErrors` aliases. @@ -138,7 +137,6 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] tuple.range(), ))); } - checker.report_diagnostic(diagnostic); } /// UP024 diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index 51af61887f917f..f89bc74bf05bb4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::stmt_if::{BranchKind, IfElifBranch, if_elif_branches}; @@ -129,7 +129,7 @@ pub(crate) fn outdated_version_block(checker: &Checker, stmt_if: &StmtIf) { ) { Ok(false) => {} Ok(true) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( OutdatedVersionBlock { reason: if op.is_lt() || op.is_lt_e() { Reason::AlwaysFalse @@ -146,15 +146,14 @@ pub(crate) fn outdated_version_block(checker: &Checker, stmt_if: &StmtIf) { } { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } Err(_) => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( OutdatedVersionBlock { reason: Reason::Invalid, }, comparison.range(), - )); + ); } } } @@ -182,28 +181,30 @@ pub(crate) fn outdated_version_block(checker: &Checker, stmt_if: &StmtIf) { }; match reason { Reason::AlwaysTrue => { - let mut diagnostic = - Diagnostic::new(OutdatedVersionBlock { reason }, branch.test.range()); + let mut diagnostic = checker.report_diagnostic( + OutdatedVersionBlock { reason }, + branch.test.range(), + ); if let Some(fix) = fix_always_true_branch(checker, stmt_if, &branch) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } Reason::AlwaysFalse => { - let mut diagnostic = - Diagnostic::new(OutdatedVersionBlock { reason }, branch.test.range()); + let mut diagnostic = checker.report_diagnostic( + OutdatedVersionBlock { reason }, + branch.test.range(), + ); if let Some(fix) = fix_always_false_branch(checker, stmt_if, &branch) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } Reason::Invalid => { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( OutdatedVersionBlock { reason: Reason::Invalid, }, comparison.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs index ae8b83e6981ce6..81b7b4971922b4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{ExprSubscript, StmtClassDef}; @@ -138,7 +138,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NonPEP695GenericClass { name: name.to_string(), }, @@ -154,7 +154,6 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD // because `find_generic` also finds the *first* Generic argument, this has the additional // benefit of bailing out with a diagnostic if multiple Generic arguments are present if generic_idx != arguments.len() - 1 { - checker.report_diagnostic(diagnostic); return; } @@ -187,6 +186,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD // just because we can't confirm that `SomethingElse` is a `TypeVar` if !visitor.any_skipped { let Some(type_vars) = check_type_vars(visitor.vars) else { + diagnostic.defuse(); return; }; @@ -209,6 +209,4 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD )) }); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs index e75cea226d39f5..39fa3eafff77ce 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtFunctionDef; use ruff_python_ast::visitor::Visitor; @@ -163,16 +163,15 @@ pub(crate) fn non_pep695_generic_function(checker: &Checker, function_def: &Stmt source: checker.source(), }; - checker.report_diagnostic( - Diagnostic::new( + checker + .report_diagnostic( NonPEP695GenericFunction { name: name.to_string(), }, TextRange::new(name.start(), parameters.end()), ) - .with_fix(Fix::unsafe_edit(Edit::insertion( + .set_fix(Fix::unsafe_edit(Edit::insertion( type_params.to_string(), name.end(), - ))), - ); + ))); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs index 9ebe7c326c1180..ab3dce82ac48f9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::parenthesize::parenthesized_range; @@ -172,14 +172,14 @@ pub(crate) fn non_pep695_type_alias_type(checker: &Checker, stmt: &StmtAssign) { return; }; - checker.report_diagnostic(create_diagnostic( + create_diagnostic( checker, stmt.into(), &target_name.id, value, &vars, TypeAliasKind::TypeAliasType, - )); + ); } /// UP040 @@ -231,14 +231,14 @@ pub(crate) fn non_pep695_type_alias(checker: &Checker, stmt: &StmtAnnAssign) { return; } - checker.report_diagnostic(create_diagnostic( + create_diagnostic( checker, stmt.into(), name, value, &vars, TypeAliasKind::TypeAlias, - )); + ); } /// Generate a [`Diagnostic`] for a non-PEP 695 type alias or type alias type. @@ -249,7 +249,7 @@ fn create_diagnostic( value: &Expr, type_vars: &[TypeVar], type_alias_kind: TypeAliasKind, -) -> Diagnostic { +) { let source = checker.source(); let comment_ranges = checker.comment_ranges(); @@ -287,12 +287,13 @@ fn create_diagnostic( } }; - Diagnostic::new( - NonPEP695TypeAlias { - name: name.to_string(), - type_alias_kind, - }, - stmt.range(), - ) - .with_fix(Fix::applicable_edit(edit, applicability)) + checker + .report_diagnostic( + NonPEP695TypeAlias { + name: name.to_string(), + type_alias_kind, + }, + stmt.range(), + ) + .set_fix(Fix::applicable_edit(edit, applicability)); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs index 0a5635c68d2ac3..cc1ed4460b6551 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_python_semantic::Binding; @@ -101,43 +101,45 @@ impl Violation for PrivateTypeParameter { } /// UP049 -pub(crate) fn private_type_parameter(checker: &Checker, binding: &Binding) -> Option { +pub(crate) fn private_type_parameter(checker: &Checker, binding: &Binding) { let semantic = checker.semantic(); - let stmt = binding.statement(semantic)?; + let Some(stmt) = binding.statement(semantic) else { + return; + }; if !binding.kind.is_type_param() { - return None; + return; } let kind = match stmt { Stmt::FunctionDef(_) => ParamKind::Function, Stmt::ClassDef(_) => ParamKind::Class, - _ => return None, + _ => return, }; let old_name = binding.name(checker.source()); if !old_name.starts_with('_') { - return None; + return; } // Sunder `_T_`, dunder `__T__`, and all all-under `_` or `__` cases should all be skipped, as // these are not "private" names if old_name.ends_with('_') { - return None; + return; } - let mut diagnostic = Diagnostic::new(PrivateTypeParameter { kind }, binding.range); + let mut diagnostic = checker.report_diagnostic(PrivateTypeParameter { kind }, binding.range); let new_name = old_name.trim_start_matches('_'); // if the new name would shadow another variable, keyword, or builtin, emit a diagnostic without // a suggested fix if ShadowedKind::new(binding, new_name, checker).shadows_any() { - return Some(diagnostic); + return; } if !is_identifier(new_name) { - return Some(diagnostic); + return; } let source = checker.source(); @@ -163,6 +165,4 @@ pub(crate) fn private_type_parameter(checker: &Checker, binding: &Binding) -> Op let fix_isolation = Checker::isolation(binding.source); Ok(Fix::applicable_edits(first, rest, applicability).isolate(fix_isolation)) }); - - Some(diagnostic) } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index 6f4d3a91b6270f..60478d7c077106 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::fmt::Write; use std::str::FromStr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyStringFlags, Expr, StringFlags, whitespace::indentation}; use ruff_python_codegen::Stylist; @@ -385,7 +385,7 @@ pub(crate) fn printf_string_formatting( return; }; if !convertible(&format_string, right) { - checker.report_diagnostic(Diagnostic::new(PrintfStringFormatting, string_expr.range())); + checker.report_diagnostic(PrintfStringFormatting, string_expr.range()); return; } @@ -446,10 +446,7 @@ pub(crate) fn printf_string_formatting( let Some(params_string) = clean_params_dictionary(right, checker.locator(), checker.stylist()) else { - checker.report_diagnostic(Diagnostic::new( - PrintfStringFormatting, - string_expr.range(), - )); + checker.report_diagnostic(PrintfStringFormatting, string_expr.range()); return; }; Cow::Owned(params_string) @@ -504,12 +501,11 @@ pub(crate) fn printf_string_formatting( // Add the `.format` call. let _ = write!(&mut contents, ".format{params_string}"); - let mut diagnostic = Diagnostic::new(PrintfStringFormatting, bin_op.range()); + let mut diagnostic = checker.report_diagnostic(PrintfStringFormatting, bin_op.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents, bin_op.range(), ))); - checker.report_diagnostic(diagnostic); } #[cfg(test)] diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs index c5dcd45264cc1b..73f4976aaf240b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs @@ -2,7 +2,7 @@ use ruff_python_parser::TokenKind; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_python_semantic::SemanticModel; @@ -85,8 +85,6 @@ impl AlwaysFixableViolation for QuotedAnnotation { /// UP037 pub(crate) fn quoted_annotation(checker: &Checker, annotation: &str, range: TextRange) { - let diagnostic = Diagnostic::new(QuotedAnnotation, range); - let placeholder_range = TextRange::up_to(annotation.text_len()); let spans_multiple_lines = annotation.contains_line_break(placeholder_range); @@ -108,7 +106,9 @@ pub(crate) fn quoted_annotation(checker: &Checker, annotation: &str, range: Text let edit = Edit::range_replacement(new_content, range); let fix = Fix::safe_edit(edit); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(QuotedAnnotation, range) + .set_fix(fix); } fn in_parameter_annotation(offset: TextSize, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs index cdefd53e24bf39..d7a6bd4126a3e4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_parser::{TokenKind, Tokens}; @@ -81,17 +81,12 @@ pub(crate) fn redundant_open_modes(checker: &Checker, call: &ast::ExprCall) { }; let reduced = mode.reduce(); if reduced != mode { - checker.report_diagnostic(create_diagnostic(call, mode_arg, reduced, checker)); + create_diagnostic(call, mode_arg, reduced, checker); } } -fn create_diagnostic( - call: &ast::ExprCall, - mode_arg: &Expr, - mode: OpenMode, - checker: &Checker, -) -> Diagnostic { - let mut diagnostic = Diagnostic::new( +fn create_diagnostic(call: &ast::ExprCall, mode_arg: &Expr, mode: OpenMode, checker: &Checker) { + let mut diagnostic = checker.report_diagnostic( RedundantOpenModes { replacement: mode.to_string(), }, @@ -109,8 +104,6 @@ fn create_diagnostic( mode_arg.range(), ))); } - - diagnostic } fn create_remove_argument_fix( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index fea862a3b98f55..45b05fc87271cc 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Keyword}; use ruff_python_semantic::Modules; @@ -94,12 +94,11 @@ pub(crate) fn replace_stdout_stderr(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, call.range()); + let mut diagnostic = checker.report_diagnostic(ReplaceStdoutStderr, call.range()); if call.arguments.find_keyword("capture_output").is_none() { diagnostic .try_set_fix(|| generate_fix(stdout, stderr, call, checker.locator().contents())); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs index bb48ba16c763e0..ced03bbce003aa 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -126,7 +126,7 @@ pub(crate) fn replace_str_enum(checker: &Checker, class_def: &ast::StmtClassDef) return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ReplaceStrEnum { name: class_def.name.to_string(), }, @@ -153,6 +153,4 @@ pub(crate) fn replace_str_enum(checker: &Checker, class_def: &ast::StmtClassDef) )) }); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs index ab1a8b82813153..132a562d079494 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -67,7 +67,7 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall return; }; - let mut diagnostic = Diagnostic::new(ReplaceUniversalNewlines, arg.range()); + let mut diagnostic = checker.report_diagnostic(ReplaceUniversalNewlines, arg.range()); if call.arguments.find_keyword("text").is_some() { diagnostic.try_set_fix(|| { @@ -85,6 +85,5 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall arg.range(), ))); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index a1794219edff7d..f87b7f52951025 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextSize}; @@ -157,12 +157,11 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall return; } - let mut diagnostic = Diagnostic::new(SuperCallWithParameters, call.arguments.range()); + let mut diagnostic = checker.report_diagnostic(SuperCallWithParameters, call.arguments.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion( call.arguments.start() + TextSize::new(1), call.arguments.end() - TextSize::new(1), ))); - checker.report_diagnostic(diagnostic); } /// Returns `true` if a call is an argumented `super` invocation. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index e00eddf688f0aa..1b40bdd91f68e1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{Name, UnqualifiedName}; use ruff_python_semantic::SemanticModel; @@ -83,7 +83,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion /// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`]. fn atom_diagnostic(checker: &Checker, target: &Expr) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( TimeoutErrorAlias { name: UnqualifiedName::from_expr(target).map(|name| name.to_string()), }, @@ -100,12 +100,11 @@ fn atom_diagnostic(checker: &Checker, target: &Expr) { import_edit, )) }); - checker.report_diagnostic(diagnostic); } /// Create a [`Diagnostic`] for a tuple of expressions. fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) { - let mut diagnostic = Diagnostic::new(TimeoutErrorAlias { name: None }, tuple.range()); + let mut diagnostic = checker.report_diagnostic(TimeoutErrorAlias { name: None }, tuple.range()); let semantic = checker.semantic(); if semantic.has_builtin_binding("TimeoutError") { // Filter out any `TimeoutErrors` aliases. @@ -150,7 +149,6 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] tuple.range(), ))); } - checker.report_diagnostic(diagnostic); } /// UP041 diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs index 210afe39674364..5792100271056f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use crate::fix::edits::pad; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -65,7 +65,7 @@ pub(crate) fn type_of_primitive(checker: &Checker, expr: &Expr, func: &Expr, arg if !semantic.match_builtin_expr(func, "type") { return; } - let mut diagnostic = Diagnostic::new(TypeOfPrimitive { primitive }, expr.range()); + let mut diagnostic = checker.report_diagnostic(TypeOfPrimitive { primitive }, expr.range()); let builtin = primitive.builtin(); if semantic.has_builtin_binding(&builtin) { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( @@ -73,5 +73,4 @@ pub(crate) fn type_of_primitive(checker: &Checker, expr: &Expr, func: &Expr, arg expr.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs index 37f8258bec9d66..e932325884ffb9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; @@ -56,7 +56,7 @@ pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) { .resolve_qualified_name(expr) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["typing", "Text"])) { - let mut diagnostic = Diagnostic::new(TypingTextStrAlias, expr.range()); + let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias, expr.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol( "str", @@ -68,6 +68,5 @@ pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) { import_edit, )) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs index e3af44207b1b01..4ed3768bac0fc6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StringLiteral; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -41,11 +41,10 @@ impl AlwaysFixableViolation for UnicodeKindPrefix { /// UP025 pub(crate) fn unicode_kind_prefix(checker: &Checker, string: &StringLiteral) { if string.flags.prefix().is_unicode() { - let mut diagnostic = Diagnostic::new(UnicodeKindPrefix, string.range); + let mut diagnostic = checker.report_diagnostic(UnicodeKindPrefix, string.range); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(TextRange::at( string.start(), TextSize::from(1), )))); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs index 7496586c70d4e5..c7bd001ac080b6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -110,7 +110,7 @@ pub(crate) fn unnecessary_builtin_import( return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryBuiltinImport { names: unused_imports .iter() @@ -138,5 +138,4 @@ pub(crate) fn unnecessary_builtin_import( checker.semantic().current_statement_parent_id(), ))) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs index 48962057624727..e92bb79150ded1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; @@ -48,10 +48,9 @@ pub(crate) fn unnecessary_class_parentheses(checker: &Checker, class_def: &ast:: return; } - let mut diagnostic = Diagnostic::new(UnnecessaryClassParentheses, arguments.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryClassParentheses, arguments.range()); diagnostic.set_fix(Fix::safe_edit(Edit::deletion( arguments.start(), arguments.end(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs index 9e0eafe8015fa6..a5beeae74bb229 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextRange}; @@ -102,7 +102,7 @@ pub(crate) fn unnecessary_default_type_args(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryDefaultTypeArgs, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryDefaultTypeArgs, expr.range()); let applicability = if checker .comment_ranges() @@ -136,7 +136,6 @@ pub(crate) fn unnecessary_default_type_args(checker: &Checker, expr: &Expr) { ), applicability, )); - checker.report_diagnostic(diagnostic); } /// Trim trailing `None` literals from the given elements. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs index 038ec258719d90..cd9f8953816bc0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs @@ -1,6 +1,6 @@ use std::fmt::Write as _; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_python_parser::{TokenKind, Tokens}; @@ -161,7 +161,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { if let Some(encoding_arg) = match_encoding_arg(&call.arguments) { if literal.to_str().is_ascii() { // Ex) Convert `"foo".encode()` to `b"foo"`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEncodeUTF8 { reason: Reason::BytesLiteral, }, @@ -172,11 +172,10 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { call, checker.tokens(), )); - checker.report_diagnostic(diagnostic); } else if let EncodingArg::Keyword(kwarg) = encoding_arg { // Ex) Convert `"unicode text©".encode(encoding="utf-8")` to // `"unicode text©".encode()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEncodeUTF8 { reason: Reason::DefaultArgument, }, @@ -191,10 +190,9 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } else if let EncodingArg::Positional(arg) = encoding_arg { // Ex) Convert `"unicode text©".encode("utf-8")` to `"unicode text©".encode()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEncodeUTF8 { reason: Reason::DefaultArgument, }, @@ -209,7 +207,6 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } } } @@ -219,7 +216,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { if let EncodingArg::Keyword(kwarg) = encoding_arg { // Ex) Convert `f"unicode text©".encode(encoding="utf-8")` to // `f"unicode text©".encode()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEncodeUTF8 { reason: Reason::DefaultArgument, }, @@ -234,10 +231,9 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } else if let EncodingArg::Positional(arg) = encoding_arg { // Ex) Convert `f"unicode text©".encode("utf-8")` to `f"unicode text©".encode()`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEncodeUTF8 { reason: Reason::DefaultArgument, }, @@ -252,7 +248,6 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) { ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs index 8a220f7b8cd69b..205dea25934abb 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -98,7 +98,7 @@ pub(crate) fn unnecessary_future_import(checker: &Checker, stmt: &Stmt, names: & if unused_imports.is_empty() { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryFutureImport { names: unused_imports .iter() @@ -137,5 +137,4 @@ pub(crate) fn unnecessary_future_import(checker: &Checker, stmt: &Stmt, names: & )), ) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs index e2a0cea9f7e01c..bdf78ab5882a10 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs @@ -1,6 +1,6 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_semantic::analyze::typing::ModuleMember; @@ -80,7 +80,7 @@ pub(crate) fn use_pep585_annotation(checker: &Checker, expr: &Expr, replacement: let Some(from) = UnqualifiedName::from_expr(expr) else { return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NonPEP585Annotation { from: from.to_string(), to: replacement.to_string(), @@ -132,5 +132,4 @@ pub(crate) fn use_pep585_annotation(checker: &Checker, expr: &Expr, replacement: } } } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 7f5071badbb334..c017a568dff701 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PythonVersion; use ruff_python_ast::helpers::{pep_604_optional, pep_604_union}; @@ -148,21 +148,15 @@ pub(crate) fn non_pep604_annotation( match operator { Pep604Operator::Optional => { - let (rule, mut diagnostic) = if is_defer_optional_to_up045_enabled(checker.settings) { - ( - Rule::NonPEP604AnnotationOptional, - Diagnostic::new(NonPEP604AnnotationOptional, expr.range()), - ) + let guard = if is_defer_optional_to_up045_enabled(checker.settings) { + checker.report_diagnostic_if_enabled(NonPEP604AnnotationOptional, expr.range()) } else { - ( - Rule::NonPEP604AnnotationUnion, - Diagnostic::new(NonPEP604AnnotationUnion, expr.range()), - ) + checker.report_diagnostic_if_enabled(NonPEP604AnnotationUnion, expr.range()) }; - if !checker.enabled(rule) { + let Some(mut diagnostic) = guard else { return; - } + }; if fixable { match slice { @@ -184,14 +178,13 @@ pub(crate) fn non_pep604_annotation( } } } - checker.report_diagnostic(diagnostic); } Pep604Operator::Union => { if !checker.enabled(Rule::NonPEP604AnnotationUnion) { return; } - let mut diagnostic = Diagnostic::new(NonPEP604AnnotationUnion, expr.range()); + let mut diagnostic = checker.report_diagnostic(NonPEP604AnnotationUnion, expr.range()); if fixable { match slice { Expr::Slice(_) => { @@ -226,7 +219,6 @@ pub(crate) fn non_pep604_annotation( } } } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index 4060deccda9bed..8e40da041ba97b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::helpers::pep_604_union; @@ -109,12 +109,10 @@ pub(crate) fn use_pep604_isinstance(checker: &Checker, expr: &Expr, func: &Expr, let Some(kind) = CallKind::from_name(builtin_function_name) else { return; }; - checker.report_diagnostic( - Diagnostic::new(NonPEP604Isinstance { kind }, expr.range()).with_fix(Fix::unsafe_edit( - Edit::range_replacement( - checker.generator().expr(&pep_604_union(&tuple.elts)), - types.range(), - ), - )), - ); + checker + .report_diagnostic(NonPEP604Isinstance { kind }, expr.range()) + .set_fix(Fix::unsafe_edit(Edit::range_replacement( + checker.generator().expr(&pep_604_union(&tuple.elts)), + types.range(), + ))); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs index c4b26a2a84ea57..0fe0fafd3f4572 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::Ranged; @@ -55,7 +55,7 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl for keyword in &arguments.keywords { if let (Some("metaclass"), expr) = (keyword.arg.as_deref(), &keyword.value) { if checker.semantic().match_builtin_expr(expr, "type") { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UselessClassMetaclassType { name: class_def.name.to_string(), }, @@ -71,8 +71,6 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl ) .map(Fix::safe_edit) }); - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs index eae3cd4389194d..608daee95fd192 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -62,12 +62,11 @@ pub(crate) fn useless_metaclass_type( return; } - let mut diagnostic = Diagnostic::new(UselessMetaclassType, stmt.range()); + let mut diagnostic = checker.report_diagnostic(UselessMetaclassType, stmt.range()); let stmt = checker.semantic().current_statement(); let parent = checker.semantic().current_statement_parent(); let edit = fix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer()); diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation( checker.semantic().current_statement_parent_id(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs index ecc0defe39a0f7..96454e2efe755d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -55,7 +55,7 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UselessObjectInheritance { name: class_def.name.to_string(), }, @@ -70,6 +70,5 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm ) .map(Fix::safe_edit) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs index 46c7ef8e2bdbbc..8d7a628d188169 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -126,7 +126,7 @@ pub(crate) fn yield_in_for_loop(checker: &Checker, stmt_for: &ast::StmtFor) { return; } - let mut diagnostic = Diagnostic::new(YieldInForLoop, stmt_for.range()); + let mut diagnostic = checker.report_diagnostic(YieldInForLoop, stmt_for.range()); let contents = checker.locator().slice( parenthesized_range( @@ -158,8 +158,6 @@ pub(crate) fn yield_in_for_loop(checker: &Checker, stmt_for: &ast::StmtFor) { stmt_for.range(), ))); } - - checker.report_diagnostic(diagnostic); } /// Return `true` if the two expressions are equivalent, and both consistent solely diff --git a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs index 04a112ab8930e5..affce997053383 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PythonVersion; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; @@ -183,7 +183,7 @@ pub(crate) fn bit_count(checker: &Checker, call: &ExprCall) { format!("{literal_text}.bit_count()") }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( BitCount { existing: SourceCodeSnippet::from_str(literal_text), replacement: SourceCodeSnippet::new(replacement.to_string()), @@ -194,6 +194,4 @@ pub(crate) fn bit_count(checker: &Checker, call: &ExprCall) { Edit::range_replacement(replacement, call.range()), applicability, )); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs index bb2c1bb5b93046..fc09185c0c85b3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -104,7 +104,7 @@ pub(crate) fn check_and_remove_from_set(checker: &Checker, if_stmt: &ast::StmtIf return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CheckAndRemoveFromSet { element: SourceCodeSnippet::from_str(checker.locator().slice(check_element)), set: check_set.id.to_string(), @@ -116,7 +116,6 @@ pub(crate) fn check_and_remove_from_set(checker: &Checker, if_stmt: &ast::StmtIf if_stmt.start(), if_stmt.end(), ))); - checker.report_diagnostic(diagnostic); } fn compare(lhs: &ComparableExpr, rhs: &ComparableExpr) -> bool { diff --git a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs index fca68546a2160c..fd495659fd2a29 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -66,7 +66,7 @@ pub(crate) fn delete_full_slice(checker: &Checker, delete: &ast::StmtDelete) { continue; }; - let mut diagnostic = Diagnostic::new(DeleteFullSlice, delete.range); + let mut diagnostic = checker.report_diagnostic(DeleteFullSlice, delete.range); // Fix is only supported for single-target deletions. if delete.targets.len() == 1 { @@ -77,8 +77,6 @@ pub(crate) fn delete_full_slice(checker: &Checker, delete: &ast::StmtDelete) { delete.end(), ))); } - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs index 2a32c4240ee2af..54b313c782d030 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Stmt, StmtFor}; use ruff_python_semantic::analyze::typing; @@ -128,13 +128,13 @@ pub(crate) fn for_loop_set_mutations(checker: &Checker, for_stmt: &StmtFor) { applicability, ); - let diagnostic = Diagnostic::new( - ForLoopSetMutations { - method_name, - batch_method_name, - }, - for_stmt.range, - ); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic( + ForLoopSetMutations { + method_name, + batch_method_name, + }, + for_stmt.range, + ) + .set_fix(fix); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs index 991d5e29bc31cd..e56b2dc452e049 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprList, ExprName, ExprTuple, Stmt, StmtFor}; use ruff_python_semantic::analyze::typing; @@ -55,26 +55,34 @@ impl AlwaysFixableViolation for ForLoopWrites { } /// FURB122 -pub(crate) fn for_loop_writes_binding(checker: &Checker, binding: &Binding) -> Option { +pub(crate) fn for_loop_writes_binding(checker: &Checker, binding: &Binding) { if !binding.kind.is_loop_var() { - return None; + return; } let semantic = checker.semantic(); - let for_stmt = binding.statement(semantic)?.as_for_stmt()?; + let Some(for_stmt) = binding + .statement(semantic) + .and_then(|stmt| stmt.as_for_stmt()) + else { + return; + }; if for_stmt.is_async { - return None; + return; } let binding_names = binding_names(&for_stmt.target); - if !binding_names.first()?.range().contains_range(binding.range) { - return None; + if !binding_names + .first() + .is_some_and(|name| name.range().contains_range(binding.range)) + { + return; } - for_loop_writes(checker, for_stmt, binding.scope, &binding_names) + for_loop_writes(checker, for_stmt, binding.scope, &binding_names); } /// FURB122 @@ -86,9 +94,7 @@ pub(crate) fn for_loop_writes_stmt(checker: &Checker, for_stmt: &StmtFor) { let scope_id = checker.semantic().scope_id; - if let Some(diagnostic) = for_loop_writes(checker, for_stmt, scope_id, &[]) { - checker.report_diagnostic(diagnostic); - } + for_loop_writes(checker, for_stmt, scope_id, &[]); } /// Find the names in a `for` loop target @@ -125,40 +131,49 @@ fn for_loop_writes( for_stmt: &StmtFor, scope_id: ScopeId, binding_names: &[&ExprName], -) -> Option { +) { if !for_stmt.orelse.is_empty() { - return None; + return; } let [Stmt::Expr(stmt_expr)] = for_stmt.body.as_slice() else { - return None; + return; }; - let call_expr = stmt_expr.value.as_call_expr()?; - let expr_attr = call_expr.func.as_attribute_expr()?; + let Some(call_expr) = stmt_expr.value.as_call_expr() else { + return; + }; + let Some(expr_attr) = call_expr.func.as_attribute_expr() else { + return; + }; if &expr_attr.attr != "write" { - return None; + return; } if !call_expr.arguments.keywords.is_empty() { - return None; + return; } let [write_arg] = call_expr.arguments.args.as_ref() else { - return None; + return; }; - let io_object_name = expr_attr.value.as_name_expr()?; + let Some(io_object_name) = expr_attr.value.as_name_expr() else { + return; + }; let semantic = checker.semantic(); // Determine whether `f` in `f.write()` was bound to a file object. - let binding = semantic.binding(semantic.resolve_name(io_object_name)?); + let Some(name) = semantic.resolve_name(io_object_name) else { + return; + }; + let binding = semantic.binding(name); if !typing::is_io_base(binding, semantic) { - return None; + return; } if loop_variables_are_used_outside_loop(binding_names, for_stmt.range, semantic, scope_id) { - return None; + return; } let locator = checker.locator(); @@ -191,14 +206,14 @@ fn for_loop_writes( applicability, ); - let diagnostic = Diagnostic::new( - ForLoopWrites { - name: io_object_name.id.to_string(), - }, - for_stmt.range, - ); - - Some(diagnostic.with_fix(fix)) + checker + .report_diagnostic( + ForLoopWrites { + name: io_object_name.id.to_string(), + }, + for_stmt.range, + ) + .set_fix(fix); } fn loop_variables_are_used_outside_loop( diff --git a/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs b/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs index 6ff1f6caefd25b..85ff8c2c532abc 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{ @@ -118,10 +118,11 @@ pub(crate) fn fromisoformat_replace_z(checker: &Checker, call: &ExprCall) { let range_to_remove = TextRange::new(value_full_range.end(), argument.end()); - let diagnostic = Diagnostic::new(FromisoformatReplaceZ, argument.range()); let fix = Fix::unsafe_edit(Edit::range_deletion(range_to_remove)); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(FromisoformatReplaceZ, argument.range()) + .set_fix(fix); } fn func_is_fromisoformat(func: &Expr, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs index 42d12254f34cd4..68767e66ea8bd0 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp}; use ruff_source_file::find_newline; @@ -145,7 +145,7 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs let replacement = try_create_replacement(checker, arg, base); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( FStringNumberFormat { replacement: replacement.as_deref().map(SourceCodeSnippet::from_str), base, @@ -157,8 +157,6 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs let edit = Edit::range_replacement(replacement, subscript.range()); diagnostic.set_fix(Fix::applicable_edit(edit, applicability)); } - - checker.report_diagnostic(diagnostic); } /// Generate a replacement, if possible. diff --git a/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs b/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs index 38e95d0cb9f3bd..c92f1450f9742d 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprStringLiteral; use ruff_text_size::TextRange; @@ -129,7 +129,7 @@ fn check_charset_exact(bytes: &[u8]) -> Option<&NamedCharset> { fn push_diagnostic(checker: &Checker, range: TextRange, charset: &NamedCharset) { let name = charset.name; - let mut diagnostic = Diagnostic::new(HardcodedStringCharset { name }, range); + let mut diagnostic = checker.report_diagnostic(HardcodedStringCharset { name }, range); diagnostic.try_set_fix(|| { let (edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("string", name), @@ -141,5 +141,4 @@ fn push_diagnostic(checker: &Checker, range: TextRange, charset: &NamedCharset) [edit], )) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs index 688799396d06a1..3f6e37bf09058e 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::Modules; @@ -109,13 +109,12 @@ pub(crate) fn hashlib_digest_hex(checker: &Checker, call: &ExprCall) { ) }) { - let mut diagnostic = Diagnostic::new(HashlibDigestHex, call.range()); + let mut diagnostic = checker.report_diagnostic(HashlibDigestHex, call.range()); if arguments.is_empty() { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( ".hexdigest".to_string(), TextRange::new(value.end(), call.func.end()), ))); } - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs index bce08c481d536c..a1cc69838b9b55 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -67,7 +67,7 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &Checker, if_expr: &ast::Ex return; } - let mut diagnostic = Diagnostic::new(IfExpInsteadOfOrOperator, *range); + let mut diagnostic = checker.report_diagnostic(IfExpInsteadOfOrOperator, *range); // Replace with `{test} or {orelse}`. diagnostic.set_fix(Fix::applicable_edit( @@ -85,8 +85,6 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &Checker, if_expr: &ast::Ex Applicability::Safe }, )); - - checker.report_diagnostic(diagnostic); } /// Parenthesize an expression for use in an `or` operator (e.g., parenthesize `x` in `x or y`), diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs b/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs index 2d1c664b5abb7f..89984e116dd205 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -130,7 +130,7 @@ pub(crate) fn if_expr_min_max(checker: &Checker, if_exp: &ast::ExprIf) { checker.generator().expr(arg2), ); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( IfExprMinMax { min_max, expression: SourceCodeSnippet::from_str(checker.locator().slice(if_exp)), @@ -145,8 +145,6 @@ pub(crate) fn if_expr_min_max(checker: &Checker, if_exp: &ast::ExprIf) { if_exp.range(), ))); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs index 8191b5baa0e88e..e11b9ecfcb5ce7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; use ruff_text_size::Ranged; @@ -88,7 +88,7 @@ pub(crate) fn no_implicit_cwd(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(ImplicitCwd, call.range()); + let mut diagnostic = checker.report_diagnostic(ImplicitCwd, call.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( @@ -101,6 +101,4 @@ pub(crate) fn no_implicit_cwd(checker: &Checker, call: &ExprCall) { [import_edit], )) }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs index 559decd68c380b..14c8cb64de7948 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, Identifier}; use ruff_text_size::Ranged; @@ -116,7 +116,7 @@ pub(crate) fn int_on_sliced_str(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(IntOnSlicedStr { base: base_u8 }, call.range()); + let mut diagnostic = checker.report_diagnostic(IntOnSlicedStr { base: base_u8 }, call.range()); diagnostic.set_fix(Fix::unsafe_edits( Edit::range_replacement( checker.locator().slice(&*expr_subscript.value).to_string(), @@ -124,5 +124,4 @@ pub(crate) fn int_on_sliced_str(checker: &Checker, call: &ExprCall) { ), [Edit::range_replacement("0".to_string(), base.range())], )); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs b/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs index 74cd1d22861e14..adf7f45a410b42 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, FixAvailability, Violation}; +use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_python_semantic::SemanticModel; @@ -69,9 +69,9 @@ pub(crate) fn isinstance_type_none(checker: &Checker, call: &ast::ExprCall) { } let fix = replace_with_identity_check(expr, call.range, false, checker); - let diagnostic = Diagnostic::new(IsinstanceTypeNone, call.range); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(IsinstanceTypeNone, call.range) + .set_fix(fix); } /// Returns `true` if the given expression is equivalent to checking if the diff --git a/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs b/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs index 93efd089f0fc11..9a6ae7f2c9d87c 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprCall, ExprName, ExprSlice, ExprSubscript, ExprUnaryOp, Int, StmtAssign, UnaryOp, @@ -89,18 +89,17 @@ pub(crate) fn list_assign_reversed(checker: &Checker, assign: &StmtAssign) { return; } - checker.report_diagnostic( - Diagnostic::new( + checker + .report_diagnostic( ListReverseCopy { name: target_expr.id.to_string(), }, assign.range(), ) - .with_fix(Fix::unsafe_edit(Edit::range_replacement( + .set_fix(Fix::unsafe_edit(Edit::range_replacement( format!("{}.reverse()", target_expr.id), assign.range(), - ))), - ); + ))); } /// Recursively removes any `list` wrappers from the expression. diff --git a/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs b/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs index b3a0f3bf63a7b0..96f265ad337325 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Number}; use ruff_text_size::Ranged; @@ -55,7 +55,7 @@ pub(crate) fn math_constant(checker: &Checker, literal: &ast::ExprNumberLiteral) }; if let Some(constant) = Constant::from_value(value) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( MathConstant { literal: checker.locator().slice(literal).into(), constant: constant.name(), @@ -63,7 +63,6 @@ pub(crate) fn math_constant(checker: &Checker, literal: &ast::ExprNumberLiteral) literal.range(), ); diagnostic.try_set_fix(|| convert_to_constant(literal, constant.name(), checker)); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs index e71c3e68a24512..52c848f778d9da 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::{Ranged, TextRange}; @@ -69,7 +69,7 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { return; } - let mut diagnostic = Diagnostic::new(MetaClassABCMeta, keyword.range); + let mut diagnostic = checker.report_diagnostic(MetaClassABCMeta, keyword.range); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( @@ -99,6 +99,4 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { ) }) }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs index b20beda23a2d7f..c6cec31947480b 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::{self as ast, Expr}; @@ -84,7 +84,8 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { Reason::EmptyArgument }; - let mut diagnostic = Diagnostic::new(PrintEmptyString { reason }, call.range()); + let mut diagnostic = + checker.report_diagnostic(PrintEmptyString { reason }, call.range()); diagnostic.set_fix( EmptyStringFix::from_call( @@ -95,8 +96,6 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { ) .into_fix(), ); - - checker.report_diagnostic(diagnostic); } [arg] if arg.is_starred_expr() => { @@ -107,7 +106,7 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { [] | [_] => { // If there's a `sep` argument, remove it, regardless of what it is. if call.arguments.find_keyword("sep").is_some() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PrintEmptyString { reason: Reason::UselessSeparator, }, @@ -123,8 +122,6 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { ) .into_fix(), ); - - checker.report_diagnostic(diagnostic); } } @@ -170,7 +167,7 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { Separator::Remove }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( PrintEmptyString { reason: if separator == Separator::Retain { Reason::EmptyArgument @@ -185,8 +182,6 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) { EmptyStringFix::from_call(call, separator, checker.semantic(), checker.generator()) .into_fix(), ); - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs index 806be8f883c35b..517a5dffdfaeb7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{self, Visitor}; use ruff_python_ast::{self as ast, Expr}; @@ -92,7 +92,7 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> { .position(|open| open.is_ref(read_from)) { let open = self.candidates.remove(open); - self.checker.report_diagnostic(Diagnostic::new( + self.checker.report_diagnostic( ReadWholeFile { filename: SourceCodeSnippet::from_str( &self.checker.generator().expr(open.filename), @@ -100,7 +100,7 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> { suggestion: make_suggestion(&open, self.checker.generator()), }, open.item.range(), - )); + ); } return; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index a8ac3d1f5bf96c..89bafa3ce01353 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -1,5 +1,5 @@ use crate::preview::is_readlines_in_for_fix_safe_enabled; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Comprehension, Expr, StmtFor}; use ruff_python_semantic::analyze::typing; @@ -85,7 +85,7 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { } } - let mut diagnostic = Diagnostic::new(ReadlinesInFor, expr_call.range()); + let mut diagnostic = checker.report_diagnostic(ReadlinesInFor, expr_call.range()); diagnostic.set_fix(if is_readlines_in_for_fix_safe_enabled(checker.settings) { Fix::safe_edit(Edit::range_deletion( expr_call.range().add_start(expr_attr.value.range().len()), @@ -95,5 +95,4 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { expr_call.range().add_start(expr_attr.value.range().len()), )) }); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs index 445215aa90b5c2..d9e4b79ff2c821 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Number}; use ruff_text_size::Ranged; @@ -96,7 +96,7 @@ pub(crate) fn redundant_log_base(checker: &Checker, call: &ast::ExprCall) { return; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RedundantLogBase { base, arg: checker.locator().slice(arg).into(), @@ -104,7 +104,6 @@ pub(crate) fn redundant_log_base(checker: &Checker, call: &ast::ExprCall) { call.range(), ); diagnostic.try_set_fix(|| generate_fix(checker, call, base, arg)); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs index 1c76b1c19697ce..e0269f57e750d7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -74,7 +74,7 @@ pub(crate) fn regex_flag_alias(checker: &Checker, expr: &Expr) { return; }; - let mut diagnostic = Diagnostic::new(RegexFlagAlias { flag }, expr.range()); + let mut diagnostic = checker.report_diagnostic(RegexFlagAlias { flag }, expr.range()); diagnostic.try_set_fix(|| { let (edit, binding) = checker.importer().get_or_import_symbol( &ImportRequest::import("re", flag.full_name()), @@ -86,7 +86,6 @@ pub(crate) fn regex_flag_alias(checker: &Checker, expr: &Expr) { [edit], )) }); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs index 751f0093781a53..4923c69f149819 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs @@ -4,7 +4,7 @@ use std::fmt::{Debug, Display, Formatter}; use anyhow::Result; use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::identifier::Identifier; @@ -112,7 +112,7 @@ pub(crate) fn reimplemented_operator(checker: &Checker, target: &FunctionLike) { return; }; let fix = target.try_fix(&operator, checker.importer(), checker.semantic()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( ReimplementedOperator { operator, target: target.kind(), @@ -120,7 +120,6 @@ pub(crate) fn reimplemented_operator(checker: &Checker, target: &FunctionLike) { target.range(), ); diagnostic.try_set_optional_fix(|| fix); - checker.report_diagnostic(diagnostic); } /// Candidate for lambda expression or function definition consisting of a return statement. diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs index 17ec6df052f51a..d97d57ccc3ab38 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs @@ -1,5 +1,5 @@ use anyhow::{Result, bail}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::any_over_expr; @@ -133,7 +133,7 @@ pub(crate) fn reimplemented_starmap(checker: &Checker, target: &StarmapCandidate } } - let mut diagnostic = Diagnostic::new(ReimplementedStarmap, target.range()); + let mut diagnostic = checker.report_diagnostic(ReimplementedStarmap, target.range()); diagnostic.try_set_fix(|| { // Import `starmap` from `itertools`. let (import_edit, starmap_name) = checker.importer().get_or_import_symbol( @@ -156,7 +156,6 @@ pub(crate) fn reimplemented_starmap(checker: &Checker, target: &StarmapCandidate ); Ok(Fix::safe_edits(import_edit, [main_edit])) }); - checker.report_diagnostic(diagnostic); } /// An enum for a node that can be considered a candidate for replacement with `starmap`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs index ed1f940e31a531..7224dcc877dd59 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs @@ -1,7 +1,7 @@ use rustc_hash::FxHashMap; use ast::traversal; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::traversal::EnclosingSuite; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -94,7 +94,7 @@ pub(crate) fn repeated_append(checker: &Checker, stmt: &Stmt) { let replacement = make_suggestion(&group, checker.generator()); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( RepeatedAppend { name: group.name().to_string(), replacement: SourceCodeSnippet::new(replacement.clone()), @@ -119,8 +119,6 @@ pub(crate) fn repeated_append(checker: &Checker, stmt: &Stmt) { group.end(), ))); } - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs b/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs index 8f2ecfe18dcf79..aafb0cdd1a1310 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; @@ -74,25 +74,23 @@ pub(crate) fn repeated_global(checker: &Checker, mut suite: &[Stmt]) { // diagnostic. if let [first, .., last] = globals_sequence { let range = first.range().cover(last.range()); - checker.report_diagnostic( - Diagnostic::new(RepeatedGlobal { global_kind }, range).with_fix(Fix::safe_edit( - Edit::range_replacement( - format!( - "{global_kind} {}", - globals_sequence - .iter() - .flat_map(|stmt| match stmt { - Stmt::Global(stmt) => &stmt.names, - Stmt::Nonlocal(stmt) => &stmt.names, - _ => unreachable!(), - }) - .map(ruff_python_ast::Identifier::id) - .format(", ") - ), - range, + checker + .report_diagnostic(RepeatedGlobal { global_kind }, range) + .set_fix(Fix::safe_edit(Edit::range_replacement( + format!( + "{global_kind} {}", + globals_sequence + .iter() + .flat_map(|stmt| match stmt { + Stmt::Global(stmt) => &stmt.names, + Stmt::Nonlocal(stmt) => &stmt.names, + _ => unreachable!(), + }) + .map(ruff_python_ast::Identifier::id) + .format(", ") ), - )), - ); + range, + ))); } suite = next_suite; diff --git a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index a2d2cacbcf7443..158bb6775d6740 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::generate_comparison; use ruff_python_ast::{self as ast, CmpOp, Expr, ExprStringLiteral}; @@ -83,8 +83,6 @@ pub(crate) fn single_item_membership_test( return; }; - let diagnostic = Diagnostic::new(SingleItemMembershipTest { membership_test }, expr.range()); - let edit = Edit::range_replacement( pad( generate_comparison( @@ -110,7 +108,9 @@ pub(crate) fn single_item_membership_test( let fix = Fix::applicable_edit(edit, applicability); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(SingleItemMembershipTest { membership_test }, expr.range()) + .set_fix(fix); } /// Return the single item wrapped in `Some` if the expression contains a single diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs index f8e6d1d502b426..28d9fea4804ff3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr}; @@ -61,14 +61,13 @@ pub(crate) fn slice_copy(checker: &Checker, subscript: &ast::ExprSubscript) { let Some(name) = match_list_full_slice(subscript, checker.semantic()) else { return; }; - let mut diagnostic = Diagnostic::new(SliceCopy, subscript.range()); + let mut diagnostic = checker.report_diagnostic(SliceCopy, subscript.range()); let replacement = generate_method_call(name.clone(), "copy", checker.generator()); diagnostic.set_fix(Fix::safe_edit(Edit::replacement( replacement, subscript.start(), subscript.end(), ))); - checker.report_diagnostic(diagnostic); } /// Matches `obj[:]` where `obj` is a list. diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs index 6ede511238e2be..43d608ddffc2a7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, PythonVersion}; use ruff_python_semantic::SemanticModel; @@ -78,7 +78,7 @@ pub(crate) fn slice_to_remove_affix_expr(checker: &Checker, if_expr: &ast::ExprI let kind = removal_data.affix_query.kind; let text = removal_data.text; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( SliceToRemovePrefixOrSuffix { affix_kind: kind, stmt_or_expression: StmtOrExpr::Expression, @@ -93,7 +93,6 @@ pub(crate) fn slice_to_remove_affix_expr(checker: &Checker, if_expr: &ast::ExprI if_expr.start(), if_expr.end(), ))); - checker.report_diagnostic(diagnostic); } } } @@ -108,7 +107,7 @@ pub(crate) fn slice_to_remove_affix_stmt(checker: &Checker, if_stmt: &ast::StmtI let kind = removal_data.affix_query.kind; let text = removal_data.text; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( SliceToRemovePrefixOrSuffix { affix_kind: kind, stmt_or_expression: StmtOrExpr::Statement, @@ -127,7 +126,6 @@ pub(crate) fn slice_to_remove_affix_stmt(checker: &Checker, if_stmt: &ast::StmtI if_stmt.start(), if_stmt.end(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs index 2eb3834aa67b23..5bdbe5f7481ced 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Edit; use ruff_diagnostics::Fix; use ruff_diagnostics::FixAvailability; @@ -178,7 +177,7 @@ pub(crate) fn sorted_min_max(checker: &Checker, subscript: &ast::ExprSubscript) (Index::Last, true) => MinMax::Min, }; - let mut diagnostic = Diagnostic::new(SortedMinMax { min_max }, subscript.range()); + let mut diagnostic = checker.report_diagnostic(SortedMinMax { min_max }, subscript.range()); if checker.semantic().has_builtin_binding(min_max.as_str()) { diagnostic.set_fix({ @@ -200,8 +199,6 @@ pub(crate) fn sorted_min_max(checker: &Checker, subscript: &ast::ExprSubscript) } }); } - - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs b/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs index 9f72b42f74124b..fa35385bed3425 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, StmtClassDef, helpers::map_subscript}; use ruff_text_size::Ranged; @@ -105,7 +105,7 @@ pub(crate) fn subclass_builtin(checker: &Checker, class: &StmtClassDef) { let user_symbol = supported_builtin.user_symbol(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( SubclassBuiltin { subclass: symbol.to_string(), replacement: user_symbol.to_string(), @@ -121,7 +121,6 @@ pub(crate) fn subclass_builtin(checker: &Checker, class: &StmtClassDef) { let other_edit = Edit::range_replacement(binding, base_expr.range()); Ok(Fix::unsafe_edits(import_edit, [other_edit])) }); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs index e3abea331979ff..ea59f4496b41b9 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic}; +use ruff_diagnostics::AlwaysFixableViolation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; @@ -75,12 +75,12 @@ pub(crate) fn type_none_comparison(checker: &Checker, compare: &ast::ExprCompare _ => return, }; - let diagnostic = Diagnostic::new(TypeNoneComparison { replacement }, compare.range); - let negate = replacement == IdentityCheck::IsNot; let fix = replace_with_identity_check(other_arg, compare.range, negate, checker); - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(TypeNoneComparison { replacement }, compare.range) + .set_fix(fix); } /// Returns the object passed to the function, if the expression is a call to diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs index 36cd8b137aff52..1795cab9862ede 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::name::Name; @@ -124,7 +124,7 @@ pub(crate) fn unnecessary_enumerate(checker: &Checker, stmt_for: &ast::StmtFor) // Both the index and the value are used. } (true, false) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEnumerate { subset: EnumerateSubset::Values, }, @@ -143,8 +143,6 @@ pub(crate) fn unnecessary_enumerate(checker: &Checker, stmt_for: &ast::StmtFor) stmt_for.target.range(), ); diagnostic.set_fix(Fix::unsafe_edits(replace_iter, [replace_target])); - - checker.report_diagnostic(diagnostic); } (false, true) => { // Ensure the sequence object works with `len`. If it doesn't, the @@ -167,7 +165,7 @@ pub(crate) fn unnecessary_enumerate(checker: &Checker, stmt_for: &ast::StmtFor) } // The value is unused, so replace with `for index in range(len(sequence))`. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEnumerate { subset: EnumerateSubset::Indices, }, @@ -205,7 +203,6 @@ pub(crate) fn unnecessary_enumerate(checker: &Checker, stmt_for: &ast::StmtFor) diagnostic.set_fix(Fix::unsafe_edits(replace_iter, [replace_target])); } } - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs index 549608ca16086d..b5352a13225b8a 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_text_size::Ranged; @@ -92,7 +92,7 @@ pub(crate) fn unnecessary_from_float(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryFromFloat { method_name, constructor, @@ -157,13 +157,11 @@ pub(crate) fn unnecessary_from_float(checker: &Checker, call: &ExprCall) { edit, [Edit::range_replacement(replacement, call.range())], )); - checker.report_diagnostic(diagnostic); return; } diagnostic.set_fix(Fix::safe_edit(edit)); - checker.report_diagnostic(diagnostic); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs index a37996e60cb6ad..140be4fd9adf3a 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_trivia::PythonWhitespace; @@ -71,7 +71,7 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal return; }; - let diagnostic = match value { + match value { Expr::StringLiteral(ast::ExprStringLiteral { value: str_literal, .. }) => { @@ -120,7 +120,7 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal }; let replacement = format!("{unary}{rest}"); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( VerboseDecimalConstructor { replacement: replacement.clone(), }, @@ -131,8 +131,6 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal replacement, value.range(), ))); - - diagnostic } Expr::Call(ast::ExprCall { func, arguments, .. @@ -184,7 +182,7 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal // does not make sense for this edge case. replacement = "\"nan\"".to_string(); } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( VerboseDecimalConstructor { replacement: replacement.clone(), }, @@ -195,15 +193,9 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal replacement, value.range(), ))); - - diagnostic - } - _ => { - return; } - }; - - checker.report_diagnostic(diagnostic); + _ => {} + } } // ```console diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index 5605918c697399..2f9d283b23f175 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::relocate::relocate_expr; use ruff_python_ast::visitor::{self, Visitor}; @@ -106,7 +106,7 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { { if self.loop_counter == 0 { let open = self.candidates.remove(open); - self.checker.report_diagnostic(Diagnostic::new( + self.checker.report_diagnostic( WriteWholeFile { filename: SourceCodeSnippet::from_str( &self.checker.generator().expr(open.filename), @@ -114,7 +114,7 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { suggestion: make_suggestion(&open, content, self.checker.generator()), }, open.item.range(), - )); + ); } else { self.candidates.remove(open); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index 9cb21628c5f113..ae66234b2439a8 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -182,7 +182,9 @@ pub(crate) fn ambiguous_unicode_character_comment( settings: &LinterSettings, ) { let text = locator.slice(range); - ambiguous_unicode_character(diagnostics, text, range, Context::Comment, settings); + for candidate in ambiguous_unicode_character(text, range, settings) { + diagnostics.extend(candidate.into_diagnostic(Context::Comment, settings)); + } } /// RUF001, RUF002 @@ -203,32 +205,20 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like: match part { ast::StringLikePart::String(string_literal) => { let text = checker.locator().slice(string_literal); - let mut diagnostics = Vec::new(); - ambiguous_unicode_character( - &mut diagnostics, - text, - string_literal.range(), - context, - checker.settings, - ); - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); + for candidate in + ambiguous_unicode_character(text, string_literal.range(), checker.settings) + { + candidate.report_diagnostic(checker, context); } } ast::StringLikePart::Bytes(_) => {} ast::StringLikePart::FString(f_string) => { for literal in f_string.elements.literals() { let text = checker.locator().slice(literal); - let mut diagnostics = Vec::new(); - ambiguous_unicode_character( - &mut diagnostics, - text, - literal.range(), - context, - checker.settings, - ); - for diagnostic in diagnostics { - checker.report_diagnostic(diagnostic); + for candidate in + ambiguous_unicode_character(text, literal.range(), checker.settings) + { + candidate.report_diagnostic(checker, context); } } } @@ -237,15 +227,15 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like: } fn ambiguous_unicode_character( - diagnostics: &mut Vec, text: &str, range: TextRange, - context: Context, settings: &LinterSettings, -) { +) -> Vec { + let mut candidates = Vec::new(); + // Most of the time, we don't need to check for ambiguous unicode characters at all. if text.is_ascii() { - return; + return candidates; } // Iterate over the "words" in the text. @@ -257,9 +247,7 @@ fn ambiguous_unicode_character( if !word_candidates.is_empty() { if word_flags.is_candidate_word() { for candidate in word_candidates.drain(..) { - if let Some(diagnostic) = candidate.into_diagnostic(context, settings) { - diagnostics.push(diagnostic); - } + candidates.push(candidate); } } word_candidates.clear(); @@ -277,9 +265,7 @@ fn ambiguous_unicode_character( current_char, representant, ); - if let Some(diagnostic) = candidate.into_diagnostic(context, settings) { - diagnostics.push(diagnostic); - } + candidates.push(candidate); } } } else if current_char.is_ascii() { @@ -304,13 +290,13 @@ fn ambiguous_unicode_character( if !word_candidates.is_empty() { if word_flags.is_candidate_word() { for candidate in word_candidates.drain(..) { - if let Some(diagnostic) = candidate.into_diagnostic(context, settings) { - diagnostics.push(diagnostic); - } + candidates.push(candidate); } } word_candidates.clear(); } + + candidates } bitflags! { @@ -388,6 +374,39 @@ impl Candidate { } None } + + fn report_diagnostic(self, checker: &Checker, context: Context) { + if !checker + .settings + .allowed_confusables + .contains(&self.confusable) + { + let char_range = TextRange::at(self.offset, self.confusable.text_len()); + match context { + Context::String => checker.report_diagnostic_if_enabled( + AmbiguousUnicodeCharacterString { + confusable: self.confusable, + representant: self.representant, + }, + char_range, + ), + Context::Docstring => checker.report_diagnostic_if_enabled( + AmbiguousUnicodeCharacterDocstring { + confusable: self.confusable, + representant: self.representant, + }, + char_range, + ), + Context::Comment => checker.report_diagnostic_if_enabled( + AmbiguousUnicodeCharacterComment { + confusable: self.confusable, + representant: self.representant, + }, + char_range, + ), + }; + } + } } struct NamedUnicode(char); diff --git a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs index d11d854384f7a5..1d2decaf563d7b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs @@ -1,7 +1,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; @@ -62,7 +62,7 @@ pub(crate) fn assert_with_print_message(checker: &Checker, stmt: &ast::StmtAsser if semantic.match_builtin_expr(&call.func, "print") { // This is the confirmed rule condition - let mut diagnostic = Diagnostic::new(AssertWithPrintMessage, call.range()); + let mut diagnostic = checker.report_diagnostic(AssertWithPrintMessage, call.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().stmt(&Stmt::Assert(ast::StmtAssert { test: stmt.test.clone(), @@ -74,7 +74,6 @@ pub(crate) fn assert_with_print_message(checker: &Checker, stmt: &ast::StmtAsser // will cease to exist. stmt.range(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs b/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs index 2674f43198c0f5..e4e08bb1df9aea 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; @@ -58,24 +58,26 @@ impl Violation for AssignmentInAssert { } /// RUF018 -pub(crate) fn assignment_in_assert(checker: &Checker, binding: &Binding) -> Option { +pub(crate) fn assignment_in_assert(checker: &Checker, binding: &Binding) { if !binding.in_assert_statement() { - return None; + return; } let semantic = checker.semantic(); - let parent_expression = binding.expression(semantic)?.as_named_expr()?; + let Some(parent_expression) = binding + .expression(semantic) + .and_then(|expr| expr.as_named_expr()) + else { + return; + }; if binding .references() .all(|reference| semantic.reference(reference).in_assert_statement()) { - return None; + return; } - Some(Diagnostic::new( - AssignmentInAssert, - parent_expression.range(), - )) + checker.report_diagnostic(AssignmentInAssert, parent_expression.range()); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index e69ba982af560f..e125e7761670a9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -1,7 +1,7 @@ use std::fmt; use ast::Stmt; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{Scope, SemanticModel, analyze::typing}; @@ -67,9 +67,9 @@ impl Violation for AsyncioDanglingTask { } /// RUF006 -pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Option { +pub(crate) fn asyncio_dangling_task(checker: &Checker, expr: &Expr, semantic: &SemanticModel) { let Expr::Call(ast::ExprCall { func, .. }) = expr else { - return None; + return; }; // Ex) `asyncio.create_task(...)` @@ -81,15 +81,14 @@ pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Op _ => None, }) { - return Some(Diagnostic::new( + checker.report_diagnostic( AsyncioDanglingTask { expr: "asyncio".to_string(), method, }, expr.range(), - )); - } - + ); + } else // Ex) `loop = ...; loop.create_task(...)` if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() { if attr == "create_task" { @@ -103,18 +102,17 @@ pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Op ] ) }) { - return Some(Diagnostic::new( + checker.report_diagnostic( AsyncioDanglingTask { expr: name.id.to_string(), method: Method::CreateTask, }, expr.range(), - )); + ); } } } } - None } /// RUF006 @@ -153,18 +151,14 @@ pub(crate) fn asyncio_dangling_binding(scope: &Scope, checker: &Checker) { continue; }; - let diagnostic = match semantic.statement(source) { + match semantic.statement(source) { Stmt::Assign(ast::StmtAssign { value, targets, .. }) if targets.len() == 1 => { - asyncio_dangling_task(value, semantic) + asyncio_dangling_task(checker, value, semantic); } Stmt::AnnAssign(ast::StmtAnnAssign { value: Some(value), .. - }) => asyncio_dangling_task(value, semantic), - _ => None, - }; - - if let Some(diagnostic) = diagnostic { - checker.report_diagnostic(diagnostic); + }) => asyncio_dangling_task(checker, value, semantic), + _ => {} } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs index 08568f2afbd089..52a58882fe2e3b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs @@ -1,7 +1,7 @@ use rustc_hash::FxHashSet; use std::iter; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Arguments, Expr, ExprStarred, ExprSubscript, ExprTuple, StmtClassDef, TypeParams, @@ -103,7 +103,7 @@ pub(crate) fn class_with_mixed_type_vars(checker: &Checker, class_def: &StmtClas return; }; - let mut diagnostic = Diagnostic::new(ClassWithMixedTypeVars, generic_base.range); + let mut diagnostic = checker.report_diagnostic(ClassWithMixedTypeVars, generic_base.range); diagnostic.try_set_optional_fix(|| { convert_type_vars( @@ -114,8 +114,6 @@ pub(crate) fn class_with_mixed_type_vars(checker: &Checker, class_def: &StmtClas checker, ) }); - - checker.report_diagnostic(diagnostic); } fn typing_generic_base_and_arguments<'a>( diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index acf3d6a4a6fcf3..743808b741fbdb 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprContext, Operator}; use ruff_text_size::{Ranged, TextRange}; @@ -210,7 +210,7 @@ pub(crate) fn collection_literal_concatenation(checker: &Checker, expr: &Expr) { Type::Tuple => format!("({})", checker.generator().expr(&new_expr)), Type::List => checker.generator().expr(&new_expr), }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( CollectionLiteralConcatenation { expression: SourceCodeSnippet::new(contents.clone()), }, @@ -227,5 +227,4 @@ pub(crate) fn collection_literal_concatenation(checker: &Checker, expr: &Expr) { expr.range(), ))); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs b/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs index 3d168d8c3fb015..d32429b66d76e7 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_python_semantic::analyze::class::is_enumeration; @@ -70,7 +70,5 @@ pub(crate) fn dataclass_enum(checker: &Checker, class_def: &StmtClassDef) { return; } - let diagnostic = Diagnostic::new(DataclassEnum, decorator.range); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(DataclassEnum, decorator.range); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs index 6458126dec1cda..05da96b2e512a6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs @@ -1,6 +1,6 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_codegen::Stylist; @@ -60,10 +60,14 @@ pub(crate) fn decimal_from_float_literal_syntax(checker: &Checker, call: &ast::E matches!(qualified_name.segments(), ["decimal", "Decimal"]) }) { - let diagnostic = Diagnostic::new(DecimalFromFloatLiteral, arg.range()).with_fix( - fix_float_literal(arg.range(), float, checker.locator(), checker.stylist()), - ); - checker.report_diagnostic(diagnostic); + checker + .report_diagnostic(DecimalFromFloatLiteral, arg.range()) + .set_fix(fix_float_literal( + arg.range(), + float, + checker.locator(), + checker.stylist(), + )); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs index 521bd5d90a1866..f569c453664513 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs @@ -1,7 +1,7 @@ use anyhow::Result; use ast::Keyword; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_constant; use ruff_python_ast::{self as ast, Expr}; @@ -100,14 +100,13 @@ pub(crate) fn default_factory_kwarg(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( DefaultFactoryKwarg { default_factory: SourceCodeSnippet::from_str(checker.locator().slice(keyword)), }, call.range(), ); diagnostic.try_set_fix(|| convert_to_positional(call, keyword, checker.locator())); - checker.report_diagnostic(diagnostic); } /// Returns `true` if a value is definitively not callable (e.g., `1` or `[]`). diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs index eaf93d17dd1b14..15e836e0f2e0e0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs @@ -1,6 +1,6 @@ use anyhow::{Result, bail}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_python_codegen::Stylist; @@ -108,11 +108,11 @@ pub(crate) fn explicit_f_string_type_conversion(checker: &Checker, f_string: &as continue; } - let mut diagnostic = Diagnostic::new(ExplicitFStringTypeConversion, expression.range()); + let mut diagnostic = + checker.report_diagnostic(ExplicitFStringTypeConversion, expression.range()); diagnostic.try_set_fix(|| { convert_call_to_conversion_flag(f_string, index, checker.locator(), checker.stylist()) }); - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs index 9e8c04521659d9..921416a37c7156 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, helpers::Truthiness}; use ruff_python_semantic::analyze::typing; @@ -86,7 +86,7 @@ pub(crate) fn falsy_dict_get_fallback(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new(FalsyDictGetFallback, fallback_arg.range()); + let mut diagnostic = checker.report_diagnostic(FalsyDictGetFallback, fallback_arg.range()); let comment_ranges = checker.comment_ranges(); @@ -106,6 +106,4 @@ pub(crate) fn falsy_dict_get_fallback(checker: &Checker, expr: &Expr) { ) .map(|edit| Fix::applicable_edit(edit, applicability)) }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs index debba91faea3e2..77159ed537ff72 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_semantic::analyze::typing::{ @@ -150,9 +150,7 @@ pub(crate) fn function_call_in_dataclass_default(checker: &Checker, class_def: & let kind = FunctionCallInDataclassDefaultArgument { name: UnqualifiedName::from_expr(func).map(|name| name.to_string()), }; - let diagnostic = Diagnostic::new(kind, expr.range()); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(kind, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs index f44d19d9e767a5..5dd3962ef2c0b7 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{CmpOp, Expr, ExprName, ExprSubscript, Stmt, StmtIf}; use ruff_python_semantic::analyze::typing; @@ -65,9 +65,9 @@ pub(crate) fn if_key_in_dict_del(checker: &Checker, stmt: &StmtIf) { let fix = replace_with_dict_pop_fix(checker, stmt, test_dict, test_key); - let diagnostic = Diagnostic::new(IfKeyInDictDel, delete.range); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(IfKeyInDictDel, delete.range) + .set_fix(fix); } fn extract_dict_and_key_from_test(test: &Expr) -> Option<(&Dict, &Key)> { diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs index 91643214f9f7e6..8968369fee7fe9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::{Expr, ExprName, Stmt, StmtAssign, StmtClassDef}; @@ -95,8 +95,6 @@ pub(crate) fn implicit_class_var_in_dataclass(checker: &mut Checker, class_def: continue; } - let diagnostic = Diagnostic::new(ImplicitClassVarInDataclass, target.range()); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(ImplicitClassVarInDataclass, target.range()); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs index 298d261e430f5c..c24ec7fa27c843 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs @@ -2,7 +2,7 @@ use std::fmt; use anyhow::{Context, Result}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; @@ -187,11 +187,10 @@ pub(crate) fn implicit_optional(checker: &Checker, parameters: &Parameters) { let conversion_type = checker.target_version().into(); let mut diagnostic = - Diagnostic::new(ImplicitOptional { conversion_type }, expr.range()); + checker.report_diagnostic(ImplicitOptional { conversion_type }, expr.range()); if parsed_annotation.kind().is_simple() { diagnostic.try_set_fix(|| generate_fix(checker, conversion_type, expr)); } - checker.report_diagnostic(diagnostic); } } else { // Unquoted annotation. @@ -203,9 +202,8 @@ pub(crate) fn implicit_optional(checker: &Checker, parameters: &Parameters) { let conversion_type = checker.target_version().into(); let mut diagnostic = - Diagnostic::new(ImplicitOptional { conversion_type }, expr.range()); + checker.report_diagnostic(ImplicitOptional { conversion_type }, expr.range()); diagnostic.try_set_fix(|| generate_fix(checker, conversion_type, expr)); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs index 1fe9f7675cb518..55d2374a82b147 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; @@ -51,7 +51,7 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare) let semantic = checker.semantic(); if is_empty(right, semantic) { - checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range())); + checker.report_diagnostic(InEmptyCollection, compare.range()); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs index 44956550d2dd55..a3b3cc6718521b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprSubscript, PythonVersion}; use ruff_text_size::Ranged; @@ -111,11 +111,10 @@ pub(crate) fn subscript_with_parenthesized_tuple(checker: &Checker, subscript: & }; let edit = Edit::range_replacement(new_source, source_range); - checker.report_diagnostic( - Diagnostic::new( + checker + .report_diagnostic( IncorrectlyParenthesizedTupleInSubscript { prefer_parentheses }, source_range, ) - .with_fix(Fix::safe_edit(edit)), - ); + .set_fix(Fix::safe_edit(edit)); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs index b7d74d1258081f..fecad284df1145 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, StmtAssert}; use ruff_text_size::Ranged; @@ -52,8 +52,5 @@ pub(crate) fn invalid_assert_message_literal_argument(checker: &Checker, stmt: & return; } - checker.report_diagnostic(Diagnostic::new( - InvalidAssertMessageLiteralArgument, - message.range(), - )); + checker.report_diagnostic(InvalidAssertMessageLiteralArgument, message.range()); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs index 0e1bbd329a0b4f..ad873444cac219 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use smallvec::SmallVec; use ast::{StmtClassDef, StmtFunctionDef}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyNodeRef, helpers::comment_indentation_after}; use ruff_python_trivia::{SuppressionKind, indentation_at_offset}; @@ -105,10 +105,9 @@ pub(crate) fn ignored_formatter_suppression_comment(checker: &Checker, suite: &a comments.sort(); for (range, reason) in comments.ignored_comments() { - checker.report_diagnostic( - Diagnostic::new(InvalidFormatterSuppressionComment { reason }, range) - .with_fix(Fix::unsafe_edit(delete_comment(range, checker.locator()))), - ); + checker + .report_diagnostic(InvalidFormatterSuppressionComment { reason }, range) + .set_fix(Fix::unsafe_edit(delete_comment(range, checker.locator()))); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs index 0118b35a91e327..fca2b26aef94c0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{Expr, ExprNumberLiteral, ExprSlice, ExprSubscript, Number}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use std::fmt; @@ -90,14 +90,14 @@ pub(crate) fn invalid_index_type(checker: &Checker, expr: &ExprSubscript) { if index_type.is_literal() { // If the index is a literal, require an integer if index_type != CheckableExprType::IntLiteral { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidIndexType { value_type: value_type.to_string(), index_type: index_type.to_string(), is_slice: false, }, index.range(), - )); + ); } } else if let Expr::Slice(ExprSlice { lower, upper, step, .. @@ -113,36 +113,36 @@ pub(crate) fn invalid_index_type(checker: &Checker, expr: &ExprSubscript) { is_slice_type, CheckableExprType::IntLiteral | CheckableExprType::NoneLiteral ) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidIndexType { value_type: value_type.to_string(), index_type: is_slice_type.to_string(), is_slice: true, }, is_slice.range(), - )); + ); } } else if let Some(is_slice_type) = CheckableExprType::try_from(is_slice.as_ref()) { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidIndexType { value_type: value_type.to_string(), index_type: is_slice_type.to_string(), is_slice: true, }, is_slice.range(), - )); + ); } } } else { // If it's some other checkable data type, it's a violation - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( InvalidIndexType { value_type: value_type.to_string(), index_type: index_type.to_string(), is_slice: false, }, index.range(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs index 7fd0e9e69d656a..db20a797b29a10 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::SemanticModel; @@ -53,7 +53,7 @@ pub(crate) fn map_int_version_parsing(checker: &Checker, call: &ast::ExprCall) { }; if is_dunder_version_split_dot(second) && semantic.match_builtin_expr(first, "int") { - checker.report_diagnostic(Diagnostic::new(MapIntVersionParsing, call.range())); + checker.report_diagnostic(MapIntVersionParsing, call.range()); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 98f54229b1201b..5ef7320b061b54 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -1,7 +1,7 @@ use memchr::memchr2_iter; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_literal::format::FormatSpec; @@ -116,9 +116,9 @@ pub(crate) fn missing_fstring_syntax(checker: &Checker, literal: &ast::StringLit } if should_be_fstring(literal, checker.locator(), semantic) { - let diagnostic = Diagnostic::new(MissingFStringSyntax, literal.range()) - .with_fix(fix_fstring_syntax(literal.range())); - checker.report_diagnostic(diagnostic); + checker + .report_diagnostic(MissingFStringSyntax, literal.range()) + .set_fix(fix_fstring_syntax(literal.range())); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs index d2350e50692f7c..684933272f5b7a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; use ruff_text_size::Ranged; @@ -118,7 +118,7 @@ pub(crate) fn mutable_class_default(checker: &Checker, class_def: &ast::StmtClas return; } - checker.report_diagnostic(Diagnostic::new(MutableClassDefault, value.range())); + checker.report_diagnostic(MutableClassDefault, value.range()); } } Stmt::Assign(ast::StmtAssign { value, targets, .. }) => { @@ -130,7 +130,7 @@ pub(crate) fn mutable_class_default(checker: &Checker, class_def: &ast::StmtClas return; } - checker.report_diagnostic(Diagnostic::new(MutableClassDefault, value.range())); + checker.report_diagnostic(MutableClassDefault, value.range()); } } _ => (), diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs index 29676fc10fe072..3eb89e8568203c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; use ruff_text_size::Ranged; @@ -86,9 +86,7 @@ pub(crate) fn mutable_dataclass_default(checker: &Checker, class_def: &ast::Stmt && !is_class_var_annotation(annotation, checker.semantic()) && !is_immutable_annotation(annotation, checker.semantic(), &[]) { - let diagnostic = Diagnostic::new(MutableDataclassDefault, value.range()); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(MutableDataclassDefault, value.range()); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs index bd39425a6bfe33..0eabf475d4bf41 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr}; @@ -87,12 +87,11 @@ pub(crate) fn mutable_fromkeys_value(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(MutableFromkeysValue, call.range()); + let mut diagnostic = checker.report_diagnostic(MutableFromkeysValue, call.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( generate_dict_comprehension(keys, value, checker.generator()), call.range(), ))); - checker.report_diagnostic(diagnostic); } /// Format a code snippet to expression `{key: value for key in keys}`, where diff --git a/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs b/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs index 7b0b71e04e6389..7d0ede8cbcb9c4 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::comment_indentation_after; use ruff_python_ast::whitespace::indentation; @@ -70,9 +70,9 @@ pub(crate) fn needless_else(checker: &Checker, stmt: AnyNodeWithOrElse) { let edit = Edit::range_deletion(remove_range); let fix = Fix::safe_edit(edit); - let diagnostic = Diagnostic::new(NeedlessElse, else_range); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(NeedlessElse, else_range) + .set_fix(fix); } /// Whether `body` contains only one `pass` or `...` statement. diff --git a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs index 57cdcf0763b61a..6257b91d82a563 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprBinOp, Operator}; use ruff_python_semantic::{SemanticModel, analyze::typing::traverse_union}; @@ -77,7 +77,7 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { }) => { // Analyze the left-hand side of the `|` operator. if let Some(never_like) = NeverLike::from_expr(left, checker.semantic()) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NeverUnion { never_like, union_like: UnionLike::PEP604, @@ -95,12 +95,11 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { expr.range(), ))); } - checker.report_diagnostic(diagnostic); } // Analyze the right-hand side of the `|` operator. if let Some(never_like) = NeverLike::from_expr(right, checker.semantic()) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NeverUnion { never_like, union_like: UnionLike::PEP604, @@ -113,7 +112,6 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { expr.range(), ))); } - checker.report_diagnostic(diagnostic); } } @@ -143,7 +141,7 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( NeverUnion { never_like, union_like: UnionLike::TypingUnion, @@ -172,7 +170,6 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { }, expr.range(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs index 3645b3e574c41d..f4fd20d008ccae 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::analyze::typing::traverse_union; @@ -70,6 +70,6 @@ pub(crate) fn none_not_at_end_of_union<'a>(checker: &Checker, union: &'a Expr) { } for none_expr in none_exprs { - checker.report_diagnostic(Diagnostic::new(NoneNotAtEndOfUnion, none_expr.range())); + checker.report_diagnostic(NoneNotAtEndOfUnion, none_expr.range()); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs index cdcf523301d413..d1706e67d69e8c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parenthesized_range; @@ -86,10 +86,9 @@ pub(crate) fn parenthesize_chained_logical_operators(checker: &Checker, expr: &a { let new_source = format!("({})", locator.slice(source_range)); let edit = Edit::range_replacement(new_source, source_range); - checker.report_diagnostic( - Diagnostic::new(ParenthesizeChainedOperators, source_range) - .with_fix(Fix::safe_edit(edit)), - ); + checker + .report_diagnostic(ParenthesizeChainedOperators, source_range) + .set_fix(Fix::safe_edit(edit)); } } _ => continue, diff --git a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index f189459e270256..26bc3252610085 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs @@ -1,6 +1,6 @@ use anyhow::Context; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::{Scope, ScopeKind}; @@ -113,7 +113,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct let Some(default) = parameter.default() else { continue; }; - let mut diagnostic = Diagnostic::new(PostInitDefault, default.range()); + let mut diagnostic = checker.report_diagnostic(PostInitDefault, default.range()); if !stopped_fixes { diagnostic.try_set_fix(|| { @@ -130,8 +130,6 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct // following parameter with a default). stopped_fixes |= diagnostic.fix.is_none(); } - - checker.report_diagnostic(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs b/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs index 46b48a4ef2d968..313353622366b5 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_pytest_style::rules::is_pytest_raises; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; @@ -98,9 +98,7 @@ pub(crate) fn pytest_raises_ambiguous_pattern(checker: &Checker, call: &ast::Exp return; } - let diagnostic = Diagnostic::new(PytestRaisesAmbiguousPattern, string.range); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(PytestRaisesAmbiguousPattern, string.range); } fn string_has_unescaped_metacharacters(value: &ast::StringLiteralValue) -> bool { diff --git a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs index 2a7e28c69a1602..eb290b9e53968c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs @@ -1,7 +1,7 @@ use anyhow::Result; use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -95,9 +95,8 @@ pub(crate) fn quadratic_list_summation(checker: &Checker, call: &ast::ExprCall) return; } - let mut diagnostic = Diagnostic::new(QuadraticListSummation, *range); + let mut diagnostic = checker.report_diagnostic(QuadraticListSummation, *range); diagnostic.try_set_fix(|| convert_to_reduce(iterable, call, checker)); - checker.report_diagnostic(diagnostic); } /// Generate a [`Fix`] to convert a `sum()` call to a `functools.reduce()` call. diff --git a/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs index 0faa8bad9a8669..6aa037e4e891a6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::analyze::typing::traverse_literal; @@ -111,7 +111,7 @@ pub(crate) fn redundant_bool_literal<'a>(checker: &Checker, literal_expr: &'a Ex let seen_others = seen_expr.contains(BooleanLiteral::OTHER); let mut diagnostic = - Diagnostic::new(RedundantBoolLiteral { seen_others }, literal_expr.range()); + checker.report_diagnostic(RedundantBoolLiteral { seen_others }, literal_expr.range()); // Provide a [`Fix`] when the complete `Literal` can be replaced. Applying the fix // can leave an unused import to be fixed by the `unused-import` rule. @@ -123,8 +123,6 @@ pub(crate) fn redundant_bool_literal<'a>(checker: &Checker, literal_expr: &'a Ex ))); } } - - checker.report_diagnostic(diagnostic); } bitflags! { diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs index 6834e7643dd781..6107c3a117849a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_source_file::LineRanges; @@ -199,15 +199,13 @@ fn sort_dunder_all(checker: &Checker, target: &ast::Expr, node: &ast::Expr) { return; } - let mut diagnostic = Diagnostic::new(UnsortedDunderAll, range); + let mut diagnostic = checker.report_diagnostic(UnsortedDunderAll, range); if let SortClassification::UnsortedAndMaybeFixable { items } = elts_analysis { if let Some(fix) = create_fix(range, elts, &items, kind, checker) { diagnostic.set_fix(fix); } } - - checker.report_diagnostic(diagnostic); } /// Attempt to return `Some(fix)`, where `fix` is a `Fix` diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index ec7157621e0e9a..d318569fdff852 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use itertools::izip; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Binding; @@ -110,38 +110,50 @@ const SORTING_STYLE: SortingStyle = SortingStyle::Natural; /// This routine checks whether the display is sorted, and emits a /// violation if it is not sorted. If the tuple/list/set was not sorted, /// it attempts to set a `Fix` on the violation. -pub(crate) fn sort_dunder_slots(checker: &Checker, binding: &Binding) -> Option { +pub(crate) fn sort_dunder_slots(checker: &Checker, binding: &Binding) { let semantic = checker.semantic(); - let (target, value) = match binding.statement(semantic)? { + let Some(stmt) = binding.statement(semantic) else { + return; + }; + + let (target, value) = match stmt { ast::Stmt::Assign(ast::StmtAssign { targets, value, .. }) => match targets.as_slice() { [target] => (target, &**value), - _ => return None, + _ => return, }, - ast::Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => { - (&**target, value.as_deref()?) - } - _ => return None, + ast::Stmt::AnnAssign(ast::StmtAnnAssign { + target, + value: Some(value), + .. + }) => (&**target, &**value), + _ => return, }; - let ast::ExprName { id, .. } = target.as_name_expr()?; + let Some(ast::ExprName { id, .. }) = target.as_name_expr() else { + return; + }; if id != "__slots__" { - return None; + return; } // We're only interested in `__slots__` in the class scope - let enclosing_class = semantic.scopes[binding.scope].kind.as_class()?; + let Some(enclosing_class) = semantic.scopes[binding.scope].kind.as_class() else { + return; + }; // and it has to be an assignment to a "display literal" (a literal dict/set/tuple/list) - let display = StringLiteralDisplay::new(value)?; + let Some(display) = StringLiteralDisplay::new(value) else { + return; + }; let sort_classification = SortClassification::of_elements(&display.elts, SORTING_STYLE); if sort_classification.is_not_a_list_of_string_literals() || sort_classification.is_sorted() { - return None; + return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnsortedDunderSlots { class_name: enclosing_class.name.id.clone(), }, @@ -163,8 +175,6 @@ pub(crate) fn sort_dunder_slots(checker: &Checker, binding: &Binding) -> Option< diagnostic.set_fix(Fix::applicable_edit(edit, applicability)); } } - - Some(diagnostic) } /// Struct representing a [display](https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries) diff --git a/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs b/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs index c20371284c9c9d..8e192ef2c32e3a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, parenthesize::parenthesized_range}; use ruff_python_parser::TokenKind; @@ -89,13 +89,11 @@ pub(crate) fn starmap_zip(checker: &Checker, call: &ExprCall) { return; } - let mut diagnostic = Diagnostic::new(StarmapZip, call.range); + let mut diagnostic = checker.report_diagnostic(StarmapZip, call.range); if let Some(fix) = replace_with_map(call, iterable_call, checker) { diagnostic.set_fix(fix); } - - checker.report_diagnostic(diagnostic); } /// Replace the `starmap` call with a call to the `map` builtin, if `map` has not been shadowed. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs index 4b97745a3a03fd..872ecfbf35335d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{Arguments, Expr, ExprCall}; @@ -88,9 +88,9 @@ pub(crate) fn unnecessary_cast_to_int(checker: &Checker, call: &ExprCall) { checker.comment_ranges(), checker.source(), ); - let diagnostic = Diagnostic::new(UnnecessaryCastToInt, call.range()); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(UnnecessaryCastToInt, call.range()) + .set_fix(fix); } /// Creates a fix that replaces `int(expression)` with `expression`. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs index f01e6aca119da5..8bc725f53e4fcd 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, Int}; use ruff_python_semantic::SemanticModel; @@ -115,7 +115,7 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element(checker: &Checke Cow::Owned(format!("iter({iterable})")) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryIterableAllocationForFirstElement { iterable: SourceCodeSnippet::new(iterable.to_string()), }, @@ -126,8 +126,6 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element(checker: &Checke format!("next({iterable})"), expr.range(), ))); - - checker.report_diagnostic(diagnostic); } /// Check that the slice [`Expr`] is a slice of the first element (e.g., `x[0]`). diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs index 34d1d9f9d0ae9c..c34d075b1a0aa4 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs @@ -1,7 +1,7 @@ use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::parenthesize::parenthesized_range; @@ -102,7 +102,7 @@ pub(crate) fn unnecessary_key_check(checker: &Checker, expr: &Expr) { return; } - let mut diagnostic = Diagnostic::new(UnnecessaryKeyCheck, expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryKeyCheck, expr.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!( "{}.get({})", @@ -127,5 +127,4 @@ pub(crate) fn unnecessary_key_check(checker: &Checker, expr: &Expr) { ), expr.range(), ))); - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs index eeef2580c6f6c4..869f23d4aa9ffd 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -91,7 +91,7 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryEmptyIterableWithinDequeCall { has_maxlen: maxlen.is_some(), }, @@ -99,8 +99,6 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a ); diagnostic.set_fix(fix_unnecessary_literal_in_deque(checker, deque, maxlen)); - - checker.report_diagnostic(diagnostic); } fn fix_unnecessary_literal_in_deque( diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs index a40bb723cbfaa3..0595b1e0f7b244 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyNodeRef, Expr, ExprContext, ExprSubscript, ExprTuple}; use ruff_python_semantic::analyze::typing::traverse_literal; @@ -105,7 +105,7 @@ pub(crate) fn unnecessary_nested_literal<'a>(checker: &Checker, literal_expr: &' literal_expr, ); - let mut diagnostic = Diagnostic::new(UnnecessaryNestedLiteral, literal_expr.range()); + let mut diagnostic = checker.report_diagnostic(UnnecessaryNestedLiteral, literal_expr.range()); // Create a [`Fix`] that flattens all nodes. if let Expr::Subscript(subscript) = literal_expr { @@ -134,6 +134,4 @@ pub(crate) fn unnecessary_nested_literal<'a>(checker: &Checker, literal_expr: &' ); diagnostic.set_fix(fix); } - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 2c9c7ed247bf72..6200664b0a2961 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Arguments, CmpOp, Expr, ExprAttribute, ExprCall, ExprCompare, ExprContext, ExprStringLiteral, @@ -116,7 +116,7 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) let new_expr = re_func.replacement(); let repl = new_expr.map(|expr| checker.generator().expr(&expr)); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnnecessaryRegularExpression { replacement: repl.clone(), }, @@ -133,8 +133,6 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) }, )); } - - checker.report_diagnostic(diagnostic); } /// The `re` functions supported by this rule. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs index 009d875ba16b7c..070ebc4e40a7c2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs @@ -1,6 +1,6 @@ use crate::Locator; use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, Expr, ExprCall, ExprNumberLiteral, Number}; use ruff_python_semantic::SemanticModel; @@ -100,9 +100,9 @@ pub(crate) fn unnecessary_round(checker: &Checker, call: &ExprCall) { let edit = unwrap_round_call(call, rounded, checker.semantic(), checker.locator()); let fix = Fix::applicable_edit(edit, applicability); - let diagnostic = Diagnostic::new(UnnecessaryRound, call.range()); - - checker.report_diagnostic(diagnostic.with_fix(fix)); + checker + .report_diagnostic(UnnecessaryRound, call.range()) + .set_fix(fix); } #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs b/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs index 18135f9bb90468..9119be93b2a114 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, StringLiteral, @@ -161,7 +161,7 @@ fn check_string(checker: &Checker, literal: &StringLiteral, module: RegexModule, let kind = PatternKind::String; let func = func.to_string(); let range = literal.range; - let mut diagnostic = Diagnostic::new(UnrawRePattern { module, func, kind }, range); + let mut diagnostic = checker.report_diagnostic(UnrawRePattern { module, func, kind }, range); if // The (no-op) `u` prefix is a syntax error when combined with `r` @@ -177,7 +177,6 @@ fn check_string(checker: &Checker, literal: &StringLiteral, module: RegexModule, literal.range().start(), ))); } - checker.report_diagnostic(diagnostic); } fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, func: &str) { @@ -188,7 +187,5 @@ fn check_bytes(checker: &Checker, literal: &BytesLiteral, module: RegexModule, f let kind = PatternKind::Bytes; let func = func.to_string(); let range = literal.range; - let diagnostic = Diagnostic::new(UnrawRePattern { module, func, kind }, range); - - checker.report_diagnostic(diagnostic); + checker.report_diagnostic(UnrawRePattern { module, func, kind }, range); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs index 6bc529ec5572e4..f9f7b32c9bd758 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor::source_order; @@ -188,11 +188,11 @@ pub(crate) fn unused_async( }; if !found_await_or_async { - checker.report_diagnostic(Diagnostic::new( + checker.report_diagnostic( UnusedAsync { name: name.to_string(), }, function_def.identifier(), - )); + ); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs index 02defb93263af1..bf4997abca3fe6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; @@ -78,7 +78,7 @@ pub(crate) fn unused_unpacked_variable(checker: &Checker, name: &str, binding: & return; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UnusedUnpackedVariable { name: name.to_string(), }, @@ -87,5 +87,4 @@ pub(crate) fn unused_unpacked_variable(checker: &Checker, name: &str, binding: & if let Some(fix) = remove_unused_variable(binding, checker) { diagnostic.set_fix(fix); } - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs b/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs index a6737dff8598ad..facb46162465b6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_semantic::{Binding, BindingId}; @@ -98,20 +98,16 @@ impl Violation for UsedDummyVariable { } /// RUF052 -pub(crate) fn used_dummy_variable( - checker: &Checker, - binding: &Binding, - binding_id: BindingId, -) -> Option { +pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_id: BindingId) { let name = binding.name(checker.source()); // Ignore `_` and dunder variables if name == "_" || is_dunder(name) { - return None; + return; } // only used variables if binding.is_unused() { - return None; + return; } // We only emit the lint on variables defined via assignments. @@ -131,12 +127,12 @@ pub(crate) fn used_dummy_variable( // - // - if !binding.kind.is_assignment() { - return None; + return; } // This excludes `global` and `nonlocal` variables. if binding.is_global() || binding.is_nonlocal() { - return None; + return; } let semantic = checker.semantic(); @@ -144,7 +140,7 @@ pub(crate) fn used_dummy_variable( // Only variables defined in function scopes let scope = &semantic.scopes[binding.scope]; if !scope.kind.is_function() { - return None; + return; } // Recall from above that we do not wish to flag "private" @@ -159,21 +155,22 @@ pub(crate) fn used_dummy_variable( .shadowed_bindings(binding_id) .any(|shadow_id| semantic.binding(shadow_id).kind.is_argument()) { - return None; + return; } if !checker.settings.dummy_variable_rgx.is_match(name) { - return None; + return; } // If the name doesn't start with an underscore, we don't consider it for a fix if !name.starts_with('_') { - return Some(Diagnostic::new( + checker.report_diagnostic( UsedDummyVariable { name: name.to_string(), shadowed_kind: None, }, binding.range(), - )); + ); + return; } // Trim the leading underscores for further checks @@ -181,7 +178,7 @@ pub(crate) fn used_dummy_variable( let shadowed_kind = ShadowedKind::new(binding, trimmed_name, checker); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = checker.report_diagnostic( UsedDummyVariable { name: name.to_string(), shadowed_kind: Some(shadowed_kind), @@ -196,8 +193,6 @@ pub(crate) fn used_dummy_variable( .map(|(edit, rest)| Fix::unsafe_edits(edit, rest)) }); } - - Some(diagnostic) } /// Suggests a potential alternative name to resolve a shadowing conflict. diff --git a/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs b/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs index d1f59ca8d21922..1076ec0edf6f7c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::comparable::ComparableExpr; @@ -44,5 +44,5 @@ pub(crate) fn useless_if_else(checker: &Checker, if_expr: &ast::ExprIf) { return; } - checker.report_diagnostic(Diagnostic::new(UselessIfElse, *range)); + checker.report_diagnostic(UselessIfElse, *range); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs index d4bcd9844deaa3..7e055c204ee644 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Int}; use ruff_text_size::Ranged; @@ -160,7 +160,7 @@ pub(crate) fn zip_instead_of_pairwise(checker: &Checker, call: &ast::ExprCall) { return; } - let mut diagnostic = Diagnostic::new(ZipInsteadOfPairwise, func.range()); + let mut diagnostic = checker.report_diagnostic(ZipInsteadOfPairwise, func.range()); diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( @@ -172,6 +172,4 @@ pub(crate) fn zip_instead_of_pairwise(checker: &Checker, call: &ast::ExprCall) { Edit::range_replacement(format!("{binding}({})", first_arg_info.id), call.range()); Ok(Fix::unsafe_edits(import_edit, [reference_edit])) }); - - checker.report_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs index dadbaa807c06d6..e8a1c7b66c0f2c 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, ExceptHandler, Expr}; @@ -80,7 +80,8 @@ pub(crate) fn error_instead_of_exception(checker: &Checker, handlers: &[ExceptHa for (expr, logging_level) in calls { if matches!(logging_level, LoggingLevel::Error) { if exc_info(&expr.arguments, checker.semantic()).is_none() { - let mut diagnostic = Diagnostic::new(ErrorInsteadOfException, expr.range()); + let mut diagnostic = + checker.report_diagnostic(ErrorInsteadOfException, expr.range()); match expr.func.as_ref() { Expr::Attribute(ast::ExprAttribute { attr, .. }) => { @@ -134,8 +135,6 @@ pub(crate) fn error_instead_of_exception(checker: &Checker, handlers: &[ExceptHa } _ => {} } - - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs index 05cb8020b47ffa..3d5737c7dcb598 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_text_size::Ranged; @@ -78,7 +78,7 @@ pub(crate) fn raise_vanilla_args(checker: &Checker, expr: &Expr) { } if contains_message(arg) { - checker.report_diagnostic(Diagnostic::new(RaiseVanillaArgs, expr.range())); + checker.report_diagnostic(RaiseVanillaArgs, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs index 5f189ab4a37f3d..ff393ad010ef45 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs @@ -1,7 +1,7 @@ use ruff_python_ast::Expr; use ruff_python_ast::helpers::map_callable; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -74,6 +74,6 @@ pub(crate) fn raise_vanilla_class(checker: &Checker, expr: &Expr) { ) }) { - checker.report_diagnostic(Diagnostic::new(RaiseVanillaClass, expr.range())); + checker.report_diagnostic(RaiseVanillaClass, expr.range()); } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs index 6ac01727446086..80035ee8477e7d 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ comparable::ComparableExpr, @@ -115,7 +115,7 @@ pub(crate) fn raise_within_try(checker: &Checker, body: &[Stmt], handlers: &[Exc .is_some_and(|builtin| matches!(builtin, "Exception" | "BaseException")) }) { - checker.report_diagnostic(Diagnostic::new(RaiseWithinTry, stmt.range())); + checker.report_diagnostic(RaiseWithinTry, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs index 87839a4e724574..38ca82348c169d 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_text_size::Ranged; @@ -73,7 +73,7 @@ pub(crate) fn try_consider_else( return; } } - checker.report_diagnostic(Diagnostic::new(TryConsiderElse, stmt.range())); + checker.report_diagnostic(TryConsiderElse, stmt.range()); } } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs index c84f2238fded99..6414dc6cdc9420 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; @@ -94,7 +94,7 @@ fn check_type_check_test(semantic: &SemanticModel, test: &Expr) -> bool { fn check_raise(checker: &Checker, exc: &Expr, item: &Stmt) { if is_builtin_exception(exc, checker.semantic()) { - checker.report_diagnostic(Diagnostic::new(TypeCheckWithoutTypeError, item.range())); + checker.report_diagnostic(TypeCheckWithoutTypeError, item.range()); } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs b/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs index 199685480eb377..b65c0dd246601c 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, ExceptHandlerExceptHandler, Expr, Stmt}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; @@ -63,7 +63,7 @@ pub(crate) fn useless_try_except(checker: &Checker, handlers: &[ExceptHandler]) }) { // Require that all handlers are useless, but create one diagnostic per handler. for handler in handlers { - checker.report_diagnostic(Diagnostic::new(UselessTryExcept, handler.range())); + checker.report_diagnostic(UselessTryExcept, handler.range()); } } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs index 252d3c5562e756..f83046c6293ef2 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; @@ -78,7 +78,7 @@ pub(crate) fn verbose_log_message(checker: &Checker, handlers: &[ExceptHandler]) }; let binding = checker.semantic().binding(id); if binding.kind.is_bound_exception() { - checker.report_diagnostic(Diagnostic::new(VerboseLogMessage, expr.range())); + checker.report_diagnostic(VerboseLogMessage, expr.range()); } } } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs index 008748680f0166..2d95dded60e2fd 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_text_size::Ranged; @@ -72,12 +72,12 @@ pub(crate) fn verbose_raise(checker: &Checker, handlers: &[ExceptHandler]) { // ...and the raised object is bound to the same name... if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() { if id == exception_name.as_str() { - let mut diagnostic = Diagnostic::new(VerboseRaise, exc.range()); + let mut diagnostic = + checker.report_diagnostic(VerboseRaise, exc.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( "raise".to_string(), raise.range(), ))); - checker.report_diagnostic(diagnostic); } } } diff --git a/crates/ruff_macros/src/violation_metadata.rs b/crates/ruff_macros/src/violation_metadata.rs index 3cf60307abf40a..bc8789d8183524 100644 --- a/crates/ruff_macros/src/violation_metadata.rs +++ b/crates/ruff_macros/src/violation_metadata.rs @@ -7,10 +7,12 @@ pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result let name = input.ident; + let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl(); + Ok(quote! { #[automatically_derived] #[expect(deprecated)] - impl ruff_diagnostics::ViolationMetadata for #name { + impl #impl_generics ruff_diagnostics::ViolationMetadata for #name #ty_generics #where_clause { fn rule_name() -> &'static str { ::ruff_macros::kebab_case!(#name) } From 9925910a29643ff1f29eafeadc2b1d26812e4993 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Wed, 28 May 2025 09:27:09 -0400 Subject: [PATCH 263/487] Add a `ViolationMetadata::rule` method (#18234) Summary -- This PR adds a macro-generated method to retrieve the `Rule` associated with a given `Violation` struct, which makes it substantially cheaper than parsing from the rule name. The rule is then converted to a `NoqaCode` for storage on the `Message` (and eventually on the new diagnostic type). The `ViolationMetadata::rule_name` method was now unused, so the `rule` method replaces it. Several types had to be moved from the `ruff_diagnostics` crate to the `ruff_linter` crate to make this work, namely the `Violation` traits and the old `Diagnostic` type, which had a constructor generic over a `Violation`. It's actually a fairly small PR, minus the hundreds of import changes. The main changes are in these files: - [crates/ruff_linter/src/message/mod.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-139754ea310d75f28307008d21c771a190038bd106efe3b9267cc2d6c0fa0921) - [crates/ruff_diagnostics/src/lib.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-8e8ea5c586935bf21ea439f24253fcfd5955d2cb130f5377c2fa7bfee3ea3a81) - [crates/ruff_linter/src/diagnostic.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-1d0c9aad90d8f9446079c5be5f284150d97797158715bd9729e6f1f70246297a) - [crates/ruff_linter/src/lib.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-eb93ef7e78a612f5fa9145412c75cf6b1a5cefba1c2233e4a11a880a1ce1fbcc) Test Plan -- Existing tests --- Cargo.lock | 3 -- crates/ruff/src/cache.rs | 2 +- crates/ruff/src/commands/check.rs | 2 +- crates/ruff/src/commands/rule.rs | 2 +- crates/ruff/src/diagnostics.rs | 2 +- crates/ruff_dev/Cargo.toml | 1 - crates/ruff_dev/src/generate_docs.rs | 2 +- crates/ruff_dev/src/generate_rules_table.rs | 2 +- crates/ruff_diagnostics/Cargo.toml | 2 -- crates/ruff_diagnostics/src/lib.rs | 4 --- .../src/checkers/ast/analyze/bindings.rs | 2 +- .../checkers/ast/analyze/deferred_scopes.rs | 2 +- crates/ruff_linter/src/checkers/ast/mod.rs | 9 +++--- crates/ruff_linter/src/checkers/filesystem.rs | 2 +- crates/ruff_linter/src/checkers/imports.rs | 2 +- .../ruff_linter/src/checkers/logical_lines.rs | 2 +- crates/ruff_linter/src/checkers/noqa.rs | 2 +- .../src/checkers/physical_lines.rs | 2 +- crates/ruff_linter/src/checkers/tokens.rs | 2 +- crates/ruff_linter/src/codes.rs | 8 ++++- .../src/diagnostic.rs | 25 ++++++++++++---- crates/ruff_linter/src/fix/edits.rs | 7 +++-- crates/ruff_linter/src/fix/mod.rs | 7 +++-- crates/ruff_linter/src/importer/insertion.rs | 2 +- crates/ruff_linter/src/importer/mod.rs | 2 +- crates/ruff_linter/src/lib.rs | 6 ++++ crates/ruff_linter/src/linter.rs | 2 +- crates/ruff_linter/src/message/diff.rs | 2 +- crates/ruff_linter/src/message/json.rs | 2 +- crates/ruff_linter/src/message/mod.rs | 30 +++++++++++-------- crates/ruff_linter/src/message/rdjson.rs | 2 +- crates/ruff_linter/src/noqa.rs | 4 +-- crates/ruff_linter/src/pyproject_toml.rs | 2 +- crates/ruff_linter/src/renamer.rs | 2 +- .../airflow/rules/dag_schedule_argument.rs | 2 +- .../airflow/rules/moved_to_provider_in_3.rs | 6 ++-- .../src/rules/airflow/rules/removal_in_3.rs | 2 +- .../suggested_to_move_to_provider_in_3.rs | 2 +- .../airflow/rules/suggested_to_update_3_0.rs | 2 +- .../rules/airflow/rules/task_variable_name.rs | 2 +- .../eradicate/rules/commented_out_code.rs | 2 +- .../rules/fastapi_non_annotated_dependency.rs | 2 +- .../rules/fastapi_redundant_response_model.rs | 2 +- .../rules/fastapi_unused_path_parameter.rs | 4 +-- .../src/rules/flake8_2020/rules/compare.rs | 2 +- .../flake8_2020/rules/name_or_attribute.rs | 2 +- .../src/rules/flake8_2020/rules/subscript.rs | 2 +- .../src/rules/flake8_annotations/helpers.rs | 2 +- .../flake8_annotations/rules/definition.rs | 2 +- .../flake8_async/rules/async_busy_wait.rs | 2 +- .../rules/async_function_with_timeout.rs | 2 +- .../flake8_async/rules/async_zero_sleep.rs | 2 +- .../flake8_async/rules/blocking_http_call.rs | 2 +- .../flake8_async/rules/blocking_open_call.rs | 2 +- .../rules/blocking_process_invocation.rs | 2 +- .../flake8_async/rules/blocking_sleep.rs | 2 +- .../rules/cancel_scope_no_checkpoint.rs | 2 +- .../rules/long_sleep_not_forever.rs | 2 +- .../src/rules/flake8_async/rules/sync_call.rs | 2 +- .../rules/flake8_bandit/rules/assert_used.rs | 6 ++-- .../rules/bad_file_permissions.rs | 2 +- .../rules/flake8_bandit/rules/django_extra.rs | 2 +- .../flake8_bandit/rules/django_raw_sql.rs | 2 +- .../rules/flake8_bandit/rules/exec_used.rs | 2 +- .../flake8_bandit/rules/flask_debug_true.rs | 2 +- .../rules/hardcoded_bind_all_interfaces.rs | 2 +- .../rules/hardcoded_password_default.rs | 2 +- .../rules/hardcoded_password_func_arg.rs | 2 +- .../rules/hardcoded_password_string.rs | 2 +- .../rules/hardcoded_sql_expression.rs | 2 +- .../rules/hardcoded_tmp_directory.rs | 2 +- .../rules/hashlib_insecure_hash_functions.rs | 2 +- .../rules/jinja2_autoescape_false.rs | 2 +- .../rules/logging_config_insecure_listen.rs | 2 +- .../flake8_bandit/rules/mako_templates.rs | 5 ++-- .../flake8_bandit/rules/paramiko_calls.rs | 2 +- .../rules/request_with_no_cert_validation.rs | 2 +- .../rules/request_without_timeout.rs | 2 +- .../flake8_bandit/rules/shell_injection.rs | 4 +-- .../rules/snmp_insecure_version.rs | 2 +- .../rules/snmp_weak_cryptography.rs | 2 +- .../rules/ssh_no_host_key_verification.rs | 2 +- .../rules/ssl_insecure_version.rs | 2 +- .../rules/ssl_with_bad_defaults.rs | 2 +- .../rules/ssl_with_no_version.rs | 2 +- .../rules/suspicious_function_call.rs | 2 +- .../flake8_bandit/rules/suspicious_imports.rs | 2 +- .../rules/tarfile_unsafe_members.rs | 5 ++-- .../rules/try_except_continue.rs | 2 +- .../flake8_bandit/rules/try_except_pass.rs | 2 +- .../flake8_bandit/rules/unsafe_markup_use.rs | 2 +- .../flake8_bandit/rules/unsafe_yaml_load.rs | 2 +- .../rules/weak_cryptographic_key.rs | 2 +- .../flake8_blind_except/rules/blind_except.rs | 2 +- ...olean_default_value_positional_argument.rs | 2 +- .../rules/boolean_positional_value_in_call.rs | 2 +- .../boolean_type_hint_positional_argument.rs | 2 +- .../rules/abstract_base_class.rs | 2 +- .../flake8_bugbear/rules/assert_false.rs | 2 +- .../rules/assert_raises_exception.rs | 2 +- .../rules/assignment_to_os_environ.rs | 2 +- .../rules/batched_without_explicit_strict.rs | 7 +++-- .../rules/cached_instance_method.rs | 2 +- .../rules/class_as_data_structure.rs | 2 +- .../rules/duplicate_exceptions.rs | 4 +-- .../flake8_bugbear/rules/duplicate_value.rs | 2 +- .../rules/except_with_empty_tuple.rs | 2 +- .../except_with_non_exception_classes.rs | 2 +- .../rules/f_string_docstring.rs | 2 +- .../function_call_in_argument_default.rs | 7 ++--- .../rules/function_uses_loop_variable.rs | 2 +- .../rules/getattr_with_constant.rs | 2 +- .../rules/jump_statement_in_finally.rs | 2 +- .../rules/loop_iterator_mutation.rs | 2 +- .../rules/loop_variable_overrides_iterator.rs | 2 +- .../rules/mutable_argument_default.rs | 2 +- .../rules/mutable_contextvar_default.rs | 2 +- .../rules/no_explicit_stacklevel.rs | 2 +- .../flake8_bugbear/rules/raise_literal.rs | 2 +- .../rules/raise_without_from_inside_except.rs | 2 +- .../rules/re_sub_positional_args.rs | 2 +- .../redundant_tuple_in_exception_handler.rs | 2 +- .../rules/return_in_generator.rs | 2 +- .../rules/reuse_of_groupby_generator.rs | 2 +- .../rules/setattr_with_constant.rs | 2 +- .../star_arg_unpacking_after_keyword_arg.rs | 2 +- .../rules/static_key_dict_comprehension.rs | 2 +- .../rules/strip_with_multi_characters.rs | 2 +- .../rules/unary_prefix_increment_decrement.rs | 2 +- .../rules/unintentional_type_annotation.rs | 2 +- .../rules/unreliable_callable_check.rs | 2 +- .../rules/unused_loop_control_variable.rs | 2 +- .../rules/useless_comparison.rs | 2 +- .../rules/useless_contextlib_suppress.rs | 2 +- .../rules/useless_expression.rs | 2 +- .../rules/zip_without_explicit_strict.rs | 2 +- .../rules/builtin_argument_shadowing.rs | 2 +- .../rules/builtin_attribute_shadowing.rs | 2 +- .../rules/builtin_import_shadowing.rs | 2 +- .../builtin_lambda_argument_shadowing.rs | 2 +- .../rules/builtin_variable_shadowing.rs | 2 +- .../rules/stdlib_module_shadowing.rs | 2 +- .../flake8_commas/rules/trailing_commas.rs | 4 +-- .../src/rules/flake8_comprehensions/fixes.rs | 2 +- .../rules/unnecessary_call_around_sorted.rs | 2 +- .../rules/unnecessary_collection_call.rs | 2 +- .../rules/unnecessary_comprehension.rs | 2 +- .../unnecessary_comprehension_in_call.rs | 4 +-- ...cessary_dict_comprehension_for_iterable.rs | 2 +- .../unnecessary_double_cast_or_process.rs | 2 +- .../rules/unnecessary_generator_dict.rs | 2 +- .../rules/unnecessary_generator_list.rs | 2 +- .../rules/unnecessary_generator_set.rs | 2 +- .../rules/unnecessary_list_call.rs | 3 +- .../unnecessary_list_comprehension_dict.rs | 3 +- .../unnecessary_list_comprehension_set.rs | 2 +- .../rules/unnecessary_literal_dict.rs | 3 +- .../rules/unnecessary_literal_set.rs | 2 +- .../unnecessary_literal_within_dict_call.rs | 2 +- .../unnecessary_literal_within_list_call.rs | 2 +- .../unnecessary_literal_within_tuple_call.rs | 2 +- .../rules/unnecessary_map.rs | 4 +-- .../rules/unnecessary_subscript_reversal.rs | 2 +- .../rules/missing_copyright_notice.rs | 2 +- .../rules/call_date_fromtimestamp.rs | 2 +- .../flake8_datetimez/rules/call_date_today.rs | 2 +- .../rules/call_datetime_fromtimestamp.rs | 2 +- .../rules/call_datetime_now_without_tzinfo.rs | 2 +- .../call_datetime_strptime_without_zone.rs | 2 +- .../rules/call_datetime_today.rs | 2 +- .../rules/call_datetime_utcfromtimestamp.rs | 2 +- .../rules/call_datetime_utcnow.rs | 2 +- .../rules/call_datetime_without_tzinfo.rs | 2 +- .../rules/datetime_min_max.rs | 2 +- .../rules/flake8_debugger/rules/debugger.rs | 2 +- .../rules/all_with_model_form.rs | 2 +- .../rules/exclude_with_model_form.rs | 2 +- .../rules/locals_in_render_function.rs | 2 +- .../rules/model_without_dunder_str.rs | 2 +- .../rules/non_leading_receiver_decorator.rs | 2 +- .../rules/nullable_model_string_field.rs | 2 +- .../rules/unordered_body_content_in_model.rs | 2 +- .../rules/string_in_exception.rs | 2 +- .../src/rules/flake8_executable/rules/mod.rs | 2 +- .../rules/shebang_leading_whitespace.rs | 6 ++-- .../rules/shebang_missing_executable_file.rs | 2 +- .../rules/shebang_missing_python.rs | 2 +- .../rules/shebang_not_executable.rs | 7 ++--- .../rules/shebang_not_first_line.rs | 2 +- .../src/rules/flake8_fixme/rules/todos.rs | 2 +- .../rules/future_required_type_annotation.rs | 2 +- .../future_rewritable_type_annotation.rs | 2 +- .../rules/f_string_in_gettext_func_call.rs | 2 +- .../rules/format_in_gettext_func_call.rs | 2 +- .../rules/printf_in_gettext_func_call.rs | 6 ++-- .../rules/explicit.rs | 4 +-- .../rules/implicit.rs | 2 +- .../rules/banned_import_alias.rs | 2 +- .../rules/banned_import_from.rs | 5 ++-- .../rules/unconventional_import_alias.rs | 2 +- .../rules/direct_logger_instantiation.rs | 2 +- .../rules/exc_info_outside_except_handler.rs | 2 +- .../rules/exception_without_exc_info.rs | 2 +- .../rules/invalid_get_logger_argument.rs | 2 +- .../log_exception_outside_except_handler.rs | 2 +- .../flake8_logging/rules/root_logger_call.rs | 2 +- .../flake8_logging/rules/undocumented_warn.rs | 2 +- .../rules/logging_call.rs | 2 +- .../rules/flake8_logging_format/violations.rs | 3 +- .../rules/implicit_namespace_package.rs | 2 +- .../rules/duplicate_class_field_definition.rs | 2 +- .../rules/multiple_starts_ends_with.rs | 4 +-- .../flake8_pie/rules/non_unique_enums.rs | 2 +- .../rules/reimplemented_container_builtin.rs | 4 +-- .../rules/unnecessary_dict_kwargs.rs | 2 +- .../rules/unnecessary_placeholder.rs | 4 +-- .../rules/unnecessary_range_start.rs | 2 +- .../flake8_pie/rules/unnecessary_spread.rs | 2 +- .../rules/flake8_print/rules/print_call.rs | 2 +- .../flake8_pyi/rules/any_eq_ne_annotation.rs | 2 +- .../rules/bad_generator_return_type.rs | 2 +- .../rules/bad_version_info_comparison.rs | 2 +- .../flake8_pyi/rules/bytestring_usage.rs | 2 +- .../rules/collections_named_tuple.rs | 2 +- .../rules/complex_assignment_in_stub.rs | 2 +- .../rules/complex_if_statement_in_stub.rs | 2 +- .../rules/custom_type_var_for_self.rs | 4 +-- .../flake8_pyi/rules/docstring_in_stubs.rs | 8 ++--- .../rules/duplicate_literal_member.rs | 2 +- .../rules/duplicate_union_member.rs | 2 +- .../rules/ellipsis_in_non_empty_class_body.rs | 2 +- .../flake8_pyi/rules/exit_annotations.rs | 2 +- .../rules/future_annotations_in_stub.rs | 2 +- .../rules/generic_not_last_base_class.rs | 2 +- .../rules/iter_method_return_iterable.rs | 2 +- .../src/rules/flake8_pyi/rules/mod.rs | 2 +- .../rules/no_return_argument_annotation.rs | 2 +- .../flake8_pyi/rules/non_empty_stub_body.rs | 2 +- .../flake8_pyi/rules/non_self_return_type.rs | 2 +- .../rules/numeric_literal_too_long.rs | 2 +- .../flake8_pyi/rules/pass_in_class_body.rs | 2 +- .../rules/pass_statement_stub_body.rs | 2 +- .../rules/pre_pep570_positional_argument.rs | 2 +- .../flake8_pyi/rules/prefix_type_params.rs | 2 +- .../rules/quoted_annotation_in_stub.rs | 2 +- .../rules/redundant_final_literal.rs | 2 +- .../rules/redundant_literal_union.rs | 2 +- .../rules/redundant_none_literal.rs | 2 +- .../rules/redundant_numeric_union.rs | 2 +- .../rules/flake8_pyi/rules/simple_defaults.rs | 4 +-- .../rules/str_or_repr_defined_in_stub.rs | 2 +- .../rules/string_or_bytes_too_long.rs | 2 +- .../rules/stub_body_multiple_statements.rs | 2 +- .../flake8_pyi/rules/type_alias_naming.rs | 2 +- .../flake8_pyi/rules/type_comment_in_stub.rs | 2 +- .../unaliased_collections_abc_set_import.rs | 3 +- .../rules/unnecessary_literal_union.rs | 2 +- .../rules/unnecessary_type_union.rs | 2 +- .../flake8_pyi/rules/unrecognized_platform.rs | 2 +- .../rules/unrecognized_version_info.rs | 2 +- .../rules/unsupported_method_call_on_all.rs | 2 +- .../rules/unused_private_type_definition.rs | 2 +- .../flake8_pytest_style/rules/assertion.rs | 2 +- .../rules/flake8_pytest_style/rules/fail.rs | 2 +- .../flake8_pytest_style/rules/fixture.rs | 4 +-- .../flake8_pytest_style/rules/imports.rs | 3 +- .../rules/flake8_pytest_style/rules/marks.rs | 2 +- .../flake8_pytest_style/rules/parametrize.rs | 2 +- .../rules/flake8_pytest_style/rules/patch.rs | 2 +- .../rules/flake8_pytest_style/rules/raises.rs | 2 +- .../rules/test_functions.rs | 2 +- .../rules/flake8_pytest_style/rules/warns.rs | 2 +- .../rules/avoidable_escaped_quote.rs | 2 +- .../rules/check_string_quotes.rs | 2 +- .../rules/unnecessary_escaped_quote.rs | 2 +- .../unnecessary_paren_on_raise_exception.rs | 2 +- .../src/rules/flake8_return/rules/function.rs | 4 +-- .../rules/private_member_access.rs | 2 +- .../flake8_simplify/rules/ast_bool_op.rs | 2 +- .../rules/flake8_simplify/rules/ast_expr.rs | 4 +-- .../rules/flake8_simplify/rules/ast_ifexp.rs | 2 +- .../flake8_simplify/rules/ast_unary_op.rs | 2 +- .../rules/flake8_simplify/rules/ast_with.rs | 4 +-- .../flake8_simplify/rules/collapsible_if.rs | 2 +- .../rules/enumerate_for_loop.rs | 2 +- .../rules/flake8_simplify/rules/fix_with.rs | 2 +- .../if_else_block_instead_of_dict_get.rs | 2 +- .../if_else_block_instead_of_dict_lookup.rs | 2 +- .../rules/if_else_block_instead_of_if_exp.rs | 2 +- .../rules/if_with_same_arms.rs | 2 +- .../flake8_simplify/rules/key_in_dict.rs | 4 +-- .../flake8_simplify/rules/needless_bool.rs | 2 +- .../rules/open_file_with_context_handler.rs | 2 +- .../rules/reimplemented_builtin.rs | 2 +- .../rules/return_in_try_except_finally.rs | 2 +- .../rules/split_static_string.rs | 2 +- .../rules/suppressible_exception.rs | 2 +- .../flake8_simplify/rules/yoda_conditions.rs | 2 +- .../rules/zip_dict_keys_and_values.rs | 4 +-- .../rules/no_slots_in_namedtuple_subclass.rs | 2 +- .../rules/no_slots_in_str_subclass.rs | 2 +- .../rules/no_slots_in_tuple_subclass.rs | 2 +- .../flake8_tidy_imports/rules/banned_api.rs | 2 +- .../rules/banned_module_level_imports.rs | 2 +- .../rules/relative_imports.rs | 2 +- .../src/rules/flake8_todos/rules/todos.rs | 2 +- .../src/rules/flake8_type_checking/helpers.rs | 2 +- .../rules/empty_type_checking_block.rs | 2 +- .../rules/runtime_cast_value.rs | 2 +- .../runtime_import_in_type_checking_block.rs | 2 +- .../rules/runtime_string_union.rs | 2 +- .../rules/type_alias_quotes.rs | 2 +- .../rules/typing_only_runtime_import.rs | 2 +- .../rules/unused_arguments.rs | 2 +- .../flake8_use_pathlib/rules/glob_rule.rs | 3 +- .../rules/invalid_pathlib_with_suffix.rs | 2 +- .../rules/os_path_getatime.rs | 3 +- .../rules/os_path_getctime.rs | 3 +- .../rules/os_path_getmtime.rs | 3 +- .../rules/os_path_getsize.rs | 3 +- .../flake8_use_pathlib/rules/os_sep_split.rs | 2 +- .../path_constructor_current_directory.rs | 2 +- .../rules/flake8_use_pathlib/violations.rs | 3 +- .../flynt/rules/static_join_to_fstring.rs | 4 +-- .../rules/isort/rules/add_required_imports.rs | 2 +- .../src/rules/isort/rules/organize_imports.rs | 2 +- .../mccabe/rules/function_is_too_complex.rs | 6 ++-- .../rules/numpy/rules/deprecated_function.rs | 2 +- .../numpy/rules/deprecated_type_alias.rs | 2 +- .../src/rules/numpy/rules/legacy_random.rs | 2 +- .../numpy/rules/numpy_2_0_deprecation.rs | 4 +-- .../pandas_vet/rules/assignment_to_df.rs | 6 ++-- .../src/rules/pandas_vet/rules/attr.rs | 2 +- .../src/rules/pandas_vet/rules/call.rs | 2 +- .../pandas_vet/rules/inplace_argument.rs | 2 +- .../rules/nunique_constant_series_check.rs | 2 +- .../src/rules/pandas_vet/rules/pd_merge.rs | 5 ++-- .../src/rules/pandas_vet/rules/read_table.rs | 2 +- .../src/rules/pandas_vet/rules/subscript.rs | 2 +- .../rules/camelcase_imported_as_acronym.rs | 2 +- .../rules/camelcase_imported_as_constant.rs | 2 +- .../rules/camelcase_imported_as_lowercase.rs | 2 +- .../constant_imported_as_non_constant.rs | 8 ++--- .../pep8_naming/rules/dunder_function_name.rs | 2 +- .../rules/error_suffix_on_exception_name.rs | 5 ++-- .../rules/invalid_argument_name.rs | 2 +- .../pep8_naming/rules/invalid_class_name.rs | 5 ++-- .../rules/invalid_first_argument_name.rs | 8 ++--- .../rules/invalid_function_name.rs | 2 +- .../pep8_naming/rules/invalid_module_name.rs | 7 +++-- .../lowercase_imported_as_non_lowercase.rs | 5 ++-- .../mixed_case_variable_in_class_scope.rs | 2 +- .../mixed_case_variable_in_global_scope.rs | 2 +- .../non_lowercase_variable_in_function.rs | 2 +- .../perflint/rules/incorrect_dict_iterator.rs | 2 +- .../rules/manual_dict_comprehension.rs | 2 +- .../rules/manual_list_comprehension.rs | 2 +- .../rules/perflint/rules/manual_list_copy.rs | 2 +- .../perflint/rules/try_except_in_loop.rs | 5 ++-- .../perflint/rules/unnecessary_list_cast.rs | 2 +- .../pycodestyle/rules/ambiguous_class_name.rs | 5 ++-- .../rules/ambiguous_function_name.rs | 5 ++-- .../rules/ambiguous_variable_name.rs | 2 +- .../rules/pycodestyle/rules/bare_except.rs | 2 +- .../rules/pycodestyle/rules/blank_lines.rs | 8 ++--- .../pycodestyle/rules/compound_statements.rs | 4 +-- .../pycodestyle/rules/doc_line_too_long.rs | 2 +- .../src/rules/pycodestyle/rules/errors.rs | 3 +- .../rules/invalid_escape_sequence.rs | 2 +- .../pycodestyle/rules/lambda_assignment.rs | 2 +- .../rules/pycodestyle/rules/line_too_long.rs | 2 +- .../pycodestyle/rules/literal_comparisons.rs | 2 +- .../logical_lines/extraneous_whitespace.rs | 8 ++--- .../rules/logical_lines/indentation.rs | 5 ++-- .../rules/logical_lines/missing_whitespace.rs | 4 +-- .../missing_whitespace_after_keyword.rs | 2 +- .../missing_whitespace_around_operator.rs | 2 +- .../logical_lines/redundant_backslash.rs | 2 +- .../logical_lines/space_around_operator.rs | 2 +- .../whitespace_around_keywords.rs | 2 +- ...hitespace_around_named_parameter_equals.rs | 2 +- .../whitespace_before_comment.rs | 2 +- .../whitespace_before_parameters.rs | 2 +- .../rules/missing_newline_at_end_of_file.rs | 2 +- .../rules/mixed_spaces_and_tabs.rs | 3 +- .../rules/module_import_not_at_top_of_file.rs | 2 +- .../rules/multiple_imports_on_one_line.rs | 2 +- .../src/rules/pycodestyle/rules/not_tests.rs | 4 +-- .../pycodestyle/rules/tab_indentation.rs | 2 +- .../rules/too_many_newlines_at_end_of_file.rs | 3 +- .../pycodestyle/rules/trailing_whitespace.rs | 2 +- .../pycodestyle/rules/type_comparison.rs | 2 +- .../rules/whitespace_after_decorator.rs | 2 +- .../rules/pydoclint/rules/check_docstring.rs | 2 +- .../src/rules/pydocstyle/rules/backslashes.rs | 2 +- .../pydocstyle/rules/blank_after_summary.rs | 2 +- .../rules/blank_before_after_class.rs | 2 +- .../rules/blank_before_after_function.rs | 2 +- .../src/rules/pydocstyle/rules/capitalized.rs | 2 +- .../pydocstyle/rules/ends_with_period.rs | 2 +- .../pydocstyle/rules/ends_with_punctuation.rs | 2 +- .../src/rules/pydocstyle/rules/if_needed.rs | 2 +- .../src/rules/pydocstyle/rules/indent.rs | 4 +-- .../rules/multi_line_summary_start.rs | 2 +- .../rules/newline_after_last_paragraph.rs | 2 +- .../rules/pydocstyle/rules/no_signature.rs | 2 +- .../rules/no_surrounding_whitespace.rs | 2 +- .../pydocstyle/rules/non_imperative_mood.rs | 2 +- .../src/rules/pydocstyle/rules/not_empty.rs | 2 +- .../src/rules/pydocstyle/rules/not_missing.rs | 2 +- .../src/rules/pydocstyle/rules/one_liner.rs | 2 +- .../src/rules/pydocstyle/rules/sections.rs | 4 +-- .../pydocstyle/rules/starts_with_this.rs | 2 +- .../rules/pydocstyle/rules/triple_quotes.rs | 2 +- .../ruff_linter/src/rules/pyflakes/fixes.rs | 2 +- .../src/rules/pyflakes/rules/assert_tuple.rs | 2 +- .../pyflakes/rules/break_outside_loop.rs | 3 +- .../pyflakes/rules/continue_outside_loop.rs | 3 +- .../pyflakes/rules/default_except_not_last.rs | 2 +- .../rules/f_string_missing_placeholders.rs | 2 +- .../rules/forward_annotation_syntax_error.rs | 3 +- .../rules/future_feature_not_defined.rs | 2 +- .../src/rules/pyflakes/rules/if_tuple.rs | 2 +- .../src/rules/pyflakes/rules/imports.rs | 3 +- .../rules/invalid_literal_comparisons.rs | 2 +- .../pyflakes/rules/invalid_print_syntax.rs | 2 +- .../pyflakes/rules/raise_not_implemented.rs | 2 +- .../pyflakes/rules/redefined_while_unused.rs | 3 +- .../src/rules/pyflakes/rules/repeated_keys.rs | 2 +- .../pyflakes/rules/return_outside_function.rs | 3 +- .../pyflakes/rules/starred_expressions.rs | 3 +- .../src/rules/pyflakes/rules/strings.rs | 2 +- .../rules/pyflakes/rules/undefined_export.rs | 3 +- .../rules/pyflakes/rules/undefined_local.rs | 2 +- .../rules/pyflakes/rules/undefined_name.rs | 3 +- .../rules/pyflakes/rules/unused_annotation.rs | 2 +- .../src/rules/pyflakes/rules/unused_import.rs | 2 +- .../rules/pyflakes/rules/unused_variable.rs | 2 +- .../pyflakes/rules/yield_outside_function.rs | 3 +- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 2 +- .../pygrep_hooks/rules/blanket_type_ignore.rs | 2 +- .../pygrep_hooks/rules/deprecated_log_warn.rs | 3 +- .../pygrep_hooks/rules/invalid_mock_access.rs | 2 +- .../src/rules/pygrep_hooks/rules/no_eval.rs | 3 +- .../src/rules/pylint/rules/and_or_ternary.rs | 3 +- .../pylint/rules/assert_on_string_literal.rs | 2 +- .../rules/pylint/rules/await_outside_async.rs | 3 +- .../pylint/rules/bad_dunder_method_name.rs | 2 +- .../src/rules/pylint/rules/bad_open_mode.rs | 2 +- .../pylint/rules/bad_staticmethod_argument.rs | 2 +- .../rules/pylint/rules/bad_str_strip_call.rs | 2 +- .../rules/bad_string_format_character.rs | 2 +- .../pylint/rules/bad_string_format_type.rs | 2 +- .../pylint/rules/bidirectional_unicode.rs | 3 +- .../rules/pylint/rules/binary_op_exception.rs | 2 +- .../rules/boolean_chained_comparison.rs | 2 +- .../rules/pylint/rules/collapsible_else_if.rs | 2 +- .../pylint/rules/compare_to_empty_string.rs | 2 +- .../pylint/rules/comparison_of_constant.rs | 2 +- .../pylint/rules/comparison_with_itself.rs | 4 +-- .../rules/pylint/rules/continue_in_finally.rs | 2 +- .../pylint/rules/dict_index_missing_items.rs | 2 +- .../pylint/rules/dict_iter_missing_items.rs | 2 +- .../src/rules/pylint/rules/duplicate_bases.rs | 2 +- .../src/rules/pylint/rules/empty_comment.rs | 2 +- .../src/rules/pylint/rules/eq_without_hash.rs | 2 +- .../pylint/rules/global_at_module_level.rs | 2 +- .../rules/pylint/rules/global_statement.rs | 2 +- .../rules/global_variable_not_assigned.rs | 3 +- .../src/rules/pylint/rules/if_stmt_min_max.rs | 2 +- .../pylint/rules/import_outside_top_level.rs | 2 +- .../rules/pylint/rules/import_private_name.rs | 2 +- .../src/rules/pylint/rules/import_self.rs | 3 +- .../rules/pylint/rules/invalid_all_format.rs | 3 +- .../rules/pylint/rules/invalid_all_object.rs | 3 +- .../rules/pylint/rules/invalid_bool_return.rs | 2 +- .../pylint/rules/invalid_bytes_return.rs | 2 +- .../pylint/rules/invalid_envvar_default.rs | 2 +- .../pylint/rules/invalid_envvar_value.rs | 2 +- .../rules/pylint/rules/invalid_hash_return.rs | 2 +- .../pylint/rules/invalid_index_return.rs | 2 +- .../pylint/rules/invalid_length_return.rs | 2 +- .../rules/pylint/rules/invalid_str_return.rs | 2 +- .../pylint/rules/invalid_string_characters.rs | 2 +- .../rules/pylint/rules/iteration_over_set.rs | 2 +- .../src/rules/pylint/rules/len_test.rs | 7 +++-- .../rules/pylint/rules/literal_membership.rs | 2 +- .../rules/load_before_global_declaration.rs | 3 +- .../src/rules/pylint/rules/logging.rs | 2 +- .../pylint/rules/magic_value_comparison.rs | 2 +- .../rules/pylint/rules/manual_import_from.rs | 2 +- .../pylint/rules/misplaced_bare_raise.rs | 2 +- .../pylint/rules/modified_iterating_set.rs | 2 +- .../rules/named_expr_without_context.rs | 2 +- .../src/rules/pylint/rules/nan_comparison.rs | 2 +- .../src/rules/pylint/rules/nested_min_max.rs | 2 +- .../rules/pylint/rules/no_method_decorator.rs | 2 +- .../src/rules/pylint/rules/no_self_use.rs | 2 +- .../pylint/rules/non_ascii_module_import.rs | 2 +- .../src/rules/pylint/rules/non_ascii_name.rs | 2 +- .../pylint/rules/non_augmented_assignment.rs | 2 +- .../rules/pylint/rules/non_slot_assignment.rs | 2 +- .../rules/pylint/rules/nonlocal_and_global.rs | 2 +- .../pylint/rules/nonlocal_without_binding.rs | 3 +- .../pylint/rules/potential_index_error.rs | 2 +- .../pylint/rules/property_with_parameters.rs | 2 +- .../pylint/rules/redeclared_assigned_name.rs | 2 +- .../rules/redefined_argument_from_local.rs | 3 +- .../rules/pylint/rules/redefined_loop_name.rs | 2 +- .../rules/redefined_slots_in_subclass.rs | 2 +- .../rules/repeated_equality_comparison.rs | 2 +- .../pylint/rules/repeated_isinstance_calls.rs | 2 +- .../pylint/rules/repeated_keyword_argument.rs | 2 +- .../src/rules/pylint/rules/return_in_init.rs | 2 +- .../pylint/rules/self_assigning_variable.rs | 2 +- .../pylint/rules/self_or_cls_assignment.rs | 2 +- .../pylint/rules/shallow_copy_environ.rs | 2 +- .../rules/pylint/rules/single_string_slots.rs | 2 +- .../pylint/rules/singledispatch_method.rs | 2 +- .../rules/singledispatchmethod_function.rs | 2 +- .../rules/subprocess_popen_preexec_fn.rs | 2 +- .../rules/subprocess_run_without_check.rs | 2 +- .../pylint/rules/super_without_brackets.rs | 2 +- .../src/rules/pylint/rules/sys_exit_alias.rs | 8 ++--- .../rules/pylint/rules/too_many_arguments.rs | 2 +- .../rules/too_many_boolean_expressions.rs | 2 +- .../rules/pylint/rules/too_many_branches.rs | 6 ++-- .../src/rules/pylint/rules/too_many_locals.rs | 2 +- .../pylint/rules/too_many_nested_blocks.rs | 2 +- .../rules/too_many_positional_arguments.rs | 2 +- .../pylint/rules/too_many_public_methods.rs | 2 +- .../rules/too_many_return_statements.rs | 6 ++-- .../rules/pylint/rules/too_many_statements.rs | 6 ++-- .../src/rules/pylint/rules/type_bivariance.rs | 2 +- .../rules/type_name_incorrect_variance.rs | 2 +- .../pylint/rules/type_param_name_mismatch.rs | 2 +- .../unexpected_special_method_signature.rs | 2 +- .../rules/unnecessary_dict_index_lookup.rs | 2 +- .../rules/unnecessary_direct_lambda_call.rs | 2 +- .../pylint/rules/unnecessary_dunder_call.rs | 2 +- .../rules/pylint/rules/unnecessary_lambda.rs | 2 +- .../rules/unnecessary_list_index_lookup.rs | 2 +- .../src/rules/pylint/rules/unreachable.rs | 2 +- .../pylint/rules/unspecified_encoding.rs | 2 +- .../pylint/rules/useless_else_on_loop.rs | 2 +- .../rules/useless_exception_statement.rs | 2 +- .../pylint/rules/useless_import_alias.rs | 2 +- .../src/rules/pylint/rules/useless_return.rs | 2 +- .../rules/pylint/rules/useless_with_lock.rs | 2 +- .../rules/yield_from_in_async_function.rs | 2 +- .../src/rules/pylint/rules/yield_in_init.rs | 2 +- ...convert_named_tuple_functional_to_class.rs | 2 +- .../convert_typed_dict_functional_to_class.rs | 2 +- .../pyupgrade/rules/datetime_utc_alias.rs | 2 +- .../rules/deprecated_c_element_tree.rs | 2 +- .../pyupgrade/rules/deprecated_import.rs | 2 +- .../pyupgrade/rules/deprecated_mock_import.rs | 2 +- .../rules/deprecated_unittest_alias.rs | 2 +- .../pyupgrade/rules/extraneous_parentheses.rs | 2 +- .../src/rules/pyupgrade/rules/f_strings.rs | 2 +- .../rules/pyupgrade/rules/format_literals.rs | 2 +- .../rules/lru_cache_with_maxsize_none.rs | 2 +- .../rules/lru_cache_without_parameters.rs | 2 +- .../rules/pyupgrade/rules/native_literals.rs | 2 +- .../pyupgrade/rules/non_pep646_unpack.rs | 2 +- .../src/rules/pyupgrade/rules/open_alias.rs | 2 +- .../rules/pyupgrade/rules/os_error_alias.rs | 4 +-- .../pyupgrade/rules/outdated_version_block.rs | 2 +- .../rules/pep695/non_pep695_generic_class.rs | 2 +- .../pep695/non_pep695_generic_function.rs | 2 +- .../rules/pep695/non_pep695_type_alias.rs | 2 +- .../rules/pep695/private_type_parameter.rs | 2 +- .../rules/printf_string_formatting.rs | 2 +- .../pyupgrade/rules/quoted_annotation.rs | 10 +++---- .../pyupgrade/rules/redundant_open_modes.rs | 2 +- .../pyupgrade/rules/replace_stdout_stderr.rs | 2 +- .../rules/pyupgrade/rules/replace_str_enum.rs | 2 +- .../rules/replace_universal_newlines.rs | 2 +- .../rules/super_call_with_parameters.rs | 2 +- .../pyupgrade/rules/timeout_error_alias.rs | 6 ++-- .../pyupgrade/rules/type_of_primitive.rs | 7 ++--- .../pyupgrade/rules/typing_text_str_alias.rs | 2 +- .../pyupgrade/rules/unicode_kind_prefix.rs | 2 +- .../rules/unnecessary_builtin_import.rs | 2 +- .../rules/unnecessary_class_parentheses.rs | 2 +- .../rules/unnecessary_coding_comment.rs | 2 +- .../rules/unnecessary_default_type_args.rs | 2 +- .../rules/unnecessary_encode_utf8.rs | 2 +- .../rules/unnecessary_future_import.rs | 2 +- .../rules/unpacked_list_comprehension.rs | 3 +- .../pyupgrade/rules/use_pep585_annotation.rs | 2 +- .../pyupgrade/rules/use_pep604_annotation.rs | 2 +- .../pyupgrade/rules/use_pep604_isinstance.rs | 2 +- .../rules/useless_class_metaclass_type.rs | 2 +- .../pyupgrade/rules/useless_metaclass_type.rs | 2 +- .../rules/useless_object_inheritance.rs | 2 +- .../pyupgrade/rules/yield_in_for_loop.rs | 2 +- .../ruff_linter/src/rules/refurb/helpers.rs | 2 +- .../src/rules/refurb/rules/bit_count.rs | 2 +- .../refurb/rules/check_and_remove_from_set.rs | 2 +- .../rules/refurb/rules/delete_full_slice.rs | 2 +- .../refurb/rules/for_loop_set_mutations.rs | 2 +- .../src/rules/refurb/rules/for_loop_writes.rs | 2 +- .../refurb/rules/fromisoformat_replace_z.rs | 2 +- .../refurb/rules/fstring_number_format.rs | 2 +- .../refurb/rules/hardcoded_string_charset.rs | 7 +++-- .../rules/refurb/rules/hashlib_digest_hex.rs | 2 +- .../rules/if_exp_instead_of_or_operator.rs | 2 +- .../src/rules/refurb/rules/if_expr_min_max.rs | 2 +- .../src/rules/refurb/rules/implicit_cwd.rs | 2 +- .../rules/refurb/rules/int_on_sliced_str.rs | 2 +- .../refurb/rules/isinstance_type_none.rs | 2 +- .../rules/refurb/rules/list_reverse_copy.rs | 2 +- .../src/rules/refurb/rules/math_constant.rs | 2 +- .../rules/refurb/rules/metaclass_abcmeta.rs | 2 +- .../rules/refurb/rules/print_empty_string.rs | 2 +- .../src/rules/refurb/rules/read_whole_file.rs | 2 +- .../rules/refurb/rules/readlines_in_for.rs | 4 +-- .../rules/refurb/rules/redundant_log_base.rs | 2 +- .../rules/refurb/rules/regex_flag_alias.rs | 2 +- .../refurb/rules/reimplemented_operator.rs | 2 +- .../refurb/rules/reimplemented_starmap.rs | 2 +- .../src/rules/refurb/rules/repeated_append.rs | 2 +- .../src/rules/refurb/rules/repeated_global.rs | 2 +- .../rules/single_item_membership_test.rs | 2 +- .../src/rules/refurb/rules/slice_copy.rs | 2 +- .../rules/slice_to_remove_prefix_or_suffix.rs | 2 +- .../src/rules/refurb/rules/sorted_min_max.rs | 8 ++--- .../rules/refurb/rules/subclass_builtin.rs | 2 +- .../refurb/rules/type_none_comparison.rs | 2 +- .../refurb/rules/unnecessary_enumerate.rs | 2 +- .../refurb/rules/unnecessary_from_float.rs | 2 +- .../rules/verbose_decimal_constructor.rs | 2 +- .../rules/refurb/rules/write_whole_file.rs | 2 +- .../ruff/rules/ambiguous_unicode_character.rs | 2 +- .../ruff/rules/assert_with_print_message.rs | 2 +- .../rules/ruff/rules/assignment_in_assert.rs | 2 +- .../rules/ruff/rules/asyncio_dangling_task.rs | 2 +- .../ruff/rules/class_with_mixed_type_vars.rs | 2 +- .../rules/collection_literal_concatenation.rs | 2 +- .../src/rules/ruff/rules/dataclass_enum.rs | 2 +- .../ruff/rules/decimal_from_float_literal.rs | 2 +- .../rules/ruff/rules/default_factory_kwarg.rs | 2 +- .../explicit_f_string_type_conversion.rs | 2 +- .../ruff/rules/falsy_dict_get_fallback.rs | 7 +++-- .../function_call_in_dataclass_default.rs | 2 +- .../rules/ruff/rules/if_key_in_dict_del.rs | 5 ++-- .../rules/implicit_classvar_in_dataclass.rs | 2 +- .../src/rules/ruff/rules/implicit_optional.rs | 2 +- .../rules/ruff/rules/in_empty_collection.rs | 2 +- ...rectly_parenthesized_tuple_in_subscript.rs | 2 +- .../rules/ruff/rules/indented_form_feed.rs | 3 +- ...invalid_assert_message_literal_argument.rs | 2 +- .../invalid_formatter_suppression_comment.rs | 2 +- .../rules/ruff/rules/invalid_index_type.rs | 2 +- .../ruff/rules/invalid_pyproject_toml.rs | 3 +- .../src/rules/ruff/rules/invalid_rule_code.rs | 8 ++--- .../ruff/rules/map_int_version_parsing.rs | 2 +- .../ruff/rules/missing_fstring_syntax.rs | 2 +- .../rules/ruff/rules/mutable_class_default.rs | 2 +- .../ruff/rules/mutable_dataclass_default.rs | 2 +- .../ruff/rules/mutable_fromkeys_value.rs | 2 +- .../src/rules/ruff/rules/needless_else.rs | 2 +- .../src/rules/ruff/rules/never_union.rs | 2 +- .../ruff/rules/none_not_at_end_of_union.rs | 2 +- .../rules/parenthesize_chained_operators.rs | 2 +- .../src/rules/ruff/rules/post_init_default.rs | 2 +- .../rules/pytest_raises_ambiguous_pattern.rs | 7 +++-- .../ruff/rules/quadratic_list_summation.rs | 2 +- .../src/rules/ruff/rules/redirected_noqa.rs | 2 +- .../ruff/rules/redundant_bool_literal.rs | 2 +- .../src/rules/ruff/rules/sort_dunder_all.rs | 2 +- .../src/rules/ruff/rules/sort_dunder_slots.rs | 2 +- .../src/rules/ruff/rules/starmap_zip.rs | 5 ++-- .../rules/static_key_dict_comprehension.rs | 3 +- .../src/rules/ruff/rules/test_rules.rs | 2 +- .../ruff/rules/unnecessary_cast_to_int.rs | 2 +- ...y_iterable_allocation_for_first_element.rs | 2 +- .../rules/ruff/rules/unnecessary_key_check.rs | 2 +- .../unnecessary_literal_within_deque_call.rs | 5 ++-- .../ruff/rules/unnecessary_nested_literal.rs | 2 +- .../rules/unnecessary_regular_expression.rs | 2 +- .../src/rules/ruff/rules/unnecessary_round.rs | 7 +++-- .../src/rules/ruff/rules/unraw_re_pattern.rs | 2 +- .../src/rules/ruff/rules/unsafe_markup_use.rs | 3 +- .../src/rules/ruff/rules/unused_async.rs | 2 +- .../src/rules/ruff/rules/unused_noqa.rs | 3 +- .../ruff/rules/unused_unpacked_variable.rs | 2 +- .../rules/ruff/rules/used_dummy_variable.rs | 2 +- .../src/rules/ruff/rules/useless_if_else.rs | 5 ++-- .../ruff/rules/zip_instead_of_pairwise.rs | 2 +- .../rules/error_instead_of_exception.rs | 2 +- .../tryceratops/rules/raise_vanilla_args.rs | 2 +- .../tryceratops/rules/raise_vanilla_class.rs | 2 +- .../tryceratops/rules/raise_within_try.rs | 2 +- .../tryceratops/rules/reraise_no_cause.rs | 3 +- .../tryceratops/rules/try_consider_else.rs | 2 +- .../rules/type_check_without_type_error.rs | 2 +- .../tryceratops/rules/useless_try_except.rs | 2 +- .../tryceratops/rules/verbose_log_message.rs | 2 +- .../rules/tryceratops/rules/verbose_raise.rs | 2 +- .../src/settings/fix_safety_table.rs | 2 +- crates/ruff_linter/src/settings/types.rs | 2 +- crates/ruff_linter/src/test.rs | 2 +- .../src/violation.rs | 6 ++-- crates/ruff_macros/src/map_codes.rs | 22 +++++--------- crates/ruff_macros/src/violation_metadata.rs | 6 ++-- .../src/server/api/requests/hover.rs | 2 +- scripts/add_rule.py | 2 +- 709 files changed, 931 insertions(+), 888 deletions(-) rename crates/{ruff_diagnostics => ruff_linter}/src/diagnostic.rs (78%) rename crates/{ruff_diagnostics => ruff_linter}/src/violation.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index 206a24a6a21fc0..2b1f0f82c5b31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2642,7 +2642,6 @@ dependencies = [ "rayon", "regex", "ruff", - "ruff_diagnostics", "ruff_formatter", "ruff_linter", "ruff_notebook", @@ -2672,9 +2671,7 @@ dependencies = [ name = "ruff_diagnostics" version = "0.0.0" dependencies = [ - "anyhow", "is-macro", - "log", "ruff_text_size", "serde", ] diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 5c3ccf81e7c30b..4c0f11392f96ba 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -349,7 +349,6 @@ impl FileCache { .iter() .map(|msg| { Message::diagnostic( - msg.rule.into(), msg.body.clone(), msg.suggestion.clone(), msg.range, @@ -357,6 +356,7 @@ impl FileCache { msg.parent, file.clone(), msg.noqa_offset, + msg.rule, ) }) .collect() diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index 0b1a223ffc8af3..801df01352c2ff 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -12,7 +12,7 @@ use rayon::prelude::*; use rustc_hash::FxHashMap; use ruff_db::panic::catch_unwind; -use ruff_diagnostics::Diagnostic; +use ruff_linter::Diagnostic; use ruff_linter::message::Message; use ruff_linter::package::PackageRoot; use ruff_linter::registry::Rule; diff --git a/crates/ruff/src/commands/rule.rs b/crates/ruff/src/commands/rule.rs index 271a0c4b1c81fb..45b071d2ea1b76 100644 --- a/crates/ruff/src/commands/rule.rs +++ b/crates/ruff/src/commands/rule.rs @@ -6,7 +6,7 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use strum::IntoEnumIterator; -use ruff_diagnostics::FixAvailability; +use ruff_linter::FixAvailability; use ruff_linter::registry::{Linter, Rule, RuleNamespace}; use crate::args::HelpFormat; diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index 6b88d7ce36c424..b656de6a5dd575 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -12,7 +12,7 @@ use colored::Colorize; use log::{debug, warn}; use rustc_hash::FxHashMap; -use ruff_diagnostics::Diagnostic; +use ruff_linter::Diagnostic; use ruff_linter::codes::Rule; use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only}; use ruff_linter::message::Message; diff --git a/crates/ruff_dev/Cargo.toml b/crates/ruff_dev/Cargo.toml index 1e44acd57390ff..99adef596b6186 100644 --- a/crates/ruff_dev/Cargo.toml +++ b/crates/ruff_dev/Cargo.toml @@ -14,7 +14,6 @@ license = { workspace = true } ty = { workspace = true } ty_project = { workspace = true, features = ["schemars"] } ruff = { workspace = true } -ruff_diagnostics = { workspace = true } ruff_formatter = { workspace = true } ruff_linter = { workspace = true, features = ["schemars"] } ruff_notebook = { workspace = true } diff --git a/crates/ruff_dev/src/generate_docs.rs b/crates/ruff_dev/src/generate_docs.rs index e422f498ee2d54..d191e826491efd 100644 --- a/crates/ruff_dev/src/generate_docs.rs +++ b/crates/ruff_dev/src/generate_docs.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use regex::{Captures, Regex}; use strum::IntoEnumIterator; -use ruff_diagnostics::FixAvailability; +use ruff_linter::FixAvailability; use ruff_linter::registry::{Linter, Rule, RuleNamespace}; use ruff_options_metadata::{OptionEntry, OptionsMetadata}; use ruff_workspace::options::Options; diff --git a/crates/ruff_dev/src/generate_rules_table.rs b/crates/ruff_dev/src/generate_rules_table.rs index 21b6aec0de63cc..48b1cdc2c9569d 100644 --- a/crates/ruff_dev/src/generate_rules_table.rs +++ b/crates/ruff_dev/src/generate_rules_table.rs @@ -8,7 +8,7 @@ use std::borrow::Cow; use std::fmt::Write; use strum::IntoEnumIterator; -use ruff_diagnostics::FixAvailability; +use ruff_linter::FixAvailability; use ruff_linter::registry::{Linter, Rule, RuleNamespace}; use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix; use ruff_options_metadata::OptionsMetadata; diff --git a/crates/ruff_diagnostics/Cargo.toml b/crates/ruff_diagnostics/Cargo.toml index 3bc24b1dbcfc5e..7fc5865b1e8d51 100644 --- a/crates/ruff_diagnostics/Cargo.toml +++ b/crates/ruff_diagnostics/Cargo.toml @@ -16,7 +16,5 @@ doctest = false [dependencies] ruff_text_size = { workspace = true } -anyhow = { workspace = true } -log = { workspace = true } is-macro = { workspace = true } serde = { workspace = true, optional = true, features = [] } diff --git a/crates/ruff_diagnostics/src/lib.rs b/crates/ruff_diagnostics/src/lib.rs index fd97fc14084722..0fd267c5d799b0 100644 --- a/crates/ruff_diagnostics/src/lib.rs +++ b/crates/ruff_diagnostics/src/lib.rs @@ -1,11 +1,7 @@ -pub use diagnostic::Diagnostic; pub use edit::Edit; pub use fix::{Applicability, Fix, IsolationLevel}; pub use source_map::{SourceMap, SourceMarker}; -pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata}; -mod diagnostic; mod edit; mod fix; mod source_map; -mod violation; diff --git a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs index f2f8147cc9255f..59b1757f66dffc 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs @@ -1,6 +1,6 @@ -use ruff_diagnostics::Fix; use ruff_text_size::Ranged; +use crate::Fix; use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::rules::{ diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 72ef6bde280f68..338c0c76578ded 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Fix; use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind}; use ruff_text_size::Ranged; use rustc_hash::FxHashMap; +use crate::Fix; use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::fix; diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 92783663c03904..b95e7538e28748 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -26,12 +26,9 @@ use std::path::Path; use itertools::Itertools; use log::debug; -use ruff_python_parser::semantic_errors::{ - SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind, -}; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{Diagnostic, Edit, IsolationLevel, Violation}; +use ruff_diagnostics::IsolationLevel; use ruff_notebook::{CellOffsets, NotebookIndex}; use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path}; use ruff_python_ast::identifier::Identifier; @@ -46,6 +43,9 @@ use ruff_python_ast::{ use ruff_python_ast::{PySourceType, helpers, str, visitor}; use ruff_python_codegen::{Generator, Stylist}; use ruff_python_index::Indexer; +use ruff_python_parser::semantic_errors::{ + SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind, +}; use ruff_python_parser::typing::{AnnotationKind, ParsedAnnotation, parse_type_annotation}; use ruff_python_parser::{ParseError, Parsed, Tokens}; use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags}; @@ -73,6 +73,7 @@ use crate::rules::pyflakes::rules::{ use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration}; use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; use crate::settings::{LinterSettings, TargetVersion, flags}; +use crate::{Diagnostic, Edit, Violation}; use crate::{Locator, docstrings, noqa}; mod analyze; diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index a92088196c0c1e..ca73348ccafb8c 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -1,9 +1,9 @@ use std::path::Path; -use ruff_diagnostics::Diagnostic; use ruff_python_ast::PythonVersion; use ruff_python_trivia::CommentRanges; +use crate::Diagnostic; use crate::Locator; use crate::package::PackageRoot; use crate::preview::is_allow_nested_roots_enabled; diff --git a/crates/ruff_linter/src/checkers/imports.rs b/crates/ruff_linter/src/checkers/imports.rs index 7ce9a527c3719d..28628f40f880c7 100644 --- a/crates/ruff_linter/src/checkers/imports.rs +++ b/crates/ruff_linter/src/checkers/imports.rs @@ -1,6 +1,5 @@ //! Lint rules based on import analysis. -use ruff_diagnostics::Diagnostic; use ruff_notebook::CellOffsets; use ruff_python_ast::statement_visitor::StatementVisitor; use ruff_python_ast::{ModModule, PySourceType, PythonVersion}; @@ -8,6 +7,7 @@ use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Parsed; +use crate::Diagnostic; use crate::Locator; use crate::directives::IsortDirectives; use crate::package::PackageRoot; diff --git a/crates/ruff_linter/src/checkers/logical_lines.rs b/crates/ruff_linter/src/checkers/logical_lines.rs index a17302f3b09651..83eac38858dd26 100644 --- a/crates/ruff_linter/src/checkers/logical_lines.rs +++ b/crates/ruff_linter/src/checkers/logical_lines.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Diagnostic; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; +use crate::Diagnostic; use crate::Locator; use crate::line_width::IndentWidth; use crate::registry::{AsRule, Rule}; diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 5e93b192d4c3cd..35f79a69fedbf1 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -5,7 +5,6 @@ use std::path::Path; use itertools::Itertools; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_python_trivia::CommentRanges; use ruff_text_size::Ranged; @@ -21,6 +20,7 @@ use crate::rules::pygrep_hooks; use crate::rules::ruff; use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA}; use crate::settings::LinterSettings; +use crate::{Diagnostic, Edit, Fix}; #[expect(clippy::too_many_arguments)] pub(crate) fn check_noqa( diff --git a/crates/ruff_linter/src/checkers/physical_lines.rs b/crates/ruff_linter/src/checkers/physical_lines.rs index c2f45a7a1a00e1..0edf20a520bffc 100644 --- a/crates/ruff_linter/src/checkers/physical_lines.rs +++ b/crates/ruff_linter/src/checkers/physical_lines.rs @@ -1,11 +1,11 @@ //! Lint rules based on checking physical lines. -use ruff_diagnostics::Diagnostic; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_source_file::UniversalNewlines; use ruff_text_size::TextSize; +use crate::Diagnostic; use crate::Locator; use crate::registry::Rule; use crate::rules::flake8_copyright::rules::missing_copyright_notice; diff --git a/crates/ruff_linter/src/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index 99d8807fe33b2e..19985985a8923e 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -2,13 +2,13 @@ use std::path::Path; -use ruff_diagnostics::Diagnostic; use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Tokens; +use crate::Diagnostic; use crate::Locator; use crate::directives::TodoComment; use crate::registry::{AsRule, Rule}; diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 2ab35f2f16c141..4051d69b2e8de0 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -6,7 +6,7 @@ use std::fmt::Formatter; use strum_macros::{AsRefStr, EnumIter}; -use crate::registry::{AsRule, Linter}; +use crate::registry::Linter; use crate::rule_selector::is_single_rule_selector; use crate::rules; @@ -1156,3 +1156,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { _ => return None, }) } + +impl std::fmt::Display for Rule { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str(self.into()) + } +} diff --git a/crates/ruff_diagnostics/src/diagnostic.rs b/crates/ruff_linter/src/diagnostic.rs similarity index 78% rename from crates/ruff_diagnostics/src/diagnostic.rs rename to crates/ruff_linter/src/diagnostic.rs index f9a93c78f140ae..5bca7eb72e04da 100644 --- a/crates/ruff_diagnostics/src/diagnostic.rs +++ b/crates/ruff_linter/src/diagnostic.rs @@ -3,12 +3,12 @@ use log::debug; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::{Fix, Violation}; +use crate::registry::AsRule; +use crate::violation::Violation; +use crate::{Fix, codes::Rule}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct Diagnostic { - /// The identifier of the diagnostic, used to align the diagnostic with a rule. - pub name: &'static str, /// The message body to display to the user, to explain the diagnostic. pub body: String, /// The message to display to the user, to explain the suggested fix. @@ -16,17 +16,24 @@ pub struct Diagnostic { pub range: TextRange, pub fix: Option, pub parent: Option, + + pub(crate) rule: Rule, } impl Diagnostic { + // TODO(brent) We temporarily allow this to avoid updating all of the call sites to add + // references. I expect this method to go away or change significantly with the rest of the + // diagnostic refactor, but if it still exists in this form at the end of the refactor, we + // should just update the call sites. + #[expect(clippy::needless_pass_by_value)] pub fn new(kind: T, range: TextRange) -> Self { Self { - name: T::rule_name(), body: Violation::message(&kind), suggestion: Violation::fix_title(&kind), range, fix: None, parent: None, + rule: T::rule(), } } @@ -50,7 +57,7 @@ impl Diagnostic { pub fn try_set_fix(&mut self, func: impl FnOnce() -> Result) { match func() { Ok(fix) => self.fix = Some(fix), - Err(err) => debug!("Failed to create fix for {}: {}", self.name, err), + Err(err) => debug!("Failed to create fix for {}: {}", self.rule, err), } } @@ -61,7 +68,7 @@ impl Diagnostic { match func() { Ok(None) => {} Ok(Some(fix)) => self.fix = Some(fix), - Err(err) => debug!("Failed to create fix for {}: {}", self.name, err), + Err(err) => debug!("Failed to create fix for {}: {}", self.rule, err), } } @@ -80,6 +87,12 @@ impl Diagnostic { } } +impl AsRule for Diagnostic { + fn rule(&self) -> Rule { + self.rule + } +} + impl Ranged for Diagnostic { fn range(&self) -> TextRange { self.range diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 6c6a36f9d7bc4b..4f2866205ea093 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -2,7 +2,6 @@ use anyhow::{Context, Result}; -use ruff_diagnostics::Edit; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt}; use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; @@ -16,6 +15,7 @@ use ruff_python_trivia::{ use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use crate::Edit; use crate::Locator; use crate::cst::matchers::{match_function_def, match_indented_block, match_statement}; use crate::fix::codemods; @@ -595,18 +595,19 @@ mod tests { use ruff_source_file::SourceFileBuilder; use test_case::test_case; - use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_python_ast::Stmt; use ruff_python_codegen::Stylist; use ruff_python_parser::{parse_expression, parse_module}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; + use crate::codes::Rule; use crate::fix::apply_fixes; use crate::fix::edits::{ add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; use crate::message::Message; + use crate::{Diagnostic, Edit, Fix}; /// Parse the given source using [`Mode::Module`] and return the first statement. fn parse_first_stmt(source: &str) -> Result { @@ -746,7 +747,6 @@ x = 1 \ iter, )); Message::diagnostic( - diag.name, diag.body, diag.suggestion, diag.range, @@ -754,6 +754,7 @@ x = 1 \ diag.parent, SourceFileBuilder::new("", "").finish(), None, + Rule::MissingNewlineAtEndOfFile, ) }; assert_eq!(apply_fixes([diag].iter(), &locator).code, expect); diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index e5dfdb64b93bb6..b61a8b80edde45 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{Edit, Fix, IsolationLevel, SourceMap}; +use ruff_diagnostics::{IsolationLevel, SourceMap}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; @@ -11,6 +11,7 @@ use crate::linter::FixTable; use crate::message::Message; use crate::registry::Rule; use crate::settings::types::UnsafeFixes; +use crate::{Edit, Fix}; pub(crate) mod codemods; pub(crate) mod edits; @@ -157,14 +158,16 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi #[cfg(test)] mod tests { - use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker}; + use ruff_diagnostics::SourceMarker; use ruff_source_file::SourceFileBuilder; use ruff_text_size::{Ranged, TextSize}; use crate::Locator; + use crate::diagnostic::Diagnostic; use crate::fix::{FixResult, apply_fixes}; use crate::message::Message; use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; + use crate::{Edit, Fix}; fn create_diagnostics( filename: &str, diff --git a/crates/ruff_linter/src/importer/insertion.rs b/crates/ruff_linter/src/importer/insertion.rs index fe46e0cb95c412..123322e50dff5d 100644 --- a/crates/ruff_linter/src/importer/insertion.rs +++ b/crates/ruff_linter/src/importer/insertion.rs @@ -1,7 +1,6 @@ //! Insert statements into Python code. use std::ops::Add; -use ruff_diagnostics::Edit; use ruff_python_ast::Stmt; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_codegen::Stylist; @@ -10,6 +9,7 @@ use ruff_python_trivia::{PythonWhitespace, textwrap::indent}; use ruff_source_file::{LineRanges, UniversalNewlineIterator}; use ruff_text_size::{Ranged, TextSize}; +use crate::Edit; use crate::Locator; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/importer/mod.rs b/crates/ruff_linter/src/importer/mod.rs index a06b68dd6cb4eb..775943caf09cdd 100644 --- a/crates/ruff_linter/src/importer/mod.rs +++ b/crates/ruff_linter/src/importer/mod.rs @@ -8,7 +8,6 @@ use std::error::Error; use anyhow::Result; use libcst_native::{ImportAlias, Name as cstName, NameOrAttribute}; -use ruff_diagnostics::Edit; use ruff_python_ast::{self as ast, Expr, ModModule, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_parser::{Parsed, Tokens}; @@ -18,6 +17,7 @@ use ruff_python_semantic::{ use ruff_python_trivia::textwrap::indent; use ruff_text_size::{Ranged, TextSize}; +use crate::Edit; use crate::Locator; use crate::cst::matchers::{match_aliases, match_import_from, match_statement}; use crate::fix; diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index 637112d605e003..0988cd36f10ce3 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -14,12 +14,17 @@ pub use rule_selector::RuleSelector; pub use rule_selector::clap_completion::RuleSelectorParser; pub use rules::pycodestyle::rules::IOError; +pub use diagnostic::Diagnostic; +pub(crate) use ruff_diagnostics::{Applicability, Edit, Fix}; +pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata}; + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); mod checkers; pub mod codes; mod comments; mod cst; +mod diagnostic; pub mod directives; mod doc_lines; mod docstrings; @@ -45,6 +50,7 @@ pub mod settings; pub mod source_kind; mod text_helpers; pub mod upstream_categories; +mod violation; #[cfg(any(test, fuzzing))] pub mod test; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index f69922cf321ec0..ca63578b7f0947 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -9,7 +9,6 @@ use itertools::Itertools; use ruff_python_parser::semantic_errors::SemanticSyntaxError; use rustc_hash::FxHashMap; -use ruff_diagnostics::Diagnostic; use ruff_notebook::Notebook; use ruff_python_ast::{ModModule, PySourceType, PythonVersion}; use ruff_python_codegen::Stylist; @@ -18,6 +17,7 @@ use ruff_python_parser::{ParseError, ParseOptions, Parsed, UnsupportedSyntaxErro use ruff_source_file::SourceFileBuilder; use ruff_text_size::Ranged; +use crate::Diagnostic; use crate::checkers::ast::check_ast; use crate::checkers::filesystem::check_file_path; use crate::checkers::imports::check_imports; diff --git a/crates/ruff_linter/src/message/diff.rs b/crates/ruff_linter/src/message/diff.rs index c0358d0b085d75..57e22e6782c45a 100644 --- a/crates/ruff_linter/src/message/diff.rs +++ b/crates/ruff_linter/src/message/diff.rs @@ -6,11 +6,11 @@ use colored::{Color, ColoredString, Colorize, Styles}; use ruff_text_size::{Ranged, TextRange, TextSize}; use similar::{ChangeTag, TextDiff}; -use ruff_diagnostics::{Applicability, Fix}; use ruff_source_file::{OneIndexed, SourceFile}; use crate::message::Message; use crate::text_helpers::ShowNonprinting; +use crate::{Applicability, Fix}; /// Renders a diff that shows the code fixes. /// diff --git a/crates/ruff_linter/src/message/json.rs b/crates/ruff_linter/src/message/json.rs index be2fbd2f761f23..3492047f440b80 100644 --- a/crates/ruff_linter/src/message/json.rs +++ b/crates/ruff_linter/src/message/json.rs @@ -4,11 +4,11 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use serde_json::{Value, json}; -use ruff_diagnostics::Edit; use ruff_notebook::NotebookIndex; use ruff_source_file::{LineColumn, OneIndexed, SourceCode}; use ruff_text_size::Ranged; +use crate::Edit; use crate::message::{Emitter, EmitterContext, Message}; #[derive(Default)] diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 2b4eedca29bc7f..84e6f20d48bb0a 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -16,7 +16,6 @@ pub use json_lines::JsonLinesEmitter; pub use junit::JunitEmitter; pub use pylint::PylintEmitter; pub use rdjson::RdjsonEmitter; -use ruff_diagnostics::{Diagnostic, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::{ParseError, UnsupportedSyntaxError}; use ruff_source_file::{LineColumn, SourceFile}; @@ -28,6 +27,7 @@ use crate::Locator; use crate::codes::NoqaCode; use crate::logging::DisplayParseErrorType; use crate::registry::Rule; +use crate::{Diagnostic, Fix}; mod azure; mod diff; @@ -60,6 +60,7 @@ pub struct Message { pub fix: Option, pub parent: Option, pub(crate) noqa_offset: Option, + noqa_code: Option, } impl Message { @@ -76,12 +77,12 @@ impl Message { fix: None, parent: None, noqa_offset: None, + noqa_code: None, } } #[expect(clippy::too_many_arguments)] pub fn diagnostic( - name: &'static str, body: String, suggestion: Option, range: TextRange, @@ -89,9 +90,10 @@ impl Message { parent: Option, file: SourceFile, noqa_offset: Option, + rule: Rule, ) -> Message { let mut diagnostic = db::Diagnostic::new( - DiagnosticId::Lint(LintName::of(name)), + DiagnosticId::Lint(LintName::of(rule.into())), Severity::Error, body, ); @@ -107,6 +109,7 @@ impl Message { fix, parent, noqa_offset, + noqa_code: Some(rule.noqa_code()), } } @@ -117,15 +120,14 @@ impl Message { noqa_offset: Option, ) -> Message { let Diagnostic { - name, body, suggestion, range, fix, parent, + rule, } = diagnostic; Self::diagnostic( - name, body, suggestion, range, @@ -133,6 +135,7 @@ impl Message { parent, file, noqa_offset, + rule, ) } @@ -235,7 +238,7 @@ impl Message { /// Returns the [`NoqaCode`] corresponding to the diagnostic message. pub fn to_noqa_code(&self) -> Option { - self.to_rule().map(|rule| rule.noqa_code()) + self.noqa_code } /// Returns the URL for the rule documentation, if it exists. @@ -371,7 +374,8 @@ impl<'a> EmitterContext<'a> { mod tests { use rustc_hash::FxHashMap; - use ruff_diagnostics::{Edit, Fix}; + use crate::codes::Rule; + use crate::{Edit, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::{Mode, ParseOptions, parse_unchecked}; use ruff_source_file::{OneIndexed, SourceFileBuilder}; @@ -417,7 +421,6 @@ def fibonacci(n): let unused_import_start = TextSize::from(7); let unused_import = Message::diagnostic( - "unused-import", "`os` imported but unused".to_string(), Some("Remove unused import: `os`".to_string()), TextRange::new(unused_import_start, TextSize::from(9)), @@ -428,11 +431,11 @@ def fibonacci(n): None, fib_source.clone(), Some(unused_import_start), + Rule::UnusedImport, ); let unused_variable_start = TextSize::from(94); let unused_variable = Message::diagnostic( - "unused-variable", "Local variable `x` is assigned to but never used".to_string(), Some("Remove assignment to unused variable `x`".to_string()), TextRange::new(unused_variable_start, TextSize::from(95)), @@ -443,13 +446,13 @@ def fibonacci(n): None, fib_source, Some(unused_variable_start), + Rule::UnusedVariable, ); let file_2 = r"if a == 1: pass"; let undefined_name_start = TextSize::from(3); let undefined_name = Message::diagnostic( - "undefined-name", "Undefined name `a`".to_string(), None, TextRange::new(undefined_name_start, TextSize::from(4)), @@ -457,6 +460,7 @@ def fibonacci(n): None, SourceFileBuilder::new("undef.py", file_2).finish(), Some(undefined_name_start), + Rule::UndefinedName, ); vec![unused_import, unused_variable, undefined_name] @@ -479,7 +483,6 @@ def foo(): let unused_import_os_start = TextSize::from(16); let unused_import_os = Message::diagnostic( - "unused-import", "`os` imported but unused".to_string(), Some("Remove unused import: `os`".to_string()), TextRange::new(unused_import_os_start, TextSize::from(18)), @@ -490,11 +493,11 @@ def foo(): None, notebook_source.clone(), Some(unused_import_os_start), + Rule::UnusedImport, ); let unused_import_math_start = TextSize::from(35); let unused_import_math = Message::diagnostic( - "unused-import", "`math` imported but unused".to_string(), Some("Remove unused import: `math`".to_string()), TextRange::new(unused_import_math_start, TextSize::from(39)), @@ -505,11 +508,11 @@ def foo(): None, notebook_source.clone(), Some(unused_import_math_start), + Rule::UnusedImport, ); let unused_variable_start = TextSize::from(98); let unused_variable = Message::diagnostic( - "unused-variable", "Local variable `x` is assigned to but never used".to_string(), Some("Remove assignment to unused variable `x`".to_string()), TextRange::new(unused_variable_start, TextSize::from(99)), @@ -520,6 +523,7 @@ def foo(): None, notebook_source, Some(unused_variable_start), + Rule::UnusedVariable, ); let mut notebook_indexes = FxHashMap::default(); diff --git a/crates/ruff_linter/src/message/rdjson.rs b/crates/ruff_linter/src/message/rdjson.rs index 32d29fa2c48895..34c89d0bd05430 100644 --- a/crates/ruff_linter/src/message/rdjson.rs +++ b/crates/ruff_linter/src/message/rdjson.rs @@ -4,10 +4,10 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use serde_json::{Value, json}; -use ruff_diagnostics::Edit; use ruff_source_file::SourceCode; use ruff_text_size::Ranged; +use crate::Edit; use crate::message::{Emitter, EmitterContext, LineColumn, Message}; #[derive(Default)] diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 8cf9c8ea165e7e..8b47e574160076 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -9,11 +9,11 @@ use anyhow::Result; use itertools::Itertools; use log::warn; -use ruff_diagnostics::Edit; use ruff_python_trivia::{CommentRanges, Cursor, indentation_at_offset}; use ruff_source_file::{LineEnding, LineRanges}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use crate::Edit; use crate::Locator; use crate::codes::NoqaCode; use crate::fs::relativize_path; @@ -1221,7 +1221,6 @@ mod tests { use insta::assert_debug_snapshot; - use ruff_diagnostics::{Diagnostic, Edit}; use ruff_python_trivia::CommentRanges; use ruff_source_file::{LineEnding, SourceFileBuilder}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -1234,6 +1233,7 @@ mod tests { use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; + use crate::{Diagnostic, Edit}; use crate::{Locator, generate_noqa_edits}; fn assert_lexed_ranges_match_slices( diff --git a/crates/ruff_linter/src/pyproject_toml.rs b/crates/ruff_linter/src/pyproject_toml.rs index b59e1774b00111..137089e199888f 100644 --- a/crates/ruff_linter/src/pyproject_toml.rs +++ b/crates/ruff_linter/src/pyproject_toml.rs @@ -3,9 +3,9 @@ use log::warn; use pyproject_toml::PyProjectToml; use ruff_text_size::{TextRange, TextSize}; -use ruff_diagnostics::Diagnostic; use ruff_source_file::SourceFile; +use crate::Diagnostic; use crate::IOError; use crate::message::Message; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/renamer.rs b/crates/ruff_linter/src/renamer.rs index f20843a1e5987a..d31793dc746e53 100644 --- a/crates/ruff_linter/src/renamer.rs +++ b/crates/ruff_linter/src/renamer.rs @@ -3,13 +3,13 @@ use anyhow::{Result, anyhow}; use itertools::Itertools; -use ruff_diagnostics::Edit; use ruff_python_ast as ast; use ruff_python_codegen::Stylist; use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel}; use ruff_python_stdlib::{builtins::is_python_builtin, keyword::is_keyword}; use ruff_text_size::Ranged; +use crate::Edit; use crate::checkers::ast::Checker; pub(crate) struct Renamer; diff --git a/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs b/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs index 3413544dedc545..c89071f1208399 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/dag_schedule_argument.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index 81841674314f2e..01e7f4c45f9251 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -1,6 +1,3 @@ -use crate::importer::ImportRequest; -use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; @@ -9,6 +6,9 @@ use ruff_text_size::Ranged; use ruff_text_size::TextRange; use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of Airflow functions and values that have been moved to it providers. diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index 3c551f4a1d9961..f9139512a229cd 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -3,7 +3,7 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, }; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{ diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index a6815fa5a93540..1177e9581820c6 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -1,7 +1,7 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index f3286a83506e97..da733f8d462388 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -3,7 +3,7 @@ use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, }; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, Expr, ExprAttribute, ExprCall, ExprName, name::QualifiedName}; use ruff_python_semantic::Modules; diff --git a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs index 5470014187d9e4..8fcd6da8c167fe 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::Violation; +use crate::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; diff --git a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs index e7578f14b4a34f..67b5c125a9f6d6 100644 --- a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs +++ b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::{LineRanges, UniversalNewlineIterator}; @@ -6,6 +5,7 @@ use ruff_text_size::TextRange; use crate::Locator; use crate::settings::LinterSettings; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use super::super::detection::comment_contains_code; diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs index b2221cfbd541f0..764abe7c9fbc6c 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_callable; @@ -7,6 +6,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::rules::fastapi::rules::is_fastapi_route; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs index 96a663eaea58e2..7f98510e0b422d 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_redundant_response_model.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Decorator, Expr, ExprCall, Keyword, StmtFunctionDef}; use ruff_python_semantic::{Modules, SemanticModel}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; use crate::rules::fastapi::rules::is_fastapi_route_decorator; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for FastAPI routes that use the optional `response_model` parameter diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs index 42220b3dc8909e..c0f1cfb5179e31 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs @@ -2,8 +2,6 @@ use std::iter::Peekable; use std::ops::Range; use std::str::CharIndices; -use ruff_diagnostics::Fix; -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault}; @@ -11,9 +9,11 @@ use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel}; use ruff_python_stdlib::identifiers::is_identifier; use ruff_text_size::{Ranged, TextSize}; +use crate::Fix; use crate::checkers::ast::Checker; use crate::fix::edits::add_parameter; use crate::rules::fastapi::rules::is_fastapi_route_decorator; +use crate::{FixAvailability, Violation}; /// ## What it does /// Identifies FastAPI routes that declare path parameters in the route path diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs index a83fe0c288edbb..902223ac10db52 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/compare.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs index fdf240834add03..e310dd24b58800 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs index e9f90c85d44f0e..1036195ddd4be7 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::flake8_2020::helpers::is_sys; diff --git a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs index 65ef77667b971b..e627e0f7c588a8 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs @@ -1,7 +1,6 @@ use itertools::Itertools; use rustc_hash::FxHashSet; -use ruff_diagnostics::Edit; use ruff_python_ast::helpers::{ ReturnStatementVisitor, pep_604_union, typing_optional, typing_union, }; @@ -14,6 +13,7 @@ use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Definition, SemanticModel}; use ruff_text_size::{TextRange, TextSize}; +use crate::Edit; use crate::checkers::ast::Checker; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs index 333845c7e292fc..cdb8d31f78509c 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -13,6 +12,7 @@ use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::registry::Rule; use crate::rules::flake8_annotations::helpers::auto_return_type; use crate::rules::ruff::typing::type_hint_resolves_to_any; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks that function arguments have type annotations. diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs index 9d5980c54852bb..d54b0da44871bf 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_busy_wait.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_async::helpers::AsyncModule; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs index 3703ded95a27fa..74c19a734a6884 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_async::helpers::AsyncModule; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index 890cadd0e4d477..1921af7412f58f 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; use ruff_python_semantic::Modules; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::rules::flake8_async::helpers::AsyncModule; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `trio.sleep(0)` or `anyio.sleep(0)`. diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index ba99ea539ddd68..ceb69137e3dea2 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -1,10 +1,10 @@ use ruff_python_ast::ExprCall; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index 99c9d23bb613a2..daf84df349ed1f 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{SemanticModel, analyze}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index fffd7d69139399..e056d324aa1407 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::typing::find_assigned_value; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs index 693ec58d3c8079..10db6b92d29a17 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs @@ -1,10 +1,10 @@ use ruff_python_ast::ExprCall; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs index 80c65c2265f687..1873a58e47e447 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{AwaitVisitor, any_over_body}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{Expr, StmtWith, WithItem}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_async::helpers::MethodName; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs index e91ec3615efd70..981dccfe3f4123 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/long_sleep_not_forever.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, ExprNumberLiteral, Number}; use ruff_python_semantic::Modules; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::rules::flake8_async::helpers::AsyncModule; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `trio.sleep()` or `anyio.sleep()` with a delay greater than 24 hours. diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs index ee0c301a4bb576..1fdcfb8f6ec650 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall}; use ruff_python_semantic::Modules; @@ -7,6 +6,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::pad; use crate::rules::flake8_async::helpers::MethodName; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for calls to trio functions that are not immediately awaited. diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs index 6ea3b04fa9a630..1366cb7ac7981b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/assert_used.rs @@ -1,9 +1,9 @@ +use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; +use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; -use ruff_diagnostics::Violation; -use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs index c99774341fc6f1..b06f9104c28b10 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs @@ -1,12 +1,12 @@ use anyhow::Result; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs index 9051f9db47bf91..0ad0de23d54bba 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs index 7876dca2c93be3..13c3556d23e144 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs index a495231702ce74..f66347d6f40051 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs index 6ab094b6778d4a..fcf8317ba2f5e1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs index 6a448047488170..9b9628bb0358cf 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs index 9cc404fba8f8d4..c59d0feaaaa164 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_default.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{Expr, Parameter, Parameters}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::{matches_password_name, string_literal}; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs index a6aacb3c5ec964..6e806cc38d9b10 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_func_arg.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Keyword; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::{matches_password_name, string_literal}; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs index 317dcad0c598bf..ca90637f9d5af9 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_password_string.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::{matches_password_name, string_literal}; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index 8fa2cbf8dc037c..7080cb57f49063 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -2,13 +2,13 @@ use std::sync::LazyLock; use regex::Regex; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::raw_contents; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_text_size::Ranged; use crate::Locator; +use crate::Violation; use crate::checkers::ast::Checker; static SQL_REGEX: LazyLock = LazyLock::new(|| { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs index 88086ca8bf7975..963314df64e2a2 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr, StringLike}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs index 035bb4c89288b6..8582b4960059e1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_false; use ruff_python_ast::{self as ast, Arguments}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::string_literal; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs index 14f96de490b83b..475a24dfaea2cf 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs index 48aab788dd0e29..16d9b307b59cb4 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs index 8ca83f4abb417b..b62a0b39e7411d 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs @@ -1,9 +1,10 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; +use crate::Violation; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for uses of the `mako` templates. /// diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs index 45f553d694ab7b..8eb8ec0bfc1ae0 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs index c6745ebb8b0153..c22bf55168c37e 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::is_const_false; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs index 7a27c805a24707..b6d3352177c518 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs index 5b578a8e5cbd9c..e8bc2204f1e7d0 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs @@ -1,13 +1,13 @@ //! Checks relating to shell injection. -use crate::preview::is_shell_injection_only_trusted_input_enabled; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; +use crate::preview::is_shell_injection_only_trusted_input_enabled; use crate::{ checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal, }; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs index 28164d334c4e98..c7e6affb4837db 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Int}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs index e89b2f0d606df7..f7453cae1e2adf 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs index c109f7c09e285e..b09cd8e4aceb94 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs index 886733befdd118..d022d1711ad193 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs index 82c6362b7b2478..1acb8d4ef4e023 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_bad_defaults.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, StmtFunctionDef}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs index 9bedb2eeb635fe..20cea65ce7a5c4 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index 6302b767c2eada..28944882b4832c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -2,11 +2,11 @@ //! //! See: use itertools::Either; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator}; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::preview::is_suspicious_function_reference_enabled; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs index 1951141db0f7a9..a3cb2ae4cd83bb 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_imports.rs @@ -1,11 +1,11 @@ //! Check for imports of or from suspicious modules. //! //! See: -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs index f1f058706487e4..8e816ee21d770d 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs @@ -1,10 +1,11 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for uses of `tarfile.extractall`. /// diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs index ac7e7ebaf037b7..5bd463285315dd 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_continue.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_bandit::helpers::is_untyped_exception; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs index 3fdf1bf37cf44b..56dcaff1e09e72 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/try_except_pass.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_bandit::helpers::is_untyped_exception; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs index f82d6fbc09b6e2..c5fc91a1f6d0aa 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_markup_use.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{Expr, ExprCall}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::{checkers::ast::Checker, settings::LinterSettings}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs index 7c6510f585a43c..dae9564053fdef 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs index c89d8a7b3a5f81..63b709fc32b44a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs @@ -1,10 +1,10 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs index c9119014f737ec..569ce05254fee0 100644 --- a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs +++ b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; @@ -7,6 +6,7 @@ use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::logging; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs index f4f352a2acfd4d..84f47e1e961fe6 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::{Decorator, Expr, Parameters}; use ruff_python_semantic::analyze::visibility; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs index 260d4e566302ff..e7350bff8af6b9 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_positional_value_in_call.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_boolean_trap::helpers::allow_boolean_trap; diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index d06a8f7d426230..51bd830a66fd9e 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::name::UnqualifiedName; @@ -6,6 +5,7 @@ use ruff_python_ast::{self as ast, Decorator, Expr, Parameters}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::visibility; +use crate::Violation; use crate::checkers::ast::Checker; use crate::preview::is_bool_subtype_of_annotation_enabled; use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs index b1aa5bf9d17966..2e42008201a999 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs index 6ec957f8fee4ff..94847783b94220 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_false; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `assert False`. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs index 0b9743aba5c197..2e9be0eef0abdb 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, WithItem}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs index d708793c1dde25..b96a5d91fad532 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assignment_to_os_environ.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs index 8afefcd940484e..8cc71d438c3450 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs @@ -1,10 +1,11 @@ -use crate::checkers::ast::Checker; -use crate::rules::flake8_bugbear::rules::is_infinite_iterable; -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_python_ast::PythonVersion; +use crate::checkers::ast::Checker; +use crate::rules::flake8_bugbear::rules::is_infinite_iterable; +use crate::{FixAvailability, Violation}; + /// ## What it does /// Checks for `itertools.batched` calls without an explicit `strict` parameter. /// diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 1c19a160acd587..d724833cf73cb8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{self as ast, Expr}; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::{class, function_type}; use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs index bf7994b990995e..1ed8395147f702 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/class_as_data_structure.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::analyze::visibility::{self, Visibility::Public}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index dcda3372b55550..e3868cde2c60ed 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -1,8 +1,6 @@ use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; @@ -11,6 +9,8 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::pad; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Edit, Fix}; /// ## What it does /// Checks for `try-except` blocks with duplicate exception handlers. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs index 346a1ae064167b..643b2c0c5e0158 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_value.rs @@ -1,7 +1,6 @@ use anyhow::{Context, Result}; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -10,6 +9,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for set literals that contain duplicate items. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs index 52375d2d74ddc5..d3f5aba2bbdf16 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_empty_tuple.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast}; use ruff_python_ast::{ExceptHandler, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs index ca3b0874463da5..66df64700d35d5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs @@ -2,10 +2,10 @@ use std::collections::VecDeque; use ruff_python_ast::{self as ast, ExceptHandler, Expr, Operator}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs index d17b380c446084..14e4f73409b218 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs index 05a24fe0212146..9243209f7f7acb 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs @@ -1,15 +1,14 @@ -use ruff_python_ast::{self as ast, Expr, Parameters}; -use ruff_text_size::Ranged; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; +use ruff_python_ast::{self as ast, Expr, Parameters}; use ruff_python_semantic::analyze::typing::{ is_immutable_annotation, is_immutable_func, is_immutable_newtype_call, is_mutable_func, }; +use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs index bef045a9b4b463..fd7b5436efa566 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::types::Node; use ruff_python_ast::visitor; @@ -6,6 +5,7 @@ use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Comprehension, Expr, ExprContext, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs index 3eee042415c5f5..ef108b77f5a18b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `getattr` that take a constant attribute value as an diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs index b4c5d7f9ec923e..87fc151c904310 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/jump_statement_in_finally.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs index 854c27466408f2..fe3ff0870045d9 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::fmt::Debug; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::name::UnqualifiedName; @@ -12,6 +11,7 @@ use ruff_python_ast::{ }; use ruff_text_size::TextRange; +use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs index 647bf675a234b7..dec0098e9a5fbb 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashMap; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index 7a4e462ad7b684..b5a7effc28c206 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -1,6 +1,5 @@ use std::fmt::Write; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::name::QualifiedName; @@ -16,6 +15,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of mutable objects as function argument defaults. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs index 1188467478bab1..1b7b699691c6e6 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::name::QualifiedName; @@ -7,6 +6,7 @@ use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::typing::{is_immutable_func, is_mutable_expr, is_mutable_func}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index 30529639b92c88..9d069216e2af02 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::{AlwaysFixableViolation, Fix}; use crate::{checkers::ast::Checker, fix::edits::add_argument}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs index 980fde34227917..bfe131f98e6378 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_literal.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs index 4b9797e327309d..a9b6c674f72788 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/raise_without_from_inside_except.rs @@ -1,11 +1,11 @@ use ruff_python_ast as ast; use ruff_python_ast::Stmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::RaiseStatementVisitor; use ruff_python_ast::statement_visitor::StatementVisitor; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs index f046d331d12fb3..4f38947c7716c1 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs @@ -2,11 +2,11 @@ use std::fmt; use ruff_python_ast::{self as ast}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs index 9af207f8b5fbf8..3d5e3834303a50 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, ExceptHandler, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for single-element tuples in exception handlers (e.g., diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs index bfffb34494177e..a24e49c2938c07 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor; use ruff_python_ast::statement_visitor::StatementVisitor; use ruff_python_ast::{self as ast, Expr, Stmt, StmtFunctionDef}; use ruff_text_size::TextRange; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs index 460dab103147d2..6cd3ddd478257a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Comprehension, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{self, Visitor}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index 8ed5452a10b471..cfc25573414197 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, Expr, ExprContext, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_codegen::Generator; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `setattr` that take a constant attribute value as an diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs index c857b3be38a426..570ac00660c2db 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/star_arg_unpacking_after_keyword_arg.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{Expr, Keyword}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs index 5852ef71dc539f..fbcae4568b05fc 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/static_key_dict_comprehension.rs @@ -1,12 +1,12 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::StoredNameFinder; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs index 3f982e5592b321..4c6ef9ef5e2dd6 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/strip_with_multi_characters.rs @@ -1,10 +1,10 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs index 1fbfc42d5a7ae3..f9c868226b299c 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unary_prefix_increment_decrement.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr, UnaryOp}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs index e9230d664b0490..5588b4c343613b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unintentional_type_annotation.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs index 8d0934505ccb3a..5bfaefe07924bd 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `hasattr` to test if an object is callable (e.g., diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 233d8e3c8cd407..00038ea515e4d3 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers; @@ -8,6 +7,7 @@ use ruff_python_semantic::Binding; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unused variables in loops (e.g., `for` and `while` statements). diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs index 274beba9ae80a9..0cfa4e2d191cf1 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_comparison.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Stmt}; use ruff_python_semantic::ScopeKind; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::at_last_top_level_expression_in_cell; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs index 866ad761b64c3d..4ec3e85d1fd9e3 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs index 39f01c9ed088d5..8afb28494c88af 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::helpers::contains_effect; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::at_last_top_level_expression_in_cell; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index 2dfb56826f63f8..73134c65b6a9c5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::add_argument; +use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does /// Checks for `zip` calls without an explicit `strict` parameter. diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index 76175c7a7ba730..a016908f75a602 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Parameter}; use ruff_python_semantic::analyze::visibility::{is_overload, is_override}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::super::helpers::shadows_builtin; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index 678e7ad4e03750..3ec4fb5d5c0daa 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::{BindingKind, Scope, ScopeId}; use ruff_source_file::SourceRow; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs index c1ce98c31777cc..4f3d9d64ee839a 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Alias; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs index 20d895047eb9e7..73dddd9a4f4274 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 23b622fc47cb8b..7d69a7fc3ce389 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::TextRange; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs index 61a4494d174912..1112921b72a2cf 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::path::{Component, Path, PathBuf}; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{PySourceType, PythonVersion}; use ruff_python_stdlib::path::is_module_file; @@ -9,6 +8,7 @@ use ruff_python_stdlib::sys::is_known_standard_library; use ruff_text_size::TextRange; use crate::settings::LinterSettings; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for modules that use the same names as Python standard-library diff --git a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs index 0002b9a14700eb..8ff3c157c09643 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs +++ b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs @@ -1,11 +1,11 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Diagnostic, Edit, Fix}; /// Simplified token type. #[derive(Copy, Clone, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs index b796ff8e958762..fdddf2e5bb07c8 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs @@ -10,7 +10,6 @@ use libcst_native::{ SimpleString, SimpleWhitespace, TrailingWhitespace, Tuple, }; -use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_python_codegen::Stylist; use ruff_python_semantic::SemanticModel; @@ -21,6 +20,7 @@ use crate::cst::helpers::{negate, space}; use crate::fix::codemods::CodegenStylist; use crate::fix::edits::pad; use crate::rules::flake8_comprehensions::rules::ObjectType; +use crate::{Edit, Fix}; use crate::{ checkers::ast::Checker, cst::matchers::{ diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs index 716ef5b6fa1da9..08395c5ea3ef21 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Fix}; use crate::rules::flake8_comprehensions::fixes; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs index 7c8d5b73867ae8..ac9f6664103e83 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextSize}; @@ -7,6 +6,7 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes::{pad_end, pad_start}; use crate::rules::flake8_comprehensions::settings::Settings; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for unnecessary `dict()`, `list()` or `tuple()` calls that can be diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs index 01d08a2664a0ac..558f2ace4fd6d6 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Comprehension, Expr}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix}; use crate::rules::flake8_comprehensions::fixes; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs index c503777cb58d14..dc3cc820c8c42d 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs @@ -1,13 +1,13 @@ -use ruff_diagnostics::FixAvailability; -use ruff_diagnostics::{Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::{Ranged, TextSize}; +use crate::FixAvailability; use crate::checkers::ast::Checker; use crate::preview::is_comprehension_with_min_max_sum_enabled; use crate::rules::flake8_comprehensions::fixes; +use crate::{Edit, Fix, Violation}; /// ## What it does /// Checks for unnecessary list or set comprehensions passed to builtin functions that take an iterable. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs index 1705b3b0ecf32c..7af4bfa6f1adc3 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -1,5 +1,4 @@ use ast::ExprName; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::any_over_expr; @@ -7,6 +6,7 @@ use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprCall, Exp use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary dict comprehension when creating a dictionary from diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs index e8988e66612159..c0811cdb065576 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableKeyword; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix}; use crate::rules::flake8_comprehensions::fixes; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs index 7602cd1e350237..3caadb61f59e01 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix}; use crate::rules::flake8_comprehensions::fixes; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs index 1627d769740ad5..44fc597fc8871c 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ExprGenerator; @@ -8,6 +7,7 @@ use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs index 0db3228cbb8b31..ed590d5e22d335 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ExprGenerator; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes::{pad_end, pad_start}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs index 784105fda70ff2..1bf052e206632b 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs @@ -1,12 +1,11 @@ use ruff_python_ast::{Arguments, Expr, ExprCall}; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; - use crate::rules::flake8_comprehensions::fixes; +use crate::{AlwaysFixableViolation, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs index eeae7b838ce5f9..5b4bbac5fc6bda 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs @@ -1,11 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; - use crate::rules::flake8_comprehensions::fixes; +use crate::{AlwaysFixableViolation, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs index b1f1755a1f093f..eadeef634a8c9a 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parenthesized_range; @@ -7,6 +6,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes::{pad_end, pad_start}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs index 235e6e8c1996a7..c87ae7ce43896f 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs @@ -1,11 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; - use crate::rules::flake8_comprehensions::fixes; +use crate::{AlwaysFixableViolation, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs index f5b0f311b684c6..14bd1d06241c87 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes::{pad_end, pad_start}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs index d80ad19733ff04..63b316675b3076 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_dict_call.rs @@ -2,11 +2,11 @@ use std::fmt; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs index e5b8fcfd3c0146..42731d64b89912 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs index 7dd8231e7cd443..4e7749325f442b 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Expr}; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::preview::is_check_comprehensions_in_tuple_call_enabled; use crate::rules::flake8_comprehensions::fixes; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs index bcd0090fcaa6ff..e31c4b15a097c9 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_map.rs @@ -1,7 +1,5 @@ use std::fmt; -use ruff_diagnostics::Fix; -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::visitor::Visitor; @@ -9,8 +7,10 @@ use ruff_python_ast::{self as ast, Expr, ExprContext, Parameters, Stmt}; use ruff_python_ast::{ExprLambda, visitor}; use ruff_python_semantic::SemanticModel; +use crate::Fix; use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary `map()` calls with lambda functions. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs index d2e0d960fa635f..0e2768eb7a3bd6 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, UnaryOp}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs index bbe034ce5dfb93..d8745ed2fbdca2 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; use crate::settings::LinterSettings; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for the absence of copyright notices within Python files. diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs index 12ab428186b59e..f1363faf1a8795 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs index b182c840cae7b6..34e2bc937d5066 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index 6781be630ca2be..d18cc494ef626a 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers::{self, DatetimeModuleAntipattern}; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index 693f15ddce90ff..e4b5a304f03407 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers::{self, DatetimeModuleAntipattern}; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index 43a265e1eede10..bc2a6cb751cf4c 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers::DatetimeModuleAntipattern; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs index 1f22d130873eb6..974300ae8be1c5 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs index 272ce7161b0436..3de3dc25189af6 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs index 8a7068b05051c5..07e67a3f1e4f9d 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index c59c321918b19e..3923266a0c197a 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers::{self, DatetimeModuleAntipattern}; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs index 83b9fcb8a1efe4..275bc18ba92885 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/datetime_min_max.rs @@ -1,11 +1,11 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs index 807cb90981011c..401bd2bd61efea 100644 --- a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs +++ b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_debugger::types::DebuggerUsingType; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs index c9ccd941ed7bfa..e2a2e88ce65647 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_django::rules::helpers::is_model_form; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs index 0de9346f4e3437..9730da1966d964 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_django::rules::helpers::is_model_form; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs index 8328d7d24bac92..4f5ad2ca4218b5 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs index acb63289c0750f..0ff3119e979da5 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::{Modules, SemanticModel, analyze}; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs index a4744674a9e26d..3a854e9756c858 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Decorator; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs index 6840d466418ad6..f2810508ae702e 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs index 85f385be465cea..f0a2e910bc4fb5 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs @@ -1,12 +1,12 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers; diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 560fbfd34a804a..85f1a4afebc57d 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::whitespace; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the use of string literals in exception constructors. diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs index e9f7c58058148f..9f92c00948ecf3 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs @@ -1,6 +1,5 @@ use std::path::Path; -use ruff_diagnostics::Diagnostic; use ruff_python_trivia::CommentRanges; pub(crate) use shebang_leading_whitespace::*; pub(crate) use shebang_missing_executable_file::*; @@ -8,6 +7,7 @@ pub(crate) use shebang_missing_python::*; pub(crate) use shebang_not_executable::*; pub(crate) use shebang_not_first_line::*; +use crate::Diagnostic; use crate::Locator; use crate::codes::Rule; use crate::comments::shebang::ShebangDirective; diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs index df3aea03fde8eb..7510b708acd70b 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs @@ -1,9 +1,9 @@ +use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; -use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_trivia::is_python_whitespace; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for whitespace before a shebang directive. diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs index a297541e2b818d..1d21ec05ce66f1 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs @@ -4,12 +4,12 @@ use std::path::Path; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::registry::AsRule; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for executable `.py` files that do not have a shebang. diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs index 137203e0e73e21..e9f6406e0feb51 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs @@ -1,9 +1,9 @@ use ruff_text_size::TextRange; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::comments::shebang::ShebangDirective; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in `.py` files that does not contain `python`, diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs index 14e7268754a6cd..b10e67cbdbf4c6 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs @@ -1,14 +1,11 @@ -#![allow(unused_imports)] - use std::path::Path; -use ruff_text_size::{Ranged, TextRange}; - -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_text_size::TextRange; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in a file that is not executable. diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs index 0405e0cebe1a93..8cdfe0db13386a 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for a shebang directive that is not at the beginning of the file. diff --git a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs index f8515c4c7e5a7c..6f133ae0df7367 100644 --- a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::directives::{TodoComment, TodoDirectiveKind}; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for "TODO" comments. diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs index 7475d238fe25c0..2305cd50527368 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs @@ -1,12 +1,12 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::{MemberNameImport, NameImport}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for uses of PEP 585- and PEP 604-style type annotations in Python diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs index 12c67d0f9a42dd..e62d1f4ed28369 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs index d165cbe48b9e37..13f35447681525 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/f_string_in_gettext_func_call.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs index 79e242d0363237..9d53d48abf3cb3 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/format_in_gettext_func_call.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs b/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs index 668ae71e2e51fe..22c8a4b7373ccc 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/rules/printf_in_gettext_func_call.rs @@ -1,9 +1,9 @@ +use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; +use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; -use ruff_diagnostics::Violation; -use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_text_size::Ranged; /// ## What it does /// Checks for printf-style formatted strings in `gettext` function calls. diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs index 173c05b8a9afa1..04aa9963229005 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs @@ -1,12 +1,12 @@ -use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_python_trivia::is_python_whitespace; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use crate::AlwaysFixableViolation; use crate::checkers::ast::Checker; +use crate::{Edit, Fix}; /// ## What it does /// Checks for string literals that are explicitly concatenated (using the diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 004ffb9537d8d1..743174c340c2cb 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use itertools::Itertools; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::{leading_quote, trailing_quote}; use ruff_python_index::Indexer; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::settings::LinterSettings; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for implicitly concatenated strings on a single line. diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs index 412fd2fba12ea2..6fc205699af0c2 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_alias.rs @@ -1,10 +1,10 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_import_conventions::settings::BannedAliases; diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs index da96e127cd9ce5..c5a64ea82a2cd4 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/banned_import_from.rs @@ -1,11 +1,10 @@ -use ruff_python_ast::Stmt; use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::Stmt; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for member imports that should instead be accessed by importing the diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs index ccf58de5f3f105..90970ae343807f 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs @@ -1,11 +1,11 @@ use rustc_hash::FxHashMap; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, Imported}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Fix, FixAvailability, Violation}; use crate::renamer::Renamer; diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs index d372a8aab8e06e..c3ff9c2f512fb2 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for direct instantiation of `logging.Logger`, as opposed to using diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs index b016bc0ca9267a..9b62775fa0c82f 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/exc_info_outside_except_handler.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; use crate::rules::flake8_logging::rules::helpers::outside_handlers; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for logging calls with `exc_info=` outside exception handlers. diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs index 8329823b468bec..0925322e329e63 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::{self as ast, Expr, ExprCall}; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::logging; use ruff_python_stdlib::logging::LoggingLevel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs index 68c5144a27a3cd..17c8ad867633f8 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for any usage of `__cached__` and `__file__` as an argument to diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs index d92a88ff93c051..1f2eb173bd583c 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/log_exception_outside_except_handler.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::analyze::logging; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::rules::flake8_logging::rules::helpers::outside_handlers; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `.exception()` logging calls outside of exception handlers. diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs index a98850d7f743ac..6c174b39e3e460 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/root_logger_call.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprCall; use ruff_python_semantic::Modules; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs index a71f372d65fdc2..2287a217b3357c 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs @@ -1,12 +1,12 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `logging.WARN`. diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs index 7780b72dc7349d..4d0f2fc2212242 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator}; use ruff_python_semantic::analyze::logging; use ruff_python_stdlib::logging::LoggingLevel; @@ -10,6 +9,7 @@ use crate::rules::flake8_logging_format::violations::{ LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat, LoggingRedundantExcInfo, LoggingStringConcat, LoggingStringFormat, LoggingWarn, }; +use crate::{Edit, Fix}; /// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord` /// class. diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/violations.rs b/crates/ruff_linter/src/rules/flake8_logging_format/violations.rs index cec46dbeb0120a..51cc9f436ff6ed 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/violations.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/violations.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::{AlwaysFixableViolation, Violation}; + /// ## What it does /// Checks for uses of `str.format` to format logging messages. /// diff --git a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs index bfddb090112634..8d29898c355ef6 100644 --- a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs +++ b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs @@ -1,6 +1,5 @@ use std::path::{Path, PathBuf}; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PySourceType; use ruff_python_ast::script::ScriptTag; @@ -11,6 +10,7 @@ use crate::Locator; use crate::comments::shebang::ShebangDirective; use crate::fs; use crate::package::PackageRoot; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for packages that are missing an `__init__.py` file. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs index c1dc4783419c0b..a430f8c9b0d29f 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs @@ -1,6 +1,5 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -8,6 +7,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for duplicate field definitions in classes. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs index a15ff261414ea9..4ac91e9ca5b3f6 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs @@ -8,11 +8,11 @@ use ruff_text_size::{Ranged, TextRange}; use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr, ExprContext, Identifier}; -use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::AlwaysFixableViolation; use crate::checkers::ast::Checker; +use crate::{Edit, Fix}; /// ## What it does /// Checks for `startswith` or `endswith` calls on the same value with diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs index 44260082982847..21464f148fe903 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs @@ -1,12 +1,12 @@ use ruff_python_semantic::SemanticModel; use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, Expr, ExprCall, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs index e78083888e02cd..77677672bd4952 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{Expr, ExprLambda}; -use ruff_diagnostics::{Edit, Fix}; -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix}; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for lambdas that can be replaced with the `list` or `dict` builtins. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs index 6164c549ed9c96..56e8691d0071c2 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs @@ -1,7 +1,6 @@ use itertools::Itertools; use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr}; @@ -10,6 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary `dict` kwargs. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs index 8e2dbd34fdf2a7..1a64d0f971c987 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs @@ -1,5 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::whitespace::trailing_comment_start_offset; @@ -9,6 +7,8 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Applicability}; +use crate::{Edit, Fix}; /// ## What it does /// Checks for unnecessary `pass` statements and ellipsis (`...`) literals in diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs index 3540b534f914cf..d9458be9cb3271 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_range_start.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for `range` calls with an unnecessary `start` argument. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs index 353b83bf143859..b5aefbfa5e3c92 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_parser::{TokenKind, Tokens}; use ruff_text_size::{Ranged, TextLen, TextSize}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary dictionary unpacking operators (`**`). diff --git a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs index ed7f4ce866f9b5..c5a851aaf83052 100644 --- a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::delete_stmt; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `print` statements. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs index db61dcff95a5e3..89ede8dc940902 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/any_eq_ne_annotation.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Parameters; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs index b6033ebab1ab20..0524b69af6098d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::helpers::map_subscript; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for simple `__iter__` methods that return `Generator`, and for diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs index 4d4a21d84fe1f5..edd8ac1cb737bf 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::preview::is_bad_version_info_in_non_stub_enabled; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs index 3267c08e5efbce..264d6505482cfb 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bytestring_usage.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for uses of `typing.ByteString` or `collections.abc.ByteString`. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs index ea122dfdaa2ce1..c969a3d092c643 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs index dda0042d59d363..0f38dfe8e68a95 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_assignment_in_stub.rs @@ -1,8 +1,8 @@ use ruff_python_ast::{Expr, StmtAssign}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs index a2525a8dfc559c..010753a0bc2759 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs index 24e39f267a5a5a..d2a8d8527a5310 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs @@ -1,9 +1,9 @@ use anyhow::{Context, bail}; use itertools::Itertools; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; +use ruff_python_ast::PythonVersion; use ruff_python_semantic::analyze::class::is_metaclass; use ruff_python_semantic::analyze::function_type::{self, FunctionType}; use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload}; @@ -11,7 +11,7 @@ use ruff_python_semantic::{Binding, ResolvedReference, ScopeId, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::{Checker, TypingImporter}; -use ruff_python_ast::PythonVersion; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for methods that use custom [`TypeVar`s][typing_TypeVar] in their diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs index f74b7046e75881..5921058fbec356 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/docstring_in_stubs.rs @@ -1,12 +1,10 @@ -use ruff_python_ast::ExprStringLiteral; - -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_text_size::Ranged; - +use ruff_python_ast::ExprStringLiteral; use ruff_python_semantic::Definition; +use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the presence of docstrings in stub files. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs index 6a8cdc0a21e53c..261a52a6192621 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -10,6 +9,7 @@ use ruff_python_semantic::analyze::typing::traverse_literal; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for duplicate members in a `typing.Literal[]` slice. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 81eb281244500f..1064ca22b8d319 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion}; @@ -10,6 +9,7 @@ use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use super::generate_union_fix; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs index 028ab7c5a6255e..aef6f5639bb490 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/ellipsis_in_non_empty_class_body.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Stmt, StmtExpr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Removes ellipses (`...`) in otherwise non-empty class bodies. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs index bb1f636bbd42b8..7e7b67091383e5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs @@ -6,13 +6,13 @@ use ruff_python_ast::{ }; use smallvec::SmallVec; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{SemanticModel, analyze::visibility::is_overload}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for incorrect function signatures on `__exit__` and `__aexit__` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 28ae4ef7014620..398f4ad22e33db 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -1,8 +1,8 @@ use ruff_python_ast::StmtImportFrom; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::{Fix, FixAvailability, Violation}; use crate::{checkers::ast::Checker, fix, preview::is_fix_future_annotations_in_stub_enabled}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs index 5aa851fb8d0148..b7ab81f3d315b0 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, helpers::map_subscript}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, add_argument, remove_argument}; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for classes inheriting from `typing.Generic[]` where `Generic[]` is diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs index d2a25aa0297385..b72a0ce0cc31f7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_text_size::Ranged; use ruff_python_semantic::{Definition, Member, MemberKind}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs index 0b9826d9768c3b..ce7b0700ac5e5c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs @@ -2,12 +2,12 @@ use std::fmt; use anyhow::Result; -use ruff_diagnostics::{Applicability, Edit, Fix}; use ruff_python_ast::{Expr, ExprContext, ExprName, ExprSubscript, ExprTuple, name::Name}; use ruff_python_codegen::Generator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::TypingImporter; +use crate::{Applicability, Edit, Fix}; pub(crate) use any_eq_ne_annotation::*; pub(crate) use bad_generator_return_type::*; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs index 87cd4ff8c6073b..c129ae33f79530 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs index 02b5a61b0d65b6..cfc005941ad618 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for non-empty function stub bodies. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs index ae2bf52d7fc5c6..9e823d11d5d390 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::{Checker, TypingImporter}; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs index a9f805501d663e..739778f4cf7141 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/numeric_literal_too_long.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_text_size::{Ranged, TextSize}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for numeric literals with a string representation longer than ten diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs index 830107855e9bb3..2177f92e78deeb 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_in_class_body.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for the presence of the `pass` statement in non-empty class bodies diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs index 8ecd9f23f834e7..f4a7f64538408a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pass_statement_stub_body.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `pass` statements in empty stub bodies. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs index 344cb2274487c2..2b83f7783df585 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/pre_pep570_positional_argument.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, ParameterWithDefault}; use ruff_python_semantic::analyze::function_type; +use crate::Violation; use crate::checkers::ast::Checker; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs index fd1d872a613892..152c54671bce07 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/prefix_type_params.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs index 529e1e06746822..63b610408b09a1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/quoted_annotation_in_stub.rs @@ -1,9 +1,9 @@ use ruff_text_size::TextRange; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs index 50c34c48209447..d710b4823f827e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, comparable::ComparableExpr}; use ruff_text_size::{Ranged, TextSize}; @@ -6,6 +5,7 @@ use ruff_text_size::{Ranged, TextSize}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for redundant `Final[Literal[...]]` annotations. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 7eb69aa53a6838..512849b70d06a5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -2,13 +2,13 @@ use std::fmt; use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs index 6d32946ca5485d..81ef4434fe191c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprNoneLiteral, Operator, PythonVersion, @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use smallvec::SmallVec; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for redundant `Literal[None]` annotations. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 3e5a47e60cb62b..c384433dddc804 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -1,12 +1,12 @@ use bitflags::bitflags; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyParameterRef, Expr, ExprBinOp, Operator, Parameters, PythonVersion}; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use super::generate_union_fix; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index bc3025d0f031d5..ad9d6137bd7040 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -1,5 +1,5 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::PythonVersion; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr, Operator, Parameters, Stmt, UnaryOp}; use ruff_python_semantic::{ScopeKind, SemanticModel, analyze::class::is_enumeration}; @@ -8,7 +8,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::flake8_pyi::rules::TypingModule; -use ruff_python_ast::PythonVersion; +use crate::{AlwaysFixableViolation, Edit, Fix, Violation}; /// ## What it does /// Checks for typed function arguments in stubs with complex default values. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs index 670d2022ec430d..6cb7ceaa2e2aa1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs @@ -1,13 +1,13 @@ use ruff_python_ast as ast; use ruff_python_ast::Stmt; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_abstract; use crate::checkers::ast::Checker; use crate::fix::edits::delete_stmt; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for redundant definitions of `__str__` or `__repr__` in stubs. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index a9d60412f4a263..76ae0e1e0b09c2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::{self as ast, StringLike}; @@ -6,6 +5,7 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the use of string and bytes literals longer than 50 characters diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs index e29aac789f2e0c..06b428d7d3a27a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/stub_body_multiple_statements.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_python_ast::identifier::Identifier; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs index d2c27f8f938efc..1333356a228aa4 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_alias_naming.rs @@ -1,8 +1,8 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs index 67f09e1ecca0c6..f8ac77d69c8a87 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs @@ -2,11 +2,11 @@ use std::sync::LazyLock; use regex::Regex; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use crate::Locator; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for the use of type comments (e.g., `x = 1 # type: int`) in stub diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs index d0bc616c095f19..ba3c63ac2f2ef7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs @@ -1,12 +1,11 @@ -use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Imported; use ruff_python_semantic::{Binding, BindingKind, Scope}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; - use crate::renamer::Renamer; +use crate::{Applicability, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `from collections.abc import Set` imports that do not alias diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index 5283220dc1858a..0062f070637409 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::pep_604_union; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the presence of multiple literal types in a union. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs index cf6505fa6c4d00..c2b7628b2f7865 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs @@ -1,5 +1,4 @@ use ast::ExprContext; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::pep_604_union; use ruff_python_ast::name::Name; @@ -8,6 +7,7 @@ use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the presence of multiple `type`s in a union. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs index 97914b05360a41..4cf4730471fd6a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs index cccf71d0734884..e01b18613f7058 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::{self as ast, CmpOp, Expr, Int}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs index 082cf4bdd8349b..e745f67210a712 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unsupported_method_call_on_all.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs index 5e481a724a251f..d5e6645642058c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unused_private_type_definition.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the presence of unused private `TypeVar`, `ParamSpec` or diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs index 71ba1e1de4abb8..795c3c7e5c7adc 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs @@ -8,7 +8,6 @@ use libcst_native::{ SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace, }; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::parenthesize::parenthesized_range; @@ -29,6 +28,7 @@ use crate::cst::matchers::match_indented_block; use crate::cst::matchers::match_module; use crate::fix::codemods::CodegenStylist; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; use super::unittest_assert::UnittestAssert; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs index 3c38e08c2b10e9..9498e51924df0d 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fail.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use super::helpers::{is_empty_or_null_string, is_pytest_fail}; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index c3b5e0cb998db6..634bf422e1e1df 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -1,5 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Decorator; use ruff_python_ast::helpers::map_callable; @@ -17,6 +15,8 @@ use rustc_hash::FxHashSet; use crate::checkers::ast::Checker; use crate::fix::edits; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Edit, Fix}; use super::helpers::{ Parentheses, get_mark_decorators, is_pytest_fixture, is_pytest_yield_fixture, diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs index e92b0f1fdc1f2c..2b6c1261852be0 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/imports.rs @@ -1,10 +1,9 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for incorrect import of pytest. diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs index 513398827d3a73..979cb7dff6c48a 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::helpers::{Parentheses, get_mark_decorators}; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs index a0f1d8f9bc9529..197802ed99ea52 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -1,6 +1,5 @@ use rustc_hash::{FxBuildHasher, FxHashMap}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::parenthesize::parenthesized_range; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::{Edit, Fix, FixAvailability, Violation}; use super::super::types; use super::helpers::{is_pytest_parametrize, split_names}; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs index 2954531a9cb39b..3936dbfff21723 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::visitor; @@ -6,6 +5,7 @@ use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, Parameters}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs index 4fc0d62833a2da..ce4763e83b4a06 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_compound_statement; use ruff_python_ast::{self as ast, Expr, Stmt, WithItem}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs index 1da6464166b85c..a188e03f90d39f 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/test_functions.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_pytest_style::rules::helpers::is_likely_pytest_test; -use ruff_diagnostics::{Edit, Fix, Violation}; +use crate::{Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtFunctionDef; use ruff_text_size::Ranged; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs index ceadb3ba08189c..9b3d6e07967d91 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_compound_statement; use ruff_python_ast::{self as ast, Expr, Stmt, WithItem}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs index 64b1b83de25cd5..b5b0ceadab0207 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs @@ -1,6 +1,5 @@ use flake8_quotes::helpers::{contains_escaped_quote, raw_contents, unescape_string}; use flake8_quotes::settings::Quote; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{Visitor, walk_f_string}; use ruff_python_ast::{self as ast, AnyStringFlags, PythonVersion, StringFlags, StringLike}; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::rules::flake8_quotes; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for strings that include escaped quotes, and suggests changing diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index 8ad5310f90d99f..6d8ae3237e989a 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StringLike; use ruff_text_size::{Ranged, TextRange}; @@ -6,6 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use super::super::settings::Quote; diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs index 8a58fccf00adc2..32ea80a749a0fa 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyStringFlags, StringFlags, StringLike}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::super::helpers::{contains_escaped_quote, raw_contents, unescape_string}; diff --git a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs index 236fbff0b41df7..37ae01b46b7fa7 100644 --- a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::BindingKind; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for unnecessary parentheses on raised exceptions. diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 5678c95714c870..9a42a7497ca324 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -2,8 +2,6 @@ use std::ops::Add; use anyhow::Result; -use ruff_diagnostics::{AlwaysFixableViolation, FixAvailability, Violation}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{is_const_false, is_const_true}; use ruff_python_ast::stmt_if::elif_else_range; @@ -22,6 +20,8 @@ use crate::fix::edits::adjust_indentation; use crate::preview::is_only_add_return_none_at_end_enabled; use crate::registry::Rule; use crate::rules::flake8_return::helpers::end_of_last_statement; +use crate::{AlwaysFixableViolation, FixAvailability, Violation}; +use crate::{Edit, Fix}; use super::super::branch::Branch; use super::super::helpers::result_exists; diff --git a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs index 7bb4ff3b21ae55..52b704d93a0f73 100644 --- a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs +++ b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{is_dunder, is_sunder}; use ruff_python_ast::name::UnqualifiedName; @@ -8,6 +7,7 @@ use ruff_python_semantic::analyze::typing::TypeChecker; use ruff_python_semantic::{BindingKind, ScopeKind, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::is_dunder_operator_method; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs index 07f2bcb20def58..e2a35821373067 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs @@ -6,7 +6,6 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Arguments, BoolOp, CmpOp, Expr, ExprContext, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{Truthiness, contains_effect}; @@ -17,6 +16,7 @@ use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for multiple `isinstance` calls on the same target. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index 8a8b5268fd2b09..ff1b0e83ea3a78 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -1,13 +1,13 @@ use ruff_python_ast::{self as ast, Arguments, Expr, str_prefix::StringLiteralPrefix}; use ruff_text_size::{Ranged, TextRange}; -use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::typing::is_dict; use crate::checkers::ast::Checker; +use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for environment variables that are not capitalized. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs index 8207ec4f3ff3cb..abcd4b214a2714 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs @@ -1,13 +1,13 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{is_const_false, is_const_true}; use ruff_python_ast::name::Name; use ruff_python_ast::parenthesize::parenthesized_range; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` expressions that can be replaced with `bool()` calls. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs index b615c4d9080ab8..4b7c249b2b82b7 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, Arguments, CmpOp, Expr, ExprContext, Stmt, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_semantic::ScopeKind; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for negated `==` operators. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs index 06cd73d100d0b6..537fb1f9d905d3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs @@ -1,17 +1,17 @@ use anyhow::bail; use ast::Expr; -use ruff_diagnostics::Fix; -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt, WithItem}; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange}; use super::fix_with; +use crate::Fix; use crate::checkers::ast::Checker; use crate::fix::edits::fits; use crate::preview::multiple_with_statements_fix_safe_enabled; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for the unnecessary nesting of multiple consecutive context diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs index fc590eaf58c816..04039a8f5f68d3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use anyhow::{Result, bail}; use libcst_native::ParenthesizedNode; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{self as ast, ElifElseClause, Expr, Stmt, whitespace}; @@ -19,6 +18,7 @@ use crate::cst::helpers::space; use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement}; use crate::fix::codemods::CodegenStylist; use crate::fix::edits::fits; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for nested `if` statements that can be collapsed into a single `if` diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs index e3c66683b87d7c..94396576f48d5d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/enumerate_for_loop.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast, Expr, Int, Number, Operator, Stmt}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs index bf1f681d43979a..e518753f40debd 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs @@ -1,13 +1,13 @@ use anyhow::{Result, bail}; use libcst_native::{CompoundStatement, Statement, Suite, With}; -use ruff_diagnostics::Edit; use ruff_python_ast as ast; use ruff_python_ast::whitespace; use ruff_python_codegen::Stylist; use ruff_source_file::LineRanges; use ruff_text_size::Ranged; +use crate::Edit; use crate::Locator; use crate::cst::matchers::{match_function_def, match_indented_block, match_statement, match_with}; use crate::fix::codemods::CodegenStylist; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs index 35b40b17d9ed23..12eb987bde0310 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::fits; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` statements that can be replaced with `dict.get` calls. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs index 55a2baee3ea981..2d116658dbcb45 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs @@ -1,6 +1,5 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableLiteral; use ruff_python_ast::helpers::contains_effect; @@ -8,6 +7,7 @@ use ruff_python_ast::{self as ast, CmpOp, ElifElseClause, Expr, Stmt}; use ruff_python_semantic::analyze::typing::{is_sys_version_block, is_type_checking_block}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index 278f7e9b2640f7..7de2bb14d33ccc 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::fits; use crate::preview::is_simplify_ternary_to_binary_enabled; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for `if`-`else`-blocks that can be replaced with a ternary operator. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs index 76fa1d5ef029dd..404c78e260475d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use anyhow::Result; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableStmt; use ruff_python_ast::parenthesize::parenthesized_range; @@ -14,6 +13,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` branches with identical arm bodies. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs index 5a21b7a9da47e6..ff15fe659034a9 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs @@ -1,5 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; -use ruff_diagnostics::{Applicability, Edit}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::parenthesize::parenthesized_range; @@ -9,6 +7,8 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix}; +use crate::{Applicability, Edit}; /// ## What it does /// Checks for key-existence checks against `dict.keys()` calls. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 40c5cc9f5a85f3..f313430121e50a 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::traversal; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` statements that can be replaced with `bool`. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 3c664654e62fc4..7005485d2b5971 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index c021a8ca0c1d75..f3737535edaec7 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::name::Name; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::fits; use crate::line_width::LineWidthBuilder; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `for` loops that can be replaced with a builtin function, like diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs index 707e9b3ad92475..636b490673a581 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/return_in_try_except_finally.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs index 780d985b47614c..44d384038819b6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/split_static_string.rs @@ -1,6 +1,5 @@ use std::cmp::Ordering; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral, StringLiteralFlags, @@ -9,6 +8,7 @@ use ruff_python_ast::{ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for static `str.split` calls that can be replaced with list literals. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs index 03d01cc898264b..3980e2692a01d6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers; use ruff_python_ast::name::UnqualifiedName; @@ -9,6 +8,7 @@ use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `try`-`except`-`pass` blocks that can be replaced with the diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs index f741bbeb148a74..687af811c9cb23 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs @@ -3,7 +3,6 @@ use std::cmp; use anyhow::Result; use libcst_native::CompOp; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr, UnaryOp}; use ruff_python_codegen::Stylist; @@ -16,6 +15,7 @@ use crate::cst::helpers::or_space; use crate::cst::matchers::{match_comparison, transform_expression}; use crate::fix::edits::pad; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for conditions that position a constant on the left-hand side of the diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs index 31dbcf72663d20..60384439bd09c9 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/zip_dict_keys_and_values.rs @@ -1,11 +1,11 @@ use ast::{ExprAttribute, ExprName, Identifier}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; +use ruff_python_semantic::analyze::typing::is_dict; use ruff_text_size::Ranged; +use crate::{AlwaysFixableViolation, Edit, Fix}; use crate::{checkers::ast::Checker, fix::snippet::SourceCodeSnippet}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; -use ruff_python_semantic::analyze::typing::is_dict; /// ## What it does /// Checks for use of `zip()` to iterate over keys and values of a dictionary at once. diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs index b10f8473d1ad49..d4ac9fcc68815a 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt, StmtClassDef, identifier::Identifier}; use ruff_python_semantic::SemanticModel; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_slots::rules::helpers::has_slots; diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs index 3603db1709ed85..020b8a529e68bf 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{Arguments, Expr, Stmt, StmtClassDef}; use ruff_python_semantic::{SemanticModel, analyze::class::is_enumeration}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_slots::rules::helpers::has_slots; diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs index c95db4be111191..b2d118b6c3def5 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{Arguments, Stmt, StmtClassDef}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::identifier::Identifier; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_slots::rules::helpers::has_slots; diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs index 9c65b36a83a261..c51d016e6ba7f5 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_tidy_imports::matchers::NameMatchPolicy; diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs index dee32f183171ee..11283e72022c7f 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_module_level_imports.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_python_ast::{Alias, AnyNodeRef, Stmt, StmtImport, StmtImportFrom}; use ruff_text_size::Ranged; use std::borrow::Cow; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_tidy_imports::matchers::{MatchName, MatchNameOrParent, NameMatchPolicy}; diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs index f181a894c707b3..73dd4b82b997a9 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs @@ -1,13 +1,13 @@ use ruff_python_ast::{self as ast, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_python_codegen::Generator; use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::flake8_tidy_imports::settings::Strictness; diff --git a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs index 51459459d5f058..e3516650b5ae30 100644 --- a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs @@ -2,13 +2,13 @@ use std::sync::LazyLock; use regex::RegexSet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; use crate::directives::{TodoComment, TodoDirective, TodoDirectiveKind}; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; /// ## What it does /// Checks that a TODO comment is labelled with "TODO". diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs index 6c432cb8e2468b..2b3d60c56e201a 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs @@ -1,6 +1,5 @@ use std::cmp::Reverse; -use ruff_diagnostics::Edit; use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::str::Quote; @@ -13,6 +12,7 @@ use ruff_python_semantic::{ }; use ruff_text_size::{Ranged, TextRange}; +use crate::Edit; use crate::Locator; use crate::rules::flake8_type_checking::settings::Settings; diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs index 11f11825a67841..c6b935929d5e82 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/empty_type_checking_block.rs @@ -1,12 +1,12 @@ use ruff_python_ast as ast; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for an empty type-checking block. diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs index 5dbb249f7c2f94..1db267f67703b9 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_cast_value.rs @@ -1,11 +1,11 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::rules::flake8_type_checking::helpers::quote_type_expression; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for unquoted type expressions in `typing.cast()` calls. diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs index 1d9cb39e0841f1..8aecca5d57825c 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use anyhow::Result; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Imported, NodeId, Scope, ScopeId}; use ruff_text_size::Ranged; @@ -14,6 +13,7 @@ use crate::fix; use crate::importer::ImportedMembers; use crate::rules::flake8_type_checking::helpers::{filter_contained, quote_annotation}; use crate::rules::flake8_type_checking::imports::ImportBinding; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for imports that are required at runtime but are only defined in diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs index 42ac5c1b917fa6..2391cbf1576cc1 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_string_union.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Expr, Operator}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs index f478fda4c990f7..cf47b80d931570 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/type_alias_quotes.rs @@ -1,5 +1,4 @@ use ast::{ExprContext, Operator}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Expr, Stmt}; @@ -10,6 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::flake8_type_checking::helpers::quote_type_expression; +use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index d4a183e9055808..e91d00409d005f 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use anyhow::Result; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, Imported, NodeId, Scope}; use ruff_text_size::{Ranged, TextRange}; @@ -19,6 +18,7 @@ use crate::rules::flake8_type_checking::helpers::{ use crate::rules::flake8_type_checking::imports::ImportBinding; use crate::rules::isort::categorize::MatchSourceStrategy; use crate::rules::isort::{ImportSection, ImportType, categorize}; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for first-party imports that are only used for type annotations, but diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs index b6ef9c1549fde6..774f689ed04745 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/rules/unused_arguments.rs @@ -1,12 +1,12 @@ use ruff_python_ast as ast; use ruff_python_ast::{Parameter, Parameters, Stmt, StmtExpr, StmtFunctionDef, StmtRaise}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::{function_type, visibility}; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/glob_rule.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/glob_rule.rs index 64f514f9d540a0..3c329bf20d769d 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/glob_rule.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/glob_rule.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for the use of `glob.glob()` and `glob.iglob()`. /// diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs index 167700d37e9483..708d418a24967d 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/invalid_pathlib_with_suffix.rs @@ -1,5 +1,5 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, StringFlags}; use ruff_python_semantic::SemanticModel; diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getatime.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getatime.rs index c9655d89441257..78c5bfe06eb072 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getatime.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getatime.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `os.path.getatime`. /// diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getctime.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getctime.rs index a7e2fdb58dc538..866c37e306b65d 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getctime.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getctime.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `os.path.getctime`. /// diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getmtime.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getmtime.rs index b9c84d0c3dcd5d..81ec1b53c21742 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getmtime.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getmtime.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `os.path.getmtime`. /// diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getsize.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getsize.rs index 829a5c4a721edf..5dd42507a0f813 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getsize.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_path_getsize.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `os.path.getsize`. /// diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs index a35ca49b537173..f6e01d037cb673 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs index a9475bf6bf7ad1..30095f629bbd1c 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs @@ -1,6 +1,5 @@ use std::ops::Range; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{Expr, ExprBinOp, ExprCall, Operator}; @@ -10,6 +9,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for `pathlib.Path` objects that are initialized with the current diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs index 1bed2ba4bf5f70..b64ffe50cfd954 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/violations.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `os.path.abspath`. /// diff --git a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs index 1af7fc68d9fa59..968c386b514707 100644 --- a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs @@ -1,14 +1,14 @@ use ast::FStringFlags; use itertools::Itertools; -use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::fix::edits::pad; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; use crate::rules::flynt::helpers; diff --git a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs index 51162773c1df5c..b1d4d9a6137557 100644 --- a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::{self as ast, ModModule, PySourceType, Stmt}; @@ -10,6 +9,7 @@ use ruff_text_size::{TextRange, TextSize}; use crate::Locator; use crate::importer::Importer; use crate::settings::LinterSettings; +use crate::{AlwaysFixableViolation, Diagnostic, Fix}; /// ## What it does /// Adds any required imports, as specified by the user, to the top of the diff --git a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index 83c13f3f4a95ef..b37e4b4a9c51cb 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -1,6 +1,5 @@ use itertools::{EitherOrBoth, Itertools}; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::whitespace::trailing_lines_end; use ruff_python_ast::{PySourceType, PythonVersion, Stmt}; @@ -19,6 +18,7 @@ use crate::package::PackageRoot; use crate::preview::is_full_path_match_source_strategy_enabled; use crate::rules::isort::categorize::MatchSourceStrategy; use crate::settings::LinterSettings; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// De-duplicates, groups, and sorts imports based on the provided `isort` settings. diff --git a/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs b/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs index b5c7ddd46aea9f..8fe6a908dcdc5c 100644 --- a/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs +++ b/crates/ruff_linter/src/rules/mccabe/rules/function_is_too_complex.rs @@ -1,8 +1,8 @@ -use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; + +use crate::Violation; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs index 03343ae61eabdc..b34f4b917d38cc 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of deprecated NumPy functions. diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs index 1b4225d185a27c..daa445642ad066 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for deprecated NumPy type aliases. diff --git a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs index 5317c3c2c79aed..cbe58e2122729c 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs index 254fdba2643194..bcc712b18dcf2b 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs @@ -1,5 +1,3 @@ -use crate::rules::numpy::helpers::{AttributeSearcher, ImportSearcher}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedNameBuilder; use ruff_python_ast::statement_visitor::StatementVisitor; @@ -10,6 +8,8 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::rules::numpy::helpers::{AttributeSearcher, ImportSearcher}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of NumPy functions and constants that were removed from diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs index c179fcb392d234..b24fd8f0bbd5cf 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs @@ -1,10 +1,8 @@ -use ruff_python_ast::{self as ast, Expr}; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for assignments to the variable `df`. diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs index 7c95a0ca562a0e..ab6b55104bce19 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pandas_vet::helpers::{Resolution, test_expression}; diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs index 86c5e600c9c70b..df8e47a861237c 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/call.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::pandas_vet::helpers::{Resolution, test_expression}; diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs index 46f74c79e884b4..99a483cee2491c 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::parenthesize::parenthesized_range; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `inplace=True` usages in `pandas` function and method diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs index 57e4ea7efbcf8a..5656e4489f3681 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr, Int}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pandas_vet::helpers::{Resolution, test_expression}; diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs index 569378888dac96..d8c90b74ac509c 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/pd_merge.rs @@ -1,11 +1,12 @@ use ruff_python_ast::{self as ast, Expr}; -use crate::checkers::ast::Checker; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for uses of `pd.merge` on Pandas objects. /// diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs index 4353b58ae04500..776e738bbb2477 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs index efef6e806ca99a..ce1ff5bcc41c68 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/subscript.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::pandas_vet::helpers::{Resolution, test_expression}; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs index fe34a67ef347b7..282e48591fcd1a 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_acronym.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str::{self}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs index 0f6eff642587b1..609cd61fc09385 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_constant.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::str::{self}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; use crate::rules::pep8_naming::settings::IgnoreNames; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs index 289e22755f40b4..c81884c0458a0a 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/camelcase_imported_as_lowercase.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; use crate::rules::pep8_naming::settings::IgnoreNames; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs index df667f3ef5a323..26e72de9289342 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/constant_imported_as_non_constant.rs @@ -1,13 +1,11 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; -use crate::{ - checkers::ast::Checker, - rules::pep8_naming::{helpers, settings::IgnoreNames}, -}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pep8_naming::{helpers, settings::IgnoreNames}; /// ## What it does /// Checks for constant imports that are aliased to non-constant-style diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs index 6ee2bc8af07ae2..c4b8f86299ced5 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -1,11 +1,11 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::{Scope, ScopeKind}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::settings::IgnoreNames; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs index 2d8c4669e21e96..ecaa71ee208b73 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/error_suffix_on_exception_name.rs @@ -1,10 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; -use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pep8_naming::settings::IgnoreNames; /// ## What it does /// Checks for custom exception definitions that omit the `Error` suffix. diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs index 604234982ae784..855e987e8f62c8 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_argument_name.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ExprLambda, Parameters, StmtFunctionDef}; use ruff_python_semantic::ScopeKind; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::visibility::is_override; use ruff_python_stdlib::str; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs index 61d7ff398879b8..c220d4d41abfc7 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_class_name.rs @@ -1,10 +1,11 @@ use ruff_python_ast::Stmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; -use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pep8_naming::settings::IgnoreNames; /// ## What it does /// Checks for class names that do not follow the `CamelCase` convention. diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index 10a622de1e8bdc..ecbc4eb24fa130 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -1,6 +1,5 @@ use anyhow::Result; -use ruff_diagnostics::{Fix, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::registry::Rule; use crate::renamer::Renamer; +use crate::{Fix, Violation}; /// ## What it does /// Checks for instance methods that use a name other than `self` for their @@ -64,8 +64,7 @@ pub(crate) struct InvalidFirstArgumentNameForMethod { } impl Violation for InvalidFirstArgumentNameForMethod { - const FIX_AVAILABILITY: ruff_diagnostics::FixAvailability = - ruff_diagnostics::FixAvailability::Sometimes; + const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes; #[derive_message_formats] fn message(&self) -> String { @@ -137,8 +136,7 @@ pub(crate) struct InvalidFirstArgumentNameForClassMethod { } impl Violation for InvalidFirstArgumentNameForClassMethod { - const FIX_AVAILABILITY: ruff_diagnostics::FixAvailability = - ruff_diagnostics::FixAvailability::Sometimes; + const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes; #[derive_message_formats] // The first string below is what shows up in the documentation diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs index 6618cbe3c8fdbf..566c55292983e4 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_function_name.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{Decorator, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::visibility; use ruff_python_stdlib::str; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::settings::IgnoreNames; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs index ccf651b723210c..0774358df3d6c4 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -1,15 +1,16 @@ use std::ffi::OsStr; use std::path::Path; -use crate::package::PackageRoot; -use crate::rules::pep8_naming::settings::IgnoreNames; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PySourceType; use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name}; use ruff_python_stdlib::path::is_module_file; use ruff_text_size::TextRange; +use crate::package::PackageRoot; +use crate::rules::pep8_naming::settings::IgnoreNames; +use crate::{Diagnostic, Violation}; + /// ## What it does /// Checks for module names that do not follow the `snake_case` naming /// convention or are otherwise invalid. diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs index ad838b0bc58662..6d2df2e8f14dfd 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/lowercase_imported_as_non_lowercase.rs @@ -1,10 +1,11 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; -use crate::{checkers::ast::Checker, rules::pep8_naming::settings::IgnoreNames}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pep8_naming::settings::IgnoreNames; /// ## What it does /// Checks for lowercase imports that are aliased to non-lowercase names. diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs index 8380762b5d966e..50fe2df5c50189 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs index 670e3fadbc8121..09ff4be6c4bbe8 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_global_scope.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs index efcf590837af7c..85050984d56ea5 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::str; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pep8_naming::helpers; diff --git a/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs b/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs index ed7710ffb6bae4..b26e7b309b89b2 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/incorrect_dict_iterator.rs @@ -1,6 +1,5 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::{Arguments, Expr}; @@ -8,6 +7,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `dict.items()` that discard either the key or the value diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs index c3b17b2d16b766..298f62af968ce3 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ self as ast, Expr, Stmt, comparable::ComparableExpr, helpers::any_over_expr, @@ -10,6 +9,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::preview::is_fix_manual_dict_comprehension_enabled; use crate::rules::perflint::helpers::{comment_strings_in_range, statement_deletion_range}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `for` loops that can be replaced by a dictionary comprehension. diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs index 461092bb972594..2566ecb770ce62 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Expr}; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::{ checkers::ast::Checker, preview::is_fix_manual_list_comprehension_enabled, rules::perflint::helpers::statement_deletion_range, }; use anyhow::{Result, anyhow}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use crate::rules::perflint::helpers::comment_strings_in_range; use ruff_macros::{ViolationMetadata, derive_message_formats}; diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs index dc8fe2a6464d4e..7e9c9b103e63b7 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; use ruff_python_semantic::analyze::typing::is_list; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs b/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs index ee1fae0b145938..87f89bf18ef37c 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/try_except_in_loop.rs @@ -1,11 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; -use ruff_python_ast::{self as ast, Stmt}; +use ruff_python_ast::{self as ast, PythonVersion, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; -use ruff_python_ast::PythonVersion; /// ## What it does /// Checks for uses of except handling via `try`-`except` within `for` and diff --git a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs index 72a6d753034b8f..5e9964e2922aae 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::typing::find_assigned_value; use ruff_text_size::TextRange; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for explicit casts to `list` on for-loop iterables. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs index 11af69e0aae11d..6bf061ede56766 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_class_name.rs @@ -1,10 +1,11 @@ use ruff_python_ast::Identifier; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::{checkers::ast::Checker, rules::pycodestyle::helpers::is_ambiguous_name}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pycodestyle::helpers::is_ambiguous_name; /// ## What it does /// Checks for the use of the characters 'l', 'O', or 'I' as class names. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs index 6d74c79d39ffb0..1cd8770c86ab06 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_function_name.rs @@ -1,10 +1,11 @@ use ruff_python_ast::Identifier; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::{checkers::ast::Checker, rules::pycodestyle::helpers::is_ambiguous_name}; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::pycodestyle::helpers::is_ambiguous_name; /// ## What it does /// Checks for the use of the characters 'l', 'O', or 'I' as function names. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs index 1f4a6f56831d2b..b16747ed191cae 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/ambiguous_variable_name.rs @@ -1,8 +1,8 @@ use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pycodestyle::helpers::is_ambiguous_name; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs index 571d327ccf1964..3040240166149d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::except; use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 0e690f70d2a4a5..906417cd899d0f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -5,10 +5,6 @@ use std::slice::Iter; use itertools::Itertools; -use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::Diagnostic; -use ruff_diagnostics::Edit; -use ruff_diagnostics::Fix; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; @@ -21,6 +17,10 @@ use ruff_source_file::{LineRanges, UniversalNewlines}; use ruff_text_size::TextRange; use ruff_text_size::TextSize; +use crate::AlwaysFixableViolation; +use crate::Diagnostic; +use crate::Edit; +use crate::Fix; use crate::Locator; use crate::checkers::logical_lines::expand_indent; use crate::line_width::IndentWidth; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs index 19c79cd78a8f21..cf93c6ab3a4735 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs @@ -1,5 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; @@ -8,6 +6,8 @@ use ruff_python_parser::{TokenIterWithContext, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextSize}; use crate::Locator; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Diagnostic, Edit, Fix}; /// ## What it does /// Checks for compound statements (multiple statements on the same line). diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs index f906523c5d9b0b..677b5ac374a821 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::Line; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for doc lines that exceed the specified maximum character length. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/errors.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/errors.rs index cc2f79d70c19e2..58baf660bc9c96 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/errors.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/errors.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// This is not a regular diagnostic; instead, it's raised when a file cannot be read /// from disk. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs index 8968a32827a367..d5e0107e162c40 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs @@ -1,6 +1,5 @@ use memchr::memchr_iter; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyStringFlags, FStringElement, StringLike, StringLikePart}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::pad_start; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for invalid escape sequences. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index eb8f15374164e5..ec00bf84f10e75 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{ @@ -11,6 +10,7 @@ use ruff_source_file::UniversalNewlines; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for lambda expressions which are assigned to a variable. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs index 2cebe72cb239aa..743c47b31e5031 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::Line; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for lines that exceed the specified maximum character length. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs index 7e261841386146..837dc7073124a1 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs @@ -1,7 +1,6 @@ use ruff_python_ast::parenthesize::parenthesized_range; use rustc_hash::FxHashMap; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::{self, generate_comparison}; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -10,6 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum EqCmpOp { diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs index 046de04af01bcf..b4a3f36efb1f00 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs @@ -1,11 +1,11 @@ -use ruff_diagnostics::AlwaysFixableViolation; -use ruff_diagnostics::Diagnostic; -use ruff_diagnostics::Edit; -use ruff_diagnostics::Fix; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; +use crate::AlwaysFixableViolation; +use crate::Diagnostic; +use crate::Edit; +use crate::Fix; use crate::checkers::logical_lines::LogicalLinesContext; use super::{LogicalLine, Whitespace}; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs index e9c828bd8f3607..409a2dd8993059 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs @@ -1,9 +1,10 @@ -use ruff_diagnostics::Diagnostic; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::TextRange; +use crate::Diagnostic; +use crate::Violation; + use super::LogicalLine; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs index 901d6203001453..46e752391f6e48 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Edit; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::Ranged; +use crate::Edit; use crate::checkers::logical_lines::LogicalLinesContext; +use crate::{AlwaysFixableViolation, Diagnostic, Fix}; use super::{DefinitionState, LogicalLine}; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs index d769462d37d77c..011a6c65733d81 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::Ranged; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for missing whitespace after keywords. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs index 8538d4acf57a74..fcad4a9b3820b2 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; @@ -6,6 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::helpers::is_non_logical_token; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for missing whitespace around all operators. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs index e4c63e50b31274..54ee31a98e2aad 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_index::Indexer; use ruff_python_parser::TokenKind; @@ -7,6 +6,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use super::LogicalLine; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs index 6fe485d76459eb..d05eba21e966d3 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use super::{LogicalLine, Whitespace}; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs index 5e0e183e249264..10528e2eb46bd1 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use super::{LogicalLine, Whitespace}; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs index 66d18bf2ca328f..9ede7a35d305e1 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for missing whitespace around the equals sign in an unannotated diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs index ee1bda94cf4a5e..cbac6fec8bba25 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_python_trivia::PythonWhitespace; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks if inline comments are separated by at least two spaces. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs index 56aa36d50e29ac..d519b682976ab1 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for extraneous whitespace immediately preceding an open parenthesis diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs index 8470ce9f9dc4a6..22f3291df5209a 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_codegen::Stylist; use ruff_text_size::{TextLen, TextRange}; use crate::Locator; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for files missing a new line at the end of the file. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs index 6d86ca97752b63..106f9d64d0bafc 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs @@ -1,10 +1,11 @@ use ruff_text_size::{TextLen, TextRange}; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::leading_indentation; use ruff_source_file::Line; +use crate::{Diagnostic, Violation}; + /// ## What it does /// Checks for mixed tabs and spaces in indentation. /// diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs index 307549e3f170f4..066de7316fbadd 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/module_import_not_at_top_of_file.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{PySourceType, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs index fa9bfe59490ad8..01a57c99c914af 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs @@ -1,6 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Alias, Stmt}; use ruff_python_codegen::Stylist; @@ -11,6 +10,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for multiple imports on one line. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs index 90eb83f9ec719f..d9f6b84d488472 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs @@ -1,12 +1,12 @@ -use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::generate_comparison; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::fix::edits::pad; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for membership tests using `not {element} in {collection}`. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs index 78daee9727c62f..e7a2a6ac044ab9 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_index::Indexer; use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for indentation that uses tabs. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs index 602c232d52102e..aae55c29ed4b8a 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs @@ -1,12 +1,13 @@ use std::iter::Peekable; use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_notebook::CellOffsets; use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange, TextSize}; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; + /// ## What it does /// Checks for files with multiple trailing blank lines. /// diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs index 3c1e8f9bfb49f2..658af77b97993a 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_index::Indexer; use ruff_source_file::Line; @@ -7,6 +6,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; use crate::registry::Rule; use crate::settings::LinterSettings; +use crate::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for superfluous trailing whitespace. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs index 296271d6c8e162..b7a0c0ca86c00c 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs @@ -1,11 +1,11 @@ use itertools::Itertools; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs index b142253f77b742..aff2c0fd193e5d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/whitespace_after_decorator.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Decorator; use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for trailing whitespace after a decorator's opening `@`. diff --git a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs index 4bb9865a17fbf3..e6c6a17b0d5e67 100644 --- a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs +++ b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs @@ -1,5 +1,4 @@ use itertools::Itertools; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::helpers::map_subscript; @@ -11,6 +10,7 @@ use ruff_python_semantic::{Definition, SemanticModel}; use ruff_source_file::NewlineWithTrailingNewline; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind}; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs index fb8e75f2e13d22..a062d173bd18b4 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/backslashes.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for docstrings that include backslashes, but are not defined as diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs index 371d0a7ed7ea06..50e6a75d3d219f 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_after_summary.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for docstring summary lines that are not separated from the docstring diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs index 6e31de56553d91..05c6cdf1aad46c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::{PythonWhitespace, indentation_at_offset}; use ruff_source_file::{Line, LineRanges, UniversalNewlineIterator}; @@ -8,6 +7,7 @@ use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for docstrings on class definitions that are not preceded by a diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs index 0d3ae7a3fd34a9..82ff0859790413 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_function.rs @@ -1,7 +1,6 @@ use regex::Regex; use std::sync::LazyLock; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::PythonWhitespace; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; @@ -11,6 +10,7 @@ use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for docstrings on functions that are separated by one or more blank diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs index 5448705c0629e8..ccc082ba38b4ad 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for docstrings that do not start with a capital letter. diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs index 173ad2552d26bc..11f7915c338ef1 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs @@ -1,7 +1,6 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::docstrings::sections::SectionKind; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::pydocstyle::helpers::logical_line; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs index 070bd61103fe9f..6b4d25fa56f006 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs @@ -1,7 +1,6 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::docstrings::sections::SectionKind; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::pydocstyle::helpers::logical_line; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs index d9f528fe142033..0ad276b81616f2 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/if_needed.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_overload; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs index a6f044b2c55d13..4e9256f0def3a6 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/indent.rs @@ -1,5 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_source_file::{Line, NewlineWithTrailingNewline}; @@ -9,6 +7,8 @@ use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Edit, Fix}; #[expect(clippy::tabs_in_doc_comments)] /// ## What it does diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs index 5d2e1270106fe4..12bbc25d3114f0 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::is_triple_quote; use ruff_python_semantic::Definition; @@ -10,6 +9,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for docstring summary lines that are not positioned on the first diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs index da56dbe1ae7283..afdaf68348ea70 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/newline_after_last_paragraph.rs @@ -1,6 +1,5 @@ use ruff_text_size::{TextLen, TextSize}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::clean_space; use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlines}; @@ -8,6 +7,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for multi-line docstrings whose closing quotes are not on their diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs index 2ca5cd7cab79aa..5a06ae6022d9fc 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/no_signature.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs index be8da37b62edf7..fd9ee2798e751e 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::NewlineWithTrailingNewline; use ruff_text_size::Ranged; @@ -6,6 +5,7 @@ use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::pydocstyle::helpers::ends_with_backslash; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs index 6a138f49b0d0ee..6369e355de29ca 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs @@ -2,12 +2,12 @@ use std::sync::LazyLock; use imperative::Mood; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::visibility::{is_property, is_test}; use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::rules::pydocstyle::helpers::normalize_word; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs index ef92f1ede4ce4a..c23e33a85b4b87 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/not_empty.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs index e243e946dacd00..1052b80f0a2ecf 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/not_missing.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::{ @@ -7,6 +6,7 @@ use ruff_python_semantic::analyze::visibility::{ use ruff_python_semantic::{Definition, Member, MemberKind, Module, ModuleKind}; use ruff_text_size::TextRange; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs index 1dafa14cac5a8b..158092f1107923 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/one_liner.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::NewlineWithTrailingNewline; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for single-line docstrings that are broken across multiple lines. diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index b59492c483943b..d7da286b169ac8 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -3,8 +3,6 @@ use regex::Regex; use rustc_hash::FxHashSet; use std::sync::LazyLock; -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_python_ast::identifier::Identifier; @@ -20,6 +18,8 @@ use crate::docstrings::styles::SectionStyle; use crate::registry::Rule; use crate::rules::pydocstyle::helpers::find_underline; use crate::rules::pydocstyle::settings::Convention; +use crate::{AlwaysFixableViolation, Violation}; +use crate::{Edit, Fix}; /// ## What it does /// Checks for over-indented sections in docstrings. diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs index a405786043afa0..1bbcc9ab4ca1a3 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/starts_with_this.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::rules::pydocstyle::helpers::normalize_word; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs index ef428f8e01d98b..374ac693697584 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::str::Quote; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for docstrings that use `'''triple single quotes'''` instead of diff --git a/crates/ruff_linter/src/rules/pyflakes/fixes.rs b/crates/ruff_linter/src/rules/pyflakes/fixes.rs index d71a8cb468209d..f2d6e64af3d8be 100644 --- a/crates/ruff_linter/src/rules/pyflakes/fixes.rs +++ b/crates/ruff_linter/src/rules/pyflakes/fixes.rs @@ -1,6 +1,5 @@ use anyhow::{Context, Ok, Result}; -use ruff_diagnostics::Edit; use ruff_python_ast as ast; use ruff_python_ast::Expr; use ruff_python_codegen::Stylist; @@ -8,6 +7,7 @@ use ruff_python_semantic::Binding; use ruff_python_trivia::{BackwardsTokenizer, SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::Ranged; +use crate::Edit; use crate::Locator; use crate::cst::matchers::{match_call_mut, match_dict, transform_expression}; diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs b/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs index d1cd8a7404e9ac..798f9bb990b43e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/assert_tuple.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs b/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs index dc77d7bd072571..88b77c2cb48edc 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/break_outside_loop.rs @@ -1,10 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for `break` statements outside of loops. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs b/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs index d91863daf695f0..a6e426b9d56534 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/continue_outside_loop.rs @@ -1,10 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for `continue` statements outside of loops. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs index fe97acddc00527..42a45f3bf42008 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::except; use ruff_python_ast::{self as ast, ExceptHandler}; use crate::Locator; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs index f15694b4276f73..4262a7baa9f88c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for f-strings that do not contain any placeholder expressions. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs b/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs index 3208461cd2a2dd..41c3796563e2d3 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for forward annotations that include invalid syntax. /// diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs b/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs index 3589e787461c31..d99f90dd7b733d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/future_feature_not_defined.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_stdlib::future::is_feature_name; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs b/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs index f0f874bb390bc4..75fb7ce65a19f8 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/if_tuple.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{Expr, StmtIf}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::stmt_if::if_elif_branches; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/imports.rs b/crates/ruff_linter/src/rules/pyflakes/rules/imports.rs index 34756acafb0211..170e4b17143e62 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/imports.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/imports.rs @@ -1,7 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::SourceRow; +use crate::Violation; + /// ## What it does /// Checks for import bindings that are shadowed by loop variables. /// diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs index 15b6c974676bf4..6a6fa725175463 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_literal_comparisons.rs @@ -1,6 +1,5 @@ use anyhow::{Error, bail}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers; use ruff_python_ast::{CmpOp, Expr}; @@ -8,6 +7,7 @@ use ruff_python_parser::{TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `is` and `is not` comparisons against literals, like integers, diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs index ef2faac12bf41a..146917dd455cc5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/invalid_print_syntax.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs b/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs index a66ebda0035e59..e19fd97b5d7e3f 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/raise_not_implemented.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `raise` statements that raise `NotImplemented`. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs b/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs index d46c3c78026b66..cec4d60495298d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs @@ -1,7 +1,8 @@ -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::SourceRow; +use crate::{FixAvailability, Violation}; + /// ## What it does /// Checks for variable definitions that redefine (or "shadow") unused /// variables. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs index a0e22fcc152f39..e769f1747c8987 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs @@ -1,7 +1,6 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use std::collections::hash_map::Entry; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::{ComparableExpr, HashableExpr}; use ruff_python_ast::parenthesize::parenthesized_range; @@ -11,6 +10,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; use crate::registry::Rule; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for dictionary literals that associate multiple values with the diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs b/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs index c4c8eb0c32877d..bd004db4543e0b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for `return` statements outside of functions. /// diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs b/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs index d9db11fc8fec68..ff1c00c57a57e7 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/starred_expressions.rs @@ -1,10 +1,9 @@ use ruff_python_ast::Expr; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for the use of too many expressions in starred assignment statements. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs index ad50e058ec95b0..8ef54ac5f2994b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs @@ -2,13 +2,13 @@ use std::string::ToString; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Fix, FixAvailability, Violation}; use super::super::cformat::CFormatSummary; use super::super::fixes::{ diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs index a7ba8f3b5d9a77..18c9ddc1d8d30d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for undefined names in `__all__`. /// diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs index 9b37ba4b8fe782..0af1d5f4fb6dc6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_local.rs @@ -1,10 +1,10 @@ use std::string::ToString; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Scope, ScopeId}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_name.rs b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_name.rs index d21d72e2670437..f1666bf9b3f5bb 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_name.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_name.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of undefined names. /// diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs index b37175a02c4354..8cd4e2bd3981fc 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_annotation.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Scope; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index ff0c15a2af86aa..68d49ed7f55375 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -4,7 +4,6 @@ use std::iter; use anyhow::{Result, anyhow, bail}; use std::collections::BTreeMap; -use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Stmt}; @@ -22,6 +21,7 @@ use crate::preview::{ use crate::registry::Rule; use crate::rules::isort::categorize::MatchSourceStrategy; use crate::rules::{isort, isort::ImportSection, isort::ImportType}; +use crate::{Applicability, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unused imports. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs index 16c1c69b1100e5..5b6296301bef4a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs @@ -1,6 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::parenthesize::parenthesized_range; @@ -11,6 +10,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::edits::delete_stmt; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the presence of unused variables in function scopes. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/yield_outside_function.rs b/crates/ruff_linter/src/rules/pyflakes/rules/yield_outside_function.rs index 22d3a4e8099dd6..7838c267db37f0 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/yield_outside_function.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/yield_outside_function.rs @@ -1,9 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::semantic_errors::YieldOutsideFunctionKind; +use crate::Violation; + #[derive(Debug, PartialEq, Eq)] pub(crate) enum DeferralKeyword { Yield, diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 39682ff7349735..fd284220ed2532 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::noqa::{self, Directive, FileNoqaDirectives, NoqaDirectives}; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for `noqa` annotations that suppress all diagnostics, as opposed to diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs index 6117169fd4aa64..ba233d6a95b61b 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs @@ -4,12 +4,12 @@ use anyhow::{Result, anyhow}; use memchr::memchr_iter; use regex::Regex; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_text_size::TextSize; use crate::Locator; +use crate::{Diagnostic, Violation}; /// ## What it does /// Check for `type: ignore` annotations that suppress all type warnings, as diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs index 5c23b05bc1b090..648702121c43c4 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/deprecated_log_warn.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::{FixAvailability, Violation}; + /// ## Removed /// This rule is identical to [G010] which should be used instead. /// diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs index ca1ec153c40782..766b6f4260c4d0 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/invalid_mock_access.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/no_eval.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/no_eval.rs index 192bd50d06f4dc..8615e4ae5102f2 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/no_eval.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/no_eval.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## Removed /// This rule is identical to [S307] which should be used instead. /// diff --git a/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs b/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs index 8baf775f86bc83..bc8bed77c77673 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/and_or_ternary.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::ViolationMetadata; +use crate::Violation; + /// ## Removal /// This rule was removed from Ruff because it was common for it to introduce behavioral changes. /// See [#9007](https://github.com/astral-sh/ruff/issues/9007) for more information. diff --git a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs index 648d535f9d0d95..3e752543092fc0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/await_outside_async.rs b/crates/ruff_linter/src/rules/pylint/rules/await_outside_async.rs index 9c768195b94931..e41275a1cab924 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/await_outside_async.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/await_outside_async.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for uses of `await` outside `async` functions. /// diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs index 1c9c613caddbdb..973d90bbadce0e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_dunder_method_name.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::is_known_dunder_method; diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs index 75d0e47fd51879..0427bab652a9a6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; use ruff_python_stdlib::open_mode::OpenMode; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs index f2abd808ff5ff3..20d979ffb27b59 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; @@ -7,6 +6,7 @@ use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::analyze::function_type::FunctionType; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs index 7ce68b0af3e798..b251bab3236a6a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_str_strip_call.rs @@ -3,12 +3,12 @@ use std::fmt; use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs index a31731b4f9352f..300f937d155eae 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprStringLiteral, StringFlags, StringLiteral}; use ruff_python_literal::{ @@ -11,6 +10,7 @@ use ruff_python_literal::{ }; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs index e61c7969456c41..2d9e165651d345 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs @@ -5,10 +5,10 @@ use ruff_python_literal::cformat::{CFormatPart, CFormatSpec, CFormatStrOrBytes, use ruff_text_size::Ranged; use rustc_hash::FxHashMap; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs index 9231d9b812a128..bba43cb06d607b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs @@ -1,7 +1,8 @@ -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; +use crate::{Diagnostic, Violation}; + const BIDI_UNICODE: [char; 10] = [ '\u{202A}', //{LEFT-TO-RIGHT EMBEDDING} '\u{202B}', //{RIGHT-TO-LEFT EMBEDDING} diff --git a/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs b/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs index f7b074c9a88ead..eefae02792e891 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/binary_op_exception.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs index ca6ae6ab943b85..1795e7ed57f025 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/boolean_chained_comparison.rs @@ -1,5 +1,4 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ BoolOp, CmpOp, Expr, ExprBoolOp, ExprCompare, @@ -8,6 +7,7 @@ use ruff_python_ast::{ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Check for chained boolean operations that can be simplified. diff --git a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs index 2a356816a57087..72b15426a7774d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs @@ -1,7 +1,6 @@ use anyhow::Result; use ast::whitespace::indentation; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, ElifElseClause, Stmt}; use ruff_python_codegen::Stylist; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::adjust_indentation; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `else` blocks that consist of a single `if` statement. diff --git a/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs index d880f8033a8f68..94217d05ced7d0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/compare_to_empty_string.rs @@ -2,10 +2,10 @@ use anyhow::bail; use itertools::Itertools; use ruff_python_ast::{self as ast, CmpOp, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs b/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs index 7d01f9ba8cc7f4..8eab156203d44e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/comparison_of_constant.rs @@ -1,10 +1,10 @@ use itertools::Itertools; use ruff_python_ast::{CmpOp, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs b/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs index a5b3f01f03421c..60697e17cd99e6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/comparison_with_itself.rs @@ -1,12 +1,12 @@ use itertools::Itertools; -use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{CmpOp, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; +use crate::fix::snippet::SourceCodeSnippet; /// ## What it does /// Checks for operations that compare a name to itself. diff --git a/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs b/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs index f71970f7db165e..a89baf379edd2e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/continue_in_finally.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs index 54039e19c40dc6..3efffacfeb15d3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_index_missing_items.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{ @@ -10,6 +9,7 @@ use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonTy use ruff_python_semantic::analyze::typing::is_dict; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs index 4ea558f94b8833..60c7b91d048c0b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::is_dict; use ruff_python_semantic::{Binding, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for dictionary unpacking in a for loop without calling `.items()`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs index 721be4d36ea68f..f33cb19424d1d8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, Arguments, Expr}; use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for duplicate base classes in class definitions. diff --git a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs index b47833050f2d44..4e607c70a8a0c2 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::{CommentRanges, is_python_whitespace}; use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for a # symbol appearing on a line not followed by an actual comment. diff --git a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs index 4a50be69c2fae2..72e1efbb03134d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprName, Identifier, StmtAnnAssign, StmtAssign, StmtClassDef, StmtFunctionDef, @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::class::{ use ruff_text_size::Ranged; use std::ops::BitOr; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs b/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs index 4c3d6304b6cd84..c92593740fc5b1 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_at_module_level.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs index 6ef6a8c97abecf..dfa63cd427270d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_statement.rs @@ -1,6 +1,6 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_variable_not_assigned.rs b/crates/ruff_linter/src/rules/pylint/rules/global_variable_not_assigned.rs index 3caa1476f06fb7..d6f3c6fa7d4fa0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_variable_not_assigned.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_variable_not_assigned.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for `global` variables that are not assigned a value in the current /// scope. diff --git a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs index dae0f80732e580..733f809e6b6095 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::parenthesize::parenthesized_range; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` statements that can be replaced with `min()` or `max()` diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs b/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs index f0d96e724f23b3..3464c26315daeb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_outside_top_level.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; +use crate::Violation; use crate::rules::flake8_tidy_imports::rules::BannedModuleImportPolicies; use crate::{ checkers::ast::Checker, codes::Rule, rules::flake8_tidy_imports::matchers::NameMatchPolicy, diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs b/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs index f70d744c1bd80f..dab7b517931581 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs @@ -2,12 +2,12 @@ use std::borrow::Cow; use itertools::Itertools; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{FromImport, Import, Imported, ResolvedReference, Scope}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::package::PackageRoot; diff --git a/crates/ruff_linter/src/rules/pylint/rules/import_self.rs b/crates/ruff_linter/src/rules/pylint/rules/import_self.rs index 1897f237f4f05b..03ba035f3e3f95 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/import_self.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/import_self.rs @@ -1,11 +1,10 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::resolve_imported_module_path; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for import statements that import the current module. diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs index 78a1095fdd51c3..e338280462d0bc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_format.rs @@ -1,9 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for invalid assignments to `__all__`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs index 3e77db99564de5..99d83fe98b6e1b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_all_object.rs @@ -1,9 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for the inclusion of invalid objects in `__all__`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs index 967c41394ace56..cb099e399aa729 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_bool_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs index 2b57f30983dcdb..9b5004e6bbafe3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_bytes_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs index 66ab20d2b54465..37d899b653d76d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs index 52dfa3365d19ae..7a2e1e3e0fb579 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs index ca88a11b673c86..dc18a2bcd93c93 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs index edc3ebd1664cb2..5cafafdac4df15 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_index_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs index cc5ca4c4028d89..7766332a5a6243 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_length_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs index f102d5305b61ba..fe2924d35bba90 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_str_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; @@ -9,6 +8,7 @@ use ruff_python_semantic::analyze::terminal::Terminal; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index f2753483994bcc..d6e66084f4c3cc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::{Token, TokenKind}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for strings that contain the control character `BS`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs b/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs index 96a8ff20407a44..92d18485ae4258 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/iteration_over_set.rs @@ -1,12 +1,12 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::comparable::HashableExpr; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for iteration over a `set` literal where each element in the set is diff --git a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs index f1e586b8a6fccf..894b854cbfccae 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs @@ -1,6 +1,3 @@ -use crate::checkers::ast::Checker; -use crate::fix::snippet::SourceCodeSnippet; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; @@ -8,6 +5,10 @@ use ruff_python_semantic::analyze::typing::find_binding_value; use ruff_python_semantic::{BindingId, SemanticModel}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; +use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; + /// ## What it does /// Checks for `len` calls on sequences in a boolean test context. /// diff --git a/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs b/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs index 9d398694da0a7a..2130cd29cf919a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/literal_membership.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for membership tests on `list` and `tuple` literals. diff --git a/crates/ruff_linter/src/rules/pylint/rules/load_before_global_declaration.rs b/crates/ruff_linter/src/rules/pylint/rules/load_before_global_declaration.rs index 7041ae8875609b..5d93d51e50a5dd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/load_before_global_declaration.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/load_before_global_declaration.rs @@ -1,7 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::SourceRow; +use crate::Violation; + /// ## What it does /// Checks for uses of names that are declared as `global` prior to the /// relevant `global` declaration. diff --git a/crates/ruff_linter/src/rules/pylint/rules/logging.rs b/crates/ruff_linter/src/rules/pylint/rules/logging.rs index 326ca4a073ac7d..62eb28d7cceca5 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/logging.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/logging.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::analyze::logging; use ruff_python_stdlib::logging::LoggingLevel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::pyflakes::cformat::CFormatSummary; diff --git a/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs index 258455bdb094be..f6728e5b38e93b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs @@ -1,10 +1,10 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, UnaryOp}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::settings::ConstantType; diff --git a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs index 0b8659aaee45a8..89c9be036b339c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Alias, Identifier, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for submodule imports that are aliased to the submodule name. diff --git a/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs b/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs index 99bc0483bc3f1f..16b8a14293512f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/misplaced_bare_raise.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::in_dunder_method; diff --git a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs index 10e1ad9dbb069e..6d99e420697f02 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_body; use ruff_python_ast::name::Name; @@ -7,6 +6,7 @@ use ruff_python_semantic::analyze::typing::is_set; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for loops in which a `set` is modified during iteration. diff --git a/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs b/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs index 0c6a0361b77611..5b306bd547a398 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/named_expr_without_context.rs @@ -1,8 +1,8 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs index e645097a05fb04..129395affd6af8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nan_comparison.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index e2f6f9d28ee550..154550c41fc651 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum MinMax { diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs index bca6480e7a51ea..99016e24c26df9 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the use of a classmethod being made without the decorator. diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index c18bfd1c02dd33..78d59d0329d238 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -7,6 +6,7 @@ use ruff_python_semantic::{ analyze::{function_type, visibility}, }; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::flake8_unused_arguments::rules::is_not_implemented_stub_with_variable; diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs index 33e164d9505a37..75046c8ffec3c0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_module_import.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs index d1f2fd19ab650d..2458f418a4b160 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::{Binding, BindingKind}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs index d96461609e693b..1562783af2d076 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_augmented_assignment.rs @@ -1,5 +1,4 @@ use ast::Expr; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::comparable::ComparableExpr; @@ -8,6 +7,7 @@ use ruff_python_ast::{ExprBinOp, ExprRef, Operator}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for assignments that can be replaced with augmented assignment diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs index ec75d836d3621b..01a237c1685ec8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs @@ -1,10 +1,10 @@ use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs index 060d1715e4c4dd..ad42ac39282e6b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_and_global.rs @@ -1,7 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_without_binding.rs b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_without_binding.rs index 08106e7b1b3b04..c6e82e3958c259 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nonlocal_without_binding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nonlocal_without_binding.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for `nonlocal` names without bindings. /// diff --git a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs index 060e3bce455ad9..4a3f29e0904acc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/potential_index_error.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs index cb1d5890641cb8..43d5a6175bd4a6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Decorator, Parameters, Stmt, identifier::Identifier}; use ruff_python_semantic::analyze::visibility::is_property; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs index 64a5e1823a9b99..fa5dfe01825552 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_argument_from_local.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_argument_from_local.rs index 1c9ea795dd6531..14a6f9de7e2f8d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_argument_from_local.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_argument_from_local.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## What it does /// Checks for variables defined in `for`, `try`, `with` statements /// that redefine function parameters. diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs index 1a55ecc21cee5f..b7dd04e992cfec 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs @@ -3,13 +3,13 @@ use std::{fmt, iter}; use regex::Regex; use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Stmt, WithItem}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs index b0273d2a4da15a..a2eb232456bb4b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs @@ -3,11 +3,11 @@ use std::hash::Hash; use ruff_python_semantic::analyze::class::iter_super_class; use rustc_hash::FxHashSet; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index 61371a360793f8..4f279fa6b1e471 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -2,7 +2,6 @@ use itertools::Itertools; use rustc_hash::{FxBuildHasher, FxHashMap}; use ast::ExprContext; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{any_over_expr, contains_effect}; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for repeated equality comparisons that can be rewritten as a membership diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_isinstance_calls.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_isinstance_calls.rs index dda289e3e432c0..9c71a3246956df 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_isinstance_calls.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_isinstance_calls.rs @@ -1,6 +1,6 @@ -use ruff_diagnostics::AlwaysFixableViolation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::AlwaysFixableViolation; use crate::fix::snippet::SourceCodeSnippet; /// ## Removed diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs index 434b30c19c6e31..f35f7b0f10c1ea 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs @@ -1,10 +1,10 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, ExprStringLiteral}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs index ee3398dfa4127b..cf14390acd7e79 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::in_dunder_method; diff --git a/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs b/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs index 9cedeae300ed72..2d9d194c6e1e0d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/self_assigning_variable.rs @@ -1,10 +1,10 @@ use itertools::Itertools; use ruff_python_ast::{self as ast, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs index df4db639e9e766..c77203bfece07d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/self_or_cls_assignment.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::ScopeKind; use ruff_python_semantic::analyze::function_type::{self as function_type, FunctionType}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs index 7517b651eceff8..2cfe984f862377 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/shallow_copy_environ.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Check for shallow `os.environ` copies. diff --git a/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs b/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs index 73265be1e07575..3cf41a00db4f67 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/single_string_slots.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, Expr, Stmt, StmtClassDef}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs index 33d02279375651..b0ac23c9331cf9 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Scope; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for methods decorated with `@singledispatch`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs index 0e740b9148491b..9bb153bdea0613 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Scope; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for non-method functions decorated with `@singledispatchmethod`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs index f3a0a0bd505cdd..b07d0bef0df98e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs index 22359975330f31..9d4848ff67eb01 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::add_argument; +use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does /// Checks for uses of `subprocess.run` without an explicit `check` argument. diff --git a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs index 9bd206c4b8a957..e9eb23cc5a97ca 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/super_without_brackets.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{ScopeKind, analyze::function_type}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Detects attempts to use `super` without parentheses. diff --git a/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs b/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs index 93e6ee00ddf367..c0a1a0445a6def 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/sys_exit_alias.rs @@ -1,11 +1,11 @@ -use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; - use ruff_python_ast::ExprCall; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; + /// ## What it does /// Checks for uses of the `exit()` and `quit()`. /// diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs index 58d4fd938e8b92..3e1156d4c50ae0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_arguments.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::{function_type, visibility}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs index 1780b99159b98b..690e3652ad1fb3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_boolean_expressions.rs @@ -1,9 +1,9 @@ use ast::{Expr, StmtIf}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs index 529e6fda2c7340..d5c82b95eb8aa3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs @@ -1,8 +1,8 @@ -use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; + +use crate::Violation; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs index 43079c837571fa..dfd3142c6370cf 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_locals.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::{Scope, ScopeKind}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs index 80900f84f80a10..4d0b1d86d4307c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_nested_blocks.rs @@ -1,9 +1,9 @@ use ast::ExceptHandler; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs index 39b0e9dfbff3f6..80abaa80718ee1 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_positional_arguments.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, identifier::Identifier}; use ruff_python_semantic::analyze::{function_type, visibility}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs index 0e6a58db944f2e..267e59ff2a715a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_public_methods.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::analyze::visibility::{self, Visibility::Public}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs index 14e29b82a6e8d2..f4fb169653137b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_return_statements.rs @@ -1,12 +1,10 @@ -use ruff_python_ast::Stmt; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::Stmt; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor::Visitor; -use crate::checkers::ast::Checker; +use crate::{Violation, checkers::ast::Checker}; /// ## What it does /// Checks for functions or methods with too many return statements. diff --git a/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs b/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs index c0b033741818f8..7033a71d83fc8a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/too_many_statements.rs @@ -1,8 +1,8 @@ -use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; - -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; +use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; + +use crate::Violation; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs b/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs index 2a92cb98932d63..b4cd0a11f4c690 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_bivariance.rs @@ -1,11 +1,11 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs b/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs index 96c63658cdda0a..f73b12fdadeb72 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_name_incorrect_variance.rs @@ -1,11 +1,11 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; diff --git a/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs b/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs index 5dfe9f22c4b933..2d1b33bd89fa06 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/type_param_name_mismatch.rs @@ -1,10 +1,10 @@ use std::fmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; diff --git a/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs b/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs index ba1db43a1cdb72..b6bc8c255c45a1 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unexpected_special_method_signature.rs @@ -2,11 +2,11 @@ use std::cmp::Ordering; use ruff_python_ast::{Decorator, Parameters, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_semantic::analyze::visibility::is_staticmethod; +use crate::Violation; use crate::checkers::ast::Checker; #[derive(Debug, Eq, PartialEq)] diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs index aa610bb280376c..9e1be524b24d25 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dict_index_lookup.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, StmtFor}; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::SequenceIndexVisitor; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for key-based dict accesses during `.items()` iterations. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs index b932c5634a70bf..17d0ab76632477 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_direct_lambda_call.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index 6d4440a4ace799..fb81176ace5259 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, OperatorPrecedence, Stmt}; use ruff_python_semantic::SemanticModel; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::is_known_dunder_method; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs index 9ecfc0801ee685..b48e6ef8c91358 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, ExprLambda, Parameter, ParameterWithDefault, visitor}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for `lambda` definitions that consist of a single function call diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs index 414a30e820ea14..991d25bac698a1 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, Expr, Int, Number, StmtFor}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::SequenceIndexVisitor; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for index-based list accesses during `enumerate` iterations. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs index b81c43d8c035d0..b590573b4c8ad8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unreachable.rs @@ -5,9 +5,9 @@ use ruff_python_ast::{Identifier, Stmt}; use ruff_python_semantic::cfg::graph::{BlockId, Condition, ControlFlowGraph, build_cfg}; use ruff_text_size::TextRange; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index fea61bc453f8ab..088041dcf189da 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -1,6 +1,5 @@ use std::fmt::{Display, Formatter}; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Expr}; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::add_argument; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for uses of `open` and related calls without an explicit `encoding` diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs index 573b3688e6d3ec..5dc74c156a891d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs @@ -1,7 +1,6 @@ use anyhow::Result; use ast::whitespace::indentation; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier; use ruff_python_ast::{self as ast, ExceptHandler, MatchCase, Stmt}; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::adjust_indentation; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `else` clauses on loops without a `break` statement. diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs index e1b27dd4da8128..4d7be33a71255b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -6,6 +5,7 @@ use ruff_python_stdlib::builtins; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs index b2ea23054c33c5..5816f8faf555bf 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_import_alias.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Alias; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for import aliases that do not rename the original package. diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs index 5bec98ebf4d85c..cef3d35b66c92d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::ReturnStatementVisitor; use ruff_python_ast::visitor::Visitor; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for functions that end with an unnecessary `return` or diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs index fcd4d8126e144b..77931841f0016e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs b/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs index fa71e3796d679e..60fc10bc5237fd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/yield_from_in_async_function.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::ScopeKind; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs b/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs index f303324704b959..1cee03fe0421c5 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/yield_in_init.rs @@ -1,9 +1,9 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::in_dunder_method; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index 90e526a3b71fb0..f88bfa38d4a6f2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -1,6 +1,5 @@ use log::debug; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::name::Name; @@ -13,6 +12,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `NamedTuple` declarations that use functional syntax. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index 73ca1f3a4d4f45..d45e4dd5cfc462 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Keyword, Stmt}; use ruff_python_codegen::Generator; @@ -9,6 +8,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `TypedDict` declarations that use functional syntax. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs index daf1129dc822d2..b67f312699739c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs @@ -1,11 +1,11 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `datetime.timezone.utc`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs index 4dcba36f8272de..7672680c72bd6b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Stmt}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of the `xml.etree.cElementTree` module. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 2c49b252cfe33f..765527b6d77991 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -1,6 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{Alias, StmtImportFrom}; @@ -11,6 +10,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::pyupgrade::fixes; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// An import was moved and renamed as part of a deprecation. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index 612b10da544e44..8da16d71ba8d11 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -5,7 +5,6 @@ use libcst_native::{ }; use log::debug; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::whitespace::indentation; @@ -18,6 +17,7 @@ use crate::Locator; use crate::checkers::ast::Checker; use crate::cst::matchers::{match_import, match_import_from, match_statement}; use crate::fix::codemods::CodegenStylist; +use crate::{AlwaysFixableViolation, Edit, Fix}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum MockReference { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs index 00871001343760..519f8d9d397b9d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_unittest_alias.rs @@ -2,11 +2,11 @@ use ruff_python_ast::{self as ast, Expr}; use rustc_hash::FxHashMap; use std::sync::LazyLock; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of deprecated methods from the `unittest` module. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs index e50b6fb812d665..34aae8693e7d1b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -1,11 +1,11 @@ use std::slice::Iter; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for extraneous parentheses. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index e5025566c997e7..0edc9bb2e8389e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use anyhow::{Context, Result}; use rustc_hash::{FxHashMap, FxHashSet}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::str::{leading_quote, trailing_quote}; @@ -19,6 +18,7 @@ use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::pyflakes::format::FormatSummary; use crate::rules::pyupgrade::helpers::{curly_escape, curly_unescape}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `str.format` calls that can be replaced with f-strings. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs index 6a3f66ab0dedf2..6865dc4e2367cf 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs @@ -4,7 +4,6 @@ use anyhow::{Result, anyhow}; use libcst_native::{Arg, Expression}; use regex::Regex; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Stylist; @@ -17,6 +16,7 @@ use crate::cst::matchers::{ }; use crate::fix::codemods::CodegenStylist; use crate::rules::pyflakes::format::FormatSummary; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary positional indices in format strings. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs index b3f176e8eb815c..abdc427515607d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Keyword}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `functools.lru_cache` that set `maxsize=None`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index 70c5da0e94234c..c4c84ddd90f41a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for unnecessary parentheses on `functools.lru_cache` decorators. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs index 00a69daf46a660..5b91362679f764 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs @@ -1,13 +1,13 @@ use std::fmt; use std::str::FromStr; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, OperatorPrecedence, UnaryOp}; use ruff_source_file::find_newline; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum LiteralType { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs index f913170a1bdd9e..79ab523ad3da1d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/non_pep646_unpack.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprSubscript, PythonVersion}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `Unpack[]` on Python 3.11 and above, and suggests diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs index 45f997c04e9045..4d74273a28e84f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `io.open`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs index 070a13f470fc68..286fb88d4e1118 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs @@ -1,13 +1,13 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; -use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{Name, UnqualifiedName}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; +use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of exceptions that alias `OSError`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index f89bc74bf05bb4..025d9be062896c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -2,7 +2,6 @@ use std::cmp::Ordering; use anyhow::Result; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::stmt_if::{BranchKind, IfElifBranch, if_elif_branches}; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::{adjust_indentation, delete_stmt}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs index 81b7b4971922b4..56137e4a93495f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{ExprSubscript, StmtClassDef}; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; use super::{ diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs index 39fa3eafff77ce..3ab3a0b6b3773a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtFunctionDef; use ruff_python_ast::visitor::Visitor; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; use super::{DisplayTypeVars, TypeVarReferenceVisitor, check_type_vars, in_nested_context}; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs index ab3dce82ac48f9..5fb8937abf6ca1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs @@ -1,6 +1,5 @@ use itertools::Itertools; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::parenthesize::parenthesized_range; @@ -9,6 +8,7 @@ use ruff_python_ast::{Expr, ExprCall, ExprName, Keyword, StmtAnnAssign, StmtAssi use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; use super::{ diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs index cc1ed4460b6551..d581cc64dcad32 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/private_type_parameter.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Applicability, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_python_semantic::Binding; use ruff_python_stdlib::identifiers::is_identifier; use ruff_text_size::Ranged; +use crate::{Applicability, Fix, FixAvailability, Violation}; use crate::{ checkers::ast::Checker, renamer::{Renamer, ShadowedKind}, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index 60478d7c077106..bcb584df8a158b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::fmt::Write; use std::str::FromStr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyStringFlags, Expr, StringFlags, whitespace::indentation}; use ruff_python_codegen::Stylist; @@ -17,6 +16,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::pyupgrade::helpers::curly_escape; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `printf`-style string formatting, and offers to replace it with diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs index 73f4976aaf240b..f890b833724486 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs @@ -1,12 +1,12 @@ -use ruff_python_parser::TokenKind; -use ruff_text_size::{TextLen, TextRange, TextSize}; - -use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; +use ruff_python_parser::TokenKind; use ruff_python_semantic::SemanticModel; use ruff_source_file::LineRanges; +use ruff_text_size::{TextLen, TextRange, TextSize}; + +use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the presence of unnecessary quotes in type annotations. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs index d7a6bd4126a3e4..13deff49417bc4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_parser::{TokenKind, Tokens}; @@ -7,6 +6,7 @@ use ruff_python_stdlib::open_mode::OpenMode; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for redundant `open` mode arguments. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index 45b05fc87271cc..47e1d67d94e78f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -1,6 +1,5 @@ use anyhow::Result; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Keyword}; use ruff_python_semantic::Modules; @@ -8,6 +7,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `subprocess.run` that send `stdout` and `stderr` to a diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs index ced03bbce003aa..19683d01d9f1e3 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for classes that inherit from both `str` and `enum.Enum`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs index 132a562d079494..bdd696039a821e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Modules; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `subprocess.run` that set the `universal_newlines` diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index f87b7f52951025..2c106da20da278 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `super` calls that pass redundant arguments. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index 1b40bdd91f68e1..f1f8d921461f98 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -1,14 +1,14 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; -use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::PythonVersion; use ruff_python_ast::name::{Name, UnqualifiedName}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; -use ruff_python_ast::PythonVersion; +use crate::fix::edits::pad; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of exceptions that alias `TimeoutError`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs index 5792100271056f..ed999a5e1404b0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs @@ -1,11 +1,10 @@ -use ruff_python_ast::Expr; - -use crate::fix::edits::pad; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::Expr; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::fix::edits::pad; +use crate::{Edit, Fix, FixAvailability, Violation}; use super::super::types::Primitive; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs index e932325884ffb9..9e8adda647a308 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs @@ -1,11 +1,11 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `typing.Text`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs index 4ed3768bac0fc6..99f1fe8ce6691c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unicode_kind_prefix.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StringLiteral; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of the Unicode kind prefix (`u`) in strings. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs index c7bd001ac080b6..bef5438882f425 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_builtin_import.rs @@ -1,12 +1,12 @@ use itertools::Itertools; use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for unnecessary imports of builtins. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs index e92bb79150ded1..10649d493ea4e9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for class definitions that include unnecessary parentheses after diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs index e81a2d0f7c87ac..cfa58b8b243fda 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs @@ -3,13 +3,13 @@ use std::sync::LazyLock; use regex::Regex; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for unnecessary UTF-8 encoding declarations. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs index a5beeae74bb229..3c11602ffb6d79 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for unnecessary default type arguments for `Generator` and diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs index cd9f8953816bc0..d143eab9e0ed5a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs @@ -1,6 +1,5 @@ use std::fmt::Write as _; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_python_parser::{TokenKind, Tokens}; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, pad, remove_argument}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for unnecessary calls to `encode` as UTF-8. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs index 205dea25934abb..73c8836a7e65bb 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs @@ -1,12 +1,12 @@ use itertools::Itertools; use ruff_python_ast::{Alias, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does /// Checks for unnecessary `__future__` imports. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs index 5c8d28ec462f25..1958a23120b91d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unpacked_list_comprehension.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::ViolationMetadata; +use crate::Violation; + /// ## Removed /// There's no [evidence](https://github.com/astral-sh/ruff/issues/12754) that generators are /// meaningfully faster than list comprehensions when combined with unpacking. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs index bdf78ab5882a10..044c21f258193e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs @@ -1,6 +1,5 @@ use ruff_python_ast::Expr; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_semantic::analyze::typing::ModuleMember; @@ -8,6 +7,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index c017a568dff701..12a7798ad3f62d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PythonVersion; use ruff_python_ast::helpers::{pep_604_optional, pep_604_union}; @@ -10,6 +9,7 @@ use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::fix::edits::pad; use crate::preview::is_defer_optional_to_up045_enabled; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for type annotations that can be rewritten based on [PEP 604] syntax. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index 8e40da041ba97b..2e0f08638b2771 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -1,12 +1,12 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_ast::helpers::pep_604_union; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum CallKind { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs index 0fe0fafd3f4572..9e364e55b3226c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs @@ -1,6 +1,6 @@ use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; -use ruff_diagnostics::{Fix, FixAvailability, Violation}; +use crate::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::Ranged; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs index 608daee95fd192..b221b2e7cd8e03 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_metaclass_type.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for the use of `__metaclass__ = type` in class definitions. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs index 96454e2efe755d..4502cbfeb40c2e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Checks for classes that inherit from `object`. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs index 8d7a628d188169..e616d4a5c95ef4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `for` loops that can be replaced with `yield from` expressions. diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index 54ca98a586e2f2..34ed1804ff9dcf 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Generator; @@ -6,6 +5,7 @@ use ruff_python_semantic::{BindingId, ResolvedReference, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix}; use ruff_python_ast::PythonVersion; /// Format a code snippet to call `name.method()`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs index affce997053383..93946dcb1ff220 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::PythonVersion; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for uses of `bin(...).count("1")` to perform a population count. diff --git a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs index fc09185c0c85b3..00ea156590cffa 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::contains_effect; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `set.remove` that can be replaced with `set.discard`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs index fd495659fd2a29..0d8ec381df1862 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; @@ -6,6 +5,7 @@ use ruff_python_semantic::analyze::typing::{is_dict, is_list}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::refurb::helpers::generate_method_call; diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs index 54b313c782d030..08ccf08d78e5b2 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, Stmt, StmtFor}; use ruff_python_semantic::analyze::typing; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; use super::helpers::parenthesize_loop_iter_if_necessary; diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs index e56b2dc452e049..11ec8333703d91 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprList, ExprName, ExprTuple, Stmt, StmtFor}; use ruff_python_semantic::analyze::typing; @@ -6,6 +5,7 @@ use ruff_python_semantic::{Binding, ScopeId, SemanticModel, TypingOnlyBindingsSt use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; use super::helpers::parenthesize_loop_iter_if_necessary; diff --git a/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs b/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs index 85ff8c2c532abc..def4bf832e068a 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fromisoformat_replace_z.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{ @@ -9,6 +8,7 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `datetime.fromisoformat()` calls diff --git a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs index 68767e66ea8bd0..c06fe722b0a8a3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp}; use ruff_source_file::find_newline; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `bin(...)[2:]` (or `hex`, or `oct`) to convert diff --git a/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs b/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs index c92f1450f9742d..d29d89244be5fd 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/hardcoded_string_charset.rs @@ -1,10 +1,11 @@ -use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::ExprStringLiteral; use ruff_text_size::TextRange; +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::{AlwaysFixableViolation, Edit, Fix}; + /// ## What it does /// Checks for uses of hardcoded charsets, which are defined in Python string module. /// diff --git a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs index 3f6e37bf09058e..837e5ed79f5484 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, ExprCall}; use ruff_python_semantic::Modules; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the use of `.digest().hex()` on a hashlib hash, like `sha512`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs index a1cc69838b9b55..7671a05b3aa660 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::Expr; @@ -12,6 +11,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for ternary `if` expressions that can be replaced with the `or` diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs b/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs index 89984e116dd205..a514b8704e0c4a 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_expr_min_max.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, CmpOp, Expr}; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `if` expressions that can be replaced with `min()` or `max()` diff --git a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs index e11b9ecfcb5ce7..204bb52f5aa894 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; use ruff_text_size::Ranged; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::{checkers::ast::Checker, importer::ImportRequest}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs index 14c8cb64de7948..74703553692bf6 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, Identifier}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `int` with an explicit base in which a string expression diff --git a/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs b/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs index adf7f45a410b42..793e0968ad9dc0 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/isinstance_type_none.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Operator}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; use crate::rules::refurb::helpers::replace_with_identity_check; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for uses of `isinstance` that check if an object is of type `None`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs b/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs index 9a6ae7f2c9d87c..bf8e6c6207f974 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/list_reverse_copy.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Expr, ExprCall, ExprName, ExprSlice, ExprSubscript, ExprUnaryOp, Int, StmtAssign, UnaryOp, @@ -8,6 +7,7 @@ use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for list reversals that can be performed in-place in lieu of diff --git a/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs b/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs index 96f265ad337325..c2db8c2c7ece67 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/math_constant.rs @@ -1,12 +1,12 @@ use anyhow::Result; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Number}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for literals that are similar to constants in `math` module. diff --git a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs index 52c848f778d9da..e079bd93d8f0c5 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs @@ -1,12 +1,12 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `metaclass=abc.ABCMeta` to define abstract base classes diff --git a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs index c6cec31947480b..b0b17456ab0e8d 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::{self as ast, Expr}; @@ -7,6 +6,7 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `print` calls with unnecessary empty strings as positional diff --git a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs index 517a5dffdfaeb7..0115f742d52aef 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{self, Visitor}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Generator; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index 89bafa3ce01353..37df344985f982 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -1,5 +1,3 @@ -use crate::preview::is_readlines_in_for_fix_safe_enabled; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Comprehension, Expr, StmtFor}; use ruff_python_semantic::analyze::typing; @@ -7,6 +5,8 @@ use ruff_python_semantic::analyze::typing::is_io_base_expr; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::preview::is_readlines_in_for_fix_safe_enabled; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `readlines()` when iterating over a file line-by-line. diff --git a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs index d9e4b79ff2c821..f1063af584b449 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs @@ -1,11 +1,11 @@ use anyhow::Result; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Number}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `math.log` calls with a redundant base. diff --git a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs index e0269f57e750d7..4719e1a29b1bd7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::Modules; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the use of shorthand aliases for regular expression flags diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs index 4923c69f149819..29d64e72186073 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs @@ -4,7 +4,6 @@ use std::fmt::{Debug, Display, Formatter}; use anyhow::Result; use itertools::Itertools; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::identifier::Identifier; @@ -15,6 +14,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::importer::{ImportRequest, Importer}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for lambda expressions and function definitions that can be replaced with a function from diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs index d97d57ccc3ab38..3fcde5bc326c66 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs @@ -1,5 +1,4 @@ use anyhow::{Result, bail}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::any_over_expr; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for generator expressions, list and set comprehensions that can diff --git a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs index 7224dcc877dd59..6674bf552dc87c 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs @@ -1,7 +1,6 @@ use rustc_hash::FxHashMap; use ast::traversal; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::traversal::EnclosingSuite; use ruff_python_ast::{self as ast, Expr, Stmt}; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for consecutive calls to `append`. diff --git a/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs b/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs index aafb0cdd1a1310..685f7124cc3399 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/repeated_global.rs @@ -1,11 +1,11 @@ use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Stmt; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for consecutive `global` (or `nonlocal`) statements. diff --git a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index 158bb6775d6740..536a5008f8fc96 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::generate_comparison; use ruff_python_ast::{self as ast, CmpOp, Expr, ExprStringLiteral}; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for membership tests against single-item containers. diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs index 28d9fea4804ff3..3b89ce2ebe474b 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr}; @@ -7,6 +6,7 @@ use ruff_python_semantic::{Binding, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::rules::refurb::helpers::generate_method_call; diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs index 43d608ddffc2a7..671cfb0fc9280f 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, PythonVersion}; use ruff_python_semantic::SemanticModel; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for code that could be written more idiomatically using diff --git a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs index 5bdbe5f7481ced..41965dc04f1f54 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs @@ -1,12 +1,12 @@ -use ruff_diagnostics::Edit; -use ruff_diagnostics::Fix; -use ruff_diagnostics::FixAvailability; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Number; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::Edit; +use crate::Fix; +use crate::FixAvailability; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs b/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs index fa35385bed3425..7d92379b36d079 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/subclass_builtin.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, StmtClassDef, helpers::map_subscript}; use ruff_text_size::Ranged; +use crate::{AlwaysFixableViolation, Edit, Fix}; use crate::{checkers::ast::Checker, importer::ImportRequest}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs index ea59f4496b41b9..b498a5ee491b9f 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/type_none_comparison.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::AlwaysFixableViolation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; +use crate::AlwaysFixableViolation; use crate::checkers::ast::Checker; use crate::rules::refurb::helpers::replace_with_identity_check; diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs index 1795cab9862ede..3313fc8c1805b0 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs @@ -1,6 +1,5 @@ use std::fmt; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::name::Name; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::pad; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `enumerate` that discard either the index or the value diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs index b5352a13225b8a..8303dd4903470b 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary `from_float` and `from_decimal` usages to construct diff --git a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs index 140be4fd9adf3a..2b8060a0c31170 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_trivia::PythonWhitespace; @@ -6,6 +5,7 @@ use ruff_text_size::Ranged; use std::borrow::Cow; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary string literal or float casts in `Decimal` diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index 2f9d283b23f175..705cd25b8fffa4 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::relocate::relocate_expr; use ruff_python_ast::visitor::{self, Visitor}; @@ -6,6 +5,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_codegen::Generator; use ruff_text_size::{Ranged, TextRange}; +use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index ae66234b2439a8..ae672e6b3d0e8c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -2,7 +2,6 @@ use std::fmt; use bitflags::bitflags; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -14,6 +13,7 @@ use crate::registry::AsRule; use crate::rules::ruff::rules::Context; use crate::rules::ruff::rules::confusables::confusable; use crate::settings::LinterSettings; +use crate::{Diagnostic, Violation}; /// ## What it does /// Checks for ambiguous Unicode characters in strings. diff --git a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs index 1d2decaf563d7b..5c956340e14aec 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `assert expression, print(message)`. diff --git a/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs b/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs index e4e08bb1df9aea..1de48b6454a9b1 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assignment_in_assert.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index e125e7761670a9..17657e6f9cd392 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -1,12 +1,12 @@ use std::fmt; use ast::Stmt; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::{Scope, SemanticModel, analyze::typing}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs index 52a58882fe2e3b..4f409f0329286e 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs @@ -1,7 +1,6 @@ use rustc_hash::FxHashSet; use std::iter; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Arguments, Expr, ExprStarred, ExprSubscript, ExprTuple, StmtClassDef, TypeParams, @@ -13,6 +12,7 @@ use crate::fix::edits::{Parentheses, remove_argument}; use crate::rules::pyupgrade::rules::pep695::{ DisplayTypeVars, TypeParamKind, TypeVar, expr_name_to_type_var, find_generic, }; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index 743808b741fbdb..566b9e822e889d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprContext, Operator}; use ruff_text_size::{Ranged, TextRange}; @@ -6,6 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; use crate::preview::is_support_slices_in_literal_concatenation_enabled; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of the `+` operator to concatenate collections. diff --git a/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs b/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs index d32429b66d76e7..84f3d65fae6076 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/dataclass_enum.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_python_semantic::analyze::class::is_enumeration; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{DataclassKind, dataclass_kind}; diff --git a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs index 05da96b2e512a6..8b1776e8e69c44 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs @@ -1,6 +1,5 @@ use std::fmt; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_codegen::Stylist; @@ -8,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `Decimal` calls passing a float literal. diff --git a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs index f569c453664513..934ead0da08088 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs @@ -1,7 +1,6 @@ use anyhow::Result; use ast::Keyword; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_constant; use ruff_python_ast::{self as ast, Expr}; @@ -11,6 +10,7 @@ use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; use crate::fix::snippet::SourceCodeSnippet; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for incorrect usages of `default_factory` as a keyword argument when diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs index 15e836e0f2e0e0..7318e8e683b6e4 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs @@ -1,6 +1,5 @@ use anyhow::{Result, bail}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_python_codegen::Stylist; @@ -12,6 +11,7 @@ use crate::cst::matchers::{ match_call_mut, match_formatted_string, match_formatted_string_expression, match_name, transform_expression, }; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for uses of `str()`, `repr()`, and `ascii()` as explicit type diff --git a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs index 921416a37c7156..64b0bcdc71768c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs @@ -1,11 +1,12 @@ -use crate::checkers::ast::Checker; -use crate::fix::edits::{Parentheses, remove_argument}; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprAttribute, helpers::Truthiness}; use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; +use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{AlwaysFixableViolation, Applicability, Fix}; + /// ## What it does /// Checks for `dict.get(key, falsy_value)` calls in boolean test positions. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs index 77159ed537ff72..3612e3f7c226ba 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs @@ -1,6 +1,5 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_semantic::analyze::typing::{ @@ -8,6 +7,7 @@ use ruff_python_semantic::analyze::typing::{ }; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{ AttrsAutoAttribs, DataclassKind, dataclass_kind, is_class_var_annotation, is_dataclass_field, diff --git a/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs index 5dd3962ef2c0b7..969aff55b24a60 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/if_key_in_dict_del.rs @@ -1,9 +1,10 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{CmpOp, Expr, ExprName, ExprSubscript, Stmt, StmtIf}; use ruff_python_semantic::analyze::typing; +use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; + type Key = Expr; type Dict = ExprName; diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs index 8968369fee7fe9..336dafbca36fbc 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_classvar_in_dataclass.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_ast::{Expr, ExprName, Stmt, StmtAssign, StmtClassDef}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{DataclassKind, dataclass_kind}; diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs index c24ec7fa27c843..7a259f92a0b02e 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs @@ -2,7 +2,6 @@ use std::fmt; use anyhow::{Context, Result}; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; @@ -10,6 +9,7 @@ use ruff_python_ast::{self as ast, Expr, Operator, Parameters}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; diff --git a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs index 55d2374a82b147..c19dc7bde459e0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs index a3b3cc6718521b..490cd20cf0d580 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprSubscript, PythonVersion}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for consistent style regarding whether nonempty tuples in subscripts diff --git a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs index 66f0c7d1c07b6d..f76f5a6c1e3eb2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs @@ -1,10 +1,11 @@ use memchr::memchr; -use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; use ruff_text_size::{TextRange, TextSize}; +use crate::{Diagnostic, Violation}; + /// ## What it does /// Checks for form feed characters preceded by either a space or a tab. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs index fecad284df1145..3d2bbbaa830703 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_assert_message_literal_argument.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, StmtAssert}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs index ad873444cac219..f5152cf1af74a4 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs @@ -3,7 +3,6 @@ use std::fmt::Display; use smallvec::SmallVec; use ast::{StmtClassDef, StmtFunctionDef}; -use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, AnyNodeRef, helpers::comment_indentation_after}; use ruff_python_trivia::{SuppressionKind, indentation_at_offset}; @@ -12,6 +11,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::fix::edits::delete_comment; +use crate::{AlwaysFixableViolation, Fix}; use super::suppression_comment_visitor::{ CaptureSuppressionComment, SuppressionComment, SuppressionCommentData, diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs index fca2b26aef94c0..067a074718a36a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_index_type.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{Expr, ExprNumberLiteral, ExprSlice, ExprSubscript, Number}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use std::fmt; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_pyproject_toml.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_pyproject_toml.rs index 875114df4dcb4e..bc91f9cf54444d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_pyproject_toml.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_pyproject_toml.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::{FixAvailability, Violation}; + /// ## What it does /// Checks for any pyproject.toml that does not conform to the schema from the relevant PEPs. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs index fa9de17d412ef1..2d1bb95d23d3ee 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs @@ -1,11 +1,11 @@ -use crate::Locator; -use crate::noqa::{Code, Directive}; -use crate::registry::Rule; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use crate::Locator; +use crate::noqa::{Code, Directive}; use crate::noqa::{Codes, NoqaDirectives}; +use crate::registry::Rule; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for `noqa` codes that are invalid. diff --git a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs index db20a797b29a10..590273f6713726 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 5ef7320b061b54..359b8472f806f8 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -1,7 +1,6 @@ use memchr::memchr2_iter; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_literal::format::FormatSpec; @@ -13,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::checkers::ast::Checker; use crate::rules::fastapi::rules::is_fastapi_route_call; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Searches for strings that look like they were meant to be f-strings, but are missing an `f` prefix. diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs index 684933272f5b7a..d0fe008bd8b20e 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_class_default.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{ dataclass_kind, has_default_copy_semantics, is_class_var_annotation, is_final_annotation, diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs index 3eb89e8568203c..34d38d60594f83 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_dataclass_default.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{dataclass_kind, is_class_var_annotation}; diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs index 0eabf475d4bf41..7ec204fd61b1b0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, Expr}; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use ruff_text_size::TextRange; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for mutable objects passed as a value argument to `dict.fromkeys`. diff --git a/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs b/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs index 7d0ede8cbcb9c4..ca510970d6b21f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/needless_else.rs @@ -1,6 +1,5 @@ use std::cmp::Ordering; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::comment_indentation_after; use ruff_python_ast::whitespace::indentation; @@ -10,6 +9,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `else` clauses that only contains `pass` and `...` statements. diff --git a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs index 6257b91d82a563..34cf80384b5f80 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprBinOp, Operator}; use ruff_python_semantic::{SemanticModel, analyze::typing::traverse_union}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `typing.NoReturn` and `typing.Never` in union types. diff --git a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs index f4fd20d008ccae..a040bc96185ca6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::Ranged; use smallvec::SmallVec; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs index d1706e67d69e8c..e7ad588a8bcf4f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/parenthesize_chained_operators.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for chained operators where adding parentheses could improve the diff --git a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index 26bc3252610085..dc9ca62e471f9a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs @@ -1,6 +1,5 @@ use anyhow::Context; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::{Scope, ScopeKind}; @@ -8,6 +7,7 @@ use ruff_python_trivia::{indentation_at_offset, textwrap}; use ruff_source_file::LineRanges; use ruff_text_size::Ranged; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::{checkers::ast::Checker, importer::ImportRequest}; use super::helpers::{DataclassKind, dataclass_kind}; diff --git a/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs b/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs index 313353622366b5..5199c0e966ea50 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/pytest_raises_ambiguous_pattern.rs @@ -1,9 +1,10 @@ -use crate::checkers::ast::Checker; -use crate::rules::flake8_pytest_style::rules::is_pytest_raises; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; +use crate::Violation; +use crate::checkers::ast::Checker; +use crate::rules::flake8_pytest_style::rules::is_pytest_raises; + /// ## What it does /// Checks for non-raw literal string arguments passed to the `match` parameter /// of `pytest.raises()` where the string contains at least one unescaped diff --git a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs index eb290b9e53968c..fef61601fbfd55 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs @@ -1,7 +1,6 @@ use anyhow::Result; use itertools::Itertools; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -10,6 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for the use of `sum()` to flatten lists of lists, which has diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index 12a2f23901634e..59b50d04d65cd2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::rule_redirects::get_redirect_target; +use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; /// ## What it does /// Checks for `noqa` directives that use redirected rule codes. diff --git a/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs index 6aa037e4e891a6..3c0fafc623a8e1 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redundant_bool_literal.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; use ruff_python_semantic::analyze::typing::traverse_literal; @@ -7,6 +6,7 @@ use ruff_text_size::Ranged; use bitflags::bitflags; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `Literal[True, False]` type annotations. diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs index 6107c3a117849a..15e0760dc2eaf9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_source_file::LineRanges; @@ -9,6 +8,7 @@ use crate::rules::ruff::rules::sequence_sorting::{ MultilineStringSequenceValue, SequenceKind, SortClassification, SortingStyle, sort_single_line_elements_sequence, }; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `__all__` definitions that are not ordered diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index d318569fdff852..3ee20c168b7468 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use itertools::izip; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_semantic::Binding; @@ -15,6 +14,7 @@ use crate::rules::ruff::rules::sequence_sorting::{ CommentComplexity, MultilineStringSequenceValue, SequenceKind, SortClassification, SortingStyle, sort_single_line_elements_sequence, }; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `__slots__` definitions that are not ordered according to a diff --git a/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs b/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs index 8e192ef2c32e3a..c5ecfabbe4ff38 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/starmap_zip.rs @@ -1,10 +1,11 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Expr, ExprCall, parenthesize::parenthesized_range}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; +use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; + /// ## What it does /// Checks for `itertools.starmap` calls where the second argument is a `zip` call. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs b/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs index 69f747b7733daf..63fd8e361446d3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## Removed /// This rule was implemented in `flake8-bugbear` and has been remapped to [B035] /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs index 38c82b5460af7c..4bad3a00663760 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs @@ -13,13 +13,13 @@ /// /// Rules that provide a fix _must_ not raise unconditionally or the linter /// will not converge. -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_text_size::TextSize; use crate::Locator; use crate::registry::Rule; +use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; /// Check if a comment exists anywhere in a given file fn comment_exists(text: &str, locator: &Locator, comment_ranges: &CommentRanges) -> bool { diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs index 872ecfbf35335d..39c45b19e80f58 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{Arguments, Expr, ExprCall}; @@ -13,6 +12,7 @@ use crate::checkers::ast::Checker; use crate::rules::ruff::rules::unnecessary_round::{ InferredType, NdigitsValue, RoundedValue, rounded_and_ndigits, }; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for `int` conversions of values that are already integers. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs index 8bc725f53e4fcd..ae09b7f544a65c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, Int}; use ruff_python_semantic::SemanticModel; @@ -9,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks the following constructs, all of which can be replaced by diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs index c34d075b1a0aa4..72887978b3b6da 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_key_check.rs @@ -1,13 +1,13 @@ use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for unnecessary key checks prior to accessing a dictionary. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs index 869f23d4aa9ffd..8ed8719a401582 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs @@ -1,9 +1,10 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; +use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; + /// ## What it does /// Checks for usages of `collections.deque` that have an empty iterable as the first argument. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs index 0595b1e0f7b244..0dbdc44d86d419 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{AnyNodeRef, Expr, ExprContext, ExprSubscript, ExprTuple}; use ruff_python_semantic::analyze::typing::traverse_literal; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for unnecessary nested `Literal`. diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 6200664b0a2961..619ec3ac04ab86 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -1,5 +1,4 @@ use itertools::Itertools; -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ Arguments, CmpOp, Expr, ExprAttribute, ExprCall, ExprCompare, ExprContext, ExprStringLiteral, @@ -10,6 +9,7 @@ use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::TextRange; use crate::checkers::ast::Checker; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs index 070ebc4e40a7c2..44ca9f9004343b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs @@ -1,6 +1,3 @@ -use crate::Locator; -use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{Arguments, Expr, ExprCall, ExprNumberLiteral, Number}; use ruff_python_semantic::SemanticModel; @@ -9,6 +6,10 @@ use ruff_python_semantic::analyze::typing; use ruff_source_file::find_newline; use ruff_text_size::Ranged; +use crate::Locator; +use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; + /// ## What it does /// Checks for `round()` calls that have no effect on the input. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs b/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs index 9119be93b2a114..557cab426998f8 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unraw_re_pattern.rs @@ -1,7 +1,6 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, StringLiteral, @@ -11,6 +10,7 @@ use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Reports the following `re` and `regex` calls when diff --git a/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs b/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs index afd79f23b5a6cb..2cc04e602b5130 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## Removed /// This rule was implemented in `bandit` and has been remapped to /// [S704](unsafe-markup-use.md) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs index f9f7b32c9bd758..a3de93719e3a6a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor::source_order; @@ -6,6 +5,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef, Expr, Stmt}; use ruff_python_semantic::Modules; use ruff_python_semantic::analyze::function_type::is_stub; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::fastapi::rules::is_fastapi_route; diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_noqa.rs index e29e7acfeb0116..52e367500b0305 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_noqa.rs @@ -1,8 +1,9 @@ use itertools::Itertools; -use ruff_diagnostics::AlwaysFixableViolation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::AlwaysFixableViolation; + #[derive(Debug, PartialEq, Eq)] pub(crate) struct UnusedCodes { pub disabled: Vec, diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs index bf4997abca3fe6..2e78152b6318b9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs @@ -1,9 +1,9 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_semantic::Binding; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the presence of unused variables in unpacked assignments. diff --git a/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs b/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs index facb46162465b6..a3d3f3a986c0de 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs @@ -1,10 +1,10 @@ -use ruff_diagnostics::{Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_dunder; use ruff_python_semantic::{Binding, BindingId}; use ruff_python_stdlib::identifiers::is_identifier; use ruff_text_size::Ranged; +use crate::{Fix, FixAvailability, Violation}; use crate::{ checkers::ast::Checker, renamer::{Renamer, ShadowedKind}, diff --git a/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs b/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs index 1076ec0edf6f7c..6d7502dd56e509 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/useless_if_else.rs @@ -1,9 +1,10 @@ -use crate::checkers::ast::Checker; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_python_ast::comparable::ComparableExpr; +use crate::Violation; +use crate::checkers::ast::Checker; + /// ## What it does /// Checks for useless `if`-`else` conditions with identical arms. /// diff --git a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs index 7e055c204ee644..261ee446c301ce 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr, Int}; use ruff_text_size::Ranged; +use crate::{Edit, Fix, FixAvailability, Violation}; use crate::{checkers::ast::Checker, importer::ImportRequest}; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs index e8a1c7b66c0f2c..5a59099caebcb6 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, ExceptHandler, Expr}; @@ -9,6 +8,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::rules::tryceratops::helpers::LoggerCandidateVisitor; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for uses of `logging.error` instead of `logging.exception` when diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs index 3d5737c7dcb598..c359367ded1015 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs @@ -1,8 +1,8 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs index ff393ad010ef45..6549f3caad079c 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs @@ -1,10 +1,10 @@ use ruff_python_ast::Expr; use ruff_python_ast::helpers::map_callable; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs index 80035ee8477e7d..ce1489637781ff 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs @@ -1,6 +1,5 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ comparable::ComparableExpr, @@ -9,6 +8,7 @@ use ruff_python_ast::{ }; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/reraise_no_cause.rs b/crates/ruff_linter/src/rules/tryceratops/rules/reraise_no_cause.rs index 2470e5a1f6a1b8..a50d64136e3f35 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/reraise_no_cause.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/reraise_no_cause.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; + /// ## Removed /// This rule is identical to [B904] which should be used instead. /// diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs index 38ca82348c169d..c27be65d8a4a3d 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs @@ -1,10 +1,10 @@ use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::contains_effect; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs index 6414dc6cdc9420..59eedace937a55 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs @@ -1,4 +1,3 @@ -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; @@ -6,6 +5,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt, StmtIf}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs b/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs index b65c0dd246601c..f52a3e95c21cce 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/useless_try_except.rs @@ -1,9 +1,9 @@ use ruff_python_ast::{self as ast, ExceptHandler, ExceptHandlerExceptHandler, Expr, Stmt}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs index f83046c6293ef2..e95100cf8190cc 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_log_message.rs @@ -1,12 +1,12 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr}; -use ruff_diagnostics::Violation; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; use ruff_python_stdlib::logging::LoggingLevel; use ruff_text_size::Ranged; +use crate::Violation; use crate::checkers::ast::Checker; use crate::rules::tryceratops::helpers::LoggerCandidateVisitor; diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs index 2d95dded60e2fd..c330632c9776d7 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs @@ -1,11 +1,11 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; -use ruff_diagnostics::{AlwaysFixableViolation, Edit, Fix}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for needless exception names in `raise` statements. diff --git a/crates/ruff_linter/src/settings/fix_safety_table.rs b/crates/ruff_linter/src/settings/fix_safety_table.rs index 45ffa014150608..bff38bbe5241f2 100644 --- a/crates/ruff_linter/src/settings/fix_safety_table.rs +++ b/crates/ruff_linter/src/settings/fix_safety_table.rs @@ -1,10 +1,10 @@ use std::fmt::{Debug, Display, Formatter}; -use ruff_diagnostics::Applicability; use ruff_macros::CacheKey; use rustc_hash::FxHashMap; use strum::IntoEnumIterator; +use crate::Applicability; use crate::{ RuleSelector, display_settings, registry::{Rule, RuleSet}, diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 28f1135a7c8bdb..a72e80284ad408 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -14,10 +14,10 @@ use serde::{Deserialize, Deserializer, Serialize, de}; use strum_macros::EnumIter; use ruff_cache::{CacheKey, CacheKeyHasher}; -use ruff_diagnostics::Applicability; use ruff_macros::CacheKey; use ruff_python_ast::{self as ast, PySourceType}; +use crate::Applicability; use crate::registry::RuleSet; use crate::rule_selector::RuleSelector; use crate::{display_settings, fs}; diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 1d5e2437c03add..7148206df82f0c 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -10,7 +10,6 @@ use itertools::Itertools; use ruff_text_size::Ranged; use rustc_hash::FxHashMap; -use ruff_diagnostics::{Applicability, FixAvailability}; use ruff_notebook::Notebook; #[cfg(not(fuzzing))] use ruff_notebook::NotebookError; @@ -29,6 +28,7 @@ use crate::packaging::detect_package_root; use crate::settings::types::UnsafeFixes; use crate::settings::{LinterSettings, flags}; use crate::source_kind::SourceKind; +use crate::{Applicability, FixAvailability}; use crate::{Locator, directives}; #[cfg(not(fuzzing))] diff --git a/crates/ruff_diagnostics/src/violation.rs b/crates/ruff_linter/src/violation.rs similarity index 96% rename from crates/ruff_diagnostics/src/violation.rs rename to crates/ruff_linter/src/violation.rs index d0c7b9c763cd92..9939efe7226b9c 100644 --- a/crates/ruff_diagnostics/src/violation.rs +++ b/crates/ruff_linter/src/violation.rs @@ -1,5 +1,7 @@ use std::fmt::{Debug, Display}; +use crate::codes::Rule; + #[derive(Debug, Copy, Clone)] pub enum FixAvailability { Sometimes, @@ -18,8 +20,8 @@ impl Display for FixAvailability { } pub trait ViolationMetadata { - /// Returns the rule name of this violation - fn rule_name() -> &'static str; + /// Returns the rule for this violation + fn rule() -> Rule; /// Returns an explanation of what this violation catches, /// why it's bad, and what users should do instead. diff --git a/crates/ruff_macros/src/map_codes.rs b/crates/ruff_macros/src/map_codes.rs index c5e7b9879be88f..7d1ccaf028f219 100644 --- a/crates/ruff_macros/src/map_codes.rs +++ b/crates/ruff_macros/src/map_codes.rs @@ -413,16 +413,17 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { #name, }); // Apply the `attrs` to each arm, like `[cfg(feature = "foo")]`. - rule_message_formats_match_arms - .extend(quote! {#(#attrs)* Self::#name => <#path as ruff_diagnostics::Violation>::message_formats(),}); + rule_message_formats_match_arms.extend( + quote! {#(#attrs)* Self::#name => <#path as crate::Violation>::message_formats(),}, + ); rule_fixable_match_arms.extend( - quote! {#(#attrs)* Self::#name => <#path as ruff_diagnostics::Violation>::FIX_AVAILABILITY,}, + quote! {#(#attrs)* Self::#name => <#path as crate::Violation>::FIX_AVAILABILITY,}, ); rule_explanation_match_arms.extend(quote! {#(#attrs)* Self::#name => #path::explain(),}); } quote! { - use ruff_diagnostics::Violation; + use crate::Violation; #[derive( EnumIter, @@ -453,24 +454,15 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { /// Returns the documentation for this rule. pub fn explanation(&self) -> Option<&'static str> { - use ruff_diagnostics::ViolationMetadata; + use crate::ViolationMetadata; match self { #rule_explanation_match_arms } } /// Returns the fix status of this rule. - pub const fn fixable(&self) -> ruff_diagnostics::FixAvailability { + pub const fn fixable(&self) -> crate::FixAvailability { match self { #rule_fixable_match_arms } } } - - impl AsRule for ruff_diagnostics::Diagnostic { - fn rule(&self) -> Rule { - self.name - .parse() - .unwrap_or_else(|_| unreachable!("invalid rule name: {}", self.name)) - } - } - } } diff --git a/crates/ruff_macros/src/violation_metadata.rs b/crates/ruff_macros/src/violation_metadata.rs index bc8789d8183524..3bdceb40790e65 100644 --- a/crates/ruff_macros/src/violation_metadata.rs +++ b/crates/ruff_macros/src/violation_metadata.rs @@ -12,9 +12,9 @@ pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result Ok(quote! { #[automatically_derived] #[expect(deprecated)] - impl #impl_generics ruff_diagnostics::ViolationMetadata for #name #ty_generics #where_clause { - fn rule_name() -> &'static str { - ::ruff_macros::kebab_case!(#name) + impl #impl_generics crate::ViolationMetadata for #name #ty_generics #where_clause { + fn rule() -> crate::registry::Rule { + crate::registry::Rule::#name } fn explain() -> Option<&'static str> { diff --git a/crates/ruff_server/src/server/api/requests/hover.rs b/crates/ruff_server/src/server/api/requests/hover.rs index 209dbf6c458621..6b391e15bcc436 100644 --- a/crates/ruff_server/src/server/api/requests/hover.rs +++ b/crates/ruff_server/src/server/api/requests/hover.rs @@ -3,7 +3,7 @@ use crate::session::DocumentSnapshot; use anyhow::Context; use lsp_types::{self as types, request as req}; use regex::Regex; -use ruff_diagnostics::FixAvailability; +use ruff_linter::FixAvailability; use ruff_linter::registry::{Linter, Rule, RuleNamespace}; use ruff_source_file::OneIndexed; use std::fmt::Write; diff --git a/scripts/add_rule.py b/scripts/add_rule.py index 013505a2e45e63..400c5ac6ec0566 100755 --- a/scripts/add_rule.py +++ b/scripts/add_rule.py @@ -92,9 +92,9 @@ def main(*, name: str, prefix: str, code: str, linter: str) -> None: with (rules_dir / f"{rule_name_snake}.rs").open("w") as fp: fp.write( f"""\ -use ruff_diagnostics::Violation; use ruff_macros::{{ViolationMetadata, derive_message_formats}}; +use crate::Violation; use crate::checkers::ast::Checker; /// ## What it does From a5ebb3f3a2b347849d0c195884ea944fb690b187 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 28 May 2025 15:54:59 +0100 Subject: [PATCH 264/487] [ty] Support ephemeral uv virtual environments (#18335) --- crates/ruff_db/src/system/path.rs | 23 ++ .../mdtest/import/site_packages_discovery.md | 59 +++++ .../src/module_resolver/resolver.rs | 8 +- .../ty_python_semantic/src/site_packages.rs | 229 +++++++++++++----- crates/ty_test/README.md | 4 +- crates/ty_test/src/lib.rs | 57 +++-- 6 files changed, 283 insertions(+), 97 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md diff --git a/crates/ruff_db/src/system/path.rs b/crates/ruff_db/src/system/path.rs index 4aea0cbe8b6419..c20b2b19f5f8ea 100644 --- a/crates/ruff_db/src/system/path.rs +++ b/crates/ruff_db/src/system/path.rs @@ -596,6 +596,13 @@ impl AsRef for Utf8PathBuf { } } +impl AsRef for camino::Utf8Component<'_> { + #[inline] + fn as_ref(&self) -> &SystemPath { + SystemPath::new(self.as_str()) + } +} + impl AsRef for str { #[inline] fn as_ref(&self) -> &SystemPath { @@ -626,6 +633,22 @@ impl Deref for SystemPathBuf { } } +impl> FromIterator

for SystemPathBuf { + fn from_iter>(iter: I) -> Self { + let mut buf = SystemPathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> Extend

for SystemPathBuf { + fn extend>(&mut self, iter: I) { + for path in iter { + self.push(path); + } + } +} + impl std::fmt::Debug for SystemPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md new file mode 100644 index 00000000000000..8a60a61afb3d5b --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md @@ -0,0 +1,59 @@ +# Tests for `site-packages` discovery + +## Ephemeral uv environments + +If you use the `--with` flag when invoking `uv run`, uv will create an "ephemeral" virtual +environment that is layered on top of the pre-existing environment. `site-packages` directories from +the pre-existing environment will be added as an import search path at runtime as well as the +`site-packages` directory from the ephemeral environment. The `VIRTUAL_ENV` environment variable +will only point to the ephemeral virtual environment, but, following uv commit +`7bba3d00d4ad1fb3daba86b98eb25d8d9e9836ae`, uv writes the `sys.prefix` path of the parent +environment to an `extends-environment` key in the ephemeral environment's `pyvenv.cfg` file. + +This test ensures that we are able to resolve imports that point to packages in either +`site-packages` directory (the one of the ephemeral environment or the one of the parent +environment) if we detect that an ephemeral uv environment has been activated. + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +implementation = CPython +uv = 0.7.6 +version_info = 3.13.2 +include-system-site-packages = false +prompt = ruff +extends-environment = /.other-environment +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/.other-environment//bar.py`: + +```py +Y: "str" = "Y" +``` + +`/src/main.py`: + +```py +from foo import X +from bar import Y + +reveal_type(X) # revealed: int +reveal_type(Y) # revealed: str +``` diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 49bf534e774ce2..4013b54da12dc1 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -14,7 +14,9 @@ use ruff_python_ast::PythonVersion; use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions}; -use crate::site_packages::{PythonEnvironment, SitePackagesDiscoveryError, SysPrefixPathOrigin}; +use crate::site_packages::{ + PythonEnvironment, SitePackagesDiscoveryError, SitePackagesPaths, SysPrefixPathOrigin, +}; use crate::{Program, PythonPath, SearchPathSettings}; use super::module::{Module, ModuleKind}; @@ -289,7 +291,7 @@ impl SearchPaths { virtual_env_path, error ); - vec![] + SitePackagesPaths::default() }; match PythonEnvironment::new( @@ -304,7 +306,7 @@ impl SearchPaths { } } else { tracing::debug!("No virtual environment found"); - vec![] + SitePackagesPaths::default() } } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 6204fff816af78..c01db5483e8f3a 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -14,11 +14,71 @@ use std::io; use std::num::NonZeroUsize; use std::ops::Deref; +use indexmap::IndexSet; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_python_ast::PythonVersion; type SitePackagesDiscoveryResult = Result; +/// An ordered, deduplicated set of `site-packages` search paths. +/// +/// Most environments will only have one `site-packages` directory. +/// Some virtual environments created with `--system-site-packages` +/// will also have the system installation's `site-packages` packages +/// available, however. Ephemeral environments created with `uv` in +/// `uv run --with` invocations, meanwhile, "extend" a parent environment +/// (which could be another virtual environment or a system installation, +/// and which could itself have multiple `site-packages` directories). +/// +/// We use an `IndexSet` here to guard against the (very remote) +/// possibility that an environment might somehow be marked as being +/// both a `--system-site-packages` virtual environment *and* an +/// ephemeral environment that extends the system environment. If this +/// were the case, the system environment's `site-packages` directory +/// *might* be added to the `SitePackagesPaths` twice, but we wouldn't +/// want duplicates to appear in this set. +#[derive(Debug, PartialEq, Eq, Default)] +pub(crate) struct SitePackagesPaths(IndexSet); + +impl SitePackagesPaths { + pub(crate) fn len(&self) -> usize { + self.0.len() + } + + fn single(path: SystemPathBuf) -> Self { + Self(IndexSet::from([path])) + } + + fn insert(&mut self, path: SystemPathBuf) { + self.0.insert(path); + } + + fn extend(&mut self, other: Self) { + self.0.extend(other.0); + } +} + +impl FromIterator for SitePackagesPaths { + fn from_iter>(iter: T) -> Self { + Self(IndexSet::from_iter(iter)) + } +} + +impl IntoIterator for SitePackagesPaths { + type Item = SystemPathBuf; + type IntoIter = indexmap::set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl PartialEq<&[SystemPathBuf]> for SitePackagesPaths { + fn eq(&self, other: &&[SystemPathBuf]) -> bool { + self.0.as_slice() == *other + } +} + #[derive(Debug)] pub(crate) enum PythonEnvironment { Virtual(VirtualEnvironment), @@ -35,7 +95,7 @@ impl PythonEnvironment { // Attempt to inspect as a virtual environment first // TODO(zanieb): Consider avoiding the clone here by checking for `pyvenv.cfg` ahead-of-time - match VirtualEnvironment::new(path.clone(), origin, system) { + match VirtualEnvironment::new(path.clone(), system) { Ok(venv) => Ok(Self::Virtual(venv)), // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment // @@ -54,7 +114,7 @@ impl PythonEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { match self { Self::Virtual(env) => env.site_packages_directories(system), Self::System(env) => env.site_packages_directories(system), @@ -111,12 +171,19 @@ pub(crate) struct VirtualEnvironment { /// This field will be `None` if so. version: Option, implementation: PythonImplementation, + + /// If this virtual environment was created using uv, + /// it may be an "ephemeral" virtual environment that dynamically adds the `site-packages` + /// directories of its parent environment to `sys.path` at runtime. + /// Newer versions of uv record the parent environment in the `pyvenv.cfg` file; + /// we'll want to add the `site-packages` directories of the parent environment + /// as search paths as well as the `site-packages` directories of this virtual environment. + parent_environment: Option>, } impl VirtualEnvironment { pub(crate) fn new( path: SysPrefixPath, - origin: SysPrefixPathOrigin, system: &dyn System, ) -> SitePackagesDiscoveryResult { fn pyvenv_cfg_line_number(index: usize) -> NonZeroUsize { @@ -128,12 +195,14 @@ impl VirtualEnvironment { let pyvenv_cfg = system .read_to_string(&pyvenv_cfg_path) - .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(origin, io_err))?; + .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(path.origin, io_err))?; let mut include_system_site_packages = false; let mut base_executable_home_path = None; let mut version_info_string = None; let mut implementation = PythonImplementation::Unknown; + let mut created_with_uv = false; + let mut parent_environment = None; // A `pyvenv.cfg` file *looks* like a `.ini` file, but actually isn't valid `.ini` syntax! // The Python standard-library's `site` module parses these files by splitting each line on @@ -178,6 +247,8 @@ impl VirtualEnvironment { _ => PythonImplementation::Unknown, }; } + "uv" => created_with_uv = true, + "extends-environment" => parent_environment = Some(value), _ => continue, } } @@ -196,11 +267,32 @@ impl VirtualEnvironment { let base_executable_home_path = PythonHomePath::new(base_executable_home_path, system) .map_err(|io_err| { SitePackagesDiscoveryError::PyvenvCfgParseError( - pyvenv_cfg_path, + pyvenv_cfg_path.clone(), PyvenvCfgParseErrorKind::InvalidHomeValue(io_err), ) })?; + // Since the `extends-environment` key is nonstandard, + // for now we only trust it if the virtual environment was created with `uv`. + let parent_environment = if created_with_uv { + parent_environment + .and_then(|sys_prefix| { + PythonEnvironment::new(sys_prefix, SysPrefixPathOrigin::DerivedFromPyvenvCfg, system) + .inspect_err(|err| { + tracing::warn!( + "Failed to resolve the parent environment of this ephemeral uv virtual environment \ + from the `extends-environment` value specified in the `pyvenv.cfg` file at {pyvenv_cfg_path}. \ + Imports will not be resolved correctly if they refer to packages installed into the parent \ + environment. Underlying error: {err}", + ); + }) + .ok() + }) + .map(Box::new) + } else { + None + }; + // but the `version`/`version_info` key is not read by the standard library, // and is provided under different keys depending on which virtual-environment creation tool // created the `pyvenv.cfg` file. Lenient parsing is appropriate here: @@ -218,6 +310,7 @@ impl VirtualEnvironment { include_system_site_packages, version, implementation, + parent_environment, }; tracing::trace!("Resolved metadata for virtual environment: {metadata:?}"); @@ -230,21 +323,34 @@ impl VirtualEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { let VirtualEnvironment { root_path, base_executable_home_path, include_system_site_packages, implementation, version, + parent_environment, } = self; - let mut site_packages_directories = vec![site_packages_directory_from_sys_prefix( - root_path, - *version, - *implementation, - system, - )?]; + let mut site_packages_directories = SitePackagesPaths::single( + site_packages_directory_from_sys_prefix(root_path, *version, *implementation, system)?, + ); + + if let Some(parent_env_site_packages) = parent_environment.as_deref() { + match parent_env_site_packages.site_packages_directories(system) { + Ok(parent_environment_site_packages) => { + site_packages_directories.extend(parent_environment_site_packages); + } + Err(err) => { + tracing::warn!( + "Failed to resolve the site-packages directories of this ephemeral uv virtual environment's \ + parent environment. Imports will not be resolved correctly if they refer to packages installed \ + into the parent environment. Underlying error: {err}" + ); + } + } + } if *include_system_site_packages { let system_sys_prefix = @@ -261,7 +367,7 @@ impl VirtualEnvironment { system, ) { Ok(site_packages_directory) => { - site_packages_directories.push(site_packages_directory); + site_packages_directories.insert(site_packages_directory); } Err(error) => tracing::warn!( "{error}. System site-packages will not be used for module resolution." @@ -309,15 +415,16 @@ impl SystemEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { let SystemEnvironment { root_path } = self; - let site_packages_directories = vec![site_packages_directory_from_sys_prefix( - root_path, - None, - PythonImplementation::Unknown, - system, - )?]; + let site_packages_directories = + SitePackagesPaths::single(site_packages_directory_from_sys_prefix( + root_path, + None, + PythonImplementation::Unknown, + system, + )?); tracing::debug!( "Resolved site-packages directories for this environment are: {site_packages_directories:?}" @@ -550,12 +657,12 @@ impl SysPrefixPath { if cfg!(target_os = "windows") { Some(Self { inner: path.to_path_buf(), - origin: SysPrefixPathOrigin::Derived, + origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg, }) } else { path.parent().map(|path| Self { inner: path.to_path_buf(), - origin: SysPrefixPathOrigin::Derived, + origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg, }) } } @@ -575,13 +682,22 @@ impl fmt::Display for SysPrefixPath { } } +/// Enumeration of sources a `sys.prefix` path can come from. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum SysPrefixPathOrigin { + /// The `sys.prefix` path came from a `--python` CLI flag PythonCliFlag, + /// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable VirtualEnvVar, + /// The `sys.prefix` path came from the `CONDA_PREFIX` environment variable CondaPrefixVar, - Derived, + /// The `sys.prefix` path was derived from a value in a `pyvenv.cfg` file: + /// either the value associated with the `home` key + /// or the value associated with the `extends-environment` key + DerivedFromPyvenvCfg, + /// A `.venv` directory was found in the current working directory, + /// and the `sys.prefix` path is the path to that virtual environment. LocalVenv, } @@ -591,7 +707,7 @@ impl SysPrefixPathOrigin { pub(crate) fn must_be_virtual_env(self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, - Self::PythonCliFlag | Self::Derived | Self::CondaPrefixVar => false, + Self::PythonCliFlag | Self::DerivedFromPyvenvCfg | Self::CondaPrefixVar => false, } } } @@ -602,7 +718,7 @@ impl Display for SysPrefixPathOrigin { Self::PythonCliFlag => f.write_str("`--python` argument"), Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"), Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"), - Self::Derived => f.write_str("derived `sys.prefix` path"), + Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"), Self::LocalVenv => f.write_str("local virtual environment"), } } @@ -692,6 +808,7 @@ mod tests { } } + #[derive(Default)] struct VirtualEnvironmentTestCase { system_site_packages: bool, pyvenv_cfg_version_field: Option<&'static str>, @@ -784,6 +901,7 @@ mod tests { pyvenv_cfg_contents.push_str(implementation_field); pyvenv_cfg_contents.push('\n'); } + // Deliberately using weird casing here to test that our pyvenv.cfg parsing is case-insensitive: if *system_site_packages { pyvenv_cfg_contents.push_str("include-system-site-packages = TRuE\n"); @@ -827,6 +945,7 @@ mod tests { env } + #[track_caller] fn assert_virtual_environment( &self, venv: &VirtualEnvironment, @@ -901,14 +1020,18 @@ mod tests { if self_venv.system_site_packages { assert_eq!( - &site_packages_directories, - &[expected_venv_site_packages, expected_system_site_packages] + site_packages_directories, + [expected_venv_site_packages, expected_system_site_packages].as_slice() ); } else { - assert_eq!(&site_packages_directories, &[expected_venv_site_packages]); + assert_eq!( + &site_packages_directories.into_iter().next().unwrap(), + &expected_venv_site_packages + ); } } + #[track_caller] fn assert_system_environment( &self, env: &SystemEnvironment, @@ -946,7 +1069,10 @@ mod tests { )) }; - assert_eq!(&site_packages_directories, &[expected_site_packages]); + assert_eq!( + site_packages_directories, + [expected_site_packages].as_slice() + ); } } @@ -1014,10 +1140,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: None, - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1031,10 +1155,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version = 3.12"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1048,10 +1170,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1065,10 +1185,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1082,10 +1200,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.13"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1101,8 +1217,7 @@ mod tests { virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: true, pyvenv_cfg_version_field: Some("version_info = 3.13"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1116,10 +1231,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = PyPy"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1134,10 +1247,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = CPython"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1152,10 +1263,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = GraalVM"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1169,12 +1278,7 @@ mod tests { minor_version: 13, free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, - virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, - implementation_field: None, - }), + virtual_env: Some(VirtualEnvironmentTestCase::default()), }; let venv = test.run().expect_venv(); assert_eq!(venv.implementation, PythonImplementation::Unknown); @@ -1271,12 +1375,11 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, pyvenv_cfg_version_field: Some("version_info = 3.13"), command_field: Some( r#"command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3"#, ), - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); diff --git a/crates/ty_test/README.md b/crates/ty_test/README.md index e16b56ec490169..3db1832231c9e3 100644 --- a/crates/ty_test/README.md +++ b/crates/ty_test/README.md @@ -298,7 +298,9 @@ python-version = "3.10" This configuration will apply to all tests in the same section, and all nested sections within that section. Nested sections can override configurations from their parent sections. -See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/ty_test/src/config.rs) for the full list of supported configuration options. +To enable logging in an mdtest, set `log = true` at the top level of the TOML block. +See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/ty_test/src/config.rs) +for the full list of supported configuration options. ### Specifying a custom typeshed diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 8375bf20461515..36533cf6dea592 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -169,7 +169,6 @@ fn run_test( let src_path = project_root.clone(); let custom_typeshed_path = test.configuration().typeshed(); - let python_path = test.configuration().python(); let python_version = test.configuration().python_version().unwrap_or_default(); let mut typeshed_files = vec![]; @@ -189,37 +188,35 @@ fn run_test( let mut full_path = embedded.full_path(&project_root); - if let Some(typeshed_path) = custom_typeshed_path { - if let Ok(relative_path) = full_path.strip_prefix(typeshed_path.join("stdlib")) { - if relative_path.as_str() == "VERSIONS" { - has_custom_versions_file = true; - } else if relative_path.extension().is_some_and(|ext| ext == "pyi") { - typeshed_files.push(relative_path.to_path_buf()); - } + if let Some(relative_path_to_custom_typeshed) = custom_typeshed_path + .and_then(|typeshed| full_path.strip_prefix(typeshed.join("stdlib")).ok()) + { + if relative_path_to_custom_typeshed.as_str() == "VERSIONS" { + has_custom_versions_file = true; + } else if relative_path_to_custom_typeshed + .extension() + .is_some_and(|ext| ext == "pyi") + { + typeshed_files.push(relative_path_to_custom_typeshed.to_path_buf()); } - } else if let Some(python_path) = python_path { - if let Ok(relative_path) = full_path.strip_prefix(python_path) { - // Construct the path to the site-packages directory - if relative_path.as_str() != "pyvenv.cfg" { - let mut new_path = SystemPathBuf::new(); - for component in full_path.components() { - let component = component.as_str(); - if component == "" { - if cfg!(target_os = "windows") { - new_path.push("Lib"); - new_path.push("site-packages"); - } else { - new_path.push("lib"); - new_path.push(format!("python{python_version}")); - new_path.push("site-packages"); - } - } else { - new_path.push(component); - } - } - full_path = new_path; - } + } else if let Some(component_index) = full_path + .components() + .position(|c| c.as_str() == "") + { + // If the path contains ``, we need to replace it with the + // actual site-packages directory based on the Python platform and version. + let mut components = full_path.components(); + let mut new_path: SystemPathBuf = + components.by_ref().take(component_index).collect(); + if cfg!(target_os = "windows") { + new_path.extend(["Lib", "site-packages"]); + } else { + new_path.push("lib"); + new_path.push(format!("python{python_version}")); + new_path.push("site-packages"); } + new_path.extend(components.skip(1)); + full_path = new_path; } db.write_file(&full_path, &embedded.code).unwrap(); From 452f992fbc4f48866c40c4b312806b8876958fa0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 28 May 2025 13:11:45 -0400 Subject: [PATCH 265/487] [ty] Simplify signature types, use them in `CallableType` (#18344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were many fields in `Signature` and friends that really had more to do with how a signature was being _used_ — how it was looked up, details about an individual call site, etc. Those fields more properly belong in `Bindings` and friends. This is a pure refactoring, and should not affect any tests or ecosystem projects. I started on this journey in support of https://github.com/astral-sh/ty/issues/462. It seemed worth pulling out as a separate PR. One major concrete benefit of this refactoring is that we can now use `CallableSignature` directly in `CallableType`. (We can't use `CallableSignature` directly in that `Type` variant because signatures are not currently interned.) --- crates/ty_python_semantic/src/types.rs | 690 ++++++++---------- crates/ty_python_semantic/src/types/call.rs | 4 +- .../ty_python_semantic/src/types/call/bind.rs | 288 +++++--- crates/ty_python_semantic/src/types/class.rs | 20 +- .../ty_python_semantic/src/types/display.rs | 6 +- crates/ty_python_semantic/src/types/infer.rs | 64 +- .../types/property_tests/type_generation.rs | 4 +- .../src/types/signatures.rs | 327 +++++---- 8 files changed, 722 insertions(+), 681 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9b53980915ea48..4176cb02738042 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -29,7 +29,7 @@ pub(crate) use self::infer::{ infer_scope_types, }; pub(crate) use self::narrow::ClassInfoConstraintFunction; -pub(crate) use self::signatures::{CallableSignature, Signature, Signatures}; +pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, file_to_module, resolve_module}; @@ -41,7 +41,7 @@ use crate::suppression::check_suppressions; use crate::symbol::{ Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings, }; -use crate::types::call::{Bindings, CallArgumentTypes, CallableBinding}; +use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; @@ -2768,7 +2768,7 @@ impl<'db> Type<'db> { Some((self, AttributeKind::NormalOrNonDataDescriptor)) } else { Some(( - Type::Callable(callable.bind_self(db)), + callable.bind_self(db), AttributeKind::NormalOrNonDataDescriptor, )) }; @@ -3546,28 +3546,28 @@ impl<'db> Type<'db> { non_negative_int_literal(db, return_ty) } - /// Returns the call signatures of a type. + /// Returns a [`Bindings`] that can be used to analyze a call to this type. You must call + /// [`match_parameters`][Bindings::match_parameters] and [`check_types`][Bindings::check_types] + /// to fully analyze a particular call site. /// - /// Note that all types have a valid [`Signatures`], even if the type is not callable. - /// Moreover, "callable" can be subtle for a union type, since some union elements might be - /// callable and some not. A union is callable if every element type is callable — and even - /// then, the elements might be inconsistent, such that there's no argument list that's valid - /// for all elements. It's usually best to only worry about "callability" relative to a - /// particular argument list, via [`try_call`][Self::try_call] and - /// [`CallErrorKind::NotCallable`]. - fn signatures(self, db: &'db dyn Db) -> Signatures<'db> { + /// Note that we return a [`Bindings`] for all types, even if the type is not callable. + /// "Callable" can be subtle for a union type, since some union elements might be callable and + /// some not. A union is callable if every element type is callable — but even then, the + /// elements might be inconsistent, such that there's no argument list that's valid for all + /// elements. It's usually best to only worry about "callability" relative to a particular + /// argument list, via [`try_call`][Self::try_call] and [`CallErrorKind::NotCallable`]. + fn bindings(self, db: &'db dyn Db) -> Bindings<'db> { match self { - Type::Callable(callable) => Signatures::single(match callable.signatures(db) { - [signature] => CallableSignature::single(self, signature.clone()), - signatures => CallableSignature::from_overloads(self, signatures.iter().cloned()), - }), + Type::Callable(callable) => { + CallableBinding::from_overloads(self, callable.signatures(db).iter().cloned()) + .into() + } Type::BoundMethod(bound_method) => { let signature = bound_method.function(db).signature(db); - Signatures::single( - CallableSignature::from_overloads(self, signature.overloads.iter().cloned()) - .with_bound_type(bound_method.self_instance(db)), - ) + CallableBinding::from_overloads(self, signature.overloads.iter().cloned()) + .with_bound_type(bound_method.self_instance(db)) + .into() } Type::MethodWrapper( @@ -3591,7 +3591,7 @@ impl<'db> Type<'db> { // specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`. let not_none = Type::none(db).negate(db); - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3617,8 +3617,8 @@ impl<'db> Type<'db> { None, ), ], - ); - Signatures::single(signature) + ) + .into() } Type::WrapperDescriptor( @@ -3644,7 +3644,7 @@ impl<'db> Type<'db> { unreachable!("Not part of outer match pattern") } }; - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3674,86 +3674,85 @@ impl<'db> Type<'db> { None, ), ], - ); - Signatures::single(signature) + ) + .into() } - Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(_)) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::object(db)), - ]), - None, - ), - )) - } - Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("self"))) - .with_annotated_type(KnownClass::Property.to_instance(db)), - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::object(db)), - ]), - None, - ), - )) - } + Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(_)) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::object(db)), + ]), + None, + ), + ) + .into(), - Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("prefix"))) - .with_annotated_type(UnionType::from_elements( - db, - [ - KnownClass::Str.to_instance(db), - KnownClass::Tuple.to_specialized_instance( - db, - [KnownClass::Str.to_instance(db)], - ), - ], - )), - Parameter::positional_only(Some(Name::new_static("start"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - Parameter::positional_only(Some(Name::new_static("end"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - ]), - Some(KnownClass::Bool.to_instance(db)), - ), - )) - } + Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("self"))) + .with_annotated_type(KnownClass::Property.to_instance(db)), + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::object(db)), + ]), + None, + ), + ) + .into(), + + Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("prefix"))) + .with_annotated_type(UnionType::from_elements( + db, + [ + KnownClass::Str.to_instance(db), + KnownClass::Tuple.to_specialized_instance( + db, + [KnownClass::Str.to_instance(db)], + ), + ], + )), + Parameter::positional_only(Some(Name::new_static("start"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("end"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + ]), + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), // TODO: We should probably also check the original return type of the function // that was decorated with `@dataclass_transform`, to see if it is consistent with // with what we configure here. - Type::DataclassTransformer(_) => Signatures::single(CallableSignature::single( + Type::DataclassTransformer(_) => Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static("func"))) .with_annotated_type(Type::object(db))]), None, ), - )), + ) + .into(), Type::FunctionLiteral(function_type) => match function_type.known(db) { Some( @@ -3762,62 +3761,54 @@ impl<'db> Type<'db> { | KnownFunction::IsAssignableTo | KnownFunction::IsDisjointFrom | KnownFunction::IsGradualEquivalentTo, - ) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("a"))) - .type_form() - .with_annotated_type(Type::any()), - Parameter::positional_only(Some(Name::new_static("b"))) - .type_form() - .with_annotated_type(Type::any()), - ]), - Some(KnownClass::Bool.to_instance(db)), - ), - ); - Signatures::single(signature) - } + ) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("a"))) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("b"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), Some( KnownFunction::IsFullyStatic | KnownFunction::IsSingleton | KnownFunction::IsSingleValued, - ) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([Parameter::positional_only(Some(Name::new_static( - "a", - ))) + ) => Binding::single( + self, + Signature::new( + Parameters::new([Parameter::positional_only(Some(Name::new_static("a"))) .type_form() .with_annotated_type(Type::any())]), - Some(KnownClass::Bool.to_instance(db)), - ), - ); - Signatures::single(signature) - } + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), - Some(KnownFunction::AssertType) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::any()), - Parameter::positional_only(Some(Name::new_static("type"))) - .type_form() - .with_annotated_type(Type::any()), - ]), - Some(Type::none(db)), - ), - ); - Signatures::single(signature) - } + Some(KnownFunction::AssertType) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("type"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(Type::none(db)), + ), + ) + .into(), Some(KnownFunction::AssertNever) => { - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static( @@ -3830,29 +3821,27 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any())]), Some(Type::none(db)), ), - ); - Signatures::single(signature) + ) + .into() } - Some(KnownFunction::Cast) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_or_keyword(Name::new_static("typ")) - .type_form() - .with_annotated_type(Type::any()), - Parameter::positional_or_keyword(Name::new_static("val")) - .with_annotated_type(Type::any()), - ]), - Some(Type::any()), - ), - ); - Signatures::single(signature) - } + Some(KnownFunction::Cast) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_or_keyword(Name::new_static("typ")) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_or_keyword(Name::new_static("val")) + .with_annotated_type(Type::any()), + ]), + Some(Type::any()), + ), + ) + .into(), Some(KnownFunction::Dataclass) => { - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ // def dataclass(cls: None, /) -> Callable[[type[_T]], type[_T]]: ... @@ -3922,12 +3911,15 @@ impl<'db> Type<'db> { None, ), ], - ); - - Signatures::single(signature) + ) + .into() } - _ => Signatures::single(function_type.signature(db).overloads.clone()), + _ => CallableBinding::from_overloads( + self, + function_type.signature(db).overloads.iter().cloned(), + ) + .into(), }, Type::ClassLiteral(class) => match class.known(db) { @@ -3942,7 +3934,7 @@ impl<'db> Type<'db> { // class bool(int): // def __new__(cls, o: object = ..., /) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static( @@ -3952,8 +3944,8 @@ impl<'db> Type<'db> { .with_default_type(Type::BooleanLiteral(false))]), Some(KnownClass::Bool.to_instance(db)), ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Str) => { @@ -3964,7 +3956,7 @@ impl<'db> Type<'db> { // @overload // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3997,8 +3989,8 @@ impl<'db> Type<'db> { Some(KnownClass::Str.to_instance(db)), ), ], - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Type) => { @@ -4012,7 +4004,7 @@ impl<'db> Type<'db> { // @overload // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -4042,29 +4034,32 @@ impl<'db> Type<'db> { Some(type_instance), ), ], - ); - Signatures::single(signature) + ) + .into() } + Some(KnownClass::NamedTuple) => { - Signatures::single(CallableSignature::todo("functional `NamedTuple` syntax")) + Binding::single(self, Signature::todo("functional `NamedTuple` syntax")).into() } + Some(KnownClass::Object) => { // ```py // class object: // def __init__(self) -> None: ... // def __new__(cls) -> Self: ... // ``` - let signature = CallableSignature::from_overloads( + Binding::single( self, - [Signature::new( + Signature::new( Parameters::empty(), Some(KnownClass::Object.to_instance(db)), - )], - ); - Signatures::single(signature) + ), + ) + .into() } + Some(KnownClass::Enum) => { - Signatures::single(CallableSignature::todo("functional `Enum` syntax")) + Binding::single(self, Signature::todo("functional `Enum` syntax")).into() } Some(KnownClass::Super) => { @@ -4077,7 +4072,7 @@ impl<'db> Type<'db> { // @overload // def __init__(self) -> None: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -4101,8 +4096,8 @@ impl<'db> Type<'db> { Some(KnownClass::Super.to_instance(db)), ), ], - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::TypeVar) => { @@ -4119,7 +4114,7 @@ impl<'db> Type<'db> { // default: Any = ..., // ) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4151,8 +4146,8 @@ impl<'db> Type<'db> { ]), Some(KnownClass::TypeVar.to_instance(db)), ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::TypeAliasType) => { @@ -4165,7 +4160,7 @@ impl<'db> Type<'db> { // type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () // ) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4190,8 +4185,8 @@ impl<'db> Type<'db> { ]), None, ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Property) => { @@ -4215,7 +4210,7 @@ impl<'db> Type<'db> { Some(Type::any()), ); - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4223,10 +4218,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - getter_signature, - )), + CallableType::single(db, getter_signature), Type::none(db), ], )) @@ -4235,10 +4227,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - setter_signature, - )), + CallableType::single(db, setter_signature), Type::none(db), ], )) @@ -4247,10 +4236,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - deleter_signature, - )), + CallableType::single(db, deleter_signature), Type::none(db), ], )) @@ -4264,8 +4250,8 @@ impl<'db> Type<'db> { ]), None, ), - ); - Signatures::single(signature) + ) + .into() } // Most class literal constructor calls are handled by `try_call_constructor` and @@ -4273,21 +4259,19 @@ impl<'db> Type<'db> { // cases (e.g. evaluating callable subtyping). TODO improve this definition // (intersection of `__new__` and `__init__` signatures? and respect metaclass // `__call__`). - _ => { - let signature = CallableSignature::single( - self, - Signature::new_generic( - class.generic_context(db), - Parameters::gradual_form(), - self.to_instance(db), - ), - ); - Signatures::single(signature) - } + _ => Binding::single( + self, + Signature::new_generic( + class.generic_context(db), + Parameters::gradual_form(), + self.to_instance(db), + ), + ) + .into(), }, Type::KnownInstance(KnownInstanceType::TypedDict) => { - Signatures::single(CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4305,28 +4289,28 @@ impl<'db> Type<'db> { ]), None, ), - )) + ) + .into() } Type::GenericAlias(_) => { // TODO annotated return type on `__new__` or metaclass `__call__` // TODO check call vs signatures of `__new__` and/or `__init__` - let signature = CallableSignature::single( + Binding::single( self, Signature::new(Parameters::gradual_form(), self.to_instance(db)), - ); - Signatures::single(signature) + ) + .into() } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - SubclassOfInner::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).signatures(db) - } + SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).bindings(db), + // Most type[] constructor calls are handled by `try_call_constructor` and not via // getting the signature here. This signature can still be used in some cases (e.g. // evaluating callable subtyping). TODO improve this definition (intersection of // `__new__` and `__init__` signatures? and respect metaclass `__call__`). - SubclassOfInner::Class(class) => Type::from(class).signatures(db), + SubclassOfInner::Class(class) => Type::from(class).bindings(db), }, Type::NominalInstance(_) | Type::ProtocolInstance(_) => { @@ -4344,39 +4328,43 @@ impl<'db> Type<'db> { .symbol { Symbol::Type(dunder_callable, boundness) => { - let mut signatures = dunder_callable.signatures(db).clone(); - signatures.replace_callable_type(dunder_callable, self); + let mut bindings = dunder_callable.bindings(db); + bindings.replace_callable_type(dunder_callable, self); if boundness == Boundness::PossiblyUnbound { - signatures.set_dunder_call_is_possibly_unbound(); + bindings.set_dunder_call_is_possibly_unbound(); } - signatures + bindings } - Symbol::Unbound => Signatures::not_callable(self), + Symbol::Unbound => CallableBinding::not_callable(self).into(), } } // Dynamic types are callable, and the return type is the same dynamic type. Similarly, // `Never` is always callable and returns `Never`. - Type::Dynamic(_) | Type::Never => Signatures::single(CallableSignature::dynamic(self)), + Type::Dynamic(_) | Type::Never => { + Binding::single(self, Signature::dynamic(self)).into() + } // Note that this correctly returns `None` if none of the union elements are callable. - Type::Union(union) => Signatures::from_union( + Type::Union(union) => Bindings::from_union( self, union .elements(db) .iter() - .map(|element| element.signatures(db)), + .map(|element| element.bindings(db)), ), Type::Intersection(_) => { - Signatures::single(CallableSignature::todo("Type::Intersection.call()")) + Binding::single(self, Signature::todo("Type::Intersection.call()")).into() } // TODO: these are actually callable - Type::MethodWrapper(_) | Type::DataclassDecorator(_) => Signatures::not_callable(self), + Type::MethodWrapper(_) | Type::DataclassDecorator(_) => { + CallableBinding::not_callable(self).into() + } // TODO: some `KnownInstance`s are callable (e.g. TypedDicts) - Type::KnownInstance(_) => Signatures::not_callable(self), + Type::KnownInstance(_) => CallableBinding::not_callable(self).into(), Type::PropertyInstance(_) | Type::AlwaysFalsy @@ -4389,7 +4377,7 @@ impl<'db> Type<'db> { | Type::Tuple(_) | Type::BoundSuper(_) | Type::TypeVar(_) - | Type::ModuleLiteral(_) => Signatures::not_callable(self), + | Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(), } } @@ -4404,8 +4392,9 @@ impl<'db> Type<'db> { db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>, ) -> Result, CallError<'db>> { - let signatures = self.signatures(db); - Bindings::match_parameters(signatures, argument_types).check_types(db, argument_types) + self.bindings(db) + .match_parameters(argument_types) + .check_types(db, argument_types) } /// Look up a dunder method on the meta-type of `self` and call it. @@ -4451,8 +4440,9 @@ impl<'db> Type<'db> { .symbol { Symbol::Type(dunder_callable, boundness) => { - let signatures = dunder_callable.signatures(db); - let bindings = Bindings::match_parameters(signatures, argument_types) + let bindings = dunder_callable + .bindings(db) + .match_parameters(argument_types) .check_types(db, argument_types)?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); @@ -4970,7 +4960,7 @@ impl<'db> Type<'db> { KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), // TODO: Use an opt-in rule for a bare `Callable` - KnownInstanceType::Callable => Ok(Type::Callable(CallableType::unknown(db))), + KnownInstanceType::Callable => Ok(CallableType::unknown(db)), KnownInstanceType::TypingSelf => { let index = semantic_index(db, scope_id.file(db)); @@ -6896,7 +6886,7 @@ impl<'db> FunctionSignature<'db> { /// Returns the "bottom" signature (subtype of all fully-static signatures.) pub(crate) fn bottom(db: &'db dyn Db) -> Self { FunctionSignature { - overloads: CallableSignature::single(Type::any(), Signature::bottom(db)), + overloads: CallableSignature::single(Signature::bottom(db)), implementation: None, } } @@ -6969,9 +6959,9 @@ impl<'db> FunctionType<'db> { /// Convert the `FunctionType` into a [`Type::Callable`]. pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::from_overloads( + Type::Callable(CallableType::new( db, - self.signature(db).overloads.iter().cloned(), + self.signature(db).overloads.clone(), false, )) } @@ -7045,7 +7035,6 @@ impl<'db> FunctionType<'db> { if let Some(overloaded) = self.to_overloaded(db) { FunctionSignature { overloads: CallableSignature::from_overloads( - Type::FunctionLiteral(self), overloaded.overloads.iter().copied().map(|overload| { type_mappings.iter().fold( overload.internal_signature(db, inherited_generic_context), @@ -7062,13 +7051,10 @@ impl<'db> FunctionType<'db> { } } else { FunctionSignature { - overloads: CallableSignature::single( - Type::FunctionLiteral(self), - type_mappings.iter().fold( - self.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ), - ), + overloads: CallableSignature::single(type_mappings.iter().fold( + self.internal_signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + )), implementation: None, } } @@ -7592,13 +7578,15 @@ pub struct BoundMethodType<'db> { impl<'db> BoundMethodType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::from_overloads( + Type::Callable(CallableType::new( db, - self.function(db) - .signature(db) - .overloads - .iter() - .map(signatures::Signature::bind_self), + CallableSignature::from_overloads( + self.function(db) + .signature(db) + .overloads + .iter() + .map(signatures::Signature::bind_self), + ), false, )) } @@ -7662,8 +7650,9 @@ impl<'db> BoundMethodType<'db> { #[salsa::interned(debug)] #[derive(PartialOrd, Ord)] pub struct CallableType<'db> { - #[returns(deref)] - signatures: Box<[Signature<'db>]>, + #[returns(ref)] + signatures: CallableSignature<'db>, + /// We use `CallableType` to represent function-like objects, like the synthesized methods /// of dataclasses or NamedTuples. These callables act like real functions when accessed /// as attributes on instances, i.e. they bind `self`. @@ -7671,49 +7660,37 @@ pub struct CallableType<'db> { } impl<'db> CallableType<'db> { - /// Create a non-overloaded callable type with a single signature. - pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self { - CallableType::new(db, vec![signature].into_boxed_slice(), false) + /// Create a callable type with a single non-overloaded signature. + pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { + Type::Callable(CallableType::new( + db, + CallableSignature::single(signature), + false, + )) } /// Create a non-overloaded, function-like callable type with a single signature. /// /// A function-like callable will bind `self` when accessed as an attribute on an instance. - pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Self { - CallableType::new(db, vec![signature].into_boxed_slice(), true) - } - - /// Create an overloaded callable type with multiple signatures. - /// - /// # Panics - /// - /// Panics if `overloads` is empty. - pub(crate) fn from_overloads(db: &'db dyn Db, overloads: I, is_function_like: bool) -> Self - where - I: IntoIterator>, - { - let overloads = overloads.into_iter().collect::>().into_boxed_slice(); - assert!( - !overloads.is_empty(), - "CallableType must have at least one signature" - ); - CallableType::new(db, overloads, is_function_like) + pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { + Type::Callable(CallableType::new( + db, + CallableSignature::single(signature), + true, + )) } /// Create a callable type which accepts any parameters and returns an `Unknown` type. - pub(crate) fn unknown(db: &'db dyn Db) -> Self { - CallableType::single( - db, - Signature::new(Parameters::unknown(), Some(Type::unknown())), - ) + pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> { + Self::single(db, Signature::unknown()) } - pub(crate) fn bind_self(self, db: &'db dyn Db) -> Self { - CallableType::from_overloads( + pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> { + Type::Callable(CallableType::new( db, - self.signatures(db).iter().map(Signature::bind_self), + self.signatures(db).bind_self(), false, - ) + )) } /// Create a callable type which represents a fully-static "bottom" callable. @@ -7722,28 +7699,24 @@ impl<'db> CallableType<'db> { /// `(*args: object, **kwargs: object) -> Never`. #[cfg(test)] pub(crate) fn bottom(db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::single(db, Signature::bottom(db))) + Self::single(db, Signature::bottom(db)) } /// Return a "normalized" version of this `Callable` type. /// /// See [`Type::normalized`] for more details. fn normalized(self, db: &'db dyn Db) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .map(|signature| signature.normalized(db)), + self.signatures(db).normalized(db), self.is_function_like(db), ) } fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .map(|signature| signature.apply_type_mapping(db, type_mapping)), + self.signatures(db).apply_type_mapping(db, type_mapping), self.is_function_like(db), ) } @@ -7753,148 +7726,63 @@ impl<'db> CallableType<'db> { db: &'db dyn Db, typevars: &mut FxOrderSet>, ) { - for signature in self.signatures(db) { - signature.find_legacy_typevars(db, typevars); - } + self.signatures(db).find_legacy_typevars(db, typevars); } /// Check whether this callable type is fully static. /// /// See [`Type::is_fully_static`] for more details. fn is_fully_static(self, db: &'db dyn Db) -> bool { - self.signatures(db) - .iter() - .all(|signature| signature.is_fully_static(db)) + self.signatures(db).is_fully_static(db) } /// Check whether this callable type is a subtype of another callable type. /// /// See [`Type::is_subtype_of`] for more details. fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - self.is_assignable_to_impl(db, other, &|self_signature, other_signature| { - self_signature.is_subtype_of(db, other_signature) - }) + let self_is_function_like = self.is_function_like(db); + let other_is_function_like = other.is_function_like(db); + (self_is_function_like || !other_is_function_like) + && self.signatures(db).is_subtype_of(db, other.signatures(db)) } /// Check whether this callable type is assignable to another callable type. /// /// See [`Type::is_assignable_to`] for more details. fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - self.is_assignable_to_impl(db, other, &|self_signature, other_signature| { - self_signature.is_assignable_to(db, other_signature) - }) - } - - /// Implementation for the various relation checks between two, possible overloaded, callable - /// types. - /// - /// The `check_signature` closure is used to check the relation between two [`Signature`]s. - fn is_assignable_to_impl(self, db: &'db dyn Db, other: Self, check_signature: &F) -> bool - where - F: Fn(&Signature<'db>, &Signature<'db>) -> bool, - { let self_is_function_like = self.is_function_like(db); let other_is_function_like = other.is_function_like(db); - - if !self_is_function_like && other_is_function_like { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - // Base case: both callable types contain a single signature. - check_signature(self_signature, other_signature) - } - - // `self` is possibly overloaded while `other` is definitely not overloaded. - (self_signatures, [other_signature]) => { - let other_callable = CallableType::single(db, other_signature.clone()); - self_signatures - .iter() - .map(|self_signature| CallableType::single(db, self_signature.clone())) - .any(|self_callable| { - self_callable.is_assignable_to_impl(db, other_callable, check_signature) - }) - } - - // `self` is definitely not overloaded while `other` is possibly overloaded. - ([self_signature], other_signatures) => { - let self_callable = CallableType::single(db, self_signature.clone()); - other_signatures - .iter() - .map(|other_signature| CallableType::single(db, other_signature.clone())) - .all(|other_callable| { - self_callable.is_assignable_to_impl(db, other_callable, check_signature) - }) - } - - // `self` is definitely overloaded while `other` is possibly overloaded. - (_, other_signatures) => other_signatures - .iter() - .map(|other_signature| CallableType::single(db, other_signature.clone())) - .all(|other_callable| { - self.is_assignable_to_impl(db, other_callable, check_signature) - }), - } + (self_is_function_like || !other_is_function_like) + && self + .signatures(db) + .is_assignable_to(db, other.signatures(db)) } /// Check whether this callable type is equivalent to another callable type. /// /// See [`Type::is_equivalent_to`] for more details. fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - if self.is_function_like(db) != other.is_function_like(db) { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - // Common case: both callable types contain a single signature, use the custom - // equivalence check instead of delegating it to the subtype check. - self_signature.is_equivalent_to(db, other_signature) - } - (self_signatures, other_signatures) => { - if !self_signatures - .iter() - .chain(other_signatures.iter()) - .all(|signature| signature.is_fully_static(db)) - { - return false; - } - if self == other { - return true; - } - self.is_subtype_of(db, other) && other.is_subtype_of(db, self) - } - } + self.is_function_like(db) == other.is_function_like(db) + && self + .signatures(db) + .is_equivalent_to(db, other.signatures(db)) } /// Check whether this callable type is gradual equivalent to another callable type. /// /// See [`Type::is_gradual_equivalent_to`] for more details. fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - if self.is_function_like(db) != other.is_function_like(db) { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - self_signature.is_gradual_equivalent_to(db, other_signature) - } - _ => { - // TODO: overloads - false - } - } + self.is_function_like(db) == other.is_function_like(db) + && self + .signatures(db) + .is_gradual_equivalent_to(db, other.signatures(db)) } /// See [`Type::replace_self_reference`]. fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .cloned() - .map(|signature| signature.replace_self_reference(db, class)), + self.signatures(db).replace_self_reference(db, class), self.is_function_like(db), ) } diff --git a/crates/ty_python_semantic/src/types/call.rs b/crates/ty_python_semantic/src/types/call.rs index 27c10e5432a0d0..bbceeec704f459 100644 --- a/crates/ty_python_semantic/src/types/call.rs +++ b/crates/ty_python_semantic/src/types/call.rs @@ -1,11 +1,11 @@ use super::context::InferContext; -use super::{CallableSignature, Signature, Signatures, Type}; +use super::{Signature, Type}; use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments}; -pub(super) use bind::{Bindings, CallableBinding}; +pub(super) use bind::{Binding, Bindings, CallableBinding}; /// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 67a8721be0688a..07888335a18af1 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,11 +3,11 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. -use smallvec::SmallVec; +use smallvec::{SmallVec, smallvec}; use super::{ - Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, CallableSignature, - InferContext, Signature, Signatures, Type, + Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, InferContext, Signature, + Type, }; use crate::db::Db; use crate::dunder_all::dunder_all_names; @@ -33,7 +33,9 @@ use ruff_python_ast as ast; /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug)] pub(crate) struct Bindings<'db> { - signatures: Signatures<'db>, + /// The type that is (hopefully) callable. + callable_type: Type<'db>, + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union /// type. elements: SmallVec<[CallableBinding<'db>; 1]>, @@ -45,6 +47,40 @@ pub(crate) struct Bindings<'db> { } impl<'db> Bindings<'db> { + /// Creates a new `Bindings` from an iterator of [`Bindings`]s. Panics if the iterator is + /// empty. + pub(crate) fn from_union(callable_type: Type<'db>, elements: I) -> Self + where + I: IntoIterator>, + { + let elements: SmallVec<_> = elements + .into_iter() + .flat_map(|s| s.elements.into_iter()) + .collect(); + assert!(!elements.is_empty()); + Self { + callable_type, + elements, + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } + + pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + for binding in &mut self.elements { + binding.replace_callable_type(before, after); + } + } + + pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) { + for binding in &mut self.elements { + binding.dunder_call_is_possibly_unbound = true; + } + } + /// Match the arguments of a call site against the parameters of a collection of possibly /// unioned, possibly overloaded signatures. /// @@ -55,30 +91,15 @@ impl<'db> Bindings<'db> { /// /// Once you have argument types available, you can call [`check_types`][Self::check_types] to /// verify that each argument type is assignable to the corresponding parameter type. - pub(crate) fn match_parameters( - signatures: Signatures<'db>, - arguments: &CallArguments<'_>, - ) -> Self { + pub(crate) fn match_parameters(mut self, arguments: &CallArguments<'_>) -> Self { let mut argument_forms = vec![None; arguments.len()]; let mut conflicting_forms = vec![false; arguments.len()]; - let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures - .iter() - .map(|signature| { - CallableBinding::match_parameters( - signature, - arguments, - &mut argument_forms, - &mut conflicting_forms, - ) - }) - .collect(); - - Bindings { - signatures, - elements, - argument_forms: argument_forms.into(), - conflicting_forms: conflicting_forms.into(), + for binding in &mut self.elements { + binding.match_parameters(arguments, &mut argument_forms, &mut conflicting_forms); } + self.argument_forms = argument_forms.into(); + self.conflicting_forms = conflicting_forms.into(); + self } /// Verify that the type of each argument is assignable to type of the parameter that it was @@ -95,8 +116,8 @@ impl<'db> Bindings<'db> { db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>, ) -> Result> { - for (signature, element) in self.signatures.iter().zip(&mut self.elements) { - element.check_types(db, signature, argument_types); + for element in &mut self.elements { + element.check_types(db, argument_types); } self.evaluate_known_cases(db); @@ -157,7 +178,7 @@ impl<'db> Bindings<'db> { } pub(crate) fn callable_type(&self) -> Type<'db> { - self.signatures.callable_type + self.callable_type } /// Returns the return type of the call. For successful calls, this is the actual return type. @@ -228,7 +249,7 @@ impl<'db> Bindings<'db> { }; // Each special case listed here should have a corresponding clause in `Type::signatures`. - for (binding, callable_signature) in self.elements.iter_mut().zip(self.signatures.iter()) { + for binding in &mut self.elements { let binding_type = binding.callable_type; for (overload_index, overload) in binding.matching_overloads_mut() { match binding_type { @@ -848,15 +869,12 @@ impl<'db> Bindings<'db> { let mut dataclass_params = DataclassParams::from(params); - if let Some(Some(Type::BooleanLiteral(order))) = - callable_signature.iter().nth(overload_index).and_then( - |signature| { - let (idx, _) = signature - .parameters() - .keyword_by_name("order")?; - overload.parameter_types().get(idx) - }, - ) + if let Some(Some(Type::BooleanLiteral(order))) = overload + .signature + .parameters() + .keyword_by_name("order") + .map(|(idx, _)| idx) + .and_then(|idx| overload.parameter_types().get(idx)) { dataclass_params.set(DataclassParams::ORDER, *order); } @@ -945,6 +963,37 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { } } +impl<'db> From> for Bindings<'db> { + fn from(from: CallableBinding<'db>) -> Bindings<'db> { + Bindings { + callable_type: from.callable_type, + elements: smallvec![from], + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } +} + +impl<'db> From> for Bindings<'db> { + fn from(from: Binding<'db>) -> Bindings<'db> { + let callable_type = from.callable_type; + let signature_type = from.signature_type; + let callable_binding = CallableBinding { + callable_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads: smallvec![from], + }; + Bindings { + callable_type, + elements: smallvec![callable_binding], + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } +} + /// Binding information for a single callable. If the callable is overloaded, there is a separate /// [`Binding`] for each overload. /// @@ -962,10 +1011,21 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug)] pub(crate) struct CallableBinding<'db> { + /// The type that is (hopefully) callable. pub(crate) callable_type: Type<'db>, + + /// The type we'll use for error messages referring to details of the called signature. For + /// calls to functions this will be the same as `callable_type`; for other callable instances + /// it may be a `__call__` method. pub(crate) signature_type: Type<'db>, + + /// If this is a callable object (i.e. called via a `__call__` method), the boundness of + /// that call method. pub(crate) dunder_call_is_possibly_unbound: bool, + /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. + pub(crate) bound_type: Option>, + /// The bindings of each overload of this callable. Will be empty if the type is not callable. /// /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a @@ -974,15 +1034,56 @@ pub(crate) struct CallableBinding<'db> { } impl<'db> CallableBinding<'db> { + pub(crate) fn from_overloads( + signature_type: Type<'db>, + overloads: impl IntoIterator>, + ) -> Self { + let overloads = overloads + .into_iter() + .map(|signature| Binding::single(signature_type, signature)) + .collect(); + Self { + callable_type: signature_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads, + } + } + + pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { + Self { + callable_type: signature_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads: smallvec![], + } + } + + pub(crate) fn with_bound_type(mut self, bound_type: Type<'db>) -> Self { + self.bound_type = Some(bound_type); + self + } + + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + for binding in &mut self.overloads { + binding.replace_callable_type(before, after); + } + } + fn match_parameters( - signature: &CallableSignature<'db>, + &mut self, arguments: &CallArguments<'_>, argument_forms: &mut [Option], conflicting_forms: &mut [bool], - ) -> Self { + ) { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let arguments = arguments.with_self(signature.bound_type); + let arguments = arguments.with_self(self.bound_type); // TODO: This checks every overload. In the proposed more detailed call checking spec [1], // arguments are checked for arity first, and are only checked for type assignability against @@ -990,37 +1091,17 @@ impl<'db> CallableBinding<'db> { // two phases. // // [1] https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation - let overloads = signature - .into_iter() - .map(|signature| { - Binding::match_parameters( - signature, - arguments.as_ref(), - argument_forms, - conflicting_forms, - ) - }) - .collect(); - - CallableBinding { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - overloads, + for overload in &mut self.overloads { + overload.match_parameters(arguments.as_ref(), argument_forms, conflicting_forms); } } - fn check_types( - &mut self, - db: &'db dyn Db, - signature: &CallableSignature<'db>, - argument_types: &CallArgumentTypes<'_, 'db>, - ) { + fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let argument_types = argument_types.with_self(signature.bound_type); - for (signature, overload) in signature.iter().zip(&mut self.overloads) { - overload.check_types(db, signature, argument_types.as_ref()); + let argument_types = argument_types.with_self(self.bound_type); + for overload in &mut self.overloads { + overload.check_types(db, argument_types.as_ref()); } } @@ -1215,9 +1296,28 @@ impl<'db> CallableBinding<'db> { } } +impl<'a, 'db> IntoIterator for &'a CallableBinding<'db> { + type Item = &'a Binding<'db>; + type IntoIter = std::slice::Iter<'a, Binding<'db>>; + + fn into_iter(self) -> Self::IntoIter { + self.overloads.iter() + } +} + /// Binding information for one of the overloads of a callable. #[derive(Debug)] pub(crate) struct Binding<'db> { + pub(crate) signature: Signature<'db>, + + /// The type that is (hopefully) callable. + pub(crate) callable_type: Type<'db>, + + /// The type we'll use for error messages referring to details of the called signature. For + /// calls to functions this will be the same as `callable_type`; for other callable instances + /// it may be a `__call__` method. + pub(crate) signature_type: Type<'db>, + /// Return type of the call. return_ty: Type<'db>, @@ -1241,18 +1341,37 @@ pub(crate) struct Binding<'db> { } impl<'db> Binding<'db> { + pub(crate) fn single(signature_type: Type<'db>, signature: Signature<'db>) -> Binding<'db> { + Binding { + signature, + callable_type: signature_type, + signature_type, + return_ty: Type::unknown(), + specialization: None, + inherited_specialization: None, + argument_parameters: Box::from([]), + parameter_tys: Box::from([]), + errors: vec![], + } + } + + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + } + fn match_parameters( - signature: &Signature<'db>, + &mut self, arguments: &CallArguments<'_>, argument_forms: &mut [Option], conflicting_forms: &mut [bool], - ) -> Self { - let parameters = signature.parameters(); + ) { + let parameters = self.signature.parameters(); // The parameter that each argument is matched with. let mut argument_parameters = vec![None; arguments.len()]; // Whether each parameter has been matched with an argument. let mut parameter_matched = vec![false; parameters.len()]; - let mut errors = vec![]; let mut next_positional = 0; let mut first_excess_positional = None; let mut num_synthetic_args = 0; @@ -1290,7 +1409,7 @@ impl<'db> Binding<'db> { .keyword_by_name(name) .or_else(|| parameters.keyword_variadic()) else { - errors.push(BindingError::UnknownArgument { + self.errors.push(BindingError::UnknownArgument { argument_name: ast::name::Name::new(name), argument_index: get_argument_index(argument_index, num_synthetic_args), }); @@ -1315,7 +1434,7 @@ impl<'db> Binding<'db> { } if parameter_matched[index] { if !parameter.is_variadic() && !parameter.is_keyword_variadic() { - errors.push(BindingError::ParameterAlreadyAssigned { + self.errors.push(BindingError::ParameterAlreadyAssigned { argument_index: get_argument_index(argument_index, num_synthetic_args), parameter: ParameterContext::new(parameter, index, positional), }); @@ -1325,7 +1444,7 @@ impl<'db> Binding<'db> { parameter_matched[index] = true; } if let Some(first_excess_argument_index) = first_excess_positional { - errors.push(BindingError::TooManyPositionalArguments { + self.errors.push(BindingError::TooManyPositionalArguments { first_excess_argument_index: get_argument_index( first_excess_argument_index, num_synthetic_args, @@ -1350,27 +1469,17 @@ impl<'db> Binding<'db> { } if !missing.is_empty() { - errors.push(BindingError::MissingArguments { + self.errors.push(BindingError::MissingArguments { parameters: ParameterContexts(missing), }); } - Self { - return_ty: signature.return_ty.unwrap_or(Type::unknown()), - specialization: None, - inherited_specialization: None, - argument_parameters: argument_parameters.into_boxed_slice(), - parameter_tys: vec![None; parameters.len()].into_boxed_slice(), - errors, - } + self.return_ty = self.signature.return_ty.unwrap_or(Type::unknown()); + self.argument_parameters = argument_parameters.into_boxed_slice(); + self.parameter_tys = vec![None; parameters.len()].into_boxed_slice(); } - fn check_types( - &mut self, - db: &'db dyn Db, - signature: &Signature<'db>, - argument_types: &CallArgumentTypes<'_, 'db>, - ) { + fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) { let mut num_synthetic_args = 0; let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { if argument_index >= num_synthetic_args { @@ -1386,6 +1495,7 @@ impl<'db> Binding<'db> { // If this overload is generic, first see if we can infer a specialization of the function // from the arguments that were passed in. + let signature = &self.signature; let parameters = signature.parameters(); if signature.generic_context.is_some() || signature.inherited_generic_context.is_some() { let mut builder = SpecializationBuilder::new(db); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 1a89063878a794..176a557673b6dd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -9,10 +9,10 @@ use super::{ use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::signatures::{Parameter, Parameters}; +use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature, - TypeMapping, TypeVarInstance, + CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping, + TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -1229,13 +1229,15 @@ impl<'db> ClassLiteral<'db> { // the `__set__` method can be called. We build a union of all possible options // to account for possible overloads. let mut value_types = UnionBuilder::new(db); - for signature in &dunder_set.signatures(db) { - for overload in signature { - if let Some(value_param) = overload.parameters().get_positional(2) { + for binding in &dunder_set.bindings(db) { + for overload in binding { + if let Some(value_param) = + overload.signature.parameters().get_positional(2) + { value_types = value_types.add( value_param.annotated_type().unwrap_or_else(Type::unknown), ); - } else if overload.parameters().is_gradual() { + } else if overload.signature.parameters().is_gradual() { value_types = value_types.add(Type::unknown()); } } @@ -1267,7 +1269,7 @@ impl<'db> ClassLiteral<'db> { } let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); - Some(Type::Callable(CallableType::function_like(db, signature))) + Some(CallableType::function_like(db, signature)) }; match (field_policy, name) { @@ -1317,7 +1319,7 @@ impl<'db> ClassLiteral<'db> { Some(KnownClass::Bool.to_instance(db)), ); - Some(Type::Callable(CallableType::function_like(db, signature))) + Some(CallableType::function_like(db, signature)) } (CodeGeneratorKind::NamedTuple, name) if name != "__init__" => { KnownClass::NamedTupleFallback diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index d0f3e9a61efa0a..1a12f2291fb0f6 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -8,7 +8,7 @@ use ruff_python_literal::escape::AsciiEscape; use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::signatures::{Parameter, Parameters, Signature}; +use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType, @@ -413,13 +413,13 @@ impl<'db> CallableType<'db> { } pub(crate) struct DisplayCallableType<'db> { - signatures: &'db [Signature<'db>], + signatures: &'db CallableSignature<'db>, db: &'db dyn Db, } impl Display for DisplayCallableType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.signatures { + match self.signatures.overloads.as_slice() { [signature] => signature.display(self.db).fmt(f), signatures => { // TODO: How to display overloads? diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 325d0e00db8ee1..b60b54a4e5d658 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -64,7 +64,9 @@ use crate::symbol::{ global_symbol, module_type_implicit_global_declaration, module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, }; -use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; +use crate::types::call::{ + Argument, Binding, Bindings, CallArgumentTypes, CallArguments, CallError, +}; use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, @@ -81,16 +83,17 @@ use crate::types::diagnostic::{ }; use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; +use crate::types::signatures::{CallableSignature, Signature}; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ - BareTypeAliasType, CallDunderError, CallableSignature, CallableType, ClassLiteral, ClassType, - DataclassParams, DynamicType, FunctionDecorators, FunctionType, GenericAlias, - IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, - MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, - Parameters, Signature, Signatures, StringLiteralType, SubclassOfType, Symbol, - SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, - TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, - TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, + BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, + DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, + IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, + MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, + StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, + TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, + TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, + todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -4847,10 +4850,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: Useful inference of a lambda's return type will require a different approach, // which does the inference of the body expression based on arguments at each call site, // rather than eagerly computing a return type without knowing the argument types. - Type::Callable(CallableType::single( - self.db(), - Signature::new(parameters, Some(Type::unknown())), - )) + CallableType::single(self.db(), Signature::new(parameters, Some(Type::unknown()))) } /// Returns the type of the first parameter if the given scope is function-like (i.e. function or lambda). @@ -4964,8 +4964,9 @@ impl<'db> TypeInferenceBuilder<'db> { } } - let signatures = callable_type.signatures(self.db()); - let bindings = Bindings::match_parameters(signatures, &call_arguments); + let bindings = callable_type + .bindings(self.db()) + .match_parameters(&call_arguments); let call_argument_types = self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms); @@ -7282,11 +7283,9 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => CallArgumentTypes::positional([self.infer_type_expression(slice_node)]), }; - let signatures = Signatures::single(CallableSignature::single( - value_ty, - generic_context.signature(self.db()), - )); - let bindings = match Bindings::match_parameters(signatures, &call_argument_types) + let binding = Binding::single(value_ty, generic_context.signature(self.db())); + let bindings = match Bindings::from(binding) + .match_parameters(&call_argument_types) .check_types(self.db(), &call_argument_types) { Ok(bindings) => bindings, @@ -8628,8 +8627,6 @@ impl<'db> TypeInferenceBuilder<'db> { CallableType::unknown(db) }; - let callable_type = Type::Callable(callable_type); - // `Signature` / `Parameters` are not a `Type` variant, so we're storing // the outer callable type on these expressions instead. self.store_expression_type(arguments_slice, callable_type); @@ -8701,20 +8698,20 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => { let argument_type = self.infer_expression(arguments_slice); - let signatures = argument_type.signatures(db); + let bindings = argument_type.bindings(db); - // SAFETY: This is enforced by the constructor methods on `Signatures` even in + // SAFETY: This is enforced by the constructor methods on `Bindings` even in // the case of a non-callable union. - let callable_signature = signatures - .iter() + let callable_binding = bindings + .into_iter() .next() - .expect("`Signatures` should have at least one `CallableSignature`"); + .expect("`Bindings` should have at least one `CallableBinding`"); - let mut signature_iter = callable_signature.iter().map(|signature| { + let mut signature_iter = callable_binding.into_iter().map(|binding| { if argument_type.is_bound_method() { - signature.bind_self() + binding.signature.bind_self() } else { - signature.clone() + binding.signature.clone() } }); @@ -8734,11 +8731,10 @@ impl<'db> TypeInferenceBuilder<'db> { return Type::unknown(); }; - Type::Callable(CallableType::from_overloads( - db, + let signature = CallableSignature::from_overloads( std::iter::once(signature).chain(signature_iter), - false, - )) + ); + Type::Callable(CallableType::new(db, signature, false)) } }, diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 362d603e6b85de..0850b38745615f 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -188,13 +188,13 @@ impl Ty { create_bound_method(db, function, builtins_class) } - Ty::Callable { params, returns } => Type::Callable(CallableType::single( + Ty::Callable { params, returns } => CallableType::single( db, Signature::new( params.into_parameters(db), returns.map(|ty| ty.into_type(db)), ), - )), + ), } } } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 2b68e7c731987f..24448b661db1e7 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -22,186 +22,204 @@ use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; -/// The signature of a possible union of callables. +/// The signature of a single callable. If the callable is overloaded, there is a separate +/// [`Signature`] for each overload. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct Signatures<'db> { - /// The type that is (hopefully) callable. - pub(crate) callable_type: Type<'db>, - /// The type we'll use for error messages referring to details of the called signature. For calls to functions this - /// will be the same as `callable_type`; for other callable instances it may be a `__call__` method. - pub(crate) signature_type: Type<'db>, - /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union - /// type. - elements: SmallVec<[CallableSignature<'db>; 1]>, +pub struct CallableSignature<'db> { + /// The signatures of each overload of this callable. Will be empty if the type is not + /// callable. + pub(crate) overloads: SmallVec<[Signature<'db>; 1]>, } -impl<'db> Signatures<'db> { - pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { - Self { - callable_type: signature_type, - signature_type, - elements: smallvec![CallableSignature::not_callable(signature_type)], - } - } - - pub(crate) fn single(signature: CallableSignature<'db>) -> Self { +impl<'db> CallableSignature<'db> { + pub(crate) fn single(signature: Signature<'db>) -> Self { Self { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - elements: smallvec![signature], + overloads: smallvec![signature], } } - /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is - /// empty. - pub(crate) fn from_union(signature_type: Type<'db>, elements: I) -> Self + /// Creates a new `CallableSignature` from an iterator of [`Signature`]s. Returns a + /// non-callable signature if the iterator is empty. + pub(crate) fn from_overloads(overloads: I) -> Self where - I: IntoIterator>, + I: IntoIterator>, { - let elements: SmallVec<_> = elements - .into_iter() - .flat_map(|s| s.elements.into_iter()) - .collect(); - assert!(!elements.is_empty()); Self { - callable_type: signature_type, - signature_type, - elements, + overloads: overloads.into_iter().collect(), } } - pub(crate) fn iter(&self) -> std::slice::Iter<'_, CallableSignature<'db>> { - self.elements.iter() + pub(crate) fn iter(&self) -> std::slice::Iter<'_, Signature<'db>> { + self.overloads.iter() } - pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { - if self.callable_type == before { - self.callable_type = after; - } - for signature in &mut self.elements { - signature.replace_callable_type(before, after); - } + pub(crate) fn as_slice(&self) -> &[Signature<'db>] { + self.overloads.as_slice() } - pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) { - for signature in &mut self.elements { - signature.dunder_call_is_possibly_unbound = true; - } + pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + Self::from_overloads( + self.overloads + .iter() + .map(|signature| signature.normalized(db)), + ) } -} -impl<'a, 'db> IntoIterator for &'a Signatures<'db> { - type Item = &'a CallableSignature<'db>; - type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() + pub(crate) fn apply_type_mapping<'a>( + &self, + db: &'db dyn Db, + type_mapping: &TypeMapping<'a, 'db>, + ) -> Self { + Self::from_overloads( + self.overloads + .iter() + .map(|signature| signature.apply_type_mapping(db, type_mapping)), + ) } -} - -/// The signature of a single callable. If the callable is overloaded, there is a separate -/// [`Signature`] for each overload. -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct CallableSignature<'db> { - /// The type that is (hopefully) callable. - pub(crate) callable_type: Type<'db>, - - /// The type we'll use for error messages referring to details of the called signature. For - /// calls to functions this will be the same as `callable_type`; for other callable instances - /// it may be a `__call__` method. - pub(crate) signature_type: Type<'db>, - - /// If this is a callable object (i.e. called via a `__call__` method), the boundness of - /// that call method. - pub(crate) dunder_call_is_possibly_unbound: bool, - - /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. - pub(crate) bound_type: Option>, - /// The signatures of each overload of this callable. Will be empty if the type is not - /// callable. - /// - /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a - /// non-overloaded callable. - pub(crate) overloads: SmallVec<[Signature<'db>; 1]>, -} - -impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { - Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, - overloads: smallvec![], + pub(crate) fn find_legacy_typevars( + &self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + for signature in &self.overloads { + signature.find_legacy_typevars(db, typevars); } } - pub(crate) fn single(signature_type: Type<'db>, signature: Signature<'db>) -> Self { + pub(crate) fn bind_self(&self) -> Self { Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, - overloads: smallvec![signature], + overloads: self.overloads.iter().map(Signature::bind_self).collect(), } } - /// Creates a new `CallableSignature` from an iterator of [`Signature`]s. Returns a - /// non-callable signature if the iterator is empty. - pub(crate) fn from_overloads(signature_type: Type<'db>, overloads: I) -> Self - where - I: IntoIterator>, - { - Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, - overloads: overloads.into_iter().collect(), - } + /// Check whether this callable type is fully static. + /// + /// See [`Type::is_fully_static`] for more details. + pub(crate) fn is_fully_static(&self, db: &'db dyn Db) -> bool { + self.overloads + .iter() + .all(|signature| signature.is_fully_static(db)) } - /// Return a signature for a dynamic callable - pub(crate) fn dynamic(signature_type: Type<'db>) -> Self { - let signature = Signature { - generic_context: None, - inherited_generic_context: None, - parameters: Parameters::gradual_form(), - return_ty: Some(signature_type), - }; - Self::single(signature_type, signature) + /// Check whether this callable type is a subtype of another callable type. + /// + /// See [`Type::is_subtype_of`] for more details. + pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { + Self::is_assignable_to_impl( + &self.overloads, + &other.overloads, + &|self_signature, other_signature| self_signature.is_subtype_of(db, other_signature), + ) } - /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo - #[allow(unused_variables)] // 'reason' only unused in debug builds - pub(crate) fn todo(reason: &'static str) -> Self { - let signature_type = todo_type!(reason); - let signature = Signature { - generic_context: None, - inherited_generic_context: None, - parameters: Parameters::todo(), - return_ty: Some(signature_type), - }; - Self::single(signature_type, signature) + /// Check whether this callable type is assignable to another callable type. + /// + /// See [`Type::is_assignable_to`] for more details. + pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool { + Self::is_assignable_to_impl( + &self.overloads, + &other.overloads, + &|self_signature, other_signature| self_signature.is_assignable_to(db, other_signature), + ) } - pub(crate) fn with_bound_type(mut self, bound_type: Type<'db>) -> Self { - self.bound_type = Some(bound_type); - self + /// Implementation for the various relation checks between two, possible overloaded, callable + /// types. + /// + /// The `check_signature` closure is used to check the relation between two [`Signature`]s. + fn is_assignable_to_impl( + self_signatures: &[Signature<'db>], + other_signatures: &[Signature<'db>], + check_signature: &F, + ) -> bool + where + F: Fn(&Signature<'db>, &Signature<'db>) -> bool, + { + match (self_signatures, other_signatures) { + ([self_signature], [other_signature]) => { + // Base case: both callable types contain a single signature. + check_signature(self_signature, other_signature) + } + + // `self` is possibly overloaded while `other` is definitely not overloaded. + (_, [_]) => self_signatures.iter().any(|self_signature| { + Self::is_assignable_to_impl( + std::slice::from_ref(self_signature), + other_signatures, + check_signature, + ) + }), + + // `self` is definitely not overloaded while `other` is possibly overloaded. + ([_], _) => other_signatures.iter().all(|other_signature| { + Self::is_assignable_to_impl( + self_signatures, + std::slice::from_ref(other_signature), + check_signature, + ) + }), + + // `self` is definitely overloaded while `other` is possibly overloaded. + (_, _) => other_signatures.iter().all(|other_signature| { + Self::is_assignable_to_impl( + self_signatures, + std::slice::from_ref(other_signature), + check_signature, + ) + }), + } } - pub(crate) fn iter(&self) -> std::slice::Iter<'_, Signature<'db>> { - self.overloads.iter() + /// Check whether this callable type is equivalent to another callable type. + /// + /// See [`Type::is_equivalent_to`] for more details. + pub(crate) fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool { + match (self.overloads.as_slice(), other.overloads.as_slice()) { + ([self_signature], [other_signature]) => { + // Common case: both callable types contain a single signature, use the custom + // equivalence check instead of delegating it to the subtype check. + self_signature.is_equivalent_to(db, other_signature) + } + (self_signatures, other_signatures) => { + if !self_signatures + .iter() + .chain(other_signatures.iter()) + .all(|signature| signature.is_fully_static(db)) + { + return false; + } + if self == other { + return true; + } + self.is_subtype_of(db, other) && other.is_subtype_of(db, self) + } + } } - pub(crate) fn as_slice(&self) -> &[Signature<'db>] { - self.overloads.as_slice() + /// Check whether this callable type is gradual equivalent to another callable type. + /// + /// See [`Type::is_gradual_equivalent_to`] for more details. + pub(crate) fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool { + match (self.overloads.as_slice(), other.overloads.as_slice()) { + ([self_signature], [other_signature]) => { + self_signature.is_gradual_equivalent_to(db, other_signature) + } + _ => { + // TODO: overloads + false + } + } } - fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { - if self.callable_type == before { - self.callable_type = after; + pub(crate) fn replace_self_reference(&self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { + Self { + overloads: self + .overloads + .iter() + .cloned() + .map(|signature| signature.replace_self_reference(db, class)) + .collect(), } } } @@ -263,6 +281,28 @@ impl<'db> Signature<'db> { } } + /// Return a signature for a dynamic callable + pub(crate) fn dynamic(signature_type: Type<'db>) -> Self { + Signature { + generic_context: None, + inherited_generic_context: None, + parameters: Parameters::gradual_form(), + return_ty: Some(signature_type), + } + } + + /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo + #[allow(unused_variables)] // 'reason' only unused in debug builds + pub(crate) fn todo(reason: &'static str) -> Self { + let signature_type = todo_type!(reason); + Signature { + generic_context: None, + inherited_generic_context: None, + parameters: Parameters::todo(), + return_ty: Some(signature_type), + } + } + /// Return a typed signature from a function definition. pub(super) fn from_function( db: &'db dyn Db, @@ -295,6 +335,11 @@ impl<'db> Signature<'db> { } } + /// Returns the signature which accepts any parameters and returns an `Unknown` type. + pub(crate) fn unknown() -> Self { + Self::new(Parameters::unknown(), Some(Type::unknown())) + } + /// Return the "bottom" signature, subtype of all other fully-static signatures. pub(crate) fn bottom(db: &'db dyn Db) -> Self { Self::new(Parameters::object(db), Some(Type::Never)) @@ -1750,7 +1795,7 @@ mod tests { assert_eq!( func.signature(&db), &FunctionSignature { - overloads: CallableSignature::single(Type::FunctionLiteral(func), expected_sig), + overloads: CallableSignature::single(expected_sig), implementation: None }, ); From e23d4ea027ecbc05213d049ac6dfcf726f4ab167 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Wed, 28 May 2025 17:24:52 -0300 Subject: [PATCH 266/487] [`flake8-bugbear`] Ignore `__debug__` attribute in `B010` (#18357) ## Summary Fixes #18353 ## Test Plan Snapshot tests --- .../resources/test/fixtures/flake8_bugbear/B009_B010.py | 3 +++ .../src/rules/flake8_bugbear/rules/setattr_with_constant.rs | 5 +++++ ...ter__rules__flake8_bugbear__tests__B009_B009_B010.py.snap | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B009_B010.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B009_B010.py index c47538b55e1c2e..ce6e5c291ecce6 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B009_B010.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B009_B010.py @@ -67,3 +67,6 @@ import builtins builtins.getattr(foo, "bar") + +# Regression test for: https://github.com/astral-sh/ruff/issues/18353 +setattr(foo, "__debug__", 0) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index cfc25573414197..a83710800d8585 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -74,6 +74,11 @@ pub(crate) fn setattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr, if !is_identifier(name.to_str()) { return; } + // Ignore if the attribute name is `__debug__`. Assigning to the `__debug__` property is a + // `SyntaxError`. + if name.to_str() == "__debug__" { + return; + } if is_mangled_private(name.to_str()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B009_B009_B010.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B009_B009_B010.py.snap index 5748ea4eb76124..8bddcba72085fa 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B009_B009_B010.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B009_B009_B010.py.snap @@ -364,6 +364,8 @@ B009_B010.py:69:1: B009 [*] Do not call `getattr` with a constant attribute valu 68 | import builtins 69 | builtins.getattr(foo, "bar") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B009 +70 | +71 | # Regression test for: https://github.com/astral-sh/ruff/issues/18353 | = help: Replace `getattr` with attribute access @@ -373,3 +375,6 @@ B009_B010.py:69:1: B009 [*] Do not call `getattr` with a constant attribute valu 68 68 | import builtins 69 |-builtins.getattr(foo, "bar") 69 |+foo.bar +70 70 | +71 71 | # Regression test for: https://github.com/astral-sh/ruff/issues/18353 +72 72 | setattr(foo, "__debug__", 0) From 16621fa19df6986c80a8d2d0c7e6d97740380d2c Mon Sep 17 00:00:00 2001 From: Hans Date: Thu, 29 May 2025 04:27:13 +0800 Subject: [PATCH 267/487] [`flake8-bugbear `] Add fix safety section (`B006`) (#17652) ## Summary This PR add the `fix safety` section for rule `B006` in `mutable_argument_default.rs` for #15584 When applying this rule for fixes, certain changes may alter the original logical behavior. For example: before: ```python def cache(x, storage=[]): storage.append(x) return storage print(cache(1)) # [1] print(cache(2)) # [1, 2] ``` after: ```python def cache(x, storage=[]): storage.append(x) return storage print(cache(1)) # [1] print(cache(2)) # [2] ``` --- .../rules/flake8_bugbear/rules/mutable_argument_default.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index b5a7effc28c206..c5e1b38969a388 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -68,6 +68,12 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// ## Options /// - `lint.flake8-bugbear.extend-immutable-calls` /// +/// ## Fix safety +/// +/// This fix is marked as unsafe because it replaces the mutable default with `None` +/// and initializes it in the function body, which may not be what the user intended, +/// as described above. +/// /// ## References /// - [Python documentation: Default Argument Values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values) #[derive(ViolationMetadata)] From c60b4d7f309918ac4e98b805fcac5a26e6925899 Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Wed, 28 May 2025 21:43:07 +0100 Subject: [PATCH 268/487] [ty] Add subtyping between Callable types and class literals with `__init__` (#17638) ## Summary Allow classes with `__init__` to be subtypes of `Callable` Fixes https://github.com/astral-sh/ty/issues/358 ## Test Plan Update is_subtype_of.md --------- Co-authored-by: Carl Meyer --- .../type_properties/is_assignable_to.md | 38 ++++ .../mdtest/type_properties/is_subtype_of.md | 133 +++++++++++++- crates/ty_python_semantic/src/types.rs | 22 ++- crates/ty_python_semantic/src/types/class.rs | 172 ++++++++++++++---- .../src/types/known_instance.rs | 10 +- 5 files changed, 329 insertions(+), 46 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index afc28ac58710c5..111e5ef9dc1385 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -608,11 +608,49 @@ c: Callable[[Any], str] = A().g ```py from typing import Any, Callable +c: Callable[[object], type] = type c: Callable[[str], Any] = str c: Callable[[str], Any] = int # error: [invalid-assignment] c: Callable[[str], Any] = object + +class A: + def __init__(self, x: int) -> None: ... + +a: Callable[[int], A] = A + +class C: + def __new__(cls, *args, **kwargs) -> "C": + return super().__new__(cls) + + def __init__(self, x: int) -> None: ... + +c: Callable[[int], C] = C +``` + +### Generic class literal types + +```toml +[environment] +python-version = "3.12" +``` + +```py +from typing import Callable + +class B[T]: + def __init__(self, x: T) -> None: ... + +b: Callable[[int], B[int]] = B[int] + +class C[T]: + def __new__(cls, *args, **kwargs) -> "C[T]": + return super().__new__(cls) + + def __init__(self, x: T) -> None: ... + +c: Callable[[int], C[int]] = C[int] ``` ### Overloads diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index b14e12f30688a1..ffa1e0fad04d72 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -1219,7 +1219,7 @@ static_assert(is_subtype_of(TypeOf[C], Callable[[], str])) #### Classes with `__new__` ```py -from typing import Callable +from typing import Callable, overload from ty_extensions import TypeOf, static_assert, is_subtype_of class A: @@ -1244,6 +1244,20 @@ static_assert(is_subtype_of(TypeOf[E], Callable[[], C])) static_assert(is_subtype_of(TypeOf[E], Callable[[], B])) static_assert(not is_subtype_of(TypeOf[D], Callable[[], C])) static_assert(is_subtype_of(TypeOf[D], Callable[[], B])) + +class F: + @overload + def __new__(cls) -> int: ... + @overload + def __new__(cls, x: int) -> "F": ... + def __new__(cls, x: int | None = None) -> "int | F": + return 1 if x is None else object.__new__(cls) + + def __init__(self, y: str) -> None: ... + +static_assert(is_subtype_of(TypeOf[F], Callable[[int], F])) +static_assert(is_subtype_of(TypeOf[F], Callable[[], int])) +static_assert(not is_subtype_of(TypeOf[F], Callable[[str], F])) ``` #### Classes with `__call__` and `__new__` @@ -1266,6 +1280,123 @@ static_assert(is_subtype_of(TypeOf[F], Callable[[], int])) static_assert(not is_subtype_of(TypeOf[F], Callable[[], str])) ``` +#### Classes with `__init__` + +```py +from typing import Callable, overload +from ty_extensions import TypeOf, static_assert, is_subtype_of + +class A: + def __init__(self, a: int) -> None: ... + +static_assert(is_subtype_of(TypeOf[A], Callable[[int], A])) +static_assert(not is_subtype_of(TypeOf[A], Callable[[], A])) + +class B: + @overload + def __init__(self, a: int) -> None: ... + @overload + def __init__(self) -> None: ... + def __init__(self, a: int | None = None) -> None: ... + +static_assert(is_subtype_of(TypeOf[B], Callable[[int], B])) +static_assert(is_subtype_of(TypeOf[B], Callable[[], B])) + +class C: ... + +# TODO: This assertion should be true once we understand `Self` +# error: [static-assert-error] "Static assertion error: argument evaluates to `False`" +static_assert(is_subtype_of(TypeOf[C], Callable[[], C])) + +class D[T]: + def __init__(self, x: T) -> None: ... + +static_assert(is_subtype_of(TypeOf[D[int]], Callable[[int], D[int]])) +static_assert(not is_subtype_of(TypeOf[D[int]], Callable[[str], D[int]])) +``` + +#### Classes with `__init__` and `__new__` + +```py +from typing import Callable, overload, Self +from ty_extensions import TypeOf, static_assert, is_subtype_of + +class A: + def __new__(cls, a: int) -> Self: + return super().__new__(cls) + + def __init__(self, a: int) -> None: ... + +static_assert(is_subtype_of(TypeOf[A], Callable[[int], A])) +static_assert(not is_subtype_of(TypeOf[A], Callable[[], A])) + +class B: + def __new__(cls, a: int) -> int: + return super().__new__(cls) + + def __init__(self, a: str) -> None: ... + +static_assert(is_subtype_of(TypeOf[B], Callable[[int], int])) +static_assert(not is_subtype_of(TypeOf[B], Callable[[str], B])) + +class C: + def __new__(cls, *args, **kwargs) -> "C": + return super().__new__(cls) + + def __init__(self, x: int) -> None: ... + +# Not subtype because __new__ signature is not fully static +static_assert(not is_subtype_of(TypeOf[C], Callable[[int], C])) +static_assert(not is_subtype_of(TypeOf[C], Callable[[], C])) + +class D: ... + +class E: + @overload + def __new__(cls) -> int: ... + @overload + def __new__(cls, x: int) -> D: ... + def __new__(cls, x: int | None = None) -> int | D: + return D() + + def __init__(self, y: str) -> None: ... + +static_assert(is_subtype_of(TypeOf[E], Callable[[int], D])) +static_assert(is_subtype_of(TypeOf[E], Callable[[], int])) + +class F[T]: + def __new__(cls, x: T) -> "F[T]": + return super().__new__(cls) + + def __init__(self, x: T) -> None: ... + +static_assert(is_subtype_of(TypeOf[F[int]], Callable[[int], F[int]])) +static_assert(not is_subtype_of(TypeOf[F[int]], Callable[[str], F[int]])) +``` + +#### Classes with `__call__`, `__new__` and `__init__` + +If `__call__`, `__new__` and `__init__` are all present, `__call__` takes precedence. + +```py +from typing import Callable +from ty_extensions import TypeOf, static_assert, is_subtype_of + +class MetaWithIntReturn(type): + def __call__(cls) -> int: + return super().__call__() + +class F(metaclass=MetaWithIntReturn): + def __new__(cls) -> str: + return super().__new__(cls) + + def __init__(self, x: int) -> None: ... + +static_assert(is_subtype_of(TypeOf[F], Callable[[], int])) +static_assert(not is_subtype_of(TypeOf[F], Callable[[], str])) +static_assert(not is_subtype_of(TypeOf[F], Callable[[int], F])) +``` + ### Bound methods ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 4176cb02738042..49bc6875579f69 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1351,12 +1351,15 @@ impl<'db> Type<'db> { } (Type::ClassLiteral(class_literal), Type::Callable(_)) => { - if let Some(callable) = class_literal.into_callable(db) { - return callable.is_subtype_of(db, target); - } - false + ClassType::NonGeneric(class_literal) + .into_callable(db) + .is_subtype_of(db, target) } + (Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias) + .into_callable(db) + .is_subtype_of(db, target), + // `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`. // `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object // is an instance of its metaclass `abc.ABCMeta`. @@ -1656,12 +1659,15 @@ impl<'db> Type<'db> { } (Type::ClassLiteral(class_literal), Type::Callable(_)) => { - if let Some(callable) = class_literal.into_callable(db) { - return callable.is_assignable_to(db, target); - } - false + ClassType::NonGeneric(class_literal) + .into_callable(db) + .is_assignable_to(db, target) } + (Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias) + .into_callable(db) + .is_assignable_to(db, target), + (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { self_function_literal .into_callable_type(db) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 176a557673b6dd..48e67055227b8f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -9,7 +9,7 @@ use super::{ use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::signatures::{Parameter, Parameters, Signature}; +use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping, TypeVarInstance, @@ -493,6 +493,142 @@ impl<'db> ClassType<'db> { .own_instance_member(db, name) .map_type(|ty| ty.apply_optional_specialization(db, specialization)) } + + /// Return a callable type (or union of callable types) that represents the callable + /// constructor signature of this class. + pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> { + let self_ty = Type::from(self); + let metaclass_dunder_call_function_symbol = self_ty + .member_lookup_with_policy( + db, + "__call__".into(), + MemberLookupPolicy::NO_INSTANCE_FALLBACK + | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, + ) + .symbol; + + if let Symbol::Type(Type::BoundMethod(metaclass_dunder_call_function), _) = + metaclass_dunder_call_function_symbol + { + // TODO: this intentionally diverges from step 1 in + // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable + // by always respecting the signature of the metaclass `__call__`, rather than + // using a heuristic which makes unwarranted assumptions to sometimes ignore it. + return metaclass_dunder_call_function.into_callable_type(db); + } + + let dunder_new_function_symbol = self_ty + .member_lookup_with_policy( + db, + "__new__".into(), + MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, + ) + .symbol; + + let dunder_new_function = + if let Symbol::Type(Type::FunctionLiteral(dunder_new_function), _) = + dunder_new_function_symbol + { + // Step 3: If the return type of the `__new__` evaluates to a type that is not a subclass of this class, + // then we should ignore the `__init__` and just return the `__new__` method. + let returns_non_subclass = + dunder_new_function + .signature(db) + .overloads + .iter() + .any(|signature| { + signature.return_ty.is_some_and(|return_ty| { + !return_ty.is_assignable_to( + db, + self_ty + .to_instance(db) + .expect("ClassType should be instantiable"), + ) + }) + }); + + let dunder_new_bound_method = + dunder_new_function.into_bound_method_type(db, self_ty); + + if returns_non_subclass { + return dunder_new_bound_method; + } + Some(dunder_new_bound_method) + } else { + None + }; + + let dunder_init_function_symbol = self_ty + .member_lookup_with_policy( + db, + "__init__".into(), + MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK + | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, + ) + .symbol; + + let correct_return_type = self_ty.to_instance(db).unwrap_or_else(Type::unknown); + + // If the class defines an `__init__` method, then we synthesize a callable type with the + // same parameters as the `__init__` method after it is bound, and with the return type of + // the concrete type of `Self`. + let synthesized_dunder_init_callable = + if let Symbol::Type(Type::FunctionLiteral(dunder_init_function), _) = + dunder_init_function_symbol + { + let synthesized_signature = |signature: Signature<'db>| { + Signature::new(signature.parameters().clone(), Some(correct_return_type)) + .bind_self() + }; + + let synthesized_dunder_init_signature = CallableSignature::from_overloads( + dunder_init_function + .signature(db) + .overloads + .iter() + .cloned() + .map(synthesized_signature), + ); + Some(Type::Callable(CallableType::new( + db, + synthesized_dunder_init_signature, + true, + ))) + } else { + None + }; + + match (dunder_new_function, synthesized_dunder_init_callable) { + (Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => { + UnionType::from_elements( + db, + vec![dunder_new_function, synthesized_dunder_init_callable], + ) + } + (Some(constructor), None) | (None, Some(constructor)) => constructor, + (None, None) => { + // If no `__new__` or `__init__` method is found, then we fall back to looking for + // an `object.__new__` method. + let new_function_symbol = self_ty + .member_lookup_with_policy( + db, + "__new__".into(), + MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, + ) + .symbol; + + if let Symbol::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol { + new_function.into_bound_method_type(db, self_ty) + } else { + // Fallback if no `object.__new__` is found. + CallableType::single( + db, + Signature::new(Parameters::empty(), Some(correct_return_type)), + ) + } + } + } + } } impl<'db> From> for ClassType<'db> { @@ -991,40 +1127,6 @@ impl<'db> ClassLiteral<'db> { )) } - pub(super) fn into_callable(self, db: &'db dyn Db) -> Option> { - let self_ty = Type::from(self); - let metaclass_call_function_symbol = self_ty - .member_lookup_with_policy( - db, - "__call__".into(), - MemberLookupPolicy::NO_INSTANCE_FALLBACK - | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, - ) - .symbol; - - if let Symbol::Type(Type::BoundMethod(metaclass_call_function), _) = - metaclass_call_function_symbol - { - // TODO: this intentionally diverges from step 1 in - // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable - // by always respecting the signature of the metaclass `__call__`, rather than - // using a heuristic which makes unwarranted assumptions to sometimes ignore it. - return Some(metaclass_call_function.into_callable_type(db)); - } - - let dunder_new_method = self_ty - .find_name_in_mro(db, "__new__") - .expect("find_name_in_mro always succeeds for class literals") - .symbol - .try_call_dunder_get(db, self_ty); - - if let Symbol::Type(Type::FunctionLiteral(dunder_new_method), _) = dunder_new_method { - return Some(dunder_new_method.into_bound_method_type(db, self.into())); - } - // TODO handle `__init__` also - None - } - /// Returns the class member of this class named `name`. /// /// The member resolves to a member on the class itself or any of its proper superclasses. diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs index e21f218b84641c..095d1cf752b9d7 100644 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ b/crates/ty_python_semantic/src/types/known_instance.rs @@ -88,7 +88,8 @@ pub enum KnownInstanceType<'db> { /// The symbol `typing.Callable` /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) Callable, - /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self`) + /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or + /// `_typeshed.Self`) TypingSelf, // Various special forms, special aliases and type qualifiers that we don't yet understand @@ -307,7 +308,6 @@ impl<'db> KnownInstanceType<'db> { | Self::Literal | Self::LiteralString | Self::Never - | Self::TypingSelf | Self::Final | Self::Concatenate | Self::Unpack @@ -322,6 +322,12 @@ impl<'db> KnownInstanceType<'db> { | Self::TypeVar(_) => { matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) } + Self::TypingSelf => { + matches!( + module, + KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed + ) + } Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy From 27743efa1bbcb8952faf1b85c2f439694c24ce1f Mon Sep 17 00:00:00 2001 From: vjurczenia Date: Wed, 28 May 2025 23:46:30 +0300 Subject: [PATCH 269/487] [`pylint`] Implement `missing-maxsplit-arg` (`PLC0207`) (#17454) ## Summary Implements `use-maxsplit-arg` (`PLC0207`) https://pylint.readthedocs.io/en/latest/user_guide/messages/convention/use-maxsplit-arg.html > Emitted when accessing only the first or last element of str.split(). The first and last element can be accessed by using str.split(sep, maxsplit=1)[0] or str.rsplit(sep, maxsplit=1)[-1] instead. This is part of https://github.com/astral-sh/ruff/issues/970 ## Test Plan `cargo test` Additionally compared Ruff output to Pylint: ``` pylint --disable=all --enable=use-maxsplit-arg crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py --no-cache --select PLC0207 ``` --------- Co-authored-by: Brent Westbrook --- .../fixtures/pylint/missing_maxsplit_arg.py | 184 ++++++++++ .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pylint/mod.rs | 1 + .../pylint/rules/missing_maxsplit_arg.rs | 134 ++++++++ .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + ...ests__PLC0207_missing_maxsplit_arg.py.snap | 324 ++++++++++++++++++ ruff.schema.json | 1 + 8 files changed, 650 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/missing_maxsplit_arg.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC0207_missing_maxsplit_arg.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py b/crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py new file mode 100644 index 00000000000000..f24e23fdbd7f59 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/missing_maxsplit_arg.py @@ -0,0 +1,184 @@ +SEQ = "1,2,3" + +class Foo(str): + class_str = "1,2,3" + + def split(self, sep=None, maxsplit=-1) -> list[str]: + return super().split(sep, maxsplit) + +class Bar(): + split = "1,2,3" + +# Errors +## Test split called directly on string literal +"1,2,3".split(",")[0] # [missing-maxsplit-arg] +"1,2,3".split(",")[-1] # [missing-maxsplit-arg] +"1,2,3".rsplit(",")[0] # [missing-maxsplit-arg] +"1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg] + +## Test split called on string variable +SEQ.split(",")[0] # [missing-maxsplit-arg] +SEQ.split(",")[-1] # [missing-maxsplit-arg] +SEQ.rsplit(",")[0] # [missing-maxsplit-arg] +SEQ.rsplit(",")[-1] # [missing-maxsplit-arg] + +## Test split called on class attribute +Foo.class_str.split(",")[0] # [missing-maxsplit-arg] +Foo.class_str.split(",")[-1] # [missing-maxsplit-arg] +Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg] +Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg] + +## Test split called on sliced string +"1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg] +"1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg] +SEQ[:3].split(",")[0] # [missing-maxsplit-arg] +Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] +"1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] +SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg] +Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg] + +## Test sep given as named argument +"1,2,3".split(sep=",")[0] # [missing-maxsplit-arg] +"1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg] +"1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg] +"1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg] + +## Special cases +"1,2,3".split("\n")[0] # [missing-maxsplit-arg] +"1,2,3".split("split")[-1] # [missing-maxsplit-arg] +"1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg] + +## Test class attribute named split +Bar.split.split(",")[0] # [missing-maxsplit-arg] +Bar.split.split(",")[-1] # [missing-maxsplit-arg] +Bar.split.rsplit(",")[0] # [missing-maxsplit-arg] +Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg] + +## Test unpacked dict literal kwargs +"1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg] + + +# OK +## Test not accessing the first or last element +### Test split called directly on string literal +"1,2,3".split(",")[1] +"1,2,3".split(",")[-2] +"1,2,3".rsplit(",")[1] +"1,2,3".rsplit(",")[-2] + +### Test split called on string variable +SEQ.split(",")[1] +SEQ.split(",")[-2] +SEQ.rsplit(",")[1] +SEQ.rsplit(",")[-2] + +### Test split called on class attribute +Foo.class_str.split(",")[1] +Foo.class_str.split(",")[-2] +Foo.class_str.rsplit(",")[1] +Foo.class_str.rsplit(",")[-2] + +### Test split called on sliced string +"1,2,3"[::-1].split(",")[1] +SEQ[:3].split(",")[1] +Foo.class_str[1:3].split(",")[-2] +"1,2,3"[::-1].rsplit(",")[1] +SEQ[:3].rsplit(",")[1] +Foo.class_str[1:3].rsplit(",")[-2] + +### Test sep given as named argument +"1,2,3".split(sep=",")[1] +"1,2,3".split(sep=",")[-2] +"1,2,3".rsplit(sep=",")[1] +"1,2,3".rsplit(sep=",")[-2] + +## Test varying maxsplit argument +### str.split() tests +"1,2,3".split(sep=",", maxsplit=1)[-1] +"1,2,3".split(sep=",", maxsplit=1)[0] +"1,2,3".split(sep=",", maxsplit=2)[-1] +"1,2,3".split(sep=",", maxsplit=2)[0] +"1,2,3".split(sep=",", maxsplit=2)[1] + +### str.rsplit() tests +"1,2,3".rsplit(sep=",", maxsplit=1)[-1] +"1,2,3".rsplit(sep=",", maxsplit=1)[0] +"1,2,3".rsplit(sep=",", maxsplit=2)[-1] +"1,2,3".rsplit(sep=",", maxsplit=2)[0] +"1,2,3".rsplit(sep=",", maxsplit=2)[1] + +## Test user-defined split +Foo("1,2,3").split(",")[0] +Foo("1,2,3").split(",")[-1] +Foo("1,2,3").rsplit(",")[0] +Foo("1,2,3").rsplit(",")[-1] + +## Test split called on sliced list +["1", "2", "3"][::-1].split(",")[0] + +## Test class attribute named split +Bar.split[0] +Bar.split[-1] +Bar.split[0] +Bar.split[-1] + +## Test unpacked dict literal kwargs +"1,2,3".split(",", **{"maxsplit": 1})[0] +"1,2,3".split(**{"sep": ",", "maxsplit": 1})[0] + + +# TODO + +## Test variable split result index +## TODO: These require the ability to resolve a variable name to a value +# Errors +result_index = 0 +"1,2,3".split(",")[result_index] # TODO: [missing-maxsplit-arg] +result_index = -1 +"1,2,3".split(",")[result_index] # TODO: [missing-maxsplit-arg] +# OK +result_index = 1 +"1,2,3".split(",")[result_index] +result_index = -2 +"1,2,3".split(",")[result_index] + + +## Test split result index modified in loop +## TODO: These require the ability to recognize being in a loop where: +## - the result of split called on a string is indexed by a variable +## - the variable index above is modified +# OK +result_index = 0 +for j in range(3): + print(SEQ.split(",")[result_index]) + result_index = result_index + 1 + + +## Test accessor +## TODO: These require the ability to get the return type of a method +## (possibly via `typing::is_string`) +class Baz(): + def __init__(self): + self.my_str = "1,2,3" + + def get_string(self) -> str: + return self.my_str + +# Errors +Baz().get_string().split(",")[0] # TODO: [missing-maxsplit-arg] +Baz().get_string().split(",")[-1] # TODO: [missing-maxsplit-arg] +# OK +Baz().get_string().split(",")[1] +Baz().get_string().split(",")[-2] + + +## Test unpacked dict instance kwargs +## TODO: These require the ability to resolve a dict variable name to a value +# Errors +kwargs_without_maxsplit = {"seq": ","} +"1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg] +# OK +kwargs_with_maxsplit = {"maxsplit": 1} +"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive +kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1} +"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index e6de35139836be..e29b4db602863f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -176,6 +176,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if checker.enabled(Rule::Airflow3Removal) { airflow::rules::airflow_3_removal_expr(checker, expr); } + if checker.enabled(Rule::MissingMaxsplitArg) { + pylint::rules::missing_maxsplit_arg(checker, value, slice, expr); + } pandas_vet::rules::subscript(checker, value, expr); } Expr::Tuple(ast::ExprTuple { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 4051d69b2e8de0..69ac2201ce5405 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -198,6 +198,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0132") => (RuleGroup::Stable, rules::pylint::rules::TypeParamNameMismatch), (Pylint, "C0205") => (RuleGroup::Stable, rules::pylint::rules::SingleStringSlots), (Pylint, "C0206") => (RuleGroup::Stable, rules::pylint::rules::DictIndexMissingItems), + (Pylint, "C0207") => (RuleGroup::Preview, rules::pylint::rules::MissingMaxsplitArg), (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), (Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 67dbdd06839381..12ee8ed2526f1a 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -231,6 +231,7 @@ mod tests { Path::new("bad_staticmethod_argument.py") )] #[test_case(Rule::LenTest, Path::new("len_as_condition.py"))] + #[test_case(Rule::MissingMaxsplitArg, Path::new("missing_maxsplit_arg.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pylint/rules/missing_maxsplit_arg.rs b/crates/ruff_linter/src/rules/pylint/rules/missing_maxsplit_arg.rs new file mode 100644 index 00000000000000..daa9e57e00d969 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/missing_maxsplit_arg.rs @@ -0,0 +1,134 @@ +use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::{ + DictItem, Expr, ExprAttribute, ExprCall, ExprDict, ExprNumberLiteral, ExprStringLiteral, + ExprSubscript, ExprUnaryOp, Keyword, Number, UnaryOp, +}; +use ruff_python_semantic::{SemanticModel, analyze::typing}; +use ruff_text_size::Ranged; + +use crate::Violation; +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for access to the first or last element of `str.split()` without +/// `maxsplit=1` +/// +/// ## Why is this bad? +/// Calling `str.split()` without `maxsplit` set splits on every delimiter in the +/// string. When accessing only the first or last element of the result, it +/// would be more efficient to only split once. +/// +/// ## Example +/// ```python +/// url = "www.example.com" +/// prefix = url.split(".")[0] +/// ``` +/// +/// Use instead: +/// ```python +/// url = "www.example.com" +/// prefix = url.split(".", maxsplit=1)[0] +/// ``` + +#[derive(ViolationMetadata)] +pub(crate) struct MissingMaxsplitArg; + +impl Violation for MissingMaxsplitArg { + #[derive_message_formats] + fn message(&self) -> String { + "Accessing only the first or last element of `str.split()` without setting `maxsplit=1`" + .to_string() + } +} + +fn is_string(expr: &Expr, semantic: &SemanticModel) -> bool { + if let Expr::Name(name) = expr { + semantic + .only_binding(name) + .is_some_and(|binding_id| typing::is_string(semantic.binding(binding_id), semantic)) + } else if let Some(binding_id) = semantic.lookup_attribute(expr) { + typing::is_string(semantic.binding(binding_id), semantic) + } else { + expr.is_string_literal_expr() + } +} + +/// PLC0207 +pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr, expr: &Expr) { + // Check the sliced expression is a function + let Expr::Call(ExprCall { + func, arguments, .. + }) = value + else { + return; + }; + + // Check the slice index is either 0 or -1 (first or last value) + let index = match slice { + Expr::NumberLiteral(ExprNumberLiteral { + value: Number::Int(number_value), + .. + }) => number_value.as_i64(), + Expr::UnaryOp(ExprUnaryOp { + op: UnaryOp::USub, + operand, + .. + }) => match operand.as_ref() { + Expr::NumberLiteral(ExprNumberLiteral { + value: Number::Int(number_value), + .. + }) => number_value.as_i64().map(|number| -number), + _ => return, + }, + _ => return, + }; + + if !matches!(index, Some(0 | -1)) { + return; + } + + let Expr::Attribute(ExprAttribute { attr, value, .. }) = func.as_ref() else { + return; + }; + + // Check the function is "split" or "rsplit" + let attr = attr.as_str(); + if !matches!(attr, "split" | "rsplit") { + return; + } + + let mut target_instance = value; + // a subscripted value could technically be subscripted further ad infinitum, so we + // recurse into the subscript expressions until we find the value being subscripted + while let Expr::Subscript(ExprSubscript { value, .. }) = target_instance.as_ref() { + target_instance = value; + } + + // Check the function is called on a string + if !is_string(target_instance, checker.semantic()) { + return; + } + + // Check the function does not have maxsplit set + if arguments.find_argument_value("maxsplit", 1).is_some() { + return; + } + + // Check maxsplit kwarg not set via unpacked dict literal + for keyword in &*arguments.keywords { + let Keyword { value, .. } = keyword; + + if let Expr::Dict(ExprDict { items, .. }) = value { + for item in items { + let DictItem { key, .. } = item; + if let Some(Expr::StringLiteral(ExprStringLiteral { value, .. })) = key { + if value.to_str() == "maxsplit" { + return; + } + } + } + } + } + + checker.report_diagnostic(MissingMaxsplitArg, expr.range()); +} diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index 3ddc469656fc4d..891691f21b7ec7 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -46,6 +46,7 @@ pub(crate) use logging::*; pub(crate) use magic_value_comparison::*; pub(crate) use manual_import_from::*; pub(crate) use misplaced_bare_raise::*; +pub(crate) use missing_maxsplit_arg::*; pub(crate) use modified_iterating_set::*; pub(crate) use named_expr_without_context::*; pub(crate) use nan_comparison::*; @@ -155,6 +156,7 @@ mod logging; mod magic_value_comparison; mod manual_import_from; mod misplaced_bare_raise; +mod missing_maxsplit_arg; mod modified_iterating_set; mod named_expr_without_context; mod nan_comparison; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC0207_missing_maxsplit_arg.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC0207_missing_maxsplit_arg.py.snap new file mode 100644 index 00000000000000..97c8800eebf657 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC0207_missing_maxsplit_arg.py.snap @@ -0,0 +1,324 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +12 | # Errors +13 | ## Test split called directly on string literal +14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^ PLC0207 +15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg] +16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +13 | ## Test split called directly on string literal +14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg] +15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg] +17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg] +15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg] +16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg] +16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg] +17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +18 | +19 | ## Test split called on string variable + | + +missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +19 | ## Test split called on string variable +20 | SEQ.split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^ PLC0207 +21 | SEQ.split(",")[-1] # [missing-maxsplit-arg] +22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +19 | ## Test split called on string variable +20 | SEQ.split(",")[0] # [missing-maxsplit-arg] +21 | SEQ.split(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^ PLC0207 +22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg] +23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +20 | SEQ.split(",")[0] # [missing-maxsplit-arg] +21 | SEQ.split(",")[-1] # [missing-maxsplit-arg] +22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^ PLC0207 +23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +21 | SEQ.split(",")[-1] # [missing-maxsplit-arg] +22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg] +23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^ PLC0207 +24 | +25 | ## Test split called on class attribute + | + +missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +25 | ## Test split called on class attribute +26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg] +28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +25 | ## Test split called on class attribute +26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg] +27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg] +29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg] +27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg] +28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg] +28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg] +29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +30 | +31 | ## Test split called on sliced string + | + +missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +31 | ## Test split called on sliced string +32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg] +34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +31 | ## Test split called on sliced string +32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg] +33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg] +35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg] +33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg] +34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^ PLC0207 +35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] +36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg] +34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg] +35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] +37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg] +35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] +36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg] +38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg] +36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] +37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg] +37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg] +38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +39 | +40 | ## Test sep given as named argument + | + +missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +40 | ## Test sep given as named argument +41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg] +43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +40 | ## Test sep given as named argument +41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg] +42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg] +44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg] +42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg] +43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg] +43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg] +44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +45 | +46 | ## Special cases + | + +missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +46 | ## Special cases +47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg] +49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +46 | ## Special cases +47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg] +48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg] +48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg] +49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +50 | +51 | ## Test class attribute named split + | + +missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +51 | ## Test class attribute named split +52 | Bar.split.split(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg] +54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +51 | ## Test class attribute named split +52 | Bar.split.split(",")[0] # [missing-maxsplit-arg] +53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg] +55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +52 | Bar.split.split(",")[0] # [missing-maxsplit-arg] +53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg] +54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg] + | + +missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg] +54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg] +55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +56 | +57 | ## Test unpacked dict literal kwargs + | + +missing_maxsplit_arg.py:58:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +57 | ## Test unpacked dict literal kwargs +58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 + | + +missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +177 | # Errors +178 | kwargs_without_maxsplit = {"seq": ","} +179 | "1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +180 | # OK +181 | kwargs_with_maxsplit = {"maxsplit": 1} + | + +missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +180 | # OK +181 | kwargs_with_maxsplit = {"maxsplit": 1} +182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 +183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1} +184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive + | + +missing_maxsplit_arg.py:184:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1` + | +182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive +183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1} +184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207 + | diff --git a/ruff.schema.json b/ruff.schema.json index 37fc8102c7eb68..7bf97547310a7c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3588,6 +3588,7 @@ "PLC020", "PLC0205", "PLC0206", + "PLC0207", "PLC0208", "PLC04", "PLC041", From 04dc48e17c2518f97436d76284a509499087d81c Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Wed, 28 May 2025 18:01:03 -0300 Subject: [PATCH 270/487] [`refurb`] Fix `FURB129` autofix generating invalid syntax (#18235) ## Summary Fixes #18231 ## Test Plan Snapshot tests --- .../resources/test/fixtures/refurb/FURB129.py | 17 ++- .../rules/refurb/rules/readlines_in_for.rs | 19 ++- ...es__refurb__tests__FURB129_FURB129.py.snap | 120 ++++++++++++++---- ...b__tests__preview__FURB129_FURB129.py.snap | 120 ++++++++++++++---- 4 files changed, 224 insertions(+), 52 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py index c5c0307098e7b3..3a163d8f21e193 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py @@ -43,7 +43,6 @@ def func(): import builtins - with builtins.open("FURB129.py") as f: for line in f.readlines(): pass @@ -51,7 +50,6 @@ def func(): from builtins import open as o - with o("FURB129.py") as f: for line in f.readlines(): pass @@ -89,3 +87,18 @@ def readlines(self) -> list[str]: pass for _not_line in f.readline(): pass + +# https://github.com/astral-sh/ruff/issues/18231 +with open("furb129.py") as f: + for line in (f).readlines(): + pass + +with open("furb129.py") as f: + [line for line in (f).readlines()] + + +with open("furb129.py") as f: + for line in (((f))).readlines(): + pass + for line in(f).readlines(): + pass diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index 37df344985f982..c78ae1fdb013a0 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -1,4 +1,5 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{Comprehension, Expr, StmtFor}; use ruff_python_semantic::analyze::typing; use ruff_python_semantic::analyze::typing::is_io_base_expr; @@ -84,15 +85,21 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { return; } } + let edit = if let Some(parenthesized_range) = parenthesized_range( + expr_attr.value.as_ref().into(), + expr_attr.into(), + checker.comment_ranges(), + checker.source(), + ) { + Edit::range_deletion(expr_call.range().add_start(parenthesized_range.len())) + } else { + Edit::range_deletion(expr_call.range().add_start(expr_attr.value.range().len())) + }; let mut diagnostic = checker.report_diagnostic(ReadlinesInFor, expr_call.range()); diagnostic.set_fix(if is_readlines_in_for_fix_safe_enabled(checker.settings) { - Fix::safe_edit(Edit::range_deletion( - expr_call.range().add_start(expr_attr.value.range().len()), - )) + Fix::safe_edit(edit) } else { - Fix::unsafe_edit(Edit::range_deletion( - expr_call.range().add_start(expr_attr.value.range().len()), - )) + Fix::unsafe_edit(edit) }); } diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap index c11b60c2727666..9a1c006794f4fd 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap @@ -204,40 +204,116 @@ FURB129.py:38:22: FURB129 [*] Instead of calling `readlines()`, iterate over fil 40 40 | for _line in bar.readlines(): 41 41 | pass -FURB129.py:48:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:47:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -47 | with builtins.open("FURB129.py") as f: -48 | for line in f.readlines(): +46 | with builtins.open("FURB129.py") as f: +47 | for line in f.readlines(): | ^^^^^^^^^^^^^ FURB129 -49 | pass +48 | pass | = help: Remove `readlines()` ℹ Unsafe fix +44 44 | import builtins 45 45 | -46 46 | -47 47 | with builtins.open("FURB129.py") as f: -48 |- for line in f.readlines(): - 48 |+ for line in f: -49 49 | pass +46 46 | with builtins.open("FURB129.py") as f: +47 |- for line in f.readlines(): + 47 |+ for line in f: +48 48 | pass +49 49 | 50 50 | -51 51 | -FURB129.py:56:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:54:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -55 | with o("FURB129.py") as f: -56 | for line in f.readlines(): +53 | with o("FURB129.py") as f: +54 | for line in f.readlines(): | ^^^^^^^^^^^^^ FURB129 -57 | pass +55 | pass | = help: Remove `readlines()` ℹ Unsafe fix -53 53 | -54 54 | -55 55 | with o("FURB129.py") as f: -56 |- for line in f.readlines(): - 56 |+ for line in f: -57 57 | pass -58 58 | -59 59 | +51 51 | from builtins import open as o +52 52 | +53 53 | with o("FURB129.py") as f: +54 |- for line in f.readlines(): + 54 |+ for line in f: +55 55 | pass +56 56 | +57 57 | + +FURB129.py:93:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +91 | # https://github.com/astral-sh/ruff/issues/18231 +92 | with open("furb129.py") as f: +93 | for line in (f).readlines(): + | ^^^^^^^^^^^^^^^ FURB129 +94 | pass + | + = help: Remove `readlines()` + +ℹ Unsafe fix +90 90 | +91 91 | # https://github.com/astral-sh/ruff/issues/18231 +92 92 | with open("furb129.py") as f: +93 |- for line in (f).readlines(): + 93 |+ for line in (f): +94 94 | pass +95 95 | +96 96 | with open("furb129.py") as f: + +FURB129.py:97:23: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +96 | with open("furb129.py") as f: +97 | [line for line in (f).readlines()] + | ^^^^^^^^^^^^^^^ FURB129 + | + = help: Remove `readlines()` + +ℹ Unsafe fix +94 94 | pass +95 95 | +96 96 | with open("furb129.py") as f: +97 |- [line for line in (f).readlines()] + 97 |+ [line for line in (f)] +98 98 | +99 99 | +100 100 | with open("furb129.py") as f: + +FURB129.py:101:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +100 | with open("furb129.py") as f: +101 | for line in (((f))).readlines(): + | ^^^^^^^^^^^^^^^^^^^ FURB129 +102 | pass +103 | for line in(f).readlines(): + | + = help: Remove `readlines()` + +ℹ Unsafe fix +98 98 | +99 99 | +100 100 | with open("furb129.py") as f: +101 |- for line in (((f))).readlines(): + 101 |+ for line in (((f))): +102 102 | pass +103 103 | for line in(f).readlines(): +104 104 | pass + +FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +101 | for line in (((f))).readlines(): +102 | pass +103 | for line in(f).readlines(): + | ^^^^^^^^^^^^^^^ FURB129 +104 | pass + | + = help: Remove `readlines()` + +ℹ Unsafe fix +100 100 | with open("furb129.py") as f: +101 101 | for line in (((f))).readlines(): +102 102 | pass +103 |- for line in(f).readlines(): + 103 |+ for line in(f): +104 104 | pass diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap index f0fdde19a88354..ff4ac5132715bc 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap @@ -204,40 +204,116 @@ FURB129.py:38:22: FURB129 [*] Instead of calling `readlines()`, iterate over fil 40 40 | for _line in bar.readlines(): 41 41 | pass -FURB129.py:48:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:47:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -47 | with builtins.open("FURB129.py") as f: -48 | for line in f.readlines(): +46 | with builtins.open("FURB129.py") as f: +47 | for line in f.readlines(): | ^^^^^^^^^^^^^ FURB129 -49 | pass +48 | pass | = help: Remove `readlines()` ℹ Safe fix +44 44 | import builtins 45 45 | -46 46 | -47 47 | with builtins.open("FURB129.py") as f: -48 |- for line in f.readlines(): - 48 |+ for line in f: -49 49 | pass +46 46 | with builtins.open("FURB129.py") as f: +47 |- for line in f.readlines(): + 47 |+ for line in f: +48 48 | pass +49 49 | 50 50 | -51 51 | -FURB129.py:56:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:54:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -55 | with o("FURB129.py") as f: -56 | for line in f.readlines(): +53 | with o("FURB129.py") as f: +54 | for line in f.readlines(): | ^^^^^^^^^^^^^ FURB129 -57 | pass +55 | pass | = help: Remove `readlines()` ℹ Safe fix -53 53 | -54 54 | -55 55 | with o("FURB129.py") as f: -56 |- for line in f.readlines(): - 56 |+ for line in f: -57 57 | pass -58 58 | -59 59 | +51 51 | from builtins import open as o +52 52 | +53 53 | with o("FURB129.py") as f: +54 |- for line in f.readlines(): + 54 |+ for line in f: +55 55 | pass +56 56 | +57 57 | + +FURB129.py:93:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +91 | # https://github.com/astral-sh/ruff/issues/18231 +92 | with open("furb129.py") as f: +93 | for line in (f).readlines(): + | ^^^^^^^^^^^^^^^ FURB129 +94 | pass + | + = help: Remove `readlines()` + +ℹ Safe fix +90 90 | +91 91 | # https://github.com/astral-sh/ruff/issues/18231 +92 92 | with open("furb129.py") as f: +93 |- for line in (f).readlines(): + 93 |+ for line in (f): +94 94 | pass +95 95 | +96 96 | with open("furb129.py") as f: + +FURB129.py:97:23: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +96 | with open("furb129.py") as f: +97 | [line for line in (f).readlines()] + | ^^^^^^^^^^^^^^^ FURB129 + | + = help: Remove `readlines()` + +ℹ Safe fix +94 94 | pass +95 95 | +96 96 | with open("furb129.py") as f: +97 |- [line for line in (f).readlines()] + 97 |+ [line for line in (f)] +98 98 | +99 99 | +100 100 | with open("furb129.py") as f: + +FURB129.py:101:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +100 | with open("furb129.py") as f: +101 | for line in (((f))).readlines(): + | ^^^^^^^^^^^^^^^^^^^ FURB129 +102 | pass +103 | for line in(f).readlines(): + | + = help: Remove `readlines()` + +ℹ Safe fix +98 98 | +99 99 | +100 100 | with open("furb129.py") as f: +101 |- for line in (((f))).readlines(): + 101 |+ for line in (((f))): +102 102 | pass +103 103 | for line in(f).readlines(): +104 104 | pass + +FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +101 | for line in (((f))).readlines(): +102 | pass +103 | for line in(f).readlines(): + | ^^^^^^^^^^^^^^^ FURB129 +104 | pass + | + = help: Remove `readlines()` + +ℹ Safe fix +100 100 | with open("furb129.py") as f: +101 101 | for line in (((f))).readlines(): +102 102 | pass +103 |- for line in(f).readlines(): + 103 |+ for line in(f): +104 104 | pass From aee3af0f7a018e9fcf921b746ad8ef76d3b84b83 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 29 May 2025 09:17:12 -0400 Subject: [PATCH 271/487] Bump 0.11.12 (#18369) --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ Cargo.lock | 6 +++--- README.md | 6 +++--- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_wasm/Cargo.toml | 2 +- docs/integrations.md | 8 ++++---- docs/tutorial.md | 2 +- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 10 files changed, 44 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca71fb47d6fc5..ecea56102313ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 0.11.12 + +### Preview features + +- \[`airflow`\] Revise fix titles (`AIR3`) ([#18215](https://github.com/astral-sh/ruff/pull/18215)) +- \[`pylint`\] Implement `missing-maxsplit-arg` (`PLC0207`) ([#17454](https://github.com/astral-sh/ruff/pull/17454)) +- \[`pyupgrade`\] New rule `UP050` (`useless-class-metaclass-type`) ([#18334](https://github.com/astral-sh/ruff/pull/18334)) +- \[`flake8-use-pathlib`\] Replace `os.symlink` with `Path.symlink_to` (`PTH211`) ([#18337](https://github.com/astral-sh/ruff/pull/18337)) + +### Bug fixes + +- \[`flake8-bugbear`\] Ignore `__debug__` attribute in `B010` ([#18357](https://github.com/astral-sh/ruff/pull/18357)) +- \[`flake8-async`\] Fix `anyio.sleep` argument name (`ASYNC115`, `ASYNC116`) ([#18262](https://github.com/astral-sh/ruff/pull/18262)) +- \[`refurb`\] Fix `FURB129` autofix generating invalid syntax ([#18235](https://github.com/astral-sh/ruff/pull/18235)) + +### Rule changes + +- \[`flake8-implicit-str-concat`\] Add autofix for `ISC003` ([#18256](https://github.com/astral-sh/ruff/pull/18256)) +- \[`pycodestyle`\] Improve the diagnostic message for `E712` ([#18328](https://github.com/astral-sh/ruff/pull/18328)) +- \[`flake8-2020`\] Fix diagnostic message for `!=` comparisons (`YTT201`) ([#18293](https://github.com/astral-sh/ruff/pull/18293)) +- \[`pyupgrade`\] Make fix unsafe if it deletes comments (`UP010`) ([#18291](https://github.com/astral-sh/ruff/pull/18291)) + +### Documentation + +- Simplify rules table to improve readability ([#18297](https://github.com/astral-sh/ruff/pull/18297)) +- Update editor integrations link in README ([#17977](https://github.com/astral-sh/ruff/pull/17977)) +- \[`flake8-bugbear`\] Add fix safety section (`B006`) ([#17652](https://github.com/astral-sh/ruff/pull/17652)) + ## 0.11.11 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index 2b1f0f82c5b31c..136af27f59b03f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.11.11" +version = "0.11.12" dependencies = [ "anyhow", "argfile", @@ -2722,7 +2722,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.11.11" +version = "0.11.12" dependencies = [ "aho-corasick", "anyhow", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "ruff_wasm" -version = "0.11.11" +version = "0.11.12" dependencies = [ "console_error_panic_hook", "console_log", diff --git a/README.md b/README.md index d54cb7bb9d97fc..451dec1f93e34d 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. -curl -LsSf https://astral.sh/ruff/0.11.11/install.sh | sh -powershell -c "irm https://astral.sh/ruff/0.11.11/install.ps1 | iex" +curl -LsSf https://astral.sh/ruff/0.11.12/install.sh | sh +powershell -c "irm https://astral.sh/ruff/0.11.12/install.ps1 | iex" ``` You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), @@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.11 + rev: v0.11.12 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index ac983938e8ac18..da31cad404ea83 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.11.11" +version = "0.11.12" publish = true authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 766adc8960ad06..64109ceef29103 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.11.11" +version = "0.11.12" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index 4325835cc688ae..d967cfd44c32f5 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_wasm" -version = "0.11.11" +version = "0.11.12" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/docs/integrations.md b/docs/integrations.md index f5b1098638de59..25c7e29c0b743f 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma stage: build interruptible: true image: - name: ghcr.io/astral-sh/ruff:0.11.11-alpine + name: ghcr.io/astral-sh/ruff:0.11.12-alpine before_script: - cd $CI_PROJECT_DIR - ruff --version @@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.11 + rev: v0.11.12 hooks: # Run the linter. - id: ruff @@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.11 + rev: v0.11.12 hooks: # Run the linter. - id: ruff @@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.11 + rev: v0.11.12 hooks: # Run the linter. - id: ruff diff --git a/docs/tutorial.md b/docs/tutorial.md index e3ab734582151e..8f14f1b2e73fbf 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.11 + rev: v0.11.12 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 0b983b27a04d20..5ce2b0ad169271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.11.11" +version = "0.11.12" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index 2b11e73624e98b..bf66533c9ffca3 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scripts" -version = "0.11.11" +version = "0.11.12" description = "" authors = ["Charles Marsh "] From 47a2ec002e945c159eaf88558c003c7948050117 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 29 May 2025 14:47:55 +0100 Subject: [PATCH 272/487] [ty] Split `Type::KnownInstance` into two type variants (#18350) --- crates/ty/docs/rules.md | 108 ++--- crates/ty_python_semantic/src/symbol.rs | 2 +- crates/ty_python_semantic/src/types.rs | 319 ++++++++++---- .../ty_python_semantic/src/types/call/bind.rs | 4 +- crates/ty_python_semantic/src/types/class.rs | 22 +- .../src/types/class_base.rs | 100 +++-- .../src/types/diagnostic.rs | 9 +- .../ty_python_semantic/src/types/display.rs | 1 + crates/ty_python_semantic/src/types/infer.rs | 297 +++++++------ .../src/types/known_instance.rs | 413 ------------------ crates/ty_python_semantic/src/types/mro.rs | 15 +- .../types/property_tests/type_generation.rs | 6 +- .../src/types/special_form.rs | 305 +++++++++++++ .../src/types/type_ordering.rs | 7 +- 14 files changed, 827 insertions(+), 781 deletions(-) delete mode 100644 crates/ty_python_semantic/src/types/known_instance.rs create mode 100644 crates/ty_python_semantic/src/types/special_form.rs diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 731d08c6452ca7..30fe9952314334 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) ## `invalid-type-variable-constraints` @@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896) ## `missing-argument` @@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925) ## `no-matching-overload` @@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944) ## `non-subscriptable` @@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967) ## `not-iterable` @@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985) ## `parameter-already-assigned` @@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) ## `raw-string-type-annotation` @@ -1231,7 +1231,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) ## `subclass-of-final-class` @@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127) ## `too-many-positional-arguments` @@ -1285,7 +1285,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) ## `type-assertion-failure` @@ -1312,7 +1312,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1356,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193) ## `unknown-argument` @@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250) ## `unresolved-attribute` @@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) ## `unresolved-import` @@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293) ## `unresolved-reference` @@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) ## `unsupported-bool-conversion` @@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005) ## `unsupported-operator` @@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331) ## `zero-stepsize-in-slice` @@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353) ## `invalid-ignore-comment` @@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057) ## `possibly-unbound-implicit-call` @@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110) ## `possibly-unbound-import` @@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079) ## `redundant-cast` @@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424) ## `undefined-reveal` @@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232) ## `unknown-rule` @@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488) ## `division-by-zero` @@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239) ## `possibly-unresolved-reference` @@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index 8e20a08954fe95..d2b2518e61cc05 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -1131,7 +1131,7 @@ fn widen_type_for_undeclared_public_symbol<'db>( // such. let is_known_instance = inferred .ignore_possibly_unbound() - .is_some_and(|ty| matches!(ty, Type::KnownInstance(_))); + .is_some_and(|ty| matches!(ty, Type::SpecialForm(_) | Type::KnownInstance(_))); if is_considered_non_modifiable || is_known_instance { inferred diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 49bc6875579f69..dfded95182bb42 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -54,8 +54,8 @@ pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic use crate::{Db, FxOrderSet, Module, Program}; pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass}; use instance::Protocol; -pub(crate) use instance::{NominalInstanceType, ProtocolInstanceType}; -pub(crate) use known_instance::KnownInstanceType; +pub use instance::{NominalInstanceType, ProtocolInstanceType}; +pub use special_form::SpecialFormType; mod builder; mod call; @@ -67,12 +67,12 @@ mod display; mod generics; mod infer; mod instance; -mod known_instance; mod mro; mod narrow; mod protocol_class; mod signatures; mod slots; +mod special_form; mod string_annotation; mod subclass_of; mod type_ordering; @@ -511,7 +511,12 @@ pub enum Type<'db> { /// The set of Python objects that conform to the interface described by a given protocol. /// Construct this variant using the `Type::instance` constructor function. ProtocolInstance(ProtocolInstanceType<'db>), - /// A single Python object that requires special treatment in the type system + /// A single Python object that requires special treatment in the type system, + /// and which exists at a location that can be known prior to any analysis by ty. + SpecialForm(SpecialFormType), + /// Singleton types that are heavily special-cased by ty, and which are usually + /// created as a result of some runtime operation (e.g. a type-alias statement, + /// a typevar definition, or `Generic[T]` in a class's bases list). KnownInstance(KnownInstanceType<'db>), /// An instance of `builtins.property` PropertyInstance(PropertyInstanceType<'db>), @@ -664,6 +669,7 @@ impl<'db> Type<'db> { | Self::ModuleLiteral(_) | Self::ClassLiteral(_) | Self::NominalInstance(_) + | Self::SpecialForm(_) | Self::KnownInstance(_) | Self::PropertyInstance(_) | Self::BoundMethod(_) @@ -691,6 +697,7 @@ impl<'db> Type<'db> { | Self::ModuleLiteral(_) | Self::FunctionLiteral(_) | Self::ClassLiteral(_) + | Self::SpecialForm(_) | Self::KnownInstance(_) | Self::StringLiteral(_) | Self::IntLiteral(_) @@ -936,19 +943,6 @@ impl<'db> Type<'db> { .expect("Expected a Type::IntLiteral variant") } - pub const fn into_known_instance(self) -> Option> { - match self { - Type::KnownInstance(known_instance) => Some(known_instance), - _ => None, - } - } - - #[track_caller] - pub fn expect_known_instance(self) -> KnownInstanceType<'db> { - self.into_known_instance() - .expect("Expected a Type::KnownInstance variant") - } - pub const fn into_tuple(self) -> Option> { match self { Type::Tuple(tuple_type) => Some(tuple_type), @@ -1042,6 +1036,7 @@ impl<'db> Type<'db> { | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) | Type::ClassLiteral(_) + | Type::SpecialForm(_) | Type::IntLiteral(_) => self, } } @@ -1385,9 +1380,11 @@ impl<'db> Type<'db> { metaclass_instance_type.is_subtype_of(db, target) }), - // For example: `Type::KnownInstance(KnownInstanceType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, - // because `Type::KnownInstance(KnownInstanceType::Type)` is a set with exactly one runtime value in it + // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, + // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it // (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime. + (Type::SpecialForm(left), right) => left.instance_fallback(db).is_subtype_of(db, right), + (Type::KnownInstance(left), right) => { left.instance_fallback(db).is_subtype_of(db, right) } @@ -1884,6 +1881,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::ClassLiteral(..) | Type::GenericAlias(..) + | Type::SpecialForm(..) | Type::KnownInstance(..)), right @ (Type::BooleanLiteral(..) | Type::IntLiteral(..) @@ -1896,6 +1894,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::ClassLiteral(..) | Type::GenericAlias(..) + | Type::SpecialForm(..) | Type::KnownInstance(..)), ) => left != right, @@ -2028,6 +2027,11 @@ impl<'db> Type<'db> { | Type::IntLiteral(..)), ) => !ty.satisfies_protocol(db, protocol), + (Type::ProtocolInstance(protocol), Type::SpecialForm(special_form)) + | (Type::SpecialForm(special_form), Type::ProtocolInstance(protocol)) => !special_form + .instance_fallback(db) + .satisfies_protocol(db, protocol), + (Type::ProtocolInstance(protocol), Type::KnownInstance(known_instance)) | (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => { !known_instance @@ -2063,15 +2067,24 @@ impl<'db> Type<'db> { .is_disjoint_from(db, other), }, + (Type::SpecialForm(special_form), Type::NominalInstance(instance)) + | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { + !special_form.is_instance_of(db, instance.class) + } + (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { !known_instance.is_instance_of(db, instance.class) } - (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple)) - | (Type::Tuple(tuple), known_instance_ty @ Type::KnownInstance(_)) => { - known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)) - } + ( + known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)), + Type::Tuple(tuple), + ) + | ( + Type::Tuple(tuple), + known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)), + ) => known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)), (Type::BooleanLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::BooleanLiteral(..)) => { @@ -2231,6 +2244,7 @@ impl<'db> Type<'db> { | Type::StringLiteral(_) | Type::LiteralString | Type::BytesLiteral(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy @@ -2345,15 +2359,16 @@ impl<'db> Type<'db> { | Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::ModuleLiteral(..) => true, - Type::KnownInstance(known_instance) => { - // Nearly all `KnownInstance` types are singletons, but if a symbol could validly + Type::SpecialForm(special_form) => { + // Nearly all `SpecialForm` types are singletons, but if a symbol could validly // originate from either `typing` or `typing_extensions` then this is not guaranteed. - // E.g. `typing.Protocol` is equivalent to `typing_extensions.Protocol`, so both are treated - // as inhabiting the type `KnownInstanceType::Protocol` in our model, but they are actually + // E.g. `typing.TypeGuard` is equivalent to `typing_extensions.TypeGuard`, so both are treated + // as inhabiting the type `SpecialFormType::TypeGuard` in our model, but they are actually // distinct symbols at different memory addresses at runtime. - !(known_instance.check_module(KnownModule::Typing) - && known_instance.check_module(KnownModule::TypingExtensions)) + !(special_form.check_module(KnownModule::Typing) + && special_form.check_module(KnownModule::TypingExtensions)) } + Type::KnownInstance(_) => false, Type::Callable(_) => { // A callable type is never a singleton because for any given signature, // there could be any number of distinct objects that are all callable with that @@ -2421,6 +2436,7 @@ impl<'db> Type<'db> { | Type::BooleanLiteral(..) | Type::StringLiteral(..) | Type::BytesLiteral(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) => true, Type::ProtocolInstance(..) => { @@ -2575,6 +2591,7 @@ impl<'db> Type<'db> { | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::AlwaysTruthy | Type::AlwaysFalsy @@ -2699,7 +2716,7 @@ impl<'db> Type<'db> { .to_instance(db) .instance_member(db, name), - Type::KnownInstance(_) => Symbol::Unbound.into(), + Type::SpecialForm(_) | Type::KnownInstance(_) => Symbol::Unbound.into(), Type::PropertyInstance(_) => KnownClass::Property .to_instance(db) @@ -3174,6 +3191,7 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::Tuple(..) | Type::TypeVar(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) | Type::PropertyInstance(..) | Type::FunctionLiteral(..) => { @@ -3455,6 +3473,7 @@ impl<'db> Type<'db> { | Type::PropertyInstance(_) | Type::BoundSuper(_) | Type::KnownInstance(_) + | Type::SpecialForm(_) | Type::AlwaysTruthy => Truthiness::AlwaysTrue, Type::AlwaysFalsy => Truthiness::AlwaysFalse, @@ -4276,7 +4295,7 @@ impl<'db> Type<'db> { .into(), }, - Type::KnownInstance(KnownInstanceType::TypedDict) => { + Type::SpecialForm(SpecialFormType::TypedDict) => { Binding::single( self, Signature::new( @@ -4369,10 +4388,11 @@ impl<'db> Type<'db> { CallableBinding::not_callable(self).into() } - // TODO: some `KnownInstance`s are callable (e.g. TypedDicts) - Type::KnownInstance(_) => CallableBinding::not_callable(self).into(), + // TODO: some `SpecialForm`s are callable (e.g. TypedDicts) + Type::SpecialForm(_) => CallableBinding::not_callable(self).into(), Type::PropertyInstance(_) + | Type::KnownInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy | Type::IntLiteral(_) @@ -4861,6 +4881,7 @@ impl<'db> Type<'db> { | Type::DataclassTransformer(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::ModuleLiteral(_) @@ -4942,33 +4963,43 @@ impl<'db> Type<'db> { Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)), - KnownInstanceType::Never | KnownInstanceType::NoReturn => Ok(Type::Never), - KnownInstanceType::LiteralString => Ok(Type::LiteralString), - KnownInstanceType::Unknown => Ok(Type::unknown()), - KnownInstanceType::AlwaysTruthy => Ok(Type::AlwaysTruthy), - KnownInstanceType::AlwaysFalsy => Ok(Type::AlwaysFalsy), + KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), + KnownInstanceType::SubscriptedProtocol(_) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], + fallback_type: Type::unknown(), + }), + KnownInstanceType::SubscriptedGeneric(_) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], + fallback_type: Type::unknown(), + }), + }, + + Type::SpecialForm(special_form) => match special_form { + SpecialFormType::Never | SpecialFormType::NoReturn => Ok(Type::Never), + SpecialFormType::LiteralString => Ok(Type::LiteralString), + SpecialFormType::Unknown => Ok(Type::unknown()), + SpecialFormType::AlwaysTruthy => Ok(Type::AlwaysTruthy), + SpecialFormType::AlwaysFalsy => Ok(Type::AlwaysFalsy), // We treat `typing.Type` exactly the same as `builtins.type`: - KnownInstanceType::Type => Ok(KnownClass::Type.to_instance(db)), - KnownInstanceType::Tuple => Ok(KnownClass::Tuple.to_instance(db)), + SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)), + SpecialFormType::Tuple => Ok(KnownClass::Tuple.to_instance(db)), // Legacy `typing` aliases - KnownInstanceType::List => Ok(KnownClass::List.to_instance(db)), - KnownInstanceType::Dict => Ok(KnownClass::Dict.to_instance(db)), - KnownInstanceType::Set => Ok(KnownClass::Set.to_instance(db)), - KnownInstanceType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)), - KnownInstanceType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)), - KnownInstanceType::Counter => Ok(KnownClass::Counter.to_instance(db)), - KnownInstanceType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)), - KnownInstanceType::Deque => Ok(KnownClass::Deque.to_instance(db)), - KnownInstanceType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)), - - KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), + SpecialFormType::List => Ok(KnownClass::List.to_instance(db)), + SpecialFormType::Dict => Ok(KnownClass::Dict.to_instance(db)), + SpecialFormType::Set => Ok(KnownClass::Set.to_instance(db)), + SpecialFormType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)), + SpecialFormType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)), + SpecialFormType::Counter => Ok(KnownClass::Counter.to_instance(db)), + SpecialFormType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)), + SpecialFormType::Deque => Ok(KnownClass::Deque.to_instance(db)), + SpecialFormType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)), // TODO: Use an opt-in rule for a bare `Callable` - KnownInstanceType::Callable => Ok(CallableType::unknown(db)), + SpecialFormType::Callable => Ok(CallableType::unknown(db)), - KnownInstanceType::TypingSelf => { + SpecialFormType::TypingSelf => { let index = semantic_index(db, scope_id.file(db)); let Some(class) = nearest_enclosing_class(db, index, scope_id) else { return Err(InvalidTypeExpressionError { @@ -4991,41 +5022,41 @@ impl<'db> Type<'db> { TypeVarKind::Legacy, ))) } - KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), - KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")), + SpecialFormType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), + SpecialFormType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")), - KnownInstanceType::Protocol(_) => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], + SpecialFormType::Literal + | SpecialFormType::Union + | SpecialFormType::Intersection => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![ + InvalidTypeExpression::RequiresArguments(*self) + ], fallback_type: Type::unknown(), }), - KnownInstanceType::Generic(_) => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], + + SpecialFormType::Protocol => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], fallback_type: Type::unknown(), }), - - KnownInstanceType::Literal - | KnownInstanceType::Union - | KnownInstanceType::Intersection => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec![ - InvalidTypeExpression::RequiresArguments(*self) - ], + SpecialFormType::Generic => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], fallback_type: Type::unknown(), }), - KnownInstanceType::Optional - | KnownInstanceType::Not - | KnownInstanceType::TypeOf - | KnownInstanceType::TypeIs - | KnownInstanceType::TypeGuard - | KnownInstanceType::Unpack - | KnownInstanceType::CallableTypeOf => Err(InvalidTypeExpressionError { + SpecialFormType::Optional + | SpecialFormType::Not + | SpecialFormType::TypeOf + | SpecialFormType::TypeIs + | SpecialFormType::TypeGuard + | SpecialFormType::Unpack + | SpecialFormType::CallableTypeOf => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ InvalidTypeExpression::RequiresOneArgument(*self) ], fallback_type: Type::unknown(), }), - KnownInstanceType::Annotated | KnownInstanceType::Concatenate => { + SpecialFormType::Annotated | SpecialFormType::Concatenate => { Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ InvalidTypeExpression::RequiresTwoArguments(*self) @@ -5034,20 +5065,20 @@ impl<'db> Type<'db> { }) } - KnownInstanceType::ClassVar | KnownInstanceType::Final => { + SpecialFormType::ClassVar | SpecialFormType::Final => { Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ - InvalidTypeExpression::TypeQualifier(*known_instance) + InvalidTypeExpression::TypeQualifier(*special_form) ], fallback_type: Type::unknown(), }) } - KnownInstanceType::ReadOnly - | KnownInstanceType::NotRequired - | KnownInstanceType::Required => Err(InvalidTypeExpressionError { + SpecialFormType::ReadOnly + | SpecialFormType::NotRequired + | SpecialFormType::Required => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ - InvalidTypeExpression::TypeQualifierRequiresOneArgument(*known_instance) + InvalidTypeExpression::TypeQualifierRequiresOneArgument(*special_form) ], fallback_type: Type::unknown(), }), @@ -5158,6 +5189,7 @@ impl<'db> Type<'db> { Type::Never => Type::Never, Type::NominalInstance(instance) => instance.to_meta_type(db), Type::KnownInstance(known_instance) => known_instance.to_meta_type(db), + Type::SpecialForm(special_form) => special_form.to_meta_type(db), Type::PropertyInstance(_) => KnownClass::Property.to_class_literal(db), Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)), Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db), @@ -5359,6 +5391,7 @@ impl<'db> Type<'db> { // some other generic context's specialization is applied to it. | Type::ClassLiteral(_) | Type::BoundSuper(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) => self, } } @@ -5458,6 +5491,7 @@ impl<'db> Type<'db> { | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::BoundSuper(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) => {} } } @@ -5472,6 +5506,7 @@ impl<'db> Type<'db> { match self { Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db), Type::StringLiteral(_) | Type::LiteralString => *self, + Type::SpecialForm(special_form) => Type::string_literal(db, special_form.repr()), Type::KnownInstance(known_instance) => Type::StringLiteral(StringLiteralType::new( db, known_instance.repr(db).to_string().into_boxed_str(), @@ -5493,6 +5528,7 @@ impl<'db> Type<'db> { Type::string_literal(db, &format!("'{}'", literal.value(db).escape_default())) } Type::LiteralString => Type::LiteralString, + Type::SpecialForm(special_form) => Type::string_literal(db, special_form.repr()), Type::KnownInstance(known_instance) => Type::StringLiteral(StringLiteralType::new( db, known_instance.repr(db).to_string().into_boxed_str(), @@ -5568,6 +5604,7 @@ impl<'db> Type<'db> { | Self::Never | Self::Callable(_) | Self::AlwaysTruthy + | Self::SpecialForm(_) | Self::AlwaysFalsy => None, } } @@ -5684,6 +5721,112 @@ impl<'db> TypeMapping<'_, 'db> { } } +/// Singleton types that are heavily special-cased by ty. Despite its name, +/// quite a different type to [`NominalInstanceType`]. +/// +/// In many ways, this enum behaves similarly to [`SpecialFormType`]. +/// Unlike instances of that variant, however, `Type::KnownInstance`s do not exist +/// at a location that can be known prior to any analysis by ty, and each variant +/// of `KnownInstanceType` can have multiple instances (as, unlike `SpecialFormType`, +/// `KnownInstanceType` variants can hold associated data). Instances of this type +/// are generally created by operations at runtime in some way, such as a type alias +/// statement, a typevar definition, or an instance of `Generic[T]` in a class's +/// bases list. +/// +/// # Ordering +/// +/// Ordering between variants is stable and should be the same between runs. +/// Ordering within variants is based on the wrapped data's salsa-assigned id and not on its values. +/// The id may change between runs, or when e.g. a `TypeVarInstance` was garbage-collected and recreated. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, Ord, PartialOrd)] +pub enum KnownInstanceType<'db> { + /// The type of `Protocol[T]`, `Protocol[U, S]`, etc -- usually only found in a class's bases list. + /// + /// Note that unsubscripted `Protocol` is represented by [`SpecialFormType::Protocol`], not this type. + SubscriptedProtocol(GenericContext<'db>), + + /// The type of `Generic[T]`, `Generic[U, S]`, etc -- usually only found in a class's bases list. + /// + /// Note that unsubscripted `Generic` is represented by [`SpecialFormType::Generic`], not this type. + SubscriptedGeneric(GenericContext<'db>), + + /// A single instance of `typing.TypeVar` + TypeVar(TypeVarInstance<'db>), + + /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) + TypeAliasType(TypeAliasType<'db>), +} + +impl<'db> KnownInstanceType<'db> { + fn normalized(self, db: &'db dyn Db) -> Self { + match self { + Self::SubscriptedProtocol(context) => Self::SubscriptedProtocol(context.normalized(db)), + Self::SubscriptedGeneric(context) => Self::SubscriptedGeneric(context.normalized(db)), + Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized(db)), + Self::TypeAliasType(type_alias) => Self::TypeAliasType(type_alias.normalized(db)), + } + } + + const fn class(self) -> KnownClass { + match self { + Self::SubscriptedProtocol(_) | Self::SubscriptedGeneric(_) => KnownClass::SpecialForm, + Self::TypeVar(_) => KnownClass::TypeVar, + Self::TypeAliasType(_) => KnownClass::TypeAliasType, + } + } + + fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { + self.class().to_class_literal(db) + } + + /// Return the instance type which this type is a subtype of. + /// + /// For example, an alias created using the `type` statement is an instance of + /// `typing.TypeAliasType`, so `KnownInstanceType::TypeAliasType(_).instance_fallback(db)` + /// returns `Type::NominalInstance(NominalInstanceType { class: })`. + fn instance_fallback(self, db: &dyn Db) -> Type { + self.class().to_instance(db) + } + + /// Return `true` if this symbol is an instance of `class`. + fn is_instance_of(self, db: &dyn Db, class: ClassType) -> bool { + self.class().is_subclass_of(db, class) + } + + /// Return the repr of the symbol at runtime + fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { + struct KnownInstanceRepr<'db> { + known_instance: KnownInstanceType<'db>, + db: &'db dyn Db, + } + + impl std::fmt::Display for KnownInstanceRepr<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.known_instance { + KnownInstanceType::SubscriptedProtocol(generic_context) => { + f.write_str("typing.Protocol")?; + generic_context.display(self.db).fmt(f) + } + KnownInstanceType::SubscriptedGeneric(generic_context) => { + f.write_str("typing.Generic")?; + generic_context.display(self.db).fmt(f) + } + KnownInstanceType::TypeAliasType(_) => f.write_str("typing.TypeAliasType"), + // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render + // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll + // have a `Type::TypeVar(_)`, which is rendered as the typevar's name. + KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), + } + } + } + + KnownInstanceRepr { + known_instance: self, + db, + } + } +} + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DynamicType { // An explicitly annotated `typing.Any` @@ -5841,10 +5984,10 @@ enum InvalidTypeExpression<'db> { Generic, /// Type qualifiers are always invalid in *type expressions*, /// but these ones are okay with 0 arguments in *annotation expressions* - TypeQualifier(KnownInstanceType<'db>), + TypeQualifier(SpecialFormType), /// Type qualifiers that are invalid in type expressions, /// and which would require exactly one argument even if they appeared in an annotation expression - TypeQualifierRequiresOneArgument(KnownInstanceType<'db>), + TypeQualifierRequiresOneArgument(SpecialFormType), /// Some types are always invalid in type expressions InvalidType(Type<'db>, ScopeId<'db>), } @@ -5882,13 +6025,13 @@ impl<'db> InvalidTypeExpression<'db> { } InvalidTypeExpression::TypeQualifier(qualifier) => write!( f, - "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)", - q = qualifier.repr(self.db) + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions)", ), InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) => write!( f, - "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)", - q = qualifier.repr(self.db) + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions, and only with exactly one argument)", ), InvalidTypeExpression::InvalidType(ty, _) => write!( f, @@ -8827,8 +8970,8 @@ impl<'db> SuperOwnerKind<'db> { Type::BytesLiteral(_) => { SuperOwnerKind::try_from_type(db, KnownClass::Bytes.to_instance(db)) } - Type::KnownInstance(known_instance) => { - SuperOwnerKind::try_from_type(db, known_instance.instance_fallback(db)) + Type::SpecialForm(special_form) => { + SuperOwnerKind::try_from_type(db, special_form.instance_fallback(db)) } _ => None, } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 07888335a18af1..fa52bcd960b374 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -22,7 +22,7 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, + SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -933,7 +933,7 @@ impl<'db> Bindings<'db> { _ => {} }, - Type::KnownInstance(KnownInstanceType::TypedDict) => { + Type::SpecialForm(SpecialFormType::TypedDict) => { overload.set_return_type(todo_type!("TypedDict")); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 48e67055227b8f..50b9e8b6820e09 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3,8 +3,8 @@ use std::sync::{LazyLock, Mutex}; use super::{ IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, - SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type, - infer_unpack_types, + SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, + infer_expression_type, infer_unpack_types, }; use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; @@ -729,9 +729,9 @@ impl<'db> ClassLiteral<'db> { pub(crate) fn legacy_generic_context(self, db: &'db dyn Db) -> Option> { self.explicit_bases(db).iter().find_map(|base| match base { Type::KnownInstance( - KnownInstanceType::Generic(generic_context) - | KnownInstanceType::Protocol(generic_context), - ) => *generic_context, + KnownInstanceType::SubscriptedGeneric(generic_context) + | KnownInstanceType::SubscriptedProtocol(generic_context), + ) => Some(*generic_context), _ => None, }) } @@ -879,11 +879,13 @@ impl<'db> ClassLiteral<'db> { // - OR be the last-but-one base (with the final base being `Generic[]` or `object`) // - OR be the last-but-two base (with the penultimate base being `Generic[]` // and the final base being `object`) - self.explicit_bases(db) - .iter() - .rev() - .take(3) - .any(|base| matches!(base, Type::KnownInstance(KnownInstanceType::Protocol(_)))) + self.explicit_bases(db).iter().rev().take(3).any(|base| { + matches!( + base, + Type::SpecialForm(SpecialFormType::Protocol) + | Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)) + ) + }) }) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index b52c525fa797f1..cf13ee5b384530 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,8 +1,8 @@ use crate::Db; use crate::types::generics::Specialization; use crate::types::{ - ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, - TypeMapping, todo_type, + ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType, + Type, TypeMapping, todo_type, }; /// Enumeration of the possible kinds of types we allow in class bases. @@ -147,74 +147,82 @@ impl<'db> ClassBase<'db> { | Type::ProtocolInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy => None, + Type::KnownInstance(known_instance) => match known_instance { - KnownInstanceType::TypeVar(_) - | KnownInstanceType::TypeAliasType(_) - | KnownInstanceType::Annotated - | KnownInstanceType::Literal - | KnownInstanceType::LiteralString - | KnownInstanceType::Union - | KnownInstanceType::NoReturn - | KnownInstanceType::Never - | KnownInstanceType::Final - | KnownInstanceType::NotRequired - | KnownInstanceType::TypeGuard - | KnownInstanceType::TypeIs - | KnownInstanceType::TypingSelf - | KnownInstanceType::Unpack - | KnownInstanceType::ClassVar - | KnownInstanceType::Concatenate - | KnownInstanceType::Required - | KnownInstanceType::TypeAlias - | KnownInstanceType::ReadOnly - | KnownInstanceType::Optional - | KnownInstanceType::Not - | KnownInstanceType::Intersection - | KnownInstanceType::TypeOf - | KnownInstanceType::CallableTypeOf - | KnownInstanceType::AlwaysTruthy - | KnownInstanceType::AlwaysFalsy => None, - KnownInstanceType::Unknown => Some(Self::unknown()), + KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic), + KnownInstanceType::SubscriptedProtocol(_) => Some(Self::Protocol), + KnownInstanceType::TypeAliasType(_) | KnownInstanceType::TypeVar(_) => None, + }, + + Type::SpecialForm(special_form) => match special_form { + SpecialFormType::Annotated + | SpecialFormType::Literal + | SpecialFormType::LiteralString + | SpecialFormType::Union + | SpecialFormType::NoReturn + | SpecialFormType::Never + | SpecialFormType::Final + | SpecialFormType::NotRequired + | SpecialFormType::TypeGuard + | SpecialFormType::TypeIs + | SpecialFormType::TypingSelf + | SpecialFormType::Unpack + | SpecialFormType::ClassVar + | SpecialFormType::Concatenate + | SpecialFormType::Required + | SpecialFormType::TypeAlias + | SpecialFormType::ReadOnly + | SpecialFormType::Optional + | SpecialFormType::Not + | SpecialFormType::Intersection + | SpecialFormType::TypeOf + | SpecialFormType::CallableTypeOf + | SpecialFormType::AlwaysTruthy + | SpecialFormType::AlwaysFalsy => None, + + SpecialFormType::Unknown => Some(Self::unknown()), + + SpecialFormType::Protocol => Some(Self::Protocol), + SpecialFormType::Generic => Some(Self::Generic), + // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO - KnownInstanceType::Dict => { + SpecialFormType::Dict => { Self::try_from_type(db, KnownClass::Dict.to_class_literal(db)) } - KnownInstanceType::List => { + SpecialFormType::List => { Self::try_from_type(db, KnownClass::List.to_class_literal(db)) } - KnownInstanceType::Type => { + SpecialFormType::Type => { Self::try_from_type(db, KnownClass::Type.to_class_literal(db)) } - KnownInstanceType::Tuple => { + SpecialFormType::Tuple => { Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db)) } - KnownInstanceType::Set => { + SpecialFormType::Set => { Self::try_from_type(db, KnownClass::Set.to_class_literal(db)) } - KnownInstanceType::FrozenSet => { + SpecialFormType::FrozenSet => { Self::try_from_type(db, KnownClass::FrozenSet.to_class_literal(db)) } - KnownInstanceType::ChainMap => { + SpecialFormType::ChainMap => { Self::try_from_type(db, KnownClass::ChainMap.to_class_literal(db)) } - KnownInstanceType::Counter => { + SpecialFormType::Counter => { Self::try_from_type(db, KnownClass::Counter.to_class_literal(db)) } - KnownInstanceType::DefaultDict => { + SpecialFormType::DefaultDict => { Self::try_from_type(db, KnownClass::DefaultDict.to_class_literal(db)) } - KnownInstanceType::Deque => { + SpecialFormType::Deque => { Self::try_from_type(db, KnownClass::Deque.to_class_literal(db)) } - KnownInstanceType::OrderedDict => { + SpecialFormType::OrderedDict => { Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db)) } - KnownInstanceType::TypedDict => Self::try_from_type(db, todo_type!("TypedDict")), - KnownInstanceType::Callable => { + SpecialFormType::TypedDict => Self::try_from_type(db, todo_type!("TypedDict")), + SpecialFormType::Callable => { Self::try_from_type(db, todo_type!("Support for Callable as a base class")) } - KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol), - KnownInstanceType::Generic(_) => Some(ClassBase::Generic), }, } } @@ -288,8 +296,8 @@ impl<'db> From> for Type<'db> { match value { ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Class(class) => class.into(), - ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)), - ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)), + ClassBase::Protocol => Type::SpecialForm(SpecialFormType::Protocol), + ClassBase::Generic => Type::SpecialForm(SpecialFormType::Generic), } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index a4dfbc7f7828d7..7943b53d76e2ba 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -5,7 +5,6 @@ use super::{ CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass, add_inferred_python_version_hint_to_diagnostic, }; -use crate::db::Db; use crate::declare_lint; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; @@ -15,7 +14,7 @@ use crate::types::string_annotation::{ IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; -use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; +use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -1823,7 +1822,6 @@ pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node: } pub(crate) fn report_invalid_arguments_to_annotated( - db: &dyn Db, context: &InferContext, subscript: &ast::ExprSubscript, ) { @@ -1833,7 +1831,7 @@ pub(crate) fn report_invalid_arguments_to_annotated( builder.into_diagnostic(format_args!( "Special form `{}` expected at least 2 arguments \ (one type and at least one metadata element)", - KnownInstanceType::Annotated.repr(db) + SpecialFormType::Annotated )); } @@ -1873,7 +1871,6 @@ pub(crate) fn report_bad_argument_to_get_protocol_members( } pub(crate) fn report_invalid_arguments_to_callable( - db: &dyn Db, context: &InferContext, subscript: &ast::ExprSubscript, ) { @@ -1882,7 +1879,7 @@ pub(crate) fn report_invalid_arguments_to_callable( }; builder.into_diagnostic(format_args!( "Special form `{}` expected exactly two arguments (parameter types and return type)", - KnownInstanceType::Callable.repr(db) + SpecialFormType::Callable )); } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 1a12f2291fb0f6..6c4dd480d03e77 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -110,6 +110,7 @@ impl Display for DisplayRepresentation<'_> { SubclassOfInner::Class(class) => write!(f, "type[{}]", class.name(self.db)), SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"), }, + Type::SpecialForm(special_form) => special_form.fmt(f), Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f), Type::FunctionLiteral(function) => { let signature = function.signature(self.db); diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b60b54a4e5d658..6187e87ca1d142 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -89,7 +89,7 @@ use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, - MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, + MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, @@ -850,7 +850,7 @@ impl<'db> TypeInferenceBuilder<'db> { // - If the class is a protocol class: check for inheritance from a non-protocol class for (i, base_class) in class.explicit_bases(self.db()).iter().enumerate() { let base_class = match base_class { - Type::KnownInstance(KnownInstanceType::Generic(None)) => { + Type::SpecialForm(SpecialFormType::Generic) => { if let Some(builder) = self .context .report_lint(&INVALID_BASE, &class_node.bases()[i]) @@ -864,7 +864,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Note that unlike several of the other errors caught in this function, // this does not lead to the class creation failing at runtime, // but it is semantically invalid. - Type::KnownInstance(KnownInstanceType::Protocol(Some(_))) => { + Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)) => { if class_node.type_params.is_none() { continue; } @@ -3084,6 +3084,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::BytesLiteral(..) | Type::LiteralString | Type::Tuple(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) | Type::PropertyInstance(..) | Type::FunctionLiteral(..) @@ -3544,10 +3545,10 @@ impl<'db> TypeInferenceBuilder<'db> { } }; - if let Some(known_instance) = target.as_name_expr().and_then(|name| { - KnownInstanceType::try_from_file_and_name(self.db(), self.file(), &name.id) + if let Some(special_form) = target.as_name_expr().and_then(|name| { + SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name.id) }) { - target_ty = Type::KnownInstance(known_instance); + target_ty = Type::SpecialForm(special_form); } self.store_expression_type(target, target_ty); @@ -3631,12 +3632,12 @@ impl<'db> TypeInferenceBuilder<'db> { if let Type::NominalInstance(instance) = declared_ty.inner_type() { if instance.class.is_known(self.db(), KnownClass::SpecialForm) { if let Some(name_expr) = target.as_name_expr() { - if let Some(known_instance) = KnownInstanceType::try_from_file_and_name( + if let Some(special_form) = SpecialFormType::try_from_file_and_name( self.db(), self.file(), &name_expr.id, ) { - declared_ty.inner = Type::KnownInstance(known_instance); + declared_ty.inner = Type::SpecialForm(special_form); } } } @@ -5958,6 +5959,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Union(_) @@ -6287,6 +6289,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) @@ -6312,6 +6315,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) @@ -7432,48 +7436,49 @@ impl<'db> TypeInferenceBuilder<'db> { value_ty, Type::IntLiteral(i64::from(bool)), ), - (Type::KnownInstance(KnownInstanceType::Protocol(None)), Type::Tuple(typevars), _) => { - self.legacy_generic_class_context( + (Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => self + .legacy_generic_class_context( value_node, typevars.elements(self.db()), LegacyGenericBase::Protocol, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) - .unwrap_or_else(Type::unknown) - } - (Type::KnownInstance(KnownInstanceType::Protocol(None)), typevar, _) => self + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))) + .unwrap_or_else(Type::unknown), + (Type::SpecialForm(SpecialFormType::Protocol), typevar, _) => self .legacy_generic_class_context( value_node, std::slice::from_ref(&typevar), LegacyGenericBase::Protocol, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))) .unwrap_or_else(Type::unknown), - (Type::KnownInstance(KnownInstanceType::Protocol(Some(_))), _, _) => { + (Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)), _, _) => { // TODO: emit a diagnostic todo_type!("doubly-specialized typing.Protocol") } - (Type::KnownInstance(KnownInstanceType::Generic(None)), Type::Tuple(typevars), _) => { - self.legacy_generic_class_context( + (Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => self + .legacy_generic_class_context( value_node, typevars.elements(self.db()), LegacyGenericBase::Generic, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) - .unwrap_or_else(Type::unknown) - } - (Type::KnownInstance(KnownInstanceType::Generic(None)), typevar, _) => self + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))) + .unwrap_or_else(Type::unknown), + (Type::SpecialForm(SpecialFormType::Generic), typevar, _) => self .legacy_generic_class_context( value_node, std::slice::from_ref(&typevar), LegacyGenericBase::Generic, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))) .unwrap_or_else(Type::unknown), - (Type::KnownInstance(KnownInstanceType::Generic(Some(_))), _, _) => { + (Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)), _, _) => { // TODO: emit a diagnostic todo_type!("doubly-specialized typing.Generic") } + (Type::SpecialForm(special_form), _, _) if special_form.class().is_special_form() => { + todo_type!("Inference of subscript on special form") + } (Type::KnownInstance(known_instance), _, _) if known_instance.class().is_special_form() => { @@ -7779,10 +7784,10 @@ impl<'db> TypeInferenceBuilder<'db> { ast::ExprContext::Load => { let name_expr_ty = self.infer_name_expression(name); match name_expr_ty { - Type::KnownInstance(KnownInstanceType::ClassVar) => { + Type::SpecialForm(SpecialFormType::ClassVar) => { TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR) } - Type::KnownInstance(KnownInstanceType::Final) => { + Type::SpecialForm(SpecialFormType::Final) => { TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL) } _ => name_expr_ty @@ -7809,7 +7814,7 @@ impl<'db> TypeInferenceBuilder<'db> { let slice = &**slice; match value_ty { - Type::KnownInstance(KnownInstanceType::Annotated) => { + Type::SpecialForm(SpecialFormType::Annotated) => { // This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but // `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly // differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`). @@ -7818,11 +7823,7 @@ impl<'db> TypeInferenceBuilder<'db> { }) = slice { if arguments.len() < 2 { - report_invalid_arguments_to_annotated( - self.db(), - &self.context, - subscript, - ); + report_invalid_arguments_to_annotated(&self.context, subscript); } if let [inner_annotation, metadata @ ..] = &arguments[..] { @@ -7840,16 +7841,12 @@ impl<'db> TypeInferenceBuilder<'db> { TypeAndQualifiers::unknown() } } else { - report_invalid_arguments_to_annotated( - self.db(), - &self.context, - subscript, - ); + report_invalid_arguments_to_annotated(&self.context, subscript); self.infer_annotation_expression_impl(slice) } } - Type::KnownInstance( - known_instance @ (KnownInstanceType::ClassVar | KnownInstanceType::Final), + Type::SpecialForm( + type_qualifier @ (SpecialFormType::ClassVar | SpecialFormType::Final), ) => match slice { ast::Expr::Tuple(..) => { if let Some(builder) = @@ -7858,7 +7855,6 @@ impl<'db> TypeInferenceBuilder<'db> { builder.into_diagnostic(format_args!( "Type qualifier `{type_qualifier}` \ expects exactly one type parameter", - type_qualifier = known_instance.repr(self.db()), )); } Type::unknown().into() @@ -7866,11 +7862,11 @@ impl<'db> TypeInferenceBuilder<'db> { _ => { let mut type_and_qualifiers = self.infer_annotation_expression_impl(slice); - match known_instance { - KnownInstanceType::ClassVar => { + match type_qualifier { + SpecialFormType::ClassVar => { type_and_qualifiers.add_qualifier(TypeQualifiers::CLASS_VAR); } - KnownInstanceType::Final => { + SpecialFormType::Final => { type_and_qualifiers.add_qualifier(TypeQualifiers::FINAL); } _ => unreachable!(), @@ -8322,7 +8318,7 @@ impl<'db> TypeInferenceBuilder<'db> { builder.expression_type(value) }; - value_ty == Type::KnownInstance(KnownInstanceType::Unpack) + value_ty == Type::SpecialForm(SpecialFormType::Unpack) } _ => false, } @@ -8393,7 +8389,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) } } - Type::KnownInstance(KnownInstanceType::Unknown) => { + Type::SpecialForm(SpecialFormType::Unknown) => { SubclassOfType::subclass_of_unknown() } _ => todo_type!("unsupported type[X] special form"), @@ -8424,7 +8420,7 @@ impl<'db> TypeInferenceBuilder<'db> { .. }) => { let parameters_ty = match self.infer_expression(value) { - Type::KnownInstance(KnownInstanceType::Union) => match &**parameters { + Type::SpecialForm(SpecialFormType::Union) => match &**parameters { ast::Expr::Tuple(tuple) => { let ty = UnionType::from_elements( self.db(), @@ -8472,9 +8468,37 @@ impl<'db> TypeInferenceBuilder<'db> { } Type::unknown() } - Type::KnownInstance(known_instance) => { - self.infer_parameterized_known_instance_type_expression(subscript, known_instance) + Type::SpecialForm(special_form) => { + self.infer_parameterized_special_form_type_expression(subscript, special_form) } + Type::KnownInstance(known_instance) => match known_instance { + KnownInstanceType::SubscriptedProtocol(_) => { + self.infer_type_expression(&subscript.slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`typing.Protocol` is not allowed in type expressions", + )); + } + Type::unknown() + } + KnownInstanceType::SubscriptedGeneric(_) => { + self.infer_type_expression(&subscript.slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`typing.Generic` is not allowed in type expressions", + )); + } + Type::unknown() + } + KnownInstanceType::TypeVar(_) => { + self.infer_type_expression(&subscript.slice); + todo_type!("TypeVar annotations") + } + KnownInstanceType::TypeAliasType(_) => { + self.infer_type_expression(&subscript.slice); + todo_type!("Generic PEP-695 type alias") + } + }, Type::Dynamic(DynamicType::Todo(_)) => { self.infer_type_expression(slice); value_ty @@ -8509,20 +8533,20 @@ impl<'db> TypeInferenceBuilder<'db> { } } - fn infer_parameterized_known_instance_type_expression( + fn infer_parameterized_special_form_type_expression( &mut self, subscript: &ast::ExprSubscript, - known_instance: KnownInstanceType, + special_form: SpecialFormType, ) -> Type<'db> { let db = self.db(); let arguments_slice = &*subscript.slice; - match known_instance { - KnownInstanceType::Annotated => { + match special_form { + SpecialFormType::Annotated => { let ast::Expr::Tuple(ast::ExprTuple { elts: arguments, .. }) = arguments_slice else { - report_invalid_arguments_to_annotated(self.db(), &self.context, subscript); + report_invalid_arguments_to_annotated(&self.context, subscript); // `Annotated[]` with less than two arguments is an error at runtime. // However, we still treat `Annotated[T]` as `T` here for the purpose of @@ -8532,7 +8556,7 @@ impl<'db> TypeInferenceBuilder<'db> { }; if arguments.len() < 2 { - report_invalid_arguments_to_annotated(self.db(), &self.context, subscript); + report_invalid_arguments_to_annotated(&self.context, subscript); } let [type_expr, metadata @ ..] = &arguments[..] else { @@ -8548,29 +8572,27 @@ impl<'db> TypeInferenceBuilder<'db> { self.store_expression_type(arguments_slice, ty); ty } - KnownInstanceType::Literal => { - match self.infer_literal_parameter_type(arguments_slice) { - Ok(ty) => ty, - Err(nodes) => { - for node in nodes { - if let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, node) - { - builder.into_diagnostic( - "Type arguments for `Literal` must be `None`, \ - a literal value (int, bool, str, or bytes), or an enum value", - ); - } - } - Type::unknown() + SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { + Ok(ty) => ty, + Err(nodes) => { + for node in nodes { + let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node) + else { + continue; + }; + builder.into_diagnostic( + "Type arguments for `Literal` must be `None`, \ + a literal value (int, bool, str, or bytes), or an enum value", + ); } + Type::unknown() } - } - KnownInstanceType::Optional => { + }, + SpecialFormType::Optional => { let param_type = self.infer_type_expression(arguments_slice); UnionType::from_elements(db, [param_type, Type::none(db)]) } - KnownInstanceType::Union => match arguments_slice { + SpecialFormType::Union => match arguments_slice { ast::Expr::Tuple(t) => { let union_ty = UnionType::from_elements( db, @@ -8581,15 +8603,7 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => self.infer_type_expression(arguments_slice), }, - KnownInstanceType::TypeVar(_) => { - self.infer_type_expression(arguments_slice); - todo_type!("TypeVar annotations") - } - KnownInstanceType::TypeAliasType(_) => { - self.infer_type_expression(arguments_slice); - todo_type!("Generic PEP-695 type alias") - } - KnownInstanceType::Callable => { + SpecialFormType::Callable => { let mut arguments = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), _ => { @@ -8616,7 +8630,7 @@ impl<'db> TypeInferenceBuilder<'db> { }; if !correct_argument_number { - report_invalid_arguments_to_callable(self.db(), &self.context, subscript); + report_invalid_arguments_to_callable(&self.context, subscript); } let callable_type = if let (Some(parameters), Some(return_type), true) = @@ -8638,12 +8652,11 @@ impl<'db> TypeInferenceBuilder<'db> { } // Type API special forms - KnownInstanceType::Not => match arguments_slice { + SpecialFormType::Not => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8653,7 +8666,7 @@ impl<'db> TypeInferenceBuilder<'db> { argument_type.negate(db) } }, - KnownInstanceType::Intersection => { + SpecialFormType::Intersection => { let elements = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), element => Either::Right(std::iter::once(element)), @@ -8670,12 +8683,11 @@ impl<'db> TypeInferenceBuilder<'db> { } ty } - KnownInstanceType::TypeOf => match arguments_slice { + SpecialFormType::TypeOf => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8686,12 +8698,11 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_expression(arguments_slice) } }, - KnownInstanceType::CallableTypeOf => match arguments_slice { + SpecialFormType::CallableTypeOf => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8721,11 +8732,10 @@ impl<'db> TypeInferenceBuilder<'db> { .report_lint(&INVALID_TYPE_FORM, arguments_slice) { builder.into_diagnostic(format_args!( - "Expected the first argument to `{}` \ + "Expected the first argument to `{special_form}` \ to be a callable object, \ - but got an object of type `{}`", - known_instance.repr(self.db()), - argument_type.display(db) + but got an object of type `{actual_type}`", + actual_type = argument_type.display(db) )); } return Type::unknown(); @@ -8739,142 +8749,129 @@ impl<'db> TypeInferenceBuilder<'db> { }, // TODO: Generics - KnownInstanceType::ChainMap => { + SpecialFormType::ChainMap => { self.infer_type_expression(arguments_slice); KnownClass::ChainMap.to_instance(db) } - KnownInstanceType::OrderedDict => { + SpecialFormType::OrderedDict => { self.infer_type_expression(arguments_slice); KnownClass::OrderedDict.to_instance(db) } - KnownInstanceType::Dict => { + SpecialFormType::Dict => { self.infer_type_expression(arguments_slice); KnownClass::Dict.to_instance(db) } - KnownInstanceType::List => { + SpecialFormType::List => { self.infer_type_expression(arguments_slice); KnownClass::List.to_instance(db) } - KnownInstanceType::DefaultDict => { + SpecialFormType::DefaultDict => { self.infer_type_expression(arguments_slice); KnownClass::DefaultDict.to_instance(db) } - KnownInstanceType::Counter => { + SpecialFormType::Counter => { self.infer_type_expression(arguments_slice); KnownClass::Counter.to_instance(db) } - KnownInstanceType::Set => { + SpecialFormType::Set => { self.infer_type_expression(arguments_slice); KnownClass::Set.to_instance(db) } - KnownInstanceType::FrozenSet => { + SpecialFormType::FrozenSet => { self.infer_type_expression(arguments_slice); KnownClass::FrozenSet.to_instance(db) } - KnownInstanceType::Deque => { + SpecialFormType::Deque => { self.infer_type_expression(arguments_slice); KnownClass::Deque.to_instance(db) } - KnownInstanceType::ReadOnly => { + SpecialFormType::ReadOnly => { self.infer_type_expression(arguments_slice); todo_type!("`ReadOnly[]` type qualifier") } - KnownInstanceType::NotRequired => { + SpecialFormType::NotRequired => { self.infer_type_expression(arguments_slice); todo_type!("`NotRequired[]` type qualifier") } - KnownInstanceType::ClassVar | KnownInstanceType::Final => { + SpecialFormType::ClassVar | SpecialFormType::Final => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let diag = builder.into_diagnostic(format_args!( - "Type qualifier `{}` is not allowed in type expressions \ + "Type qualifier `{special_form}` is not allowed in type expressions \ (only in annotation expressions)", - known_instance.repr(self.db()) )); diagnostic::add_type_expression_reference_link(diag); } self.infer_type_expression(arguments_slice) } - KnownInstanceType::Required => { + SpecialFormType::Required => { self.infer_type_expression(arguments_slice); todo_type!("`Required[]` type qualifier") } - KnownInstanceType::TypeIs => { + SpecialFormType::TypeIs => { self.infer_type_expression(arguments_slice); todo_type!("`TypeIs[]` special form") } - KnownInstanceType::TypeGuard => { + SpecialFormType::TypeGuard => { self.infer_type_expression(arguments_slice); todo_type!("`TypeGuard[]` special form") } - KnownInstanceType::Concatenate => { + SpecialFormType::Concatenate => { self.infer_type_expression(arguments_slice); todo_type!("`Concatenate[]` special form") } - KnownInstanceType::Unpack => { + SpecialFormType::Unpack => { self.infer_type_expression(arguments_slice); todo_type!("`Unpack[]` special form") } - KnownInstanceType::Protocol(_) => { - self.infer_type_expression(arguments_slice); - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.Protocol` is not allowed in type expressions", - )); - } - Type::unknown() - } - KnownInstanceType::Generic(_) => { + SpecialFormType::NoReturn + | SpecialFormType::Never + | SpecialFormType::AlwaysTruthy + | SpecialFormType::AlwaysFalsy => { self.infer_type_expression(arguments_slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`typing.Generic` is not allowed in type expressions", + "Type `{special_form}` expected no type parameter", )); } Type::unknown() } - KnownInstanceType::NoReturn - | KnownInstanceType::Never - | KnownInstanceType::AlwaysTruthy - | KnownInstanceType::AlwaysFalsy => { + SpecialFormType::TypingSelf + | SpecialFormType::TypeAlias + | SpecialFormType::TypedDict + | SpecialFormType::Unknown => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Type `{}` expected no type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected no type parameter", )); } Type::unknown() } - KnownInstanceType::TypingSelf - | KnownInstanceType::TypeAlias - | KnownInstanceType::TypedDict - | KnownInstanceType::Unknown => { + SpecialFormType::LiteralString => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "Special form `{}` expected no type parameter", - known_instance.repr(self.db()) + let mut diag = builder.into_diagnostic(format_args!( + "Type `{special_form}` expected no type parameter", )); + diag.info("Did you mean to use `Literal[...]` instead?"); } Type::unknown() } - KnownInstanceType::LiteralString => { - self.infer_type_expression(arguments_slice); - + SpecialFormType::Type => self.infer_subclass_of_type_expression(arguments_slice), + SpecialFormType::Tuple => self.infer_tuple_type_expression(arguments_slice), + SpecialFormType::Generic | SpecialFormType::Protocol => { + self.infer_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - let mut diag = builder.into_diagnostic(format_args!( - "Type `{}` expected no type parameter", - known_instance.repr(self.db()) + builder.into_diagnostic(format_args!( + "`{special_form}` is not allowed in type expressions", )); - diag.info("Did you mean to use `Literal[...]` instead?"); } Type::unknown() } - KnownInstanceType::Type => self.infer_subclass_of_type_expression(arguments_slice), - KnownInstanceType::Tuple => self.infer_tuple_type_expression(arguments_slice), } } @@ -8886,7 +8883,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO handle type aliases ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { let value_ty = self.infer_expression(value); - if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) { + if matches!(value_ty, Type::SpecialForm(SpecialFormType::Literal)) { let ty = self.infer_literal_parameter_type(slice)?; // This branch deals with annotations such as `Literal[Literal[1]]`. @@ -9427,7 +9424,7 @@ mod tests { let name_ty = var_ty.member(&db, "__name__").symbol.expect_type(); assert_eq!(name_ty.display(&db).to_string(), expected_name_ty); - let KnownInstanceType::TypeVar(typevar) = var_ty.expect_known_instance() else { + let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = var_ty else { panic!("expected TypeVar"); }; diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs deleted file mode 100644 index 095d1cf752b9d7..00000000000000 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ /dev/null @@ -1,413 +0,0 @@ -//! The `KnownInstance` type. -//! -//! Despite its name, this is quite a different type from [`super::NominalInstanceType`]. -//! For the vast majority of instance-types in Python, we cannot say how many possible -//! inhabitants there are or could be of that type at runtime. Each variant of the -//! [`KnownInstanceType`] enum, however, represents a specific runtime symbol -//! that requires heavy special-casing in the type system. Thus any one `KnownInstance` -//! variant can only be inhabited by one or two specific objects at runtime with -//! locations that are known in advance. - -use std::fmt::Display; - -use super::generics::GenericContext; -use super::{ClassType, Type, TypeAliasType, TypeVarInstance, class::KnownClass}; -use crate::db::Db; -use crate::module_resolver::{KnownModule, file_to_module}; -use ruff_db::files::File; - -/// Enumeration of specific runtime symbols that are special enough -/// that they can each be considered to inhabit a unique type. -/// -/// # Ordering -/// -/// Ordering between variants is stable and should be the same between runs. -/// Ordering within variants (for variants that wrap associate data) -/// is based on the known-instance's salsa-assigned id and not on its values. -/// The id may change between runs, or when the type var instance was garbage collected and recreated. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, PartialOrd, Ord)] -pub enum KnownInstanceType<'db> { - /// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`) - Annotated, - /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) - Literal, - /// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`) - LiteralString, - /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) - Optional, - /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) - Union, - /// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`) - NoReturn, - /// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`) - Never, - /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) - Tuple, - /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) - List, - /// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`) - Dict, - /// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`) - Set, - /// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`) - FrozenSet, - /// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`) - ChainMap, - /// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`) - Counter, - /// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`) - DefaultDict, - /// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`) - Deque, - /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) - OrderedDict, - /// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`) - Protocol(Option>), - /// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`) - Generic(Option>), - /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) - Type, - /// A single instance of `typing.TypeVar` - TypeVar(TypeVarInstance<'db>), - /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) - TypeAliasType(TypeAliasType<'db>), - /// The symbol `ty_extensions.Unknown` - Unknown, - /// The symbol `ty_extensions.AlwaysTruthy` - AlwaysTruthy, - /// The symbol `ty_extensions.AlwaysFalsy` - AlwaysFalsy, - /// The symbol `ty_extensions.Not` - Not, - /// The symbol `ty_extensions.Intersection` - Intersection, - /// The symbol `ty_extensions.TypeOf` - TypeOf, - /// The symbol `ty_extensions.CallableTypeOf` - CallableTypeOf, - /// The symbol `typing.Callable` - /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) - Callable, - /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or - /// `_typeshed.Self`) - TypingSelf, - - // Various special forms, special aliases and type qualifiers that we don't yet understand - // (all currently inferred as TODO in most contexts): - Final, - ClassVar, - Concatenate, - Unpack, - Required, - NotRequired, - TypeAlias, - TypeGuard, - TypedDict, - TypeIs, - ReadOnly, - // TODO: fill this enum out with more special forms, etc. -} - -impl<'db> KnownInstanceType<'db> { - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { - match self { - Self::Annotated - | Self::Literal - | Self::LiteralString - | Self::Optional - | Self::Union - | Self::NoReturn - | Self::Never - | Self::Tuple - | Self::Type - | Self::TypingSelf - | Self::Final - | Self::ClassVar - | Self::Callable - | Self::Concatenate - | Self::Unpack - | Self::Required - | Self::NotRequired - | Self::TypeAlias - | Self::TypeGuard - | Self::TypedDict - | Self::TypeIs - | Self::List - | Self::Dict - | Self::DefaultDict - | Self::Set - | Self::FrozenSet - | Self::Counter - | Self::Deque - | Self::ChainMap - | Self::OrderedDict - | Self::ReadOnly - | Self::Unknown - | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::Not - | Self::Intersection - | Self::TypeOf - | Self::CallableTypeOf => self, - Self::TypeVar(tvar) => Self::TypeVar(tvar.normalized(db)), - Self::Protocol(ctx) => Self::Protocol(ctx.map(|ctx| ctx.normalized(db))), - Self::Generic(ctx) => Self::Generic(ctx.map(|ctx| ctx.normalized(db))), - Self::TypeAliasType(alias) => Self::TypeAliasType(alias.normalized(db)), - } - } - - /// Return the repr of the symbol at runtime - pub(crate) fn repr(self, db: &'db dyn Db) -> impl Display + 'db { - KnownInstanceRepr { - known_instance: self, - db, - } - } - - /// Return the [`KnownClass`] which this symbol is an instance of - pub(crate) const fn class(self) -> KnownClass { - match self { - Self::Annotated => KnownClass::SpecialForm, - Self::Literal => KnownClass::SpecialForm, - Self::LiteralString => KnownClass::SpecialForm, - Self::Optional => KnownClass::SpecialForm, - Self::Union => KnownClass::SpecialForm, - Self::NoReturn => KnownClass::SpecialForm, - Self::Never => KnownClass::SpecialForm, - Self::Tuple => KnownClass::SpecialForm, - Self::Type => KnownClass::SpecialForm, - Self::TypingSelf => KnownClass::SpecialForm, - Self::Final => KnownClass::SpecialForm, - Self::ClassVar => KnownClass::SpecialForm, - Self::Callable => KnownClass::SpecialForm, - Self::Concatenate => KnownClass::SpecialForm, - Self::Unpack => KnownClass::SpecialForm, - Self::Required => KnownClass::SpecialForm, - Self::NotRequired => KnownClass::SpecialForm, - Self::TypeAlias => KnownClass::SpecialForm, - Self::TypeGuard => KnownClass::SpecialForm, - Self::TypedDict => KnownClass::SpecialForm, - Self::TypeIs => KnownClass::SpecialForm, - Self::ReadOnly => KnownClass::SpecialForm, - Self::List => KnownClass::StdlibAlias, - Self::Dict => KnownClass::StdlibAlias, - Self::DefaultDict => KnownClass::StdlibAlias, - Self::Set => KnownClass::StdlibAlias, - Self::FrozenSet => KnownClass::StdlibAlias, - Self::Counter => KnownClass::StdlibAlias, - Self::Deque => KnownClass::StdlibAlias, - Self::ChainMap => KnownClass::StdlibAlias, - Self::OrderedDict => KnownClass::StdlibAlias, - Self::Protocol(_) => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says - Self::Generic(_) => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says - Self::TypeVar(_) => KnownClass::TypeVar, - Self::TypeAliasType(_) => KnownClass::TypeAliasType, - Self::TypeOf => KnownClass::SpecialForm, - Self::Not => KnownClass::SpecialForm, - Self::Intersection => KnownClass::SpecialForm, - Self::CallableTypeOf => KnownClass::SpecialForm, - Self::Unknown => KnownClass::Object, - Self::AlwaysTruthy => KnownClass::Object, - Self::AlwaysFalsy => KnownClass::Object, - } - } - - /// Return the instance type which this type is a subtype of. - /// - /// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`, - /// so `KnownInstanceType::Literal.instance_fallback(db)` - /// returns `Type::NominalInstance(NominalInstanceType { class: })`. - pub(super) fn instance_fallback(self, db: &dyn Db) -> Type { - self.class().to_instance(db) - } - - /// Return `true` if this symbol is an instance of `class`. - pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool { - self.class().is_subclass_of(db, class) - } - - pub(super) fn try_from_file_and_name( - db: &'db dyn Db, - file: File, - symbol_name: &str, - ) -> Option { - let candidate = match symbol_name { - "ClassVar" => Self::ClassVar, - "Deque" => Self::Deque, - "List" => Self::List, - "Dict" => Self::Dict, - "DefaultDict" => Self::DefaultDict, - "Set" => Self::Set, - "FrozenSet" => Self::FrozenSet, - "Counter" => Self::Counter, - "ChainMap" => Self::ChainMap, - "OrderedDict" => Self::OrderedDict, - "Generic" => Self::Generic(None), - "Protocol" => Self::Protocol(None), - "Optional" => Self::Optional, - "Union" => Self::Union, - "NoReturn" => Self::NoReturn, - "Tuple" => Self::Tuple, - "Type" => Self::Type, - "Callable" => Self::Callable, - "Annotated" => Self::Annotated, - "Literal" => Self::Literal, - "Never" => Self::Never, - "Self" => Self::TypingSelf, - "Final" => Self::Final, - "Unpack" => Self::Unpack, - "Required" => Self::Required, - "TypeAlias" => Self::TypeAlias, - "TypeGuard" => Self::TypeGuard, - "TypedDict" => Self::TypedDict, - "TypeIs" => Self::TypeIs, - "ReadOnly" => Self::ReadOnly, - "Concatenate" => Self::Concatenate, - "NotRequired" => Self::NotRequired, - "LiteralString" => Self::LiteralString, - "Unknown" => Self::Unknown, - "AlwaysTruthy" => Self::AlwaysTruthy, - "AlwaysFalsy" => Self::AlwaysFalsy, - "Not" => Self::Not, - "Intersection" => Self::Intersection, - "TypeOf" => Self::TypeOf, - "CallableTypeOf" => Self::CallableTypeOf, - _ => return None, - }; - - candidate - .check_module(file_to_module(db, file)?.known()?) - .then_some(candidate) - } - - /// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate. - /// - /// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`. - /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. - pub(super) fn check_module(self, module: KnownModule) -> bool { - match self { - Self::ClassVar - | Self::Deque - | Self::List - | Self::Dict - | Self::DefaultDict - | Self::Set - | Self::FrozenSet - | Self::Counter - | Self::ChainMap - | Self::OrderedDict - | Self::Optional - | Self::Union - | Self::NoReturn - | Self::Tuple - | Self::Type - | Self::Generic(_) - | Self::Callable => module.is_typing(), - Self::Annotated - | Self::Protocol(_) - | Self::Literal - | Self::LiteralString - | Self::Never - | Self::Final - | Self::Concatenate - | Self::Unpack - | Self::Required - | Self::NotRequired - | Self::TypeAlias - | Self::TypeGuard - | Self::TypedDict - | Self::TypeIs - | Self::ReadOnly - | Self::TypeAliasType(_) - | Self::TypeVar(_) => { - matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) - } - Self::TypingSelf => { - matches!( - module, - KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed - ) - } - Self::Unknown - | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::Not - | Self::Intersection - | Self::TypeOf - | Self::CallableTypeOf => module.is_ty_extensions(), - } - } - - pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { - self.class().to_class_literal(db) - } -} - -struct KnownInstanceRepr<'db> { - known_instance: KnownInstanceType<'db>, - db: &'db dyn Db, -} - -impl Display for KnownInstanceRepr<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.known_instance { - KnownInstanceType::Annotated => f.write_str("typing.Annotated"), - KnownInstanceType::Literal => f.write_str("typing.Literal"), - KnownInstanceType::LiteralString => f.write_str("typing.LiteralString"), - KnownInstanceType::Optional => f.write_str("typing.Optional"), - KnownInstanceType::Union => f.write_str("typing.Union"), - KnownInstanceType::NoReturn => f.write_str("typing.NoReturn"), - KnownInstanceType::Never => f.write_str("typing.Never"), - KnownInstanceType::Tuple => f.write_str("typing.Tuple"), - KnownInstanceType::Type => f.write_str("typing.Type"), - KnownInstanceType::TypingSelf => f.write_str("typing.Self"), - KnownInstanceType::Final => f.write_str("typing.Final"), - KnownInstanceType::ClassVar => f.write_str("typing.ClassVar"), - KnownInstanceType::Callable => f.write_str("typing.Callable"), - KnownInstanceType::Concatenate => f.write_str("typing.Concatenate"), - KnownInstanceType::Unpack => f.write_str("typing.Unpack"), - KnownInstanceType::Required => f.write_str("typing.Required"), - KnownInstanceType::NotRequired => f.write_str("typing.NotRequired"), - KnownInstanceType::TypeAlias => f.write_str("typing.TypeAlias"), - KnownInstanceType::TypeGuard => f.write_str("typing.TypeGuard"), - KnownInstanceType::TypedDict => f.write_str("typing.TypedDict"), - KnownInstanceType::TypeIs => f.write_str("typing.TypeIs"), - KnownInstanceType::List => f.write_str("typing.List"), - KnownInstanceType::Dict => f.write_str("typing.Dict"), - KnownInstanceType::DefaultDict => f.write_str("typing.DefaultDict"), - KnownInstanceType::Set => f.write_str("typing.Set"), - KnownInstanceType::FrozenSet => f.write_str("typing.FrozenSet"), - KnownInstanceType::Counter => f.write_str("typing.Counter"), - KnownInstanceType::Deque => f.write_str("typing.Deque"), - KnownInstanceType::ChainMap => f.write_str("typing.ChainMap"), - KnownInstanceType::OrderedDict => f.write_str("typing.OrderedDict"), - KnownInstanceType::Protocol(generic_context) => { - f.write_str("typing.Protocol")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - KnownInstanceType::Generic(generic_context) => { - f.write_str("typing.Generic")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - KnownInstanceType::ReadOnly => f.write_str("typing.ReadOnly"), - // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render - // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll - // have a `Type::TypeVar(_)`, which is rendered as the typevar's name. - KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), - KnownInstanceType::TypeAliasType(_) => f.write_str("typing.TypeAliasType"), - KnownInstanceType::Unknown => f.write_str("ty_extensions.Unknown"), - KnownInstanceType::AlwaysTruthy => f.write_str("ty_extensions.AlwaysTruthy"), - KnownInstanceType::AlwaysFalsy => f.write_str("ty_extensions.AlwaysFalsy"), - KnownInstanceType::Not => f.write_str("ty_extensions.Not"), - KnownInstanceType::Intersection => f.write_str("ty_extensions.Intersection"), - KnownInstanceType::TypeOf => f.write_str("ty_extensions.TypeOf"), - KnownInstanceType::CallableTypeOf => f.write_str("ty_extensions.CallableTypeOf"), - } - } -} diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 44957fb929f732..4b45888524d911 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher; use crate::Db; use crate::types::class_base::ClassBase; use crate::types::generics::Specialization; -use crate::types::{ClassLiteral, ClassType, KnownInstanceType, Type}; +use crate::types::{ClassLiteral, ClassType, KnownInstanceType, SpecialFormType, Type}; /// The inferred method resolution order of a given class. /// @@ -92,7 +92,7 @@ impl<'db> Mro<'db> { original_bases: &[Type<'db>], remaining_bases: &[Type<'db>], ) { - if original_bases.contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) { + if original_bases.contains(&Type::SpecialForm(SpecialFormType::Protocol)) { return; } if remaining_bases.iter().any(Type::is_generic_alias) { @@ -146,7 +146,8 @@ impl<'db> Mro<'db> { single_base, Type::GenericAlias(_) | Type::KnownInstance( - KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) + KnownInstanceType::SubscriptedGeneric(_) + | KnownInstanceType::SubscriptedProtocol(_) ) ) => { @@ -178,7 +179,7 @@ impl<'db> Mro<'db> { // (see `infer::TypeInferenceBuilder::check_class_definitions`), // which is why we only care about `KnownInstanceType::Generic(Some(_))`, // not `KnownInstanceType::Generic(None)`. - if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base { + if let Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)) = base { maybe_add_generic( &mut resolved_bases, original_bases, @@ -226,7 +227,11 @@ impl<'db> Mro<'db> { if class.has_pep_695_type_params(db) && original_bases.iter().any(|base| { - matches!(base, Type::KnownInstance(KnownInstanceType::Generic(_))) + matches!( + base, + Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)) + | Type::SpecialForm(SpecialFormType::Generic) + ) }) { return Err(MroErrorKind::Pep695ClassWithGenericInheritance); diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 0850b38745615f..c419b1c31b662e 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -1,8 +1,8 @@ use crate::db::tests::TestDb; use crate::symbol::{builtins_symbol, known_module_symbol}; use crate::types::{ - BoundMethodType, CallableType, IntersectionBuilder, KnownClass, KnownInstanceType, Parameter, - Parameters, Signature, SubclassOfType, TupleType, Type, UnionType, + BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters, + Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType, }; use crate::{Db, KnownModule}; use hashbrown::HashSet; @@ -142,7 +142,7 @@ impl Ty { Ty::AbcClassLiteral(s) => known_module_symbol(db, KnownModule::Abc, s) .symbol .expect_type(), - Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal), + Ty::TypingLiteral => Type::SpecialForm(SpecialFormType::Literal), Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).symbol.expect_type(), Ty::KnownClassInstance(known_class) => known_class.to_instance(db), Ty::Union(tys) => { diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs new file mode 100644 index 00000000000000..73ef8362ad540b --- /dev/null +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -0,0 +1,305 @@ +//! An enumeration of special forms in the Python type system. +//! Each of these is considered to inhabit a unique type in our model of the type system. + +use super::{ClassType, Type, class::KnownClass}; +use crate::db::Db; +use crate::module_resolver::{KnownModule, file_to_module}; +use ruff_db::files::File; +use std::str::FromStr; + +/// Enumeration of specific runtime symbols that are special enough +/// that they can each be considered to inhabit a unique type. +/// +/// # Ordering +/// +/// Ordering is stable and should be the same between runs. +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + salsa::Update, + PartialOrd, + Ord, + strum_macros::EnumString, +)] +pub enum SpecialFormType { + /// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`) + Annotated, + /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) + Literal, + /// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`) + LiteralString, + /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) + Optional, + /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) + Union, + /// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`) + NoReturn, + /// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`) + Never, + /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) + Tuple, + /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) + List, + /// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`) + Dict, + /// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`) + Set, + /// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`) + FrozenSet, + /// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`) + ChainMap, + /// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`) + Counter, + /// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`) + DefaultDict, + /// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`) + Deque, + /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) + OrderedDict, + /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) + Type, + /// The symbol `ty_extensions.Unknown` + Unknown, + /// The symbol `ty_extensions.AlwaysTruthy` + AlwaysTruthy, + /// The symbol `ty_extensions.AlwaysFalsy` + AlwaysFalsy, + /// The symbol `ty_extensions.Not` + Not, + /// The symbol `ty_extensions.Intersection` + Intersection, + /// The symbol `ty_extensions.TypeOf` + TypeOf, + /// The symbol `ty_extensions.CallableTypeOf` + CallableTypeOf, + /// The symbol `typing.Callable` + /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) + Callable, + /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or `_typeshed.Self`) + #[strum(serialize = "Self")] + TypingSelf, + /// The symbol `typing.Final` (which can also be found as `typing_extensions.Final`) + Final, + /// The symbol `typing.ClassVar` (which can also be found as `typing_extensions.ClassVar`) + ClassVar, + /// The symbol `typing.Concatenate` (which can also be found as `typing_extensions.Concatenate`) + Concatenate, + /// The symbol `typing.Unpack` (which can also be found as `typing_extensions.Unpack`) + Unpack, + /// The symbol `typing.Required` (which can also be found as `typing_extensions.Required`) + Required, + /// The symbol `typing.NotRequired` (which can also be found as `typing_extensions.NotRequired`) + NotRequired, + /// The symbol `typing.TypeAlias` (which can also be found as `typing_extensions.TypeAlias`) + TypeAlias, + /// The symbol `typing.TypeGuard` (which can also be found as `typing_extensions.TypeGuard`) + TypeGuard, + /// The symbol `typing.TypedDict` (which can also be found as `typing_extensions.TypedDict`) + TypedDict, + /// The symbol `typing.TypeIs` (which can also be found as `typing_extensions.TypeIs`) + TypeIs, + /// The symbol `typing.ReadOnly` (which can also be found as `typing_extensions.ReadOnly`) + ReadOnly, + + /// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`) + /// + /// Note that instances of subscripted `typing.Protocol` are not represented by this type; + /// see also [`super::KnownInstanceType::SubscriptedProtocol`]. + Protocol, + + /// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`). + /// + /// Note that instances of subscripted `typing.Generic` are not represented by this type; + /// see also [`super::KnownInstanceType::SubscriptedGeneric`]. + Generic, +} + +impl SpecialFormType { + /// Return the [`KnownClass`] which this symbol is an instance of + pub(crate) const fn class(self) -> KnownClass { + match self { + Self::Annotated + | Self::Literal + | Self::LiteralString + | Self::Optional + | Self::Union + | Self::NoReturn + | Self::Never + | Self::Tuple + | Self::Type + | Self::TypingSelf + | Self::Final + | Self::ClassVar + | Self::Callable + | Self::Concatenate + | Self::Unpack + | Self::Required + | Self::NotRequired + | Self::TypeAlias + | Self::TypeGuard + | Self::TypedDict + | Self::TypeIs + | Self::TypeOf + | Self::Not + | Self::Intersection + | Self::CallableTypeOf + | Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says + | Self::Generic // actually `type` at runtime but this is what typeshed says + | Self::ReadOnly => KnownClass::SpecialForm, + + Self::List + | Self::Dict + | Self::DefaultDict + | Self::Set + | Self::FrozenSet + | Self::Counter + | Self::Deque + | Self::ChainMap + | Self::OrderedDict => KnownClass::StdlibAlias, + + Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy => KnownClass::Object, + } + } + + /// Return the instance type which this type is a subtype of. + /// + /// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`, + /// so `SpecialFormType::Literal.instance_fallback(db)` + /// returns `Type::NominalInstance(NominalInstanceType { class: })`. + pub(super) fn instance_fallback(self, db: &dyn Db) -> Type { + self.class().to_instance(db) + } + + /// Return `true` if this symbol is an instance of `class`. + pub(super) fn is_instance_of(self, db: &dyn Db, class: ClassType) -> bool { + self.class().is_subclass_of(db, class) + } + + pub(super) fn try_from_file_and_name( + db: &dyn Db, + file: File, + symbol_name: &str, + ) -> Option { + let candidate = Self::from_str(symbol_name).ok()?; + candidate + .check_module(file_to_module(db, file)?.known()?) + .then_some(candidate) + } + + /// Return `true` if `module` is a module from which this `SpecialFormType` variant can validly originate. + /// + /// Most variants can only exist in one module, which is the same as `self.class().canonical_module(db)`. + /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. + pub(super) fn check_module(self, module: KnownModule) -> bool { + match self { + Self::ClassVar + | Self::Deque + | Self::List + | Self::Dict + | Self::DefaultDict + | Self::Set + | Self::FrozenSet + | Self::Counter + | Self::ChainMap + | Self::OrderedDict + | Self::Optional + | Self::Union + | Self::NoReturn + | Self::Tuple + | Self::Type + | Self::Generic + | Self::Callable => module.is_typing(), + + Self::Annotated + | Self::Literal + | Self::LiteralString + | Self::Never + | Self::Final + | Self::Concatenate + | Self::Unpack + | Self::Required + | Self::NotRequired + | Self::TypeAlias + | Self::TypeGuard + | Self::TypedDict + | Self::TypeIs + | Self::Protocol + | Self::ReadOnly => { + matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) + } + + Self::TypingSelf => matches!( + module, + KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed + ), + + Self::Unknown + | Self::AlwaysTruthy + | Self::AlwaysFalsy + | Self::Not + | Self::Intersection + | Self::TypeOf + | Self::CallableTypeOf => module.is_ty_extensions(), + } + } + + pub(super) fn to_meta_type(self, db: &dyn Db) -> Type { + self.class().to_class_literal(db) + } + + /// Return the repr of the symbol at runtime + pub(super) const fn repr(self) -> &'static str { + match self { + SpecialFormType::Annotated => "typing.Annotated", + SpecialFormType::Literal => "typing.Literal", + SpecialFormType::LiteralString => "typing.LiteralString", + SpecialFormType::Optional => "typing.Optional", + SpecialFormType::Union => "typing.Union", + SpecialFormType::NoReturn => "typing.NoReturn", + SpecialFormType::Never => "typing.Never", + SpecialFormType::Tuple => "typing.Tuple", + SpecialFormType::Type => "typing.Type", + SpecialFormType::TypingSelf => "typing.Self", + SpecialFormType::Final => "typing.Final", + SpecialFormType::ClassVar => "typing.ClassVar", + SpecialFormType::Callable => "typing.Callable", + SpecialFormType::Concatenate => "typing.Concatenate", + SpecialFormType::Unpack => "typing.Unpack", + SpecialFormType::Required => "typing.Required", + SpecialFormType::NotRequired => "typing.NotRequired", + SpecialFormType::TypeAlias => "typing.TypeAlias", + SpecialFormType::TypeGuard => "typing.TypeGuard", + SpecialFormType::TypedDict => "typing.TypedDict", + SpecialFormType::TypeIs => "typing.TypeIs", + SpecialFormType::List => "typing.List", + SpecialFormType::Dict => "typing.Dict", + SpecialFormType::DefaultDict => "typing.DefaultDict", + SpecialFormType::Set => "typing.Set", + SpecialFormType::FrozenSet => "typing.FrozenSet", + SpecialFormType::Counter => "typing.Counter", + SpecialFormType::Deque => "typing.Deque", + SpecialFormType::ChainMap => "typing.ChainMap", + SpecialFormType::OrderedDict => "typing.OrderedDict", + SpecialFormType::ReadOnly => "typing.ReadOnly", + SpecialFormType::Unknown => "ty_extensions.Unknown", + SpecialFormType::AlwaysTruthy => "ty_extensions.AlwaysTruthy", + SpecialFormType::AlwaysFalsy => "ty_extensions.AlwaysFalsy", + SpecialFormType::Not => "ty_extensions.Not", + SpecialFormType::Intersection => "ty_extensions.Intersection", + SpecialFormType::TypeOf => "ty_extensions.TypeOf", + SpecialFormType::CallableTypeOf => "ty_extensions.CallableTypeOf", + SpecialFormType::Protocol => "typing.Protocol", + SpecialFormType::Generic => "typing.Generic", + } + } +} + +impl std::fmt::Display for SpecialFormType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.repr()) + } +} diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index cb2b309f63d193..669d116fd9fc89 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -179,10 +179,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::BoundSuper(_), _) => Ordering::Less, (_, Type::BoundSuper(_)) => Ordering::Greater, - (Type::KnownInstance(left_instance), Type::KnownInstance(right_instance)) => { - left_instance.cmp(right_instance) - } + (Type::SpecialForm(left), Type::SpecialForm(right)) => left.cmp(right), + (Type::SpecialForm(_), _) => Ordering::Less, + (_, Type::SpecialForm(_)) => Ordering::Greater, + (Type::KnownInstance(left), Type::KnownInstance(right)) => left.cmp(right), (Type::KnownInstance(_), _) => Ordering::Less, (_, Type::KnownInstance(_)) => Ordering::Greater, From a827b16ebdeffe20869bb7ee1f430c9878f266f8 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Wed, 21 May 2025 12:53:18 -0400 Subject: [PATCH 273/487] ruff_python_parser: add `Tokens::before` method This is analogous to the existing `Tokens::after` method. Its implementation is almost identical. We plan to use this for looking at the tokens immediately before the cursor when fetching completions. --- crates/ruff_python_parser/src/lib.rs | 100 ++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/crates/ruff_python_parser/src/lib.rs b/crates/ruff_python_parser/src/lib.rs index eae7ea75ab8414..fa5230a01677fc 100644 --- a/crates/ruff_python_parser/src/lib.rs +++ b/crates/ruff_python_parser/src/lib.rs @@ -637,6 +637,41 @@ impl Tokens { } } + /// Returns a slice of tokens before the given [`TextSize`] offset. + /// + /// If the given offset is between two tokens, the returned slice will end just before the + /// following token. In other words, if the offset is between the end of previous token and + /// start of next token, the returned slice will end just before the next token. + /// + /// # Panics + /// + /// If the given offset is inside a token range at any point + /// other than the start of the range. + pub fn before(&self, offset: TextSize) -> &[Token] { + match self.binary_search_by(|token| token.start().cmp(&offset)) { + Ok(idx) => &self[..idx], + Err(idx) => { + // We can't use `saturating_sub` here because a file could contain a BOM header, in + // which case the token starts at offset 3 for UTF-8 encoded file content. + if idx > 0 { + if let Some(prev) = self.get(idx - 1) { + // If it's equal to the end offset, then it's at a token boundary which is + // valid. If it's greater than the end offset, then it's in the gap between + // the tokens which is valid as well. + assert!( + offset >= prev.end(), + "Offset {:?} is inside a token range {:?}", + offset, + prev.range() + ); + } + } + + &self[..idx] + } + } + } + /// Returns a slice of tokens after the given [`TextSize`] offset. /// /// If the given offset is between two tokens, the returned slice will start from the following @@ -645,7 +680,8 @@ impl Tokens { /// /// # Panics /// - /// If the given offset is inside a token range. + /// If the given offset is inside a token range at any point + /// other than the start of the range. pub fn after(&self, offset: TextSize) -> &[Token] { match self.binary_search_by(|token| token.start().cmp(&offset)) { Ok(idx) => &self[idx..], @@ -947,6 +983,68 @@ mod tests { tokens.after(TextSize::new(5)); } + #[test] + fn tokens_before_offset_at_first_token_start() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(0)); + assert_eq!(before.len(), 0); + } + + #[test] + fn tokens_before_offset_after_first_token_gap() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(3)); + assert_eq!(before.len(), 1); + assert_eq!(before.last().unwrap().kind(), TokenKind::Def); + } + + #[test] + fn tokens_before_offset_at_second_token_start() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(4)); + assert_eq!(before.len(), 1); + assert_eq!(before.last().unwrap().kind(), TokenKind::Def); + } + + #[test] + fn tokens_before_offset_at_token_start() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(8)); + assert_eq!(before.len(), 3); + assert_eq!(before.last().unwrap().kind(), TokenKind::Lpar); + } + + #[test] + fn tokens_before_offset_at_token_end() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(11)); + assert_eq!(before.len(), 6); + assert_eq!(before.last().unwrap().kind(), TokenKind::Newline); + } + + #[test] + fn tokens_before_offset_between_tokens() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(13)); + assert_eq!(before.len(), 6); + assert_eq!(before.last().unwrap().kind(), TokenKind::Newline); + } + + #[test] + fn tokens_before_offset_at_last_token_end() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + let before = tokens.before(TextSize::new(33)); + assert_eq!(before.len(), 10); + assert_eq!(before.last().unwrap().kind(), TokenKind::Pass); + } + + #[test] + #[should_panic(expected = "Offset 5 is inside a token range 4..7")] + fn tokens_before_offset_inside_token() { + let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); + tokens.before(TextSize::new(5)); + } + #[test] fn tokens_in_range_at_token_offset() { let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter()); From 33ed502edbee14cc578e7def9dd0a6494fc589fb Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 22 May 2025 08:56:41 -0400 Subject: [PATCH 274/487] ty_ide: improve completions by using scopes Previously, completions were based on just returning every identifier parsed in the current Python file. In this commit, we change it to identify an expression under the cursor and then return all symbols available to the scope containing that expression. This is still returning too much, and also, in some cases, not enough. Namely, it doesn't really take the specific context into account other than scope. But this does improve on the status quo. For example: def foo(): ... def bar(): def fast(): ... def foofoo(): ... f When asking for completions here, the LSP will no longer include `fast` as a possible completion in this context. Ref https://github.com/astral-sh/ty/issues/86 --- crates/ruff_python_ast/src/name.rs | 7 + crates/ty_ide/src/completion.rs | 879 +++++++++++++++++- .../ty_python_semantic/src/semantic_model.rs | 29 +- 3 files changed, 893 insertions(+), 22 deletions(-) diff --git a/crates/ruff_python_ast/src/name.rs b/crates/ruff_python_ast/src/name.rs index f57f4ebdf7ed3c..725576a2493588 100644 --- a/crates/ruff_python_ast/src/name.rs +++ b/crates/ruff_python_ast/src/name.rs @@ -111,6 +111,13 @@ impl From for compact_str::CompactString { } } +impl From for String { + #[inline] + fn from(name: Name) -> Self { + name.as_str().into() + } +} + impl FromIterator for Name { fn from_iter>(iter: I) -> Self { Self(iter.into_iter().collect()) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 2082b14a4d64e5..ab8b6e7cf0c84b 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1,40 +1,877 @@ use ruff_db::files::File; -use ruff_db::parsed::parsed_module; -use ruff_python_ast::visitor::source_order::SourceOrderVisitor; -use ruff_python_ast::{AnyNodeRef, Identifier}; -use ruff_text_size::TextSize; +use ruff_db::parsed::{ParsedModule, parsed_module}; +use ruff_python_parser::TokenAt; +use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Db; +use crate::find_node::{CoveringNode, covering_node}; #[derive(Debug, Clone)] pub struct Completion { pub label: String, } -pub fn completion(db: &dyn Db, file: File, _offset: TextSize) -> Vec { +pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec { let parsed = parsed_module(db.upcast(), file); - identifiers(parsed.syntax().into()) + + let Some(target) = find_target(parsed, offset) else { + return vec![]; + }; + + let model = ty_python_semantic::SemanticModel::new(db.upcast(), file); + let mut completions = model.completions(target.node()); + completions.sort(); + completions.dedup(); + completions .into_iter() - .map(|label| Completion { label }) + .map(|name| Completion { label: name.into() }) .collect() } -fn identifiers(node: AnyNodeRef) -> Vec { - struct Visitor { - identifiers: Vec, +fn find_target(parsed: &ParsedModule, offset: TextSize) -> Option { + let offset = match parsed.tokens().at_offset(offset) { + TokenAt::None => { + return Some(covering_node( + parsed.syntax().into(), + TextRange::empty(offset), + )); + } + TokenAt::Single(tok) => tok.end(), + TokenAt::Between(_, tok) => tok.start(), + }; + let before = parsed.tokens().before(offset); + let last = before.last()?; + let covering_node = covering_node(parsed.syntax().into(), last.range()); + Some(covering_node) +} + +#[cfg(test)] +mod tests { + use insta::assert_snapshot; + + use crate::completion; + use crate::tests::{CursorTest, cursor_test}; + + // At time of writing (2025-05-22), the tests below show some of the + // naivete of our completions. That is, we don't even take what has been + // typed into account. We just kind return all possible completions + // regardless of what has been typed and rely on the client to do filtering + // based on prefixes and what not. + // + // In the future, we might consider using "text edits,"[1] which will let + // us have more control over which completions are shown to the end user. + // But that will require us to at least do some kind of filtering based on + // what has been typed. + // + // [1]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion + + #[test] + fn empty() { + let test = cursor_test( + "\ + +", + ); + + assert_snapshot!(test.completions(), @""); } - impl<'a> SourceOrderVisitor<'a> for Visitor { - fn visit_identifier(&mut self, id: &'a Identifier) { - self.identifiers.push(id.id.as_str().to_string()); - } + #[test] + fn imports1() { + let test = cursor_test( + "\ +import re + + +", + ); + + assert_snapshot!(test.completions(), @"re"); } - let mut visitor = Visitor { - identifiers: vec![], - }; - node.visit_source_order(&mut visitor); - visitor.identifiers.sort(); - visitor.identifiers.dedup(); - visitor.identifiers + #[test] + fn imports2() { + let test = cursor_test( + "\ +from os import path + + +", + ); + + assert_snapshot!(test.completions(), @"path"); + } + + // N.B. We don't currently explore module APIs. This + // is still just emitting symbols from the detected scope. + #[test] + fn module_api() { + let test = cursor_test( + "\ +import re + +re. +", + ); + + assert_snapshot!(test.completions(), @"re"); + } + + #[test] + fn one_function_prefix() { + let test = cursor_test( + "\ +def foo(): ... + +f +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn one_function_not_prefix() { + let test = cursor_test( + "\ +def foo(): ... + +g +", + ); + + assert_snapshot!(test.completions(), @r" + foo + g + "); + } + + #[test] + fn one_function_blank() { + let test = cursor_test( + "\ +def foo(): ... + + +", + ); + + assert_snapshot!(test.completions(), @r" + foo + "); + } + + #[test] + fn nested_function_prefix() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): ... + +f +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn nested_function_blank() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): ... + + +", + ); + + assert_snapshot!(test.completions(), @r" + foo + "); + } + + #[test] + fn nested_function_not_in_global_scope_prefix() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): ... + f +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + foofoo + "); + } + + #[test] + fn nested_function_not_in_global_scope_blank() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): ... + +", + ); + + // FIXME: Should include `foofoo`. + // + // `foofoo` isn't included at present (2025-05-22). The problem + // here is that the AST for `def foo():` doesn't encompass the + // trailing indentation. So when the cursor position is in that + // trailing indentation, we can't (easily) get a handle to the + // right scope. And even if we could, the AST expressions for + // `def foo():` and `def foofoo(): ...` end at precisely the + // same point. So there is no AST we can hold after the end of + // `foofoo` but before the end of `foo`. So at the moment, it's + // not totally clear how to get the right scope. + // + // If we didn't want to change the ranges on the AST nodes, + // another approach here would be to get the inner most scope, + // and explore its ancestors until we get to a level that + // matches the current cursor's indentation. This seems fraught + // however. It's not clear to me that we can always assume a + // correspondence between scopes and indentation level. + assert_snapshot!(test.completions(), @r" + foo + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_prefix1() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + f +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + foofoo + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_prefix2() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + f", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + foofoo + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_prefix3() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + f +def frob(): ... +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + foofoo + frob + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_prefix4() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... +f +def frob(): ... +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + frob + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_prefix5() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + f +def frob(): ... +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + foofoo + foofoofoo + frob + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_blank1() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + +", + ); + + // FIXME: Should include `foofoo` (but not `foofoofoo`). + // + // The tests below fail for the same reason that + // `nested_function_not_in_global_scope_blank` fails: there is no + // space in the AST ranges after the end of `foofoofoo` but before + // the end of `foofoo`. So either the AST needs to be tweaked to + // account for the indented whitespace, or some other technique + // needs to be used to get the scope containing `foofoo` but not + // `foofoofoo`. + assert_snapshot!(test.completions(), @r" + foo + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_blank2() { + let test = cursor_test( + " \ +def foo(): + def foofoo(): + def foofoofoo(): ... + ", + ); + + // FIXME: Should include `foofoo` (but not `foofoofoo`). + assert_snapshot!(test.completions(), @r" + foo + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_blank3() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + +def frob(): ... + ", + ); + + // FIXME: Should include `foofoo` (but not `foofoofoo`). + assert_snapshot!(test.completions(), @r" + foo + frob + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_blank4() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + + +def frob(): ... +", + ); + + // FIXME: Should include `foofoo` (but not `foofoofoo`). + assert_snapshot!(test.completions(), @r" + foo + frob + "); + } + + #[test] + fn double_nested_function_not_in_global_scope_blank5() { + let test = cursor_test( + "\ +def foo(): + def foofoo(): + def foofoofoo(): ... + + + +def frob(): ... +", + ); + + // FIXME: Should include `foofoo` (but not `foofoofoo`). + assert_snapshot!(test.completions(), @r" + foo + frob + "); + } + + #[test] + fn list_comprehension1() { + let test = cursor_test( + "\ +[ for bar in [1, 2, 3]] +", + ); + + // It's not totally clear why `for` shows up in the + // symbol tables of the detected scopes here. My guess + // is that there's perhaps some sub-optimal behavior + // here because the list comprehension as written is not + // valid. + assert_snapshot!(test.completions(), @r" + bar + for + "); + } + + #[test] + fn list_comprehension2() { + let test = cursor_test( + "\ +[f for foo in [1, 2, 3]] +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn lambda_prefix1() { + let test = cursor_test( + "\ +(lambda foo: (1 + f + 2))(2) +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn lambda_prefix2() { + let test = cursor_test( + "\ +(lambda foo: f + 1)(2) +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn lambda_prefix3() { + let test = cursor_test( + "\ +(lambda foo: (f + 1))(2) +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn lambda_prefix4() { + let test = cursor_test( + "\ +(lambda foo: 1 + f)(2) +", + ); + + assert_snapshot!(test.completions(), @r" + f + foo + "); + } + + #[test] + fn lambda_blank1() { + let test = cursor_test( + "\ +(lambda foo: 1 + + 2)(2) +", + ); + + assert_snapshot!(test.completions(), @"foo"); + } + + #[test] + fn lambda_blank2() { + let test = cursor_test( + "\ +(lambda foo: + 1)(2) +", + ); + + // FIXME: Should include `foo`. + // + // These fails for similar reasons as above: the body of the + // lambda doesn't include the position of because + // is inside leading or trailing whitespace. (Even + // when enclosed in parentheses. Specifically, parentheses + // aren't part of the node's range unless it's relevant e.g., + // tuples.) + // + // The `lambda_blank1` test works because there are expressions + // on either side of . + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn lambda_blank3() { + let test = cursor_test( + "\ +(lambda foo: ( + 1))(2) +", + ); + + // FIXME: Should include `foo`. + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn lambda_blank4() { + let test = cursor_test( + "\ +(lambda foo: 1 + )(2) +", + ); + + // FIXME: Should include `foo`. + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn class_prefix1() { + let test = cursor_test( + "\ +class Foo: + bar = 1 + quux = b + frob = 3 +", + ); + + assert_snapshot!(test.completions(), @r" + Foo + b + bar + frob + quux + "); + } + + #[test] + fn class_prefix2() { + let test = cursor_test( + "\ +class Foo: + bar = 1 + quux = b +", + ); + + assert_snapshot!(test.completions(), @r" + Foo + b + bar + quux + "); + } + + #[test] + fn class_blank1() { + let test = cursor_test( + "\ +class Foo: + bar = 1 + quux = + frob = 3 +", + ); + + // FIXME: Should include `bar`, `quux` and `frob`. + // (Unclear if `Foo` should be included, but a false + // positive isn't the end of the world.) + // + // These don't work for similar reasons as other + // tests above with the inside of whitespace. + assert_snapshot!(test.completions(), @r" + Foo + "); + } + + #[test] + fn class_blank2() { + let test = cursor_test( + "\ +class Foo: + bar = 1 + quux = + frob = 3 +", + ); + + // FIXME: Should include `bar`, `quux` and `frob`. + // (Unclear if `Foo` should be included, but a false + // positive isn't the end of the world.) + assert_snapshot!(test.completions(), @r" + Foo + "); + } + + #[test] + fn class_super1() { + let test = cursor_test( + "\ +class Bar: ... + +class Foo(): + bar = 1 +", + ); + + assert_snapshot!(test.completions(), @r" + Bar + Foo + "); + } + + #[test] + fn class_super2() { + let test = cursor_test( + "\ +class Foo(): + bar = 1 + +class Bar: ... +", + ); + + assert_snapshot!(test.completions(), @r" + Bar + Foo + "); + } + + #[test] + fn class_super3() { + let test = cursor_test( + "\ +class Foo( + bar = 1 + +class Bar: ... +", + ); + + assert_snapshot!(test.completions(), @r" + Bar + Foo + "); + } + + #[test] + fn class_super4() { + let test = cursor_test( + "\ +class Bar: ... + +class Foo(", + ); + + assert_snapshot!(test.completions(), @r" + Bar + Foo + "); + } + + // We don't yet take function parameters into account. + #[test] + fn call_prefix1() { + let test = cursor_test( + "\ +def bar(okay=None): ... + +foo = 1 + +bar(o +", + ); + + assert_snapshot!(test.completions(), @r" + bar + foo + o + "); + } + + #[test] + fn call_blank1() { + let test = cursor_test( + "\ +def bar(okay=None): ... + +foo = 1 + +bar( +", + ); + + assert_snapshot!(test.completions(), @r" + bar + foo + "); + } + + #[test] + fn duplicate1() { + let test = cursor_test( + "\ +def foo(): ... + +class C: + def foo(self): ... + def bar(self): + f +", + ); + + assert_snapshot!(test.completions(), @r" + C + bar + f + foo + self + "); + } + + #[test] + fn instance_methods_are_not_regular_functions1() { + let test = cursor_test( + "\ +class C: + def foo(self): ... + + +", + ); + + assert_snapshot!(test.completions(), @"C"); + } + + #[test] + fn instance_methods_are_not_regular_functions2() { + let test = cursor_test( + "\ +class C: + def foo(self): ... + def bar(self): + f +", + ); + + // FIXME: Should NOT include `foo` here, since + // that is only a method that can be called on + // `self`. + assert_snapshot!(test.completions(), @r" + C + bar + f + foo + self + "); + } + + #[test] + fn identifier_keyword_clash1() { + let test = cursor_test( + "\ +classy_variable_name = 1 + +class +", + ); + + assert_snapshot!(test.completions(), @"classy_variable_name"); + } + + #[test] + fn identifier_keyword_clash2() { + let test = cursor_test( + "\ +some_symbol = 1 + +print(f\"{some +", + ); + + assert_snapshot!(test.completions(), @r" + print + some + some_symbol + "); + } + + impl CursorTest { + fn completions(&self) -> String { + let completions = completion(&self.db, self.file, self.cursor_offset); + if completions.is_empty() { + return "".to_string(); + } + completions + .into_iter() + .map(|completion| completion.label) + .collect::>() + .join("\n") + } + } } diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index eaa9d13131fcc8..9ed888e603e004 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -1,7 +1,7 @@ use ruff_db::files::{File, FilePath}; use ruff_db::source::line_index; use ruff_python_ast as ast; -use ruff_python_ast::{Expr, ExprRef}; +use ruff_python_ast::{Expr, ExprRef, name::Name}; use ruff_source_file::LineIndex; use crate::Db; @@ -9,6 +9,7 @@ use crate::module_name::ModuleName; use crate::module_resolver::{Module, resolve_module}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::semantic_index; +use crate::semantic_index::symbol::FileScopeId; use crate::types::{Type, binding_type, infer_scope_types}; pub struct SemanticModel<'db> { @@ -38,6 +39,32 @@ impl<'db> SemanticModel<'db> { pub fn resolve_module(&self, module_name: &ModuleName) -> Option { resolve_module(self.db, module_name) } + + /// Returns completions for symbols available in the scope containing the + /// given expression. + /// + /// If a scope could not be determined, then completions for the global + /// scope of this model's `File` are returned. + pub fn completions(&self, node: ast::AnyNodeRef<'_>) -> Vec { + let index = semantic_index(self.db, self.file); + let file_scope = match node { + ast::AnyNodeRef::Identifier(identifier) => index.expression_scope_id(identifier), + node => match node.as_expr_ref() { + // If we couldn't identify a specific + // expression that we're in, then just + // fall back to the global scope. + None => FileScopeId::global(), + Some(expr) => index.expression_scope_id(expr), + }, + }; + let mut symbols = vec![]; + for (file_scope, _) in index.ancestor_scopes(file_scope) { + for symbol in index.symbol_table(file_scope).symbols() { + symbols.push(symbol.name().clone()); + } + } + symbols + } } pub trait HasType { From 7df79cfb70c9865784ea2841298ec8ab21b3566a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 29 May 2025 16:08:15 +0100 Subject: [PATCH 275/487] Add `offset` method to `ruff_python_trivia::Cursor` (#18371) --- crates/ruff_python_trivia/src/cursor.rs | 5 +++++ crates/ty_test/src/assertion.rs | 24 +++++++++++------------- crates/ty_test/src/parser.rs | 25 +++++++++---------------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/crates/ruff_python_trivia/src/cursor.rs b/crates/ruff_python_trivia/src/cursor.rs index 8ab9c37d41f4bf..a2c7e17f2b1a14 100644 --- a/crates/ruff_python_trivia/src/cursor.rs +++ b/crates/ruff_python_trivia/src/cursor.rs @@ -21,6 +21,11 @@ impl<'a> Cursor<'a> { } } + /// Retrieves the current offset of the cursor within the source code. + pub fn offset(&self) -> TextSize { + self.source_length - self.text_len() + } + /// Return the remaining input as a string slice. pub fn chars(&self) -> Chars<'a> { self.chars.clone() diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index 2e9bfd338ceb03..8df0b1caf2bf8a 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -40,7 +40,7 @@ use ruff_db::parsed::parsed_module; use ruff_db::source::{SourceText, line_index, source_text}; use ruff_python_trivia::{CommentRanges, Cursor}; use ruff_source_file::{LineIndex, OneIndexed}; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use ruff_text_size::{Ranged, TextRange, TextSize}; use smallvec::SmallVec; use std::ops::Deref; use std::str::FromStr; @@ -360,11 +360,6 @@ impl<'a> ErrorAssertionParser<'a> { } } - /// Retrieves the current offset of the cursor within the source code. - fn offset(&self) -> TextSize { - self.comment_source.text_len() - self.cursor.text_len() - } - /// Consume characters in the assertion comment until we find a non-whitespace character fn skip_whitespace(&mut self) { self.cursor.eat_while(char::is_whitespace); @@ -387,9 +382,10 @@ impl<'a> ErrorAssertionParser<'a> { if rule.is_some() { return Err(ErrorAssertionParseError::ColumnNumberAfterRuleCode); } - let offset = self.offset() - TextSize::new(1); + let offset = self.cursor.offset() - TextSize::new(1); self.cursor.eat_while(|c| !c.is_whitespace()); - let column_str = &self.comment_source[TextRange::new(offset, self.offset())]; + let column_str = + &self.comment_source[TextRange::new(offset, self.cursor.offset())]; column = OneIndexed::from_str(column_str) .map(Some) .map_err(|e| ErrorAssertionParseError::BadColumnNumber(column_str, e))?; @@ -400,12 +396,14 @@ impl<'a> ErrorAssertionParser<'a> { if rule.is_some() { return Err(ErrorAssertionParseError::MultipleRuleCodes); } - let offset = self.offset(); + let offset = self.cursor.offset(); self.cursor.eat_while(|c| c != ']'); if self.cursor.is_eof() { return Err(ErrorAssertionParseError::UnclosedRuleCode); } - rule = Some(self.comment_source[TextRange::new(offset, self.offset())].trim()); + rule = Some( + self.comment_source[TextRange::new(offset, self.cursor.offset())].trim(), + ); self.cursor.bump(); } @@ -413,8 +411,8 @@ impl<'a> ErrorAssertionParser<'a> { '"' => { let comment_source = self.comment_source.trim(); return if comment_source.ends_with('"') { - let rest = - &comment_source[self.offset().to_usize()..comment_source.len() - 1]; + let rest = &comment_source + [self.cursor.offset().to_usize()..comment_source.len() - 1]; Ok(ErrorAssertion { rule, column, @@ -434,7 +432,7 @@ impl<'a> ErrorAssertionParser<'a> { unexpected => { return Err(ErrorAssertionParseError::UnexpectedCharacter { character: unexpected, - offset: self.offset().to_usize(), + offset: self.cursor.offset().to_usize(), }); } } diff --git a/crates/ty_test/src/parser.rs b/crates/ty_test/src/parser.rs index 805ce0d409ac61..b1cc448beb8ef0 100644 --- a/crates/ty_test/src/parser.rs +++ b/crates/ty_test/src/parser.rs @@ -409,7 +409,6 @@ struct Parser<'s> { explicit_path: Option<&'s str>, source: &'s str, - source_len: TextSize, /// Stack of ancestor sections. stack: SectionStack, @@ -438,7 +437,6 @@ impl<'s> Parser<'s> { cursor: Cursor::new(source), preceding_blank_lines: 0, explicit_path: None, - source_len: source.text_len(), stack: SectionStack::new(root_section_id), current_section_files: FxHashMap::default(), current_section_has_config: false, @@ -460,7 +458,7 @@ impl<'s> Parser<'s> { } } - fn skip_whitespace(&mut self) { + fn skip_non_newline_whitespace(&mut self) { self.cursor.eat_while(|c| c.is_whitespace() && c != '\n'); } @@ -474,11 +472,11 @@ impl<'s> Parser<'s> { } fn consume_until(&mut self, mut end_predicate: impl FnMut(char) -> bool) -> Option<&'s str> { - let start = self.offset().to_usize(); + let start = self.cursor.offset().to_usize(); while !self.cursor.is_eof() { if end_predicate(self.cursor.first()) { - return Some(&self.source[start..self.offset().to_usize()]); + return Some(&self.source[start..self.cursor.offset().to_usize()]); } self.cursor.bump(); } @@ -537,7 +535,7 @@ impl<'s> Parser<'s> { if self.cursor.eat_char2('`', '`') { // We saw the triple-backtick beginning of a code block. - let backtick_offset_start = self.offset() - "```".text_len(); + let backtick_offset_start = self.cursor.offset() - "```".text_len(); if self.preceding_blank_lines < 1 && self.explicit_path.is_none() { bail!( @@ -545,14 +543,14 @@ impl<'s> Parser<'s> { ); } - self.skip_whitespace(); + self.skip_non_newline_whitespace(); // Parse the code block language specifier let lang = self .consume_until(|c| matches!(c, ' ' | '\n')) .unwrap_or_default(); - self.skip_whitespace(); + self.skip_non_newline_whitespace(); if !self.cursor.eat_char('\n') { bail!( @@ -570,7 +568,7 @@ impl<'s> Parser<'s> { code = &code[..code.len() - '\n'.len_utf8()]; } - let backtick_offset_end = self.offset() - "```".text_len(); + let backtick_offset_end = self.cursor.offset() - "```".text_len(); self.process_code_block( lang, @@ -590,7 +588,7 @@ impl<'s> Parser<'s> { if let Some(path) = self.consume_until(|c| matches!(c, '`' | '\n')) { if self.cursor.eat_char('`') { - self.skip_whitespace(); + self.skip_non_newline_whitespace(); if self.cursor.eat_char(':') { self.explicit_path = Some(path); } @@ -609,7 +607,7 @@ impl<'s> Parser<'s> { self.explicit_path = None; if c.is_whitespace() { - self.skip_whitespace(); + self.skip_non_newline_whitespace(); if self.cursor.eat_char('`') && self.cursor.eat_char('`') && self.cursor.eat_char('`') @@ -821,11 +819,6 @@ impl<'s> Parser<'s> { } } - /// Retrieves the current offset of the cursor within the source code. - fn offset(&self) -> TextSize { - self.source_len - self.cursor.text_len() - } - fn line_index(&self, char_index: TextSize) -> u32 { self.source.count_lines(TextRange::up_to(char_index)) } From 9d3cad95bca58788e58c03f28dde35b29525a4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20N=C3=A4slund?= Date: Thu, 29 May 2025 20:59:49 +0200 Subject: [PATCH 276/487] [`refurb`] Add coverage of `set` and `frozenset` calls (`FURB171`) (#18035) ## Summary Adds coverage of using set(...) in addition to `{...} in SingleItemMembershipTest. Fixes #15792 (and replaces the old PR #15793) ## Test Plan Updated unit test and snapshot. Steps to reproduce are in the issue linked above. --- .../refurb/{FURB171.py => FURB171_0.py} | 0 .../test/fixtures/refurb/FURB171_1.py | 53 +++++++ crates/ruff_linter/src/rules/refurb/mod.rs | 3 +- .../rules/single_item_membership_test.rs | 26 +++- ..._refurb__tests__FURB171_FURB171_0.py.snap} | 26 ++-- ...__refurb__tests__FURB171_FURB171_1.py.snap | 141 ++++++++++++++++++ 6 files changed, 232 insertions(+), 17 deletions(-) rename crates/ruff_linter/resources/test/fixtures/refurb/{FURB171.py => FURB171_0.py} (100%) create mode 100644 crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py rename crates/ruff_linter/src/rules/refurb/snapshots/{ruff_linter__rules__refurb__tests__FURB171_FURB171.py.snap => ruff_linter__rules__refurb__tests__FURB171_FURB171_0.py.snap} (87%) create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_1.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB171.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB171_0.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/refurb/FURB171.py rename to crates/ruff_linter/resources/test/fixtures/refurb/FURB171_0.py diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py new file mode 100644 index 00000000000000..41109f6cfa6181 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py @@ -0,0 +1,53 @@ +# Errors. + +if 1 in set([1]): + print("Single-element set") + +if 1 in set((1,)): + print("Single-element set") + +if 1 in set({1}): + print("Single-element set") + +if 1 in frozenset([1]): + print("Single-element set") + +if 1 in frozenset((1,)): + print("Single-element set") + +if 1 in frozenset({1}): + print("Single-element set") + +if 1 in set(set([1])): + print('Recursive solution') + + + +# Non-errors. + +if 1 in set((1, 2)): + pass + +if 1 in set([1, 2]): + pass + +if 1 in set({1, 2}): + pass + +if 1 in frozenset((1, 2)): + pass + +if 1 in frozenset([1, 2]): + pass + +if 1 in frozenset({1, 2}): + pass + +if 1 in set(1,): + pass + +if 1 in set(1,2): + pass + +if 1 in set((x for x in range(2))): + pass diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index d841bcafe1cb79..a14531e43aa4af 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -35,7 +35,8 @@ mod tests { #[test_case(Rule::UnnecessaryFromFloat, Path::new("FURB164.py"))] #[test_case(Rule::PrintEmptyString, Path::new("FURB105.py"))] #[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))] - #[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))] + #[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171_0.py"))] + #[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171_1.py"))] #[test_case(Rule::BitCount, Path::new("FURB161.py"))] #[test_case(Rule::IntOnSlicedStr, Path::new("FURB166.py"))] #[test_case(Rule::RegexFlagAlias, Path::new("FURB167.py"))] diff --git a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index 536a5008f8fc96..f0b9930bf29b4e 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs @@ -1,6 +1,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::generate_comparison; use ruff_python_ast::{self as ast, CmpOp, Expr, ExprStringLiteral}; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -78,8 +79,8 @@ pub(crate) fn single_item_membership_test( _ => return, }; - // Check if the right-hand side is a single-item object. - let Some(item) = single_item(right) else { + // Check if the right-hand side is a single-item object + let Some(item) = single_item(right, checker.semantic()) else { return; }; @@ -115,7 +116,7 @@ pub(crate) fn single_item_membership_test( /// Return the single item wrapped in `Some` if the expression contains a single /// item, otherwise return `None`. -fn single_item(expr: &Expr) -> Option<&Expr> { +fn single_item<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a Expr> { match expr { Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) @@ -124,6 +125,19 @@ fn single_item(expr: &Expr) -> Option<&Expr> { [item] => Some(item), _ => None, }, + Expr::Call(ast::ExprCall { + func, + arguments, + range: _, + }) => { + if arguments.len() != 1 || !is_set_method(func, semantic) { + return None; + } + + arguments + .find_positional(0) + .and_then(|arg| single_item(arg, semantic)) + } string_expr @ Expr::StringLiteral(ExprStringLiteral { value: string, .. }) if string.chars().count() == 1 => { @@ -133,6 +147,12 @@ fn single_item(expr: &Expr) -> Option<&Expr> { } } +fn is_set_method(func: &Expr, semantic: &SemanticModel) -> bool { + ["set", "frozenset"] + .iter() + .any(|s| semantic.match_builtin_expr(func, s)) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum MembershipTest { /// Ex) `1 in [1]` diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_0.py.snap similarity index 87% rename from crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171.py.snap rename to crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_0.py.snap index e4af1c04278177..8bc2c8a6afa10a 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_0.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB171.py:3:4: FURB171 [*] Membership test against single-item container +FURB171_0.py:3:4: FURB171 [*] Membership test against single-item container | 1 | # Errors. 2 | @@ -20,7 +20,7 @@ FURB171.py:3:4: FURB171 [*] Membership test against single-item container 5 5 | 6 6 | if 1 in [1]: -FURB171.py:6:4: FURB171 [*] Membership test against single-item container +FURB171_0.py:6:4: FURB171 [*] Membership test against single-item container | 4 | print("Single-element tuple") 5 | @@ -40,7 +40,7 @@ FURB171.py:6:4: FURB171 [*] Membership test against single-item container 8 8 | 9 9 | if 1 in {1}: -FURB171.py:9:4: FURB171 [*] Membership test against single-item container +FURB171_0.py:9:4: FURB171 [*] Membership test against single-item container | 7 | print("Single-element list") 8 | @@ -60,7 +60,7 @@ FURB171.py:9:4: FURB171 [*] Membership test against single-item container 11 11 | 12 12 | if "a" in "a": -FURB171.py:12:4: FURB171 [*] Membership test against single-item container +FURB171_0.py:12:4: FURB171 [*] Membership test against single-item container | 10 | print("Single-element set") 11 | @@ -80,7 +80,7 @@ FURB171.py:12:4: FURB171 [*] Membership test against single-item container 14 14 | 15 15 | if 1 not in (1,): -FURB171.py:15:4: FURB171 [*] Membership test against single-item container +FURB171_0.py:15:4: FURB171 [*] Membership test against single-item container | 13 | print("Single-element string") 14 | @@ -100,7 +100,7 @@ FURB171.py:15:4: FURB171 [*] Membership test against single-item container 17 17 | 18 18 | if not 1 in (1,): -FURB171.py:18:8: FURB171 [*] Membership test against single-item container +FURB171_0.py:18:8: FURB171 [*] Membership test against single-item container | 16 | print("Check `not in` membership test") 17 | @@ -120,7 +120,7 @@ FURB171.py:18:8: FURB171 [*] Membership test against single-item container 20 20 | 21 21 | # Non-errors. -FURB171.py:52:5: FURB171 [*] Membership test against single-item container +FURB171_0.py:52:5: FURB171 [*] Membership test against single-item container | 51 | # https://github.com/astral-sh/ruff/issues/10063 52 | _ = a in ( @@ -147,7 +147,7 @@ FURB171.py:52:5: FURB171 [*] Membership test against single-item container 57 54 | _ = a in ( # Foo1 58 55 | ( # Foo2 -FURB171.py:57:5: FURB171 [*] Membership test against single-item container +FURB171_0.py:57:5: FURB171 [*] Membership test against single-item container | 55 | ) 56 | @@ -199,7 +199,7 @@ FURB171.py:57:5: FURB171 [*] Membership test against single-item container 74 63 | foo = ( 75 64 | lorem() -FURB171.py:77:28: FURB171 [*] Membership test against single-item container +FURB171_0.py:77:28: FURB171 [*] Membership test against single-item container | 75 | lorem() 76 | .ipsum() @@ -228,7 +228,7 @@ FURB171.py:77:28: FURB171 [*] Membership test against single-item container 83 79 | 84 80 | foo = ( -FURB171.py:87:28: FURB171 [*] Membership test against single-item container +FURB171_0.py:87:28: FURB171 [*] Membership test against single-item container | 85 | lorem() 86 | .ipsum() @@ -262,7 +262,7 @@ FURB171.py:87:28: FURB171 [*] Membership test against single-item container 95 93 | 96 94 | foo = lorem() \ -FURB171.py:98:24: FURB171 [*] Membership test against single-item container +FURB171_0.py:98:24: FURB171 [*] Membership test against single-item container | 96 | foo = lorem() \ 97 | .ipsum() \ @@ -292,7 +292,7 @@ FURB171.py:98:24: FURB171 [*] Membership test against single-item container 104 100 | def _(): 105 101 | if foo not \ -FURB171.py:105:8: FURB171 [*] Membership test against single-item container +FURB171_0.py:105:8: FURB171 [*] Membership test against single-item container | 104 | def _(): 105 | if foo not \ @@ -323,7 +323,7 @@ FURB171.py:105:8: FURB171 [*] Membership test against single-item container 112 107 | def _(): 113 108 | if foo not \ -FURB171.py:113:8: FURB171 [*] Membership test against single-item container +FURB171_0.py:113:8: FURB171 [*] Membership test against single-item container | 112 | def _(): 113 | if foo not \ diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_1.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_1.py.snap new file mode 100644 index 00000000000000..01e249adf5ba11 --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB171_FURB171_1.py.snap @@ -0,0 +1,141 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB171_1.py:3:4: FURB171 [*] Membership test against single-item container + | +1 | # Errors. +2 | +3 | if 1 in set([1]): + | ^^^^^^^^^^^^^ FURB171 +4 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +1 1 | # Errors. +2 2 | +3 |-if 1 in set([1]): + 3 |+if 1 == 1: +4 4 | print("Single-element set") +5 5 | +6 6 | if 1 in set((1,)): + +FURB171_1.py:6:4: FURB171 [*] Membership test against single-item container + | +4 | print("Single-element set") +5 | +6 | if 1 in set((1,)): + | ^^^^^^^^^^^^^^ FURB171 +7 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +3 3 | if 1 in set([1]): +4 4 | print("Single-element set") +5 5 | +6 |-if 1 in set((1,)): + 6 |+if 1 == 1: +7 7 | print("Single-element set") +8 8 | +9 9 | if 1 in set({1}): + +FURB171_1.py:9:4: FURB171 [*] Membership test against single-item container + | + 7 | print("Single-element set") + 8 | + 9 | if 1 in set({1}): + | ^^^^^^^^^^^^^ FURB171 +10 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +6 6 | if 1 in set((1,)): +7 7 | print("Single-element set") +8 8 | +9 |-if 1 in set({1}): + 9 |+if 1 == 1: +10 10 | print("Single-element set") +11 11 | +12 12 | if 1 in frozenset([1]): + +FURB171_1.py:12:4: FURB171 [*] Membership test against single-item container + | +10 | print("Single-element set") +11 | +12 | if 1 in frozenset([1]): + | ^^^^^^^^^^^^^^^^^^^ FURB171 +13 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +9 9 | if 1 in set({1}): +10 10 | print("Single-element set") +11 11 | +12 |-if 1 in frozenset([1]): + 12 |+if 1 == 1: +13 13 | print("Single-element set") +14 14 | +15 15 | if 1 in frozenset((1,)): + +FURB171_1.py:15:4: FURB171 [*] Membership test against single-item container + | +13 | print("Single-element set") +14 | +15 | if 1 in frozenset((1,)): + | ^^^^^^^^^^^^^^^^^^^^ FURB171 +16 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +12 12 | if 1 in frozenset([1]): +13 13 | print("Single-element set") +14 14 | +15 |-if 1 in frozenset((1,)): + 15 |+if 1 == 1: +16 16 | print("Single-element set") +17 17 | +18 18 | if 1 in frozenset({1}): + +FURB171_1.py:18:4: FURB171 [*] Membership test against single-item container + | +16 | print("Single-element set") +17 | +18 | if 1 in frozenset({1}): + | ^^^^^^^^^^^^^^^^^^^ FURB171 +19 | print("Single-element set") + | + = help: Convert to equality test + +ℹ Safe fix +15 15 | if 1 in frozenset((1,)): +16 16 | print("Single-element set") +17 17 | +18 |-if 1 in frozenset({1}): + 18 |+if 1 == 1: +19 19 | print("Single-element set") +20 20 | +21 21 | if 1 in set(set([1])): + +FURB171_1.py:21:4: FURB171 [*] Membership test against single-item container + | +19 | print("Single-element set") +20 | +21 | if 1 in set(set([1])): + | ^^^^^^^^^^^^^^^^^^ FURB171 +22 | print('Recursive solution') + | + = help: Convert to equality test + +ℹ Safe fix +18 18 | if 1 in frozenset({1}): +19 19 | print("Single-element set") +20 20 | +21 |-if 1 in set(set([1])): + 21 |+if 1 == 1: +22 22 | print('Recursive solution') +23 23 | +24 24 | From 2c3f091e0e947f7723f63c3cbcd47cfa696dac34 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 29 May 2025 15:04:31 -0400 Subject: [PATCH 277/487] Rename `ruff_linter::Diagnostic` to `OldDiagnostic` (#18355) Summary -- It's a bit late in the refactoring process, but I think there are still a couple of PRs left before getting rid of this type entirely, so I thought it would still be worth doing. This PR is just a quick rename with no other changes. Test Plan -- Existing tests --- crates/ruff/src/commands/check.rs | 4 +- crates/ruff/src/diagnostics.rs | 4 +- crates/ruff_linter/src/checkers/ast/mod.rs | 22 ++++---- crates/ruff_linter/src/checkers/filesystem.rs | 6 +-- crates/ruff_linter/src/checkers/imports.rs | 4 +- .../ruff_linter/src/checkers/logical_lines.rs | 8 +-- crates/ruff_linter/src/checkers/noqa.rs | 8 +-- .../src/checkers/physical_lines.rs | 6 +-- crates/ruff_linter/src/checkers/tokens.rs | 6 +-- crates/ruff_linter/src/diagnostic.rs | 8 +-- crates/ruff_linter/src/fix/edits.rs | 4 +- crates/ruff_linter/src/fix/mod.rs | 4 +- crates/ruff_linter/src/lib.rs | 2 +- crates/ruff_linter/src/linter.rs | 6 +-- crates/ruff_linter/src/message/mod.rs | 10 ++-- crates/ruff_linter/src/noqa.rs | 18 +++---- crates/ruff_linter/src/pyproject_toml.rs | 6 +-- .../eradicate/rules/commented_out_code.rs | 6 +-- .../rules/stdlib_module_shadowing.rs | 6 +-- .../flake8_commas/rules/trailing_commas.rs | 12 ++--- .../rules/missing_copyright_notice.rs | 6 +-- .../src/rules/flake8_executable/rules/mod.rs | 4 +- .../rules/shebang_leading_whitespace.rs | 6 +-- .../rules/shebang_missing_executable_file.rs | 8 +-- .../rules/shebang_missing_python.rs | 6 +-- .../rules/shebang_not_executable.rs | 8 +-- .../rules/shebang_not_first_line.rs | 6 +-- .../src/rules/flake8_fixme/rules/todos.rs | 12 ++--- .../rules/implicit.rs | 8 +-- .../rules/implicit_namespace_package.rs | 8 +-- .../flake8_pyi/rules/type_comment_in_stub.rs | 6 +-- .../src/rules/flake8_todos/rules/todos.rs | 29 ++++++----- .../rules/isort/rules/add_required_imports.rs | 8 +-- .../src/rules/isort/rules/organize_imports.rs | 8 +-- .../pep8_naming/rules/invalid_module_name.rs | 6 +-- .../rules/pycodestyle/rules/blank_lines.rs | 19 +++---- .../pycodestyle/rules/compound_statements.rs | 13 +++-- .../pycodestyle/rules/doc_line_too_long.rs | 6 +-- .../rules/pycodestyle/rules/line_too_long.rs | 6 +-- .../logical_lines/extraneous_whitespace.rs | 16 +++--- .../rules/logical_lines/indentation.rs | 18 +++---- .../rules/logical_lines/missing_whitespace.rs | 4 +- .../missing_whitespace_after_keyword.rs | 4 +- .../missing_whitespace_around_operator.rs | 12 ++--- .../logical_lines/redundant_backslash.rs | 4 +- .../logical_lines/space_around_operator.rs | 18 ++++--- .../whitespace_around_keywords.rs | 10 ++-- ...hitespace_around_named_parameter_equals.rs | 10 ++-- .../whitespace_before_comment.rs | 10 ++-- .../whitespace_before_parameters.rs | 4 +- .../rules/missing_newline_at_end_of_file.rs | 6 +-- .../rules/mixed_spaces_and_tabs.rs | 6 +-- .../pycodestyle/rules/tab_indentation.rs | 6 +-- .../rules/too_many_newlines_at_end_of_file.rs | 10 ++-- .../pycodestyle/rules/trailing_whitespace.rs | 8 +-- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 10 ++-- .../pygrep_hooks/rules/blanket_type_ignore.rs | 6 +-- .../pylint/rules/bidirectional_unicode.rs | 6 +-- .../src/rules/pylint/rules/empty_comment.rs | 8 +-- .../pylint/rules/invalid_string_characters.rs | 14 ++--- .../pyupgrade/rules/extraneous_parentheses.rs | 6 +-- .../rules/unnecessary_coding_comment.rs | 6 +-- .../ruff/rules/ambiguous_unicode_character.rs | 12 ++--- .../rules/ruff/rules/indented_form_feed.rs | 6 +-- .../src/rules/ruff/rules/invalid_rule_code.rs | 12 ++--- .../src/rules/ruff/rules/redirected_noqa.rs | 13 +++-- .../src/rules/ruff/rules/test_rules.rs | 52 +++++++++---------- 67 files changed, 316 insertions(+), 304 deletions(-) diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index 801df01352c2ff..672485b869ae89 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -12,7 +12,7 @@ use rayon::prelude::*; use rustc_hash::FxHashMap; use ruff_db::panic::catch_unwind; -use ruff_linter::Diagnostic; +use ruff_linter::OldDiagnostic; use ruff_linter::message::Message; use ruff_linter::package::PackageRoot; use ruff_linter::registry::Rule; @@ -131,7 +131,7 @@ pub(crate) fn check( Diagnostics::new( vec![Message::from_diagnostic( - Diagnostic::new(IOError { message }, TextRange::default()), + OldDiagnostic::new(IOError { message }, TextRange::default()), dummy, None, )], diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index b656de6a5dd575..7e2de003d31c6a 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -12,7 +12,7 @@ use colored::Colorize; use log::{debug, warn}; use rustc_hash::FxHashMap; -use ruff_linter::Diagnostic; +use ruff_linter::OldDiagnostic; use ruff_linter::codes::Rule; use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only}; use ruff_linter::message::Message; @@ -64,7 +64,7 @@ impl Diagnostics { let source_file = SourceFileBuilder::new(name, "").finish(); Self::new( vec![Message::from_diagnostic( - Diagnostic::new( + OldDiagnostic::new( IOError { message: err.to_string(), }, diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index b95e7538e28748..869fa3a724670d 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -73,7 +73,7 @@ use crate::rules::pyflakes::rules::{ use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration}; use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; use crate::settings::{LinterSettings, TargetVersion, flags}; -use crate::{Diagnostic, Edit, Violation}; +use crate::{Edit, OldDiagnostic, Violation}; use crate::{Locator, docstrings, noqa}; mod analyze; @@ -225,7 +225,7 @@ pub(crate) struct Checker<'a> { /// A set of deferred nodes to be analyzed after the AST traversal (e.g., `for` loops). analyze: deferred::Analyze, /// The cumulative set of diagnostics computed across all lint rules. - diagnostics: RefCell>, + diagnostics: RefCell>, /// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations. flake8_bugbear_seen: RefCell>, /// The end offset of the last visited statement. @@ -391,7 +391,7 @@ impl<'a> Checker<'a> { ) -> DiagnosticGuard<'chk, 'a> { DiagnosticGuard { checker: self, - diagnostic: Some(Diagnostic::new(kind, range)), + diagnostic: Some(OldDiagnostic::new(kind, range)), } } @@ -405,7 +405,7 @@ impl<'a> Checker<'a> { kind: T, range: TextRange, ) -> Option> { - let diagnostic = Diagnostic::new(kind, range); + let diagnostic = OldDiagnostic::new(kind, range); if self.enabled(diagnostic.rule()) { Some(DiagnosticGuard { checker: self, @@ -2892,7 +2892,7 @@ impl<'a> Checker<'a> { if self.semantic.global_scope().uses_star_imports() { if self.enabled(Rule::UndefinedLocalWithImportStarUsage) { self.diagnostics.get_mut().push( - Diagnostic::new( + OldDiagnostic::new( pyflakes::rules::UndefinedLocalWithImportStarUsage { name: name.to_string(), }, @@ -2907,7 +2907,7 @@ impl<'a> Checker<'a> { || !self.path.ends_with("__init__.py") { self.diagnostics.get_mut().push( - Diagnostic::new( + OldDiagnostic::new( pyflakes::rules::UndefinedExport { name: name.to_string(), }, @@ -2975,7 +2975,7 @@ pub(crate) fn check_ast( cell_offsets: Option<&CellOffsets>, notebook_index: Option<&NotebookIndex>, target_version: TargetVersion, -) -> (Vec, Vec) { +) -> (Vec, Vec) { let module_path = package .map(PackageRoot::path) .and_then(|package| to_module_path(package, path)); @@ -3062,7 +3062,7 @@ pub(crate) struct DiagnosticGuard<'a, 'b> { /// The diagnostic that we want to report. /// /// This is always `Some` until the `Drop` (or `defuse`) call. - diagnostic: Option, + diagnostic: Option, } impl DiagnosticGuard<'_, '_> { @@ -3076,9 +3076,9 @@ impl DiagnosticGuard<'_, '_> { } impl std::ops::Deref for DiagnosticGuard<'_, '_> { - type Target = Diagnostic; + type Target = OldDiagnostic; - fn deref(&self) -> &Diagnostic { + fn deref(&self) -> &OldDiagnostic { // OK because `self.diagnostic` is only `None` within `Drop`. self.diagnostic.as_ref().unwrap() } @@ -3086,7 +3086,7 @@ impl std::ops::Deref for DiagnosticGuard<'_, '_> { /// Return a mutable borrow of the diagnostic in this guard. impl std::ops::DerefMut for DiagnosticGuard<'_, '_> { - fn deref_mut(&mut self) -> &mut Diagnostic { + fn deref_mut(&mut self) -> &mut OldDiagnostic { // OK because `self.diagnostic` is only `None` within `Drop`. self.diagnostic.as_mut().unwrap() } diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index ca73348ccafb8c..be09e345dac2e7 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -3,8 +3,8 @@ use std::path::Path; use ruff_python_ast::PythonVersion; use ruff_python_trivia::CommentRanges; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::package::PackageRoot; use crate::preview::is_allow_nested_roots_enabled; use crate::registry::Rule; @@ -20,8 +20,8 @@ pub(crate) fn check_file_path( comment_ranges: &CommentRanges, settings: &LinterSettings, target_version: PythonVersion, -) -> Vec { - let mut diagnostics: Vec = vec![]; +) -> Vec { + let mut diagnostics: Vec = vec![]; // flake8-no-pep420 if settings.rules.enabled(Rule::ImplicitNamespacePackage) { diff --git a/crates/ruff_linter/src/checkers/imports.rs b/crates/ruff_linter/src/checkers/imports.rs index 28628f40f880c7..860061214221a6 100644 --- a/crates/ruff_linter/src/checkers/imports.rs +++ b/crates/ruff_linter/src/checkers/imports.rs @@ -7,8 +7,8 @@ use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Parsed; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::directives::IsortDirectives; use crate::package::PackageRoot; use crate::registry::Rule; @@ -28,7 +28,7 @@ pub(crate) fn check_imports( source_type: PySourceType, cell_offsets: Option<&CellOffsets>, target_version: PythonVersion, -) -> Vec { +) -> Vec { // Extract all import blocks from the AST. let tracker = { let mut tracker = diff --git a/crates/ruff_linter/src/checkers/logical_lines.rs b/crates/ruff_linter/src/checkers/logical_lines.rs index 83eac38858dd26..cabbb1ad8b5ea5 100644 --- a/crates/ruff_linter/src/checkers/logical_lines.rs +++ b/crates/ruff_linter/src/checkers/logical_lines.rs @@ -4,8 +4,8 @@ use ruff_python_parser::{TokenKind, Tokens}; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::line_width::IndentWidth; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle::rules::logical_lines::{ @@ -40,7 +40,7 @@ pub(crate) fn check_logical_lines( indexer: &Indexer, stylist: &Stylist, settings: &LinterSettings, -) -> Vec { +) -> Vec { let mut context = LogicalLinesContext::new(settings); let mut prev_line = None; @@ -196,7 +196,7 @@ pub(crate) fn check_logical_lines( #[derive(Debug, Clone)] pub(crate) struct LogicalLinesContext<'a> { settings: &'a LinterSettings, - diagnostics: Vec, + diagnostics: Vec, } impl<'a> LogicalLinesContext<'a> { @@ -207,7 +207,7 @@ impl<'a> LogicalLinesContext<'a> { } } - pub(crate) fn push_diagnostic(&mut self, diagnostic: Diagnostic) { + pub(crate) fn push_diagnostic(&mut self, diagnostic: OldDiagnostic) { if self.settings.rules.enabled(diagnostic.rule()) { self.diagnostics.push(diagnostic); } diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 35f79a69fedbf1..09d34b25c5f666 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -20,11 +20,11 @@ use crate::rules::pygrep_hooks; use crate::rules::ruff; use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA}; use crate::settings::LinterSettings; -use crate::{Diagnostic, Edit, Fix}; +use crate::{Edit, Fix, OldDiagnostic}; #[expect(clippy::too_many_arguments)] pub(crate) fn check_noqa( - diagnostics: &mut Vec, + diagnostics: &mut Vec, path: &Path, locator: &Locator, comment_ranges: &CommentRanges, @@ -136,7 +136,7 @@ pub(crate) fn check_noqa( if matches.is_empty() { let edit = delete_comment(directive.range(), locator); let mut diagnostic = - Diagnostic::new(UnusedNOQA { codes: None }, directive.range()); + OldDiagnostic::new(UnusedNOQA { codes: None }, directive.range()); diagnostic.set_fix(Fix::safe_edit(edit)); diagnostics.push(diagnostic); @@ -212,7 +212,7 @@ pub(crate) fn check_noqa( directive.range(), ) }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( UnusedNOQA { codes: Some(UnusedCodes { disabled: disabled_codes diff --git a/crates/ruff_linter/src/checkers/physical_lines.rs b/crates/ruff_linter/src/checkers/physical_lines.rs index 0edf20a520bffc..1edfed656a2138 100644 --- a/crates/ruff_linter/src/checkers/physical_lines.rs +++ b/crates/ruff_linter/src/checkers/physical_lines.rs @@ -5,8 +5,8 @@ use ruff_python_index::Indexer; use ruff_source_file::UniversalNewlines; use ruff_text_size::TextSize; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::registry::Rule; use crate::rules::flake8_copyright::rules::missing_copyright_notice; use crate::rules::pycodestyle::rules::{ @@ -23,8 +23,8 @@ pub(crate) fn check_physical_lines( indexer: &Indexer, doc_lines: &[TextSize], settings: &LinterSettings, -) -> Vec { - let mut diagnostics: Vec = vec![]; +) -> Vec { + let mut diagnostics: Vec = vec![]; let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong); let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong); diff --git a/crates/ruff_linter/src/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index 19985985a8923e..0ea5973175d8fc 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -8,8 +8,8 @@ use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Tokens; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::directives::TodoComment; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle::rules::BlankLinesChecker; @@ -29,8 +29,8 @@ pub(crate) fn check_tokens( settings: &LinterSettings, source_type: PySourceType, cell_offsets: Option<&CellOffsets>, -) -> Vec { - let mut diagnostics: Vec = vec![]; +) -> Vec { + let mut diagnostics: Vec = vec![]; let comment_ranges = indexer.comment_ranges(); if settings.rules.any_enabled(&[ diff --git a/crates/ruff_linter/src/diagnostic.rs b/crates/ruff_linter/src/diagnostic.rs index 5bca7eb72e04da..68dc05734b62e6 100644 --- a/crates/ruff_linter/src/diagnostic.rs +++ b/crates/ruff_linter/src/diagnostic.rs @@ -8,7 +8,7 @@ use crate::violation::Violation; use crate::{Fix, codes::Rule}; #[derive(Debug, PartialEq, Eq, Clone)] -pub struct Diagnostic { +pub struct OldDiagnostic { /// The message body to display to the user, to explain the diagnostic. pub body: String, /// The message to display to the user, to explain the suggested fix. @@ -20,7 +20,7 @@ pub struct Diagnostic { pub(crate) rule: Rule, } -impl Diagnostic { +impl OldDiagnostic { // TODO(brent) We temporarily allow this to avoid updating all of the call sites to add // references. I expect this method to go away or change significantly with the rest of the // diagnostic refactor, but if it still exists in this form at the end of the refactor, we @@ -87,13 +87,13 @@ impl Diagnostic { } } -impl AsRule for Diagnostic { +impl AsRule for OldDiagnostic { fn rule(&self) -> Rule { self.rule } } -impl Ranged for Diagnostic { +impl Ranged for OldDiagnostic { fn range(&self) -> TextRange { self.range } diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 4f2866205ea093..fc51bdcebbd8a1 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -607,7 +607,7 @@ mod tests { add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; use crate::message::Message; - use crate::{Diagnostic, Edit, Fix}; + use crate::{Edit, Fix, OldDiagnostic}; /// Parse the given source using [`Mode::Module`] and return the first statement. fn parse_first_stmt(source: &str) -> Result { @@ -738,7 +738,7 @@ x = 1 \ let diag = { use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; let mut iter = edits.into_iter(); - let diag = Diagnostic::new( + let diag = OldDiagnostic::new( MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary. TextRange::default(), ) diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index b61a8b80edde45..261893e4408a35 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -163,7 +163,7 @@ mod tests { use ruff_text_size::{Ranged, TextSize}; use crate::Locator; - use crate::diagnostic::Diagnostic; + use crate::diagnostic::OldDiagnostic; use crate::fix::{FixResult, apply_fixes}; use crate::message::Message; use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; @@ -177,7 +177,7 @@ mod tests { edit.into_iter() .map(|edit| { // The choice of rule here is arbitrary. - let diagnostic = Diagnostic::new(MissingNewlineAtEndOfFile, edit.range()); + let diagnostic = OldDiagnostic::new(MissingNewlineAtEndOfFile, edit.range()); Message::from_diagnostic( diagnostic.with_fix(Fix::safe_edit(edit)), SourceFileBuilder::new(filename, source).finish(), diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index 0988cd36f10ce3..c6f92b22d25787 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -14,7 +14,7 @@ pub use rule_selector::RuleSelector; pub use rule_selector::clap_completion::RuleSelectorParser; pub use rules::pycodestyle::rules::IOError; -pub use diagnostic::Diagnostic; +pub use diagnostic::OldDiagnostic; pub(crate) use ruff_diagnostics::{Applicability, Edit, Fix}; pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata}; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index ca63578b7f0947..cab0b25cbc56f6 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -17,7 +17,7 @@ use ruff_python_parser::{ParseError, ParseOptions, Parsed, UnsupportedSyntaxErro use ruff_source_file::SourceFileBuilder; use ruff_text_size::Ranged; -use crate::Diagnostic; +use crate::OldDiagnostic; use crate::checkers::ast::check_ast; use crate::checkers::filesystem::check_file_path; use crate::checkers::imports::check_imports; @@ -438,7 +438,7 @@ pub fn add_noqa_to_path( ) } -/// Generate a [`Message`] for each [`Diagnostic`] triggered by the given source +/// Generate a [`Message`] for each [`OldDiagnostic`] triggered by the given source /// code. pub fn lint_only( path: &Path, @@ -503,7 +503,7 @@ pub fn lint_only( /// Convert from diagnostics to messages. fn diagnostics_to_messages( - diagnostics: Vec, + diagnostics: Vec, parse_errors: &[ParseError], unsupported_syntax_errors: &[UnsupportedSyntaxError], semantic_syntax_errors: &[SemanticSyntaxError], diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 84e6f20d48bb0a..74eef57f247a54 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -27,7 +27,7 @@ use crate::Locator; use crate::codes::NoqaCode; use crate::logging::DisplayParseErrorType; use crate::registry::Rule; -use crate::{Diagnostic, Fix}; +use crate::{Fix, OldDiagnostic}; mod azure; mod diff; @@ -50,7 +50,7 @@ mod text; /// `noqa` offsets. /// /// For diagnostic messages, the [`db::Diagnostic`]'s primary message contains the -/// [`Diagnostic::body`], and the primary annotation optionally contains the suggestion accompanying +/// [`OldDiagnostic::body`], and the primary annotation optionally contains the suggestion accompanying /// a fix. The `db::Diagnostic::id` field contains the kebab-case lint name derived from the `Rule`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Message { @@ -113,13 +113,13 @@ impl Message { } } - /// Create a [`Message`] from the given [`Diagnostic`] corresponding to a rule violation. + /// Create a [`Message`] from the given [`OldDiagnostic`] corresponding to a rule violation. pub fn from_diagnostic( - diagnostic: Diagnostic, + diagnostic: OldDiagnostic, file: SourceFile, noqa_offset: Option, ) -> Message { - let Diagnostic { + let OldDiagnostic { body, suggestion, range, diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 8b47e574160076..d09aad4fc22a00 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1233,7 +1233,7 @@ mod tests { use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; - use crate::{Diagnostic, Edit}; + use crate::{Edit, OldDiagnostic}; use crate::{Locator, generate_noqa_edits}; fn assert_lexed_ranges_match_slices( @@ -1253,7 +1253,7 @@ mod tests { /// Create a [`Message`] with a placeholder filename and rule code from `diagnostic`. fn message_from_diagnostic( - diagnostic: Diagnostic, + diagnostic: OldDiagnostic, path: impl AsRef, source: &str, ) -> Message { @@ -2842,7 +2842,7 @@ mod tests { assert_eq!(count, 0); assert_eq!(output, format!("{contents}")); - let messages = [Diagnostic::new( + let messages = [OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, @@ -2865,11 +2865,11 @@ mod tests { assert_eq!(output, "x = 1 # noqa: F841\n"); let messages = [ - Diagnostic::new( + OldDiagnostic::new( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), ), - Diagnostic::new( + OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, @@ -2894,11 +2894,11 @@ mod tests { assert_eq!(output, "x = 1 # noqa: E741, F841\n"); let messages = [ - Diagnostic::new( + OldDiagnostic::new( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), ), - Diagnostic::new( + OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, @@ -2936,7 +2936,7 @@ print( ) "#; let noqa_line_for = [TextRange::new(8.into(), 68.into())].into_iter().collect(); - let messages = [Diagnostic::new( + let messages = [OldDiagnostic::new( PrintfStringFormatting, TextRange::new(12.into(), 79.into()), )] @@ -2968,7 +2968,7 @@ print( foo; bar = "; - let messages = [Diagnostic::new( + let messages = [OldDiagnostic::new( UselessSemicolon, TextRange::new(4.into(), 5.into()), )] diff --git a/crates/ruff_linter/src/pyproject_toml.rs b/crates/ruff_linter/src/pyproject_toml.rs index 137089e199888f..d7021615351c0e 100644 --- a/crates/ruff_linter/src/pyproject_toml.rs +++ b/crates/ruff_linter/src/pyproject_toml.rs @@ -5,8 +5,8 @@ use ruff_text_size::{TextRange, TextSize}; use ruff_source_file::SourceFile; -use crate::Diagnostic; use crate::IOError; +use crate::OldDiagnostic; use crate::message::Message; use crate::registry::Rule; use crate::rules::ruff::rules::InvalidPyprojectToml; @@ -29,7 +29,7 @@ pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) - source_file.name(), ); if settings.rules.enabled(Rule::IOError) { - let diagnostic = Diagnostic::new(IOError { message }, TextRange::default()); + let diagnostic = OldDiagnostic::new(IOError { message }, TextRange::default()); messages.push(Message::from_diagnostic(diagnostic, source_file, None)); } else { warn!( @@ -51,7 +51,7 @@ pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) - if settings.rules.enabled(Rule::InvalidPyprojectToml) { let toml_err = err.message().to_string(); - let diagnostic = Diagnostic::new(InvalidPyprojectToml { message: toml_err }, range); + let diagnostic = OldDiagnostic::new(InvalidPyprojectToml { message: toml_err }, range); messages.push(Message::from_diagnostic(diagnostic, source_file, None)); } diff --git a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs index 67b5c125a9f6d6..118b28207bb8db 100644 --- a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs +++ b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs @@ -5,7 +5,7 @@ use ruff_text_size::TextRange; use crate::Locator; use crate::settings::LinterSettings; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; use super::super::detection::comment_contains_code; @@ -47,7 +47,7 @@ impl Violation for CommentedOutCode { /// ERA001 pub(crate) fn commented_out_code( - diagnostics: &mut Vec, + diagnostics: &mut Vec, locator: &Locator, comment_ranges: &CommentRanges, settings: &LinterSettings, @@ -65,7 +65,7 @@ pub(crate) fn commented_out_code( // Verify that the comment is on its own line, and that it contains code. if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) { - let mut diagnostic = Diagnostic::new(CommentedOutCode, range); + let mut diagnostic = OldDiagnostic::new(CommentedOutCode, range); diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion( locator.full_lines_range(range), ))); diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs index 1112921b72a2cf..4246e4edc45c18 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs @@ -8,7 +8,7 @@ use ruff_python_stdlib::sys::is_known_standard_library; use ruff_text_size::TextRange; use crate::settings::LinterSettings; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for modules that use the same names as Python standard-library @@ -69,7 +69,7 @@ pub(crate) fn stdlib_module_shadowing( mut path: &Path, settings: &LinterSettings, target_version: PythonVersion, -) -> Option { +) -> Option { if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file) { return None; } @@ -107,7 +107,7 @@ pub(crate) fn stdlib_module_shadowing( return None; } - Some(Diagnostic::new( + Some(OldDiagnostic::new( StdlibModuleShadowing { name: module_name.to_string(), }, diff --git a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs index 8ff3c157c09643..e176bdc67b8894 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs +++ b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs @@ -5,7 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::{AlwaysFixableViolation, Violation}; -use crate::{Diagnostic, Edit, Fix}; +use crate::{Edit, Fix, OldDiagnostic}; /// Simplified token type. #[derive(Copy, Clone, PartialEq, Eq)] @@ -238,7 +238,7 @@ impl AlwaysFixableViolation for ProhibitedTrailingComma { /// COM812, COM818, COM819 pub(crate) fn trailing_commas( - diagnostics: &mut Vec, + diagnostics: &mut Vec, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -319,7 +319,7 @@ fn check_token( prev_prev: SimpleToken, context: Context, locator: &Locator, -) -> Option { +) -> Option { // Is it allowed to have a trailing comma before this token? let comma_allowed = token.ty == TokenType::ClosingBracket && match context.ty { @@ -352,7 +352,7 @@ fn check_token( }; if comma_prohibited { - let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, prev.range()); + let mut diagnostic = OldDiagnostic::new(ProhibitedTrailingComma, prev.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range()))); return Some(diagnostic); } @@ -361,7 +361,7 @@ fn check_token( // Approximation: any comma followed by a statement-ending newline. let bare_comma_prohibited = prev.ty == TokenType::Comma && token.ty == TokenType::Newline; if bare_comma_prohibited { - return Some(Diagnostic::new(TrailingCommaOnBareTuple, prev.range())); + return Some(OldDiagnostic::new(TrailingCommaOnBareTuple, prev.range())); } if !comma_allowed { @@ -383,7 +383,7 @@ fn check_token( ); if comma_required { let mut diagnostic = - Diagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end())); + OldDiagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end())); // Create a replacement that includes the final bracket (or other token), // rather than just inserting a comma at the end. This prevents the UP034 fix // removing any brackets in the same linter pass - doing both at the same time could diff --git a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs index d8745ed2fbdca2..c6612e9ca4f161 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs @@ -3,7 +3,7 @@ use ruff_text_size::{TextRange, TextSize}; use crate::Locator; use crate::settings::LinterSettings; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for the absence of copyright notices within Python files. @@ -32,7 +32,7 @@ impl Violation for MissingCopyrightNotice { pub(crate) fn missing_copyright_notice( locator: &Locator, settings: &LinterSettings, -) -> Option { +) -> Option { // Ignore files that are too small to contain a copyright notice. if locator.len() < settings.flake8_copyright.min_file_size { return None; @@ -54,7 +54,7 @@ pub(crate) fn missing_copyright_notice( } } - Some(Diagnostic::new( + Some(OldDiagnostic::new( MissingCopyrightNotice, TextRange::default(), )) diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs index 9f92c00948ecf3..bde95037ffb711 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs @@ -7,8 +7,8 @@ pub(crate) use shebang_missing_python::*; pub(crate) use shebang_not_executable::*; pub(crate) use shebang_not_first_line::*; -use crate::Diagnostic; use crate::Locator; +use crate::OldDiagnostic; use crate::codes::Rule; use crate::comments::shebang::ShebangDirective; use crate::settings::LinterSettings; @@ -20,7 +20,7 @@ mod shebang_not_executable; mod shebang_not_first_line; pub(crate) fn from_tokens( - diagnostics: &mut Vec, + diagnostics: &mut Vec, path: &Path, locator: &Locator, comment_ranges: &CommentRanges, diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs index 7510b708acd70b..bf75f3a94d6f15 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs @@ -3,7 +3,7 @@ use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for whitespace before a shebang directive. @@ -47,7 +47,7 @@ impl AlwaysFixableViolation for ShebangLeadingWhitespace { pub(crate) fn shebang_leading_whitespace( range: TextRange, locator: &Locator, -) -> Option { +) -> Option { // If the shebang is at the beginning of the file, abort. if range.start() == TextSize::from(0) { return None; @@ -63,7 +63,7 @@ pub(crate) fn shebang_leading_whitespace( } let prefix = TextRange::up_to(range.start()); - let mut diagnostic = Diagnostic::new(ShebangLeadingWhitespace, prefix); + let mut diagnostic = OldDiagnostic::new(ShebangLeadingWhitespace, prefix); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix))); Some(diagnostic) } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs index 1d21ec05ce66f1..d36428e16d7c79 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs @@ -9,7 +9,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::registry::AsRule; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for executable `.py` files that do not have a shebang. @@ -49,14 +49,14 @@ impl Violation for ShebangMissingExecutableFile { /// EXE002 #[cfg(target_family = "unix")] -pub(crate) fn shebang_missing_executable_file(filepath: &Path) -> Option { +pub(crate) fn shebang_missing_executable_file(filepath: &Path) -> Option { // WSL supports Windows file systems, which do not have executable bits. // Instead, everything is executable. Therefore, we skip this rule on WSL. if is_wsl::is_wsl() { return None; } if let Ok(true) = is_executable(filepath) { - return Some(Diagnostic::new( + return Some(OldDiagnostic::new( ShebangMissingExecutableFile, TextRange::default(), )); @@ -65,6 +65,6 @@ pub(crate) fn shebang_missing_executable_file(filepath: &Path) -> Option Option { +pub(crate) fn shebang_missing_executable_file(_filepath: &Path) -> Option { None } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs index e9f6406e0feb51..d3ab16a5becd87 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs @@ -3,7 +3,7 @@ use ruff_text_size::TextRange; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::comments::shebang::ShebangDirective; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in `.py` files that does not contain `python`, @@ -44,10 +44,10 @@ impl Violation for ShebangMissingPython { pub(crate) fn shebang_missing_python( range: TextRange, shebang: &ShebangDirective, -) -> Option { +) -> Option { if shebang.contains("python") || shebang.contains("pytest") || shebang.contains("uv run") { return None; } - Some(Diagnostic::new(ShebangMissingPython, range)) + Some(OldDiagnostic::new(ShebangMissingPython, range)) } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs index b10e67cbdbf4c6..0b2093f360e4dc 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs @@ -5,7 +5,7 @@ use ruff_text_size::TextRange; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in a file that is not executable. @@ -48,7 +48,7 @@ impl Violation for ShebangNotExecutable { /// EXE001 #[cfg(target_family = "unix")] -pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange) -> Option { +pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange) -> Option { // WSL supports Windows file systems, which do not have executable bits. // Instead, everything is executable. Therefore, we skip this rule on WSL. if is_wsl::is_wsl() { @@ -56,13 +56,13 @@ pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange) -> Optio } if let Ok(false) = is_executable(filepath) { - return Some(Diagnostic::new(ShebangNotExecutable, range)); + return Some(OldDiagnostic::new(ShebangNotExecutable, range)); } None } #[cfg(not(target_family = "unix"))] -pub(crate) fn shebang_not_executable(_filepath: &Path, _range: TextRange) -> Option { +pub(crate) fn shebang_not_executable(_filepath: &Path, _range: TextRange) -> Option { None } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs index 8cdfe0db13386a..891ff6aebbe360 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs @@ -3,7 +3,7 @@ use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for a shebang directive that is not at the beginning of the file. @@ -42,7 +42,7 @@ impl Violation for ShebangNotFirstLine { } /// EXE005 -pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Option { +pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Option { // If the shebang is at the beginning of the file, abort. if range.start() == TextSize::from(0) { return None; @@ -57,5 +57,5 @@ pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Opt return None; } - Some(Diagnostic::new(ShebangNotFirstLine, range)) + Some(OldDiagnostic::new(ShebangNotFirstLine, range)) } diff --git a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs index 6f133ae0df7367..84f2b0fe839ef2 100644 --- a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs @@ -1,7 +1,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::directives::{TodoComment, TodoDirectiveKind}; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for "TODO" comments. @@ -114,19 +114,19 @@ impl Violation for LineContainsHack { } } -pub(crate) fn todos(diagnostics: &mut Vec, directive_ranges: &[TodoComment]) { +pub(crate) fn todos(diagnostics: &mut Vec, directive_ranges: &[TodoComment]) { diagnostics.extend( directive_ranges .iter() .map(|TodoComment { directive, .. }| match directive.kind { // FIX001 - TodoDirectiveKind::Fixme => Diagnostic::new(LineContainsFixme, directive.range), + TodoDirectiveKind::Fixme => OldDiagnostic::new(LineContainsFixme, directive.range), // FIX002 - TodoDirectiveKind::Hack => Diagnostic::new(LineContainsHack, directive.range), + TodoDirectiveKind::Hack => OldDiagnostic::new(LineContainsHack, directive.range), // FIX003 - TodoDirectiveKind::Todo => Diagnostic::new(LineContainsTodo, directive.range), + TodoDirectiveKind::Todo => OldDiagnostic::new(LineContainsTodo, directive.range), // FIX004 - TodoDirectiveKind::Xxx => Diagnostic::new(LineContainsXxx, directive.range), + TodoDirectiveKind::Xxx => OldDiagnostic::new(LineContainsXxx, directive.range), }), ); } diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 743174c340c2cb..db3f798f248419 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -11,7 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::settings::LinterSettings; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// ## What it does /// Checks for implicitly concatenated strings on a single line. @@ -103,7 +103,7 @@ impl Violation for MultiLineImplicitStringConcatenation { /// ISC001, ISC002 pub(crate) fn implicit( - diagnostics: &mut Vec, + diagnostics: &mut Vec, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -145,12 +145,12 @@ pub(crate) fn implicit( }; if locator.contains_line_break(TextRange::new(a_range.end(), b_range.start())) { - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( MultiLineImplicitStringConcatenation, TextRange::new(a_range.start(), b_range.end()), )); } else { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( SingleLineImplicitStringConcatenation, TextRange::new(a_range.start(), b_range.end()), ); diff --git a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs index 8d29898c355ef6..f5b6a4366ec4b4 100644 --- a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs +++ b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs @@ -10,7 +10,7 @@ use crate::Locator; use crate::comments::shebang::ShebangDirective; use crate::fs; use crate::package::PackageRoot; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for packages that are missing an `__init__.py` file. @@ -64,7 +64,7 @@ pub(crate) fn implicit_namespace_package( project_root: &Path, src: &[PathBuf], allow_nested_roots: bool, -) -> Option { +) -> Option { if package.is_none() // Ignore non-`.py` files, which don't require an `__init__.py`. && PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file) @@ -83,7 +83,7 @@ pub(crate) fn implicit_namespace_package( // Ignore PEP 723 scripts. && ScriptTag::parse(locator.contents().as_bytes()).is_none() { - return Some(Diagnostic::new( + return Some(OldDiagnostic::new( ImplicitNamespacePackage { filename: fs::relativize_path(path), parent: None, @@ -100,7 +100,7 @@ pub(crate) fn implicit_namespace_package( .ancestors() .find(|parent| !parent.join("__init__.py").exists()) { - return Some(Diagnostic::new( + return Some(OldDiagnostic::new( ImplicitNamespacePackage { filename: fs::relativize_path(path), parent: Some(fs::relativize_path(parent)), diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs index f8ac77d69c8a87..62177e18a6f14f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs @@ -6,7 +6,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use crate::Locator; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for the use of type comments (e.g., `x = 1 # type: int`) in stub @@ -38,7 +38,7 @@ impl Violation for TypeCommentInStub { /// PYI033 pub(crate) fn type_comment_in_stub( - diagnostics: &mut Vec, + diagnostics: &mut Vec, locator: &Locator, comment_ranges: &CommentRanges, ) { @@ -46,7 +46,7 @@ pub(crate) fn type_comment_in_stub( let comment = locator.slice(range); if TYPE_COMMENT_REGEX.is_match(comment) && !TYPE_IGNORE_REGEX.is_match(comment) { - diagnostics.push(Diagnostic::new(TypeCommentInStub, range)); + diagnostics.push(OldDiagnostic::new(TypeCommentInStub, range)); } } } diff --git a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs index e3516650b5ae30..9ee015251c7f14 100644 --- a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs @@ -8,7 +8,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; use crate::directives::{TodoComment, TodoDirective, TodoDirectiveKind}; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic, Violation}; /// ## What it does /// Checks that a TODO comment is labelled with "TODO". @@ -248,7 +248,7 @@ static ISSUE_LINK_TODO_LINE_REGEX_SET: LazyLock = LazyLock::new(|| { }); pub(crate) fn todos( - diagnostics: &mut Vec, + diagnostics: &mut Vec, todo_comments: &[TodoComment], locator: &Locator, comment_ranges: &CommentRanges, @@ -307,20 +307,20 @@ pub(crate) fn todos( if !has_issue_link { // TD003 - diagnostics.push(Diagnostic::new(MissingTodoLink, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoLink, directive.range)); } } } /// Check that the directive itself is valid. This function modifies `diagnostics` in-place. -fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirective) { +fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirective) { if directive.content == "TODO" { return; } if directive.content.to_uppercase() == "TODO" { // TD006 - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( InvalidTodoCapitalization { tag: directive.content.to_string(), }, @@ -335,7 +335,7 @@ fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirective diagnostics.push(diagnostic); } else { // TD001 - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( InvalidTodoTag { tag: directive.content.to_string(), }, @@ -346,7 +346,7 @@ fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirective /// Checks for "static" errors in the comment: missing colon, missing author, etc. fn static_errors( - diagnostics: &mut Vec, + diagnostics: &mut Vec, comment: &str, comment_range: TextRange, directive: &TodoDirective, @@ -367,13 +367,13 @@ fn static_errors( TextSize::try_from(end_index).unwrap() } else { // TD002 - diagnostics.push(Diagnostic::new(MissingTodoAuthor, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoAuthor, directive.range)); TextSize::new(0) } } else { // TD002 - diagnostics.push(Diagnostic::new(MissingTodoAuthor, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoAuthor, directive.range)); TextSize::new(0) }; @@ -382,18 +382,21 @@ fn static_errors( if let Some(after_colon) = after_author.strip_prefix(':') { if after_colon.is_empty() { // TD005 - diagnostics.push(Diagnostic::new(MissingTodoDescription, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoDescription, directive.range)); } else if !after_colon.starts_with(char::is_whitespace) { // TD007 - diagnostics.push(Diagnostic::new(MissingSpaceAfterTodoColon, directive.range)); + diagnostics.push(OldDiagnostic::new( + MissingSpaceAfterTodoColon, + directive.range, + )); } } else { // TD004 - diagnostics.push(Diagnostic::new(MissingTodoColon, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoColon, directive.range)); if after_author.is_empty() { // TD005 - diagnostics.push(Diagnostic::new(MissingTodoDescription, directive.range)); + diagnostics.push(OldDiagnostic::new(MissingTodoDescription, directive.range)); } } } diff --git a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs index b1d4d9a6137557..0f263ba83cade4 100644 --- a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs @@ -9,7 +9,7 @@ use ruff_text_size::{TextRange, TextSize}; use crate::Locator; use crate::importer::Importer; use crate::settings::LinterSettings; -use crate::{AlwaysFixableViolation, Diagnostic, Fix}; +use crate::{AlwaysFixableViolation, Fix, OldDiagnostic}; /// ## What it does /// Adds any required imports, as specified by the user, to the top of the @@ -91,7 +91,7 @@ fn add_required_import( locator: &Locator, stylist: &Stylist, source_type: PySourceType, -) -> Option { +) -> Option { // Don't add imports to semantically-empty files. if parsed.suite().iter().all(is_docstring_stmt) { return None; @@ -112,7 +112,7 @@ fn add_required_import( } // Always insert the diagnostic at top-of-file. - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MissingRequiredImport(required_import.to_string()), TextRange::default(), ); @@ -129,7 +129,7 @@ pub(crate) fn add_required_imports( stylist: &Stylist, settings: &LinterSettings, source_type: PySourceType, -) -> Vec { +) -> Vec { settings .isort .required_imports diff --git a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index b37e4b4a9c51cb..6fdd6e0794f637 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -18,7 +18,7 @@ use crate::package::PackageRoot; use crate::preview::is_full_path_match_source_strategy_enabled; use crate::rules::isort::categorize::MatchSourceStrategy; use crate::settings::LinterSettings; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// ## What it does /// De-duplicates, groups, and sorts imports based on the provided `isort` settings. @@ -98,7 +98,7 @@ pub(crate) fn organize_imports( source_type: PySourceType, tokens: &Tokens, target_version: PythonVersion, -) -> Option { +) -> Option { let indentation = locator.slice(extract_indentation_range(&block.imports, locator)); let indentation = leading_indentation(indentation); @@ -110,7 +110,7 @@ pub(crate) fn organize_imports( || indexer .followed_by_multi_statement_line(block.imports.last().unwrap(), locator.contents()) { - return Some(Diagnostic::new(UnsortedImports, range)); + return Some(OldDiagnostic::new(UnsortedImports, range)); } // Extract comments. Take care to grab any inline comments from the last line. @@ -155,7 +155,7 @@ pub(crate) fn organize_imports( if matches_ignoring_indentation(actual, &expected) { return None; } - let mut diagnostic = Diagnostic::new(UnsortedImports, range); + let mut diagnostic = OldDiagnostic::new(UnsortedImports, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( indent(&expected, indentation).to_string(), fix_range, diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs index 0774358df3d6c4..d2d60c6a4be4e9 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -9,7 +9,7 @@ use ruff_text_size::TextRange; use crate::package::PackageRoot; use crate::rules::pep8_naming::settings::IgnoreNames; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for module names that do not follow the `snake_case` naming @@ -54,7 +54,7 @@ pub(crate) fn invalid_module_name( path: &Path, package: Option>, ignore_names: &IgnoreNames, -) -> Option { +) -> Option { if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) { return None; } @@ -80,7 +80,7 @@ pub(crate) fn invalid_module_name( if ignore_names.matches(&module_name) { return None; } - return Some(Diagnostic::new( + return Some(OldDiagnostic::new( InvalidModuleName { name: module_name.to_string(), }, diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 906417cd899d0f..723ccbf49d2976 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -18,10 +18,10 @@ use ruff_text_size::TextRange; use ruff_text_size::TextSize; use crate::AlwaysFixableViolation; -use crate::Diagnostic; use crate::Edit; use crate::Fix; use crate::Locator; +use crate::OldDiagnostic; use crate::checkers::logical_lines::expand_indent; use crate::line_width::IndentWidth; use crate::rules::pycodestyle::helpers::is_non_logical_token; @@ -721,7 +721,7 @@ impl<'a> BlankLinesChecker<'a> { } /// E301, E302, E303, E304, E305, E306 - pub(crate) fn check_lines(&self, tokens: &Tokens, diagnostics: &mut Vec) { + pub(crate) fn check_lines(&self, tokens: &Tokens, diagnostics: &mut Vec) { let mut prev_indent_length: Option = None; let mut prev_logical_line: Option = None; let mut state = BlankLinesState::default(); @@ -824,7 +824,7 @@ impl<'a> BlankLinesChecker<'a> { line: &LogicalLineInfo, state: &BlankLinesState, prev_indent_length: Option, - diagnostics: &mut Vec, + diagnostics: &mut Vec, ) { if line.preceding_blank_lines == 0 // Only applies to methods. @@ -842,7 +842,8 @@ impl<'a> BlankLinesChecker<'a> { && !self.source_type.is_stub() { // E301 - let mut diagnostic = Diagnostic::new(BlankLineBetweenMethods, line.first_token_range); + let mut diagnostic = + OldDiagnostic::new(BlankLineBetweenMethods, line.first_token_range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( self.stylist.line_ending().to_string(), self.locator.line_start(state.last_non_comment_line_end), @@ -896,7 +897,7 @@ impl<'a> BlankLinesChecker<'a> { && !line.is_beginning_of_cell { // E302 - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( BlankLinesTopLevel { actual_blank_lines: line.preceding_blank_lines.count(), expected_blank_lines: expected_blank_lines_before_definition, @@ -940,7 +941,7 @@ impl<'a> BlankLinesChecker<'a> { if line.blank_lines > max_blank_lines { // E303 - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( TooManyBlankLines { actual_blank_lines: line.blank_lines.count(), }, @@ -966,7 +967,7 @@ impl<'a> BlankLinesChecker<'a> { && line.preceding_blank_lines > 0 { // E304 - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( BlankLineAfterDecorator { actual_blank_lines: line.preceding_blank_lines.count(), }, @@ -1013,7 +1014,7 @@ impl<'a> BlankLinesChecker<'a> { && !line.is_beginning_of_cell { // E305 - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( BlankLinesAfterFunctionOrClass { actual_blank_lines: line.preceding_blank_lines.count(), }, @@ -1057,7 +1058,7 @@ impl<'a> BlankLinesChecker<'a> { { // E306 let mut diagnostic = - Diagnostic::new(BlankLinesBeforeNestedDefinition, line.first_token_range); + OldDiagnostic::new(BlankLinesBeforeNestedDefinition, line.first_token_range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( self.stylist.line_ending().to_string(), diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs index cf93c6ab3a4735..4a5e166fce88fd 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs @@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextSize}; use crate::Locator; use crate::{AlwaysFixableViolation, Violation}; -use crate::{Diagnostic, Edit, Fix}; +use crate::{Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for compound statements (multiple statements on the same line). @@ -98,7 +98,7 @@ impl AlwaysFixableViolation for UselessSemicolon { /// E701, E702, E703 pub(crate) fn compound_statements( - diagnostics: &mut Vec, + diagnostics: &mut Vec, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -167,7 +167,7 @@ pub(crate) fn compound_statements( !has_non_trivia_tokens_till(token_iter.clone(), cell_range.end()) })) { - let mut diagnostic = Diagnostic::new(UselessSemicolon, range); + let mut diagnostic = OldDiagnostic::new(UselessSemicolon, range); diagnostic.set_fix(Fix::safe_edit(Edit::deletion( indexer .preceded_by_continuations(range.start(), locator.contents()) @@ -224,7 +224,10 @@ pub(crate) fn compound_statements( | TokenKind::NonLogicalNewline => {} _ => { if let Some(range) = semi { - diagnostics.push(Diagnostic::new(MultipleStatementsOnOneLineSemicolon, range)); + diagnostics.push(OldDiagnostic::new( + MultipleStatementsOnOneLineSemicolon, + range, + )); // Reset. semi = None; @@ -232,7 +235,7 @@ pub(crate) fn compound_statements( } if let Some(range) = colon { - diagnostics.push(Diagnostic::new(MultipleStatementsOnOneLineColon, range)); + diagnostics.push(OldDiagnostic::new(MultipleStatementsOnOneLineColon, range)); // Reset. colon = None; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs index 677b5ac374a821..c669a9c7e2663e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -4,7 +4,7 @@ use ruff_source_file::Line; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for doc lines that exceed the specified maximum character length. @@ -86,7 +86,7 @@ pub(crate) fn doc_line_too_long( line: &Line, comment_ranges: &CommentRanges, settings: &LinterSettings, -) -> Option { +) -> Option { let limit = settings.pycodestyle.max_doc_length?; Overlong::try_from_line( line, @@ -100,7 +100,7 @@ pub(crate) fn doc_line_too_long( settings.tab_size, ) .map(|overlong| { - Diagnostic::new( + OldDiagnostic::new( DocLineTooLong(overlong.width(), limit.value() as usize), overlong.range(), ) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs index 743c47b31e5031..dd93733790e075 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs @@ -4,7 +4,7 @@ use ruff_source_file::Line; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for lines that exceed the specified maximum character length. @@ -84,7 +84,7 @@ pub(crate) fn line_too_long( line: &Line, comment_ranges: &CommentRanges, settings: &LinterSettings, -) -> Option { +) -> Option { let limit = settings.pycodestyle.max_line_length; Overlong::try_from_line( @@ -99,7 +99,7 @@ pub(crate) fn line_too_long( settings.tab_size, ) .map(|overlong| { - Diagnostic::new( + OldDiagnostic::new( LineTooLong(overlong.width(), limit.value() as usize), overlong.range(), ) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs index b4a3f36efb1f00..d05d154e17c203 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs @@ -3,9 +3,9 @@ use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; use crate::AlwaysFixableViolation; -use crate::Diagnostic; use crate::Edit; use crate::Fix; +use crate::OldDiagnostic; use crate::checkers::logical_lines::LogicalLinesContext; use super::{LogicalLine, Whitespace}; @@ -165,7 +165,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin BracketOrPunctuation::OpenBracket(symbol) if symbol != '{' || fstrings == 0 => { let (trailing, trailing_len) = line.trailing_whitespace(token); if !matches!(trailing, Whitespace::None) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceAfterOpenBracket { symbol }, TextRange::at(token.end(), trailing_len), ); @@ -179,7 +179,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) = line.leading_whitespace(token) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforeCloseBracket { symbol }, TextRange::at(token.start() - offset, offset), ); @@ -205,7 +205,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // If we're in the second half of a double colon, disallow // any whitespace (e.g., `foo[1: :2]` or `foo[1 : : 2]`). if matches!(prev_token, Some(TokenKind::Colon)) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ); @@ -220,7 +220,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // Or `foo[index :, 2]`, but not `foo[index :, 2]`. if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ); @@ -245,7 +245,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // whitespace before the colon and so should the fix if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ); @@ -262,7 +262,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin .filter(|next| matches!(next.kind(), TokenKind::Colon)) .unwrap_or(&token); if line.trailing_whitespace(token) != whitespace { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ); @@ -280,7 +280,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // Avoid removing any whitespace for f-string debug expressions. continue; } - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs index 409a2dd8993059..de7349d059b6e4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs @@ -2,7 +2,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::TextRange; -use crate::Diagnostic; +use crate::OldDiagnostic; use crate::Violation; use super::LogicalLine; @@ -264,19 +264,19 @@ pub(crate) fn indentation( prev_indent_level: Option, indent_size: usize, range: TextRange, -) -> Vec { +) -> Vec { let mut diagnostics = vec![]; if indent_level % indent_size != 0 { diagnostics.push(if logical_line.is_comment_only() { - Diagnostic::new( + OldDiagnostic::new( IndentationWithInvalidMultipleComment { indent_width: indent_size, }, range, ) } else { - Diagnostic::new( + OldDiagnostic::new( IndentationWithInvalidMultiple { indent_width: indent_size, }, @@ -290,24 +290,24 @@ pub(crate) fn indentation( if indent_expect && indent_level <= prev_indent_level.unwrap_or(0) { diagnostics.push(if logical_line.is_comment_only() { - Diagnostic::new(NoIndentedBlockComment, range) + OldDiagnostic::new(NoIndentedBlockComment, range) } else { - Diagnostic::new(NoIndentedBlock, range) + OldDiagnostic::new(NoIndentedBlock, range) }); } else if !indent_expect && prev_indent_level.is_some_and(|prev_indent_level| indent_level > prev_indent_level) { diagnostics.push(if logical_line.is_comment_only() { - Diagnostic::new(UnexpectedIndentationComment, range) + OldDiagnostic::new(UnexpectedIndentationComment, range) } else { - Diagnostic::new(UnexpectedIndentation, range) + OldDiagnostic::new(UnexpectedIndentation, range) }); } if indent_expect { let expected_indent_amount = if indent_char == '\t' { 8 } else { 4 }; let expected_indent_level = prev_indent_level.unwrap_or(0) + expected_indent_amount; if indent_level > expected_indent_level { - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( OverIndented { is_comment: logical_line.is_comment_only(), }, diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs index 46e752391f6e48..e572022bd79146 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs @@ -4,7 +4,7 @@ use ruff_text_size::Ranged; use crate::Edit; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Diagnostic, Fix}; +use crate::{AlwaysFixableViolation, Fix, OldDiagnostic}; use super::{DefinitionState, LogicalLine}; @@ -104,7 +104,7 @@ pub(crate) fn missing_whitespace(line: &LogicalLine, context: &mut LogicalLinesC } let diagnostic = - Diagnostic::new(MissingWhitespace { token: kind }, token.range()); + OldDiagnostic::new(MissingWhitespace { token: kind }, token.range()); let fix = Fix::safe_edit(Edit::insertion(" ".to_string(), token.end())); context.push_diagnostic(diagnostic.with_fix(fix)); } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs index 011a6c65733d81..9883e34e7ad33f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs @@ -4,7 +4,7 @@ use ruff_text_size::Ranged; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for missing whitespace after keywords. @@ -71,7 +71,7 @@ pub(crate) fn missing_whitespace_after_keyword( )) && tok0.end() == tok1.start() { - let mut diagnostic = Diagnostic::new(MissingWhitespaceAfterKeyword, tok0.range()); + let mut diagnostic = OldDiagnostic::new(MissingWhitespaceAfterKeyword, tok0.range()); diagnostic.set_fix(Fix::safe_edit(Edit::insertion(" ".to_string(), tok0.end()))); context.push_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs index fcad4a9b3820b2..1eaf0d7cb1497d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs @@ -5,7 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::helpers::is_non_logical_token; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for missing whitespace around all operators. @@ -314,15 +314,15 @@ impl From for NeedsSpace { } } -fn diagnostic_kind_for_operator(operator: TokenKind, range: TextRange) -> Diagnostic { +fn diagnostic_kind_for_operator(operator: TokenKind, range: TextRange) -> OldDiagnostic { if operator == TokenKind::Percent { - Diagnostic::new(MissingWhitespaceAroundModuloOperator, range) + OldDiagnostic::new(MissingWhitespaceAroundModuloOperator, range) } else if operator.is_bitwise_or_shift() { - Diagnostic::new(MissingWhitespaceAroundBitwiseOrShiftOperator, range) + OldDiagnostic::new(MissingWhitespaceAroundBitwiseOrShiftOperator, range) } else if operator.is_arithmetic() { - Diagnostic::new(MissingWhitespaceAroundArithmeticOperator, range) + OldDiagnostic::new(MissingWhitespaceAroundArithmeticOperator, range) } else { - Diagnostic::new(MissingWhitespaceAroundOperator, range) + OldDiagnostic::new(MissingWhitespaceAroundOperator, range) } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs index 54ee31a98e2aad..4726c28fdf8bf0 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs @@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; use super::LogicalLine; @@ -75,7 +75,7 @@ pub(crate) fn redundant_backslash( for continuation_line in &continuation_lines[start_index..end_index] { let backslash_end = locator.line_end(*continuation_line); let backslash_start = backslash_end - TextSize::new(1); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( RedundantBackslash, TextRange::new(backslash_start, backslash_end), ); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs index d05eba21e966d3..7194333e17140d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs @@ -3,7 +3,7 @@ use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; use super::{LogicalLine, Whitespace}; @@ -206,7 +206,7 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin if !after_operator { match line.leading_whitespace(token) { (Whitespace::Tab, offset) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( TabBeforeOperator, TextRange::at(token.start() - offset, offset), ); @@ -217,7 +217,7 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin context.push_diagnostic(diagnostic); } (Whitespace::Many, offset) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MultipleSpacesBeforeOperator, TextRange::at(token.start() - offset, offset), ); @@ -234,7 +234,7 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { let mut diagnostic = - Diagnostic::new(TabAfterOperator, TextRange::at(token.end(), len)); + OldDiagnostic::new(TabAfterOperator, TextRange::at(token.end(), len)); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( " ".to_string(), TextRange::at(token.end(), len), @@ -242,7 +242,7 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin context.push_diagnostic(diagnostic); } (Whitespace::Many, len) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MultipleSpacesAfterOperator, TextRange::at(token.end(), len), ); @@ -267,7 +267,7 @@ pub(crate) fn space_after_comma(line: &LogicalLine, context: &mut LogicalLinesCo match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { let mut diagnostic = - Diagnostic::new(TabAfterComma, TextRange::at(token.end(), len)); + OldDiagnostic::new(TabAfterComma, TextRange::at(token.end(), len)); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( " ".to_string(), TextRange::at(token.end(), len), @@ -275,8 +275,10 @@ pub(crate) fn space_after_comma(line: &LogicalLine, context: &mut LogicalLinesCo context.push_diagnostic(diagnostic); } (Whitespace::Many, len) => { - let mut diagnostic = - Diagnostic::new(MultipleSpacesAfterComma, TextRange::at(token.end(), len)); + let mut diagnostic = OldDiagnostic::new( + MultipleSpacesAfterComma, + TextRange::at(token.end(), len), + ); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( " ".to_string(), TextRange::at(token.end(), len), diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs index 10528e2eb46bd1..2a1d5055f7bc63 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs @@ -2,7 +2,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; use super::{LogicalLine, Whitespace}; @@ -133,7 +133,7 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic match line.leading_whitespace(token) { (Whitespace::Tab, offset) => { let start = token.start(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( TabBeforeKeyword, TextRange::at(start - offset, offset), ); @@ -145,7 +145,7 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic } (Whitespace::Many, offset) => { let start = token.start(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MultipleSpacesBeforeKeyword, TextRange::at(start - offset, offset), ); @@ -162,7 +162,7 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { let mut diagnostic = - Diagnostic::new(TabAfterKeyword, TextRange::at(token.end(), len)); + OldDiagnostic::new(TabAfterKeyword, TextRange::at(token.end(), len)); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( " ".to_string(), TextRange::at(token.end(), len), @@ -170,7 +170,7 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic context.push_diagnostic(diagnostic); } (Whitespace::Many, len) => { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MultipleSpacesAfterKeyword, TextRange::at(token.end(), len), ); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs index 9ede7a35d305e1..5aa5b0332776a9 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for missing whitespace around the equals sign in an unannotated @@ -126,7 +126,7 @@ pub(crate) fn whitespace_around_named_parameter_equals( let start = token.start(); if start == prev_end && prev_end != TextSize::new(0) { let mut diagnostic = - Diagnostic::new(MissingWhitespaceAroundParameterEquals, token.range); + OldDiagnostic::new(MissingWhitespaceAroundParameterEquals, token.range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( " ".to_string(), token.start(), @@ -141,7 +141,7 @@ pub(crate) fn whitespace_around_named_parameter_equals( let next_start = next.start(); if next_start == token.end() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( MissingWhitespaceAroundParameterEquals, token.range, ); @@ -157,7 +157,7 @@ pub(crate) fn whitespace_around_named_parameter_equals( } else { // If there's space between the preceding token and the equals sign, report it. if token.start() != prev_end { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( UnexpectedSpacesAroundKeywordParameterEquals, TextRange::new(prev_end, token.start()), ); @@ -171,7 +171,7 @@ pub(crate) fn whitespace_around_named_parameter_equals( iter.next(); } else { if next.start() != token.end() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( UnexpectedSpacesAroundKeywordParameterEquals, TextRange::new(token.end(), next.start()), ); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs index cbac6fec8bba25..abd6522eb04bce 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs @@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks if inline comments are separated by at least two spaces. @@ -185,7 +185,7 @@ pub(crate) fn whitespace_before_comment( let is_inline_comment = !line_text.trim_whitespace().is_empty(); if is_inline_comment { if range.start() - prev_end < " ".text_len() { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( TooFewSpacesBeforeInlineComment, TextRange::new(prev_end, range.start()), ); @@ -210,7 +210,7 @@ pub(crate) fn whitespace_before_comment( if is_inline_comment { if bad_prefix.is_some() || comment.chars().next().is_some_and(char::is_whitespace) { - let mut diagnostic = Diagnostic::new(NoSpaceAfterInlineComment, range); + let mut diagnostic = OldDiagnostic::new(NoSpaceAfterInlineComment, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format_leading_space(token_text), range, @@ -220,7 +220,7 @@ pub(crate) fn whitespace_before_comment( } else if let Some(bad_prefix) = bad_prefix { if bad_prefix != '!' || !line.is_start_of_file() { if bad_prefix != '#' { - let mut diagnostic = Diagnostic::new(NoSpaceAfterBlockComment, range); + let mut diagnostic = OldDiagnostic::new(NoSpaceAfterBlockComment, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format_leading_space(token_text), range, @@ -228,7 +228,7 @@ pub(crate) fn whitespace_before_comment( context.push_diagnostic(diagnostic); } else if !comment.is_empty() { let mut diagnostic = - Diagnostic::new(MultipleLeadingHashesForBlockComment, range); + OldDiagnostic::new(MultipleLeadingHashesForBlockComment, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format_leading_hashes(token_text), range, diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs index d519b682976ab1..17806ef42d9550 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for extraneous whitespace immediately preceding an open parenthesis @@ -76,7 +76,7 @@ pub(crate) fn whitespace_before_parameters(line: &LogicalLine, context: &mut Log let end = token.end() - TextSize::from(1); let kind: WhitespaceBeforeParameters = WhitespaceBeforeParameters { bracket: kind }; - let mut diagnostic = Diagnostic::new(kind, TextRange::new(start, end)); + let mut diagnostic = OldDiagnostic::new(kind, TextRange::new(start, end)); diagnostic.set_fix(Fix::safe_edit(Edit::deletion(start, end))); context.push_diagnostic(diagnostic); } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs index 22f3291df5209a..f256c58b4d19c7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs @@ -3,7 +3,7 @@ use ruff_python_codegen::Stylist; use ruff_text_size::{TextLen, TextRange}; use crate::Locator; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for files missing a new line at the end of the file. @@ -40,7 +40,7 @@ impl AlwaysFixableViolation for MissingNewlineAtEndOfFile { pub(crate) fn no_newline_at_end_of_file( locator: &Locator, stylist: &Stylist, -) -> Option { +) -> Option { let source = locator.contents(); // Ignore empty and BOM only files. @@ -51,7 +51,7 @@ pub(crate) fn no_newline_at_end_of_file( if !source.ends_with(['\n', '\r']) { let range = TextRange::empty(locator.contents().text_len()); - let mut diagnostic = Diagnostic::new(MissingNewlineAtEndOfFile, range); + let mut diagnostic = OldDiagnostic::new(MissingNewlineAtEndOfFile, range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( stylist.line_ending().to_string(), range.start(), diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs index 106f9d64d0bafc..128844f98b7bb4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs @@ -4,7 +4,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::leading_indentation; use ruff_source_file::Line; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for mixed tabs and spaces in indentation. @@ -37,11 +37,11 @@ impl Violation for MixedSpacesAndTabs { } /// E101 -pub(crate) fn mixed_spaces_and_tabs(line: &Line) -> Option { +pub(crate) fn mixed_spaces_and_tabs(line: &Line) -> Option { let indent = leading_indentation(line.as_str()); if indent.contains(' ') && indent.contains('\t') { - Some(Diagnostic::new( + Some(OldDiagnostic::new( MixedSpacesAndTabs, TextRange::at(line.start(), indent.text_len()), )) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs index e7a2a6ac044ab9..10fb88ac8ddef7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs @@ -4,7 +4,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for indentation that uses tabs. @@ -34,7 +34,7 @@ impl Violation for TabIndentation { /// W191 pub(crate) fn tab_indentation( - diagnostics: &mut Vec, + diagnostics: &mut Vec, locator: &Locator, indexer: &Indexer, ) { @@ -46,7 +46,7 @@ pub(crate) fn tab_indentation( // Determine whether the tab is part of the line's indentation. if let Some(indent) = tab_indentation_at_line_start(range.start(), locator, indexer) { - diagnostics.push(Diagnostic::new(TabIndentation, indent)); + diagnostics.push(OldDiagnostic::new(TabIndentation, indent)); } // Advance to the next line. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs index aae55c29ed4b8a..4b41734f7f740c 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs @@ -6,7 +6,7 @@ use ruff_notebook::CellOffsets; use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for files with multiple trailing blank lines. @@ -59,7 +59,7 @@ impl AlwaysFixableViolation for TooManyNewlinesAtEndOfFile { /// W391 pub(crate) fn too_many_newlines_at_end_of_file( - diagnostics: &mut Vec, + diagnostics: &mut Vec, tokens: &Tokens, cell_offsets: Option<&CellOffsets>, ) { @@ -76,7 +76,7 @@ pub(crate) fn too_many_newlines_at_end_of_file( fn notebook_newline_diagnostics<'a>( mut tokens_iter: Peekable>, cell_offsets: &CellOffsets, -) -> Vec { +) -> Vec { let mut results = Vec::new(); let offset_iter = cell_offsets.iter().rev(); @@ -101,7 +101,7 @@ fn notebook_newline_diagnostics<'a>( fn newline_diagnostic<'a>( tokens_iter: &mut Peekable>, in_notebook: bool, -) -> Option { +) -> Option { let mut num_trailing_newlines: u32 = 0; let mut newline_range_start: Option = None; let mut newline_range_end: Option = None; @@ -137,7 +137,7 @@ fn newline_diagnostic<'a>( let diagnostic_range = TextRange::new(start, end); Some( - Diagnostic::new( + OldDiagnostic::new( TooManyNewlinesAtEndOfFile { num_trailing_newlines, in_notebook, diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs index 658af77b97993a..7db1304722435e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -6,7 +6,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; use crate::registry::Rule; use crate::settings::LinterSettings; -use crate::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for superfluous trailing whitespace. @@ -78,7 +78,7 @@ pub(crate) fn trailing_whitespace( locator: &Locator, indexer: &Indexer, settings: &LinterSettings, -) -> Option { +) -> Option { let whitespace_len: TextSize = line .chars() .rev() @@ -95,7 +95,7 @@ pub(crate) fn trailing_whitespace( }; if range == line.range() { if settings.rules.enabled(Rule::BlankLineWithWhitespace) { - let mut diagnostic = Diagnostic::new(BlankLineWithWhitespace, range); + let mut diagnostic = OldDiagnostic::new(BlankLineWithWhitespace, range); // Remove any preceding continuations, to avoid introducing a potential // syntax error. diagnostic.set_fix(Fix::applicable_edit( @@ -110,7 +110,7 @@ pub(crate) fn trailing_whitespace( return Some(diagnostic); } } else if settings.rules.enabled(Rule::TrailingWhitespace) { - let mut diagnostic = Diagnostic::new(TrailingWhitespace, range); + let mut diagnostic = OldDiagnostic::new(TrailingWhitespace, range); diagnostic.set_fix(Fix::applicable_edit( Edit::range_deletion(range), applicability, diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index fd284220ed2532..b3f63e584b1891 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Locator; use crate::noqa::{self, Directive, FileNoqaDirectives, NoqaDirectives}; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// ## What it does /// Check for `noqa` annotations that suppress all diagnostics, as opposed to @@ -74,14 +74,14 @@ impl Violation for BlanketNOQA { /// PGH004 pub(crate) fn blanket_noqa( - diagnostics: &mut Vec, + diagnostics: &mut Vec, noqa_directives: &NoqaDirectives, locator: &Locator, file_noqa_directives: &FileNoqaDirectives, ) { for line in file_noqa_directives.lines() { if let Directive::All(_) = line.parsed_file_exemption { - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( BlanketNOQA { missing_colon: false, file_exemption: true, @@ -105,7 +105,7 @@ pub(crate) fn blanket_noqa( // Ex) `# noqa F401` let start = all.end(); let end = start + cursor.token_len(); - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( BlanketNOQA { missing_colon: true, file_exemption: false, @@ -116,7 +116,7 @@ pub(crate) fn blanket_noqa( diagnostics.push(diagnostic); } else { // Otherwise, it looks like an intentional blanket `noqa` annotation. - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( BlanketNOQA { missing_colon: false, file_exemption: false, diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs index ba233d6a95b61b..8388acd8e6a960 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs @@ -9,7 +9,7 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::TextSize; use crate::Locator; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Check for `type: ignore` annotations that suppress all type warnings, as @@ -52,7 +52,7 @@ impl Violation for BlanketTypeIgnore { /// PGH003 pub(crate) fn blanket_type_ignore( - diagnostics: &mut Vec, + diagnostics: &mut Vec, comment_ranges: &CommentRanges, locator: &Locator, ) { @@ -92,7 +92,7 @@ pub(crate) fn blanket_type_ignore( // Match the optional `[...]` tag. if let Ok(codes) = parse_type_ignore_tag(comment) { if codes.is_empty() { - diagnostics.push(Diagnostic::new( + diagnostics.push(OldDiagnostic::new( BlanketTypeIgnore, range.add_start(TextSize::try_from(start).unwrap()), )); diff --git a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs index bba43cb06d607b..4f3566850cbf47 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs @@ -1,7 +1,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; const BIDI_UNICODE: [char; 10] = [ '\u{202A}', //{LEFT-TO-RIGHT EMBEDDING} @@ -53,10 +53,10 @@ impl Violation for BidirectionalUnicode { } /// PLE2502 -pub(crate) fn bidirectional_unicode(line: &Line) -> Vec { +pub(crate) fn bidirectional_unicode(line: &Line) -> Vec { let mut diagnostics = Vec::new(); if line.contains(BIDI_UNICODE) { - diagnostics.push(Diagnostic::new(BidirectionalUnicode, line.full_range())); + diagnostics.push(OldDiagnostic::new(BidirectionalUnicode, line.full_range())); } diagnostics } diff --git a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs index 4e607c70a8a0c2..2dd0ac29b7d031 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs @@ -4,7 +4,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// ## What it does /// Checks for a # symbol appearing on a line not followed by an actual comment. @@ -45,7 +45,7 @@ impl Violation for EmptyComment { /// PLR2044 pub(crate) fn empty_comments( - diagnostics: &mut Vec, + diagnostics: &mut Vec, comment_ranges: &CommentRanges, locator: &Locator, ) { @@ -65,7 +65,7 @@ pub(crate) fn empty_comments( } /// Return a [`Diagnostic`] if the comment at the given [`TextRange`] is empty. -fn empty_comment(range: TextRange, locator: &Locator) -> Option { +fn empty_comment(range: TextRange, locator: &Locator) -> Option { // Check: is the comment empty? if !locator .slice(range) @@ -97,7 +97,7 @@ fn empty_comment(range: TextRange, locator: &Locator) -> Option { }); Some( - Diagnostic::new(EmptyComment, TextRange::new(first_hash_col, line.end())).with_fix( + OldDiagnostic::new(EmptyComment, TextRange::new(first_hash_col, line.end())).with_fix( Fix::safe_edit(if let Some(deletion_start_col) = deletion_start_col { Edit::deletion(line.start() + deletion_start_col, line.end()) } else { diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index d6e66084f4c3cc..8bbc5541a752ea 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -3,7 +3,7 @@ use ruff_python_parser::{Token, TokenKind}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// ## What it does /// Checks for strings that contain the control character `BS`. @@ -181,7 +181,7 @@ impl Violation for InvalidCharacterZeroWidthSpace { /// PLE2510, PLE2512, PLE2513, PLE2514, PLE2515 pub(crate) fn invalid_string_characters( - diagnostics: &mut Vec, + diagnostics: &mut Vec, token: &Token, locator: &Locator, ) { @@ -197,13 +197,13 @@ pub(crate) fn invalid_string_characters( let c = match_.chars().next().unwrap(); let range = TextRange::at(location, c.text_len()); let (replacement, mut diagnostic) = match c { - '\x08' => ("\\b", Diagnostic::new(InvalidCharacterBackspace, range)), - '\x1A' => ("\\x1A", Diagnostic::new(InvalidCharacterSub, range)), - '\x1B' => ("\\x1B", Diagnostic::new(InvalidCharacterEsc, range)), - '\0' => ("\\0", Diagnostic::new(InvalidCharacterNul, range)), + '\x08' => ("\\b", OldDiagnostic::new(InvalidCharacterBackspace, range)), + '\x1A' => ("\\x1A", OldDiagnostic::new(InvalidCharacterSub, range)), + '\x1B' => ("\\x1B", OldDiagnostic::new(InvalidCharacterEsc, range)), + '\0' => ("\\0", OldDiagnostic::new(InvalidCharacterNul, range)), '\u{200b}' => ( "\\u200b", - Diagnostic::new(InvalidCharacterZeroWidthSpace, range), + OldDiagnostic::new(InvalidCharacterZeroWidthSpace, range), ), _ => { continue; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs index 34aae8693e7d1b..94dde654dd6ab8 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -5,7 +5,7 @@ use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for extraneous parentheses. @@ -115,7 +115,7 @@ fn match_extraneous_parentheses(tokens: &mut Iter<'_, Token>) -> Option<(TextRan /// UP034 pub(crate) fn extraneous_parentheses( - diagnostics: &mut Vec, + diagnostics: &mut Vec, tokens: &Tokens, locator: &Locator, ) { @@ -129,7 +129,7 @@ pub(crate) fn extraneous_parentheses( continue; }; - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( ExtraneousParentheses, TextRange::new(start_range.start(), end_range.end()), ); diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs index cfa58b8b243fda..cadf274f081dbd 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs @@ -9,7 +9,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for unnecessary UTF-8 encoding declarations. @@ -66,7 +66,7 @@ struct CodingCommentRange { /// UP009 pub(crate) fn unnecessary_coding_comment( - diagnostics: &mut Vec, + diagnostics: &mut Vec, locator: &Locator, comment_ranges: &CommentRanges, ) { @@ -106,7 +106,7 @@ pub(crate) fn unnecessary_coding_comment( } let fix = Fix::safe_edit(Edit::range_deletion(range.line)); - let diagnostic = Diagnostic::new(UTF8EncodingDeclaration, range.comment); + let diagnostic = OldDiagnostic::new(UTF8EncodingDeclaration, range.comment); diagnostics.push(diagnostic.with_fix(fix)); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index ae672e6b3d0e8c..a8c1d89fcdaa32 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -13,7 +13,7 @@ use crate::registry::AsRule; use crate::rules::ruff::rules::Context; use crate::rules::ruff::rules::confusables::confusable; use crate::settings::LinterSettings; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for ambiguous Unicode characters in strings. @@ -176,7 +176,7 @@ impl Violation for AmbiguousUnicodeCharacterComment { /// RUF003 pub(crate) fn ambiguous_unicode_character_comment( - diagnostics: &mut Vec, + diagnostics: &mut Vec, locator: &Locator, range: TextRange, settings: &LinterSettings, @@ -342,25 +342,25 @@ impl Candidate { } } - fn into_diagnostic(self, context: Context, settings: &LinterSettings) -> Option { + fn into_diagnostic(self, context: Context, settings: &LinterSettings) -> Option { if !settings.allowed_confusables.contains(&self.confusable) { let char_range = TextRange::at(self.offset, self.confusable.text_len()); let diagnostic = match context { - Context::String => Diagnostic::new( + Context::String => OldDiagnostic::new( AmbiguousUnicodeCharacterString { confusable: self.confusable, representant: self.representant, }, char_range, ), - Context::Docstring => Diagnostic::new( + Context::Docstring => OldDiagnostic::new( AmbiguousUnicodeCharacterDocstring { confusable: self.confusable, representant: self.representant, }, char_range, ), - Context::Comment => Diagnostic::new( + Context::Comment => OldDiagnostic::new( AmbiguousUnicodeCharacterComment { confusable: self.confusable, representant: self.representant, diff --git a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs index f76f5a6c1e3eb2..1c95430ea72be6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs @@ -4,7 +4,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; use ruff_text_size::{TextRange, TextSize}; -use crate::{Diagnostic, Violation}; +use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for form feed characters preceded by either a space or a tab. @@ -49,7 +49,7 @@ const SPACE: u8 = b' '; const TAB: u8 = b'\t'; /// RUF054 -pub(crate) fn indented_form_feed(line: &Line) -> Option { +pub(crate) fn indented_form_feed(line: &Line) -> Option { let index_relative_to_line = memchr(FORM_FEED, line.as_bytes())?; if index_relative_to_line == 0 { @@ -68,5 +68,5 @@ pub(crate) fn indented_form_feed(line: &Line) -> Option { let absolute_index = line.start() + TextSize::new(relative_index); let range = TextRange::at(absolute_index, 1.into()); - Some(Diagnostic::new(IndentedFormFeed, range)) + Some(OldDiagnostic::new(IndentedFormFeed, range)) } diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs index 2d1bb95d23d3ee..14f0a4e84d0ab2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs @@ -5,7 +5,7 @@ use crate::Locator; use crate::noqa::{Code, Directive}; use crate::noqa::{Codes, NoqaDirectives}; use crate::registry::Rule; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for `noqa` codes that are invalid. @@ -48,7 +48,7 @@ impl AlwaysFixableViolation for InvalidRuleCode { /// RUF102 for invalid noqa codes pub(crate) fn invalid_noqa_code( - diagnostics: &mut Vec, + diagnostics: &mut Vec, noqa_directives: &NoqaDirectives, locator: &Locator, external: &[String], @@ -86,8 +86,8 @@ fn code_is_valid(code: &Code, external: &[String]) -> bool { fn all_codes_invalid_diagnostic( directive: &Codes<'_>, invalid_codes: Vec<&Code<'_>>, -) -> Diagnostic { - Diagnostic::new( +) -> OldDiagnostic { + OldDiagnostic::new( InvalidRuleCode { rule_code: invalid_codes .into_iter() @@ -104,8 +104,8 @@ fn some_codes_are_invalid_diagnostic( codes: &Codes, invalid_code: &Code, locator: &Locator, -) -> Diagnostic { - let diagnostic = Diagnostic::new( +) -> OldDiagnostic { + let diagnostic = OldDiagnostic::new( InvalidRuleCode { rule_code: invalid_code.to_string(), }, diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index 59b50d04d65cd2..5093883f29c3bd 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -3,7 +3,7 @@ use ruff_text_size::Ranged; use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::rule_redirects::get_redirect_target; -use crate::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; /// ## What it does /// Checks for `noqa` directives that use redirected rule codes. @@ -43,7 +43,10 @@ impl AlwaysFixableViolation for RedirectedNOQA { } /// RUF101 for in-line noqa directives -pub(crate) fn redirected_noqa(diagnostics: &mut Vec, noqa_directives: &NoqaDirectives) { +pub(crate) fn redirected_noqa( + diagnostics: &mut Vec, + noqa_directives: &NoqaDirectives, +) { for line in noqa_directives.lines() { let Directive::Codes(directive) = &line.directive else { continue; @@ -55,7 +58,7 @@ pub(crate) fn redirected_noqa(diagnostics: &mut Vec, noqa_directives /// RUF101 for file noqa directives pub(crate) fn redirected_file_noqa( - diagnostics: &mut Vec, + diagnostics: &mut Vec, noqa_directives: &FileNoqaDirectives, ) { for line in noqa_directives.lines() { @@ -68,10 +71,10 @@ pub(crate) fn redirected_file_noqa( } /// Convert a sequence of [Codes] into [Diagnostic]s and append them to `diagnostics`. -fn build_diagnostics(diagnostics: &mut Vec, codes: &Codes<'_>) { +fn build_diagnostics(diagnostics: &mut Vec, codes: &Codes<'_>) { for code in codes.iter() { if let Some(redirected) = get_redirect_target(code.as_str()) { - let mut diagnostic = Diagnostic::new( + let mut diagnostic = OldDiagnostic::new( RedirectedNOQA { original: code.to_string(), target: redirected.to_string(), diff --git a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs index 4bad3a00663760..d3a43f21f54188 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs @@ -19,7 +19,7 @@ use ruff_text_size::TextSize; use crate::Locator; use crate::registry::Rule; -use crate::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; /// Check if a comment exists anywhere in a given file fn comment_exists(text: &str, locator: &Locator, comment_ranges: &CommentRanges) -> bool { @@ -48,7 +48,7 @@ pub(crate) const TEST_RULES: &[Rule] = &[ ]; pub(crate) trait TestRule { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option; + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option; } /// ## What it does @@ -79,8 +79,8 @@ impl Violation for StableTestRule { } impl TestRule for StableTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( StableTestRule, ruff_text_size::TextRange::default(), )) @@ -115,13 +115,13 @@ impl Violation for StableTestRuleSafeFix { } impl TestRule for StableTestRuleSafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { let comment = "# fix from stable-test-rule-safe-fix\n".to_string(); if comment_exists(&comment, locator, comment_ranges) { None } else { Some( - Diagnostic::new(StableTestRuleSafeFix, ruff_text_size::TextRange::default()) + OldDiagnostic::new(StableTestRuleSafeFix, ruff_text_size::TextRange::default()) .with_fix(Fix::safe_edit(Edit::insertion(comment, TextSize::new(0)))), ) } @@ -156,13 +156,13 @@ impl Violation for StableTestRuleUnsafeFix { } impl TestRule for StableTestRuleUnsafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { let comment = "# fix from stable-test-rule-unsafe-fix\n".to_string(); if comment_exists(&comment, locator, comment_ranges) { None } else { Some( - Diagnostic::new( + OldDiagnostic::new( StableTestRuleUnsafeFix, ruff_text_size::TextRange::default(), ) @@ -200,13 +200,13 @@ impl Violation for StableTestRuleDisplayOnlyFix { } impl TestRule for StableTestRuleDisplayOnlyFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { let comment = "# fix from stable-test-rule-display-only-fix\n".to_string(); if comment_exists(&comment, locator, comment_ranges) { None } else { Some( - Diagnostic::new( + OldDiagnostic::new( StableTestRuleDisplayOnlyFix, ruff_text_size::TextRange::default(), ) @@ -247,8 +247,8 @@ impl Violation for PreviewTestRule { } impl TestRule for PreviewTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( PreviewTestRule, ruff_text_size::TextRange::default(), )) @@ -283,8 +283,8 @@ impl Violation for DeprecatedTestRule { } impl TestRule for DeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( DeprecatedTestRule, ruff_text_size::TextRange::default(), )) @@ -319,8 +319,8 @@ impl Violation for AnotherDeprecatedTestRule { } impl TestRule for AnotherDeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( AnotherDeprecatedTestRule, ruff_text_size::TextRange::default(), )) @@ -355,8 +355,8 @@ impl Violation for RemovedTestRule { } impl TestRule for RemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( RemovedTestRule, ruff_text_size::TextRange::default(), )) @@ -391,8 +391,8 @@ impl Violation for AnotherRemovedTestRule { } impl TestRule for AnotherRemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( AnotherRemovedTestRule, ruff_text_size::TextRange::default(), )) @@ -427,8 +427,8 @@ impl Violation for RedirectedFromTestRule { } impl TestRule for RedirectedFromTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( RedirectedFromTestRule, ruff_text_size::TextRange::default(), )) @@ -463,8 +463,8 @@ impl Violation for RedirectedToTestRule { } impl TestRule for RedirectedToTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( RedirectedToTestRule, ruff_text_size::TextRange::default(), )) @@ -499,8 +499,8 @@ impl Violation for RedirectedFromPrefixTestRule { } impl TestRule for RedirectedFromPrefixTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(Diagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + Some(OldDiagnostic::new( RedirectedFromPrefixTestRule, ruff_text_size::TextRange::default(), )) From 3445d1322d978b05aa4575d71c58d3cacde5c64f Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 30 May 2025 04:30:40 +0800 Subject: [PATCH 278/487] [`airflow`] Add unsafe fix module moved cases (`AIR302`) (#18093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Add utility functions `generate_import_edit` and `generate_remove_and_runtime_import_edit` to generate the fix needed for the airflow rules. 1. `generate_import_edit` is for the cases where the member name has changed. (e.g., `airflow.datasts.Dataset` to `airflow.sdk.Asset`) It's just extracted from the original logic 2. `generate_remove_and_runtime_import_edit` is for cases where the member name has not changed. (e.g., `airflow.operators.pig_operator.PigOperator` to `airflow.providers.apache.pig.hooks.pig.PigCliHook`) This is newly introduced. As it introduced runtime import, I mark it as an unsafe fix. Under the hook, it tried to find the original import statement, remove it, and add a new import fix --- * rules fix * `airflow.sensors.external_task_sensor.ExternalTaskSensorLink` → `airflow.providers.standard.sensors.external_task.ExternalDagLink` ## Test Plan The existing test fixtures have been updated --- .../test/fixtures/airflow/AIR302_amazon.py | 31 +- .../fixtures/airflow/AIR302_common_sql.py | 5 +- .../test/fixtures/airflow/AIR302_hive.py | 60 +- .../fixtures/airflow/AIR302_kubernetes.py | 40 +- .../test/fixtures/airflow/AIR302_standard.py | 48 +- .../ruff_linter/src/rules/airflow/helpers.rs | 76 +- .../airflow/rules/moved_to_provider_in_3.rs | 44 +- ...rflow__tests__AIR302_AIR302_amazon.py.snap | 273 +++- ...rflow__tests__AIR302_AIR302_celery.py.snap | 42 +- ...w__tests__AIR302_AIR302_common_sql.py.snap | 629 ++++++-- ..._tests__AIR302_AIR302_daskexecutor.py.snap | 10 +- ...irflow__tests__AIR302_AIR302_druid.py.snap | 63 +- ..._airflow__tests__AIR302_AIR302_fab.py.snap | 262 +++- ...airflow__tests__AIR302_AIR302_hdfs.py.snap | 24 +- ...airflow__tests__AIR302_AIR302_hive.py.snap | 478 ++++-- ...airflow__tests__AIR302_AIR302_http.py.snap | 25 +- ...airflow__tests__AIR302_AIR302_jdbc.py.snap | 27 +- ...w__tests__AIR302_AIR302_kubernetes.py.snap | 1293 +++++++++++------ ...irflow__tests__AIR302_AIR302_mysql.py.snap | 43 +- ...rflow__tests__AIR302_AIR302_oracle.py.snap | 10 +- ...ow__tests__AIR302_AIR302_papermill.py.snap | 10 +- ..._airflow__tests__AIR302_AIR302_pig.py.snap | 24 +- ...low__tests__AIR302_AIR302_postgres.py.snap | 12 +- ...rflow__tests__AIR302_AIR302_presto.py.snap | 10 +- ...irflow__tests__AIR302_AIR302_samba.py.snap | 10 +- ...irflow__tests__AIR302_AIR302_slack.py.snap | 38 +- ...airflow__tests__AIR302_AIR302_smtp.py.snap | 22 +- ...rflow__tests__AIR302_AIR302_sqlite.py.snap | 10 +- ...low__tests__AIR302_AIR302_standard.py.snap | 554 +++++-- ...flow__tests__AIR302_AIR302_zendesk.py.snap | 10 +- 30 files changed, 3080 insertions(+), 1103 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_amazon.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_amazon.py index b72ae553bfa491..befad4b8e03f32 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_amazon.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_amazon.py @@ -5,35 +5,30 @@ provide_bucket_name, ) from airflow.operators.gcs_to_s3 import GCSToS3Operator -from airflow.operators.google_api_to_s3_transfer import ( - GoogleApiToS3Operator, - GoogleApiToS3Transfer, -) -from airflow.operators.redshift_to_s3_operator import ( - RedshiftToS3Operator, - RedshiftToS3Transfer, -) +from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator from airflow.operators.s3_file_transform_operator import S3FileTransformOperator -from airflow.operators.s3_to_redshift_operator import ( - S3ToRedshiftOperator, - S3ToRedshiftTransfer, -) +from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator from airflow.sensors.s3_key_sensor import S3KeySensor S3Hook() provide_bucket_name() GCSToS3Operator() - GoogleApiToS3Operator() +RedshiftToS3Operator() +S3FileTransformOperator() +S3ToRedshiftOperator() +S3KeySensor() + +from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer + GoogleApiToS3Transfer() -RedshiftToS3Operator() +from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer + RedshiftToS3Transfer() -S3FileTransformOperator() +from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer -S3ToRedshiftOperator() S3ToRedshiftTransfer() - -S3KeySensor() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py index 1ceed1949bc51a..56e74a188dc4b8 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_common_sql.py @@ -4,10 +4,13 @@ ConnectorProtocol, DbApiHook, ) + +ConnectorProtocol() +DbApiHook() + from airflow.hooks.dbapi_hook import DbApiHook from airflow.operators.check_operator import SQLCheckOperator -ConnectorProtocol() DbApiHook() SQLCheckOperator() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_hive.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_hive.py index a018a9fc11a1d5..8f3076b2d364e9 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_hive.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_hive.py @@ -12,55 +12,59 @@ ) from airflow.operators.hive_operator import HiveOperator from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator -from airflow.operators.hive_to_mysql import ( - HiveToMySqlOperator, - HiveToMySqlTransfer, -) +from airflow.operators.hive_to_mysql import HiveToMySqlOperator from airflow.operators.hive_to_samba_operator import HiveToSambaOperator -from airflow.operators.mssql_to_hive import ( - MsSqlToHiveOperator, - MsSqlToHiveTransfer, -) -from airflow.operators.mysql_to_hive import ( - MySqlToHiveOperator, - MySqlToHiveTransfer, -) -from airflow.operators.s3_to_hive_operator import ( - S3ToHiveOperator, - S3ToHiveTransfer, -) -from airflow.sensors.hive_partition_sensor import HivePartitionSensor -from airflow.sensors.metastore_partition_sensor import MetastorePartitionSensor -from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor - -closest_ds_partition() -max_partition() +HIVE_QUEUE_PRIORITIES HiveCliHook() HiveMetastoreHook() HiveServer2Hook() -HIVE_QUEUE_PRIORITIES -HiveOperator() +closest_ds_partition() +max_partition() +HiveOperator() HiveStatsCollectionOperator() - HiveToMySqlOperator() +HiveToSambaOperator() + + +from airflow.operators.hive_to_mysql import HiveToMySqlTransfer + HiveToMySqlTransfer() -HiveToSambaOperator() +from airflow.operators.mysql_to_hive import MySqlToHiveOperator + +MySqlToHiveOperator() + +from airflow.operators.mysql_to_hive import MySqlToHiveTransfer + +MySqlToHiveTransfer() + +from airflow.operators.mssql_to_hive import MsSqlToHiveOperator MsSqlToHiveOperator() + +from airflow.operators.mssql_to_hive import MsSqlToHiveTransfer + MsSqlToHiveTransfer() -MySqlToHiveOperator() -MySqlToHiveTransfer() +from airflow.operators.s3_to_hive_operator import S3ToHiveOperator S3ToHiveOperator() + +from airflow.operators.s3_to_hive_operator import S3ToHiveTransfer + S3ToHiveTransfer() +from airflow.sensors.hive_partition_sensor import HivePartitionSensor + HivePartitionSensor() +from airflow.sensors.metastore_partition_sensor import MetastorePartitionSensor + MetastorePartitionSensor() +from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor + NamedHivePartitionSensor() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_kubernetes.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_kubernetes.py index 2d943b8731ff0f..0f9a6614dab5ad 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_kubernetes.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_kubernetes.py @@ -16,14 +16,7 @@ from airflow.kubernetes.kubernetes_helper_functions import ( add_pod_suffix, annotations_for_logging_task_metadata, - annotations_to_key, create_pod_id, - get_logs_task_metadata, - rand_str, -) -from airflow.kubernetes.pod import ( - Port, - Resources, ) ALL_NAMESPACES @@ -37,21 +30,13 @@ get_kube_client() add_pod_suffix() -create_pod_id() - annotations_for_logging_task_metadata() -annotations_to_key() -get_logs_task_metadata() -rand_str() - -Port() -Resources() +create_pod_id() from airflow.kubernetes.pod_generator import ( PodDefaults, PodGenerator, - PodGeneratorDeprecated, add_pod_suffix, datetime_to_label_safe_datestring, extend_object_field, @@ -61,18 +46,16 @@ rand_str, ) +PodDefaults() +PodGenerator() +add_pod_suffix() datetime_to_label_safe_datestring() extend_object_field() label_safe_datestring_to_datetime() make_safe_label_value() merge_objects() -PodGenerator() -PodDefaults() -PodGeneratorDeprecated() -add_pod_suffix() rand_str() - from airflow.kubernetes.pod_generator_deprecated import ( PodDefaults, PodGenerator, @@ -90,7 +73,6 @@ PodLauncher() PodStatus() - from airflow.kubernetes.pod_launcher_deprecated import ( PodDefaults, PodLauncher, @@ -115,3 +97,17 @@ Secret() Volume() VolumeMount() + +from airflow.kubernetes.kubernetes_helper_functions import ( + annotations_to_key, + get_logs_task_metadata, + rand_str, +) + +annotations_to_key() +get_logs_task_metadata() +rand_str() + +from airflow.kubernetes.pod_generator import PodGeneratorDeprecated + +PodGeneratorDeprecated() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_standard.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_standard.py index 3758c3afb556ba..a43031057ed381 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_standard.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_standard.py @@ -5,10 +5,6 @@ TriggerDagRunLink, TriggerDagRunOperator, ) -from airflow.operators.dummy import ( - DummyOperator, - EmptyOperator, -) from airflow.operators.latest_only_operator import LatestOnlyOperator from airflow.operators.python_operator import ( BranchPythonOperator, @@ -19,15 +15,12 @@ from airflow.sensors.external_task_sensor import ( ExternalTaskMarker, ExternalTaskSensor, - ExternalTaskSensorLink, ) BashOperator() TriggerDagRunLink() TriggerDagRunOperator() -DummyOperator() -EmptyOperator() LatestOnlyOperator() @@ -38,25 +31,48 @@ ExternalTaskMarker() ExternalTaskSensor() -ExternalTaskSensorLink() -from airflow.operators.dummy_operator import ( - DummyOperator, - EmptyOperator, -) - -DummyOperator() -EmptyOperator() from airflow.hooks.subprocess import SubprocessResult + SubprocessResult() + from airflow.hooks.subprocess import working_directory + working_directory() + from airflow.operators.datetime import target_times_as_dates + target_times_as_dates() + from airflow.operators.trigger_dagrun import TriggerDagRunLink + TriggerDagRunLink() + from airflow.sensors.external_task import ExternalTaskSensorLink + ExternalTaskSensorLink() + from airflow.sensors.time_delta import WaitSensor -WaitSensor() \ No newline at end of file + +WaitSensor() + +from airflow.operators.dummy import DummyOperator + +DummyOperator() + +from airflow.operators.dummy import EmptyOperator + +EmptyOperator() + +from airflow.operators.dummy_operator import DummyOperator + +DummyOperator() + +from airflow.operators.dummy_operator import EmptyOperator + +EmptyOperator() + +from airflow.sensors.external_task_sensor import ExternalTaskSensorLink + +ExternalTaskSensorLink() diff --git a/crates/ruff_linter/src/rules/airflow/helpers.rs b/crates/ruff_linter/src/rules/airflow/helpers.rs index 90c99234d39905..b5997e916ff947 100644 --- a/crates/ruff_linter/src/rules/airflow/helpers.rs +++ b/crates/ruff_linter/src/rules/airflow/helpers.rs @@ -1,10 +1,17 @@ +use crate::checkers::ast::Checker; +use crate::fix::edits::remove_unused_imports; +use crate::importer::ImportRequest; use crate::rules::numpy::helpers::{AttributeSearcher, ImportSearcher}; +use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::name::QualifiedNameBuilder; use ruff_python_ast::statement_visitor::StatementVisitor; use ruff_python_ast::visitor::Visitor; -use ruff_python_ast::{Expr, ExprName, StmtTry}; +use ruff_python_ast::{Expr, ExprAttribute, ExprName, StmtTry}; use ruff_python_semantic::Exceptions; use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{MemberNameImport, NameImport}; +use ruff_text_size::Ranged; +use ruff_text_size::TextRange; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum Replacement { @@ -170,3 +177,70 @@ pub(crate) fn is_airflow_builtin_or_provider( _ => false, } } + +/// Return the [`ast::ExprName`] at the head of the expression, if any. +pub(crate) fn match_head(value: &Expr) -> Option<&ExprName> { + match value { + Expr::Attribute(ExprAttribute { value, .. }) => value.as_name_expr(), + Expr::Name(name) => Some(name), + _ => None, + } +} + +/// Return the [`Fix`] that imports the new name and updates where the import is referenced. +/// This is used for cases that member name has changed. +/// (e.g., `airflow.datasts.Dataset` to `airflow.sdk.Asset`) +pub(crate) fn generate_import_edit( + expr: &Expr, + checker: &Checker, + module: &str, + name: &str, + ranged: TextRange, +) -> Option { + let (import_edit, _) = checker + .importer() + .get_or_import_symbol( + &ImportRequest::import_from(module, name), + expr.start(), + checker.semantic(), + ) + .ok()?; + let replacement_edit = Edit::range_replacement(name.to_string(), ranged.range()); + Some(Fix::safe_edits(import_edit, [replacement_edit])) +} + +/// Return the [`Fix`] that remove the original import and import the same name with new path. +/// This is used for cases that member name has not changed. +/// (e.g., `airflow.operators.pig_operator.PigOperator` to `airflow.providers.apache.pig.hooks.pig.PigCliHook`) +pub(crate) fn generate_remove_and_runtime_import_edit( + expr: &Expr, + checker: &Checker, + module: &str, + name: &str, +) -> Option { + let head = match_head(expr)?; + let semantic = checker.semantic(); + let binding = semantic + .resolve_name(head) + .or_else(|| checker.semantic().lookup_symbol(&head.id)) + .map(|id| checker.semantic().binding(id))?; + let stmt = binding.statement(semantic)?; + let remove_edit = remove_unused_imports( + std::iter::once(name), + stmt, + None, + checker.locator(), + checker.stylist(), + checker.indexer(), + ) + .ok()?; + let import_edit = checker.importer().add_import( + &NameImport::ImportFrom(MemberNameImport::member( + (*module).to_string(), + name.to_string(), + )), + expr.start(), + ); + + Some(Fix::unsafe_edits(remove_edit, [import_edit])) +} diff --git a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs index 01e7f4c45f9251..596032a583139f 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/moved_to_provider_in_3.rs @@ -1,3 +1,8 @@ +use crate::checkers::ast::Checker; +use crate::rules::airflow::helpers::{ + ProviderReplacement, generate_import_edit, generate_remove_and_runtime_import_edit, + is_guarded_by_try_except, +}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; @@ -5,10 +10,7 @@ use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use ruff_text_size::TextRange; -use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; -use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use crate::{Edit, Fix, FixAvailability, Violation}; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for uses of Airflow functions and values that have been moved to it providers. @@ -1169,7 +1171,7 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan [ "airflow", "sensors", - "external_task", + "external_task" | "external_task_sensor", "ExternalTaskSensorLink", ] => ProviderReplacement::AutoImport { module: "airflow.providers.standard.sensors.external_task", @@ -1181,7 +1183,7 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan "airflow", "sensors", "external_task_sensor", - rest @ ("ExternalTaskMarker" | "ExternalTaskSensor" | "ExternalTaskSensorLink"), + rest @ ("ExternalTaskMarker" | "ExternalTaskSensor"), ] => ProviderReplacement::SourceModuleMovedToProvider { module: "airflow.providers.standard.sensors.external_task", name: (*rest).to_string(), @@ -1219,21 +1221,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan return; } - checker - .report_diagnostic( - Airflow3MovedToProvider { - deprecated: qualified_name, - replacement: replacement.clone(), - }, - ranged, - ) - .try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from(module, name), - expr.start(), - checker.semantic(), - )?; - let replacement_edit = Edit::range_replacement(binding, ranged); - Ok(Fix::safe_edits(import_edit, [replacement_edit])) - }); + let mut diagnostic = checker.report_diagnostic( + Airflow3MovedToProvider { + deprecated: qualified_name, + replacement: replacement.clone(), + }, + ranged, + ); + + if let Some(fix) = generate_import_edit(expr, checker, module, name, ranged) + .or_else(|| generate_remove_and_runtime_import_edit(expr, checker, module, name)) + { + diagnostic.set_fix(fix); + } } diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap index 6fe74a7765148d..398278fd91beaa 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_amazon.py.snap @@ -1,113 +1,252 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_amazon.py:23:1: AIR302 `airflow.hooks.S3_hook.S3Hook` is moved into `amazon` provider in Airflow 3.0; +AIR302_amazon.py:14:1: AIR302 [*] `airflow.hooks.S3_hook.S3Hook` is moved into `amazon` provider in Airflow 3.0; | -21 | from airflow.sensors.s3_key_sensor import S3KeySensor -22 | -23 | S3Hook() +12 | from airflow.sensors.s3_key_sensor import S3KeySensor +13 | +14 | S3Hook() | ^^^^^^ AIR302 -24 | provide_bucket_name() +15 | provide_bucket_name() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3Hook` from `airflow.providers.amazon.aws.hooks.s3` instead. -AIR302_amazon.py:24:1: AIR302 `airflow.hooks.S3_hook.provide_bucket_name` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.S3_hook import ( +4 |- S3Hook, +5 4 | provide_bucket_name, +6 5 | ) +7 6 | from airflow.operators.gcs_to_s3 import GCSToS3Operator +-------------------------------------------------------------------------------- +10 9 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.hooks.s3 import S3Hook +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:15:1: AIR302 [*] `airflow.hooks.S3_hook.provide_bucket_name` is moved into `amazon` provider in Airflow 3.0; | -23 | S3Hook() -24 | provide_bucket_name() +14 | S3Hook() +15 | provide_bucket_name() | ^^^^^^^^^^^^^^^^^^^ AIR302 -25 | -26 | GCSToS3Operator() +16 | +17 | GCSToS3Operator() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `provide_bucket_name` from `airflow.providers.amazon.aws.hooks.s3` instead. -AIR302_amazon.py:26:1: AIR302 `airflow.operators.gcs_to_s3.GCSToS3Operator` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.S3_hook import ( +4 4 | S3Hook, +5 |- provide_bucket_name, +6 5 | ) +7 6 | from airflow.operators.gcs_to_s3 import GCSToS3Operator +8 7 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +-------------------------------------------------------------------------------- +10 9 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.hooks.s3 import provide_bucket_name +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:17:1: AIR302 [*] `airflow.operators.gcs_to_s3.GCSToS3Operator` is moved into `amazon` provider in Airflow 3.0; | -24 | provide_bucket_name() -25 | -26 | GCSToS3Operator() +15 | provide_bucket_name() +16 | +17 | GCSToS3Operator() | ^^^^^^^^^^^^^^^ AIR302 -27 | -28 | GoogleApiToS3Operator() +18 | GoogleApiToS3Operator() +19 | RedshiftToS3Operator() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GCSToS3Operator` from `airflow.providers.amazon.aws.transfers.gcs_to_s3` instead. -AIR302_amazon.py:28:1: AIR302 `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Operator` is moved into `amazon` provider in Airflow 3.0; - | -26 | GCSToS3Operator() -27 | -28 | GoogleApiToS3Operator() - | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -29 | GoogleApiToS3Transfer() - | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead. +ℹ Unsafe fix +4 4 | S3Hook, +5 5 | provide_bucket_name, +6 6 | ) +7 |-from airflow.operators.gcs_to_s3 import GCSToS3Operator +8 7 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +9 8 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 9 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.transfers.gcs_to_s3 import GCSToS3Operator +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() -AIR302_amazon.py:29:1: AIR302 `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Transfer` is moved into `amazon` provider in Airflow 3.0; +AIR302_amazon.py:18:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Operator` is moved into `amazon` provider in Airflow 3.0; | -28 | GoogleApiToS3Operator() -29 | GoogleApiToS3Transfer() +17 | GCSToS3Operator() +18 | GoogleApiToS3Operator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -30 | -31 | RedshiftToS3Operator() +19 | RedshiftToS3Operator() +20 | S3FileTransformOperator() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead. -AIR302_amazon.py:31:1: AIR302 `airflow.operators.redshift_to_s3_operator.RedshiftToS3Operator` is moved into `amazon` provider in Airflow 3.0; - | -29 | GoogleApiToS3Transfer() -30 | -31 | RedshiftToS3Operator() - | ^^^^^^^^^^^^^^^^^^^^ AIR302 -32 | RedshiftToS3Transfer() - | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead. +ℹ Unsafe fix +5 5 | provide_bucket_name, +6 6 | ) +7 7 | from airflow.operators.gcs_to_s3 import GCSToS3Operator +8 |-from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +9 8 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 9 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.transfers.google_api_to_s3 import GoogleApiToS3Operator +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() -AIR302_amazon.py:32:1: AIR302 `airflow.operators.redshift_to_s3_operator.RedshiftToS3Transfer` is moved into `amazon` provider in Airflow 3.0; +AIR302_amazon.py:19:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Operator` is moved into `amazon` provider in Airflow 3.0; | -31 | RedshiftToS3Operator() -32 | RedshiftToS3Transfer() +17 | GCSToS3Operator() +18 | GoogleApiToS3Operator() +19 | RedshiftToS3Operator() | ^^^^^^^^^^^^^^^^^^^^ AIR302 -33 | -34 | S3FileTransformOperator() +20 | S3FileTransformOperator() +21 | S3ToRedshiftOperator() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead. -AIR302_amazon.py:34:1: AIR302 `airflow.operators.s3_file_transform_operator.S3FileTransformOperator` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +6 6 | ) +7 7 | from airflow.operators.gcs_to_s3 import GCSToS3Operator +8 8 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +9 |-from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 9 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.transfers.redshift_to_s3 import RedshiftToS3Operator +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:20:1: AIR302 [*] `airflow.operators.s3_file_transform_operator.S3FileTransformOperator` is moved into `amazon` provider in Airflow 3.0; | -32 | RedshiftToS3Transfer() -33 | -34 | S3FileTransformOperator() +18 | GoogleApiToS3Operator() +19 | RedshiftToS3Operator() +20 | S3FileTransformOperator() | ^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -35 | -36 | S3ToRedshiftOperator() +21 | S3ToRedshiftOperator() +22 | S3KeySensor() | = help: Install `apache-airflow-providers-amazon>=3.0.0` and use `S3FileTransformOperator` from `airflow.providers.amazon.aws.operators.s3` instead. -AIR302_amazon.py:36:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRedshiftOperator` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +7 7 | from airflow.operators.gcs_to_s3 import GCSToS3Operator +8 8 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +9 9 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 |-from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 10 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.operators.s3 import S3FileTransformOperator +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:21:1: AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftOperator` is moved into `amazon` provider in Airflow 3.0; | -34 | S3FileTransformOperator() -35 | -36 | S3ToRedshiftOperator() +19 | RedshiftToS3Operator() +20 | S3FileTransformOperator() +21 | S3ToRedshiftOperator() | ^^^^^^^^^^^^^^^^^^^^ AIR302 -37 | S3ToRedshiftTransfer() +22 | S3KeySensor() | = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead. -AIR302_amazon.py:37:1: AIR302 `airflow.operators.s3_to_redshift_operator.S3ToRedshiftTransfer` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +8 8 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator +9 9 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 10 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 |-from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 11 | from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.transfers.s3_to_redshift import S3ToRedshiftOperator +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:22:1: AIR302 [*] `airflow.sensors.s3_key_sensor.S3KeySensor` is moved into `amazon` provider in Airflow 3.0; + | +20 | S3FileTransformOperator() +21 | S3ToRedshiftOperator() +22 | S3KeySensor() + | ^^^^^^^^^^^ AIR302 +23 | +24 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer + | + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3KeySensor` from `airflow.providers.amazon.aws.sensors.s3` instead. + +ℹ Unsafe fix +9 9 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator +10 10 | from airflow.operators.s3_file_transform_operator import S3FileTransformOperator +11 11 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftOperator +12 |-from airflow.sensors.s3_key_sensor import S3KeySensor + 12 |+from airflow.providers.amazon.aws.sensors.s3 import S3KeySensor +13 13 | +14 14 | S3Hook() +15 15 | provide_bucket_name() + +AIR302_amazon.py:26:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Transfer` is moved into `amazon` provider in Airflow 3.0; | -36 | S3ToRedshiftOperator() -37 | S3ToRedshiftTransfer() +24 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer +25 | +26 | GoogleApiToS3Transfer() + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +27 | +28 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer + | + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead. + +ℹ Unsafe fix +22 22 | S3KeySensor() +23 23 | +24 24 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer + 25 |+from airflow.providers.amazon.aws.transfers.google_api_to_s3 import GoogleApiToS3Operator +25 26 | +26 27 | GoogleApiToS3Transfer() +27 28 | + +AIR302_amazon.py:30:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Transfer` is moved into `amazon` provider in Airflow 3.0; + | +28 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer +29 | +30 | RedshiftToS3Transfer() | ^^^^^^^^^^^^^^^^^^^^ AIR302 -38 | -39 | S3KeySensor() +31 | +32 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead. -AIR302_amazon.py:39:1: AIR302 `airflow.sensors.s3_key_sensor.S3KeySensor` is moved into `amazon` provider in Airflow 3.0; +ℹ Unsafe fix +26 26 | GoogleApiToS3Transfer() +27 27 | +28 28 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer + 29 |+from airflow.providers.amazon.aws.transfers.redshift_to_s3 import RedshiftToS3Operator +29 30 | +30 31 | RedshiftToS3Transfer() +31 32 | + +AIR302_amazon.py:34:1: AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftTransfer` is moved into `amazon` provider in Airflow 3.0; | -37 | S3ToRedshiftTransfer() -38 | -39 | S3KeySensor() - | ^^^^^^^^^^^ AIR302 +32 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer +33 | +34 | S3ToRedshiftTransfer() + | ^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3KeySensor` from `airflow.providers.amazon.aws.sensors.s3` instead. + = help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead. + +ℹ Unsafe fix +30 30 | RedshiftToS3Transfer() +31 31 | +32 32 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer + 33 |+from airflow.providers.amazon.aws.transfers.s3_to_redshift import S3ToRedshiftOperator +33 34 | +34 35 | S3ToRedshiftTransfer() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap index 7a07ac8ac20bfe..991c2bcad81186 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_celery.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_celery.py:9:1: AIR302 `airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG` is moved into `celery` provider in Airflow 3.0; +AIR302_celery.py:9:1: AIR302 [*] `airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG` is moved into `celery` provider in Airflow 3.0; | 7 | ) 8 | @@ -12,7 +12,20 @@ AIR302_celery.py:9:1: AIR302 `airflow.config_templates.default_celery.DEFAULT_CE | = help: Install `apache-airflow-providers-celery>=3.3.0` and use `DEFAULT_CELERY_CONFIG` from `airflow.providers.celery.executors.default_celery` instead. -AIR302_celery.py:11:1: AIR302 `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.config_templates.default_celery import DEFAULT_CELERY_CONFIG +4 3 | from airflow.executors.celery_executor import ( +5 4 | CeleryExecutor, +6 5 | app, +7 6 | ) + 7 |+from airflow.providers.celery.executors.default_celery import DEFAULT_CELERY_CONFIG +8 8 | +9 9 | DEFAULT_CELERY_CONFIG +10 10 | + +AIR302_celery.py:11:1: AIR302 [*] `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0; | 9 | DEFAULT_CELERY_CONFIG 10 | @@ -22,10 +35,33 @@ AIR302_celery.py:11:1: AIR302 `airflow.executors.celery_executor.app` is moved i | = help: Install `apache-airflow-providers-celery>=3.3.0` and use `app` from `airflow.providers.celery.executors.celery_executor_utils` instead. -AIR302_celery.py:12:1: AIR302 `airflow.executors.celery_executor.CeleryExecutor` is moved into `celery` provider in Airflow 3.0; +ℹ Unsafe fix +3 3 | from airflow.config_templates.default_celery import DEFAULT_CELERY_CONFIG +4 4 | from airflow.executors.celery_executor import ( +5 5 | CeleryExecutor, +6 |- app, +7 6 | ) + 7 |+from airflow.providers.celery.executors.celery_executor_utils import app +8 8 | +9 9 | DEFAULT_CELERY_CONFIG +10 10 | + +AIR302_celery.py:12:1: AIR302 [*] `airflow.executors.celery_executor.CeleryExecutor` is moved into `celery` provider in Airflow 3.0; | 11 | app 12 | CeleryExecutor() | ^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-celery>=3.3.0` and use `CeleryExecutor` from `airflow.providers.celery.executors.celery_executor` instead. + +ℹ Unsafe fix +2 2 | +3 3 | from airflow.config_templates.default_celery import DEFAULT_CELERY_CONFIG +4 4 | from airflow.executors.celery_executor import ( +5 |- CeleryExecutor, +6 5 | app, +7 6 | ) + 7 |+from airflow.providers.celery.executors.celery_executor import CeleryExecutor +8 8 | +9 9 | DEFAULT_CELERY_CONFIG +10 10 | diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap index 0ea625549ad523..22798b40115783 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_common_sql.py.snap @@ -1,290 +1,639 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_common_sql.py:10:1: AIR302 `airflow.hooks.dbapi.ConnectorProtocol` is moved into `common-sql` provider in Airflow 3.0; +AIR302_common_sql.py:8:1: AIR302 [*] `airflow.hooks.dbapi.ConnectorProtocol` is moved into `common-sql` provider in Airflow 3.0; + | +6 | ) +7 | +8 | ConnectorProtocol() + | ^^^^^^^^^^^^^^^^^ AIR302 +9 | DbApiHook() + | + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `ConnectorProtocol` from `airflow.providers.common.sql.hooks.sql` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.dbapi import ( +4 |- ConnectorProtocol, +5 4 | DbApiHook, +6 5 | ) + 6 |+from airflow.providers.common.sql.hooks.sql import ConnectorProtocol +7 7 | +8 8 | ConnectorProtocol() +9 9 | DbApiHook() + +AIR302_common_sql.py:9:1: AIR302 [*] `airflow.hooks.dbapi.DbApiHook` is moved into `common-sql` provider in Airflow 3.0; | - 8 | from airflow.operators.check_operator import SQLCheckOperator - 9 | -10 | ConnectorProtocol() - | ^^^^^^^^^^^^^^^^^ AIR302 -11 | DbApiHook() -12 | SQLCheckOperator() + 8 | ConnectorProtocol() + 9 | DbApiHook() + | ^^^^^^^^^ AIR302 +10 | +11 | from airflow.hooks.dbapi_hook import DbApiHook | - = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `ConnectorProtocol` from `airflow.providers.common.sql.hooks.sql` instead. + = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead. + +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.dbapi import ( +4 4 | ConnectorProtocol, +5 |- DbApiHook, +6 5 | ) + 6 |+from airflow.providers.common.sql.hooks.sql import DbApiHook +7 7 | +8 8 | ConnectorProtocol() +9 9 | DbApiHook() -AIR302_common_sql.py:11:1: AIR302 `airflow.hooks.dbapi_hook.DbApiHook` is moved into `common-sql` provider in Airflow 3.0; +AIR302_common_sql.py:14:1: AIR302 [*] `airflow.hooks.dbapi_hook.DbApiHook` is moved into `common-sql` provider in Airflow 3.0; | -10 | ConnectorProtocol() -11 | DbApiHook() +12 | from airflow.operators.check_operator import SQLCheckOperator +13 | +14 | DbApiHook() | ^^^^^^^^^ AIR302 -12 | SQLCheckOperator() +15 | SQLCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead. -AIR302_common_sql.py:12:1: AIR302 `airflow.operators.check_operator.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +8 8 | ConnectorProtocol() +9 9 | DbApiHook() +10 10 | +11 |-from airflow.hooks.dbapi_hook import DbApiHook +12 11 | from airflow.operators.check_operator import SQLCheckOperator + 12 |+from airflow.providers.common.sql.hooks.sql import DbApiHook +13 13 | +14 14 | DbApiHook() +15 15 | SQLCheckOperator() + +AIR302_common_sql.py:15:1: AIR302 [*] `airflow.operators.check_operator.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -10 | ConnectorProtocol() -11 | DbApiHook() -12 | SQLCheckOperator() +14 | DbApiHook() +15 | SQLCheckOperator() | ^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:18:1: AIR302 `airflow.operators.sql.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +9 9 | DbApiHook() +10 10 | +11 11 | from airflow.hooks.dbapi_hook import DbApiHook +12 |-from airflow.operators.check_operator import SQLCheckOperator + 12 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +13 13 | +14 14 | DbApiHook() +15 15 | SQLCheckOperator() + +AIR302_common_sql.py:21:1: AIR302 [*] `airflow.operators.sql.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -16 | from airflow.operators.sql import SQLCheckOperator -17 | -18 | SQLCheckOperator() +19 | from airflow.operators.sql import SQLCheckOperator +20 | +21 | SQLCheckOperator() | ^^^^^^^^^^^^^^^^ AIR302 -19 | CheckOperator() +22 | CheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:19:1: AIR302 `airflow.operators.check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +16 16 | +17 17 | +18 18 | from airflow.operators.check_operator import CheckOperator +19 |-from airflow.operators.sql import SQLCheckOperator + 19 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +20 20 | +21 21 | SQLCheckOperator() +22 22 | CheckOperator() + +AIR302_common_sql.py:22:1: AIR302 [*] `airflow.operators.check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -18 | SQLCheckOperator() -19 | CheckOperator() +21 | SQLCheckOperator() +22 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:24:1: AIR302 `airflow.operators.druid_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +17 17 | +18 18 | from airflow.operators.check_operator import CheckOperator +19 19 | from airflow.operators.sql import SQLCheckOperator + 20 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +20 21 | +21 22 | SQLCheckOperator() +22 23 | CheckOperator() + +AIR302_common_sql.py:27:1: AIR302 [*] `airflow.operators.druid_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -22 | from airflow.operators.druid_check_operator import CheckOperator -23 | -24 | CheckOperator() +25 | from airflow.operators.druid_check_operator import CheckOperator +26 | +27 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:29:1: AIR302 `airflow.operators.presto_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +23 23 | +24 24 | +25 25 | from airflow.operators.druid_check_operator import CheckOperator + 26 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +26 27 | +27 28 | CheckOperator() +28 29 | + +AIR302_common_sql.py:32:1: AIR302 [*] `airflow.operators.presto_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -27 | from airflow.operators.presto_check_operator import CheckOperator -28 | -29 | CheckOperator() +30 | from airflow.operators.presto_check_operator import CheckOperator +31 | +32 | CheckOperator() | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:39:1: AIR302 `airflow.operators.druid_check_operator.DruidCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +28 28 | +29 29 | +30 30 | from airflow.operators.presto_check_operator import CheckOperator + 31 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +31 32 | +32 33 | CheckOperator() +33 34 | + +AIR302_common_sql.py:42:1: AIR302 [*] `airflow.operators.druid_check_operator.DruidCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -37 | from airflow.operators.presto_check_operator import PrestoCheckOperator -38 | -39 | DruidCheckOperator() +40 | from airflow.operators.presto_check_operator import PrestoCheckOperator +41 | +42 | DruidCheckOperator() | ^^^^^^^^^^^^^^^^^^ AIR302 -40 | PrestoCheckOperator() -41 | IntervalCheckOperator() +43 | PrestoCheckOperator() +44 | IntervalCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:40:1: AIR302 `airflow.operators.presto_check_operator.PrestoCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +38 38 | ) +39 39 | from airflow.operators.druid_check_operator import DruidCheckOperator +40 40 | from airflow.operators.presto_check_operator import PrestoCheckOperator + 41 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +41 42 | +42 43 | DruidCheckOperator() +43 44 | PrestoCheckOperator() + +AIR302_common_sql.py:43:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -39 | DruidCheckOperator() -40 | PrestoCheckOperator() +42 | DruidCheckOperator() +43 | PrestoCheckOperator() | ^^^^^^^^^^^^^^^^^^^ AIR302 -41 | IntervalCheckOperator() -42 | SQLIntervalCheckOperator() +44 | IntervalCheckOperator() +45 | SQLIntervalCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:41:1: AIR302 `airflow.operators.check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +38 38 | ) +39 39 | from airflow.operators.druid_check_operator import DruidCheckOperator +40 40 | from airflow.operators.presto_check_operator import PrestoCheckOperator + 41 |+from airflow.providers.common.sql.operators.sql import SQLCheckOperator +41 42 | +42 43 | DruidCheckOperator() +43 44 | PrestoCheckOperator() + +AIR302_common_sql.py:44:1: AIR302 [*] `airflow.operators.check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -39 | DruidCheckOperator() -40 | PrestoCheckOperator() -41 | IntervalCheckOperator() +42 | DruidCheckOperator() +43 | PrestoCheckOperator() +44 | IntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -42 | SQLIntervalCheckOperator() +45 | SQLIntervalCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:42:1: AIR302 `airflow.operators.check_operator.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +34 34 | +35 35 | from airflow.operators.check_operator import ( +36 36 | IntervalCheckOperator, +37 |- SQLIntervalCheckOperator, +38 37 | ) +39 38 | from airflow.operators.druid_check_operator import DruidCheckOperator +40 39 | from airflow.operators.presto_check_operator import PrestoCheckOperator + 40 |+from airflow.providers.common.sql.operators.sql import SQLIntervalCheckOperator +41 41 | +42 42 | DruidCheckOperator() +43 43 | PrestoCheckOperator() + +AIR302_common_sql.py:45:1: AIR302 [*] `airflow.operators.check_operator.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -40 | PrestoCheckOperator() -41 | IntervalCheckOperator() -42 | SQLIntervalCheckOperator() +43 | PrestoCheckOperator() +44 | IntervalCheckOperator() +45 | SQLIntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:51:1: AIR302 `airflow.operators.presto_check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +34 34 | +35 35 | from airflow.operators.check_operator import ( +36 36 | IntervalCheckOperator, +37 |- SQLIntervalCheckOperator, +38 37 | ) +39 38 | from airflow.operators.druid_check_operator import DruidCheckOperator +40 39 | from airflow.operators.presto_check_operator import PrestoCheckOperator + 40 |+from airflow.providers.common.sql.operators.sql import SQLIntervalCheckOperator +41 41 | +42 42 | DruidCheckOperator() +43 43 | PrestoCheckOperator() + +AIR302_common_sql.py:54:1: AIR302 [*] `airflow.operators.presto_check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -49 | from airflow.operators.sql import SQLIntervalCheckOperator -50 | -51 | IntervalCheckOperator() +52 | from airflow.operators.sql import SQLIntervalCheckOperator +53 | +54 | IntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -52 | SQLIntervalCheckOperator() -53 | PrestoIntervalCheckOperator() +55 | SQLIntervalCheckOperator() +56 | PrestoIntervalCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:52:1: AIR302 `airflow.operators.sql.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +50 50 | PrestoIntervalCheckOperator, +51 51 | ) +52 52 | from airflow.operators.sql import SQLIntervalCheckOperator + 53 |+from airflow.providers.common.sql.operators.sql import SQLIntervalCheckOperator +53 54 | +54 55 | IntervalCheckOperator() +55 56 | SQLIntervalCheckOperator() + +AIR302_common_sql.py:55:1: AIR302 [*] `airflow.operators.sql.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -51 | IntervalCheckOperator() -52 | SQLIntervalCheckOperator() +54 | IntervalCheckOperator() +55 | SQLIntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -53 | PrestoIntervalCheckOperator() +56 | PrestoIntervalCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:53:1: AIR302 `airflow.operators.presto_check_operator.PrestoIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +49 49 | IntervalCheckOperator, +50 50 | PrestoIntervalCheckOperator, +51 51 | ) +52 |-from airflow.operators.sql import SQLIntervalCheckOperator + 52 |+from airflow.providers.common.sql.operators.sql import SQLIntervalCheckOperator +53 53 | +54 54 | IntervalCheckOperator() +55 55 | SQLIntervalCheckOperator() + +AIR302_common_sql.py:56:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -51 | IntervalCheckOperator() -52 | SQLIntervalCheckOperator() -53 | PrestoIntervalCheckOperator() +54 | IntervalCheckOperator() +55 | SQLIntervalCheckOperator() +56 | PrestoIntervalCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:61:1: AIR302 `airflow.operators.check_operator.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +50 50 | PrestoIntervalCheckOperator, +51 51 | ) +52 52 | from airflow.operators.sql import SQLIntervalCheckOperator + 53 |+from airflow.providers.common.sql.operators.sql import SQLIntervalCheckOperator +53 54 | +54 55 | IntervalCheckOperator() +55 56 | SQLIntervalCheckOperator() + +AIR302_common_sql.py:64:1: AIR302 [*] `airflow.operators.check_operator.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -59 | ) -60 | -61 | SQLThresholdCheckOperator() +62 | ) +63 | +64 | SQLThresholdCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -62 | ThresholdCheckOperator() +65 | ThresholdCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:62:1: AIR302 `airflow.operators.check_operator.ThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +57 57 | +58 58 | +59 59 | from airflow.operators.check_operator import ( +60 |- SQLThresholdCheckOperator, +61 60 | ThresholdCheckOperator, +62 61 | ) + 62 |+from airflow.providers.common.sql.operators.sql import SQLThresholdCheckOperator +63 63 | +64 64 | SQLThresholdCheckOperator() +65 65 | ThresholdCheckOperator() + +AIR302_common_sql.py:65:1: AIR302 [*] `airflow.operators.check_operator.ThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -61 | SQLThresholdCheckOperator() -62 | ThresholdCheckOperator() +64 | SQLThresholdCheckOperator() +65 | ThresholdCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:67:1: AIR302 `airflow.operators.sql.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +57 57 | +58 58 | +59 59 | from airflow.operators.check_operator import ( +60 |- SQLThresholdCheckOperator, +61 60 | ThresholdCheckOperator, +62 61 | ) + 62 |+from airflow.providers.common.sql.operators.sql import SQLThresholdCheckOperator +63 63 | +64 64 | SQLThresholdCheckOperator() +65 65 | ThresholdCheckOperator() + +AIR302_common_sql.py:70:1: AIR302 [*] `airflow.operators.sql.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -65 | from airflow.operators.sql import SQLThresholdCheckOperator -66 | -67 | SQLThresholdCheckOperator() +68 | from airflow.operators.sql import SQLThresholdCheckOperator +69 | +70 | SQLThresholdCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:75:1: AIR302 `airflow.operators.check_operator.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +65 65 | ThresholdCheckOperator() +66 66 | +67 67 | +68 |-from airflow.operators.sql import SQLThresholdCheckOperator + 68 |+from airflow.providers.common.sql.operators.sql import SQLThresholdCheckOperator +69 69 | +70 70 | SQLThresholdCheckOperator() +71 71 | + +AIR302_common_sql.py:78:1: AIR302 [*] `airflow.operators.check_operator.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -73 | ) -74 | -75 | SQLValueCheckOperator() +76 | ) +77 | +78 | SQLValueCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -76 | ValueCheckOperator() +79 | ValueCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:76:1: AIR302 `airflow.operators.check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +71 71 | +72 72 | +73 73 | from airflow.operators.check_operator import ( +74 |- SQLValueCheckOperator, +75 74 | ValueCheckOperator, +76 75 | ) + 76 |+from airflow.providers.common.sql.operators.sql import SQLValueCheckOperator +77 77 | +78 78 | SQLValueCheckOperator() +79 79 | ValueCheckOperator() + +AIR302_common_sql.py:79:1: AIR302 [*] `airflow.operators.check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -75 | SQLValueCheckOperator() -76 | ValueCheckOperator() +78 | SQLValueCheckOperator() +79 | ValueCheckOperator() | ^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:85:1: AIR302 `airflow.operators.sql.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +71 71 | +72 72 | +73 73 | from airflow.operators.check_operator import ( +74 |- SQLValueCheckOperator, +75 74 | ValueCheckOperator, +76 75 | ) + 76 |+from airflow.providers.common.sql.operators.sql import SQLValueCheckOperator +77 77 | +78 78 | SQLValueCheckOperator() +79 79 | ValueCheckOperator() + +AIR302_common_sql.py:88:1: AIR302 [*] `airflow.operators.sql.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -83 | from airflow.operators.sql import SQLValueCheckOperator -84 | -85 | SQLValueCheckOperator() +86 | from airflow.operators.sql import SQLValueCheckOperator +87 | +88 | SQLValueCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -86 | ValueCheckOperator() -87 | PrestoValueCheckOperator() +89 | ValueCheckOperator() +90 | PrestoValueCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:86:1: AIR302 `airflow.operators.presto_check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +83 83 | PrestoValueCheckOperator, +84 84 | ValueCheckOperator, +85 85 | ) +86 |-from airflow.operators.sql import SQLValueCheckOperator + 86 |+from airflow.providers.common.sql.operators.sql import SQLValueCheckOperator +87 87 | +88 88 | SQLValueCheckOperator() +89 89 | ValueCheckOperator() + +AIR302_common_sql.py:89:1: AIR302 [*] `airflow.operators.presto_check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -85 | SQLValueCheckOperator() -86 | ValueCheckOperator() +88 | SQLValueCheckOperator() +89 | ValueCheckOperator() | ^^^^^^^^^^^^^^^^^^ AIR302 -87 | PrestoValueCheckOperator() +90 | PrestoValueCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:87:1: AIR302 `airflow.operators.presto_check_operator.PrestoValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +84 84 | ValueCheckOperator, +85 85 | ) +86 86 | from airflow.operators.sql import SQLValueCheckOperator + 87 |+from airflow.providers.common.sql.operators.sql import SQLValueCheckOperator +87 88 | +88 89 | SQLValueCheckOperator() +89 90 | ValueCheckOperator() + +AIR302_common_sql.py:90:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -85 | SQLValueCheckOperator() -86 | ValueCheckOperator() -87 | PrestoValueCheckOperator() +88 | SQLValueCheckOperator() +89 | ValueCheckOperator() +90 | PrestoValueCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:99:1: AIR302 `airflow.operators.sql.BaseSQLOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +84 84 | ValueCheckOperator, +85 85 | ) +86 86 | from airflow.operators.sql import SQLValueCheckOperator + 87 |+from airflow.providers.common.sql.operators.sql import SQLValueCheckOperator +87 88 | +88 89 | SQLValueCheckOperator() +89 90 | ValueCheckOperator() + +AIR302_common_sql.py:102:1: AIR302 [*] `airflow.operators.sql.BaseSQLOperator` is moved into `common-sql` provider in Airflow 3.0; | - 97 | ) - 98 | - 99 | BaseSQLOperator() +100 | ) +101 | +102 | BaseSQLOperator() | ^^^^^^^^^^^^^^^ AIR302 -100 | BranchSQLOperator() -101 | SQLTableCheckOperator() +103 | BranchSQLOperator() +104 | SQLTableCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BaseSQLOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:100:1: AIR302 `airflow.operators.sql.BranchSQLOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +91 91 | +92 92 | +93 93 | from airflow.operators.sql import ( +94 |- BaseSQLOperator, +95 94 | BranchSQLOperator, +96 95 | SQLColumnCheckOperator, +97 96 | SQLTableCheckOperator, +98 97 | _convert_to_float_if_possible, +99 98 | parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import BaseSQLOperator +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:103:1: AIR302 [*] `airflow.operators.sql.BranchSQLOperator` is moved into `common-sql` provider in Airflow 3.0; | - 99 | BaseSQLOperator() -100 | BranchSQLOperator() +102 | BaseSQLOperator() +103 | BranchSQLOperator() | ^^^^^^^^^^^^^^^^^ AIR302 -101 | SQLTableCheckOperator() -102 | SQLColumnCheckOperator() +104 | SQLTableCheckOperator() +105 | SQLColumnCheckOperator() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BranchSQLOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:101:1: AIR302 `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +92 92 | +93 93 | from airflow.operators.sql import ( +94 94 | BaseSQLOperator, +95 |- BranchSQLOperator, +96 95 | SQLColumnCheckOperator, +97 96 | SQLTableCheckOperator, +98 97 | _convert_to_float_if_possible, +99 98 | parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import BranchSQLOperator +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:104:1: AIR302 [*] `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | - 99 | BaseSQLOperator() -100 | BranchSQLOperator() -101 | SQLTableCheckOperator() +102 | BaseSQLOperator() +103 | BranchSQLOperator() +104 | SQLTableCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -102 | SQLColumnCheckOperator() -103 | _convert_to_float_if_possible() +105 | SQLColumnCheckOperator() +106 | _convert_to_float_if_possible() | = help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLTableCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:102:1: AIR302 `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +94 94 | BaseSQLOperator, +95 95 | BranchSQLOperator, +96 96 | SQLColumnCheckOperator, +97 |- SQLTableCheckOperator, +98 97 | _convert_to_float_if_possible, +99 98 | parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import SQLTableCheckOperator +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:105:1: AIR302 [*] `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0; | -100 | BranchSQLOperator() -101 | SQLTableCheckOperator() -102 | SQLColumnCheckOperator() +103 | BranchSQLOperator() +104 | SQLTableCheckOperator() +105 | SQLColumnCheckOperator() | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 -103 | _convert_to_float_if_possible() -104 | parse_boolean() +106 | _convert_to_float_if_possible() +107 | parse_boolean() | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SQLColumnCheckOperator` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:103:1: AIR302 `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +93 93 | from airflow.operators.sql import ( +94 94 | BaseSQLOperator, +95 95 | BranchSQLOperator, +96 |- SQLColumnCheckOperator, +97 96 | SQLTableCheckOperator, +98 97 | _convert_to_float_if_possible, +99 98 | parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import SQLColumnCheckOperator +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:106:1: AIR302 [*] `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0; | -101 | SQLTableCheckOperator() -102 | SQLColumnCheckOperator() -103 | _convert_to_float_if_possible() +104 | SQLTableCheckOperator() +105 | SQLColumnCheckOperator() +106 | _convert_to_float_if_possible() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -104 | parse_boolean() +107 | parse_boolean() | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `_convert_to_float_if_possible` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:104:1: AIR302 `airflow.operators.sql.parse_boolean` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +95 95 | BranchSQLOperator, +96 96 | SQLColumnCheckOperator, +97 97 | SQLTableCheckOperator, +98 |- _convert_to_float_if_possible, +99 98 | parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import _convert_to_float_if_possible +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:107:1: AIR302 [*] `airflow.operators.sql.parse_boolean` is moved into `common-sql` provider in Airflow 3.0; | -102 | SQLColumnCheckOperator() -103 | _convert_to_float_if_possible() -104 | parse_boolean() +105 | SQLColumnCheckOperator() +106 | _convert_to_float_if_possible() +107 | parse_boolean() | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `parse_boolean` from `airflow.providers.common.sql.operators.sql` instead. -AIR302_common_sql.py:109:1: AIR302 `airflow.sensors.sql.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +96 96 | SQLColumnCheckOperator, +97 97 | SQLTableCheckOperator, +98 98 | _convert_to_float_if_possible, +99 |- parse_boolean, +100 99 | ) + 100 |+from airflow.providers.common.sql.operators.sql import parse_boolean +101 101 | +102 102 | BaseSQLOperator() +103 103 | BranchSQLOperator() + +AIR302_common_sql.py:112:1: AIR302 [*] `airflow.sensors.sql.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; | -107 | from airflow.sensors.sql import SqlSensor -108 | -109 | SqlSensor() +110 | from airflow.sensors.sql import SqlSensor +111 | +112 | SqlSensor() | ^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead. -AIR302_common_sql.py:114:1: AIR302 `airflow.sensors.sql_sensor.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; +ℹ Unsafe fix +107 107 | parse_boolean() +108 108 | +109 109 | +110 |-from airflow.sensors.sql import SqlSensor + 110 |+from airflow.providers.common.sql.sensors.sql import SqlSensor +111 111 | +112 112 | SqlSensor() +113 113 | + +AIR302_common_sql.py:117:1: AIR302 [*] `airflow.sensors.sql_sensor.SqlSensor` is moved into `common-sql` provider in Airflow 3.0; | -112 | from airflow.sensors.sql_sensor import SqlSensor -113 | -114 | SqlSensor() +115 | from airflow.sensors.sql_sensor import SqlSensor +116 | +117 | SqlSensor() | ^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead. + +ℹ Unsafe fix +112 112 | SqlSensor() +113 113 | +114 114 | +115 |-from airflow.sensors.sql_sensor import SqlSensor + 115 |+from airflow.providers.common.sql.sensors.sql import SqlSensor +116 116 | +117 117 | SqlSensor() +118 118 | diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap index 9faa2e670baf44..5015a44a45113e 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_daskexecutor.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_daskexecutor.py:5:1: AIR302 `airflow.executors.dask_executor.DaskExecutor` is moved into `daskexecutor` provider in Airflow 3.0; +AIR302_daskexecutor.py:5:1: AIR302 [*] `airflow.executors.dask_executor.DaskExecutor` is moved into `daskexecutor` provider in Airflow 3.0; | 3 | from airflow.executors.dask_executor import DaskExecutor 4 | @@ -9,3 +9,11 @@ AIR302_daskexecutor.py:5:1: AIR302 `airflow.executors.dask_executor.DaskExecutor | ^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-daskexecutor>=1.0.0` and use `DaskExecutor` from `airflow.providers.daskexecutor.executors.dask_executor` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.executors.dask_executor import DaskExecutor + 3 |+from airflow.providers.daskexecutor.executors.dask_executor import DaskExecutor +4 4 | +5 5 | DaskExecutor() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap index d033a654e03905..9dc5ba8ef9f5f2 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_druid.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_druid.py:12:1: AIR302 `airflow.hooks.druid_hook.DruidDbApiHook` is moved into `apache-druid` provider in Airflow 3.0; +AIR302_druid.py:12:1: AIR302 [*] `airflow.hooks.druid_hook.DruidDbApiHook` is moved into `apache-druid` provider in Airflow 3.0; | 10 | ) 11 | @@ -11,7 +11,23 @@ AIR302_druid.py:12:1: AIR302 `airflow.hooks.druid_hook.DruidDbApiHook` is moved | = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidDbApiHook` from `airflow.providers.apache.druid.hooks.druid` instead. -AIR302_druid.py:13:1: AIR302 `airflow.hooks.druid_hook.DruidHook` is moved into `apache-druid` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.druid_hook import ( +4 |- DruidDbApiHook, +5 4 | DruidHook, +6 5 | ) +7 6 | from airflow.operators.hive_to_druid import ( +8 7 | HiveToDruidOperator, +9 8 | HiveToDruidTransfer, +10 9 | ) + 10 |+from airflow.providers.apache.druid.hooks.druid import DruidDbApiHook +11 11 | +12 12 | DruidDbApiHook() +13 13 | DruidHook() + +AIR302_druid.py:13:1: AIR302 [*] `airflow.hooks.druid_hook.DruidHook` is moved into `apache-druid` provider in Airflow 3.0; | 12 | DruidDbApiHook() 13 | DruidHook() @@ -21,7 +37,22 @@ AIR302_druid.py:13:1: AIR302 `airflow.hooks.druid_hook.DruidHook` is moved into | = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidHook` from `airflow.providers.apache.druid.hooks.druid` instead. -AIR302_druid.py:15:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidOperator` is moved into `apache-druid` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.druid_hook import ( +4 4 | DruidDbApiHook, +5 |- DruidHook, +6 5 | ) +7 6 | from airflow.operators.hive_to_druid import ( +8 7 | HiveToDruidOperator, +9 8 | HiveToDruidTransfer, +10 9 | ) + 10 |+from airflow.providers.apache.druid.hooks.druid import DruidHook +11 11 | +12 12 | DruidDbApiHook() +13 13 | DruidHook() + +AIR302_druid.py:15:1: AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidOperator` is moved into `apache-druid` provider in Airflow 3.0; | 13 | DruidHook() 14 | @@ -31,10 +62,34 @@ AIR302_druid.py:15:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidOperato | = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead. -AIR302_druid.py:16:1: AIR302 `airflow.operators.hive_to_druid.HiveToDruidTransfer` is moved into `apache-druid` provider in Airflow 3.0; +ℹ Unsafe fix +5 5 | DruidHook, +6 6 | ) +7 7 | from airflow.operators.hive_to_druid import ( +8 |- HiveToDruidOperator, +9 8 | HiveToDruidTransfer, +10 9 | ) + 10 |+from airflow.providers.apache.druid.transfers.hive_to_druid import HiveToDruidOperator +11 11 | +12 12 | DruidDbApiHook() +13 13 | DruidHook() + +AIR302_druid.py:16:1: AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidTransfer` is moved into `apache-druid` provider in Airflow 3.0; | 15 | HiveToDruidOperator() 16 | HiveToDruidTransfer() | ^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead. + +ℹ Unsafe fix +5 5 | DruidHook, +6 6 | ) +7 7 | from airflow.operators.hive_to_druid import ( +8 |- HiveToDruidOperator, +9 8 | HiveToDruidTransfer, +10 9 | ) + 10 |+from airflow.providers.apache.druid.transfers.hive_to_druid import HiveToDruidOperator +11 11 | +12 12 | DruidDbApiHook() +13 13 | DruidHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap index e8dcbedc9f9797..1bd1da740f6d96 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_fab.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_fab.py:10:1: AIR302 `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; +AIR302_fab.py:10:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; | 8 | ) 9 | @@ -12,7 +12,21 @@ AIR302_fab.py:10:1: AIR302 `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. -AIR302_fab.py:11:1: AIR302 `airflow.api.auth.backend.basic_auth.init_app` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.api.auth.backend.basic_auth import ( +4 |- CLIENT_AUTH, +5 4 | auth_current_user, +6 5 | init_app, +7 6 | requires_authentication, +8 7 | ) + 8 |+from airflow.providers.fab.auth_manager.api.auth.backend.basic_auth import CLIENT_AUTH +9 9 | +10 10 | CLIENT_AUTH +11 11 | init_app() + +AIR302_fab.py:11:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.init_app` is moved into `fab` provider in Airflow 3.0; | 10 | CLIENT_AUTH 11 | init_app() @@ -22,7 +36,19 @@ AIR302_fab.py:11:1: AIR302 `airflow.api.auth.backend.basic_auth.init_app` is mov | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. -AIR302_fab.py:12:1: AIR302 `airflow.api.auth.backend.basic_auth.auth_current_user` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +3 3 | from airflow.api.auth.backend.basic_auth import ( +4 4 | CLIENT_AUTH, +5 5 | auth_current_user, +6 |- init_app, +7 6 | requires_authentication, +8 7 | ) + 8 |+from airflow.providers.fab.auth_manager.api.auth.backend.basic_auth import init_app +9 9 | +10 10 | CLIENT_AUTH +11 11 | init_app() + +AIR302_fab.py:12:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.auth_current_user` is moved into `fab` provider in Airflow 3.0; | 10 | CLIENT_AUTH 11 | init_app() @@ -32,7 +58,20 @@ AIR302_fab.py:12:1: AIR302 `airflow.api.auth.backend.basic_auth.auth_current_use | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `auth_current_user` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. -AIR302_fab.py:13:1: AIR302 `airflow.api.auth.backend.basic_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.api.auth.backend.basic_auth import ( +4 4 | CLIENT_AUTH, +5 |- auth_current_user, +6 5 | init_app, +7 6 | requires_authentication, +8 7 | ) + 8 |+from airflow.providers.fab.auth_manager.api.auth.backend.basic_auth import auth_current_user +9 9 | +10 10 | CLIENT_AUTH +11 11 | init_app() + +AIR302_fab.py:13:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | 11 | init_app() 12 | auth_current_user() @@ -43,7 +82,18 @@ AIR302_fab.py:13:1: AIR302 `airflow.api.auth.backend.basic_auth.requires_authent | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead. -AIR302_fab.py:23:1: AIR302 `airflow.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +4 4 | CLIENT_AUTH, +5 5 | auth_current_user, +6 6 | init_app, +7 |- requires_authentication, +8 7 | ) + 8 |+from airflow.providers.fab.auth_manager.api.auth.backend.basic_auth import requires_authentication +9 9 | +10 10 | CLIENT_AUTH +11 11 | init_app() + +AIR302_fab.py:23:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; | 21 | ) 22 | @@ -54,7 +104,19 @@ AIR302_fab.py:23:1: AIR302 `airflow.api.auth.backend.kerberos_auth.log` is moved | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:24:1: AIR302 `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +16 16 | CLIENT_AUTH, +17 17 | find_user, +18 18 | init_app, +19 |- log, +20 19 | requires_authentication, +21 20 | ) + 21 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import log +22 22 | +23 23 | log() +24 24 | CLIENT_AUTH + +AIR302_fab.py:24:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; | 23 | log() 24 | CLIENT_AUTH @@ -64,7 +126,22 @@ AIR302_fab.py:24:1: AIR302 `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:25:1: AIR302 `airflow.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +13 13 | requires_authentication() +14 14 | +15 15 | from airflow.api.auth.backend.kerberos_auth import ( +16 |- CLIENT_AUTH, +17 16 | find_user, +18 17 | init_app, +19 18 | log, +20 19 | requires_authentication, +21 20 | ) + 21 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import CLIENT_AUTH +22 22 | +23 23 | log() +24 24 | CLIENT_AUTH + +AIR302_fab.py:25:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; | 23 | log() 24 | CLIENT_AUTH @@ -75,7 +152,21 @@ AIR302_fab.py:25:1: AIR302 `airflow.api.auth.backend.kerberos_auth.find_user` is | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:26:1: AIR302 `airflow.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +14 14 | +15 15 | from airflow.api.auth.backend.kerberos_auth import ( +16 16 | CLIENT_AUTH, +17 |- find_user, +18 17 | init_app, +19 18 | log, +20 19 | requires_authentication, +21 20 | ) + 21 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import find_user +22 22 | +23 23 | log() +24 24 | CLIENT_AUTH + +AIR302_fab.py:26:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; | 24 | CLIENT_AUTH 25 | find_user() @@ -85,7 +176,20 @@ AIR302_fab.py:26:1: AIR302 `airflow.api.auth.backend.kerberos_auth.init_app` is | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:27:1: AIR302 `airflow.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +15 15 | from airflow.api.auth.backend.kerberos_auth import ( +16 16 | CLIENT_AUTH, +17 17 | find_user, +18 |- init_app, +19 18 | log, +20 19 | requires_authentication, +21 20 | ) + 21 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import init_app +22 22 | +23 23 | log() +24 24 | CLIENT_AUTH + +AIR302_fab.py:27:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | 25 | find_user() 26 | init_app() @@ -96,7 +200,18 @@ AIR302_fab.py:27:1: AIR302 `airflow.api.auth.backend.kerberos_auth.requires_auth | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:37:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +17 17 | find_user, +18 18 | init_app, +19 19 | log, +20 |- requires_authentication, +21 20 | ) + 21 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import requires_authentication +22 22 | +23 23 | log() +24 24 | CLIENT_AUTH + +AIR302_fab.py:37:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0; | 35 | ) 36 | @@ -107,7 +222,19 @@ AIR302_fab.py:37:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:38:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +30 30 | CLIENT_AUTH, +31 31 | find_user, +32 32 | init_app, +33 |- log, +34 33 | requires_authentication, +35 34 | ) + 35 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import log +36 36 | +37 37 | log() +38 38 | CLIENT_AUTH + +AIR302_fab.py:38:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0; | 37 | log() 38 | CLIENT_AUTH @@ -117,7 +244,22 @@ AIR302_fab.py:38:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:39:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +27 27 | requires_authentication() +28 28 | +29 29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import ( +30 |- CLIENT_AUTH, +31 30 | find_user, +32 31 | init_app, +33 32 | log, +34 33 | requires_authentication, +35 34 | ) + 35 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import CLIENT_AUTH +36 36 | +37 37 | log() +38 38 | CLIENT_AUTH + +AIR302_fab.py:39:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0; | 37 | log() 38 | CLIENT_AUTH @@ -128,7 +270,21 @@ AIR302_fab.py:39:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:40:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +28 28 | +29 29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import ( +30 30 | CLIENT_AUTH, +31 |- find_user, +32 31 | init_app, +33 32 | log, +34 33 | requires_authentication, +35 34 | ) + 35 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import find_user +36 36 | +37 37 | log() +38 38 | CLIENT_AUTH + +AIR302_fab.py:40:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0; | 38 | CLIENT_AUTH 39 | find_user() @@ -138,7 +294,20 @@ AIR302_fab.py:40:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:41:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +29 29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import ( +30 30 | CLIENT_AUTH, +31 31 | find_user, +32 |- init_app, +33 32 | log, +34 33 | requires_authentication, +35 34 | ) + 35 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import init_app +36 36 | +37 37 | log() +38 38 | CLIENT_AUTH + +AIR302_fab.py:41:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0; | 39 | find_user() 40 | init_app() @@ -149,7 +318,18 @@ AIR302_fab.py:41:1: AIR302 `airflow.auth.managers.fab.api.auth.backend.kerberos_ | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead. -AIR302_fab.py:49:1: AIR302 `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +31 31 | find_user, +32 32 | init_app, +33 33 | log, +34 |- requires_authentication, +35 34 | ) + 35 |+from airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth import requires_authentication +36 36 | +37 37 | log() +38 38 | CLIENT_AUTH + +AIR302_fab.py:49:1: AIR302 [*] `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0; | 47 | ) 48 | @@ -160,7 +340,21 @@ AIR302_fab.py:49:1: AIR302 `airflow.auth.managers.fab.fab_auth_manager.FabAuthMa | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAuthManager` from `airflow.providers.fab.auth_manager.fab_auth_manager` instead. -AIR302_fab.py:50:1: AIR302 `airflow.auth.managers.fab.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +40 40 | init_app() +41 41 | requires_authentication() +42 42 | +43 |-from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager +44 43 | from airflow.auth.managers.fab.security_manager.override import ( +45 44 | MAX_NUM_DATABASE_USER_SESSIONS, +46 45 | FabAirflowSecurityManagerOverride, +47 46 | ) + 47 |+from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager +48 48 | +49 49 | FabAuthManager() +50 50 | MAX_NUM_DATABASE_USER_SESSIONS + +AIR302_fab.py:50:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` is moved into `fab` provider in Airflow 3.0; | 49 | FabAuthManager() 50 | MAX_NUM_DATABASE_USER_SESSIONS @@ -169,7 +363,19 @@ AIR302_fab.py:50:1: AIR302 `airflow.auth.managers.fab.security_manager.override. | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `MAX_NUM_DATABASE_USER_SESSIONS` from `airflow.providers.fab.auth_manager.security_manager.override` instead. -AIR302_fab.py:51:1: AIR302 `airflow.auth.managers.fab.security_manager.override.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +42 42 | +43 43 | from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager +44 44 | from airflow.auth.managers.fab.security_manager.override import ( +45 |- MAX_NUM_DATABASE_USER_SESSIONS, +46 45 | FabAirflowSecurityManagerOverride, +47 46 | ) + 47 |+from airflow.providers.fab.auth_manager.security_manager.override import MAX_NUM_DATABASE_USER_SESSIONS +48 48 | +49 49 | FabAuthManager() +50 50 | MAX_NUM_DATABASE_USER_SESSIONS + +AIR302_fab.py:51:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.override.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; | 49 | FabAuthManager() 50 | MAX_NUM_DATABASE_USER_SESSIONS @@ -180,7 +386,18 @@ AIR302_fab.py:51:1: AIR302 `airflow.auth.managers.fab.security_manager.override. | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead. -AIR302_fab.py:55:1: AIR302 `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; +ℹ Unsafe fix +43 43 | from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager +44 44 | from airflow.auth.managers.fab.security_manager.override import ( +45 45 | MAX_NUM_DATABASE_USER_SESSIONS, +46 |- FabAirflowSecurityManagerOverride, +47 46 | ) + 47 |+from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride +48 48 | +49 49 | FabAuthManager() +50 50 | MAX_NUM_DATABASE_USER_SESSIONS + +AIR302_fab.py:55:1: AIR302 [*] `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0; | 53 | from airflow.www.security import FabAirflowSecurityManagerOverride 54 | @@ -188,3 +405,12 @@ AIR302_fab.py:55:1: AIR302 `airflow.www.security.FabAirflowSecurityManagerOverri | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead. + +ℹ Unsafe fix +50 50 | MAX_NUM_DATABASE_USER_SESSIONS +51 51 | FabAirflowSecurityManagerOverride() +52 52 | +53 |-from airflow.www.security import FabAirflowSecurityManagerOverride + 53 |+from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride +54 54 | +55 55 | FabAirflowSecurityManagerOverride() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap index 950f1ebc74bcc2..a3e75ff1ec4654 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hdfs.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_hdfs.py:6:1: AIR302 `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved into `apache-hdfs` provider in Airflow 3.0; +AIR302_hdfs.py:6:1: AIR302 [*] `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved into `apache-hdfs` provider in Airflow 3.0; | 4 | from airflow.sensors.web_hdfs_sensor import WebHdfsSensor 5 | @@ -11,10 +11,30 @@ AIR302_hdfs.py:6:1: AIR302 `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved int | = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHDFSHook` from `airflow.providers.apache.hdfs.hooks.webhdfs` instead. -AIR302_hdfs.py:7:1: AIR302 `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is moved into `apache-hdfs` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.webhdfs_hook import WebHDFSHook +4 3 | from airflow.sensors.web_hdfs_sensor import WebHdfsSensor + 4 |+from airflow.providers.apache.hdfs.hooks.webhdfs import WebHDFSHook +5 5 | +6 6 | WebHDFSHook() +7 7 | WebHdfsSensor() + +AIR302_hdfs.py:7:1: AIR302 [*] `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is moved into `apache-hdfs` provider in Airflow 3.0; | 6 | WebHDFSHook() 7 | WebHdfsSensor() | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHdfsSensor` from `airflow.providers.apache.hdfs.sensors.web_hdfs` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.webhdfs_hook import WebHDFSHook +4 |-from airflow.sensors.web_hdfs_sensor import WebHdfsSensor + 4 |+from airflow.providers.apache.hdfs.sensors.web_hdfs import WebHdfsSensor +5 5 | +6 6 | WebHDFSHook() +7 7 | WebHdfsSensor() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap index ef984b1ca92e69..b941acb2d7b055 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_hive.py.snap @@ -1,208 +1,452 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_hive.py:36:1: AIR302 `airflow.macros.hive.closest_ds_partition` is moved into `apache-hive` provider in Airflow 3.0; +AIR302_hive.py:18:1: AIR302 [*] `airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES` is moved into `apache-hive` provider in Airflow 3.0; | -34 | from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor -35 | -36 | closest_ds_partition() - | ^^^^^^^^^^^^^^^^^^^^ AIR302 -37 | max_partition() +16 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator +17 | +18 | HIVE_QUEUE_PRIORITIES + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +19 | HiveCliHook() +20 | HiveMetastoreHook() | - = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `closest_ds_partition` from `airflow.providers.apache.hive.macros.hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HIVE_QUEUE_PRIORITIES` from `airflow.providers.apache.hive.hooks.hive` instead. -AIR302_hive.py:37:1: AIR302 `airflow.macros.hive.max_partition` is moved into `apache-hive` provider in Airflow 3.0; - | -36 | closest_ds_partition() -37 | max_partition() - | ^^^^^^^^^^^^^ AIR302 -38 | -39 | HiveCliHook() - | - = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `max_partition` from `airflow.providers.apache.hive.macros.hive` instead. +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.hive_hooks import ( +4 |- HIVE_QUEUE_PRIORITIES, +5 4 | HiveCliHook, +6 5 | HiveMetastoreHook, +7 6 | HiveServer2Hook, +-------------------------------------------------------------------------------- +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.hooks.hive import HIVE_QUEUE_PRIORITIES +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() -AIR302_hive.py:39:1: AIR302 `airflow.hooks.hive_hooks.HiveCliHook` is moved into `apache-hive` provider in Airflow 3.0; +AIR302_hive.py:19:1: AIR302 [*] `airflow.hooks.hive_hooks.HiveCliHook` is moved into `apache-hive` provider in Airflow 3.0; | -37 | max_partition() -38 | -39 | HiveCliHook() +18 | HIVE_QUEUE_PRIORITIES +19 | HiveCliHook() | ^^^^^^^^^^^ AIR302 -40 | HiveMetastoreHook() -41 | HiveServer2Hook() +20 | HiveMetastoreHook() +21 | HiveServer2Hook() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveCliHook` from `airflow.providers.apache.hive.hooks.hive` instead. -AIR302_hive.py:40:1: AIR302 `airflow.hooks.hive_hooks.HiveMetastoreHook` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.hive_hooks import ( +4 4 | HIVE_QUEUE_PRIORITIES, +5 |- HiveCliHook, +6 5 | HiveMetastoreHook, +7 6 | HiveServer2Hook, +8 7 | ) +-------------------------------------------------------------------------------- +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.hooks.hive import HiveCliHook +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:20:1: AIR302 [*] `airflow.hooks.hive_hooks.HiveMetastoreHook` is moved into `apache-hive` provider in Airflow 3.0; | -39 | HiveCliHook() -40 | HiveMetastoreHook() +18 | HIVE_QUEUE_PRIORITIES +19 | HiveCliHook() +20 | HiveMetastoreHook() | ^^^^^^^^^^^^^^^^^ AIR302 -41 | HiveServer2Hook() -42 | HIVE_QUEUE_PRIORITIES +21 | HiveServer2Hook() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveMetastoreHook` from `airflow.providers.apache.hive.hooks.hive` instead. -AIR302_hive.py:41:1: AIR302 `airflow.hooks.hive_hooks.HiveServer2Hook` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +3 3 | from airflow.hooks.hive_hooks import ( +4 4 | HIVE_QUEUE_PRIORITIES, +5 5 | HiveCliHook, +6 |- HiveMetastoreHook, +7 6 | HiveServer2Hook, +8 7 | ) +9 8 | from airflow.macros.hive import ( +-------------------------------------------------------------------------------- +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.hooks.hive import HiveMetastoreHook +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:21:1: AIR302 [*] `airflow.hooks.hive_hooks.HiveServer2Hook` is moved into `apache-hive` provider in Airflow 3.0; | -39 | HiveCliHook() -40 | HiveMetastoreHook() -41 | HiveServer2Hook() +19 | HiveCliHook() +20 | HiveMetastoreHook() +21 | HiveServer2Hook() | ^^^^^^^^^^^^^^^ AIR302 -42 | HIVE_QUEUE_PRIORITIES +22 | +23 | closest_ds_partition() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveServer2Hook` from `airflow.providers.apache.hive.hooks.hive` instead. -AIR302_hive.py:42:1: AIR302 `airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +4 4 | HIVE_QUEUE_PRIORITIES, +5 5 | HiveCliHook, +6 6 | HiveMetastoreHook, +7 |- HiveServer2Hook, +8 7 | ) +9 8 | from airflow.macros.hive import ( +10 9 | closest_ds_partition, +-------------------------------------------------------------------------------- +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.hooks.hive import HiveServer2Hook +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:23:1: AIR302 [*] `airflow.macros.hive.closest_ds_partition` is moved into `apache-hive` provider in Airflow 3.0; | -40 | HiveMetastoreHook() -41 | HiveServer2Hook() -42 | HIVE_QUEUE_PRIORITIES - | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -43 | -44 | HiveOperator() +21 | HiveServer2Hook() +22 | +23 | closest_ds_partition() + | ^^^^^^^^^^^^^^^^^^^^ AIR302 +24 | max_partition() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HIVE_QUEUE_PRIORITIES` from `airflow.providers.apache.hive.hooks.hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `closest_ds_partition` from `airflow.providers.apache.hive.macros.hive` instead. + +ℹ Unsafe fix +7 7 | HiveServer2Hook, +8 8 | ) +9 9 | from airflow.macros.hive import ( +10 |- closest_ds_partition, +11 10 | max_partition, +12 11 | ) +13 12 | from airflow.operators.hive_operator import HiveOperator +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.macros.hive import closest_ds_partition +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() -AIR302_hive.py:44:1: AIR302 `airflow.operators.hive_operator.HiveOperator` is moved into `apache-hive` provider in Airflow 3.0; +AIR302_hive.py:24:1: AIR302 [*] `airflow.macros.hive.max_partition` is moved into `apache-hive` provider in Airflow 3.0; | -42 | HIVE_QUEUE_PRIORITIES -43 | -44 | HiveOperator() +23 | closest_ds_partition() +24 | max_partition() + | ^^^^^^^^^^^^^ AIR302 +25 | +26 | HiveOperator() + | + = help: Install `apache-airflow-providers-apache-hive>=5.1.0` and use `max_partition` from `airflow.providers.apache.hive.macros.hive` instead. + +ℹ Unsafe fix +8 8 | ) +9 9 | from airflow.macros.hive import ( +10 10 | closest_ds_partition, +11 |- max_partition, +12 11 | ) +13 12 | from airflow.operators.hive_operator import HiveOperator +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.macros.hive import max_partition +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:26:1: AIR302 [*] `airflow.operators.hive_operator.HiveOperator` is moved into `apache-hive` provider in Airflow 3.0; + | +24 | max_partition() +25 | +26 | HiveOperator() | ^^^^^^^^^^^^ AIR302 -45 | -46 | HiveStatsCollectionOperator() +27 | HiveStatsCollectionOperator() +28 | HiveToMySqlOperator() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveOperator` from `airflow.providers.apache.hive.operators.hive` instead. -AIR302_hive.py:46:1: AIR302 `airflow.operators.hive_stats_operator.HiveStatsCollectionOperator` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +10 10 | closest_ds_partition, +11 11 | max_partition, +12 12 | ) +13 |-from airflow.operators.hive_operator import HiveOperator +14 13 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.operators.hive import HiveOperator +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:27:1: AIR302 [*] `airflow.operators.hive_stats_operator.HiveStatsCollectionOperator` is moved into `apache-hive` provider in Airflow 3.0; | -44 | HiveOperator() -45 | -46 | HiveStatsCollectionOperator() +26 | HiveOperator() +27 | HiveStatsCollectionOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -47 | -48 | HiveToMySqlOperator() +28 | HiveToMySqlOperator() +29 | HiveToSambaOperator() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveStatsCollectionOperator` from `airflow.providers.apache.hive.operators.hive_stats` instead. -AIR302_hive.py:48:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlOperator` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +11 11 | max_partition, +12 12 | ) +13 13 | from airflow.operators.hive_operator import HiveOperator +14 |-from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 14 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.operators.hive_stats import HiveStatsCollectionOperator +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:28:1: AIR302 [*] `airflow.operators.hive_to_mysql.HiveToMySqlOperator` is moved into `apache-hive` provider in Airflow 3.0; | -46 | HiveStatsCollectionOperator() -47 | -48 | HiveToMySqlOperator() +26 | HiveOperator() +27 | HiveStatsCollectionOperator() +28 | HiveToMySqlOperator() | ^^^^^^^^^^^^^^^^^^^ AIR302 -49 | HiveToMySqlTransfer() +29 | HiveToSambaOperator() | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToMySqlOperator` from `airflow.providers.apache.hive.transfers.hive_to_mysql` instead. -AIR302_hive.py:49:1: AIR302 `airflow.operators.hive_to_mysql.HiveToMySqlTransfer` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +12 12 | ) +13 13 | from airflow.operators.hive_operator import HiveOperator +14 14 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 |-from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 15 | from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.transfers.hive_to_mysql import HiveToMySqlOperator +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() + +AIR302_hive.py:29:1: AIR302 [*] `airflow.operators.hive_to_samba_operator.HiveToSambaOperator` is moved into `apache-hive` provider in Airflow 3.0; | -48 | HiveToMySqlOperator() -49 | HiveToMySqlTransfer() +27 | HiveStatsCollectionOperator() +28 | HiveToMySqlOperator() +29 | HiveToSambaOperator() | ^^^^^^^^^^^^^^^^^^^ AIR302 -50 | -51 | HiveToSambaOperator() | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToMySqlOperator` from `airflow.providers.apache.hive.transfers.hive_to_mysql` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToSambaOperator` from `airflow.providers.apache.hive.transfers.hive_to_samba` instead. + +ℹ Unsafe fix +13 13 | from airflow.operators.hive_operator import HiveOperator +14 14 | from airflow.operators.hive_stats_operator import HiveStatsCollectionOperator +15 15 | from airflow.operators.hive_to_mysql import HiveToMySqlOperator +16 |-from airflow.operators.hive_to_samba_operator import HiveToSambaOperator + 16 |+from airflow.providers.apache.hive.transfers.hive_to_samba import HiveToSambaOperator +17 17 | +18 18 | HIVE_QUEUE_PRIORITIES +19 19 | HiveCliHook() -AIR302_hive.py:51:1: AIR302 `airflow.operators.hive_to_samba_operator.HiveToSambaOperator` is moved into `apache-hive` provider in Airflow 3.0; +AIR302_hive.py:34:1: AIR302 [*] `airflow.operators.hive_to_mysql.HiveToMySqlTransfer` is moved into `apache-hive` provider in Airflow 3.0; | -49 | HiveToMySqlTransfer() -50 | -51 | HiveToSambaOperator() +32 | from airflow.operators.hive_to_mysql import HiveToMySqlTransfer +33 | +34 | HiveToMySqlTransfer() | ^^^^^^^^^^^^^^^^^^^ AIR302 -52 | -53 | MsSqlToHiveOperator() +35 | +36 | from airflow.operators.mysql_to_hive import MySqlToHiveOperator | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToSambaOperator` from `airflow.providers.apache.hive.transfers.hive_to_samba` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HiveToMySqlOperator` from `airflow.providers.apache.hive.transfers.hive_to_mysql` instead. -AIR302_hive.py:53:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +30 30 | +31 31 | +32 32 | from airflow.operators.hive_to_mysql import HiveToMySqlTransfer + 33 |+from airflow.providers.apache.hive.transfers.hive_to_mysql import HiveToMySqlOperator +33 34 | +34 35 | HiveToMySqlTransfer() +35 36 | + +AIR302_hive.py:38:1: AIR302 [*] `airflow.operators.mysql_to_hive.MySqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | -51 | HiveToSambaOperator() -52 | -53 | MsSqlToHiveOperator() +36 | from airflow.operators.mysql_to_hive import MySqlToHiveOperator +37 | +38 | MySqlToHiveOperator() | ^^^^^^^^^^^^^^^^^^^ AIR302 -54 | MsSqlToHiveTransfer() +39 | +40 | from airflow.operators.mysql_to_hive import MySqlToHiveTransfer | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. + +ℹ Unsafe fix +33 33 | +34 34 | HiveToMySqlTransfer() +35 35 | +36 |-from airflow.operators.mysql_to_hive import MySqlToHiveOperator + 36 |+from airflow.providers.apache.hive.transfers.mysql_to_hive import MySqlToHiveOperator +37 37 | +38 38 | MySqlToHiveOperator() +39 39 | -AIR302_hive.py:54:1: AIR302 `airflow.operators.mssql_to_hive.MsSqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; +AIR302_hive.py:42:1: AIR302 [*] `airflow.operators.mysql_to_hive.MySqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | -53 | MsSqlToHiveOperator() -54 | MsSqlToHiveTransfer() +40 | from airflow.operators.mysql_to_hive import MySqlToHiveTransfer +41 | +42 | MySqlToHiveTransfer() | ^^^^^^^^^^^^^^^^^^^ AIR302 -55 | -56 | MySqlToHiveOperator() +43 | +44 | from airflow.operators.mssql_to_hive import MsSqlToHiveOperator | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. -AIR302_hive.py:56:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +38 38 | MySqlToHiveOperator() +39 39 | +40 40 | from airflow.operators.mysql_to_hive import MySqlToHiveTransfer + 41 |+from airflow.providers.apache.hive.transfers.mysql_to_hive import MySqlToHiveOperator +41 42 | +42 43 | MySqlToHiveTransfer() +43 44 | + +AIR302_hive.py:46:1: AIR302 [*] `airflow.operators.mssql_to_hive.MsSqlToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | -54 | MsSqlToHiveTransfer() -55 | -56 | MySqlToHiveOperator() +44 | from airflow.operators.mssql_to_hive import MsSqlToHiveOperator +45 | +46 | MsSqlToHiveOperator() | ^^^^^^^^^^^^^^^^^^^ AIR302 -57 | MySqlToHiveTransfer() +47 | +48 | from airflow.operators.mssql_to_hive import MsSqlToHiveTransfer | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. -AIR302_hive.py:57:1: AIR302 `airflow.operators.mysql_to_hive.MySqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +41 41 | +42 42 | MySqlToHiveTransfer() +43 43 | +44 |-from airflow.operators.mssql_to_hive import MsSqlToHiveOperator + 44 |+from airflow.providers.apache.hive.transfers.mssql_to_hive import MsSqlToHiveOperator +45 45 | +46 46 | MsSqlToHiveOperator() +47 47 | + +AIR302_hive.py:50:1: AIR302 [*] `airflow.operators.mssql_to_hive.MsSqlToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | -56 | MySqlToHiveOperator() -57 | MySqlToHiveTransfer() +48 | from airflow.operators.mssql_to_hive import MsSqlToHiveTransfer +49 | +50 | MsSqlToHiveTransfer() | ^^^^^^^^^^^^^^^^^^^ AIR302 -58 | -59 | S3ToHiveOperator() +51 | +52 | from airflow.operators.s3_to_hive_operator import S3ToHiveOperator | - = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MySqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mysql_to_hive` instead. + = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MsSqlToHiveOperator` from `airflow.providers.apache.hive.transfers.mssql_to_hive` instead. -AIR302_hive.py:59:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +46 46 | MsSqlToHiveOperator() +47 47 | +48 48 | from airflow.operators.mssql_to_hive import MsSqlToHiveTransfer + 49 |+from airflow.providers.apache.hive.transfers.mssql_to_hive import MsSqlToHiveOperator +49 50 | +50 51 | MsSqlToHiveTransfer() +51 52 | + +AIR302_hive.py:54:1: AIR302 [*] `airflow.operators.s3_to_hive_operator.S3ToHiveOperator` is moved into `apache-hive` provider in Airflow 3.0; | -57 | MySqlToHiveTransfer() -58 | -59 | S3ToHiveOperator() +52 | from airflow.operators.s3_to_hive_operator import S3ToHiveOperator +53 | +54 | S3ToHiveOperator() | ^^^^^^^^^^^^^^^^ AIR302 -60 | S3ToHiveTransfer() +55 | +56 | from airflow.operators.s3_to_hive_operator import S3ToHiveTransfer | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `S3ToHiveOperator` from `airflow.providers.apache.hive.transfers.s3_to_hive` instead. -AIR302_hive.py:60:1: AIR302 `airflow.operators.s3_to_hive_operator.S3ToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +49 49 | +50 50 | MsSqlToHiveTransfer() +51 51 | +52 |-from airflow.operators.s3_to_hive_operator import S3ToHiveOperator + 52 |+from airflow.providers.apache.hive.transfers.s3_to_hive import S3ToHiveOperator +53 53 | +54 54 | S3ToHiveOperator() +55 55 | + +AIR302_hive.py:58:1: AIR302 [*] `airflow.operators.s3_to_hive_operator.S3ToHiveTransfer` is moved into `apache-hive` provider in Airflow 3.0; | -59 | S3ToHiveOperator() -60 | S3ToHiveTransfer() +56 | from airflow.operators.s3_to_hive_operator import S3ToHiveTransfer +57 | +58 | S3ToHiveTransfer() | ^^^^^^^^^^^^^^^^ AIR302 -61 | -62 | HivePartitionSensor() +59 | +60 | from airflow.sensors.hive_partition_sensor import HivePartitionSensor | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `S3ToHiveOperator` from `airflow.providers.apache.hive.transfers.s3_to_hive` instead. -AIR302_hive.py:62:1: AIR302 `airflow.sensors.hive_partition_sensor.HivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +54 54 | S3ToHiveOperator() +55 55 | +56 56 | from airflow.operators.s3_to_hive_operator import S3ToHiveTransfer + 57 |+from airflow.providers.apache.hive.transfers.s3_to_hive import S3ToHiveOperator +57 58 | +58 59 | S3ToHiveTransfer() +59 60 | + +AIR302_hive.py:62:1: AIR302 [*] `airflow.sensors.hive_partition_sensor.HivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | -60 | S3ToHiveTransfer() +60 | from airflow.sensors.hive_partition_sensor import HivePartitionSensor 61 | 62 | HivePartitionSensor() | ^^^^^^^^^^^^^^^^^^^ AIR302 63 | -64 | MetastorePartitionSensor() +64 | from airflow.sensors.metastore_partition_sensor import MetastorePartitionSensor | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `HivePartitionSensor` from `airflow.providers.apache.hive.sensors.hive_partition` instead. -AIR302_hive.py:64:1: AIR302 `airflow.sensors.metastore_partition_sensor.MetastorePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +57 57 | +58 58 | S3ToHiveTransfer() +59 59 | +60 |-from airflow.sensors.hive_partition_sensor import HivePartitionSensor + 60 |+from airflow.providers.apache.hive.sensors.hive_partition import HivePartitionSensor +61 61 | +62 62 | HivePartitionSensor() +63 63 | + +AIR302_hive.py:66:1: AIR302 [*] `airflow.sensors.metastore_partition_sensor.MetastorePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | -62 | HivePartitionSensor() -63 | -64 | MetastorePartitionSensor() - | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +64 | from airflow.sensors.metastore_partition_sensor import MetastorePartitionSensor 65 | -66 | NamedHivePartitionSensor() +66 | MetastorePartitionSensor() + | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +67 | +68 | from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `MetastorePartitionSensor` from `airflow.providers.apache.hive.sensors.metastore_partition` instead. -AIR302_hive.py:66:1: AIR302 `airflow.sensors.named_hive_partition_sensor.NamedHivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; +ℹ Unsafe fix +61 61 | +62 62 | HivePartitionSensor() +63 63 | +64 |-from airflow.sensors.metastore_partition_sensor import MetastorePartitionSensor + 64 |+from airflow.providers.apache.hive.sensors.metastore_partition import MetastorePartitionSensor +65 65 | +66 66 | MetastorePartitionSensor() +67 67 | + +AIR302_hive.py:70:1: AIR302 [*] `airflow.sensors.named_hive_partition_sensor.NamedHivePartitionSensor` is moved into `apache-hive` provider in Airflow 3.0; | -64 | MetastorePartitionSensor() -65 | -66 | NamedHivePartitionSensor() +68 | from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor +69 | +70 | NamedHivePartitionSensor() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-apache-hive>=1.0.0` and use `NamedHivePartitionSensor` from `airflow.providers.apache.hive.sensors.named_hive_partition` instead. + +ℹ Unsafe fix +65 65 | +66 66 | MetastorePartitionSensor() +67 67 | +68 |-from airflow.sensors.named_hive_partition_sensor import NamedHivePartitionSensor + 68 |+from airflow.providers.apache.hive.sensors.named_hive_partition import NamedHivePartitionSensor +69 69 | +70 70 | NamedHivePartitionSensor() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap index ea7e022ac9e1d6..3a8993133ed596 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_http.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_http.py:7:1: AIR302 `airflow.hooks.http_hook.HttpHook` is moved into `http` provider in Airflow 3.0; +AIR302_http.py:7:1: AIR302 [*] `airflow.hooks.http_hook.HttpHook` is moved into `http` provider in Airflow 3.0; | 5 | from airflow.sensors.http_sensor import HttpSensor 6 | @@ -12,6 +12,17 @@ AIR302_http.py:7:1: AIR302 `airflow.hooks.http_hook.HttpHook` is moved into `htt | = help: Install `apache-airflow-providers-http>=1.0.0` and use `HttpHook` from `airflow.providers.http.hooks.http` instead. +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.http_hook import HttpHook +4 3 | from airflow.operators.http_operator import SimpleHttpOperator +5 4 | from airflow.sensors.http_sensor import HttpSensor + 5 |+from airflow.providers.http.hooks.http import HttpHook +6 6 | +7 7 | HttpHook() +8 8 | SimpleHttpOperator() + AIR302_http.py:8:1: AIR302 [*] `airflow.operators.http_operator.SimpleHttpOperator` is moved into `http` provider in Airflow 3.0; | 7 | HttpHook() @@ -32,7 +43,7 @@ AIR302_http.py:8:1: AIR302 [*] `airflow.operators.http_operator.SimpleHttpOperat 9 |+HttpOperator() 9 10 | HttpSensor() -AIR302_http.py:9:1: AIR302 `airflow.sensors.http_sensor.HttpSensor` is moved into `http` provider in Airflow 3.0; +AIR302_http.py:9:1: AIR302 [*] `airflow.sensors.http_sensor.HttpSensor` is moved into `http` provider in Airflow 3.0; | 7 | HttpHook() 8 | SimpleHttpOperator() @@ -40,3 +51,13 @@ AIR302_http.py:9:1: AIR302 `airflow.sensors.http_sensor.HttpSensor` is moved int | ^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-http>=1.0.0` and use `HttpSensor` from `airflow.providers.http.sensors.http` instead. + +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.http_hook import HttpHook +4 4 | from airflow.operators.http_operator import SimpleHttpOperator +5 |-from airflow.sensors.http_sensor import HttpSensor + 5 |+from airflow.providers.http.sensors.http import HttpSensor +6 6 | +7 7 | HttpHook() +8 8 | SimpleHttpOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap index f541547a7f796b..dd6177f5435593 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_jdbc.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_jdbc.py:8:1: AIR302 `airflow.hooks.jdbc_hook.JdbcHook` is moved into `jdbc` provider in Airflow 3.0; +AIR302_jdbc.py:8:1: AIR302 [*] `airflow.hooks.jdbc_hook.JdbcHook` is moved into `jdbc` provider in Airflow 3.0; | 6 | ) 7 | @@ -11,10 +11,33 @@ AIR302_jdbc.py:8:1: AIR302 `airflow.hooks.jdbc_hook.JdbcHook` is moved into `jdb | = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `JdbcHook` from `airflow.providers.jdbc.hooks.jdbc` instead. -AIR302_jdbc.py:9:1: AIR302 `airflow.hooks.jdbc_hook.jaydebeapi` is moved into `jdbc` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.jdbc_hook import ( +4 |- JdbcHook, +5 4 | jaydebeapi, +6 5 | ) + 6 |+from airflow.providers.jdbc.hooks.jdbc import JdbcHook +7 7 | +8 8 | JdbcHook() +9 9 | jaydebeapi() + +AIR302_jdbc.py:9:1: AIR302 [*] `airflow.hooks.jdbc_hook.jaydebeapi` is moved into `jdbc` provider in Airflow 3.0; | 8 | JdbcHook() 9 | jaydebeapi() | ^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-jdbc>=1.0.0` and use `jaydebeapi` from `airflow.providers.jdbc.hooks.jdbc` instead. + +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.jdbc_hook import ( +4 4 | JdbcHook, +5 |- jaydebeapi, +6 5 | ) + 6 |+from airflow.providers.jdbc.hooks.jdbc import jaydebeapi +7 7 | +8 8 | JdbcHook() +9 9 | jaydebeapi() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap index 9624ed1a53cadf..f46860be1581f2 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_kubernetes.py.snap @@ -1,599 +1,946 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_kubernetes.py:29:1: AIR302 `airflow.executors.kubernetes_executor_types.ALL_NAMESPACES` is moved into `cncf-kubernetes` provider in Airflow 3.0; +AIR302_kubernetes.py:22:1: AIR302 [*] `airflow.executors.kubernetes_executor_types.ALL_NAMESPACES` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -27 | ) -28 | -29 | ALL_NAMESPACES +20 | ) +21 | +22 | ALL_NAMESPACES | ^^^^^^^^^^^^^^ AIR302 -30 | POD_EXECUTOR_DONE_KEY +23 | POD_EXECUTOR_DONE_KEY | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `ALL_NAMESPACES` from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types` instead. -AIR302_kubernetes.py:30:1: AIR302 `airflow.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -29 | ALL_NAMESPACES -30 | POD_EXECUTOR_DONE_KEY +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.executors.kubernetes_executor_types import ( +4 |- ALL_NAMESPACES, +5 4 | POD_EXECUTOR_DONE_KEY, +6 5 | ) +7 6 | from airflow.kubernetes.k8s_model import ( +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import ALL_NAMESPACES +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:23:1: AIR302 [*] `airflow.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +22 | ALL_NAMESPACES +23 | POD_EXECUTOR_DONE_KEY | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -31 | -32 | K8SModel() +24 | +25 | K8SModel() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `POD_EXECUTOR_DONE_KEY` from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types` instead. -AIR302_kubernetes.py:32:1: AIR302 `airflow.kubernetes.k8s_model.K8SModel` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -30 | POD_EXECUTOR_DONE_KEY -31 | -32 | K8SModel() +ℹ Unsafe fix +2 2 | +3 3 | from airflow.executors.kubernetes_executor_types import ( +4 4 | ALL_NAMESPACES, +5 |- POD_EXECUTOR_DONE_KEY, +6 5 | ) +7 6 | from airflow.kubernetes.k8s_model import ( +8 7 | K8SModel, +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import POD_EXECUTOR_DONE_KEY +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:25:1: AIR302 [*] `airflow.kubernetes.k8s_model.K8SModel` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +23 | POD_EXECUTOR_DONE_KEY +24 | +25 | K8SModel() | ^^^^^^^^ AIR302 -33 | append_to_pod() +26 | append_to_pod() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `K8SModel` from `airflow.providers.cncf.kubernetes.k8s_model` instead. -AIR302_kubernetes.py:33:1: AIR302 `airflow.kubernetes.k8s_model.append_to_pod` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -32 | K8SModel() -33 | append_to_pod() +ℹ Unsafe fix +5 5 | POD_EXECUTOR_DONE_KEY, +6 6 | ) +7 7 | from airflow.kubernetes.k8s_model import ( +8 |- K8SModel, +9 8 | append_to_pod, +10 9 | ) +11 10 | from airflow.kubernetes.kube_client import ( +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.k8s_model import K8SModel +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:26:1: AIR302 [*] `airflow.kubernetes.k8s_model.append_to_pod` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +25 | K8SModel() +26 | append_to_pod() | ^^^^^^^^^^^^^ AIR302 -34 | -35 | _disable_verify_ssl() +27 | +28 | _disable_verify_ssl() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `append_to_pod` from `airflow.providers.cncf.kubernetes.k8s_model` instead. -AIR302_kubernetes.py:35:1: AIR302 `airflow.kubernetes.kube_client._disable_verify_ssl` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -33 | append_to_pod() -34 | -35 | _disable_verify_ssl() +ℹ Unsafe fix +6 6 | ) +7 7 | from airflow.kubernetes.k8s_model import ( +8 8 | K8SModel, +9 |- append_to_pod, +10 9 | ) +11 10 | from airflow.kubernetes.kube_client import ( +12 11 | _disable_verify_ssl, +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.k8s_model import append_to_pod +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:28:1: AIR302 [*] `airflow.kubernetes.kube_client._disable_verify_ssl` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +26 | append_to_pod() +27 | +28 | _disable_verify_ssl() | ^^^^^^^^^^^^^^^^^^^ AIR302 -36 | _enable_tcp_keepalive() -37 | get_kube_client() +29 | _enable_tcp_keepalive() +30 | get_kube_client() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `_disable_verify_ssl` from `airflow.providers.cncf.kubernetes.kube_client` instead. -AIR302_kubernetes.py:36:1: AIR302 `airflow.kubernetes.kube_client._enable_tcp_keepalive` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -35 | _disable_verify_ssl() -36 | _enable_tcp_keepalive() +ℹ Unsafe fix +9 9 | append_to_pod, +10 10 | ) +11 11 | from airflow.kubernetes.kube_client import ( +12 |- _disable_verify_ssl, +13 12 | _enable_tcp_keepalive, +14 13 | get_kube_client, +15 14 | ) +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.kube_client import _disable_verify_ssl +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:29:1: AIR302 [*] `airflow.kubernetes.kube_client._enable_tcp_keepalive` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +28 | _disable_verify_ssl() +29 | _enable_tcp_keepalive() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -37 | get_kube_client() +30 | get_kube_client() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `_enable_tcp_keepalive` from `airflow.providers.cncf.kubernetes.kube_client` instead. -AIR302_kubernetes.py:37:1: AIR302 `airflow.kubernetes.kube_client.get_kube_client` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -35 | _disable_verify_ssl() -36 | _enable_tcp_keepalive() -37 | get_kube_client() +ℹ Unsafe fix +10 10 | ) +11 11 | from airflow.kubernetes.kube_client import ( +12 12 | _disable_verify_ssl, +13 |- _enable_tcp_keepalive, +14 13 | get_kube_client, +15 14 | ) +16 15 | from airflow.kubernetes.kubernetes_helper_functions import ( +-------------------------------------------------------------------------------- +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.kube_client import _enable_tcp_keepalive +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:30:1: AIR302 [*] `airflow.kubernetes.kube_client.get_kube_client` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +28 | _disable_verify_ssl() +29 | _enable_tcp_keepalive() +30 | get_kube_client() | ^^^^^^^^^^^^^^^ AIR302 -38 | -39 | add_pod_suffix() +31 | +32 | add_pod_suffix() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_kube_client` from `airflow.providers.cncf.kubernetes.kube_client` instead. -AIR302_kubernetes.py:39:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -37 | get_kube_client() -38 | -39 | add_pod_suffix() +ℹ Unsafe fix +11 11 | from airflow.kubernetes.kube_client import ( +12 12 | _disable_verify_ssl, +13 13 | _enable_tcp_keepalive, +14 |- get_kube_client, +15 14 | ) +16 15 | from airflow.kubernetes.kubernetes_helper_functions import ( +17 16 | add_pod_suffix, +18 17 | annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.kube_client import get_kube_client +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:32:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +30 | get_kube_client() +31 | +32 | add_pod_suffix() | ^^^^^^^^^^^^^^ AIR302 -40 | create_pod_id() +33 | annotations_for_logging_task_metadata() +34 | create_pod_id() | = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `add_unique_suffix` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. ℹ Safe fix -25 25 | Port, -26 26 | Resources, -27 27 | ) - 28 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import add_unique_suffix -28 29 | -29 30 | ALL_NAMESPACES -30 31 | POD_EXECUTOR_DONE_KEY +18 18 | annotations_for_logging_task_metadata, +19 19 | create_pod_id, +20 20 | ) + 21 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import add_unique_suffix +21 22 | +22 23 | ALL_NAMESPACES +23 24 | POD_EXECUTOR_DONE_KEY -------------------------------------------------------------------------------- -36 37 | _enable_tcp_keepalive() -37 38 | get_kube_client() -38 39 | -39 |-add_pod_suffix() - 40 |+add_unique_suffix() -40 41 | create_pod_id() -41 42 | -42 43 | annotations_for_logging_task_metadata() - -AIR302_kubernetes.py:40:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.create_pod_id` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -39 | add_pod_suffix() -40 | create_pod_id() - | ^^^^^^^^^^^^^ AIR302 -41 | -42 | annotations_for_logging_task_metadata() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `create_unique_id` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. - -ℹ Safe fix -25 25 | Port, -26 26 | Resources, -27 27 | ) - 28 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import create_unique_id -28 29 | -29 30 | ALL_NAMESPACES -30 31 | POD_EXECUTOR_DONE_KEY --------------------------------------------------------------------------------- -37 38 | get_kube_client() -38 39 | -39 40 | add_pod_suffix() -40 |-create_pod_id() - 41 |+create_unique_id() -41 42 | -42 43 | annotations_for_logging_task_metadata() -43 44 | annotations_to_key() - -AIR302_kubernetes.py:42:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.annotations_for_logging_task_metadata` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -40 | create_pod_id() -41 | -42 | annotations_for_logging_task_metadata() +29 30 | _enable_tcp_keepalive() +30 31 | get_kube_client() +31 32 | +32 |-add_pod_suffix() + 33 |+add_unique_suffix() +33 34 | annotations_for_logging_task_metadata() +34 35 | create_pod_id() +35 36 | + +AIR302_kubernetes.py:33:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.annotations_for_logging_task_metadata` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +32 | add_pod_suffix() +33 | annotations_for_logging_task_metadata() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -43 | annotations_to_key() -44 | get_logs_task_metadata() +34 | create_pod_id() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `annotations_for_logging_task_metadata` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. -AIR302_kubernetes.py:43:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.annotations_to_key` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -42 | annotations_for_logging_task_metadata() -43 | annotations_to_key() - | ^^^^^^^^^^^^^^^^^^ AIR302 -44 | get_logs_task_metadata() -45 | rand_str() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `annotations_to_key` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. - -AIR302_kubernetes.py:44:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.get_logs_task_metadata` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -42 | annotations_for_logging_task_metadata() -43 | annotations_to_key() -44 | get_logs_task_metadata() - | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 -45 | rand_str() +ℹ Unsafe fix +15 15 | ) +16 16 | from airflow.kubernetes.kubernetes_helper_functions import ( +17 17 | add_pod_suffix, +18 |- annotations_for_logging_task_metadata, +19 18 | create_pod_id, +20 19 | ) + 20 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import annotations_for_logging_task_metadata +21 21 | +22 22 | ALL_NAMESPACES +23 23 | POD_EXECUTOR_DONE_KEY + +AIR302_kubernetes.py:34:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.create_pod_id` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +32 | add_pod_suffix() +33 | annotations_for_logging_task_metadata() +34 | create_pod_id() + | ^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_logs_task_metadata` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `create_unique_id` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. -AIR302_kubernetes.py:45:1: AIR302 `airflow.kubernetes.kubernetes_helper_functions.rand_str` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -43 | annotations_to_key() -44 | get_logs_task_metadata() -45 | rand_str() - | ^^^^^^^^ AIR302 -46 | -47 | Port() +ℹ Safe fix +18 18 | annotations_for_logging_task_metadata, +19 19 | create_pod_id, +20 20 | ) + 21 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import create_unique_id +21 22 | +22 23 | ALL_NAMESPACES +23 24 | POD_EXECUTOR_DONE_KEY +-------------------------------------------------------------------------------- +31 32 | +32 33 | add_pod_suffix() +33 34 | annotations_for_logging_task_metadata() +34 |-create_pod_id() + 35 |+create_unique_id() +35 36 | +36 37 | +37 38 | from airflow.kubernetes.pod_generator import ( + +AIR302_kubernetes.py:49:1: AIR302 [*] `airflow.kubernetes.pod_generator.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +47 | ) +48 | +49 | PodDefaults() + | ^^^^^^^^^^^ AIR302 +50 | PodGenerator() +51 | add_pod_suffix() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. -AIR302_kubernetes.py:47:1: AIR302 [*] `airflow.kubernetes.pod.Port` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -45 | rand_str() -46 | -47 | Port() - | ^^^^ AIR302 -48 | Resources() +ℹ Unsafe fix +35 35 | +36 36 | +37 37 | from airflow.kubernetes.pod_generator import ( +38 |- PodDefaults, +39 38 | PodGenerator, +40 39 | add_pod_suffix, +41 40 | datetime_to_label_safe_datestring, +-------------------------------------------------------------------------------- +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.utils.xcom_sidecar import PodDefaults +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:50:1: AIR302 [*] `airflow.kubernetes.pod_generator.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +49 | PodDefaults() +50 | PodGenerator() + | ^^^^^^^^^^^^ AIR302 +51 | add_pod_suffix() +52 | datetime_to_label_safe_datestring() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1ContainerPort` from `kubernetes.client.models` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -ℹ Safe fix -25 25 | Port, -26 26 | Resources, -27 27 | ) - 28 |+from kubernetes.client.models import V1ContainerPort -28 29 | -29 30 | ALL_NAMESPACES -30 31 | POD_EXECUTOR_DONE_KEY +ℹ Unsafe fix +36 36 | +37 37 | from airflow.kubernetes.pod_generator import ( +38 38 | PodDefaults, +39 |- PodGenerator, +40 39 | add_pod_suffix, +41 40 | datetime_to_label_safe_datestring, +42 41 | extend_object_field, -------------------------------------------------------------------------------- -44 45 | get_logs_task_metadata() -45 46 | rand_str() -46 47 | -47 |-Port() - 48 |+V1ContainerPort() -48 49 | Resources() -49 50 | -50 51 | - -AIR302_kubernetes.py:48:1: AIR302 [*] `airflow.kubernetes.pod.Resources` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -47 | Port() -48 | Resources() - | ^^^^^^^^^ AIR302 +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:51:1: AIR302 [*] `airflow.kubernetes.pod_generator.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +49 | PodDefaults() +50 | PodGenerator() +51 | add_pod_suffix() + | ^^^^^^^^^^^^^^ AIR302 +52 | datetime_to_label_safe_datestring() +53 | extend_object_field() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1ResourceRequirements` from `kubernetes.client.models` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `add_unique_suffix` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. ℹ Safe fix -25 25 | Port, -26 26 | Resources, -27 27 | ) - 28 |+from kubernetes.client.models import V1ResourceRequirements -28 29 | -29 30 | ALL_NAMESPACES -30 31 | POD_EXECUTOR_DONE_KEY --------------------------------------------------------------------------------- -45 46 | rand_str() -46 47 | -47 48 | Port() -48 |-Resources() - 49 |+V1ResourceRequirements() -49 50 | -50 51 | -51 52 | from airflow.kubernetes.pod_generator import ( - -AIR302_kubernetes.py:64:1: AIR302 `airflow.kubernetes.pod_generator.datetime_to_label_safe_datestring` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -62 | ) -63 | -64 | datetime_to_label_safe_datestring() +45 45 | merge_objects, +46 46 | rand_str, +47 47 | ) + 48 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import add_unique_suffix +48 49 | +49 50 | PodDefaults() +50 51 | PodGenerator() +51 |-add_pod_suffix() + 52 |+add_unique_suffix() +52 53 | datetime_to_label_safe_datestring() +53 54 | extend_object_field() +54 55 | label_safe_datestring_to_datetime() + +AIR302_kubernetes.py:52:1: AIR302 [*] `airflow.kubernetes.pod_generator.datetime_to_label_safe_datestring` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +50 | PodGenerator() +51 | add_pod_suffix() +52 | datetime_to_label_safe_datestring() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -65 | extend_object_field() -66 | label_safe_datestring_to_datetime() +53 | extend_object_field() +54 | label_safe_datestring_to_datetime() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `datetime_to_label_safe_datestring` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:65:1: AIR302 `airflow.kubernetes.pod_generator.extend_object_field` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -64 | datetime_to_label_safe_datestring() -65 | extend_object_field() +ℹ Unsafe fix +38 38 | PodDefaults, +39 39 | PodGenerator, +40 40 | add_pod_suffix, +41 |- datetime_to_label_safe_datestring, +42 41 | extend_object_field, +43 42 | label_safe_datestring_to_datetime, +44 43 | make_safe_label_value, +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import datetime_to_label_safe_datestring +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:53:1: AIR302 [*] `airflow.kubernetes.pod_generator.extend_object_field` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +51 | add_pod_suffix() +52 | datetime_to_label_safe_datestring() +53 | extend_object_field() | ^^^^^^^^^^^^^^^^^^^ AIR302 -66 | label_safe_datestring_to_datetime() -67 | make_safe_label_value() +54 | label_safe_datestring_to_datetime() +55 | make_safe_label_value() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `extend_object_field` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:66:1: AIR302 `airflow.kubernetes.pod_generator.label_safe_datestring_to_datetime` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -64 | datetime_to_label_safe_datestring() -65 | extend_object_field() -66 | label_safe_datestring_to_datetime() +ℹ Unsafe fix +39 39 | PodGenerator, +40 40 | add_pod_suffix, +41 41 | datetime_to_label_safe_datestring, +42 |- extend_object_field, +43 42 | label_safe_datestring_to_datetime, +44 43 | make_safe_label_value, +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import extend_object_field +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:54:1: AIR302 [*] `airflow.kubernetes.pod_generator.label_safe_datestring_to_datetime` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +52 | datetime_to_label_safe_datestring() +53 | extend_object_field() +54 | label_safe_datestring_to_datetime() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -67 | make_safe_label_value() -68 | merge_objects() +55 | make_safe_label_value() +56 | merge_objects() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `label_safe_datestring_to_datetime` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:67:1: AIR302 `airflow.kubernetes.pod_generator.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -65 | extend_object_field() -66 | label_safe_datestring_to_datetime() -67 | make_safe_label_value() +ℹ Unsafe fix +40 40 | add_pod_suffix, +41 41 | datetime_to_label_safe_datestring, +42 42 | extend_object_field, +43 |- label_safe_datestring_to_datetime, +44 43 | make_safe_label_value, +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import label_safe_datestring_to_datetime +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:55:1: AIR302 [*] `airflow.kubernetes.pod_generator.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +53 | extend_object_field() +54 | label_safe_datestring_to_datetime() +55 | make_safe_label_value() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -68 | merge_objects() -69 | PodGenerator() +56 | merge_objects() +57 | rand_str() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `make_safe_label_value` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:68:1: AIR302 `airflow.kubernetes.pod_generator.merge_objects` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -66 | label_safe_datestring_to_datetime() -67 | make_safe_label_value() -68 | merge_objects() +ℹ Unsafe fix +41 41 | datetime_to_label_safe_datestring, +42 42 | extend_object_field, +43 43 | label_safe_datestring_to_datetime, +44 |- make_safe_label_value, +45 44 | merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import make_safe_label_value +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:56:1: AIR302 [*] `airflow.kubernetes.pod_generator.merge_objects` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +54 | label_safe_datestring_to_datetime() +55 | make_safe_label_value() +56 | merge_objects() | ^^^^^^^^^^^^^ AIR302 -69 | PodGenerator() -70 | PodDefaults() +57 | rand_str() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `merge_objects` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:69:1: AIR302 `airflow.kubernetes.pod_generator.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -67 | make_safe_label_value() -68 | merge_objects() -69 | PodGenerator() - | ^^^^^^^^^^^^ AIR302 -70 | PodDefaults() -71 | PodGeneratorDeprecated() +ℹ Unsafe fix +42 42 | extend_object_field, +43 43 | label_safe_datestring_to_datetime, +44 44 | make_safe_label_value, +45 |- merge_objects, +46 45 | rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.pod_generator import merge_objects +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:57:1: AIR302 [*] `airflow.kubernetes.pod_generator.rand_str` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +55 | make_safe_label_value() +56 | merge_objects() +57 | rand_str() + | ^^^^^^^^ AIR302 +58 | +59 | from airflow.kubernetes.pod_generator_deprecated import ( | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. -AIR302_kubernetes.py:70:1: AIR302 `airflow.kubernetes.pod_generator.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -68 | merge_objects() -69 | PodGenerator() -70 | PodDefaults() +ℹ Unsafe fix +43 43 | label_safe_datestring_to_datetime, +44 44 | make_safe_label_value, +45 45 | merge_objects, +46 |- rand_str, +47 46 | ) + 47 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import rand_str +48 48 | +49 49 | PodDefaults() +50 50 | PodGenerator() + +AIR302_kubernetes.py:69:1: AIR302 [*] `airflow.kubernetes.pod_generator_deprecated.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +67 | ) +68 | +69 | PodDefaults() | ^^^^^^^^^^^ AIR302 -71 | PodGeneratorDeprecated() -72 | add_pod_suffix() +70 | PodGenerator() +71 | make_safe_label_value() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. -AIR302_kubernetes.py:71:1: AIR302 `airflow.kubernetes.pod_generator.PodGeneratorDeprecated` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -69 | PodGenerator() -70 | PodDefaults() -71 | PodGeneratorDeprecated() - | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 -72 | add_pod_suffix() -73 | rand_str() +ℹ Unsafe fix +57 57 | rand_str() +58 58 | +59 59 | from airflow.kubernetes.pod_generator_deprecated import ( +60 |- PodDefaults, +61 60 | PodGenerator, +62 61 | make_safe_label_value, +63 62 | ) +-------------------------------------------------------------------------------- +65 64 | PodLauncher, +66 65 | PodStatus, +67 66 | ) + 67 |+from airflow.providers.cncf.kubernetes.utils.xcom_sidecar import PodDefaults +68 68 | +69 69 | PodDefaults() +70 70 | PodGenerator() + +AIR302_kubernetes.py:70:1: AIR302 [*] `airflow.kubernetes.pod_generator_deprecated.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +69 | PodDefaults() +70 | PodGenerator() + | ^^^^^^^^^^^^ AIR302 +71 | make_safe_label_value() | = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -AIR302_kubernetes.py:72:1: AIR302 [*] `airflow.kubernetes.pod_generator.add_pod_suffix` is moved into `cncf-kubernetes` provider in Airflow 3.0; +ℹ Unsafe fix +58 58 | +59 59 | from airflow.kubernetes.pod_generator_deprecated import ( +60 60 | PodDefaults, +61 |- PodGenerator, +62 61 | make_safe_label_value, +63 62 | ) +64 63 | from airflow.kubernetes.pod_launcher import ( +65 64 | PodLauncher, +66 65 | PodStatus, +67 66 | ) + 67 |+from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator +68 68 | +69 69 | PodDefaults() +70 70 | PodGenerator() + +AIR302_kubernetes.py:71:1: AIR302 [*] `airflow.kubernetes.pod_generator_deprecated.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +69 | PodDefaults() +70 | PodGenerator() +71 | make_safe_label_value() + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +72 | +73 | PodLauncher() | -70 | PodDefaults() -71 | PodGeneratorDeprecated() -72 | add_pod_suffix() - | ^^^^^^^^^^^^^^ AIR302 -73 | rand_str() + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `make_safe_label_value` from `airflow.providers.cncf.kubernetes.pod_generator` instead. + +ℹ Unsafe fix +59 59 | from airflow.kubernetes.pod_generator_deprecated import ( +60 60 | PodDefaults, +61 61 | PodGenerator, +62 |- make_safe_label_value, +63 62 | ) +64 63 | from airflow.kubernetes.pod_launcher import ( +65 64 | PodLauncher, +66 65 | PodStatus, +67 66 | ) + 67 |+from airflow.providers.cncf.kubernetes.pod_generator import make_safe_label_value +68 68 | +69 69 | PodDefaults() +70 70 | PodGenerator() + +AIR302_kubernetes.py:73:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +71 | make_safe_label_value() +72 | +73 | PodLauncher() + | ^^^^^^^^^^^ AIR302 +74 | PodStatus() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=10.0.0` and use `add_unique_suffix` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodManager` from `airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix -60 60 | merge_objects, -61 61 | rand_str, -62 62 | ) - 63 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import add_unique_suffix -63 64 | -64 65 | datetime_to_label_safe_datestring() -65 66 | extend_object_field() --------------------------------------------------------------------------------- -69 70 | PodGenerator() -70 71 | PodDefaults() -71 72 | PodGeneratorDeprecated() -72 |-add_pod_suffix() - 73 |+add_unique_suffix() -73 74 | rand_str() -74 75 | +65 65 | PodLauncher, +66 66 | PodStatus, +67 67 | ) + 68 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodManager +68 69 | +69 70 | PodDefaults() +70 71 | PodGenerator() +71 72 | make_safe_label_value() +72 73 | +73 |-PodLauncher() + 74 |+PodManager() +74 75 | PodStatus() 75 76 | +76 77 | from airflow.kubernetes.pod_launcher_deprecated import ( -AIR302_kubernetes.py:73:1: AIR302 `airflow.kubernetes.pod_generator.rand_str` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -71 | PodGeneratorDeprecated() -72 | add_pod_suffix() -73 | rand_str() - | ^^^^^^^^ AIR302 - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. - -AIR302_kubernetes.py:86:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; +AIR302_kubernetes.py:74:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodStatus` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -84 | ) -85 | -86 | PodDefaults() - | ^^^^^^^^^^^ AIR302 -87 | PodGenerator() -88 | make_safe_label_value() +73 | PodLauncher() +74 | PodStatus() + | ^^^^^^^^^ AIR302 +75 | +76 | from airflow.kubernetes.pod_launcher_deprecated import ( | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodPhase` from ` airflow.providers.cncf.kubernetes.utils.pod_manager` instead. -AIR302_kubernetes.py:87:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.PodGenerator` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -86 | PodDefaults() -87 | PodGenerator() - | ^^^^^^^^^^^^ AIR302 -88 | make_safe_label_value() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. +ℹ Safe fix +65 65 | PodLauncher, +66 66 | PodStatus, +67 67 | ) + 68 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodPhase +68 69 | +69 70 | PodDefaults() +70 71 | PodGenerator() +71 72 | make_safe_label_value() +72 73 | +73 74 | PodLauncher() +74 |-PodStatus() + 75 |+PodPhase() +75 76 | +76 77 | from airflow.kubernetes.pod_launcher_deprecated import ( +77 78 | PodDefaults, -AIR302_kubernetes.py:88:1: AIR302 `airflow.kubernetes.pod_generator_deprecated.make_safe_label_value` is moved into `cncf-kubernetes` provider in Airflow 3.0; +AIR302_kubernetes.py:90:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -86 | PodDefaults() -87 | PodGenerator() -88 | make_safe_label_value() - | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +88 | from airflow.kubernetes.volume_mount import VolumeMount 89 | -90 | PodLauncher() +90 | PodDefaults() + | ^^^^^^^^^^^ AIR302 +91 | PodLauncher() +92 | PodStatus() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `make_safe_label_value` from `airflow.providers.cncf.kubernetes.pod_generator` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. -AIR302_kubernetes.py:90:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -88 | make_safe_label_value() -89 | -90 | PodLauncher() +ℹ Unsafe fix +74 74 | PodStatus() +75 75 | +76 76 | from airflow.kubernetes.pod_launcher_deprecated import ( +77 |- PodDefaults, +78 77 | PodLauncher, +79 78 | PodStatus, +80 79 | get_kube_client, +-------------------------------------------------------------------------------- +86 85 | ) +87 86 | from airflow.kubernetes.volume import Volume +88 87 | from airflow.kubernetes.volume_mount import VolumeMount + 88 |+from airflow.providers.cncf.kubernetes.utils.xcom_sidecar import PodDefaults +89 89 | +90 90 | PodDefaults() +91 91 | PodLauncher() + +AIR302_kubernetes.py:91:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +90 | PodDefaults() +91 | PodLauncher() | ^^^^^^^^^^^ AIR302 -91 | PodStatus() +92 | PodStatus() +93 | get_kube_client() | = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodManager` from `airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix -82 82 | PodLauncher, -83 83 | PodStatus, -84 84 | ) - 85 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodManager -85 86 | -86 87 | PodDefaults() -87 88 | PodGenerator() -88 89 | make_safe_label_value() +86 86 | ) +87 87 | from airflow.kubernetes.volume import Volume +88 88 | from airflow.kubernetes.volume_mount import VolumeMount + 89 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodManager 89 90 | -90 |-PodLauncher() - 91 |+PodManager() -91 92 | PodStatus() -92 93 | -93 94 | - -AIR302_kubernetes.py:91:1: AIR302 [*] `airflow.kubernetes.pod_launcher.PodStatus` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -90 | PodLauncher() -91 | PodStatus() +90 91 | PodDefaults() +91 |-PodLauncher() + 92 |+PodManager() +92 93 | PodStatus() +93 94 | get_kube_client() +94 95 | + +AIR302_kubernetes.py:92:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodStatus` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +90 | PodDefaults() +91 | PodLauncher() +92 | PodStatus() | ^^^^^^^^^ AIR302 +93 | get_kube_client() | = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodPhase` from ` airflow.providers.cncf.kubernetes.utils.pod_manager` instead. ℹ Safe fix -82 82 | PodLauncher, -83 83 | PodStatus, -84 84 | ) - 85 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodPhase -85 86 | -86 87 | PodDefaults() -87 88 | PodGenerator() -88 89 | make_safe_label_value() +86 86 | ) +87 87 | from airflow.kubernetes.volume import Volume +88 88 | from airflow.kubernetes.volume_mount import VolumeMount + 89 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodPhase 89 90 | -90 91 | PodLauncher() -91 |-PodStatus() - 92 |+PodPhase() -92 93 | -93 94 | -94 95 | from airflow.kubernetes.pod_launcher_deprecated import ( - -AIR302_kubernetes.py:108:1: AIR302 `airflow.kubernetes.pod_launcher_deprecated.PodDefaults` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -106 | from airflow.kubernetes.volume_mount import VolumeMount -107 | -108 | PodDefaults() - | ^^^^^^^^^^^ AIR302 -109 | PodLauncher() -110 | PodStatus() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodDefaults` from `airflow.providers.cncf.kubernetes.utils.xcom_sidecar` instead. - -AIR302_kubernetes.py:109:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodLauncher` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -108 | PodDefaults() -109 | PodLauncher() - | ^^^^^^^^^^^ AIR302 -110 | PodStatus() -111 | get_kube_client() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodManager` from `airflow.providers.cncf.kubernetes.utils.pod_manager` instead. +90 91 | PodDefaults() +91 92 | PodLauncher() +92 |-PodStatus() + 93 |+PodPhase() +93 94 | get_kube_client() +94 95 | +95 96 | PodRuntimeInfoEnv() + +AIR302_kubernetes.py:93:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.get_kube_client` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +91 | PodLauncher() +92 | PodStatus() +93 | get_kube_client() + | ^^^^^^^^^^^^^^^ AIR302 +94 | +95 | PodRuntimeInfoEnv() + | + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_kube_client` from `airflow.providers.cncf.kubernetes.kube_client` instead. -ℹ Safe fix -104 104 | ) -105 105 | from airflow.kubernetes.volume import Volume -106 106 | from airflow.kubernetes.volume_mount import VolumeMount - 107 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodManager -107 108 | -108 109 | PodDefaults() -109 |-PodLauncher() - 110 |+PodManager() -110 111 | PodStatus() -111 112 | get_kube_client() -112 113 | +ℹ Unsafe fix +77 77 | PodDefaults, +78 78 | PodLauncher, +79 79 | PodStatus, +80 |- get_kube_client, +81 80 | ) +82 81 | from airflow.kubernetes.pod_runtime_info_env import PodRuntimeInfoEnv +83 82 | from airflow.kubernetes.secret import ( +-------------------------------------------------------------------------------- +86 85 | ) +87 86 | from airflow.kubernetes.volume import Volume +88 87 | from airflow.kubernetes.volume_mount import VolumeMount + 88 |+from airflow.providers.cncf.kubernetes.kube_client import get_kube_client +89 89 | +90 90 | PodDefaults() +91 91 | PodLauncher() -AIR302_kubernetes.py:110:1: AIR302 [*] `airflow.kubernetes.pod_launcher_deprecated.PodStatus` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -108 | PodDefaults() -109 | PodLauncher() -110 | PodStatus() - | ^^^^^^^^^ AIR302 -111 | get_kube_client() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=3.0.0` and use `PodPhase` from ` airflow.providers.cncf.kubernetes.utils.pod_manager` instead. +AIR302_kubernetes.py:95:1: AIR302 [*] `airflow.kubernetes.pod_runtime_info_env.PodRuntimeInfoEnv` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +93 | get_kube_client() +94 | +95 | PodRuntimeInfoEnv() + | ^^^^^^^^^^^^^^^^^ AIR302 +96 | K8SModel() +97 | Secret() + | + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1EnvVar` from `kubernetes.client.models` instead. ℹ Safe fix -104 104 | ) -105 105 | from airflow.kubernetes.volume import Volume -106 106 | from airflow.kubernetes.volume_mount import VolumeMount - 107 |+from airflow.providers.cncf.kubernetes.utils.pod_manager import PodPhase -107 108 | -108 109 | PodDefaults() -109 110 | PodLauncher() -110 |-PodStatus() - 111 |+PodPhase() -111 112 | get_kube_client() -112 113 | -113 114 | PodRuntimeInfoEnv() +86 86 | ) +87 87 | from airflow.kubernetes.volume import Volume +88 88 | from airflow.kubernetes.volume_mount import VolumeMount + 89 |+from kubernetes.client.models import V1EnvVar +89 90 | +90 91 | PodDefaults() +91 92 | PodLauncher() +92 93 | PodStatus() +93 94 | get_kube_client() +94 95 | +95 |-PodRuntimeInfoEnv() + 96 |+V1EnvVar() +96 97 | K8SModel() +97 98 | Secret() +98 99 | Volume() + +AIR302_kubernetes.py:96:1: AIR302 [*] `airflow.kubernetes.secret.K8SModel` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +95 | PodRuntimeInfoEnv() +96 | K8SModel() + | ^^^^^^^^ AIR302 +97 | Secret() +98 | Volume() + | + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `K8SModel` from `airflow.providers.cncf.kubernetes.k8s_model` instead. -AIR302_kubernetes.py:111:1: AIR302 `airflow.kubernetes.pod_launcher_deprecated.get_kube_client` is moved into `cncf-kubernetes` provider in Airflow 3.0; - | -109 | PodLauncher() -110 | PodStatus() -111 | get_kube_client() - | ^^^^^^^^^^^^^^^ AIR302 -112 | -113 | PodRuntimeInfoEnv() - | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_kube_client` from `airflow.providers.cncf.kubernetes.kube_client` instead. +ℹ Unsafe fix +81 81 | ) +82 82 | from airflow.kubernetes.pod_runtime_info_env import PodRuntimeInfoEnv +83 83 | from airflow.kubernetes.secret import ( +84 |- K8SModel, +85 84 | Secret, +86 85 | ) +87 86 | from airflow.kubernetes.volume import Volume +88 87 | from airflow.kubernetes.volume_mount import VolumeMount + 88 |+from airflow.providers.cncf.kubernetes.k8s_model import K8SModel +89 89 | +90 90 | PodDefaults() +91 91 | PodLauncher() + +AIR302_kubernetes.py:97:1: AIR302 [*] `airflow.kubernetes.secret.Secret` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +95 | PodRuntimeInfoEnv() +96 | K8SModel() +97 | Secret() + | ^^^^^^ AIR302 +98 | Volume() +99 | VolumeMount() + | + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `Secret` from `airflow.providers.cncf.kubernetes.secret` instead. + +ℹ Unsafe fix +82 82 | from airflow.kubernetes.pod_runtime_info_env import PodRuntimeInfoEnv +83 83 | from airflow.kubernetes.secret import ( +84 84 | K8SModel, +85 |- Secret, +86 85 | ) +87 86 | from airflow.kubernetes.volume import Volume +88 87 | from airflow.kubernetes.volume_mount import VolumeMount + 88 |+from airflow.providers.cncf.kubernetes.secret import Secret +89 89 | +90 90 | PodDefaults() +91 91 | PodLauncher() + +AIR302_kubernetes.py:98:1: AIR302 [*] `airflow.kubernetes.volume.Volume` is moved into `cncf-kubernetes` provider in Airflow 3.0; + | +96 | K8SModel() +97 | Secret() +98 | Volume() + | ^^^^^^ AIR302 +99 | VolumeMount() + | + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1Volume` from `kubernetes.client.models` instead. -AIR302_kubernetes.py:113:1: AIR302 [*] `airflow.kubernetes.pod_runtime_info_env.PodRuntimeInfoEnv` is moved into `cncf-kubernetes` provider in Airflow 3.0; +ℹ Safe fix +86 86 | ) +87 87 | from airflow.kubernetes.volume import Volume +88 88 | from airflow.kubernetes.volume_mount import VolumeMount + 89 |+from kubernetes.client.models import V1Volume +89 90 | +90 91 | PodDefaults() +91 92 | PodLauncher() +-------------------------------------------------------------------------------- +95 96 | PodRuntimeInfoEnv() +96 97 | K8SModel() +97 98 | Secret() +98 |-Volume() + 99 |+V1Volume() +99 100 | VolumeMount() +100 101 | +101 102 | from airflow.kubernetes.kubernetes_helper_functions import ( + +AIR302_kubernetes.py:99:1: AIR302 [*] `airflow.kubernetes.volume_mount.VolumeMount` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -111 | get_kube_client() -112 | -113 | PodRuntimeInfoEnv() - | ^^^^^^^^^^^^^^^^^ AIR302 -114 | K8SModel() -115 | Secret() + 97 | Secret() + 98 | Volume() + 99 | VolumeMount() + | ^^^^^^^^^^^ AIR302 +100 | +101 | from airflow.kubernetes.kubernetes_helper_functions import ( | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1EnvVar` from `kubernetes.client.models` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1VolumeMount` from `kubernetes.client.models` instead. ℹ Safe fix -104 104 | ) -105 105 | from airflow.kubernetes.volume import Volume -106 106 | from airflow.kubernetes.volume_mount import VolumeMount - 107 |+from kubernetes.client.models import V1EnvVar -107 108 | -108 109 | PodDefaults() -109 110 | PodLauncher() -110 111 | PodStatus() -111 112 | get_kube_client() -112 113 | -113 |-PodRuntimeInfoEnv() - 114 |+V1EnvVar() -114 115 | K8SModel() -115 116 | Secret() -116 117 | Volume() - -AIR302_kubernetes.py:114:1: AIR302 `airflow.kubernetes.secret.K8SModel` is moved into `cncf-kubernetes` provider in Airflow 3.0; +86 86 | ) +87 87 | from airflow.kubernetes.volume import Volume +88 88 | from airflow.kubernetes.volume_mount import VolumeMount + 89 |+from kubernetes.client.models import V1VolumeMount +89 90 | +90 91 | PodDefaults() +91 92 | PodLauncher() +-------------------------------------------------------------------------------- +96 97 | K8SModel() +97 98 | Secret() +98 99 | Volume() +99 |-VolumeMount() + 100 |+V1VolumeMount() +100 101 | +101 102 | from airflow.kubernetes.kubernetes_helper_functions import ( +102 103 | annotations_to_key, + +AIR302_kubernetes.py:107:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.annotations_to_key` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -113 | PodRuntimeInfoEnv() -114 | K8SModel() - | ^^^^^^^^ AIR302 -115 | Secret() -116 | Volume() +105 | ) +106 | +107 | annotations_to_key() + | ^^^^^^^^^^^^^^^^^^ AIR302 +108 | get_logs_task_metadata() +109 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `K8SModel` from `airflow.providers.cncf.kubernetes.k8s_model` instead. - -AIR302_kubernetes.py:115:1: AIR302 `airflow.kubernetes.secret.Secret` is moved into `cncf-kubernetes` provider in Airflow 3.0; + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `annotations_to_key` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + +ℹ Unsafe fix +99 99 | VolumeMount() +100 100 | +101 101 | from airflow.kubernetes.kubernetes_helper_functions import ( +102 |- annotations_to_key, +103 102 | get_logs_task_metadata, +104 103 | rand_str, +105 104 | ) + 105 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import annotations_to_key +106 106 | +107 107 | annotations_to_key() +108 108 | get_logs_task_metadata() + +AIR302_kubernetes.py:108:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.get_logs_task_metadata` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -113 | PodRuntimeInfoEnv() -114 | K8SModel() -115 | Secret() - | ^^^^^^ AIR302 -116 | Volume() -117 | VolumeMount() +107 | annotations_to_key() +108 | get_logs_task_metadata() + | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 +109 | rand_str() | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `Secret` from `airflow.providers.cncf.kubernetes.secret` instead. - -AIR302_kubernetes.py:116:1: AIR302 [*] `airflow.kubernetes.volume.Volume` is moved into `cncf-kubernetes` provider in Airflow 3.0; + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `get_logs_task_metadata` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + +ℹ Unsafe fix +100 100 | +101 101 | from airflow.kubernetes.kubernetes_helper_functions import ( +102 102 | annotations_to_key, +103 |- get_logs_task_metadata, +104 103 | rand_str, +105 104 | ) + 105 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import get_logs_task_metadata +106 106 | +107 107 | annotations_to_key() +108 108 | get_logs_task_metadata() + +AIR302_kubernetes.py:109:1: AIR302 [*] `airflow.kubernetes.kubernetes_helper_functions.rand_str` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -114 | K8SModel() -115 | Secret() -116 | Volume() - | ^^^^^^ AIR302 -117 | VolumeMount() +107 | annotations_to_key() +108 | get_logs_task_metadata() +109 | rand_str() + | ^^^^^^^^ AIR302 +110 | +111 | from airflow.kubernetes.pod_generator import PodGeneratorDeprecated | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1Volume` from `kubernetes.client.models` instead. - -ℹ Safe fix -104 104 | ) -105 105 | from airflow.kubernetes.volume import Volume -106 106 | from airflow.kubernetes.volume_mount import VolumeMount - 107 |+from kubernetes.client.models import V1Volume -107 108 | -108 109 | PodDefaults() -109 110 | PodLauncher() --------------------------------------------------------------------------------- -113 114 | PodRuntimeInfoEnv() -114 115 | K8SModel() -115 116 | Secret() -116 |-Volume() - 117 |+V1Volume() -117 118 | VolumeMount() - -AIR302_kubernetes.py:117:1: AIR302 [*] `airflow.kubernetes.volume_mount.VolumeMount` is moved into `cncf-kubernetes` provider in Airflow 3.0; + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `rand_str` from `airflow.providers.cncf.kubernetes.kubernetes_helper_functions` instead. + +ℹ Unsafe fix +101 101 | from airflow.kubernetes.kubernetes_helper_functions import ( +102 102 | annotations_to_key, +103 103 | get_logs_task_metadata, +104 |- rand_str, +105 104 | ) + 105 |+from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import rand_str +106 106 | +107 107 | annotations_to_key() +108 108 | get_logs_task_metadata() + +AIR302_kubernetes.py:113:1: AIR302 [*] `airflow.kubernetes.pod_generator.PodGeneratorDeprecated` is moved into `cncf-kubernetes` provider in Airflow 3.0; | -115 | Secret() -116 | Volume() -117 | VolumeMount() - | ^^^^^^^^^^^ AIR302 +111 | from airflow.kubernetes.pod_generator import PodGeneratorDeprecated +112 | +113 | PodGeneratorDeprecated() + | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 | - = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `V1VolumeMount` from `kubernetes.client.models` instead. + = help: Install `apache-airflow-providers-cncf-kubernetes>=7.4.0` and use `PodGenerator` from `airflow.providers.cncf.kubernetes.pod_generator` instead. -ℹ Safe fix -104 104 | ) -105 105 | from airflow.kubernetes.volume import Volume -106 106 | from airflow.kubernetes.volume_mount import VolumeMount - 107 |+from kubernetes.client.models import V1VolumeMount -107 108 | -108 109 | PodDefaults() -109 110 | PodLauncher() --------------------------------------------------------------------------------- -114 115 | K8SModel() -115 116 | Secret() -116 117 | Volume() -117 |-VolumeMount() - 118 |+V1VolumeMount() +ℹ Unsafe fix +109 109 | rand_str() +110 110 | +111 111 | from airflow.kubernetes.pod_generator import PodGeneratorDeprecated + 112 |+from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator +112 113 | +113 114 | PodGeneratorDeprecated() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap index 5032528ef3f467..7dee3ad2230615 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_mysql.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_mysql.py:9:1: AIR302 `airflow.hooks.mysql_hook.MySqlHook` is moved into `mysql` provider in Airflow 3.0; +AIR302_mysql.py:9:1: AIR302 [*] `airflow.hooks.mysql_hook.MySqlHook` is moved into `mysql` provider in Airflow 3.0; | 7 | ) 8 | @@ -12,7 +12,20 @@ AIR302_mysql.py:9:1: AIR302 `airflow.hooks.mysql_hook.MySqlHook` is moved into ` | = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `MySqlHook` from `airflow.providers.mysql.hooks.mysql` instead. -AIR302_mysql.py:10:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlOperator` is moved into `mysql` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.mysql_hook import MySqlHook +4 3 | from airflow.operators.presto_to_mysql import ( +5 4 | PrestoToMySqlOperator, +6 5 | PrestoToMySqlTransfer, +7 6 | ) + 7 |+from airflow.providers.mysql.hooks.mysql import MySqlHook +8 8 | +9 9 | MySqlHook() +10 10 | PrestoToMySqlOperator() + +AIR302_mysql.py:10:1: AIR302 [*] `airflow.operators.presto_to_mysql.PrestoToMySqlOperator` is moved into `mysql` provider in Airflow 3.0; | 9 | MySqlHook() 10 | PrestoToMySqlOperator() @@ -21,7 +34,19 @@ AIR302_mysql.py:10:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlOpe | = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `PrestoToMySqlOperator` from `airflow.providers.mysql.transfers.presto_to_mysql` instead. -AIR302_mysql.py:11:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlTransfer` is moved into `mysql` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.mysql_hook import MySqlHook +4 4 | from airflow.operators.presto_to_mysql import ( +5 |- PrestoToMySqlOperator, +6 5 | PrestoToMySqlTransfer, +7 6 | ) + 7 |+from airflow.providers.mysql.transfers.presto_to_mysql import PrestoToMySqlOperator +8 8 | +9 9 | MySqlHook() +10 10 | PrestoToMySqlOperator() + +AIR302_mysql.py:11:1: AIR302 [*] `airflow.operators.presto_to_mysql.PrestoToMySqlTransfer` is moved into `mysql` provider in Airflow 3.0; | 9 | MySqlHook() 10 | PrestoToMySqlOperator() @@ -29,3 +54,15 @@ AIR302_mysql.py:11:1: AIR302 `airflow.operators.presto_to_mysql.PrestoToMySqlTra | ^^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-mysql>=1.0.0` and use `PrestoToMySqlOperator` from `airflow.providers.mysql.transfers.presto_to_mysql` instead. + +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.mysql_hook import MySqlHook +4 4 | from airflow.operators.presto_to_mysql import ( +5 |- PrestoToMySqlOperator, +6 5 | PrestoToMySqlTransfer, +7 6 | ) + 7 |+from airflow.providers.mysql.transfers.presto_to_mysql import PrestoToMySqlOperator +8 8 | +9 9 | MySqlHook() +10 10 | PrestoToMySqlOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap index 300e736fe21ba6..c29dc82076b18f 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_oracle.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_oracle.py:5:1: AIR302 `airflow.hooks.oracle_hook.OracleHook` is moved into `oracle` provider in Airflow 3.0; +AIR302_oracle.py:5:1: AIR302 [*] `airflow.hooks.oracle_hook.OracleHook` is moved into `oracle` provider in Airflow 3.0; | 3 | from airflow.hooks.oracle_hook import OracleHook 4 | @@ -9,3 +9,11 @@ AIR302_oracle.py:5:1: AIR302 `airflow.hooks.oracle_hook.OracleHook` is moved int | ^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-oracle>=1.0.0` and use `OracleHook` from `airflow.providers.oracle.hooks.oracle` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.oracle_hook import OracleHook + 3 |+from airflow.providers.oracle.hooks.oracle import OracleHook +4 4 | +5 5 | OracleHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap index 47c24b7bb3a24a..99e06729e59248 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_papermill.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_papermill.py:5:1: AIR302 `airflow.operators.papermill_operator.PapermillOperator` is moved into `papermill` provider in Airflow 3.0; +AIR302_papermill.py:5:1: AIR302 [*] `airflow.operators.papermill_operator.PapermillOperator` is moved into `papermill` provider in Airflow 3.0; | 3 | from airflow.operators.papermill_operator import PapermillOperator 4 | @@ -9,3 +9,11 @@ AIR302_papermill.py:5:1: AIR302 `airflow.operators.papermill_operator.PapermillO | ^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-papermill>=1.0.0` and use `PapermillOperator` from `airflow.providers.papermill.operators.papermill` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.operators.papermill_operator import PapermillOperator + 3 |+from airflow.providers.papermill.operators.papermill import PapermillOperator +4 4 | +5 5 | PapermillOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap index ebdf12601ef2ce..a9e0812af0fc92 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_pig.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_pig.py:6:1: AIR302 `airflow.hooks.pig_hook.PigCliHook` is moved into `apache-pig` provider in Airflow 3.0; +AIR302_pig.py:6:1: AIR302 [*] `airflow.hooks.pig_hook.PigCliHook` is moved into `apache-pig` provider in Airflow 3.0; | 4 | from airflow.operators.pig_operator import PigOperator 5 | @@ -11,10 +11,30 @@ AIR302_pig.py:6:1: AIR302 `airflow.hooks.pig_hook.PigCliHook` is moved into `apa | = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `PigCliHook` from `airflow.providers.apache.pig.hooks.pig` instead. -AIR302_pig.py:7:1: AIR302 `airflow.operators.pig_operator.PigOperator` is moved into `apache-pig` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.pig_hook import PigCliHook +4 3 | from airflow.operators.pig_operator import PigOperator + 4 |+from airflow.providers.apache.pig.hooks.pig import PigCliHook +5 5 | +6 6 | PigCliHook() +7 7 | PigOperator() + +AIR302_pig.py:7:1: AIR302 [*] `airflow.operators.pig_operator.PigOperator` is moved into `apache-pig` provider in Airflow 3.0; | 6 | PigCliHook() 7 | PigOperator() | ^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-apache-pig>=1.0.0` and use `PigOperator` from `airflow.providers.apache.pig.operators.pig` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.pig_hook import PigCliHook +4 |-from airflow.operators.pig_operator import PigOperator + 4 |+from airflow.providers.apache.pig.operators.pig import PigOperator +5 5 | +6 6 | PigCliHook() +7 7 | PigOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap index 152004281dc4b0..6f026c04651e43 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_postgres.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_postgres.py:6:1: AIR302 `airflow.hooks.postgres_hook.PostgresHook` is moved into `postgres` provider in Airflow 3.0; +AIR302_postgres.py:6:1: AIR302 [*] `airflow.hooks.postgres_hook.PostgresHook` is moved into `postgres` provider in Airflow 3.0; | 4 | from airflow.operators.postgres_operator import Mapping 5 | @@ -11,6 +11,16 @@ AIR302_postgres.py:6:1: AIR302 `airflow.hooks.postgres_hook.PostgresHook` is mov | = help: Install `apache-airflow-providers-postgres>=1.0.0` and use `PostgresHook` from `airflow.providers.postgres.hooks.postgres` instead. +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.postgres_hook import PostgresHook +4 3 | from airflow.operators.postgres_operator import Mapping + 4 |+from airflow.providers.postgres.hooks.postgres import PostgresHook +5 5 | +6 6 | PostgresHook() +7 7 | Mapping() + AIR302_postgres.py:7:1: AIR302 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0 | 6 | PostgresHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap index b83445e9b3776d..3c0117a196b90b 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_presto.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_presto.py:5:1: AIR302 `airflow.hooks.presto_hook.PrestoHook` is moved into `presto` provider in Airflow 3.0; +AIR302_presto.py:5:1: AIR302 [*] `airflow.hooks.presto_hook.PrestoHook` is moved into `presto` provider in Airflow 3.0; | 3 | from airflow.hooks.presto_hook import PrestoHook 4 | @@ -9,3 +9,11 @@ AIR302_presto.py:5:1: AIR302 `airflow.hooks.presto_hook.PrestoHook` is moved int | ^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-presto>=1.0.0` and use `PrestoHook` from `airflow.providers.presto.hooks.presto` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.presto_hook import PrestoHook + 3 |+from airflow.providers.presto.hooks.presto import PrestoHook +4 4 | +5 5 | PrestoHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap index ed5fd6f01c7d63..02ec8f73138d57 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_samba.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_samba.py:5:1: AIR302 `airflow.hooks.samba_hook.SambaHook` is moved into `samba` provider in Airflow 3.0; +AIR302_samba.py:5:1: AIR302 [*] `airflow.hooks.samba_hook.SambaHook` is moved into `samba` provider in Airflow 3.0; | 3 | from airflow.hooks.samba_hook import SambaHook 4 | @@ -9,3 +9,11 @@ AIR302_samba.py:5:1: AIR302 `airflow.hooks.samba_hook.SambaHook` is moved into ` | ^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-samba>=1.0.0` and use `SambaHook` from `airflow.providers.samba.hooks.samba` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.samba_hook import SambaHook + 3 |+from airflow.providers.samba.hooks.samba import SambaHook +4 4 | +5 5 | SambaHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap index bdcca0d6584003..fbb1c5033c424c 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_slack.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_slack.py:6:1: AIR302 `airflow.hooks.slack_hook.SlackHook` is moved into `slack` provider in Airflow 3.0; +AIR302_slack.py:6:1: AIR302 [*] `airflow.hooks.slack_hook.SlackHook` is moved into `slack` provider in Airflow 3.0; | 4 | from airflow.operators.slack_operator import SlackAPIOperator, SlackAPIPostOperator 5 | @@ -12,7 +12,17 @@ AIR302_slack.py:6:1: AIR302 `airflow.hooks.slack_hook.SlackHook` is moved into ` | = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackHook` from `airflow.providers.slack.hooks.slack` instead. -AIR302_slack.py:7:1: AIR302 `airflow.operators.slack_operator.SlackAPIOperator` is moved into `slack` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.slack_hook import SlackHook +4 3 | from airflow.operators.slack_operator import SlackAPIOperator, SlackAPIPostOperator + 4 |+from airflow.providers.slack.hooks.slack import SlackHook +5 5 | +6 6 | SlackHook() +7 7 | SlackAPIOperator() + +AIR302_slack.py:7:1: AIR302 [*] `airflow.operators.slack_operator.SlackAPIOperator` is moved into `slack` provider in Airflow 3.0; | 6 | SlackHook() 7 | SlackAPIOperator() @@ -21,7 +31,18 @@ AIR302_slack.py:7:1: AIR302 `airflow.operators.slack_operator.SlackAPIOperator` | = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackAPIOperator` from `airflow.providers.slack.operators.slack` instead. -AIR302_slack.py:8:1: AIR302 `airflow.operators.slack_operator.SlackAPIPostOperator` is moved into `slack` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.slack_hook import SlackHook +4 |-from airflow.operators.slack_operator import SlackAPIOperator, SlackAPIPostOperator + 4 |+from airflow.operators.slack_operator import SlackAPIPostOperator + 5 |+from airflow.providers.slack.operators.slack import SlackAPIOperator +5 6 | +6 7 | SlackHook() +7 8 | SlackAPIOperator() + +AIR302_slack.py:8:1: AIR302 [*] `airflow.operators.slack_operator.SlackAPIPostOperator` is moved into `slack` provider in Airflow 3.0; | 6 | SlackHook() 7 | SlackAPIOperator() @@ -29,3 +50,14 @@ AIR302_slack.py:8:1: AIR302 `airflow.operators.slack_operator.SlackAPIPostOperat | ^^^^^^^^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-slack>=1.0.0` and use `SlackAPIPostOperator` from `airflow.providers.slack.operators.slack` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.slack_hook import SlackHook +4 |-from airflow.operators.slack_operator import SlackAPIOperator, SlackAPIPostOperator + 4 |+from airflow.operators.slack_operator import SlackAPIOperator + 5 |+from airflow.providers.slack.operators.slack import SlackAPIPostOperator +5 6 | +6 7 | SlackHook() +7 8 | SlackAPIOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap index 57de3258a1803b..ed4184860418ba 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_smtp.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_smtp.py:5:1: AIR302 `airflow.operators.email_operator.EmailOperator` is moved into `smtp` provider in Airflow 3.0; +AIR302_smtp.py:5:1: AIR302 [*] `airflow.operators.email_operator.EmailOperator` is moved into `smtp` provider in Airflow 3.0; | 3 | from airflow.operators.email_operator import EmailOperator 4 | @@ -12,7 +12,16 @@ AIR302_smtp.py:5:1: AIR302 `airflow.operators.email_operator.EmailOperator` is m | = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `EmailOperator` from `airflow.providers.smtp.operators.smtp` instead. -AIR302_smtp.py:9:1: AIR302 `airflow.operators.email.EmailOperator` is moved into `smtp` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.operators.email_operator import EmailOperator + 3 |+from airflow.providers.smtp.operators.smtp import EmailOperator +4 4 | +5 5 | EmailOperator() +6 6 | + +AIR302_smtp.py:9:1: AIR302 [*] `airflow.operators.email.EmailOperator` is moved into `smtp` provider in Airflow 3.0; | 7 | from airflow.operators.email import EmailOperator 8 | @@ -20,3 +29,12 @@ AIR302_smtp.py:9:1: AIR302 `airflow.operators.email.EmailOperator` is moved into | ^^^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-smtp>=1.0.0` and use `EmailOperator` from `airflow.providers.smtp.operators.smtp` instead. + +ℹ Unsafe fix +4 4 | +5 5 | EmailOperator() +6 6 | +7 |-from airflow.operators.email import EmailOperator + 7 |+from airflow.providers.smtp.operators.smtp import EmailOperator +8 8 | +9 9 | EmailOperator() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap index c7f759ded27735..543d17fc0c2bfa 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_sqlite.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_sqlite.py:5:1: AIR302 `airflow.hooks.sqlite_hook.SqliteHook` is moved into `sqlite` provider in Airflow 3.0; +AIR302_sqlite.py:5:1: AIR302 [*] `airflow.hooks.sqlite_hook.SqliteHook` is moved into `sqlite` provider in Airflow 3.0; | 3 | from airflow.hooks.sqlite_hook import SqliteHook 4 | @@ -9,3 +9,11 @@ AIR302_sqlite.py:5:1: AIR302 `airflow.hooks.sqlite_hook.SqliteHook` is moved int | ^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-sqlite>=1.0.0` and use `SqliteHook` from `airflow.providers.sqlite.hooks.sqlite` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.sqlite_hook import SqliteHook + 3 |+from airflow.providers.sqlite.hooks.sqlite import SqliteHook +4 4 | +5 5 | SqliteHook() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap index c5be3bce31105f..4ad415626de339 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_standard.py.snap @@ -1,232 +1,490 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_standard.py:25:1: AIR302 `airflow.operators.bash_operator.BashOperator` is moved into `standard` provider in Airflow 3.0; +AIR302_standard.py:20:1: AIR302 [*] `airflow.operators.bash_operator.BashOperator` is moved into `standard` provider in Airflow 3.0; | -23 | ) -24 | -25 | BashOperator() +18 | ) +19 | +20 | BashOperator() | ^^^^^^^^^^^^ AIR302 -26 | -27 | TriggerDagRunLink() +21 | +22 | TriggerDagRunLink() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BashOperator` from `airflow.providers.standard.operators.bash` instead. -AIR302_standard.py:27:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.operators.bash_operator import BashOperator +4 3 | from airflow.operators.dagrun_operator import ( +5 4 | TriggerDagRunLink, +6 5 | TriggerDagRunOperator, +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.bash import BashOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:22:1: AIR302 [*] `airflow.operators.dagrun_operator.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; | -25 | BashOperator() -26 | -27 | TriggerDagRunLink() +20 | BashOperator() +21 | +22 | TriggerDagRunLink() | ^^^^^^^^^^^^^^^^^ AIR302 -28 | TriggerDagRunOperator() -29 | DummyOperator() +23 | TriggerDagRunOperator() | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunLink` from `airflow.providers.standard.operators.trigger_dagrun` instead. -AIR302_standard.py:28:1: AIR302 `airflow.operators.dagrun_operator.TriggerDagRunOperator` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +2 2 | +3 3 | from airflow.operators.bash_operator import BashOperator +4 4 | from airflow.operators.dagrun_operator import ( +5 |- TriggerDagRunLink, +6 5 | TriggerDagRunOperator, +7 6 | ) +8 7 | from airflow.operators.latest_only_operator import LatestOnlyOperator +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunLink +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:23:1: AIR302 [*] `airflow.operators.dagrun_operator.TriggerDagRunOperator` is moved into `standard` provider in Airflow 3.0; | -27 | TriggerDagRunLink() -28 | TriggerDagRunOperator() +22 | TriggerDagRunLink() +23 | TriggerDagRunOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -29 | DummyOperator() -30 | EmptyOperator() +24 | +25 | LatestOnlyOperator() | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunOperator` from `airflow.providers.standard.operators.trigger_dagrun` instead. -AIR302_standard.py:29:1: AIR302 `airflow.operators.dummy.DummyOperator` is moved into `standard` provider in Airflow 3.0; - | -27 | TriggerDagRunLink() -28 | TriggerDagRunOperator() -29 | DummyOperator() - | ^^^^^^^^^^^^^ AIR302 -30 | EmptyOperator() - | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. +ℹ Unsafe fix +3 3 | from airflow.operators.bash_operator import BashOperator +4 4 | from airflow.operators.dagrun_operator import ( +5 5 | TriggerDagRunLink, +6 |- TriggerDagRunOperator, +7 6 | ) +8 7 | from airflow.operators.latest_only_operator import LatestOnlyOperator +9 8 | from airflow.operators.python_operator import ( +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunOperator +19 19 | +20 20 | BashOperator() +21 21 | -AIR302_standard.py:30:1: AIR302 `airflow.operators.dummy.EmptyOperator` is moved into `standard` provider in Airflow 3.0; +AIR302_standard.py:25:1: AIR302 [*] `airflow.operators.latest_only_operator.LatestOnlyOperator` is moved into `standard` provider in Airflow 3.0; | -28 | TriggerDagRunOperator() -29 | DummyOperator() -30 | EmptyOperator() - | ^^^^^^^^^^^^^ AIR302 -31 | -32 | LatestOnlyOperator() - | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. - -AIR302_standard.py:32:1: AIR302 `airflow.operators.latest_only_operator.LatestOnlyOperator` is moved into `standard` provider in Airflow 3.0; - | -30 | EmptyOperator() -31 | -32 | LatestOnlyOperator() +23 | TriggerDagRunOperator() +24 | +25 | LatestOnlyOperator() | ^^^^^^^^^^^^^^^^^^ AIR302 -33 | -34 | BranchPythonOperator() +26 | +27 | BranchPythonOperator() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `LatestOnlyOperator` from `airflow.providers.standard.operators.latest_only` instead. -AIR302_standard.py:34:1: AIR302 `airflow.operators.python_operator.BranchPythonOperator` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +5 5 | TriggerDagRunLink, +6 6 | TriggerDagRunOperator, +7 7 | ) +8 |-from airflow.operators.latest_only_operator import LatestOnlyOperator +9 8 | from airflow.operators.python_operator import ( +10 9 | BranchPythonOperator, +11 10 | PythonOperator, +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.latest_only import LatestOnlyOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:27:1: AIR302 [*] `airflow.operators.python_operator.BranchPythonOperator` is moved into `standard` provider in Airflow 3.0; | -32 | LatestOnlyOperator() -33 | -34 | BranchPythonOperator() +25 | LatestOnlyOperator() +26 | +27 | BranchPythonOperator() | ^^^^^^^^^^^^^^^^^^^^ AIR302 -35 | PythonOperator() -36 | PythonVirtualenvOperator() +28 | PythonOperator() +29 | PythonVirtualenvOperator() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchPythonOperator` from `airflow.providers.standard.operators.python` instead. -AIR302_standard.py:35:1: AIR302 `airflow.operators.python_operator.PythonOperator` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +7 7 | ) +8 8 | from airflow.operators.latest_only_operator import LatestOnlyOperator +9 9 | from airflow.operators.python_operator import ( +10 |- BranchPythonOperator, +11 10 | PythonOperator, +12 11 | PythonVirtualenvOperator, +13 12 | ShortCircuitOperator, +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.python import BranchPythonOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:28:1: AIR302 [*] `airflow.operators.python_operator.PythonOperator` is moved into `standard` provider in Airflow 3.0; | -34 | BranchPythonOperator() -35 | PythonOperator() +27 | BranchPythonOperator() +28 | PythonOperator() | ^^^^^^^^^^^^^^ AIR302 -36 | PythonVirtualenvOperator() -37 | ShortCircuitOperator() +29 | PythonVirtualenvOperator() +30 | ShortCircuitOperator() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonOperator` from `airflow.providers.standard.operators.python` instead. -AIR302_standard.py:36:1: AIR302 `airflow.operators.python_operator.PythonVirtualenvOperator` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +8 8 | from airflow.operators.latest_only_operator import LatestOnlyOperator +9 9 | from airflow.operators.python_operator import ( +10 10 | BranchPythonOperator, +11 |- PythonOperator, +12 11 | PythonVirtualenvOperator, +13 12 | ShortCircuitOperator, +14 13 | ) +-------------------------------------------------------------------------------- +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.python import PythonOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:29:1: AIR302 [*] `airflow.operators.python_operator.PythonVirtualenvOperator` is moved into `standard` provider in Airflow 3.0; | -34 | BranchPythonOperator() -35 | PythonOperator() -36 | PythonVirtualenvOperator() +27 | BranchPythonOperator() +28 | PythonOperator() +29 | PythonVirtualenvOperator() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 -37 | ShortCircuitOperator() +30 | ShortCircuitOperator() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonVirtualenvOperator` from `airflow.providers.standard.operators.python` instead. -AIR302_standard.py:37:1: AIR302 `airflow.operators.python_operator.ShortCircuitOperator` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +9 9 | from airflow.operators.python_operator import ( +10 10 | BranchPythonOperator, +11 11 | PythonOperator, +12 |- PythonVirtualenvOperator, +13 12 | ShortCircuitOperator, +14 13 | ) +15 14 | from airflow.sensors.external_task_sensor import ( +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.python import PythonVirtualenvOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:30:1: AIR302 [*] `airflow.operators.python_operator.ShortCircuitOperator` is moved into `standard` provider in Airflow 3.0; | -35 | PythonOperator() -36 | PythonVirtualenvOperator() -37 | ShortCircuitOperator() +28 | PythonOperator() +29 | PythonVirtualenvOperator() +30 | ShortCircuitOperator() | ^^^^^^^^^^^^^^^^^^^^ AIR302 -38 | -39 | ExternalTaskMarker() +31 | +32 | ExternalTaskMarker() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `ShortCircuitOperator` from `airflow.providers.standard.operators.python` instead. -AIR302_standard.py:39:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskMarker` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +10 10 | BranchPythonOperator, +11 11 | PythonOperator, +12 12 | PythonVirtualenvOperator, +13 |- ShortCircuitOperator, +14 13 | ) +15 14 | from airflow.sensors.external_task_sensor import ( +16 15 | ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.operators.python import ShortCircuitOperator +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:32:1: AIR302 [*] `airflow.sensors.external_task_sensor.ExternalTaskMarker` is moved into `standard` provider in Airflow 3.0; | -37 | ShortCircuitOperator() -38 | -39 | ExternalTaskMarker() +30 | ShortCircuitOperator() +31 | +32 | ExternalTaskMarker() | ^^^^^^^^^^^^^^^^^^ AIR302 -40 | ExternalTaskSensor() -41 | ExternalTaskSensorLink() +33 | ExternalTaskSensor() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskMarker` from `airflow.providers.standard.sensors.external_task` instead. -AIR302_standard.py:40:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskSensor` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +13 13 | ShortCircuitOperator, +14 14 | ) +15 15 | from airflow.sensors.external_task_sensor import ( +16 |- ExternalTaskMarker, +17 16 | ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.sensors.external_task import ExternalTaskMarker +19 19 | +20 20 | BashOperator() +21 21 | + +AIR302_standard.py:33:1: AIR302 [*] `airflow.sensors.external_task_sensor.ExternalTaskSensor` is moved into `standard` provider in Airflow 3.0; | -39 | ExternalTaskMarker() -40 | ExternalTaskSensor() +32 | ExternalTaskMarker() +33 | ExternalTaskSensor() | ^^^^^^^^^^^^^^^^^^ AIR302 -41 | ExternalTaskSensorLink() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensor` from `airflow.providers.standard.sensors.external_task` instead. -AIR302_standard.py:41:1: AIR302 `airflow.sensors.external_task_sensor.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; - | -39 | ExternalTaskMarker() -40 | ExternalTaskSensor() -41 | ExternalTaskSensorLink() - | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 -42 | -43 | from airflow.operators.dummy_operator import ( - | - = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensorLink` from `airflow.providers.standard.sensors.external_task` instead. - -AIR302_standard.py:48:1: AIR302 `airflow.operators.dummy_operator.DummyOperator` is moved into `standard` provider in Airflow 3.0; - | -46 | ) -47 | -48 | DummyOperator() - | ^^^^^^^^^^^^^ AIR302 -49 | EmptyOperator() - | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. - -AIR302_standard.py:49:1: AIR302 `airflow.operators.dummy_operator.EmptyOperator` is moved into `standard` provider in Airflow 3.0; - | -48 | DummyOperator() -49 | EmptyOperator() - | ^^^^^^^^^^^^^ AIR302 -50 | -51 | from airflow.hooks.subprocess import SubprocessResult - | - = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. +ℹ Unsafe fix +14 14 | ) +15 15 | from airflow.sensors.external_task_sensor import ( +16 16 | ExternalTaskMarker, +17 |- ExternalTaskSensor, +18 17 | ) + 18 |+from airflow.providers.standard.sensors.external_task import ExternalTaskSensor +19 19 | +20 20 | BashOperator() +21 21 | -AIR302_standard.py:52:1: AIR302 `airflow.hooks.subprocess.SubprocessResult` is moved into `standard` provider in Airflow 3.0; +AIR302_standard.py:38:1: AIR302 [*] `airflow.hooks.subprocess.SubprocessResult` is moved into `standard` provider in Airflow 3.0; | -51 | from airflow.hooks.subprocess import SubprocessResult -52 | SubprocessResult() +36 | from airflow.hooks.subprocess import SubprocessResult +37 | +38 | SubprocessResult() | ^^^^^^^^^^^^^^^^ AIR302 -53 | from airflow.hooks.subprocess import working_directory -54 | working_directory() +39 | +40 | from airflow.hooks.subprocess import working_directory | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `SubprocessResult` from `airflow.providers.standard.hooks.subprocess` instead. -AIR302_standard.py:54:1: AIR302 `airflow.hooks.subprocess.working_directory` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +33 33 | ExternalTaskSensor() +34 34 | +35 35 | +36 |-from airflow.hooks.subprocess import SubprocessResult + 36 |+from airflow.providers.standard.hooks.subprocess import SubprocessResult +37 37 | +38 38 | SubprocessResult() +39 39 | + +AIR302_standard.py:42:1: AIR302 [*] `airflow.hooks.subprocess.working_directory` is moved into `standard` provider in Airflow 3.0; | -52 | SubprocessResult() -53 | from airflow.hooks.subprocess import working_directory -54 | working_directory() +40 | from airflow.hooks.subprocess import working_directory +41 | +42 | working_directory() | ^^^^^^^^^^^^^^^^^ AIR302 -55 | from airflow.operators.datetime import target_times_as_dates -56 | target_times_as_dates() +43 | +44 | from airflow.operators.datetime import target_times_as_dates | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `working_directory` from `airflow.providers.standard.hooks.subprocess` instead. -AIR302_standard.py:56:1: AIR302 `airflow.operators.datetime.target_times_as_dates` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +37 37 | +38 38 | SubprocessResult() +39 39 | +40 |-from airflow.hooks.subprocess import working_directory + 40 |+from airflow.providers.standard.hooks.subprocess import working_directory +41 41 | +42 42 | working_directory() +43 43 | + +AIR302_standard.py:46:1: AIR302 [*] `airflow.operators.datetime.target_times_as_dates` is moved into `standard` provider in Airflow 3.0; | -54 | working_directory() -55 | from airflow.operators.datetime import target_times_as_dates -56 | target_times_as_dates() +44 | from airflow.operators.datetime import target_times_as_dates +45 | +46 | target_times_as_dates() | ^^^^^^^^^^^^^^^^^^^^^ AIR302 -57 | from airflow.operators.trigger_dagrun import TriggerDagRunLink -58 | TriggerDagRunLink() +47 | +48 | from airflow.operators.trigger_dagrun import TriggerDagRunLink | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `target_times_as_dates` from `airflow.providers.standard.operators.datetime` instead. -AIR302_standard.py:58:1: AIR302 `airflow.operators.trigger_dagrun.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +41 41 | +42 42 | working_directory() +43 43 | +44 |-from airflow.operators.datetime import target_times_as_dates + 44 |+from airflow.providers.standard.operators.datetime import target_times_as_dates +45 45 | +46 46 | target_times_as_dates() +47 47 | + +AIR302_standard.py:50:1: AIR302 [*] `airflow.operators.trigger_dagrun.TriggerDagRunLink` is moved into `standard` provider in Airflow 3.0; | -56 | target_times_as_dates() -57 | from airflow.operators.trigger_dagrun import TriggerDagRunLink -58 | TriggerDagRunLink() +48 | from airflow.operators.trigger_dagrun import TriggerDagRunLink +49 | +50 | TriggerDagRunLink() | ^^^^^^^^^^^^^^^^^ AIR302 -59 | from airflow.sensors.external_task import ExternalTaskSensorLink -60 | ExternalTaskSensorLink() +51 | +52 | from airflow.sensors.external_task import ExternalTaskSensorLink | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunLink` from `airflow.providers.standard.operators.trigger_dagrun` instead. -AIR302_standard.py:60:1: AIR302 [*] `airflow.sensors.external_task.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; +ℹ Unsafe fix +45 45 | +46 46 | target_times_as_dates() +47 47 | +48 |-from airflow.operators.trigger_dagrun import TriggerDagRunLink + 48 |+from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunLink +49 49 | +50 50 | TriggerDagRunLink() +51 51 | + +AIR302_standard.py:54:1: AIR302 [*] `airflow.sensors.external_task.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; | -58 | TriggerDagRunLink() -59 | from airflow.sensors.external_task import ExternalTaskSensorLink -60 | ExternalTaskSensorLink() +52 | from airflow.sensors.external_task import ExternalTaskSensorLink +53 | +54 | ExternalTaskSensorLink() | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 -61 | from airflow.sensors.time_delta import WaitSensor -62 | WaitSensor() +55 | +56 | from airflow.sensors.time_delta import WaitSensor | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalDagLink` from `airflow.providers.standard.sensors.external_task` instead. ℹ Safe fix -57 57 | from airflow.operators.trigger_dagrun import TriggerDagRunLink -58 58 | TriggerDagRunLink() -59 59 | from airflow.sensors.external_task import ExternalTaskSensorLink -60 |-ExternalTaskSensorLink() - 60 |+from airflow.providers.standard.sensors.external_task import ExternalDagLink - 61 |+ExternalDagLink() -61 62 | from airflow.sensors.time_delta import WaitSensor -62 63 | WaitSensor() - -AIR302_standard.py:62:1: AIR302 `airflow.sensors.time_delta.WaitSensor` is moved into `standard` provider in Airflow 3.0; - | -60 | ExternalTaskSensorLink() -61 | from airflow.sensors.time_delta import WaitSensor -62 | WaitSensor() +50 50 | TriggerDagRunLink() +51 51 | +52 52 | from airflow.sensors.external_task import ExternalTaskSensorLink + 53 |+from airflow.providers.standard.sensors.external_task import ExternalDagLink +53 54 | +54 |-ExternalTaskSensorLink() + 55 |+ExternalDagLink() +55 56 | +56 57 | from airflow.sensors.time_delta import WaitSensor +57 58 | + +AIR302_standard.py:58:1: AIR302 [*] `airflow.sensors.time_delta.WaitSensor` is moved into `standard` provider in Airflow 3.0; + | +56 | from airflow.sensors.time_delta import WaitSensor +57 | +58 | WaitSensor() | ^^^^^^^^^^ AIR302 +59 | +60 | from airflow.operators.dummy import DummyOperator | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `WaitSensor` from `airflow.providers.standard.sensors.time_delta` instead. + +ℹ Unsafe fix +53 53 | +54 54 | ExternalTaskSensorLink() +55 55 | +56 |-from airflow.sensors.time_delta import WaitSensor + 56 |+from airflow.providers.standard.sensors.time_delta import WaitSensor +57 57 | +58 58 | WaitSensor() +59 59 | + +AIR302_standard.py:62:1: AIR302 [*] `airflow.operators.dummy.DummyOperator` is moved into `standard` provider in Airflow 3.0; + | +60 | from airflow.operators.dummy import DummyOperator +61 | +62 | DummyOperator() + | ^^^^^^^^^^^^^ AIR302 +63 | +64 | from airflow.operators.dummy import EmptyOperator + | + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. + +ℹ Safe fix +58 58 | WaitSensor() +59 59 | +60 60 | from airflow.operators.dummy import DummyOperator + 61 |+from airflow.providers.standard.operators.empty import EmptyOperator +61 62 | +62 |-DummyOperator() + 63 |+EmptyOperator() +63 64 | +64 65 | from airflow.operators.dummy import EmptyOperator +65 66 | + +AIR302_standard.py:66:1: AIR302 [*] `airflow.operators.dummy.EmptyOperator` is moved into `standard` provider in Airflow 3.0; + | +64 | from airflow.operators.dummy import EmptyOperator +65 | +66 | EmptyOperator() + | ^^^^^^^^^^^^^ AIR302 +67 | +68 | from airflow.operators.dummy_operator import DummyOperator + | + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. + +ℹ Unsafe fix +61 61 | +62 62 | DummyOperator() +63 63 | +64 |-from airflow.operators.dummy import EmptyOperator + 64 |+from airflow.providers.standard.operators.empty import EmptyOperator +65 65 | +66 66 | EmptyOperator() +67 67 | + +AIR302_standard.py:70:1: AIR302 [*] `airflow.operators.dummy_operator.DummyOperator` is moved into `standard` provider in Airflow 3.0; + | +68 | from airflow.operators.dummy_operator import DummyOperator +69 | +70 | DummyOperator() + | ^^^^^^^^^^^^^ AIR302 +71 | +72 | from airflow.operators.dummy_operator import EmptyOperator + | + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. + +ℹ Unsafe fix +66 66 | EmptyOperator() +67 67 | +68 68 | from airflow.operators.dummy_operator import DummyOperator + 69 |+from airflow.providers.standard.operators.empty import EmptyOperator +69 70 | +70 71 | DummyOperator() +71 72 | + +AIR302_standard.py:74:1: AIR302 [*] `airflow.operators.dummy_operator.EmptyOperator` is moved into `standard` provider in Airflow 3.0; + | +72 | from airflow.operators.dummy_operator import EmptyOperator +73 | +74 | EmptyOperator() + | ^^^^^^^^^^^^^ AIR302 +75 | +76 | from airflow.sensors.external_task_sensor import ExternalTaskSensorLink + | + = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. + +ℹ Unsafe fix +69 69 | +70 70 | DummyOperator() +71 71 | +72 |-from airflow.operators.dummy_operator import EmptyOperator + 72 |+from airflow.providers.standard.operators.empty import EmptyOperator +73 73 | +74 74 | EmptyOperator() +75 75 | + +AIR302_standard.py:78:1: AIR302 [*] `airflow.sensors.external_task_sensor.ExternalTaskSensorLink` is moved into `standard` provider in Airflow 3.0; + | +76 | from airflow.sensors.external_task_sensor import ExternalTaskSensorLink +77 | +78 | ExternalTaskSensorLink() + | ^^^^^^^^^^^^^^^^^^^^^^ AIR302 + | + = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalDagLink` from `airflow.providers.standard.sensors.external_task` instead. + +ℹ Safe fix +74 74 | EmptyOperator() +75 75 | +76 76 | from airflow.sensors.external_task_sensor import ExternalTaskSensorLink + 77 |+from airflow.providers.standard.sensors.external_task import ExternalDagLink +77 78 | +78 |-ExternalTaskSensorLink() + 79 |+ExternalDagLink() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap index 8b6488034f21f6..e0ee88653f4dbf 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_zendesk.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR302_zendesk.py:5:1: AIR302 `airflow.hooks.zendesk_hook.ZendeskHook` is moved into `zendesk` provider in Airflow 3.0; +AIR302_zendesk.py:5:1: AIR302 [*] `airflow.hooks.zendesk_hook.ZendeskHook` is moved into `zendesk` provider in Airflow 3.0; | 3 | from airflow.hooks.zendesk_hook import ZendeskHook 4 | @@ -9,3 +9,11 @@ AIR302_zendesk.py:5:1: AIR302 `airflow.hooks.zendesk_hook.ZendeskHook` is moved | ^^^^^^^^^^^ AIR302 | = help: Install `apache-airflow-providers-zendesk>=1.0.0` and use `ZendeskHook` from `airflow.providers.zendesk.hooks.zendesk` instead. + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.zendesk_hook import ZendeskHook + 3 |+from airflow.providers.zendesk.hooks.zendesk import ZendeskHook +4 4 | +5 5 | ZendeskHook() From 695de4f27f66c4968b79d913826488deee19cb52 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Fri, 30 May 2025 01:17:18 +0200 Subject: [PATCH 279/487] [ty] Add diagnosis for function with no return statement but with return type annotation (#18359) ## Summary Partially implement https://github.com/astral-sh/ty/issues/538, ```py from pathlib import Path def setup_test_project(registry_name: str, registry_url: str, project_dir: str) -> Path: pyproject_file = Path(project_dir) / "pyproject.toml" pyproject_file.write_text("...", encoding="utf-8") ``` As no return statement is defined in the function `setup_test_project` with annotated return type `Path`, we provide the following diagnosis : - error[invalid-return-type]: Function **always** implicitly returns `None`, which is not assignable to return type `Path` with a subdiagnostic : - note: Consider changing your return annotation to `-> None` or adding a `return` statement ## Test Plan mdtests with snapshots to capture the subdiagnostic. I have to mention that existing snapshots were modified since they now fall in this category. --------- Co-authored-by: Carl Meyer --- .../resources/mdtest/function/return_type.md | 14 ++++++++ ..._`inv\342\200\246_(35563a74094b14d5).snap" | 3 +- ...t_ret\342\200\246_(393cb38bf7119649).snap" | 3 +- ...t_ret\342\200\246_(3d2d19aa49b28f1c).snap" | 34 +++++++++++++++++++ ...nvalid_return_type_(a91e0c67519cd77f).snap | 6 ++-- ...type_\342\200\246_(c3a523878447af6b).snap" | 6 ++-- .../src/types/diagnostic.rs | 22 +++++++++--- crates/ty_python_semantic/src/types/infer.rs | 2 ++ 8 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/function/return_type.md b/crates/ty_python_semantic/resources/mdtest/function/return_type.md index 93d4843b70c8ca..1ca2b10dca9b44 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/ty_python_semantic/resources/mdtest/function/return_type.md @@ -278,6 +278,20 @@ def f(cond: bool) -> int: return 2 ``` +## Invalid implicit return type always None + + + +If the function has no `return` statement or if it has only bare `return` statement (no variable in +the return statement), then we show a diagnostic hint that the return annotation should be `-> None` +or a `return` statement should be added. + +```py +# error: [invalid-return-type] +def f() -> int: + print("hello") +``` + ## NotImplemented ### Default Python version diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" index 67c96c3139e5f4..d9a0e67f6d4594 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" @@ -24,13 +24,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `str` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `str` --> src/mdtest_snippet.py:7:25 | 6 | class Concrete(Abstract): 7 | def method(self) -> str: ... # error: [invalid-return-type] | ^^^ | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: Only functions in stub files, methods on protocol classes, or methods with `@abstractmethod` are permitted to have empty bodies info: Class `Concrete` has `typing.Protocol` in its MRO, but it is not a protocol class info: Only classes that directly inherit from `typing.Protocol` or `typing_extensions.Protocol` are considered protocol classes diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" index e77bd91b65ba51..889888f0a30cab 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" @@ -71,7 +71,7 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:12:22 | 11 | # error: [invalid-return-type] @@ -80,6 +80,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 13 | if cond: 14 | raise ValueError() | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" new file mode 100644 index 00000000000000..a4fd88d692c93d --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" @@ -0,0 +1,34 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: return_type.md - Function return type - Invalid implicit return type always None +mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | # error: [invalid-return-type] +2 | def f() -> int: +3 | print("hello") +``` + +# Diagnostics + +``` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` + --> src/mdtest_snippet.py:2:12 + | +1 | # error: [invalid-return-type] +2 | def f() -> int: + | ^^^ +3 | print("hello") + | +info: Consider changing the return annotation to `-> None` or adding a `return` statement +info: rule `invalid-return-type` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap index d452f178202ba8..4f36ded37648c3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:2:12 | 1 | # error: [invalid-return-type] @@ -43,6 +43,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not | ^^^ 3 | 1 | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` @@ -84,13 +85,14 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `T` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T` --> src/mdtest_snippet.py:18:16 | 17 | # error: [invalid-return-type] 18 | def m(x: T) -> T: ... | ^ | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" index bf469ce066d669..6f834b61e36376 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" @@ -46,7 +46,7 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:6:14 | 5 | # error: [invalid-return-type] @@ -55,12 +55,13 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 7 | print("...") 8 | ... | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:11:14 | 10 | # error: [invalid-return-type] @@ -69,6 +70,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 12 | f"""{foo} is a function that ...""" 13 | ... | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 7943b53d76e2ba..e93101375599e0 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1685,15 +1685,29 @@ pub(super) fn report_implicit_return_type( expected_ty: Type, has_empty_body: bool, enclosing_class_of_method: Option, + no_return: bool, ) { let Some(builder) = context.report_lint(&INVALID_RETURN_TYPE, range) else { return; }; let db = context.db(); - let mut diagnostic = builder.into_diagnostic(format_args!( - "Function can implicitly return `None`, which is not assignable to return type `{}`", - expected_ty.display(db) - )); + + // If no return statement is defined in the function, then the function always returns `None` + let mut diagnostic = if no_return { + let mut diag = builder.into_diagnostic(format_args!( + "Function always implicitly returns `None`, which is not assignable to return type `{}`", + expected_ty.display(db), + )); + diag.info( + "Consider changing the return annotation to `-> None` or adding a `return` statement", + ); + diag + } else { + builder.into_diagnostic(format_args!( + "Function can implicitly return `None`, which is not assignable to return type `{}`", + expected_ty.display(db), + )) + }; if !has_empty_body { return; } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 6187e87ca1d142..50964ded3daaf1 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1872,12 +1872,14 @@ impl<'db> TypeInferenceBuilder<'db> { if use_def.can_implicit_return(self.db()) && !Type::none(self.db()).is_assignable_to(self.db(), declared_ty) { + let no_return = self.return_types_and_ranges.is_empty(); report_implicit_return_type( &self.context, returns.range(), declared_ty, has_empty_body, enclosing_class_context, + no_return, ); } } From 9b0dfc505f41f354b94ce9083adedfb43440f3d2 Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 30 May 2025 06:27:27 +0700 Subject: [PATCH 280/487] [ty] Callable types are disjoint from non-callable `@final` nominal instance types (#18368) ## Summary Resolves [#513](https://github.com/astral-sh/ty/issues/513). Callable types are now considered to be disjoint from nominal instance types where: * The class is `@final`, and * Its `__call__` either does not exist or is not assignable to `(...) -> Unknown`. ## Test Plan Markdown tests. --------- Co-authored-by: Carl Meyer --- .../type_properties/is_disjoint_from.md | 48 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 20 ++++++++ 2 files changed, 68 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index a5935725b6ae38..46cf9c907483e5 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -439,3 +439,51 @@ static_assert(is_disjoint_from(Callable[[], None], Literal[b""])) static_assert(is_disjoint_from(Callable[[], None], Literal[1])) static_assert(is_disjoint_from(Callable[[], None], Literal[True])) ``` + +A callable type is disjoint from nominal instance types where the classes are final and whose +`__call__` is not callable. + +```py +from ty_extensions import CallableTypeOf, is_disjoint_from, static_assert +from typing_extensions import Any, Callable, final + +@final +class C: ... + +static_assert(is_disjoint_from(bool, Callable[..., Any])) +static_assert(is_disjoint_from(C, Callable[..., Any])) +static_assert(is_disjoint_from(bool | C, Callable[..., Any])) + +static_assert(not is_disjoint_from(str, Callable[..., Any])) +static_assert(not is_disjoint_from(bool | str, Callable[..., Any])) + +def bound_with_valid_type(): + @final + class D: + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + + static_assert(not is_disjoint_from(D, Callable[..., Any])) + +def possibly_unbound_with_valid_type(flag: bool): + @final + class E: + if flag: + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + + static_assert(not is_disjoint_from(E, Callable[..., Any])) + +def bound_with_invalid_type(): + @final + class F: + __call__: int = 1 + + static_assert(is_disjoint_from(F, Callable[..., Any])) + +def possibly_unbound_with_invalid_type(flag: bool): + @final + class G: + if flag: + __call__: int = 1 + + static_assert(is_disjoint_from(G, Callable[..., Any])) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dfded95182bb42..c8f3afa6256c6a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2177,6 +2177,26 @@ impl<'db> Type<'db> { true } + ( + Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), + Type::NominalInstance(instance), + ) + | ( + Type::NominalInstance(instance), + Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), + ) if instance.class.is_final(db) => { + let member = self.member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ); + match member.symbol { + // TODO: ideally this would check disjointness of the `__call__` signature and the callable signature + Symbol::Type(ty, _) => !ty.is_assignable_to(db, CallableType::unknown(db)), + Symbol::Unbound => true, + } + } + ( Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _, From 363f061f0920158c7eedd27825bc39f197543547 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 29 May 2025 17:11:13 -0700 Subject: [PATCH 281/487] [ty] _typeshed.Self is not a special form (#18377) ## Summary This change was based on a mis-reading of a comment in typeshed, and a wrong assumption about what was causing a test failure in a prior PR. Reverting it doesn't cause any tests to fail. ## Test Plan Existing tests. --- crates/ty_python_semantic/src/types/special_form.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index 73ef8362ad540b..a17531cb1339fe 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -79,7 +79,7 @@ pub enum SpecialFormType { /// The symbol `typing.Callable` /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) Callable, - /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or `_typeshed.Self`) + /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self`) #[strum(serialize = "Self")] TypingSelf, /// The symbol `typing.Final` (which can also be found as `typing_extensions.Final`) @@ -227,16 +227,12 @@ impl SpecialFormType { | Self::TypeGuard | Self::TypedDict | Self::TypeIs + | Self::TypingSelf | Self::Protocol | Self::ReadOnly => { matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) } - Self::TypingSelf => matches!( - module, - KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed - ), - Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy From ad2f667ee4323dd0c12224338734d722d13d28b4 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 30 May 2025 07:32:21 +0100 Subject: [PATCH 282/487] [ty] Improve tests for `site-packages` discovery (#18374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Convert tests demonstrating our resilience to malformed/absent `version` fields in `pyvenf.cfg` files to mdtests. Also make them more expansive. - Convert the regression test I added in https://github.com/astral-sh/ruff/pull/18157 to an mdtest - Add comments next to unit tests that cannot be converted to mdtests (but where it's not obvious why they can't) so I don't have to do this exercise again 😄 - In `site_packages.rs`, factor out the logic for figuring out where we expect the system-installation `site-packages` to be. Currently we have the same logic twice. ## Test Plan `cargo test -p ty_python_semantic` --- .../mdtest/import/site_packages_discovery.md | 148 ++++++++++++++++++ .../ty_python_semantic/src/site_packages.rs | 92 ++++------- 2 files changed, 175 insertions(+), 65 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md index 8a60a61afb3d5b..a1c256c3755334 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md +++ b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md @@ -1,5 +1,115 @@ # Tests for `site-packages` discovery +## Malformed or absent `version` fields + +The `version`/`version_info` key in a `pyvenv.cfg` file is provided by most virtual-environment +creation tools to indicate the Python version the virtual environment is for. They key is useful for +our purposes, so we try to parse it when possible. However, the key is not read by the CPython +standard library, and is provided under different keys depending on which virtual-environment +creation tool created the `pyvenv.cfg` file (the stdlib `venv` module calls the key `version`, +whereas uv and virtualenv both call it `version_info`). We therefore do not return an error when +discovering a virtual environment's `site-packages` directory if the virtula environment contains a +`pyvenv.cfg` file which doesn't have this key, or if the associated value of the key doesn't parse +according to our expectations. The file isn't really *invalid* in this situation. + +### No `version` field + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/src/main.py`: + +```py +from foo import X + +reveal_type(X) # revealed: int +``` + +### Malformed stdlib-style version field + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +version = wut +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/src/main.py`: + +```py +from foo import X + +reveal_type(X) # revealed: int +``` + +### Malformed uv-style version field + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +version_info = no-really-wut +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/src/main.py`: + +```py +from foo import X + +reveal_type(X) # revealed: int +``` + ## Ephemeral uv environments If you use the `--with` flag when invoking `uv run`, uv will create an "ephemeral" virtual @@ -57,3 +167,41 @@ from bar import Y reveal_type(X) # revealed: int reveal_type(Y) # revealed: str ``` + +## `pyvenv.cfg` files with unusual values + +`pyvenv.cfg` files can have unusual values in them, which can contain arbitrary characters. This +includes `=` characters. The following is a regression test for +. + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +version_info = 3.13 +command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3 +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/src/main.py`: + +```py +from foo import X + +reveal_type(X) # revealed: int +``` diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index c01db5483e8f3a..7da9e9ca7c93d9 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -1001,22 +1001,7 @@ mod tests { )) }; - let expected_system_site_packages = if cfg!(target_os = "windows") { - SystemPathBuf::from(&*format!( - r"\Python3.{}\Lib\site-packages", - self.minor_version - )) - } else if self.free_threaded { - SystemPathBuf::from(&*format!( - "/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages", - minor_version = self.minor_version - )) - } else { - SystemPathBuf::from(&*format!( - "/Python3.{minor_version}/lib/python3.{minor_version}/site-packages", - minor_version = self.minor_version - )) - }; + let expected_system_site_packages = self.expected_system_site_packages(); if self_venv.system_site_packages { assert_eq!( @@ -1051,33 +1036,33 @@ mod tests { ); let site_packages_directories = env.site_packages_directories(&self.system).unwrap(); + let expected_site_packages = self.expected_system_site_packages(); + assert_eq!( + site_packages_directories, + std::slice::from_ref(&expected_site_packages) + ); + } - let expected_site_packages = if cfg!(target_os = "windows") { - SystemPathBuf::from(&*format!( - r"\Python3.{}\Lib\site-packages", - self.minor_version - )) + fn expected_system_site_packages(&self) -> SystemPathBuf { + let minor_version = self.minor_version; + if cfg!(target_os = "windows") { + SystemPathBuf::from(&*format!(r"\Python3.{minor_version}\Lib\site-packages")) } else if self.free_threaded { SystemPathBuf::from(&*format!( - "/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages", - minor_version = self.minor_version + "/Python3.{minor_version}/lib/python3.{minor_version}t/site-packages" )) } else { SystemPathBuf::from(&*format!( - "/Python3.{minor_version}/lib/python3.{minor_version}/site-packages", - minor_version = self.minor_version + "/Python3.{minor_version}/lib/python3.{minor_version}/site-packages" )) - }; - - assert_eq!( - site_packages_directories, - [expected_site_packages].as_slice() - ); + } } } #[test] fn can_find_site_packages_directory_no_virtual_env() { + // Shouldn't be converted to an mdtest because mdtest automatically creates a + // pyvenv.cfg file for you if it sees you creating a `site-packages` directory. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, @@ -1090,6 +1075,8 @@ mod tests { #[test] fn can_find_site_packages_directory_no_virtual_env_freethreaded() { + // Shouldn't be converted to an mdtest because mdtest automatically creates a + // pyvenv.cfg file for you if it sees you creating a `site-packages` directory. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 13, @@ -1132,23 +1119,10 @@ mod tests { ); } - #[test] - fn can_find_site_packages_directory_no_version_field_in_pyvenv_cfg() { - let test = PythonEnvironmentTestCase { - system: TestSystem::default(), - minor_version: 12, - free_threaded: false, - origin: SysPrefixPathOrigin::VirtualEnvVar, - virtual_env: Some(VirtualEnvironmentTestCase { - pyvenv_cfg_version_field: None, - ..VirtualEnvironmentTestCase::default() - }), - }; - test.run(); - } - #[test] fn can_find_site_packages_directory_venv_style_version_field_in_pyvenv_cfg() { + // Shouldn't be converted to an mdtest because we want to assert + // that we parsed the `version` field correctly in `test.run()`. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, @@ -1164,6 +1138,8 @@ mod tests { #[test] fn can_find_site_packages_directory_uv_style_version_field_in_pyvenv_cfg() { + // Shouldn't be converted to an mdtest because we want to assert + // that we parsed the `version` field correctly in `test.run()`. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, @@ -1179,6 +1155,8 @@ mod tests { #[test] fn can_find_site_packages_directory_virtualenv_style_version_field_in_pyvenv_cfg() { + // Shouldn't be converted to an mdtest because we want to assert + // that we parsed the `version` field correctly in `test.run()`. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 12, @@ -1209,6 +1187,9 @@ mod tests { #[test] fn finds_system_site_packages() { + // Can't be converted to an mdtest because the system installation's `sys.prefix` + // path is at a different location relative to the `pyvenv.cfg` file's `home` value + // on Windows. let test = PythonEnvironmentTestCase { system: TestSystem::default(), minor_version: 13, @@ -1366,25 +1347,6 @@ mod tests { ); } - /// See - #[test] - fn parsing_pyvenv_cfg_with_equals_in_value() { - let test = PythonEnvironmentTestCase { - system: TestSystem::default(), - minor_version: 13, - free_threaded: true, - origin: SysPrefixPathOrigin::VirtualEnvVar, - virtual_env: Some(VirtualEnvironmentTestCase { - pyvenv_cfg_version_field: Some("version_info = 3.13"), - command_field: Some( - r#"command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3"#, - ), - ..VirtualEnvironmentTestCase::default() - }), - }; - test.run(); - } - #[test] fn parsing_pyvenv_cfg_with_key_but_no_value_fails() { let system = TestSystem::default(); From b5b6b657cc058661a89e1006895ae4b1d912b7e3 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 30 May 2025 20:46:39 +0800 Subject: [PATCH 283/487] [`airflow`] Add unsafe fix for module moved cases (`AIR301`) (#18367) ## Summary Follow up on https://github.com/astral-sh/ruff/pull/18093 and apply it to AIR301 ## Test Plan The existing test fixtures have been updated --- .../test/fixtures/airflow/AIR301_names.py | 70 +- .../test/fixtures/airflow/AIR301_names_fix.py | 79 +- .../airflow/AIR301_provider_names_fix.py | 64 +- .../src/rules/airflow/rules/removal_in_3.rs | 34 +- ...irflow__tests__AIR301_AIR301_names.py.snap | 713 ++++---------- ...ow__tests__AIR301_AIR301_names_fix.py.snap | 888 ++++++++++++++---- ...__AIR301_AIR301_provider_names_fix.py.snap | 391 ++++---- 7 files changed, 1196 insertions(+), 1043 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py index e3654a4c52ba68..89335ae88b0c6e 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py @@ -10,22 +10,10 @@ PY312, ) from airflow.api_connexion.security import requires_access -from airflow.configuration import ( - as_dict, - get, - getboolean, - getfloat, - getint, - has_option, - remove_option, - set, -) from airflow.contrib.aws_athena_hook import AWSAthenaHook from airflow.datasets import DatasetAliasEvent -from airflow.hooks.base_hook import BaseHook from airflow.operators.subdag import SubDagOperator from airflow.secrets.local_filesystem import LocalFilesystemBackend -from airflow.sensors.base_sensor_operator import BaseSensorOperator from airflow.triggers.external_task import TaskStateTrigger from airflow.utils import dates from airflow.utils.dag_cycle_tester import test_cycle @@ -40,13 +28,10 @@ ) from airflow.utils.db import create_session from airflow.utils.decorators import apply_defaults -from airflow.utils.file import TemporaryDirectory, mkdirs -from airflow.utils.helpers import chain as helper_chain -from airflow.utils.helpers import cross_downstream as helper_cross_downstream -from airflow.utils.log import secrets_masker +from airflow.utils.file import mkdirs from airflow.utils.state import SHUTDOWN, terminating_states from airflow.utils.trigger_rule import TriggerRule -from airflow.www.auth import has_access +from airflow.www.auth import has_access, has_access_dataset from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key # airflow root @@ -55,11 +40,6 @@ # airflow.api_connexion.security requires_access - -# airflow.configuration -get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - - # airflow.contrib.* AWSAthenaHook() @@ -68,10 +48,6 @@ DatasetAliasEvent() -# airflow.hooks -BaseHook() - - # airflow.operators.subdag.* SubDagOperator() @@ -81,10 +57,6 @@ LocalFilesystemBackend() -# airflow.sensors.base_sensor_operator -BaseSensorOperator() - - # airflow.triggers.external_task TaskStateTrigger() @@ -114,15 +86,8 @@ apply_defaults # airflow.utils.file -TemporaryDirectory() mkdirs -# airflow.utils.helpers -helper_chain -helper_cross_downstream - -# airflow.utils.log -secrets_masker # airflow.utils.state SHUTDOWN @@ -135,37 +100,8 @@ # airflow.www.auth has_access +has_access_dataset # airflow.www.utils get_sensitive_variables_fields should_hide_value_for_key - -# airflow.operators.python -from airflow.operators.python import get_current_context - -get_current_context() - -# airflow.providers.mysql -from airflow.providers.mysql.datasets.mysql import sanitize_uri - -sanitize_uri - -# airflow.providers.postgres -from airflow.providers.postgres.datasets.postgres import sanitize_uri - -sanitize_uri - -# airflow.providers.trino -from airflow.providers.trino.datasets.trino import sanitize_uri - -sanitize_uri - -# airflow.notifications.basenotifier -from airflow.notifications.basenotifier import BaseNotifier - -BaseNotifier() - -# airflow.auth.manager -from airflow.auth.managers.base_auth_manager import BaseAuthManager - -BaseAuthManager() diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_fix.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_fix.py index ac4f45eb428117..556c211c7990f0 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_fix.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names_fix.py @@ -3,7 +3,6 @@ from airflow.api_connexion.security import requires_access_dataset from airflow.auth.managers.models.resource_details import ( DatasetDetails, - ) from airflow.datasets.manager import ( DatasetManager, @@ -12,15 +11,13 @@ ) from airflow.lineage.hook import DatasetLineageInfo from airflow.metrics.validators import AllowListValidator, BlockListValidator -from airflow.secrets.local_filesystm import load_connections +from airflow.secrets.local_filesystem import load_connections from airflow.security.permissions import RESOURCE_DATASET -from airflow.www.auth import has_access_dataset requires_access_dataset() DatasetDetails() - DatasetManager() dataset_manager() resolve_dataset_manager() @@ -34,7 +31,6 @@ RESOURCE_DATASET -has_access_dataset() from airflow.listeners.spec.dataset import ( on_dataset_changed, @@ -43,3 +39,76 @@ on_dataset_created() on_dataset_changed() + + +# airflow.operators.python +from airflow.operators.python import get_current_context + +get_current_context() + +# airflow.providers.mysql +from airflow.providers.mysql.datasets.mysql import sanitize_uri + +sanitize_uri + +# airflow.providers.postgres +from airflow.providers.postgres.datasets.postgres import sanitize_uri + +sanitize_uri + +# airflow.providers.trino +from airflow.providers.trino.datasets.trino import sanitize_uri + +sanitize_uri + +# airflow.notifications.basenotifier +from airflow.notifications.basenotifier import BaseNotifier + +BaseNotifier() + +# airflow.auth.manager +from airflow.auth.managers.base_auth_manager import BaseAuthManager + +BaseAuthManager() + + +from airflow.configuration import ( + as_dict, + get, + getboolean, + getfloat, + getint, + has_option, + remove_option, + set, +) + +# airflow.configuration +get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +from airflow.hooks.base_hook import BaseHook + +# airflow.hooks +BaseHook() + +from airflow.sensors.base_sensor_operator import BaseSensorOperator + +# airflow.sensors.base_sensor_operator +BaseSensorOperator() +BaseHook() + +from airflow.utils.helpers import chain as helper_chain +from airflow.utils.helpers import cross_downstream as helper_cross_downstream + +# airflow.utils.helpers +helper_chain +helper_cross_downstream + +# airflow.utils.file +from airflow.utils.file import TemporaryDirectory + +TemporaryDirectory() + +from airflow.utils.log import secrets_masker + +# airflow.utils.log +secrets_masker diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_provider_names_fix.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_provider_names_fix.py index f5a22b6c169aeb..a89048d3e47f81 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_provider_names_fix.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_provider_names_fix.py @@ -1,54 +1,54 @@ from __future__ import annotations from airflow.providers.amazon.aws.auth_manager.avp.entities import AvpEntities -from airflow.providers.amazon.aws.datasets.s3 import ( - convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage, -) -from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset -from airflow.providers.common.io.dataset.file import ( - convert_dataset_to_openlineage as io_convert_dataset_to_openlineage, -) -from airflow.providers.common.io.dataset.file import create_dataset as io_create_dataset - -from airflow.providers.google.datasets.bigquery import ( - create_dataset as bigquery_create_dataset, -) -from airflow.providers.google.datasets.gcs import ( - convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage, -) -from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset from airflow.providers.openlineage.utils.utils import ( DatasetInfo, translate_airflow_dataset, ) +from airflow.secrets.local_filesystem import load_connections +from airflow.security.permissions import RESOURCE_DATASET AvpEntities.DATASET +# airflow.providers.openlineage.utils.utils +DatasetInfo() +translate_airflow_dataset() + +# airflow.secrets.local_filesystem +load_connections() + +# airflow.security.permissions +RESOURCE_DATASET + +from airflow.providers.amazon.aws.datasets.s3 import ( + convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage, +) +from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset + s3_create_dataset() s3_convert_dataset_to_openlineage() +from airflow.providers.common.io.dataset.file import ( + convert_dataset_to_openlineage as io_convert_dataset_to_openlineage, +) +from airflow.providers.common.io.dataset.file import create_dataset as io_create_dataset + io_create_dataset() io_convert_dataset_to_openlineage() +# # airflow.providers.google.datasets.bigquery +from airflow.providers.google.datasets.bigquery import ( + create_dataset as bigquery_create_dataset, +) -# airflow.providers.google.datasets.bigquery bigquery_create_dataset() + # airflow.providers.google.datasets.gcs +from airflow.providers.google.datasets.gcs import ( + convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage, +) +from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset + gcs_create_dataset() gcs_convert_dataset_to_openlineage() -# airflow.providers.openlineage.utils.utils -DatasetInfo() -translate_airflow_dataset() -# -# airflow.secrets.local_filesystem -load_connections() -# -# airflow.security.permissions -RESOURCE_DATASET - -# airflow.timetables -DatasetTriggeredTimetable() -# -# airflow.www.auth -has_access_dataset diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index f9139512a229cd..bafbf1fb4dd45f 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -1,7 +1,7 @@ use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; use crate::rules::airflow::helpers::{ - Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, + Replacement, generate_import_edit, generate_remove_and_runtime_import_edit, + is_airflow_builtin_or_provider, is_guarded_by_try_except, }; use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; @@ -614,7 +614,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { }, // airflow.configuration - // TODO: check whether we could improve it [ "airflow", "configuration", @@ -984,24 +983,19 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { } let import_target = name.split('.').next().unwrap_or(name); + let mut diagnostic = checker.report_diagnostic( + Airflow3Removal { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ); - checker - .report_diagnostic( - Airflow3Removal { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - range, - ) - .try_set_fix(|| { - let (import_edit, _) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from(module, import_target), - expr.start(), - checker.semantic(), - )?; - let replacement_edit = Edit::range_replacement(name.to_string(), range); - Ok(Fix::safe_edits(import_edit, [replacement_edit])) - }); + if let Some(fix) = generate_import_edit(expr, checker, module, import_target, range) + .or_else(|| generate_remove_and_runtime_import_edit(expr, checker, module, name)) + { + diagnostic.set_fix(fix); + } } /// Check whether a customized Airflow plugin contains removed extensions. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap index 6385190a4e79e2..7a55d729e6b7af 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap @@ -1,651 +1,294 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_names.py:53:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 +AIR301_names.py:38:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 +AIR301_names.py:38:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 +AIR301_names.py:38:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 +AIR301_names.py:38:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 +AIR301_names.py:38:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 +AIR301_names.py:38:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:53:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 +AIR301_names.py:38:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 | -52 | # airflow root -53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +37 | # airflow root +38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -54 | -55 | # airflow.api_connexion.security +39 | +40 | # airflow.api_connexion.security | = help: Use `sys.version_info` instead -AIR301_names.py:56:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 +AIR301_names.py:41:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 | -55 | # airflow.api_connexion.security -56 | requires_access +40 | # airflow.api_connexion.security +41 | requires_access | ^^^^^^^^^^^^^^^ AIR301 +42 | +43 | # airflow.contrib.* | = help: Use `airflow.api_fastapi.core_api.security.requires_access_*` instead -AIR301_names.py:60:1: AIR301 [*] `airflow.configuration.get` is removed in Airflow 3.0 +AIR301_names.py:44:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^ AIR301 - | - = help: Use `conf.get` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+conf.get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* - -AIR301_names.py:60:6: AIR301 [*] `airflow.configuration.getboolean` is removed in Airflow 3.0 - | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^ AIR301 +43 | # airflow.contrib.* +44 | AWSAthenaHook() + | ^^^^^^^^^^^^^ AIR301 | - = help: Use `conf.getboolean` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, conf.getboolean, getfloat, getint, has_option, remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* + = help: The whole `airflow.contrib` module has been removed. -AIR301_names.py:60:18: AIR301 [*] `airflow.configuration.getfloat` is removed in Airflow 3.0 +AIR301_names.py:48:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^ AIR301 +47 | # airflow.datasets +48 | DatasetAliasEvent() + | ^^^^^^^^^^^^^^^^^ AIR301 | - = help: Use `conf.getfloat` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, conf.getfloat, getint, has_option, remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* -AIR301_names.py:60:28: AIR301 [*] `airflow.configuration.getint` is removed in Airflow 3.0 +AIR301_names.py:52:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^ AIR301 +51 | # airflow.operators.subdag.* +52 | SubDagOperator() + | ^^^^^^^^^^^^^^ AIR301 | - = help: Use `conf.getint` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, getfloat, conf.getint, has_option, remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* + = help: The whole `airflow.subdag` module has been removed. -AIR301_names.py:60:36: AIR301 [*] `airflow.configuration.has_option` is removed in Airflow 3.0 +AIR301_names.py:61:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^ AIR301 +60 | # airflow.triggers.external_task +61 | TaskStateTrigger() + | ^^^^^^^^^^^^^^^^ AIR301 +62 | +63 | # airflow.utils.date | - = help: Use `conf.has_option` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, getfloat, getint, conf.has_option, remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* -AIR301_names.py:60:48: AIR301 [*] `airflow.configuration.remove_option` is removed in Airflow 3.0 +AIR301_names.py:64:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^^^^ AIR301 +63 | # airflow.utils.date +64 | dates.date_range + | ^^^^^^^^^^^^^^^^ AIR301 +65 | dates.days_ago | - = help: Use `conf.remove_option` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, getfloat, getint, has_option, conf.remove_option, as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* -AIR301_names.py:60:63: AIR301 [*] `airflow.configuration.as_dict` is removed in Airflow 3.0 +AIR301_names.py:65:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^ AIR301 +63 | # airflow.utils.date +64 | dates.date_range +65 | dates.days_ago + | ^^^^^^^^^^^^^^ AIR301 +66 | +67 | date_range | - = help: Use `conf.as_dict` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, getfloat, getint, has_option, remove_option, conf.as_dict, set -61 62 | -62 63 | -63 64 | # airflow.contrib.* + = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:60:72: AIR301 [*] `airflow.configuration.set` is removed in Airflow 3.0 +AIR301_names.py:67:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 | -59 | # airflow.configuration -60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^ AIR301 +65 | dates.days_ago +66 | +67 | date_range + | ^^^^^^^^^^ AIR301 +68 | days_ago +69 | infer_time_unit | - = help: Use `conf.set` from `airflow.configuration` instead. - -ℹ Safe fix -19 19 | has_option, -20 20 | remove_option, -21 21 | set, - 22 |+conf, -22 23 | ) -23 24 | from airflow.contrib.aws_athena_hook import AWSAthenaHook -24 25 | from airflow.datasets import DatasetAliasEvent --------------------------------------------------------------------------------- -57 58 | -58 59 | -59 60 | # airflow.configuration -60 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - 61 |+get, getboolean, getfloat, getint, has_option, remove_option, as_dict, conf.set -61 62 | -62 63 | -63 64 | # airflow.contrib.* -AIR301_names.py:64:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0 +AIR301_names.py:68:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 | -63 | # airflow.contrib.* -64 | AWSAthenaHook() - | ^^^^^^^^^^^^^ AIR301 +67 | date_range +68 | days_ago + | ^^^^^^^^ AIR301 +69 | infer_time_unit +70 | parse_execution_date | - = help: The whole `airflow.contrib` module has been removed. + = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:68:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 +AIR301_names.py:69:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 | -67 | # airflow.datasets -68 | DatasetAliasEvent() - | ^^^^^^^^^^^^^^^^^ AIR301 +67 | date_range +68 | days_ago +69 | infer_time_unit + | ^^^^^^^^^^^^^^^ AIR301 +70 | parse_execution_date +71 | round_time | -AIR301_names.py:72:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 +AIR301_names.py:70:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 | -71 | # airflow.hooks -72 | BaseHook() - | ^^^^^^^^ AIR301 +68 | days_ago +69 | infer_time_unit +70 | parse_execution_date + | ^^^^^^^^^^^^^^^^^^^^ AIR301 +71 | round_time +72 | scale_time_units | - = help: Use `BaseHook` from `airflow.hooks.base` instead. -AIR301_names.py:76:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0 +AIR301_names.py:71:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 | -75 | # airflow.operators.subdag.* -76 | SubDagOperator() - | ^^^^^^^^^^^^^^ AIR301 +69 | infer_time_unit +70 | parse_execution_date +71 | round_time + | ^^^^^^^^^^ AIR301 +72 | scale_time_units | - = help: The whole `airflow.subdag` module has been removed. -AIR301_names.py:85:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 +AIR301_names.py:72:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 | -84 | # airflow.sensors.base_sensor_operator -85 | BaseSensorOperator() - | ^^^^^^^^^^^^^^^^^^ AIR301 +70 | parse_execution_date +71 | round_time +72 | scale_time_units + | ^^^^^^^^^^^^^^^^ AIR301 +73 | +74 | # This one was not deprecated. | - = help: Use `BaseSensorOperator` from `airflow.sdk.bases.sensor` instead. -AIR301_names.py:89:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 +AIR301_names.py:79:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 | -88 | # airflow.triggers.external_task -89 | TaskStateTrigger() - | ^^^^^^^^^^^^^^^^ AIR301 -90 | -91 | # airflow.utils.date +78 | # airflow.utils.dag_cycle_tester +79 | test_cycle + | ^^^^^^^^^^ AIR301 | -AIR301_names.py:92:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 +AIR301_names.py:83:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 | -91 | # airflow.utils.date -92 | dates.date_range - | ^^^^^^^^^^^^^^^^ AIR301 -93 | dates.days_ago +82 | # airflow.utils.db +83 | create_session + | ^^^^^^^^^^^^^^ AIR301 +84 | +85 | # airflow.utils.decorators | -AIR301_names.py:93:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 +AIR301_names.py:86:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0 | -91 | # airflow.utils.date -92 | dates.date_range -93 | dates.days_ago +85 | # airflow.utils.decorators +86 | apply_defaults | ^^^^^^^^^^^^^^ AIR301 -94 | -95 | date_range +87 | +88 | # airflow.utils.file | - = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead + = help: `apply_defaults` is now unconditionally done and can be safely removed. -AIR301_names.py:95:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 +AIR301_names.py:89:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 | -93 | dates.days_ago -94 | -95 | date_range - | ^^^^^^^^^^ AIR301 -96 | days_ago -97 | infer_time_unit +88 | # airflow.utils.file +89 | mkdirs + | ^^^^^^ AIR301 | + = help: Use `pathlib.Path({path}).mkdir` instead -AIR301_names.py:96:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 +AIR301_names.py:93:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 | -95 | date_range -96 | days_ago +92 | # airflow.utils.state +93 | SHUTDOWN | ^^^^^^^^ AIR301 -97 | infer_time_unit -98 | parse_execution_date +94 | terminating_states | - = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:97:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 +AIR301_names.py:94:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 | -95 | date_range -96 | days_ago -97 | infer_time_unit - | ^^^^^^^^^^^^^^^ AIR301 -98 | parse_execution_date -99 | round_time +92 | # airflow.utils.state +93 | SHUTDOWN +94 | terminating_states + | ^^^^^^^^^^^^^^^^^^ AIR301 +95 | +96 | # airflow.utils.trigger_rule | -AIR301_names.py:98:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 - | - 96 | days_ago - 97 | infer_time_unit - 98 | parse_execution_date - | ^^^^^^^^^^^^^^^^^^^^ AIR301 - 99 | round_time -100 | scale_time_units - | - -AIR301_names.py:99:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 - | - 97 | infer_time_unit - 98 | parse_execution_date - 99 | round_time - | ^^^^^^^^^^ AIR301 -100 | scale_time_units - | +AIR301_names.py:97:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 + | +96 | # airflow.utils.trigger_rule +97 | TriggerRule.DUMMY + | ^^^^^^^^^^^^^^^^^ AIR301 +98 | TriggerRule.NONE_FAILED_OR_SKIPPED + | -AIR301_names.py:100:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 - | - 98 | parse_execution_date - 99 | round_time -100 | scale_time_units - | ^^^^^^^^^^^^^^^^ AIR301 -101 | -102 | # This one was not deprecated. - | +AIR301_names.py:98:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 + | +96 | # airflow.utils.trigger_rule +97 | TriggerRule.DUMMY +98 | TriggerRule.NONE_FAILED_OR_SKIPPED + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 + | -AIR301_names.py:107:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 +AIR301_names.py:102:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 | -106 | # airflow.utils.dag_cycle_tester -107 | test_cycle +101 | # airflow.www.auth +102 | has_access | ^^^^^^^^^^ AIR301 +103 | has_access_dataset | -AIR301_names.py:111:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 +AIR301_names.py:103:1: AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0 | -110 | # airflow.utils.db -111 | create_session - | ^^^^^^^^^^^^^^ AIR301 -112 | -113 | # airflow.utils.decorators - | - -AIR301_names.py:114:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0 - | -113 | # airflow.utils.decorators -114 | apply_defaults - | ^^^^^^^^^^^^^^ AIR301 -115 | -116 | # airflow.utils.file - | - = help: `apply_defaults` is now unconditionally done and can be safely removed. - -AIR301_names.py:117:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 - | -116 | # airflow.utils.file -117 | TemporaryDirectory() - | ^^^^^^^^^^^^^^^^^^ AIR301 -118 | mkdirs - | - = help: Use `TemporaryDirectory` from `tempfile` instead. - -AIR301_names.py:118:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 - | -116 | # airflow.utils.file -117 | TemporaryDirectory() -118 | mkdirs - | ^^^^^^ AIR301 -119 | -120 | # airflow.utils.helpers - | - = help: Use `pathlib.Path({path}).mkdir` instead - -AIR301_names.py:121:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0 - | -120 | # airflow.utils.helpers -121 | helper_chain - | ^^^^^^^^^^^^ AIR301 -122 | helper_cross_downstream - | - = help: Use `chain` from `airflow.sdk` instead. - -ℹ Safe fix -48 48 | from airflow.utils.trigger_rule import TriggerRule -49 49 | from airflow.www.auth import has_access -50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key - 51 |+from airflow.sdk import chain -51 52 | -52 53 | # airflow root -53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 --------------------------------------------------------------------------------- -118 119 | mkdirs -119 120 | -120 121 | # airflow.utils.helpers -121 |-helper_chain - 122 |+chain -122 123 | helper_cross_downstream -123 124 | -124 125 | # airflow.utils.log - -AIR301_names.py:122:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 - | -120 | # airflow.utils.helpers -121 | helper_chain -122 | helper_cross_downstream - | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -123 | -124 | # airflow.utils.log - | - = help: Use `cross_downstream` from `airflow.sdk` instead. - -ℹ Safe fix -48 48 | from airflow.utils.trigger_rule import TriggerRule -49 49 | from airflow.www.auth import has_access -50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key - 51 |+from airflow.sdk import cross_downstream -51 52 | -52 53 | # airflow root -53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 --------------------------------------------------------------------------------- -119 120 | -120 121 | # airflow.utils.helpers -121 122 | helper_chain -122 |-helper_cross_downstream - 123 |+cross_downstream -123 124 | -124 125 | # airflow.utils.log -125 126 | secrets_masker - -AIR301_names.py:125:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 - | -124 | # airflow.utils.log -125 | secrets_masker - | ^^^^^^^^^^^^^^ AIR301 -126 | -127 | # airflow.utils.state - | - = help: Use `secrets_masker` from `airflow.sdk.execution_time` instead. - -AIR301_names.py:128:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 - | -127 | # airflow.utils.state -128 | SHUTDOWN - | ^^^^^^^^ AIR301 -129 | terminating_states - | - -AIR301_names.py:129:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 - | -127 | # airflow.utils.state -128 | SHUTDOWN -129 | terminating_states +101 | # airflow.www.auth +102 | has_access +103 | has_access_dataset | ^^^^^^^^^^^^^^^^^^ AIR301 -130 | -131 | # airflow.utils.trigger_rule +104 | +105 | # airflow.www.utils | -AIR301_names.py:132:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 - | -131 | # airflow.utils.trigger_rule -132 | TriggerRule.DUMMY - | ^^^^^^^^^^^^^^^^^ AIR301 -133 | TriggerRule.NONE_FAILED_OR_SKIPPED +AIR301_names.py:106:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 | - -AIR301_names.py:133:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 - | -131 | # airflow.utils.trigger_rule -132 | TriggerRule.DUMMY -133 | TriggerRule.NONE_FAILED_OR_SKIPPED - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 - | - -AIR301_names.py:137:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 - | -136 | # airflow.www.auth -137 | has_access - | ^^^^^^^^^^ AIR301 -138 | -139 | # airflow.www.utils - | - -AIR301_names.py:140:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 - | -139 | # airflow.www.utils -140 | get_sensitive_variables_fields +105 | # airflow.www.utils +106 | get_sensitive_variables_fields | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -141 | should_hide_value_for_key +107 | should_hide_value_for_key | -AIR301_names.py:141:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 +AIR301_names.py:107:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 | -139 | # airflow.www.utils -140 | get_sensitive_variables_fields -141 | should_hide_value_for_key +105 | # airflow.www.utils +106 | get_sensitive_variables_fields +107 | should_hide_value_for_key | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -142 | -143 | # airflow.operators.python - | - -AIR301_names.py:146:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0 - | -144 | from airflow.operators.python import get_current_context -145 | -146 | get_current_context() - | ^^^^^^^^^^^^^^^^^^^ AIR301 -147 | -148 | # airflow.providers.mysql - | - = help: Use `get_current_context` from `airflow.sdk` instead. - -AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 - | -149 | from airflow.providers.mysql.datasets.mysql import sanitize_uri -150 | -151 | sanitize_uri - | ^^^^^^^^^^^^ AIR301 -152 | -153 | # airflow.providers.postgres - | - = help: Use `sanitize_uri` from `airflow.providers.mysql.assets.mysql` instead. - -AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 - | -154 | from airflow.providers.postgres.datasets.postgres import sanitize_uri -155 | -156 | sanitize_uri - | ^^^^^^^^^^^^ AIR301 -157 | -158 | # airflow.providers.trino - | - = help: Use `sanitize_uri` from `airflow.providers.postgres.assets.postgres` instead. - -AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 - | -159 | from airflow.providers.trino.datasets.trino import sanitize_uri -160 | -161 | sanitize_uri - | ^^^^^^^^^^^^ AIR301 -162 | -163 | # airflow.notifications.basenotifier - | - = help: Use `sanitize_uri` from `airflow.providers.trino.assets.trino` instead. - -AIR301_names.py:166:1: AIR301 `airflow.notifications.basenotifier.BaseNotifier` is removed in Airflow 3.0 - | -164 | from airflow.notifications.basenotifier import BaseNotifier -165 | -166 | BaseNotifier() - | ^^^^^^^^^^^^ AIR301 -167 | -168 | # airflow.auth.manager - | - = help: Use `BaseNotifier` from `airflow.sdk.bases.notifier` instead. - -AIR301_names.py:171:1: AIR301 `airflow.auth.managers.base_auth_manager.BaseAuthManager` is removed in Airflow 3.0 - | -169 | from airflow.auth.managers.base_auth_manager import BaseAuthManager -170 | -171 | BaseAuthManager() - | ^^^^^^^^^^^^^^^ AIR301 | - = help: Use `BaseAuthManager` from `airflow.api_fastapi.auth.managers.base_auth_manager` instead. diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap index fb1cc25229cb23..e9e738ca15b998 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names_fix.py.snap @@ -1,296 +1,780 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_names_fix.py:19:1: AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0 +AIR301_names_fix.py:17:1: AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0 | -17 | from airflow.www.auth import has_access_dataset -18 | -19 | requires_access_dataset() +15 | from airflow.security.permissions import RESOURCE_DATASET +16 | +17 | requires_access_dataset() | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -20 | -21 | DatasetDetails() +18 | +19 | DatasetDetails() | = help: Use `requires_access_asset` from `airflow.api_fastapi.core_api.security` instead. ℹ Safe fix -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset - 18 |+from airflow.api_fastapi.core_api.security import requires_access_asset +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET + 16 |+from airflow.api_fastapi.core_api.security import requires_access_asset +16 17 | +17 |-requires_access_dataset() + 18 |+requires_access_asset() 18 19 | -19 |-requires_access_dataset() - 20 |+requires_access_asset() +19 20 | DatasetDetails() 20 21 | -21 22 | DatasetDetails() -22 23 | -AIR301_names_fix.py:21:1: AIR301 [*] `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0 +AIR301_names_fix.py:19:1: AIR301 [*] `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0 | -19 | requires_access_dataset() -20 | -21 | DatasetDetails() +17 | requires_access_dataset() +18 | +19 | DatasetDetails() | ^^^^^^^^^^^^^^ AIR301 +20 | +21 | DatasetManager() | = help: Use `AssetDetails` from `airflow.api_fastapi.auth.managers.models.resource_details` instead. ℹ Safe fix -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset - 18 |+from airflow.api_fastapi.auth.managers.models.resource_details import AssetDetails +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET + 16 |+from airflow.api_fastapi.auth.managers.models.resource_details import AssetDetails +16 17 | +17 18 | requires_access_dataset() 18 19 | -19 20 | requires_access_dataset() +19 |-DatasetDetails() + 20 |+AssetDetails() 20 21 | -21 |-DatasetDetails() - 22 |+AssetDetails() -22 23 | -23 24 | -24 25 | DatasetManager() +21 22 | DatasetManager() +22 23 | dataset_manager() -AIR301_names_fix.py:24:1: AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0 +AIR301_names_fix.py:21:1: AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0 | -24 | DatasetManager() +19 | DatasetDetails() +20 | +21 | DatasetManager() | ^^^^^^^^^^^^^^ AIR301 -25 | dataset_manager() -26 | resolve_dataset_manager() +22 | dataset_manager() +23 | resolve_dataset_manager() | = help: Use `AssetManager` from `airflow.assets.manager` instead. ℹ Safe fix -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset - 18 |+from airflow.assets.manager import AssetManager +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET + 16 |+from airflow.assets.manager import AssetManager +16 17 | +17 18 | requires_access_dataset() 18 19 | -19 20 | requires_access_dataset() +19 20 | DatasetDetails() 20 21 | -21 22 | DatasetDetails() -22 23 | -23 24 | -24 |-DatasetManager() - 25 |+AssetManager() -25 26 | dataset_manager() -26 27 | resolve_dataset_manager() -27 28 | - -AIR301_names_fix.py:25:1: AIR301 [*] `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0 - | -24 | DatasetManager() -25 | dataset_manager() +21 |-DatasetManager() + 22 |+AssetManager() +22 23 | dataset_manager() +23 24 | resolve_dataset_manager() +24 25 | + +AIR301_names_fix.py:22:1: AIR301 [*] `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0 + | +21 | DatasetManager() +22 | dataset_manager() | ^^^^^^^^^^^^^^^ AIR301 -26 | resolve_dataset_manager() +23 | resolve_dataset_manager() | = help: Use `asset_manager` from `airflow.assets.manager` instead. ℹ Safe fix -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset - 18 |+from airflow.assets.manager import asset_manager +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET + 16 |+from airflow.assets.manager import asset_manager +16 17 | +17 18 | requires_access_dataset() 18 19 | -19 20 | requires_access_dataset() +19 20 | DatasetDetails() 20 21 | --------------------------------------------------------------------------------- -22 23 | -23 24 | -24 25 | DatasetManager() -25 |-dataset_manager() - 26 |+asset_manager() -26 27 | resolve_dataset_manager() -27 28 | -28 29 | DatasetLineageInfo() - -AIR301_names_fix.py:26:1: AIR301 [*] `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0 - | -24 | DatasetManager() -25 | dataset_manager() -26 | resolve_dataset_manager() +21 22 | DatasetManager() +22 |-dataset_manager() + 23 |+asset_manager() +23 24 | resolve_dataset_manager() +24 25 | +25 26 | DatasetLineageInfo() + +AIR301_names_fix.py:23:1: AIR301 [*] `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0 + | +21 | DatasetManager() +22 | dataset_manager() +23 | resolve_dataset_manager() | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -27 | -28 | DatasetLineageInfo() +24 | +25 | DatasetLineageInfo() | = help: Use `resolve_asset_manager` from `airflow.assets.manager` instead. ℹ Safe fix -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset - 18 |+from airflow.assets.manager import resolve_asset_manager +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET + 16 |+from airflow.assets.manager import resolve_asset_manager +16 17 | +17 18 | requires_access_dataset() 18 19 | -19 20 | requires_access_dataset() -20 21 | -------------------------------------------------------------------------------- -23 24 | -24 25 | DatasetManager() -25 26 | dataset_manager() -26 |-resolve_dataset_manager() - 27 |+resolve_asset_manager() -27 28 | -28 29 | DatasetLineageInfo() -29 30 | - -AIR301_names_fix.py:28:1: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0 - | -26 | resolve_dataset_manager() -27 | -28 | DatasetLineageInfo() +20 21 | +21 22 | DatasetManager() +22 23 | dataset_manager() +23 |-resolve_dataset_manager() + 24 |+resolve_asset_manager() +24 25 | +25 26 | DatasetLineageInfo() +26 27 | + +AIR301_names_fix.py:25:1: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0 + | +23 | resolve_dataset_manager() +24 | +25 | DatasetLineageInfo() | ^^^^^^^^^^^^^^^^^^ AIR301 -29 | -30 | AllowListValidator() +26 | +27 | AllowListValidator() | = help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead. ℹ Safe fix -10 10 | dataset_manager, -11 11 | resolve_dataset_manager, -12 12 | ) -13 |-from airflow.lineage.hook import DatasetLineageInfo - 13 |+from airflow.lineage.hook import DatasetLineageInfo, AssetLineageInfo -14 14 | from airflow.metrics.validators import AllowListValidator, BlockListValidator -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET +9 9 | dataset_manager, +10 10 | resolve_dataset_manager, +11 11 | ) +12 |-from airflow.lineage.hook import DatasetLineageInfo + 12 |+from airflow.lineage.hook import DatasetLineageInfo, AssetLineageInfo +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET -------------------------------------------------------------------------------- -25 25 | dataset_manager() -26 26 | resolve_dataset_manager() -27 27 | -28 |-DatasetLineageInfo() - 28 |+AssetLineageInfo() -29 29 | -30 30 | AllowListValidator() -31 31 | BlockListValidator() +22 22 | dataset_manager() +23 23 | resolve_dataset_manager() +24 24 | +25 |-DatasetLineageInfo() + 25 |+AssetLineageInfo() +26 26 | +27 27 | AllowListValidator() +28 28 | BlockListValidator() -AIR301_names_fix.py:30:1: AIR301 [*] `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0 +AIR301_names_fix.py:27:1: AIR301 [*] `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0 | -28 | DatasetLineageInfo() -29 | -30 | AllowListValidator() +25 | DatasetLineageInfo() +26 | +27 | AllowListValidator() | ^^^^^^^^^^^^^^^^^^ AIR301 -31 | BlockListValidator() +28 | BlockListValidator() | = help: Use `PatternAllowListValidator` from `airflow.metrics.validators` instead. ℹ Safe fix -11 11 | resolve_dataset_manager, -12 12 | ) -13 13 | from airflow.lineage.hook import DatasetLineageInfo -14 |-from airflow.metrics.validators import AllowListValidator, BlockListValidator - 14 |+from airflow.metrics.validators import AllowListValidator, BlockListValidator, PatternAllowListValidator -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset +10 10 | resolve_dataset_manager, +11 11 | ) +12 12 | from airflow.lineage.hook import DatasetLineageInfo +13 |-from airflow.metrics.validators import AllowListValidator, BlockListValidator + 13 |+from airflow.metrics.validators import AllowListValidator, BlockListValidator, PatternAllowListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET +16 16 | -------------------------------------------------------------------------------- -27 27 | -28 28 | DatasetLineageInfo() +24 24 | +25 25 | DatasetLineageInfo() +26 26 | +27 |-AllowListValidator() + 27 |+PatternAllowListValidator() +28 28 | BlockListValidator() 29 29 | -30 |-AllowListValidator() - 30 |+PatternAllowListValidator() -31 31 | BlockListValidator() -32 32 | -33 33 | load_connections() +30 30 | load_connections() -AIR301_names_fix.py:31:1: AIR301 [*] `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0 +AIR301_names_fix.py:28:1: AIR301 [*] `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0 | -30 | AllowListValidator() -31 | BlockListValidator() +27 | AllowListValidator() +28 | BlockListValidator() | ^^^^^^^^^^^^^^^^^^ AIR301 -32 | -33 | load_connections() +29 | +30 | load_connections() | = help: Use `PatternBlockListValidator` from `airflow.metrics.validators` instead. ℹ Safe fix -11 11 | resolve_dataset_manager, -12 12 | ) -13 13 | from airflow.lineage.hook import DatasetLineageInfo -14 |-from airflow.metrics.validators import AllowListValidator, BlockListValidator - 14 |+from airflow.metrics.validators import AllowListValidator, BlockListValidator, PatternBlockListValidator -15 15 | from airflow.secrets.local_filesystm import load_connections -16 16 | from airflow.security.permissions import RESOURCE_DATASET -17 17 | from airflow.www.auth import has_access_dataset +10 10 | resolve_dataset_manager, +11 11 | ) +12 12 | from airflow.lineage.hook import DatasetLineageInfo +13 |-from airflow.metrics.validators import AllowListValidator, BlockListValidator + 13 |+from airflow.metrics.validators import AllowListValidator, BlockListValidator, PatternBlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 15 | from airflow.security.permissions import RESOURCE_DATASET +16 16 | -------------------------------------------------------------------------------- -28 28 | DatasetLineageInfo() +25 25 | DatasetLineageInfo() +26 26 | +27 27 | AllowListValidator() +28 |-BlockListValidator() + 28 |+PatternBlockListValidator() 29 29 | -30 30 | AllowListValidator() -31 |-BlockListValidator() - 31 |+PatternBlockListValidator() -32 32 | -33 33 | load_connections() -34 34 | +30 30 | load_connections() +31 31 | -AIR301_names_fix.py:35:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0 +AIR301_names_fix.py:30:1: AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0 | -33 | load_connections() -34 | -35 | RESOURCE_DATASET +28 | BlockListValidator() +29 | +30 | load_connections() + | ^^^^^^^^^^^^^^^^ AIR301 +31 | +32 | RESOURCE_DATASET + | + = help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead. + +ℹ Safe fix +11 11 | ) +12 12 | from airflow.lineage.hook import DatasetLineageInfo +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 |-from airflow.secrets.local_filesystem import load_connections + 14 |+from airflow.secrets.local_filesystem import load_connections, load_connections_dict +15 15 | from airflow.security.permissions import RESOURCE_DATASET +16 16 | +17 17 | requires_access_dataset() +-------------------------------------------------------------------------------- +27 27 | AllowListValidator() +28 28 | BlockListValidator() +29 29 | +30 |-load_connections() + 30 |+load_connections_dict() +31 31 | +32 32 | RESOURCE_DATASET +33 33 | + +AIR301_names_fix.py:32:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0 + | +30 | load_connections() +31 | +32 | RESOURCE_DATASET | ^^^^^^^^^^^^^^^^ AIR301 -36 | -37 | has_access_dataset() | = help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead. ℹ Safe fix -13 13 | from airflow.lineage.hook import DatasetLineageInfo -14 14 | from airflow.metrics.validators import AllowListValidator, BlockListValidator -15 15 | from airflow.secrets.local_filesystm import load_connections -16 |-from airflow.security.permissions import RESOURCE_DATASET - 16 |+from airflow.security.permissions import RESOURCE_DATASET, RESOURCE_ASSET -17 17 | from airflow.www.auth import has_access_dataset +12 12 | from airflow.lineage.hook import DatasetLineageInfo +13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator +14 14 | from airflow.secrets.local_filesystem import load_connections +15 |-from airflow.security.permissions import RESOURCE_DATASET + 15 |+from airflow.security.permissions import RESOURCE_DATASET, RESOURCE_ASSET +16 16 | +17 17 | requires_access_dataset() 18 18 | -19 19 | requires_access_dataset() -------------------------------------------------------------------------------- -32 32 | -33 33 | load_connections() +29 29 | +30 30 | load_connections() +31 31 | +32 |-RESOURCE_DATASET + 32 |+RESOURCE_ASSET +33 33 | 34 34 | -35 |-RESOURCE_DATASET - 35 |+RESOURCE_ASSET -36 36 | -37 37 | has_access_dataset() -38 38 | +35 35 | from airflow.listeners.spec.dataset import ( -AIR301_names_fix.py:37:1: AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0 +AIR301_names_fix.py:40:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0 | -35 | RESOURCE_DATASET -36 | -37 | has_access_dataset() +38 | ) +39 | +40 | on_dataset_created() | ^^^^^^^^^^^^^^^^^^ AIR301 -38 | -39 | from airflow.listeners.spec.dataset import ( +41 | on_dataset_changed() | + = help: Use `on_asset_created` from `airflow.listeners.spec.asset` instead. + +ℹ Safe fix +36 36 | on_dataset_changed, +37 37 | on_dataset_created, +38 38 | ) + 39 |+from airflow.listeners.spec.asset import on_asset_created +39 40 | +40 |-on_dataset_created() + 41 |+on_asset_created() +41 42 | on_dataset_changed() +42 43 | +43 44 | -AIR301_names_fix.py:44:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0 +AIR301_names_fix.py:41:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0 | -42 | ) -43 | -44 | on_dataset_created() +40 | on_dataset_created() +41 | on_dataset_changed() | ^^^^^^^^^^^^^^^^^^ AIR301 -45 | on_dataset_changed() | - = help: Use `on_asset_created` from `airflow.listeners.spec.asset` instead. + = help: Use `on_asset_changed` from `airflow.listeners.spec.asset` instead. ℹ Safe fix -40 40 | on_dataset_changed, -41 41 | on_dataset_created, -42 42 | ) - 43 |+from airflow.listeners.spec.asset import on_asset_created +36 36 | on_dataset_changed, +37 37 | on_dataset_created, +38 38 | ) + 39 |+from airflow.listeners.spec.asset import on_asset_changed +39 40 | +40 41 | on_dataset_created() +41 |-on_dataset_changed() + 42 |+on_asset_changed() +42 43 | 43 44 | -44 |-on_dataset_created() - 45 |+on_asset_created() -45 46 | on_dataset_changed() +44 45 | # airflow.operators.python + +AIR301_names_fix.py:47:1: AIR301 [*] `airflow.operators.python.get_current_context` is removed in Airflow 3.0 + | +45 | from airflow.operators.python import get_current_context +46 | +47 | get_current_context() + | ^^^^^^^^^^^^^^^^^^^ AIR301 +48 | +49 | # airflow.providers.mysql + | + = help: Use `get_current_context` from `airflow.sdk` instead. + +ℹ Unsafe fix +42 42 | +43 43 | +44 44 | # airflow.operators.python +45 |-from airflow.operators.python import get_current_context + 45 |+from airflow.sdk import get_current_context +46 46 | +47 47 | get_current_context() +48 48 | + +AIR301_names_fix.py:52:1: AIR301 [*] `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 + | +50 | from airflow.providers.mysql.datasets.mysql import sanitize_uri +51 | +52 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 +53 | +54 | # airflow.providers.postgres + | + = help: Use `sanitize_uri` from `airflow.providers.mysql.assets.mysql` instead. + +ℹ Unsafe fix +47 47 | get_current_context() +48 48 | +49 49 | # airflow.providers.mysql +50 |-from airflow.providers.mysql.datasets.mysql import sanitize_uri + 50 |+from airflow.providers.mysql.assets.mysql import sanitize_uri +51 51 | +52 52 | sanitize_uri +53 53 | + +AIR301_names_fix.py:57:1: AIR301 [*] `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 + | +55 | from airflow.providers.postgres.datasets.postgres import sanitize_uri +56 | +57 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 +58 | +59 | # airflow.providers.trino + | + = help: Use `sanitize_uri` from `airflow.providers.postgres.assets.postgres` instead. + +ℹ Unsafe fix +52 52 | sanitize_uri +53 53 | +54 54 | # airflow.providers.postgres +55 |-from airflow.providers.postgres.datasets.postgres import sanitize_uri + 55 |+from airflow.providers.postgres.assets.postgres import sanitize_uri +56 56 | +57 57 | sanitize_uri +58 58 | + +AIR301_names_fix.py:62:1: AIR301 [*] `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 + | +60 | from airflow.providers.trino.datasets.trino import sanitize_uri +61 | +62 | sanitize_uri + | ^^^^^^^^^^^^ AIR301 +63 | +64 | # airflow.notifications.basenotifier + | + = help: Use `sanitize_uri` from `airflow.providers.trino.assets.trino` instead. -AIR301_names_fix.py:45:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0 +ℹ Unsafe fix +57 57 | sanitize_uri +58 58 | +59 59 | # airflow.providers.trino +60 |-from airflow.providers.trino.datasets.trino import sanitize_uri + 60 |+from airflow.providers.trino.assets.trino import sanitize_uri +61 61 | +62 62 | sanitize_uri +63 63 | + +AIR301_names_fix.py:67:1: AIR301 [*] `airflow.notifications.basenotifier.BaseNotifier` is removed in Airflow 3.0 + | +65 | from airflow.notifications.basenotifier import BaseNotifier +66 | +67 | BaseNotifier() + | ^^^^^^^^^^^^ AIR301 +68 | +69 | # airflow.auth.manager | -44 | on_dataset_created() -45 | on_dataset_changed() + = help: Use `BaseNotifier` from `airflow.sdk.bases.notifier` instead. + +ℹ Unsafe fix +62 62 | sanitize_uri +63 63 | +64 64 | # airflow.notifications.basenotifier +65 |-from airflow.notifications.basenotifier import BaseNotifier + 65 |+from airflow.sdk.bases.notifier import BaseNotifier +66 66 | +67 67 | BaseNotifier() +68 68 | + +AIR301_names_fix.py:72:1: AIR301 [*] `airflow.auth.managers.base_auth_manager.BaseAuthManager` is removed in Airflow 3.0 + | +70 | from airflow.auth.managers.base_auth_manager import BaseAuthManager +71 | +72 | BaseAuthManager() + | ^^^^^^^^^^^^^^^ AIR301 + | + = help: Use `BaseAuthManager` from `airflow.api_fastapi.auth.managers.base_auth_manager` instead. + +ℹ Unsafe fix +67 67 | BaseNotifier() +68 68 | +69 69 | # airflow.auth.manager +70 |-from airflow.auth.managers.base_auth_manager import BaseAuthManager + 70 |+from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager +71 71 | +72 72 | BaseAuthManager() +73 73 | + +AIR301_names_fix.py:87:1: AIR301 [*] `airflow.configuration.get` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.get` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+conf, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:6: AIR301 [*] `airflow.configuration.getboolean` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.getboolean` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, conf, getfloat, getint, has_option, remove_option, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:18: AIR301 [*] `airflow.configuration.getfloat` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.getfloat` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, conf, getint, has_option, remove_option, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:28: AIR301 [*] `airflow.configuration.getint` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.getint` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, getfloat, conf, has_option, remove_option, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:36: AIR301 [*] `airflow.configuration.has_option` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.has_option` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, getfloat, getint, conf, remove_option, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:48: AIR301 [*] `airflow.configuration.remove_option` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.remove_option` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, getfloat, getint, has_option, conf, as_dict, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:63: AIR301 [*] `airflow.configuration.as_dict` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.as_dict` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, getfloat, getint, has_option, remove_option, conf, set +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:87:72: AIR301 [*] `airflow.configuration.set` is removed in Airflow 3.0 + | +86 | # airflow.configuration +87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^ AIR301 +88 | from airflow.hooks.base_hook import BaseHook + | + = help: Use `conf.set` from `airflow.configuration` instead. + +ℹ Safe fix +81 81 | has_option, +82 82 | remove_option, +83 83 | set, + 84 |+conf, +84 85 | ) +85 86 | +86 87 | # airflow.configuration +87 |-get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + 88 |+get, getboolean, getfloat, getint, has_option, remove_option, as_dict, conf +88 89 | from airflow.hooks.base_hook import BaseHook +89 90 | +90 91 | # airflow.hooks + +AIR301_names_fix.py:91:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 + | +90 | # airflow.hooks +91 | BaseHook() + | ^^^^^^^^ AIR301 +92 | +93 | from airflow.sensors.base_sensor_operator import BaseSensorOperator + | + = help: Use `BaseHook` from `airflow.hooks.base` instead. + +ℹ Unsafe fix +85 85 | +86 86 | # airflow.configuration +87 87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +88 |-from airflow.hooks.base_hook import BaseHook + 88 |+from airflow.hooks.base import BaseHook +89 89 | +90 90 | # airflow.hooks +91 91 | BaseHook() + +AIR301_names_fix.py:96:1: AIR301 [*] `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 + | +95 | # airflow.sensors.base_sensor_operator +96 | BaseSensorOperator() | ^^^^^^^^^^^^^^^^^^ AIR301 +97 | BaseHook() | - = help: Use `on_asset_changed` from `airflow.listeners.spec.asset` instead. + = help: Use `BaseSensorOperator` from `airflow.sdk.bases.sensor` instead. + +ℹ Unsafe fix +90 90 | # airflow.hooks +91 91 | BaseHook() +92 92 | +93 |-from airflow.sensors.base_sensor_operator import BaseSensorOperator + 93 |+from airflow.sdk.bases.sensor import BaseSensorOperator +94 94 | +95 95 | # airflow.sensors.base_sensor_operator +96 96 | BaseSensorOperator() + +AIR301_names_fix.py:97:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 + | +95 | # airflow.sensors.base_sensor_operator +96 | BaseSensorOperator() +97 | BaseHook() + | ^^^^^^^^ AIR301 +98 | +99 | from airflow.utils.helpers import chain as helper_chain + | + = help: Use `BaseHook` from `airflow.hooks.base` instead. + +ℹ Unsafe fix +85 85 | +86 86 | # airflow.configuration +87 87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set +88 |-from airflow.hooks.base_hook import BaseHook +89 88 | +90 89 | # airflow.hooks +91 90 | BaseHook() +92 91 | +93 92 | from airflow.sensors.base_sensor_operator import BaseSensorOperator + 93 |+from airflow.hooks.base import BaseHook +94 94 | +95 95 | # airflow.sensors.base_sensor_operator +96 96 | BaseSensorOperator() + +AIR301_names_fix.py:103:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0 + | +102 | # airflow.utils.helpers +103 | helper_chain + | ^^^^^^^^^^^^ AIR301 +104 | helper_cross_downstream + | + = help: Use `chain` from `airflow.sdk` instead. ℹ Safe fix -40 40 | on_dataset_changed, -41 41 | on_dataset_created, -42 42 | ) - 43 |+from airflow.listeners.spec.asset import on_asset_changed -43 44 | -44 45 | on_dataset_created() -45 |-on_dataset_changed() - 46 |+on_asset_changed() +98 98 | +99 99 | from airflow.utils.helpers import chain as helper_chain +100 100 | from airflow.utils.helpers import cross_downstream as helper_cross_downstream + 101 |+from airflow.sdk import chain +101 102 | +102 103 | # airflow.utils.helpers +103 |-helper_chain + 104 |+chain +104 105 | helper_cross_downstream +105 106 | +106 107 | # airflow.utils.file + +AIR301_names_fix.py:104:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 + | +102 | # airflow.utils.helpers +103 | helper_chain +104 | helper_cross_downstream + | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 +105 | +106 | # airflow.utils.file + | + = help: Use `cross_downstream` from `airflow.sdk` instead. + +ℹ Safe fix +98 98 | +99 99 | from airflow.utils.helpers import chain as helper_chain +100 100 | from airflow.utils.helpers import cross_downstream as helper_cross_downstream + 101 |+from airflow.sdk import cross_downstream +101 102 | +102 103 | # airflow.utils.helpers +103 104 | helper_chain +104 |-helper_cross_downstream + 105 |+cross_downstream +105 106 | +106 107 | # airflow.utils.file +107 108 | from airflow.utils.file import TemporaryDirectory + +AIR301_names_fix.py:109:1: AIR301 [*] `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 + | +107 | from airflow.utils.file import TemporaryDirectory +108 | +109 | TemporaryDirectory() + | ^^^^^^^^^^^^^^^^^^ AIR301 +110 | +111 | from airflow.utils.log import secrets_masker + | + = help: Use `TemporaryDirectory` from `tempfile` instead. + +ℹ Unsafe fix +104 104 | helper_cross_downstream +105 105 | +106 106 | # airflow.utils.file +107 |-from airflow.utils.file import TemporaryDirectory + 107 |+from tempfile import TemporaryDirectory +108 108 | +109 109 | TemporaryDirectory() +110 110 | + +AIR301_names_fix.py:114:1: AIR301 [*] `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 + | +113 | # airflow.utils.log +114 | secrets_masker + | ^^^^^^^^^^^^^^ AIR301 + | + = help: Use `secrets_masker` from `airflow.sdk.execution_time` instead. + +ℹ Unsafe fix +108 108 | +109 109 | TemporaryDirectory() +110 110 | +111 |-from airflow.utils.log import secrets_masker + 111 |+from airflow.sdk.execution_time import secrets_masker +112 112 | +113 113 | # airflow.utils.log +114 114 | secrets_masker diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap index dc2a39b486dae4..6c53d5dd6660de 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_provider_names_fix.py.snap @@ -1,216 +1,243 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_provider_names_fix.py:25:1: AIR301 [*] `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0 +AIR301_provider_names_fix.py:11:1: AIR301 [*] `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0 | -23 | ) -24 | -25 | AvpEntities.DATASET + 9 | from airflow.security.permissions import RESOURCE_DATASET +10 | +11 | AvpEntities.DATASET | ^^^^^^^^^^^^^^^^^^^ AIR301 -26 | -27 | s3_create_dataset() +12 | +13 | # airflow.providers.openlineage.utils.utils | = help: Use `AvpEntities.ASSET` from `airflow.providers.amazon.aws.auth_manager.avp.entities` instead. ℹ Safe fix -22 22 | translate_airflow_dataset, -23 23 | ) -24 24 | -25 |-AvpEntities.DATASET - 25 |+AvpEntities.ASSET -26 26 | -27 27 | s3_create_dataset() -28 28 | s3_convert_dataset_to_openlineage() - -AIR301_provider_names_fix.py:27:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0 - | -25 | AvpEntities.DATASET -26 | -27 | s3_create_dataset() +8 8 | from airflow.secrets.local_filesystem import load_connections +9 9 | from airflow.security.permissions import RESOURCE_DATASET +10 10 | +11 |-AvpEntities.DATASET + 11 |+AvpEntities +12 12 | +13 13 | # airflow.providers.openlineage.utils.utils +14 14 | DatasetInfo() + +AIR301_provider_names_fix.py:14:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0 + | +13 | # airflow.providers.openlineage.utils.utils +14 | DatasetInfo() + | ^^^^^^^^^^^ AIR301 +15 | translate_airflow_dataset() + | + = help: Use `AssetInfo` from `airflow.providers.openlineage.utils.utils` instead. + +ℹ Safe fix +4 4 | from airflow.providers.openlineage.utils.utils import ( +5 5 | DatasetInfo, +6 6 | translate_airflow_dataset, + 7 |+AssetInfo, +7 8 | ) +8 9 | from airflow.secrets.local_filesystem import load_connections +9 10 | from airflow.security.permissions import RESOURCE_DATASET +-------------------------------------------------------------------------------- +11 12 | AvpEntities.DATASET +12 13 | +13 14 | # airflow.providers.openlineage.utils.utils +14 |-DatasetInfo() + 15 |+AssetInfo() +15 16 | translate_airflow_dataset() +16 17 | +17 18 | # airflow.secrets.local_filesystem + +AIR301_provider_names_fix.py:15:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0 + | +13 | # airflow.providers.openlineage.utils.utils +14 | DatasetInfo() +15 | translate_airflow_dataset() + | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 +16 | +17 | # airflow.secrets.local_filesystem + | + = help: Use `translate_airflow_asset` from `airflow.providers.openlineage.utils.utils` instead. + +ℹ Safe fix +4 4 | from airflow.providers.openlineage.utils.utils import ( +5 5 | DatasetInfo, +6 6 | translate_airflow_dataset, + 7 |+translate_airflow_asset, +7 8 | ) +8 9 | from airflow.secrets.local_filesystem import load_connections +9 10 | from airflow.security.permissions import RESOURCE_DATASET +-------------------------------------------------------------------------------- +12 13 | +13 14 | # airflow.providers.openlineage.utils.utils +14 15 | DatasetInfo() +15 |-translate_airflow_dataset() + 16 |+translate_airflow_asset() +16 17 | +17 18 | # airflow.secrets.local_filesystem +18 19 | load_connections() + +AIR301_provider_names_fix.py:18:1: AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0 + | +17 | # airflow.secrets.local_filesystem +18 | load_connections() + | ^^^^^^^^^^^^^^^^ AIR301 +19 | +20 | # airflow.security.permissions + | + = help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead. + +ℹ Safe fix +5 5 | DatasetInfo, +6 6 | translate_airflow_dataset, +7 7 | ) +8 |-from airflow.secrets.local_filesystem import load_connections + 8 |+from airflow.secrets.local_filesystem import load_connections, load_connections_dict +9 9 | from airflow.security.permissions import RESOURCE_DATASET +10 10 | +11 11 | AvpEntities.DATASET +-------------------------------------------------------------------------------- +15 15 | translate_airflow_dataset() +16 16 | +17 17 | # airflow.secrets.local_filesystem +18 |-load_connections() + 18 |+load_connections_dict() +19 19 | +20 20 | # airflow.security.permissions +21 21 | RESOURCE_DATASET + +AIR301_provider_names_fix.py:21:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0 + | +20 | # airflow.security.permissions +21 | RESOURCE_DATASET + | ^^^^^^^^^^^^^^^^ AIR301 +22 | +23 | from airflow.providers.amazon.aws.datasets.s3 import ( + | + = help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead. + +ℹ Safe fix +6 6 | translate_airflow_dataset, +7 7 | ) +8 8 | from airflow.secrets.local_filesystem import load_connections +9 |-from airflow.security.permissions import RESOURCE_DATASET + 9 |+from airflow.security.permissions import RESOURCE_DATASET, RESOURCE_ASSET +10 10 | +11 11 | AvpEntities.DATASET +12 12 | +-------------------------------------------------------------------------------- +18 18 | load_connections() +19 19 | +20 20 | # airflow.security.permissions +21 |-RESOURCE_DATASET + 21 |+RESOURCE_ASSET +22 22 | +23 23 | from airflow.providers.amazon.aws.datasets.s3 import ( +24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage, + +AIR301_provider_names_fix.py:28:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0 + | +26 | from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset +27 | +28 | s3_create_dataset() | ^^^^^^^^^^^^^^^^^ AIR301 -28 | s3_convert_dataset_to_openlineage() +29 | s3_convert_dataset_to_openlineage() | = help: Use `create_asset` from `airflow.providers.amazon.aws.assets.s3` instead. ℹ Safe fix -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, -23 23 | ) - 24 |+from airflow.providers.amazon.aws.assets.s3 import create_asset -24 25 | -25 26 | AvpEntities.DATASET -26 27 | -27 |-s3_create_dataset() - 28 |+create_asset() -28 29 | s3_convert_dataset_to_openlineage() -29 30 | -30 31 | io_create_dataset() - -AIR301_provider_names_fix.py:28:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0 - | -27 | s3_create_dataset() -28 | s3_convert_dataset_to_openlineage() +24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage, +25 25 | ) +26 26 | from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset + 27 |+from airflow.providers.amazon.aws.assets.s3 import create_asset +27 28 | +28 |-s3_create_dataset() + 29 |+create_asset() +29 30 | s3_convert_dataset_to_openlineage() +30 31 | +31 32 | from airflow.providers.common.io.dataset.file import ( + +AIR301_provider_names_fix.py:29:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0 + | +28 | s3_create_dataset() +29 | s3_convert_dataset_to_openlineage() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -29 | -30 | io_create_dataset() +30 | +31 | from airflow.providers.common.io.dataset.file import ( | = help: Use `convert_asset_to_openlineage` from `airflow.providers.amazon.aws.assets.s3` instead. ℹ Safe fix -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, -23 23 | ) - 24 |+from airflow.providers.amazon.aws.assets.s3 import convert_asset_to_openlineage -24 25 | -25 26 | AvpEntities.DATASET -26 27 | -27 28 | s3_create_dataset() -28 |-s3_convert_dataset_to_openlineage() - 29 |+convert_asset_to_openlineage() -29 30 | -30 31 | io_create_dataset() -31 32 | io_convert_dataset_to_openlineage() - -AIR301_provider_names_fix.py:36:1: AIR301 [*] `airflow.providers.google.datasets.bigquery.create_dataset` is removed in Airflow 3.0 - | -35 | # airflow.providers.google.datasets.bigquery -36 | bigquery_create_dataset() +24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage, +25 25 | ) +26 26 | from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset + 27 |+from airflow.providers.amazon.aws.assets.s3 import convert_asset_to_openlineage +27 28 | +28 29 | s3_create_dataset() +29 |-s3_convert_dataset_to_openlineage() + 30 |+convert_asset_to_openlineage() +30 31 | +31 32 | from airflow.providers.common.io.dataset.file import ( +32 33 | convert_dataset_to_openlineage as io_convert_dataset_to_openlineage, + +AIR301_provider_names_fix.py:45:1: AIR301 [*] `airflow.providers.google.datasets.bigquery.create_dataset` is removed in Airflow 3.0 + | +43 | ) +44 | +45 | bigquery_create_dataset() | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -37 | # airflow.providers.google.datasets.gcs -38 | gcs_create_dataset() +46 | +47 | # airflow.providers.google.datasets.gcs | = help: Use `create_asset` from `airflow.providers.google.assets.bigquery` instead. ℹ Safe fix -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, -23 23 | ) - 24 |+from airflow.providers.google.assets.bigquery import create_asset -24 25 | -25 26 | AvpEntities.DATASET -26 27 | --------------------------------------------------------------------------------- -33 34 | -34 35 | -35 36 | # airflow.providers.google.datasets.bigquery -36 |-bigquery_create_dataset() - 37 |+create_asset() -37 38 | # airflow.providers.google.datasets.gcs -38 39 | gcs_create_dataset() -39 40 | gcs_convert_dataset_to_openlineage() - -AIR301_provider_names_fix.py:38:1: AIR301 [*] `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0 - | -36 | bigquery_create_dataset() -37 | # airflow.providers.google.datasets.gcs -38 | gcs_create_dataset() +41 41 | from airflow.providers.google.datasets.bigquery import ( +42 42 | create_dataset as bigquery_create_dataset, +43 43 | ) + 44 |+from airflow.providers.google.assets.bigquery import create_asset +44 45 | +45 |-bigquery_create_dataset() + 46 |+create_asset() +46 47 | +47 48 | # airflow.providers.google.datasets.gcs +48 49 | from airflow.providers.google.datasets.gcs import ( + +AIR301_provider_names_fix.py:53:1: AIR301 [*] `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0 + | +51 | from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset +52 | +53 | gcs_create_dataset() | ^^^^^^^^^^^^^^^^^^ AIR301 -39 | gcs_convert_dataset_to_openlineage() -40 | # airflow.providers.openlineage.utils.utils +54 | gcs_convert_dataset_to_openlineage() | = help: Use `create_asset` from `airflow.providers.google.assets.gcs` instead. ℹ Safe fix -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, -23 23 | ) - 24 |+from airflow.providers.google.assets.gcs import create_asset -24 25 | -25 26 | AvpEntities.DATASET -26 27 | --------------------------------------------------------------------------------- -35 36 | # airflow.providers.google.datasets.bigquery -36 37 | bigquery_create_dataset() -37 38 | # airflow.providers.google.datasets.gcs -38 |-gcs_create_dataset() - 39 |+create_asset() -39 40 | gcs_convert_dataset_to_openlineage() -40 41 | # airflow.providers.openlineage.utils.utils -41 42 | DatasetInfo() - -AIR301_provider_names_fix.py:39:1: AIR301 [*] `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0 - | -37 | # airflow.providers.google.datasets.gcs -38 | gcs_create_dataset() -39 | gcs_convert_dataset_to_openlineage() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -40 | # airflow.providers.openlineage.utils.utils -41 | DatasetInfo() - | - = help: Use `convert_asset_to_openlineage` from `airflow.providers.google.assets.gcs` instead. +49 49 | convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage, +50 50 | ) +51 51 | from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset + 52 |+from airflow.providers.google.assets.gcs import create_asset +52 53 | +53 |-gcs_create_dataset() + 54 |+create_asset() +54 55 | gcs_convert_dataset_to_openlineage() -ℹ Safe fix -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, -23 23 | ) - 24 |+from airflow.providers.google.assets.gcs import convert_asset_to_openlineage -24 25 | -25 26 | AvpEntities.DATASET -26 27 | --------------------------------------------------------------------------------- -36 37 | bigquery_create_dataset() -37 38 | # airflow.providers.google.datasets.gcs -38 39 | gcs_create_dataset() -39 |-gcs_convert_dataset_to_openlineage() - 40 |+convert_asset_to_openlineage() -40 41 | # airflow.providers.openlineage.utils.utils -41 42 | DatasetInfo() -42 43 | translate_airflow_dataset() - -AIR301_provider_names_fix.py:41:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0 - | -39 | gcs_convert_dataset_to_openlineage() -40 | # airflow.providers.openlineage.utils.utils -41 | DatasetInfo() - | ^^^^^^^^^^^ AIR301 -42 | translate_airflow_dataset() -43 | # +AIR301_provider_names_fix.py:54:1: AIR301 [*] `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0 | - = help: Use `AssetInfo` from `airflow.providers.openlineage.utils.utils` instead. - -ℹ Safe fix -20 20 | from airflow.providers.openlineage.utils.utils import ( -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, - 23 |+AssetInfo, -23 24 | ) -24 25 | -25 26 | AvpEntities.DATASET --------------------------------------------------------------------------------- -38 39 | gcs_create_dataset() -39 40 | gcs_convert_dataset_to_openlineage() -40 41 | # airflow.providers.openlineage.utils.utils -41 |-DatasetInfo() - 42 |+AssetInfo() -42 43 | translate_airflow_dataset() -43 44 | # -44 45 | # airflow.secrets.local_filesystem - -AIR301_provider_names_fix.py:42:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0 - | -40 | # airflow.providers.openlineage.utils.utils -41 | DatasetInfo() -42 | translate_airflow_dataset() - | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -43 | # -44 | # airflow.secrets.local_filesystem +53 | gcs_create_dataset() +54 | gcs_convert_dataset_to_openlineage() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 | - = help: Use `translate_airflow_asset` from `airflow.providers.openlineage.utils.utils` instead. + = help: Use `convert_asset_to_openlineage` from `airflow.providers.google.assets.gcs` instead. ℹ Safe fix -20 20 | from airflow.providers.openlineage.utils.utils import ( -21 21 | DatasetInfo, -22 22 | translate_airflow_dataset, - 23 |+translate_airflow_asset, -23 24 | ) -24 25 | -25 26 | AvpEntities.DATASET --------------------------------------------------------------------------------- -39 40 | gcs_convert_dataset_to_openlineage() -40 41 | # airflow.providers.openlineage.utils.utils -41 42 | DatasetInfo() -42 |-translate_airflow_dataset() - 43 |+translate_airflow_asset() -43 44 | # -44 45 | # airflow.secrets.local_filesystem -45 46 | load_connections() +49 49 | convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage, +50 50 | ) +51 51 | from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset + 52 |+from airflow.providers.google.assets.gcs import convert_asset_to_openlineage +52 53 | +53 54 | gcs_create_dataset() +54 |-gcs_convert_dataset_to_openlineage() + 55 |+convert_asset_to_openlineage() From 0c29e258c621eff754665f3878053c83ce7625fd Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 30 May 2025 21:27:14 +0800 Subject: [PATCH 284/487] [`airflow`] Add unsafe fix for module moved cases (`AIR311`) (#18366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Follow up on https://github.com/astral-sh/ruff/pull/18093 and apply it to AIR311 --- Rules fixed * `airflow.models.datasets.expand_alias_to_datasets` → `airflow.models.asset.expand_alias_to_assets` * `airflow.models.baseoperatorlink.BaseOperatorLink` → `airflow.sdk.BaseOperatorLink` ## Test Plan The existing test fixtures have been updated --- .../test/fixtures/airflow/AIR311_names.py | 35 +- .../airflow/rules/suggested_to_update_3_0.rs | 38 +- ...irflow__tests__AIR311_AIR311_names.py.snap | 790 +++++++++++------- 3 files changed, 533 insertions(+), 330 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py index 242b650938f9c1..47c7b4270d8ffb 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py @@ -9,19 +9,12 @@ expand_alias_to_datasets, ) from airflow.datasets.metadata import Metadata -from airflow.decorators import dag, setup, task, task_group, teardown -from airflow.io.path import ObjectStoragePath -from airflow.io.storage import attach -from airflow.models import DAG as DAGFromModel -from airflow.models import ( - Connection, - Variable, +from airflow.decorators import ( + dag, + setup, + task, + task_group, ) -from airflow.models.baseoperator import chain, chain_linear, cross_downstream -from airflow.models.baseoperatorlink import BaseOperatorLink -from airflow.models.dag import DAG as DAGFromDag -from airflow.timetables.datasets import DatasetOrTimeSchedule -from airflow.utils.dag_parsing_context import get_parsing_context # airflow DatasetFromRoot() @@ -39,9 +32,22 @@ task() task_group() setup() +from airflow.decorators import teardown +from airflow.io.path import ObjectStoragePath +from airflow.io.storage import attach +from airflow.models import DAG as DAGFromModel +from airflow.models import ( + Connection, + Variable, +) +from airflow.models.baseoperator import chain, chain_linear, cross_downstream +from airflow.models.baseoperatorlink import BaseOperatorLink +from airflow.models.dag import DAG as DAGFromDag + +# airflow.decorators teardown() -# airflow.io +# # airflow.io ObjectStoragePath() attach() @@ -60,6 +66,9 @@ # airflow.models.dag DAGFromDag() +from airflow.timetables.datasets import DatasetOrTimeSchedule +from airflow.utils.dag_parsing_context import get_parsing_context + # airflow.timetables.datasets DatasetOrTimeSchedule() diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index da733f8d462388..f7914999c12406 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -1,7 +1,7 @@ use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; +use crate::rules::airflow::helpers::{Replacement, is_airflow_builtin_or_provider}; use crate::rules::airflow::helpers::{ - Replacement, is_airflow_builtin_or_provider, is_guarded_by_try_except, + generate_import_edit, generate_remove_and_runtime_import_edit, is_guarded_by_try_except, }; use crate::{Edit, Fix, FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; @@ -211,7 +211,7 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { name: "AssetAny", }, "expand_alias_to_datasets" => Replacement::AutoImport { - module: "airflow.sdk", + module: "airflow.models.asset", name: "expand_alias_to_assets", }, _ => return, @@ -256,7 +256,7 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { name: (*rest).to_string(), }, ["airflow", "models", "baseoperatorlink", "BaseOperatorLink"] => Replacement::AutoImport { - module: "airflow.sdk.definitions.baseoperatorlink", + module: "airflow.sdk", name: "BaseOperatorLink", }, // airflow.model..DAG @@ -301,22 +301,16 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { if is_guarded_by_try_except(expr, module, name, checker.semantic()) { return; } - - checker - .report_diagnostic( - Airflow3SuggestedUpdate { - deprecated: qualified_name.to_string(), - replacement: replacement.clone(), - }, - range, - ) - .try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from(module, name), - expr.start(), - checker.semantic(), - )?; - let replacement_edit = Edit::range_replacement(binding, range); - Ok(Fix::safe_edits(import_edit, [replacement_edit])) - }); + let mut diagnostic = checker.report_diagnostic( + Airflow3SuggestedUpdate { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ); + if let Some(fix) = generate_import_edit(expr, checker, module, name, range) + .or_else(|| generate_remove_and_runtime_import_edit(expr, checker, module, name)) + { + diagnostic.set_fix(fix); + } } diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap index 840f84b87c20ea..b34b16ffcc62c3 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap @@ -1,404 +1,604 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR311_names.py:27:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR311_names.py:20:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -26 | # airflow -27 | DatasetFromRoot() +19 | # airflow +20 | DatasetFromRoot() | ^^^^^^^^^^^^^^^ AIR311 -28 | -29 | # airflow.datasets +21 | +22 | # airflow.datasets | = help: Use `Asset` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import Asset -25 26 | -26 27 | # airflow -27 |-DatasetFromRoot() - 28 |+Asset() -28 29 | -29 30 | # airflow.datasets -30 31 | Dataset() - -AIR311_names.py:30:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -29 | # airflow.datasets -30 | Dataset() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.sdk import Asset +18 19 | +19 20 | # airflow +20 |-DatasetFromRoot() + 21 |+Asset() +21 22 | +22 23 | # airflow.datasets +23 24 | Dataset() + +AIR311_names.py:23:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +22 | # airflow.datasets +23 | Dataset() | ^^^^^^^ AIR311 -31 | DatasetAlias() -32 | DatasetAll() +24 | DatasetAlias() +25 | DatasetAll() | = help: Use `Asset` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import Asset -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() -28 29 | -29 30 | # airflow.datasets -30 |-Dataset() - 31 |+Asset() -31 32 | DatasetAlias() -32 33 | DatasetAll() -33 34 | DatasetAny() - -AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -29 | # airflow.datasets -30 | Dataset() -31 | DatasetAlias() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.sdk import Asset +18 19 | +19 20 | # airflow +20 21 | DatasetFromRoot() +21 22 | +22 23 | # airflow.datasets +23 |-Dataset() + 24 |+Asset() +24 25 | DatasetAlias() +25 26 | DatasetAll() +26 27 | DatasetAny() + +AIR311_names.py:24:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +22 | # airflow.datasets +23 | Dataset() +24 | DatasetAlias() | ^^^^^^^^^^^^ AIR311 -32 | DatasetAll() -33 | DatasetAny() +25 | DatasetAll() +26 | DatasetAny() | = help: Use `AssetAlias` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import AssetAlias -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() -28 29 | -29 30 | # airflow.datasets -30 31 | Dataset() -31 |-DatasetAlias() - 32 |+AssetAlias() -32 33 | DatasetAll() -33 34 | DatasetAny() -34 35 | Metadata() - -AIR311_names.py:32:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -30 | Dataset() -31 | DatasetAlias() -32 | DatasetAll() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.sdk import AssetAlias +18 19 | +19 20 | # airflow +20 21 | DatasetFromRoot() +21 22 | +22 23 | # airflow.datasets +23 24 | Dataset() +24 |-DatasetAlias() + 25 |+AssetAlias() +25 26 | DatasetAll() +26 27 | DatasetAny() +27 28 | Metadata() + +AIR311_names.py:25:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +23 | Dataset() +24 | DatasetAlias() +25 | DatasetAll() | ^^^^^^^^^^ AIR311 -33 | DatasetAny() -34 | Metadata() +26 | DatasetAny() +27 | Metadata() | = help: Use `AssetAll` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import AssetAll -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.sdk import AssetAll +18 19 | +19 20 | # airflow +20 21 | DatasetFromRoot() -------------------------------------------------------------------------------- -29 30 | # airflow.datasets -30 31 | Dataset() -31 32 | DatasetAlias() -32 |-DatasetAll() - 33 |+AssetAll() -33 34 | DatasetAny() -34 35 | Metadata() -35 36 | expand_alias_to_datasets() - -AIR311_names.py:33:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -31 | DatasetAlias() -32 | DatasetAll() -33 | DatasetAny() +22 23 | # airflow.datasets +23 24 | Dataset() +24 25 | DatasetAlias() +25 |-DatasetAll() + 26 |+AssetAll() +26 27 | DatasetAny() +27 28 | Metadata() +28 29 | expand_alias_to_datasets() + +AIR311_names.py:26:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +24 | DatasetAlias() +25 | DatasetAll() +26 | DatasetAny() | ^^^^^^^^^^ AIR311 -34 | Metadata() -35 | expand_alias_to_datasets() +27 | Metadata() +28 | expand_alias_to_datasets() | = help: Use `AssetAny` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import AssetAny -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.sdk import AssetAny +18 19 | +19 20 | # airflow +20 21 | DatasetFromRoot() -------------------------------------------------------------------------------- -30 31 | Dataset() -31 32 | DatasetAlias() -32 33 | DatasetAll() -33 |-DatasetAny() - 34 |+AssetAny() -34 35 | Metadata() -35 36 | expand_alias_to_datasets() -36 37 | - -AIR311_names.py:34:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -32 | DatasetAll() -33 | DatasetAny() -34 | Metadata() +23 24 | Dataset() +24 25 | DatasetAlias() +25 26 | DatasetAll() +26 |-DatasetAny() + 27 |+AssetAny() +27 28 | Metadata() +28 29 | expand_alias_to_datasets() +29 30 | + +AIR311_names.py:27:1: AIR311 [*] `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +25 | DatasetAll() +26 | DatasetAny() +27 | Metadata() | ^^^^^^^^ AIR311 -35 | expand_alias_to_datasets() +28 | expand_alias_to_datasets() | = help: Use `Metadata` from `airflow.sdk` instead. -AIR311_names.py:35:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -33 | DatasetAny() -34 | Metadata() -35 | expand_alias_to_datasets() +ℹ Unsafe fix +8 8 | DatasetAny, +9 9 | expand_alias_to_datasets, +10 10 | ) +11 |-from airflow.datasets.metadata import Metadata +12 11 | from airflow.decorators import ( +13 12 | dag, +14 13 | setup, +15 14 | task, +16 15 | task_group, +17 16 | ) + 17 |+from airflow.sdk import Metadata +18 18 | +19 19 | # airflow +20 20 | DatasetFromRoot() + +AIR311_names.py:28:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +26 | DatasetAny() +27 | Metadata() +28 | expand_alias_to_datasets() | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR311 -36 | -37 | # airflow.decorators +29 | +30 | # airflow.decorators | - = help: Use `expand_alias_to_assets` from `airflow.sdk` instead. + = help: Use `expand_alias_to_assets` from `airflow.models.asset` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import expand_alias_to_assets -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() +15 15 | task, +16 16 | task_group, +17 17 | ) + 18 |+from airflow.models.asset import expand_alias_to_assets +18 19 | +19 20 | # airflow +20 21 | DatasetFromRoot() -------------------------------------------------------------------------------- -32 33 | DatasetAll() -33 34 | DatasetAny() -34 35 | Metadata() -35 |-expand_alias_to_datasets() - 36 |+expand_alias_to_assets() -36 37 | -37 38 | # airflow.decorators -38 39 | dag() - -AIR311_names.py:38:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -37 | # airflow.decorators -38 | dag() +25 26 | DatasetAll() +26 27 | DatasetAny() +27 28 | Metadata() +28 |-expand_alias_to_datasets() + 29 |+expand_alias_to_assets() +29 30 | +30 31 | # airflow.decorators +31 32 | dag() + +AIR311_names.py:31:1: AIR311 [*] `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +30 | # airflow.decorators +31 | dag() | ^^^ AIR311 -39 | task() -40 | task_group() +32 | task() +33 | task_group() | = help: Use `dag` from `airflow.sdk` instead. -AIR311_names.py:39:1: AIR311 `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -37 | # airflow.decorators -38 | dag() -39 | task() +ℹ Unsafe fix +10 10 | ) +11 11 | from airflow.datasets.metadata import Metadata +12 12 | from airflow.decorators import ( +13 |- dag, +14 13 | setup, +15 14 | task, +16 15 | task_group, +17 16 | ) + 17 |+from airflow.sdk import dag +18 18 | +19 19 | # airflow +20 20 | DatasetFromRoot() + +AIR311_names.py:32:1: AIR311 [*] `airflow.decorators.task` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +30 | # airflow.decorators +31 | dag() +32 | task() | ^^^^ AIR311 -40 | task_group() -41 | setup() +33 | task_group() +34 | setup() | = help: Use `task` from `airflow.sdk` instead. -AIR311_names.py:40:1: AIR311 `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -38 | dag() -39 | task() -40 | task_group() +ℹ Unsafe fix +12 12 | from airflow.decorators import ( +13 13 | dag, +14 14 | setup, +15 |- task, +16 15 | task_group, +17 16 | ) + 17 |+from airflow.sdk import task +18 18 | +19 19 | # airflow +20 20 | DatasetFromRoot() + +AIR311_names.py:33:1: AIR311 [*] `airflow.decorators.task_group` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +31 | dag() +32 | task() +33 | task_group() | ^^^^^^^^^^ AIR311 -41 | setup() -42 | teardown() +34 | setup() +35 | from airflow.decorators import teardown | = help: Use `task_group` from `airflow.sdk` instead. -AIR311_names.py:41:1: AIR311 `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -39 | task() -40 | task_group() -41 | setup() +ℹ Unsafe fix +13 13 | dag, +14 14 | setup, +15 15 | task, +16 |- task_group, +17 16 | ) + 17 |+from airflow.sdk import task_group +18 18 | +19 19 | # airflow +20 20 | DatasetFromRoot() + +AIR311_names.py:34:1: AIR311 [*] `airflow.decorators.setup` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +32 | task() +33 | task_group() +34 | setup() | ^^^^^ AIR311 -42 | teardown() +35 | from airflow.decorators import teardown +36 | from airflow.io.path import ObjectStoragePath | = help: Use `setup` from `airflow.sdk` instead. -AIR311_names.py:42:1: AIR311 `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -40 | task_group() -41 | setup() -42 | teardown() +ℹ Unsafe fix +11 11 | from airflow.datasets.metadata import Metadata +12 12 | from airflow.decorators import ( +13 13 | dag, +14 |- setup, +15 14 | task, +16 15 | task_group, +17 16 | ) + 17 |+from airflow.sdk import setup +18 18 | +19 19 | # airflow +20 20 | DatasetFromRoot() + +AIR311_names.py:48:1: AIR311 [*] `airflow.decorators.teardown` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +47 | # airflow.decorators +48 | teardown() | ^^^^^^^^ AIR311 -43 | -44 | # airflow.io +49 | +50 | # # airflow.io | = help: Use `teardown` from `airflow.sdk` instead. -AIR311_names.py:45:1: AIR311 `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -44 | # airflow.io -45 | ObjectStoragePath() +ℹ Unsafe fix +32 32 | task() +33 33 | task_group() +34 34 | setup() +35 |-from airflow.decorators import teardown +36 35 | from airflow.io.path import ObjectStoragePath +37 36 | from airflow.io.storage import attach +38 37 | from airflow.models import DAG as DAGFromModel +-------------------------------------------------------------------------------- +43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 43 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk import teardown +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:51:1: AIR311 [*] `airflow.io.path.ObjectStoragePath` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +50 | # # airflow.io +51 | ObjectStoragePath() | ^^^^^^^^^^^^^^^^^ AIR311 -46 | attach() +52 | attach() | = help: Use `ObjectStoragePath` from `airflow.sdk` instead. -AIR311_names.py:46:1: AIR311 `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -44 | # airflow.io -45 | ObjectStoragePath() -46 | attach() +ℹ Unsafe fix +33 33 | task_group() +34 34 | setup() +35 35 | from airflow.decorators import teardown +36 |-from airflow.io.path import ObjectStoragePath +37 36 | from airflow.io.storage import attach +38 37 | from airflow.models import DAG as DAGFromModel +39 38 | from airflow.models import ( +-------------------------------------------------------------------------------- +43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 43 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk import ObjectStoragePath +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:52:1: AIR311 [*] `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +50 | # # airflow.io +51 | ObjectStoragePath() +52 | attach() | ^^^^^^ AIR311 -47 | -48 | # airflow.models +53 | +54 | # airflow.models | = help: Use `attach` from `airflow.sdk.io` instead. -AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -48 | # airflow.models -49 | Connection() +ℹ Unsafe fix +34 34 | setup() +35 35 | from airflow.decorators import teardown +36 36 | from airflow.io.path import ObjectStoragePath +37 |-from airflow.io.storage import attach +38 37 | from airflow.models import DAG as DAGFromModel +39 38 | from airflow.models import ( +40 39 | Connection, +-------------------------------------------------------------------------------- +43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 43 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk.io import attach +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:55:1: AIR311 [*] `airflow.models.Connection` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +54 | # airflow.models +55 | Connection() | ^^^^^^^^^^ AIR311 -50 | DAGFromModel() -51 | Variable() +56 | DAGFromModel() +57 | Variable() | = help: Use `Connection` from `airflow.sdk` instead. -AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -48 | # airflow.models -49 | Connection() -50 | DAGFromModel() +ℹ Unsafe fix +37 37 | from airflow.io.storage import attach +38 38 | from airflow.models import DAG as DAGFromModel +39 39 | from airflow.models import ( +40 |- Connection, +41 40 | Variable, +42 41 | ) +43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 43 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk import Connection +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:56:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +54 | # airflow.models +55 | Connection() +56 | DAGFromModel() | ^^^^^^^^^^^^ AIR311 -51 | Variable() +57 | Variable() | = help: Use `DAG` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import DAG -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() +43 43 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 44 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 45 | from airflow.models.dag import DAG as DAGFromDag + 46 |+from airflow.sdk import DAG +46 47 | +47 48 | # airflow.decorators +48 49 | teardown() -------------------------------------------------------------------------------- -47 48 | -48 49 | # airflow.models -49 50 | Connection() -50 |-DAGFromModel() - 51 |+DAG() -51 52 | Variable() -52 53 | -53 54 | # airflow.models.baseoperator - -AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -49 | Connection() -50 | DAGFromModel() -51 | Variable() +53 54 | +54 55 | # airflow.models +55 56 | Connection() +56 |-DAGFromModel() + 57 |+DAG() +57 58 | Variable() +58 59 | +59 60 | # airflow.models.baseoperator + +AIR311_names.py:57:1: AIR311 [*] `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +55 | Connection() +56 | DAGFromModel() +57 | Variable() | ^^^^^^^^ AIR311 -52 | -53 | # airflow.models.baseoperator +58 | +59 | # airflow.models.baseoperator | = help: Use `Variable` from `airflow.sdk` instead. -AIR311_names.py:54:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -53 | # airflow.models.baseoperator -54 | chain() +ℹ Unsafe fix +38 38 | from airflow.models import DAG as DAGFromModel +39 39 | from airflow.models import ( +40 40 | Connection, +41 |- Variable, +42 41 | ) +43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 43 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk import Variable +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:60:1: AIR311 [*] `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +59 | # airflow.models.baseoperator +60 | chain() | ^^^^^ AIR311 -55 | chain_linear() -56 | cross_downstream() +61 | chain_linear() +62 | cross_downstream() | = help: Use `chain` from `airflow.sdk` instead. -AIR311_names.py:55:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -53 | # airflow.models.baseoperator -54 | chain() -55 | chain_linear() +ℹ Unsafe fix +40 40 | Connection, +41 41 | Variable, +42 42 | ) +43 |-from airflow.models.baseoperator import chain, chain_linear, cross_downstream + 43 |+from airflow.models.baseoperator import chain_linear, cross_downstream +44 44 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 45 | from airflow.models.dag import DAG as DAGFromDag + 46 |+from airflow.sdk import chain +46 47 | +47 48 | # airflow.decorators +48 49 | teardown() + +AIR311_names.py:61:1: AIR311 [*] `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +59 | # airflow.models.baseoperator +60 | chain() +61 | chain_linear() | ^^^^^^^^^^^^ AIR311 -56 | cross_downstream() +62 | cross_downstream() | = help: Use `chain_linear` from `airflow.sdk` instead. -AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -54 | chain() -55 | chain_linear() -56 | cross_downstream() +ℹ Unsafe fix +40 40 | Connection, +41 41 | Variable, +42 42 | ) +43 |-from airflow.models.baseoperator import chain, chain_linear, cross_downstream + 43 |+from airflow.models.baseoperator import chain, cross_downstream +44 44 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 45 | from airflow.models.dag import DAG as DAGFromDag + 46 |+from airflow.sdk import chain_linear +46 47 | +47 48 | # airflow.decorators +48 49 | teardown() + +AIR311_names.py:62:1: AIR311 [*] `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +60 | chain() +61 | chain_linear() +62 | cross_downstream() | ^^^^^^^^^^^^^^^^ AIR311 -57 | -58 | # airflow.models.baseoperatolinker +63 | +64 | # airflow.models.baseoperatolinker | = help: Use `cross_downstream` from `airflow.sdk` instead. -AIR311_names.py:59:1: AIR311 `airflow.models.baseoperatorlink.BaseOperatorLink` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -58 | # airflow.models.baseoperatolinker -59 | BaseOperatorLink() +ℹ Unsafe fix +40 40 | Connection, +41 41 | Variable, +42 42 | ) +43 |-from airflow.models.baseoperator import chain, chain_linear, cross_downstream + 43 |+from airflow.models.baseoperator import chain, chain_linear +44 44 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 45 | from airflow.models.dag import DAG as DAGFromDag + 46 |+from airflow.sdk import cross_downstream +46 47 | +47 48 | # airflow.decorators +48 49 | teardown() + +AIR311_names.py:65:1: AIR311 [*] `airflow.models.baseoperatorlink.BaseOperatorLink` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +64 | # airflow.models.baseoperatolinker +65 | BaseOperatorLink() | ^^^^^^^^^^^^^^^^ AIR311 -60 | -61 | # airflow.models.dag - | - = help: Use `BaseOperatorLink` from `airflow.sdk.definitions.baseoperatorlink` instead. - -AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -61 | # airflow.models.dag -62 | DAGFromDag() +66 | +67 | # airflow.models.dag + | + = help: Use `BaseOperatorLink` from `airflow.sdk` instead. + +ℹ Unsafe fix +41 41 | Variable, +42 42 | ) +43 43 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 |-from airflow.models.baseoperatorlink import BaseOperatorLink +45 44 | from airflow.models.dag import DAG as DAGFromDag + 45 |+from airflow.sdk import BaseOperatorLink +46 46 | +47 47 | # airflow.decorators +48 48 | teardown() + +AIR311_names.py:68:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +67 | # airflow.models.dag +68 | DAGFromDag() | ^^^^^^^^^^ AIR311 -63 | # airflow.timetables.datasets -64 | DatasetOrTimeSchedule() +69 | from airflow.timetables.datasets import DatasetOrTimeSchedule +70 | from airflow.utils.dag_parsing_context import get_parsing_context | = help: Use `DAG` from `airflow.sdk` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.sdk import DAG -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() +43 43 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream +44 44 | from airflow.models.baseoperatorlink import BaseOperatorLink +45 45 | from airflow.models.dag import DAG as DAGFromDag + 46 |+from airflow.sdk import DAG +46 47 | +47 48 | # airflow.decorators +48 49 | teardown() -------------------------------------------------------------------------------- -59 60 | BaseOperatorLink() -60 61 | -61 62 | # airflow.models.dag -62 |-DAGFromDag() - 63 |+DAG() -63 64 | # airflow.timetables.datasets -64 65 | DatasetOrTimeSchedule() -65 66 | - -AIR311_names.py:64:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -62 | DAGFromDag() -63 | # airflow.timetables.datasets -64 | DatasetOrTimeSchedule() +65 66 | BaseOperatorLink() +66 67 | +67 68 | # airflow.models.dag +68 |-DAGFromDag() + 69 |+DAG() +69 70 | from airflow.timetables.datasets import DatasetOrTimeSchedule +70 71 | from airflow.utils.dag_parsing_context import get_parsing_context +71 72 | + +AIR311_names.py:73:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +72 | # airflow.timetables.datasets +73 | DatasetOrTimeSchedule() | ^^^^^^^^^^^^^^^^^^^^^ AIR311 -65 | -66 | # airflow.utils.dag_parsing_context +74 | +75 | # airflow.utils.dag_parsing_context | = help: Use `AssetOrTimeSchedule` from `airflow.timetables.assets` instead. ℹ Safe fix -22 22 | from airflow.models.dag import DAG as DAGFromDag -23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule -24 24 | from airflow.utils.dag_parsing_context import get_parsing_context - 25 |+from airflow.timetables.assets import AssetOrTimeSchedule -25 26 | -26 27 | # airflow -27 28 | DatasetFromRoot() --------------------------------------------------------------------------------- -61 62 | # airflow.models.dag -62 63 | DAGFromDag() -63 64 | # airflow.timetables.datasets -64 |-DatasetOrTimeSchedule() - 65 |+AssetOrTimeSchedule() -65 66 | -66 67 | # airflow.utils.dag_parsing_context -67 68 | get_parsing_context() - -AIR311_names.py:67:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. - | -66 | # airflow.utils.dag_parsing_context -67 | get_parsing_context() +68 68 | DAGFromDag() +69 69 | from airflow.timetables.datasets import DatasetOrTimeSchedule +70 70 | from airflow.utils.dag_parsing_context import get_parsing_context + 71 |+from airflow.timetables.assets import AssetOrTimeSchedule +71 72 | +72 73 | # airflow.timetables.datasets +73 |-DatasetOrTimeSchedule() + 74 |+AssetOrTimeSchedule() +74 75 | +75 76 | # airflow.utils.dag_parsing_context +76 77 | get_parsing_context() + +AIR311_names.py:76:1: AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +75 | # airflow.utils.dag_parsing_context +76 | get_parsing_context() | ^^^^^^^^^^^^^^^^^^^ AIR311 | = help: Use `get_parsing_context` from `airflow.sdk` instead. + +ℹ Unsafe fix +67 67 | # airflow.models.dag +68 68 | DAGFromDag() +69 69 | from airflow.timetables.datasets import DatasetOrTimeSchedule +70 |-from airflow.utils.dag_parsing_context import get_parsing_context + 70 |+from airflow.sdk import get_parsing_context +71 71 | +72 72 | # airflow.timetables.datasets +73 73 | DatasetOrTimeSchedule() From 8005ebb405ad7d348e72342331638aab2aebacdc Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 30 May 2025 15:31:33 +0200 Subject: [PATCH 285/487] Update salsa past generational id change (#18362) --- Cargo.lock | 29 ++++++++++++++++++++------ Cargo.toml | 2 +- crates/ruff_db/src/testing.rs | 10 ++++----- crates/ty_python_semantic/src/types.rs | 2 +- fuzz/Cargo.toml | 2 +- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 136af27f59b03f..94f7f1d6fd321b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -1106,6 +1118,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hashbrown" @@ -3177,13 +3193,14 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" -version = "0.21.1" -source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c" +version = "0.22.0" +source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" dependencies = [ "boxcar", "compact_str", "crossbeam-queue", "dashmap 6.1.0", + "hashbrown 0.14.5", "hashbrown 0.15.3", "hashlink", "indexmap", @@ -3200,13 +3217,13 @@ dependencies = [ [[package]] name = "salsa-macro-rules" -version = "0.21.1" -source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c" +version = "0.22.0" +source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" [[package]] name = "salsa-macros" -version = "0.21.1" -source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c" +version = "0.22.0" +source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 80f7825dc03484..1b01cf219cd9d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "4818b15f3b7516555d39f5a41cb75970448bee4c" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "40d7844a7a7449a136e0946920a678b55a82f30b" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } diff --git a/crates/ruff_db/src/testing.rs b/crates/ruff_db/src/testing.rs index 1ecbeb2c6c993f..1ed58f1977c231 100644 --- a/crates/ruff_db/src/testing.rs +++ b/crates/ruff_db/src/testing.rs @@ -13,12 +13,12 @@ pub fn assert_function_query_was_not_run( Q: Fn(QDb, I) -> R, I: salsa::plumbing::AsId + std::fmt::Debug + Copy, { - let id = input.as_id().as_u32(); + let id = input.as_id(); let (query_name, will_execute_event) = find_will_execute_event(db, query, input, events); db.attach(|_| { if let Some(will_execute_event) = will_execute_event { - panic!("Expected query {query_name}({id}) not to have run but it did: {will_execute_event:?}\n\n{events:#?}"); + panic!("Expected query {query_name}({id:?}) not to have run but it did: {will_execute_event:?}\n\n{events:#?}"); } }); } @@ -65,7 +65,7 @@ pub fn assert_function_query_was_run( Q: Fn(QDb, I) -> R, I: salsa::plumbing::AsId + std::fmt::Debug + Copy, { - let id = input.as_id().as_u32(); + let id = input.as_id(); let (query_name, will_execute_event) = find_will_execute_event(db, query, input, events); db.attach(|_| { @@ -224,7 +224,7 @@ fn query_was_not_run() { } #[test] -#[should_panic(expected = "Expected query len(0) not to have run but it did:")] +#[should_panic(expected = "Expected query len(Id(0)) not to have run but it did:")] fn query_was_not_run_fails_if_query_was_run() { use crate::tests::TestDb; use salsa::prelude::*; @@ -287,7 +287,7 @@ fn const_query_was_not_run_fails_if_query_was_run() { } #[test] -#[should_panic(expected = "Expected query len(0) to have run but it did not:")] +#[should_panic(expected = "Expected query len(Id(0)) to have run but it did not:")] fn query_was_run_fails_if_query_was_not_run() { use crate::tests::TestDb; use salsa::prelude::*; diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c8f3afa6256c6a..6665ddb7a8a651 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -9189,7 +9189,7 @@ impl<'db> BoundSuperType<'db> { // Make sure that the `Type` enum does not grow unexpectedly. #[cfg(not(debug_assertions))] #[cfg(target_pointer_width = "64")] -static_assertions::assert_eq_size!(Type, [u8; 16]); +static_assertions::assert_eq_size!(Type, [u8; 24]); #[cfg(test)] pub(crate) mod tests { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6eca24519666cf..23a9e50b8faf75 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "4818b15f3b7516555d39f5a41cb75970448bee4c" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "40d7844a7a7449a136e0946920a678b55a82f30b" } similar = { version = "2.5.0" } tracing = { version = "0.1.40" } From c713e76e4db7f5001c5954e8e25abeb6a308a2a4 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 30 May 2025 09:34:38 -0400 Subject: [PATCH 286/487] Add a `SourceFile` to `OldDiagnostic` (#18356) Summary -- This is the last main difference between the `OldDiagnostic` and `Message` types, so attaching a `SourceFile` to `OldDiagnostic` should make combining the two types almost trivial. Initially I updated the remaining rules without access to a `Checker` to take a `&SourceFile` directly, but after Micha's suggestion in https://github.com/astral-sh/ruff/pull/18356#discussion_r2113281552, I updated all of these calls to take a `LintContext` instead. This new type is a thin wrapper around a `RefCell>` and a `SourceFile` and now has the `report_diagnostic` method returning a `DiagnosticGuard` instead of `Checker`. This allows the same `Drop`-based implementation to be used in cases without a `Checker` and also avoids a lot of intermediate allocations of `Vec`s. `Checker` now also contains a `LintContext`, which it defers to for its `report_diagnostic` methods, which I preserved for convenience. Test Plan -- Existing tests --- crates/ruff/src/commands/check.rs | 3 +- crates/ruff/src/diagnostics.rs | 6 +- crates/ruff_linter/src/checkers/ast/mod.rs | 143 +++++++++++----- crates/ruff_linter/src/checkers/filesystem.rs | 26 +-- crates/ruff_linter/src/checkers/imports.rs | 21 ++- .../ruff_linter/src/checkers/logical_lines.rs | 48 +++--- crates/ruff_linter/src/checkers/noqa.rs | 30 ++-- .../src/checkers/physical_lines.rs | 48 +++--- crates/ruff_linter/src/checkers/tokens.rs | 77 ++++----- crates/ruff_linter/src/diagnostic.rs | 6 +- crates/ruff_linter/src/fix/edits.rs | 16 +- crates/ruff_linter/src/fix/mod.rs | 12 +- crates/ruff_linter/src/linter.rs | 161 ++++++++++-------- crates/ruff_linter/src/message/mod.rs | 7 +- crates/ruff_linter/src/noqa.rs | 31 ++-- crates/ruff_linter/src/pyproject_toml.rs | 15 +- .../eradicate/rules/commented_out_code.rs | 15 +- .../flake8_annotations/rules/definition.rs | 7 +- .../rules/stdlib_module_shadowing.rs | 23 ++- .../flake8_commas/rules/trailing_commas.rs | 29 ++-- .../rules/missing_copyright_notice.rs | 17 +- .../src/rules/flake8_executable/rules/mod.rs | 24 +-- .../rules/shebang_leading_whitespace.rs | 16 +- .../rules/shebang_missing_executable_file.rs | 24 +-- .../rules/shebang_missing_python.rs | 10 +- .../rules/shebang_not_executable.rs | 19 ++- .../rules/shebang_not_first_line.rs | 11 +- .../src/rules/flake8_fixme/rules/todos.rs | 39 +++-- .../rules/implicit.rs | 13 +- .../rules/implicit_namespace_package.rs | 21 ++- .../flake8_pyi/rules/type_comment_in_stub.rs | 7 +- .../src/rules/flake8_todos/rules/todos.rs | 40 ++--- .../rules/isort/rules/add_required_imports.rs | 36 ++-- .../src/rules/isort/rules/organize_imports.rs | 14 +- .../pep8_naming/rules/invalid_module_name.rs | 16 +- .../rules/pycodestyle/rules/blank_lines.rs | 47 +++-- .../pycodestyle/rules/compound_statements.rs | 28 ++- .../pycodestyle/rules/doc_line_too_long.rs | 22 ++- .../rules/pycodestyle/rules/line_too_long.rs | 17 +- .../logical_lines/extraneous_whitespace.rs | 88 +++++----- .../rules/logical_lines/indentation.rs | 47 ++--- .../rules/logical_lines/missing_whitespace.rs | 14 +- .../missing_whitespace_after_keyword.rs | 10 +- .../missing_whitespace_around_operator.rs | 59 ++++--- .../logical_lines/redundant_backslash.rs | 16 +- .../logical_lines/space_around_operator.rs | 88 +++++----- .../whitespace_around_keywords.rs | 59 +++---- ...hitespace_around_named_parameter_equals.rs | 54 +++--- .../whitespace_before_comment.rs | 59 ++++--- .../whitespace_before_parameters.rs | 10 +- .../rules/missing_newline_at_end_of_file.rs | 13 +- .../rules/mixed_spaces_and_tabs.rs | 10 +- .../pycodestyle/rules/tab_indentation.rs | 11 +- .../rules/too_many_newlines_at_end_of_file.rs | 39 ++--- .../pycodestyle/rules/trailing_whitespace.rs | 13 +- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 16 +- .../pygrep_hooks/rules/blanket_type_ignore.rs | 9 +- .../pylint/rules/bidirectional_unicode.rs | 8 +- .../src/rules/pylint/rules/empty_comment.rs | 25 ++- .../pylint/rules/invalid_string_characters.rs | 30 ++-- .../pyupgrade/rules/extraneous_parentheses.rs | 12 +- .../rules/unnecessary_coding_comment.rs | 11 +- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- .../ruff/rules/ambiguous_unicode_character.rs | 31 ++-- .../rules/ruff/rules/indented_form_feed.rs | 18 +- .../src/rules/ruff/rules/invalid_rule_code.rs | 65 +++---- .../src/rules/ruff/rules/redirected_noqa.rs | 22 +-- .../src/rules/ruff/rules/test_rules.rs | 107 +++++------- 68 files changed, 1069 insertions(+), 1022 deletions(-) diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index 672485b869ae89..249654f9e8a50d 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -131,8 +131,7 @@ pub(crate) fn check( Diagnostics::new( vec![Message::from_diagnostic( - OldDiagnostic::new(IOError { message }, TextRange::default()), - dummy, + OldDiagnostic::new(IOError { message }, TextRange::default(), &dummy), None, )], FxHashMap::default(), diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index 7e2de003d31c6a..d562c009b1ffd1 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -69,8 +69,8 @@ impl Diagnostics { message: err.to_string(), }, TextRange::default(), + &source_file, ), - source_file, None, )], FxHashMap::default(), @@ -235,7 +235,7 @@ pub(crate) fn lint_path( }; let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); - lint_pyproject_toml(source_file, settings) + lint_pyproject_toml(&source_file, settings) } else { vec![] }; @@ -396,7 +396,7 @@ pub(crate) fn lint_stdin( } return Ok(Diagnostics { - messages: lint_pyproject_toml(source_file, &settings.linter), + messages: lint_pyproject_toml(&source_file, &settings.linter), fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]), notebook_indexes: FxHashMap::default(), }); diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 869fa3a724670d..db57ecc87fa1d3 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -57,7 +57,7 @@ use ruff_python_semantic::{ }; use ruff_python_stdlib::builtins::{MAGIC_GLOBALS, python_builtins}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::{OneIndexed, SourceRow}; +use ruff_source_file::{OneIndexed, SourceFile, SourceRow}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::annotation::AnnotationContext; @@ -224,8 +224,6 @@ pub(crate) struct Checker<'a> { visit: deferred::Visit<'a>, /// A set of deferred nodes to be analyzed after the AST traversal (e.g., `for` loops). analyze: deferred::Analyze, - /// The cumulative set of diagnostics computed across all lint rules. - diagnostics: RefCell>, /// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations. flake8_bugbear_seen: RefCell>, /// The end offset of the last visited statement. @@ -239,6 +237,7 @@ pub(crate) struct Checker<'a> { semantic_checker: SemanticSyntaxChecker, /// Errors collected by the `semantic_checker`. semantic_errors: RefCell>, + context: &'a LintContext<'a>, } impl<'a> Checker<'a> { @@ -259,6 +258,7 @@ impl<'a> Checker<'a> { cell_offsets: Option<&'a CellOffsets>, notebook_index: Option<&'a NotebookIndex>, target_version: TargetVersion, + context: &'a LintContext<'a>, ) -> Checker<'a> { let semantic = SemanticModel::new(&settings.typing_modules, path, module); Self { @@ -279,7 +279,6 @@ impl<'a> Checker<'a> { semantic, visit: deferred::Visit::default(), analyze: deferred::Analyze::default(), - diagnostics: RefCell::default(), flake8_bugbear_seen: RefCell::default(), cell_offsets, notebook_index, @@ -288,6 +287,7 @@ impl<'a> Checker<'a> { target_version, semantic_checker: SemanticSyntaxChecker::new(), semantic_errors: RefCell::default(), + context, } } } @@ -389,10 +389,7 @@ impl<'a> Checker<'a> { kind: T, range: TextRange, ) -> DiagnosticGuard<'chk, 'a> { - DiagnosticGuard { - checker: self, - diagnostic: Some(OldDiagnostic::new(kind, range)), - } + self.context.report_diagnostic(kind, range) } /// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is @@ -405,15 +402,8 @@ impl<'a> Checker<'a> { kind: T, range: TextRange, ) -> Option> { - let diagnostic = OldDiagnostic::new(kind, range); - if self.enabled(diagnostic.rule()) { - Some(DiagnosticGuard { - checker: self, - diagnostic: Some(diagnostic), - }) - } else { - None - } + self.context + .report_diagnostic_if_enabled(kind, range, self.settings) } /// Adds a [`TextRange`] to the set of ranges of variable names @@ -2891,30 +2881,26 @@ impl<'a> Checker<'a> { } else { if self.semantic.global_scope().uses_star_imports() { if self.enabled(Rule::UndefinedLocalWithImportStarUsage) { - self.diagnostics.get_mut().push( - OldDiagnostic::new( - pyflakes::rules::UndefinedLocalWithImportStarUsage { - name: name.to_string(), - }, - range, - ) - .with_parent(definition.start()), - ); + self.report_diagnostic( + pyflakes::rules::UndefinedLocalWithImportStarUsage { + name: name.to_string(), + }, + range, + ) + .set_parent(definition.start()); } } else { if self.enabled(Rule::UndefinedExport) { if is_undefined_export_in_dunder_init_enabled(self.settings) || !self.path.ends_with("__init__.py") { - self.diagnostics.get_mut().push( - OldDiagnostic::new( - pyflakes::rules::UndefinedExport { - name: name.to_string(), - }, - range, - ) - .with_parent(definition.start()), - ); + self.report_diagnostic( + pyflakes::rules::UndefinedExport { + name: name.to_string(), + }, + range, + ) + .set_parent(definition.start()); } } } @@ -2975,7 +2961,8 @@ pub(crate) fn check_ast( cell_offsets: Option<&CellOffsets>, notebook_index: Option<&NotebookIndex>, target_version: TargetVersion, -) -> (Vec, Vec) { + context: &LintContext, +) -> Vec { let module_path = package .map(PackageRoot::path) .and_then(|package| to_module_path(package, path)); @@ -3015,6 +3002,7 @@ pub(crate) fn check_ast( cell_offsets, notebook_index, target_version, + context, ); checker.bind_builtins(); @@ -3041,12 +3029,83 @@ pub(crate) fn check_ast( analyze::deferred_scopes(&checker); let Checker { - diagnostics, - semantic_errors, - .. + semantic_errors, .. } = checker; - (diagnostics.into_inner(), semantic_errors.into_inner()) + semantic_errors.into_inner() +} + +/// A type for collecting diagnostics in a given file. +/// +/// [`LintContext::report_diagnostic`] can be used to obtain a [`DiagnosticGuard`], which will push +/// a [`Violation`] to the contained [`OldDiagnostic`] collection on `Drop`. +pub(crate) struct LintContext<'a> { + diagnostics: RefCell>, + source_file: &'a SourceFile, +} + +impl<'a> LintContext<'a> { + /// Create a new collector with the given `source_file` and an empty collection of + /// `OldDiagnostic`s. + pub(crate) fn new(source_file: &'a SourceFile) -> Self { + Self { + diagnostics: RefCell::default(), + source_file, + } + } + + /// Return a [`DiagnosticGuard`] for reporting a diagnostic. + /// + /// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic + /// before it is added to the collection in the collector on `Drop`. + pub(crate) fn report_diagnostic<'chk, T: Violation>( + &'chk self, + kind: T, + range: TextRange, + ) -> DiagnosticGuard<'chk, 'a> { + DiagnosticGuard { + context: self, + diagnostic: Some(OldDiagnostic::new(kind, range, self.source_file)), + } + } + + /// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is + /// enabled. + /// + /// Prefer [`DiagnosticsCollector::report_diagnostic`] in general because the conversion from an + /// `OldDiagnostic` to a `Rule` is somewhat expensive. + pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>( + &'chk self, + kind: T, + range: TextRange, + settings: &LinterSettings, + ) -> Option> { + let diagnostic = OldDiagnostic::new(kind, range, self.source_file); + if settings.rules.enabled(diagnostic.rule()) { + Some(DiagnosticGuard { + context: self, + diagnostic: Some(diagnostic), + }) + } else { + None + } + } + + pub(crate) fn into_diagnostics(self) -> Vec { + self.diagnostics.into_inner() + } + + pub(crate) fn is_empty(&self) -> bool { + self.diagnostics.borrow().is_empty() + } + + pub(crate) fn as_mut_vec(&mut self) -> &mut Vec { + self.diagnostics.get_mut() + } + + pub(crate) fn iter(&mut self) -> impl Iterator { + self.diagnostics.get_mut().iter() + } } /// An abstraction for mutating a diagnostic. @@ -3058,7 +3117,7 @@ pub(crate) fn check_ast( /// adding fixes or parent ranges. pub(crate) struct DiagnosticGuard<'a, 'b> { /// The parent checker that will receive the diagnostic on `Drop`. - checker: &'a Checker<'b>, + context: &'a LintContext<'b>, /// The diagnostic that we want to report. /// /// This is always `Some` until the `Drop` (or `defuse`) call. @@ -3100,7 +3159,7 @@ impl Drop for DiagnosticGuard<'_, '_> { } if let Some(diagnostic) = self.diagnostic.take() { - self.checker.diagnostics.borrow_mut().push(diagnostic); + self.context.diagnostics.borrow_mut().push(diagnostic); } } } diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index be09e345dac2e7..69c95a1dec9a12 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -4,7 +4,7 @@ use ruff_python_ast::PythonVersion; use ruff_python_trivia::CommentRanges; use crate::Locator; -use crate::OldDiagnostic; +use crate::checkers::ast::LintContext; use crate::package::PackageRoot; use crate::preview::is_allow_nested_roots_enabled; use crate::registry::Rule; @@ -20,13 +20,12 @@ pub(crate) fn check_file_path( comment_ranges: &CommentRanges, settings: &LinterSettings, target_version: PythonVersion, -) -> Vec { - let mut diagnostics: Vec = vec![]; - + context: &LintContext, +) { // flake8-no-pep420 if settings.rules.enabled(Rule::ImplicitNamespacePackage) { let allow_nested_roots = is_allow_nested_roots_enabled(settings); - if let Some(diagnostic) = implicit_namespace_package( + implicit_namespace_package( path, package, locator, @@ -34,26 +33,17 @@ pub(crate) fn check_file_path( &settings.project_root, &settings.src, allow_nested_roots, - ) { - diagnostics.push(diagnostic); - } + context, + ); } // pep8-naming if settings.rules.enabled(Rule::InvalidModuleName) { - if let Some(diagnostic) = - invalid_module_name(path, package, &settings.pep8_naming.ignore_names) - { - diagnostics.push(diagnostic); - } + invalid_module_name(path, package, &settings.pep8_naming.ignore_names, context); } // flake8-builtins if settings.rules.enabled(Rule::StdlibModuleShadowing) { - if let Some(diagnostic) = stdlib_module_shadowing(path, settings, target_version) { - diagnostics.push(diagnostic); - } + stdlib_module_shadowing(path, settings, target_version, context); } - - diagnostics } diff --git a/crates/ruff_linter/src/checkers/imports.rs b/crates/ruff_linter/src/checkers/imports.rs index 860061214221a6..d01249bcd4ae1c 100644 --- a/crates/ruff_linter/src/checkers/imports.rs +++ b/crates/ruff_linter/src/checkers/imports.rs @@ -8,7 +8,6 @@ use ruff_python_index::Indexer; use ruff_python_parser::Parsed; use crate::Locator; -use crate::OldDiagnostic; use crate::directives::IsortDirectives; use crate::package::PackageRoot; use crate::registry::Rule; @@ -16,6 +15,8 @@ use crate::rules::isort; use crate::rules::isort::block::{Block, BlockBuilder}; use crate::settings::LinterSettings; +use super::ast::LintContext; + #[expect(clippy::too_many_arguments)] pub(crate) fn check_imports( parsed: &Parsed, @@ -28,7 +29,8 @@ pub(crate) fn check_imports( source_type: PySourceType, cell_offsets: Option<&CellOffsets>, target_version: PythonVersion, -) -> Vec { + context: &LintContext, +) { // Extract all import blocks from the AST. let tracker = { let mut tracker = @@ -40,11 +42,10 @@ pub(crate) fn check_imports( let blocks: Vec<&Block> = tracker.iter().collect(); // Enforce import rules. - let mut diagnostics = vec![]; if settings.rules.enabled(Rule::UnsortedImports) { for block in &blocks { if !block.imports.is_empty() { - if let Some(diagnostic) = isort::rules::organize_imports( + isort::rules::organize_imports( block, locator, stylist, @@ -54,21 +55,19 @@ pub(crate) fn check_imports( source_type, parsed.tokens(), target_version, - ) { - diagnostics.push(diagnostic); - } + context, + ); } } } if settings.rules.enabled(Rule::MissingRequiredImport) { - diagnostics.extend(isort::rules::add_required_imports( + isort::rules::add_required_imports( parsed, locator, stylist, settings, source_type, - )); + context, + ); } - - diagnostics } diff --git a/crates/ruff_linter/src/checkers/logical_lines.rs b/crates/ruff_linter/src/checkers/logical_lines.rs index cabbb1ad8b5ea5..f6c31e2f446d4b 100644 --- a/crates/ruff_linter/src/checkers/logical_lines.rs +++ b/crates/ruff_linter/src/checkers/logical_lines.rs @@ -4,10 +4,8 @@ use ruff_python_parser::{TokenKind, Tokens}; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; -use crate::Locator; -use crate::OldDiagnostic; use crate::line_width::IndentWidth; -use crate::registry::{AsRule, Rule}; +use crate::registry::Rule; use crate::rules::pycodestyle::rules::logical_lines::{ LogicalLines, TokenFlags, extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword, missing_whitespace_around_operator, redundant_backslash, @@ -16,6 +14,9 @@ use crate::rules::pycodestyle::rules::logical_lines::{ whitespace_before_parameters, }; use crate::settings::LinterSettings; +use crate::{Locator, Violation}; + +use super::ast::{DiagnosticGuard, LintContext}; /// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size. pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize { @@ -40,8 +41,9 @@ pub(crate) fn check_logical_lines( indexer: &Indexer, stylist: &Stylist, settings: &LinterSettings, -) -> Vec { - let mut context = LogicalLinesContext::new(settings); + lint_context: &LintContext, +) { + let mut context = LogicalLinesContext::new(settings, lint_context); let mut prev_line = None; let mut prev_indent_level = None; @@ -170,7 +172,7 @@ pub(crate) fn check_logical_lines( let indent_size = 4; if enforce_indentation { - for diagnostic in indentation( + indentation( &line, prev_line.as_ref(), indent_char, @@ -178,11 +180,9 @@ pub(crate) fn check_logical_lines( prev_indent_level, indent_size, range, - ) { - if settings.rules.enabled(diagnostic.rule()) { - context.push_diagnostic(diagnostic); - } - } + lint_context, + settings, + ); } if !line.is_comment_only() { @@ -190,26 +190,24 @@ pub(crate) fn check_logical_lines( prev_indent_level = Some(indent_level); } } - context.diagnostics } -#[derive(Debug, Clone)] -pub(crate) struct LogicalLinesContext<'a> { +pub(crate) struct LogicalLinesContext<'a, 'b> { settings: &'a LinterSettings, - diagnostics: Vec, + context: &'a LintContext<'b>, } -impl<'a> LogicalLinesContext<'a> { - fn new(settings: &'a LinterSettings) -> Self { - Self { - settings, - diagnostics: Vec::new(), - } +impl<'a, 'b> LogicalLinesContext<'a, 'b> { + fn new(settings: &'a LinterSettings, context: &'a LintContext<'b>) -> Self { + Self { settings, context } } - pub(crate) fn push_diagnostic(&mut self, diagnostic: OldDiagnostic) { - if self.settings.rules.enabled(diagnostic.rule()) { - self.diagnostics.push(diagnostic); - } + pub(crate) fn report_diagnostic<'chk, T: Violation>( + &'chk self, + kind: T, + range: TextRange, + ) -> Option> { + self.context + .report_diagnostic_if_enabled(kind, range, self.settings) } } diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 09d34b25c5f666..d87e4343a96e82 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -8,7 +8,6 @@ use rustc_hash::FxHashSet; use ruff_python_trivia::CommentRanges; use ruff_text_size::Ranged; -use crate::Locator; use crate::fix::edits::delete_comment; use crate::noqa::{ Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping, @@ -20,11 +19,13 @@ use crate::rules::pygrep_hooks; use crate::rules::ruff; use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA}; use crate::settings::LinterSettings; -use crate::{Edit, Fix, OldDiagnostic}; +use crate::{Edit, Fix, Locator}; + +use super::ast::LintContext; #[expect(clippy::too_many_arguments)] pub(crate) fn check_noqa( - diagnostics: &mut Vec, + context: &mut LintContext, path: &Path, locator: &Locator, comment_ranges: &CommentRanges, @@ -46,7 +47,7 @@ pub(crate) fn check_noqa( let mut ignored_diagnostics = vec![]; // Remove any ignored diagnostics. - 'outer: for (index, diagnostic) in diagnostics.iter().enumerate() { + 'outer: for (index, diagnostic) in context.iter().enumerate() { let rule = diagnostic.rule(); if matches!(rule, Rule::BlanketNOQA) { @@ -135,11 +136,9 @@ pub(crate) fn check_noqa( Directive::All(directive) => { if matches.is_empty() { let edit = delete_comment(directive.range(), locator); - let mut diagnostic = - OldDiagnostic::new(UnusedNOQA { codes: None }, directive.range()); + let mut diagnostic = context + .report_diagnostic(UnusedNOQA { codes: None }, directive.range()); diagnostic.set_fix(Fix::safe_edit(edit)); - - diagnostics.push(diagnostic); } } Directive::Codes(directive) => { @@ -159,9 +158,7 @@ pub(crate) fn check_noqa( if seen_codes.insert(original_code) { let is_code_used = if is_file_level { - diagnostics - .iter() - .any(|diag| diag.rule().noqa_code() == code) + context.iter().any(|diag| diag.rule().noqa_code() == code) } else { matches.iter().any(|match_| *match_ == code) } || settings @@ -212,7 +209,7 @@ pub(crate) fn check_noqa( directive.range(), ) }; - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( UnusedNOQA { codes: Some(UnusedCodes { disabled: disabled_codes @@ -236,7 +233,6 @@ pub(crate) fn check_noqa( directive.range(), ); diagnostic.set_fix(Fix::safe_edit(edit)); - diagnostics.push(diagnostic); } } } @@ -247,8 +243,8 @@ pub(crate) fn check_noqa( && !per_file_ignores.contains(Rule::RedirectedNOQA) && !exemption.includes(Rule::RedirectedNOQA) { - ruff::rules::redirected_noqa(diagnostics, &noqa_directives); - ruff::rules::redirected_file_noqa(diagnostics, &file_noqa_directives); + ruff::rules::redirected_noqa(context, &noqa_directives); + ruff::rules::redirected_file_noqa(context, &file_noqa_directives); } if settings.rules.enabled(Rule::BlanketNOQA) @@ -256,7 +252,7 @@ pub(crate) fn check_noqa( && !exemption.enumerates(Rule::BlanketNOQA) { pygrep_hooks::rules::blanket_noqa( - diagnostics, + context, &noqa_directives, locator, &file_noqa_directives, @@ -267,7 +263,7 @@ pub(crate) fn check_noqa( && !per_file_ignores.contains(Rule::InvalidRuleCode) && !exemption.enumerates(Rule::InvalidRuleCode) { - ruff::rules::invalid_noqa_code(diagnostics, &noqa_directives, locator, &settings.external); + ruff::rules::invalid_noqa_code(context, &noqa_directives, locator, &settings.external); } ignored_diagnostics.sort_unstable(); diff --git a/crates/ruff_linter/src/checkers/physical_lines.rs b/crates/ruff_linter/src/checkers/physical_lines.rs index 1edfed656a2138..6bdf97e34b375b 100644 --- a/crates/ruff_linter/src/checkers/physical_lines.rs +++ b/crates/ruff_linter/src/checkers/physical_lines.rs @@ -6,7 +6,6 @@ use ruff_source_file::UniversalNewlines; use ruff_text_size::TextSize; use crate::Locator; -use crate::OldDiagnostic; use crate::registry::Rule; use crate::rules::flake8_copyright::rules::missing_copyright_notice; use crate::rules::pycodestyle::rules::{ @@ -17,15 +16,16 @@ use crate::rules::pylint; use crate::rules::ruff::rules::indented_form_feed; use crate::settings::LinterSettings; +use super::ast::LintContext; + pub(crate) fn check_physical_lines( locator: &Locator, stylist: &Stylist, indexer: &Indexer, doc_lines: &[TextSize], settings: &LinterSettings, -) -> Vec { - let mut diagnostics: Vec = vec![]; - + context: &LintContext, +) { let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong); let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong); let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile); @@ -45,54 +45,38 @@ pub(crate) fn check_physical_lines( .is_some() { if enforce_doc_line_too_long { - if let Some(diagnostic) = doc_line_too_long(&line, comment_ranges, settings) { - diagnostics.push(diagnostic); - } + doc_line_too_long(&line, comment_ranges, settings, context); } } if enforce_mixed_spaces_and_tabs { - if let Some(diagnostic) = mixed_spaces_and_tabs(&line) { - diagnostics.push(diagnostic); - } + mixed_spaces_and_tabs(&line, context); } if enforce_line_too_long { - if let Some(diagnostic) = line_too_long(&line, comment_ranges, settings) { - diagnostics.push(diagnostic); - } + line_too_long(&line, comment_ranges, settings, context); } if enforce_bidirectional_unicode { - diagnostics.extend(pylint::rules::bidirectional_unicode(&line)); + pylint::rules::bidirectional_unicode(&line, context); } if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace { - if let Some(diagnostic) = trailing_whitespace(&line, locator, indexer, settings) { - diagnostics.push(diagnostic); - } + trailing_whitespace(&line, locator, indexer, settings, context); } if settings.rules.enabled(Rule::IndentedFormFeed) { - if let Some(diagnostic) = indented_form_feed(&line) { - diagnostics.push(diagnostic); - } + indented_form_feed(&line, context); } } if enforce_no_newline_at_end_of_file { - if let Some(diagnostic) = no_newline_at_end_of_file(locator, stylist) { - diagnostics.push(diagnostic); - } + no_newline_at_end_of_file(locator, stylist, context); } if enforce_copyright_notice { - if let Some(diagnostic) = missing_copyright_notice(locator, settings) { - diagnostics.push(diagnostic); - } + missing_copyright_notice(locator, settings, context); } - - diagnostics } #[cfg(test)] @@ -100,8 +84,10 @@ mod tests { use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::parse_module; + use ruff_source_file::SourceFileBuilder; use crate::Locator; + use crate::checkers::ast::LintContext; use crate::line_width::LineLength; use crate::registry::Rule; use crate::rules::pycodestyle; @@ -118,6 +104,8 @@ mod tests { let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); let check_with_max_line_length = |line_length: LineLength| { + let source_file = SourceFileBuilder::new("", line).finish(); + let diagnostics = LintContext::new(&source_file); check_physical_lines( &locator, &stylist, @@ -130,7 +118,9 @@ mod tests { }, ..LinterSettings::for_rule(Rule::LineTooLong) }, - ) + &diagnostics, + ); + diagnostics.into_diagnostics() }; let line_length = LineLength::try_from(8).unwrap(); assert_eq!(check_with_max_line_length(line_length), vec![]); diff --git a/crates/ruff_linter/src/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index 0ea5973175d8fc..63eaa882ab4e5f 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -9,7 +9,6 @@ use ruff_python_index::Indexer; use ruff_python_parser::Tokens; use crate::Locator; -use crate::OldDiagnostic; use crate::directives::TodoComment; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle::rules::BlankLinesChecker; @@ -19,6 +18,8 @@ use crate::rules::{ }; use crate::settings::LinterSettings; +use super::ast::LintContext; + #[expect(clippy::too_many_arguments)] pub(crate) fn check_tokens( tokens: &Tokens, @@ -29,8 +30,8 @@ pub(crate) fn check_tokens( settings: &LinterSettings, source_type: PySourceType, cell_offsets: Option<&CellOffsets>, -) -> Vec { - let mut diagnostics: Vec = vec![]; + context: &mut LintContext, +) { let comment_ranges = indexer.comment_ranges(); if settings.rules.any_enabled(&[ @@ -41,16 +42,23 @@ pub(crate) fn check_tokens( Rule::BlankLinesAfterFunctionOrClass, Rule::BlankLinesBeforeNestedDefinition, ]) { - BlankLinesChecker::new(locator, stylist, settings, source_type, cell_offsets) - .check_lines(tokens, &mut diagnostics); + BlankLinesChecker::new( + locator, + stylist, + settings, + source_type, + cell_offsets, + context, + ) + .check_lines(tokens); } if settings.rules.enabled(Rule::BlanketTypeIgnore) { - pygrep_hooks::rules::blanket_type_ignore(&mut diagnostics, comment_ranges, locator); + pygrep_hooks::rules::blanket_type_ignore(context, comment_ranges, locator); } if settings.rules.enabled(Rule::EmptyComment) { - pylint::rules::empty_comments(&mut diagnostics, comment_ranges, locator); + pylint::rules::empty_comments(context, comment_ranges, locator); } if settings @@ -58,25 +66,20 @@ pub(crate) fn check_tokens( .enabled(Rule::AmbiguousUnicodeCharacterComment) { for range in comment_ranges { - ruff::rules::ambiguous_unicode_character_comment( - &mut diagnostics, - locator, - range, - settings, - ); + ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings); } } if settings.rules.enabled(Rule::CommentedOutCode) { - eradicate::rules::commented_out_code(&mut diagnostics, locator, comment_ranges, settings); + eradicate::rules::commented_out_code(context, locator, comment_ranges, settings); } if settings.rules.enabled(Rule::UTF8EncodingDeclaration) { - pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, comment_ranges); + pyupgrade::rules::unnecessary_coding_comment(context, locator, comment_ranges); } if settings.rules.enabled(Rule::TabIndentation) { - pycodestyle::rules::tab_indentation(&mut diagnostics, locator, indexer); + pycodestyle::rules::tab_indentation(context, locator, indexer); } if settings.rules.any_enabled(&[ @@ -87,7 +90,7 @@ pub(crate) fn check_tokens( Rule::InvalidCharacterZeroWidthSpace, ]) { for token in tokens { - pylint::rules::invalid_string_characters(&mut diagnostics, token, locator); + pylint::rules::invalid_string_characters(context, token, locator); } } @@ -97,7 +100,7 @@ pub(crate) fn check_tokens( Rule::UselessSemicolon, ]) { pycodestyle::rules::compound_statements( - &mut diagnostics, + context, tokens, locator, indexer, @@ -110,13 +113,7 @@ pub(crate) fn check_tokens( Rule::SingleLineImplicitStringConcatenation, Rule::MultiLineImplicitStringConcatenation, ]) { - flake8_implicit_str_concat::rules::implicit( - &mut diagnostics, - tokens, - locator, - indexer, - settings, - ); + flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings); } if settings.rules.any_enabled(&[ @@ -124,15 +121,15 @@ pub(crate) fn check_tokens( Rule::TrailingCommaOnBareTuple, Rule::ProhibitedTrailingComma, ]) { - flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, indexer); + flake8_commas::rules::trailing_commas(context, tokens, locator, indexer); } if settings.rules.enabled(Rule::ExtraneousParentheses) { - pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator); + pyupgrade::rules::extraneous_parentheses(context, tokens, locator); } if source_type.is_stub() && settings.rules.enabled(Rule::TypeCommentInStub) { - flake8_pyi::rules::type_comment_in_stub(&mut diagnostics, locator, comment_ranges); + flake8_pyi::rules::type_comment_in_stub(context, locator, comment_ranges); } if settings.rules.any_enabled(&[ @@ -142,13 +139,7 @@ pub(crate) fn check_tokens( Rule::ShebangNotFirstLine, Rule::ShebangMissingPython, ]) { - flake8_executable::rules::from_tokens( - &mut diagnostics, - path, - locator, - comment_ranges, - settings, - ); + flake8_executable::rules::from_tokens(context, path, locator, comment_ranges, settings); } if settings.rules.any_enabled(&[ @@ -172,19 +163,15 @@ pub(crate) fn check_tokens( TodoComment::from_comment(comment, *comment_range, i) }) .collect(); - flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, comment_ranges); - flake8_fixme::rules::todos(&mut diagnostics, &todo_comments); + flake8_todos::rules::todos(context, &todo_comments, locator, comment_ranges); + flake8_fixme::rules::todos(context, &todo_comments); } if settings.rules.enabled(Rule::TooManyNewlinesAtEndOfFile) { - pycodestyle::rules::too_many_newlines_at_end_of_file( - &mut diagnostics, - tokens, - cell_offsets, - ); + pycodestyle::rules::too_many_newlines_at_end_of_file(context, tokens, cell_offsets); } - diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.rule())); - - diagnostics + context + .as_mut_vec() + .retain(|diagnostic| settings.rules.enabled(diagnostic.rule())); } diff --git a/crates/ruff_linter/src/diagnostic.rs b/crates/ruff_linter/src/diagnostic.rs index 68dc05734b62e6..8ae3f7c18cff1d 100644 --- a/crates/ruff_linter/src/diagnostic.rs +++ b/crates/ruff_linter/src/diagnostic.rs @@ -1,6 +1,7 @@ use anyhow::Result; use log::debug; +use ruff_source_file::SourceFile; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::registry::AsRule; @@ -18,6 +19,8 @@ pub struct OldDiagnostic { pub parent: Option, pub(crate) rule: Rule, + + pub(crate) file: SourceFile, } impl OldDiagnostic { @@ -26,7 +29,7 @@ impl OldDiagnostic { // diagnostic refactor, but if it still exists in this form at the end of the refactor, we // should just update the call sites. #[expect(clippy::needless_pass_by_value)] - pub fn new(kind: T, range: TextRange) -> Self { + pub fn new(kind: T, range: TextRange, file: &SourceFile) -> Self { Self { body: Violation::message(&kind), suggestion: Violation::fix_title(&kind), @@ -34,6 +37,7 @@ impl OldDiagnostic { fix: None, parent: None, rule: T::rule(), + file: file.clone(), } } diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index fc51bdcebbd8a1..7d324380229850 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -600,14 +600,12 @@ mod tests { use ruff_python_parser::{parse_expression, parse_module}; use ruff_text_size::{Ranged, TextRange, TextSize}; - use crate::Locator; - use crate::codes::Rule; use crate::fix::apply_fixes; use crate::fix::edits::{ add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; use crate::message::Message; - use crate::{Edit, Fix, OldDiagnostic}; + use crate::{Edit, Fix, Locator, OldDiagnostic}; /// Parse the given source using [`Mode::Module`] and return the first statement. fn parse_first_stmt(source: &str) -> Result { @@ -741,21 +739,13 @@ x = 1 \ let diag = OldDiagnostic::new( MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary. TextRange::default(), + &SourceFileBuilder::new("", "").finish(), ) .with_fix(Fix::safe_edits( iter.next().ok_or(anyhow!("expected edits nonempty"))?, iter, )); - Message::diagnostic( - diag.body, - diag.suggestion, - diag.range, - diag.fix, - diag.parent, - SourceFileBuilder::new("", "").finish(), - None, - Rule::MissingNewlineAtEndOfFile, - ) + Message::from_diagnostic(diag, None) }; assert_eq!(apply_fixes([diag].iter(), &locator).code, expect); Ok(()) diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index 261893e4408a35..ae77973ed7f99b 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -177,12 +177,12 @@ mod tests { edit.into_iter() .map(|edit| { // The choice of rule here is arbitrary. - let diagnostic = OldDiagnostic::new(MissingNewlineAtEndOfFile, edit.range()); - Message::from_diagnostic( - diagnostic.with_fix(Fix::safe_edit(edit)), - SourceFileBuilder::new(filename, source).finish(), - None, - ) + let diagnostic = OldDiagnostic::new( + MissingNewlineAtEndOfFile, + edit.range(), + &SourceFileBuilder::new(filename, source).finish(), + ); + Message::from_diagnostic(diagnostic.with_fix(Fix::safe_edit(edit)), None) }) .collect() } diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index cab0b25cbc56f6..99e35681ec6ce7 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -1,6 +1,4 @@ use std::borrow::Cow; -use std::cell::LazyCell; -use std::ops::Deref; use std::path::Path; use anyhow::{Result, anyhow}; @@ -14,11 +12,11 @@ use ruff_python_ast::{ModModule, PySourceType, PythonVersion}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::{ParseError, ParseOptions, Parsed, UnsupportedSyntaxError}; -use ruff_source_file::SourceFileBuilder; +use ruff_source_file::{SourceFile, SourceFileBuilder}; use ruff_text_size::Ranged; use crate::OldDiagnostic; -use crate::checkers::ast::check_ast; +use crate::checkers::ast::{LintContext, check_ast}; use crate::checkers::filesystem::check_file_path; use crate::checkers::imports::check_imports; use crate::checkers::noqa::check_noqa; @@ -113,8 +111,11 @@ pub fn check_path( parsed: &Parsed, target_version: TargetVersion, ) -> Vec { + let source_file = + SourceFileBuilder::new(path.to_string_lossy().as_ref(), locator.contents()).finish(); + // Aggregate all diagnostics. - let mut diagnostics = vec![]; + let mut diagnostics = LintContext::new(&source_file); // Aggregate all semantic syntax errors. let mut semantic_syntax_errors = vec![]; @@ -136,7 +137,7 @@ pub fn check_path( .iter_enabled() .any(|rule_code| rule_code.lint_source().is_tokens()) { - diagnostics.extend(check_tokens( + check_tokens( tokens, path, locator, @@ -145,7 +146,8 @@ pub fn check_path( settings, source_type, source_kind.as_ipy_notebook().map(Notebook::cell_offsets), - )); + &mut diagnostics, + ); } // Run the filesystem-based rules. @@ -154,14 +156,15 @@ pub fn check_path( .iter_enabled() .any(|rule_code| rule_code.lint_source().is_filesystem()) { - diagnostics.extend(check_file_path( + check_file_path( path, package, locator, comment_ranges, settings, target_version.linter_version(), - )); + &diagnostics, + ); } // Run the logical line-based rules. @@ -170,9 +173,14 @@ pub fn check_path( .iter_enabled() .any(|rule_code| rule_code.lint_source().is_logical_lines()) { - diagnostics.extend(crate::checkers::logical_lines::check_logical_lines( - tokens, locator, indexer, stylist, settings, - )); + crate::checkers::logical_lines::check_logical_lines( + tokens, + locator, + indexer, + stylist, + settings, + &diagnostics, + ); } // Run the AST-based rules only if there are no syntax errors. @@ -180,7 +188,7 @@ pub fn check_path( let cell_offsets = source_kind.as_ipy_notebook().map(Notebook::cell_offsets); let notebook_index = source_kind.as_ipy_notebook().map(Notebook::index); - let (new_diagnostics, new_semantic_syntax_errors) = check_ast( + semantic_syntax_errors.extend(check_ast( parsed, locator, stylist, @@ -194,9 +202,8 @@ pub fn check_path( cell_offsets, notebook_index, target_version, - ); - diagnostics.extend(new_diagnostics); - semantic_syntax_errors.extend(new_semantic_syntax_errors); + &diagnostics, + )); let use_imports = !directives.isort.skip_file && settings @@ -205,7 +212,7 @@ pub fn check_path( .any(|rule_code| rule_code.lint_source().is_imports()); if use_imports || use_doc_lines { if use_imports { - let import_diagnostics = check_imports( + check_imports( parsed, locator, indexer, @@ -216,9 +223,8 @@ pub fn check_path( source_type, cell_offsets, target_version.linter_version(), + &diagnostics, ); - - diagnostics.extend(import_diagnostics); } if use_doc_lines { doc_lines.extend(doc_lines_from_ast(parsed.suite(), locator)); @@ -238,9 +244,14 @@ pub fn check_path( .iter_enabled() .any(|rule_code| rule_code.lint_source().is_physical_lines()) { - diagnostics.extend(check_physical_lines( - locator, stylist, indexer, &doc_lines, settings, - )); + check_physical_lines( + locator, + stylist, + indexer, + &doc_lines, + settings, + &diagnostics, + ); } // Raise violations for internal test rules @@ -250,47 +261,70 @@ pub fn check_path( if !settings.rules.enabled(*test_rule) { continue; } - let diagnostic = match test_rule { + match test_rule { Rule::StableTestRule => { - test_rules::StableTestRule::diagnostic(locator, comment_ranges) - } - Rule::StableTestRuleSafeFix => { - test_rules::StableTestRuleSafeFix::diagnostic(locator, comment_ranges) - } - Rule::StableTestRuleUnsafeFix => { - test_rules::StableTestRuleUnsafeFix::diagnostic(locator, comment_ranges) + test_rules::StableTestRule::diagnostic(locator, comment_ranges, &diagnostics); } + Rule::StableTestRuleSafeFix => test_rules::StableTestRuleSafeFix::diagnostic( + locator, + comment_ranges, + &diagnostics, + ), + Rule::StableTestRuleUnsafeFix => test_rules::StableTestRuleUnsafeFix::diagnostic( + locator, + comment_ranges, + &diagnostics, + ), Rule::StableTestRuleDisplayOnlyFix => { - test_rules::StableTestRuleDisplayOnlyFix::diagnostic(locator, comment_ranges) + test_rules::StableTestRuleDisplayOnlyFix::diagnostic( + locator, + comment_ranges, + &diagnostics, + ); } Rule::PreviewTestRule => { - test_rules::PreviewTestRule::diagnostic(locator, comment_ranges) + test_rules::PreviewTestRule::diagnostic(locator, comment_ranges, &diagnostics); } Rule::DeprecatedTestRule => { - test_rules::DeprecatedTestRule::diagnostic(locator, comment_ranges) + test_rules::DeprecatedTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ); } Rule::AnotherDeprecatedTestRule => { - test_rules::AnotherDeprecatedTestRule::diagnostic(locator, comment_ranges) + test_rules::AnotherDeprecatedTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ); } Rule::RemovedTestRule => { - test_rules::RemovedTestRule::diagnostic(locator, comment_ranges) - } - Rule::AnotherRemovedTestRule => { - test_rules::AnotherRemovedTestRule::diagnostic(locator, comment_ranges) - } - Rule::RedirectedToTestRule => { - test_rules::RedirectedToTestRule::diagnostic(locator, comment_ranges) - } - Rule::RedirectedFromTestRule => { - test_rules::RedirectedFromTestRule::diagnostic(locator, comment_ranges) + test_rules::RemovedTestRule::diagnostic(locator, comment_ranges, &diagnostics); } + Rule::AnotherRemovedTestRule => test_rules::AnotherRemovedTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ), + Rule::RedirectedToTestRule => test_rules::RedirectedToTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ), + Rule::RedirectedFromTestRule => test_rules::RedirectedFromTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ), Rule::RedirectedFromPrefixTestRule => { - test_rules::RedirectedFromPrefixTestRule::diagnostic(locator, comment_ranges) + test_rules::RedirectedFromPrefixTestRule::diagnostic( + locator, + comment_ranges, + &diagnostics, + ); } _ => unreachable!("All test rules must have an implementation"), - }; - if let Some(diagnostic) = diagnostic { - diagnostics.push(diagnostic); } } } @@ -308,7 +342,9 @@ pub fn check_path( RuleSet::empty() }; if !per_file_ignores.is_empty() { - diagnostics.retain(|diagnostic| !per_file_ignores.contains(diagnostic.rule())); + diagnostics + .as_mut_vec() + .retain(|diagnostic| !per_file_ignores.contains(diagnostic.rule())); } // Enforce `noqa` directives. @@ -330,11 +366,13 @@ pub fn check_path( ); if noqa.is_enabled() { for index in ignored.iter().rev() { - diagnostics.swap_remove(*index); + diagnostics.as_mut_vec().swap_remove(*index); } } } + let mut diagnostics = diagnostics.into_diagnostics(); + if parsed.has_valid_syntax() { // Remove fixes for any rules marked as unfixable. for diagnostic in &mut diagnostics { @@ -372,9 +410,9 @@ pub fn check_path( parsed.errors(), syntax_errors, &semantic_syntax_errors, - path, locator, directives, + &source_file, ) } @@ -507,35 +545,24 @@ fn diagnostics_to_messages( parse_errors: &[ParseError], unsupported_syntax_errors: &[UnsupportedSyntaxError], semantic_syntax_errors: &[SemanticSyntaxError], - path: &Path, locator: &Locator, directives: &Directives, + source_file: &SourceFile, ) -> Vec { - let file = LazyCell::new(|| { - let mut builder = - SourceFileBuilder::new(path.to_string_lossy().as_ref(), locator.contents()); - - if let Some(line_index) = locator.line_index() { - builder.set_line_index(line_index.clone()); - } - - builder.finish() - }); - parse_errors .iter() - .map(|parse_error| Message::from_parse_error(parse_error, locator, file.deref().clone())) + .map(|parse_error| Message::from_parse_error(parse_error, locator, source_file.clone())) .chain(unsupported_syntax_errors.iter().map(|syntax_error| { - Message::from_unsupported_syntax_error(syntax_error, file.deref().clone()) + Message::from_unsupported_syntax_error(syntax_error, source_file.clone()) })) .chain( semantic_syntax_errors .iter() - .map(|error| Message::from_semantic_syntax_error(error, file.deref().clone())), + .map(|error| Message::from_semantic_syntax_error(error, source_file.clone())), ) .chain(diagnostics.into_iter().map(|diagnostic| { let noqa_offset = directives.noqa_line_for.resolve(diagnostic.start()); - Message::from_diagnostic(diagnostic, file.deref().clone(), Some(noqa_offset)) + Message::from_diagnostic(diagnostic, Some(noqa_offset)) })) .collect() } diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 74eef57f247a54..49bd23cd1ebbf6 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -114,11 +114,7 @@ impl Message { } /// Create a [`Message`] from the given [`OldDiagnostic`] corresponding to a rule violation. - pub fn from_diagnostic( - diagnostic: OldDiagnostic, - file: SourceFile, - noqa_offset: Option, - ) -> Message { + pub fn from_diagnostic(diagnostic: OldDiagnostic, noqa_offset: Option) -> Message { let OldDiagnostic { body, suggestion, @@ -126,6 +122,7 @@ impl Message { fix, parent, rule, + file, } = diagnostic; Self::diagnostic( body, diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index d09aad4fc22a00..a7ff0c0c45b926 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1252,14 +1252,9 @@ mod tests { } /// Create a [`Message`] with a placeholder filename and rule code from `diagnostic`. - fn message_from_diagnostic( - diagnostic: OldDiagnostic, - path: impl AsRef, - source: &str, - ) -> Message { + fn message_from_diagnostic(diagnostic: OldDiagnostic) -> Message { let noqa_offset = diagnostic.start(); - let file = SourceFileBuilder::new(path.as_ref().to_string_lossy(), source).finish(); - Message::from_diagnostic(diagnostic, file, Some(noqa_offset)) + Message::from_diagnostic(diagnostic, Some(noqa_offset)) } #[test] @@ -2842,13 +2837,15 @@ mod tests { assert_eq!(count, 0); assert_eq!(output, format!("{contents}")); + let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); let messages = [OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, TextRange::new(TextSize::from(0), TextSize::from(0)), + &source_file, )] - .map(|d| message_from_diagnostic(d, path, contents)); + .map(message_from_diagnostic); let contents = "x = 1"; let noqa_line_for = NoqaMapping::default(); @@ -2864,19 +2861,22 @@ mod tests { assert_eq!(count, 1); assert_eq!(output, "x = 1 # noqa: F841\n"); + let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); let messages = [ OldDiagnostic::new( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), + &source_file, ), OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, TextRange::new(TextSize::from(0), TextSize::from(0)), + &source_file, ), ] - .map(|d| message_from_diagnostic(d, path, contents)); + .map(message_from_diagnostic); let contents = "x = 1 # noqa: E741\n"; let noqa_line_for = NoqaMapping::default(); let comment_ranges = @@ -2893,19 +2893,22 @@ mod tests { assert_eq!(count, 1); assert_eq!(output, "x = 1 # noqa: E741, F841\n"); + let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); let messages = [ OldDiagnostic::new( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), + &source_file, ), OldDiagnostic::new( UnusedVariable { name: "x".to_string(), }, TextRange::new(TextSize::from(0), TextSize::from(0)), + &source_file, ), ] - .map(|d| message_from_diagnostic(d, path, contents)); + .map(message_from_diagnostic); let contents = "x = 1 # noqa"; let noqa_line_for = NoqaMapping::default(); let comment_ranges = @@ -2936,11 +2939,13 @@ print( ) "#; let noqa_line_for = [TextRange::new(8.into(), 68.into())].into_iter().collect(); + let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish(); let messages = [OldDiagnostic::new( PrintfStringFormatting, TextRange::new(12.into(), 79.into()), + &source_file, )] - .map(|d| message_from_diagnostic(d, path, source)); + .map(message_from_diagnostic); let comment_ranges = CommentRanges::default(); let edits = generate_noqa_edits( path, @@ -2968,11 +2973,13 @@ print( foo; bar = "; + let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish(); let messages = [OldDiagnostic::new( UselessSemicolon, TextRange::new(4.into(), 5.into()), + &source_file, )] - .map(|d| message_from_diagnostic(d, path, source)); + .map(message_from_diagnostic); let noqa_line_for = NoqaMapping::default(); let comment_ranges = CommentRanges::default(); let edits = generate_noqa_edits( diff --git a/crates/ruff_linter/src/pyproject_toml.rs b/crates/ruff_linter/src/pyproject_toml.rs index d7021615351c0e..b9e3c51b15891d 100644 --- a/crates/ruff_linter/src/pyproject_toml.rs +++ b/crates/ruff_linter/src/pyproject_toml.rs @@ -12,7 +12,7 @@ use crate::registry::Rule; use crate::rules::ruff::rules::InvalidPyprojectToml; use crate::settings::LinterSettings; -pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) -> Vec { +pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings) -> Vec { let Some(err) = toml::from_str::(source_file.source_text()).err() else { return Vec::default(); }; @@ -29,8 +29,9 @@ pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) - source_file.name(), ); if settings.rules.enabled(Rule::IOError) { - let diagnostic = OldDiagnostic::new(IOError { message }, TextRange::default()); - messages.push(Message::from_diagnostic(diagnostic, source_file, None)); + let diagnostic = + OldDiagnostic::new(IOError { message }, TextRange::default(), source_file); + messages.push(Message::from_diagnostic(diagnostic, None)); } else { warn!( "{}{}{} {message}", @@ -51,8 +52,12 @@ pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) - if settings.rules.enabled(Rule::InvalidPyprojectToml) { let toml_err = err.message().to_string(); - let diagnostic = OldDiagnostic::new(InvalidPyprojectToml { message: toml_err }, range); - messages.push(Message::from_diagnostic(diagnostic, source_file, None)); + let diagnostic = OldDiagnostic::new( + InvalidPyprojectToml { message: toml_err }, + range, + source_file, + ); + messages.push(Message::from_diagnostic(diagnostic, None)); } messages diff --git a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs index 118b28207bb8db..2165b6c704bdff 100644 --- a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs +++ b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs @@ -4,8 +4,9 @@ use ruff_source_file::{LineRanges, UniversalNewlineIterator}; use ruff_text_size::TextRange; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::settings::LinterSettings; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; use super::super::detection::comment_contains_code; @@ -47,7 +48,7 @@ impl Violation for CommentedOutCode { /// ERA001 pub(crate) fn commented_out_code( - diagnostics: &mut Vec, + context: &LintContext, locator: &Locator, comment_ranges: &CommentRanges, settings: &LinterSettings, @@ -65,11 +66,11 @@ pub(crate) fn commented_out_code( // Verify that the comment is on its own line, and that it contains code. if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) { - let mut diagnostic = OldDiagnostic::new(CommentedOutCode, range); - diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion( - locator.full_lines_range(range), - ))); - diagnostics.push(diagnostic); + context + .report_diagnostic(CommentedOutCode, range) + .set_fix(Fix::display_only_edit(Edit::range_deletion( + locator.full_lines_range(range), + ))); } } } diff --git a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs index cdb8d31f78509c..a83206cdc18e14 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs @@ -533,7 +533,7 @@ fn check_dynamically_typed<'a, 'b, F>( checker: &'a Checker<'b>, annotation: &Expr, func: F, - diagnostics: &mut Vec>, + context: &mut Vec>, ) where F: FnOnce() -> String, { @@ -545,14 +545,13 @@ fn check_dynamically_typed<'a, 'b, F>( checker, checker.target_version(), ) { - diagnostics + context .push(checker.report_diagnostic(AnyType { name: func() }, annotation.range())); } } } else { if type_hint_resolves_to_any(annotation, checker, checker.target_version()) { - diagnostics - .push(checker.report_diagnostic(AnyType { name: func() }, annotation.range())); + context.push(checker.report_diagnostic(AnyType { name: func() }, annotation.range())); } } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs index 4246e4edc45c18..66f9e6f657c986 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/stdlib_module_shadowing.rs @@ -7,8 +7,9 @@ use ruff_python_stdlib::path::is_module_file; use ruff_python_stdlib::sys::is_known_standard_library; use ruff_text_size::TextRange; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::settings::LinterSettings; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for modules that use the same names as Python standard-library @@ -69,9 +70,10 @@ pub(crate) fn stdlib_module_shadowing( mut path: &Path, settings: &LinterSettings, target_version: PythonVersion, -) -> Option { + context: &LintContext, +) { if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file) { - return None; + return; } // strip src and root prefixes before converting to a fully-qualified module path @@ -83,7 +85,8 @@ pub(crate) fn stdlib_module_shadowing( // for modules like `modname/__init__.py`, use the parent directory name, otherwise just trim // the `.py` extension let path = if is_module_file(path) { - Cow::from(path.parent()?) + let Some(parent) = path.parent() else { return }; + Cow::from(parent) } else { Cow::from(path.with_extension("")) }; @@ -96,23 +99,25 @@ pub(crate) fn stdlib_module_shadowing( .map(|c| c.as_os_str().to_string_lossy()) .rev(); - let module_name = components.next()?; + let Some(module_name) = components.next() else { + return; + }; if is_allowed_module(settings, target_version, &module_name) { - return None; + return; } // not allowed generally, but check for a parent in non-strict mode if !settings.flake8_builtins.strict_checking && components.next().is_some() { - return None; + return; } - Some(OldDiagnostic::new( + context.report_diagnostic( StdlibModuleShadowing { name: module_name.to_string(), }, TextRange::default(), - )) + ); } /// Return the longest prefix of `path` between `settings.src` and `settings.project_root`. diff --git a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs index e176bdc67b8894..6b2dc0fb51386b 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs +++ b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs @@ -4,8 +4,9 @@ use ruff_python_parser::{TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::{AlwaysFixableViolation, Violation}; -use crate::{Edit, Fix, OldDiagnostic}; +use crate::{Edit, Fix}; /// Simplified token type. #[derive(Copy, Clone, PartialEq, Eq)] @@ -238,7 +239,7 @@ impl AlwaysFixableViolation for ProhibitedTrailingComma { /// COM812, COM818, COM819 pub(crate) fn trailing_commas( - diagnostics: &mut Vec, + lint_context: &LintContext, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -291,9 +292,7 @@ pub(crate) fn trailing_commas( // Update the comma context stack. let context = update_context(token, prev, prev_prev, &mut stack); - if let Some(diagnostic) = check_token(token, prev, prev_prev, context, locator) { - diagnostics.push(diagnostic); - } + check_token(token, prev, prev_prev, context, locator, lint_context); // Pop the current context if the current token ended it. // The top context is never popped (if unbalanced closing brackets). @@ -319,7 +318,8 @@ fn check_token( prev_prev: SimpleToken, context: Context, locator: &Locator, -) -> Option { + lint_context: &LintContext, +) { // Is it allowed to have a trailing comma before this token? let comma_allowed = token.ty == TokenType::ClosingBracket && match context.ty { @@ -352,20 +352,22 @@ fn check_token( }; if comma_prohibited { - let mut diagnostic = OldDiagnostic::new(ProhibitedTrailingComma, prev.range()); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range()))); - return Some(diagnostic); + let mut diagnostic = lint_context.report_diagnostic(ProhibitedTrailingComma, prev.range()); + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); + return; } // Is prev a prohibited trailing comma on a bare tuple? // Approximation: any comma followed by a statement-ending newline. let bare_comma_prohibited = prev.ty == TokenType::Comma && token.ty == TokenType::Newline; if bare_comma_prohibited { - return Some(OldDiagnostic::new(TrailingCommaOnBareTuple, prev.range())); + lint_context.report_diagnostic(TrailingCommaOnBareTuple, prev.range()); + return; } if !comma_allowed { - return None; + return; } // Comma is required if: @@ -383,7 +385,7 @@ fn check_token( ); if comma_required { let mut diagnostic = - OldDiagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end())); + lint_context.report_diagnostic(MissingTrailingComma, TextRange::empty(prev_prev.end())); // Create a replacement that includes the final bracket (or other token), // rather than just inserting a comma at the end. This prevents the UP034 fix // removing any brackets in the same linter pass - doing both at the same time could @@ -393,9 +395,6 @@ fn check_token( format!("{contents},"), prev_prev.range(), ))); - Some(diagnostic) - } else { - None } } diff --git a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs index c6612e9ca4f161..f2c578036c4f8a 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs @@ -2,8 +2,9 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::settings::LinterSettings; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for the absence of copyright notices within Python files. @@ -32,10 +33,11 @@ impl Violation for MissingCopyrightNotice { pub(crate) fn missing_copyright_notice( locator: &Locator, settings: &LinterSettings, -) -> Option { + context: &LintContext, +) { // Ignore files that are too small to contain a copyright notice. if locator.len() < settings.flake8_copyright.min_file_size { - return None; + return; } // Only search the first 4096 bytes in the file. @@ -47,15 +49,12 @@ pub(crate) fn missing_copyright_notice( Some(ref author) => { // Ensure that it's immediately followed by the author. if contents[match_.end()..].trim_start().starts_with(author) { - return None; + return; } } - None => return None, + None => return, } } - Some(OldDiagnostic::new( - MissingCopyrightNotice, - TextRange::default(), - )) + context.report_diagnostic(MissingCopyrightNotice, TextRange::default()); } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs index bde95037ffb711..82065b8af00210 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs @@ -8,7 +8,7 @@ pub(crate) use shebang_not_executable::*; pub(crate) use shebang_not_first_line::*; use crate::Locator; -use crate::OldDiagnostic; +use crate::checkers::ast::LintContext; use crate::codes::Rule; use crate::comments::shebang::ShebangDirective; use crate::settings::LinterSettings; @@ -20,7 +20,7 @@ mod shebang_not_executable; mod shebang_not_first_line; pub(crate) fn from_tokens( - diagnostics: &mut Vec, + context: &LintContext, path: &Path, locator: &Locator, comment_ranges: &CommentRanges, @@ -32,31 +32,21 @@ pub(crate) fn from_tokens( if let Some(shebang) = ShebangDirective::try_extract(comment) { has_any_shebang = true; - if let Some(diagnostic) = shebang_missing_python(range, &shebang) { - diagnostics.push(diagnostic); - } + shebang_missing_python(range, &shebang, context); if settings.rules.enabled(Rule::ShebangNotExecutable) { - if let Some(diagnostic) = shebang_not_executable(path, range) { - diagnostics.push(diagnostic); - } + shebang_not_executable(path, range, context); } - if let Some(diagnostic) = shebang_leading_whitespace(range, locator) { - diagnostics.push(diagnostic); - } + shebang_leading_whitespace(context, range, locator); - if let Some(diagnostic) = shebang_not_first_line(range, locator) { - diagnostics.push(diagnostic); - } + shebang_not_first_line(range, locator, context); } } if !has_any_shebang { if settings.rules.enabled(Rule::ShebangMissingExecutableFile) { - if let Some(diagnostic) = shebang_missing_executable_file(path) { - diagnostics.push(diagnostic); - } + shebang_missing_executable_file(path, context); } } } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs index bf75f3a94d6f15..f7ea698b50ffcc 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs @@ -3,7 +3,8 @@ use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::checkers::ast::LintContext; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for whitespace before a shebang directive. @@ -45,12 +46,13 @@ impl AlwaysFixableViolation for ShebangLeadingWhitespace { /// EXE004 pub(crate) fn shebang_leading_whitespace( + context: &LintContext, range: TextRange, locator: &Locator, -) -> Option { +) { // If the shebang is at the beginning of the file, abort. if range.start() == TextSize::from(0) { - return None; + return; } // If the entire prefix _isn't_ whitespace, abort (this is handled by EXE005). @@ -59,11 +61,11 @@ pub(crate) fn shebang_leading_whitespace( .chars() .all(|c| is_python_whitespace(c) || matches!(c, '\r' | '\n')) { - return None; + return; } let prefix = TextRange::up_to(range.start()); - let mut diagnostic = OldDiagnostic::new(ShebangLeadingWhitespace, prefix); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix))); - Some(diagnostic) + context + .report_diagnostic(ShebangLeadingWhitespace, prefix) + .set_fix(Fix::safe_edit(Edit::range_deletion(prefix))); } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs index d36428e16d7c79..8057afbeef1544 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs @@ -1,15 +1,11 @@ -#![allow(unused_imports)] - use std::path::Path; -use ruff_text_size::{Ranged, TextRange}; - use ruff_macros::{ViolationMetadata, derive_message_formats}; -use crate::registry::AsRule; +use crate::Violation; +use crate::checkers::ast::LintContext; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for executable `.py` files that do not have a shebang. @@ -49,22 +45,20 @@ impl Violation for ShebangMissingExecutableFile { /// EXE002 #[cfg(target_family = "unix")] -pub(crate) fn shebang_missing_executable_file(filepath: &Path) -> Option { +pub(crate) fn shebang_missing_executable_file(filepath: &Path, context: &LintContext) { // WSL supports Windows file systems, which do not have executable bits. // Instead, everything is executable. Therefore, we skip this rule on WSL. + if is_wsl::is_wsl() { - return None; + return; } if let Ok(true) = is_executable(filepath) { - return Some(OldDiagnostic::new( + context.report_diagnostic( ShebangMissingExecutableFile, - TextRange::default(), - )); + ruff_text_size::TextRange::default(), + ); } - None } #[cfg(not(target_family = "unix"))] -pub(crate) fn shebang_missing_executable_file(_filepath: &Path) -> Option { - None -} +pub(crate) fn shebang_missing_executable_file(_filepath: &Path, _diagnostics: &LintContext) {} diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs index d3ab16a5becd87..f1beb8eb8ad2b1 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_python.rs @@ -2,8 +2,9 @@ use ruff_text_size::TextRange; use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::comments::shebang::ShebangDirective; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in `.py` files that does not contain `python`, @@ -44,10 +45,11 @@ impl Violation for ShebangMissingPython { pub(crate) fn shebang_missing_python( range: TextRange, shebang: &ShebangDirective, -) -> Option { + context: &LintContext, +) { if shebang.contains("python") || shebang.contains("pytest") || shebang.contains("uv run") { - return None; + return; } - Some(OldDiagnostic::new(ShebangMissingPython, range)) + context.report_diagnostic(ShebangMissingPython, range); } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs index 0b2093f360e4dc..80cdb9859a2193 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs @@ -3,9 +3,10 @@ use std::path::Path; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::TextRange; +use crate::Violation; +use crate::checkers::ast::LintContext; #[cfg(target_family = "unix")] use crate::rules::flake8_executable::helpers::is_executable; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for a shebang directive in a file that is not executable. @@ -48,21 +49,23 @@ impl Violation for ShebangNotExecutable { /// EXE001 #[cfg(target_family = "unix")] -pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange) -> Option { +pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange, context: &LintContext) { // WSL supports Windows file systems, which do not have executable bits. // Instead, everything is executable. Therefore, we skip this rule on WSL. + if is_wsl::is_wsl() { - return None; + return; } if let Ok(false) = is_executable(filepath) { - return Some(OldDiagnostic::new(ShebangNotExecutable, range)); + context.report_diagnostic(ShebangNotExecutable, range); } - - None } #[cfg(not(target_family = "unix"))] -pub(crate) fn shebang_not_executable(_filepath: &Path, _range: TextRange) -> Option { - None +pub(crate) fn shebang_not_executable( + _filepath: &Path, + _range: TextRange, + _diagnostics: &LintContext, +) { } diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs index 891ff6aebbe360..31145048efb83d 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs @@ -3,7 +3,8 @@ use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{OldDiagnostic, Violation}; +use crate::Violation; +use crate::checkers::ast::LintContext; /// ## What it does /// Checks for a shebang directive that is not at the beginning of the file. @@ -42,10 +43,10 @@ impl Violation for ShebangNotFirstLine { } /// EXE005 -pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Option { +pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator, context: &LintContext) { // If the shebang is at the beginning of the file, abort. if range.start() == TextSize::from(0) { - return None; + return; } // If the entire prefix is whitespace, abort (this is handled by EXE004). @@ -54,8 +55,8 @@ pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Opt .chars() .all(|c| is_python_whitespace(c) || matches!(c, '\r' | '\n')) { - return None; + return; } - Some(OldDiagnostic::new(ShebangNotFirstLine, range)) + context.report_diagnostic(ShebangNotFirstLine, range); } diff --git a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs index 84f2b0fe839ef2..06b4efdb3eb1f4 100644 --- a/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_fixme/rules/todos.rs @@ -1,7 +1,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::directives::{TodoComment, TodoDirectiveKind}; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for "TODO" comments. @@ -114,19 +115,25 @@ impl Violation for LineContainsHack { } } -pub(crate) fn todos(diagnostics: &mut Vec, directive_ranges: &[TodoComment]) { - diagnostics.extend( - directive_ranges - .iter() - .map(|TodoComment { directive, .. }| match directive.kind { - // FIX001 - TodoDirectiveKind::Fixme => OldDiagnostic::new(LineContainsFixme, directive.range), - // FIX002 - TodoDirectiveKind::Hack => OldDiagnostic::new(LineContainsHack, directive.range), - // FIX003 - TodoDirectiveKind::Todo => OldDiagnostic::new(LineContainsTodo, directive.range), - // FIX004 - TodoDirectiveKind::Xxx => OldDiagnostic::new(LineContainsXxx, directive.range), - }), - ); +pub(crate) fn todos(context: &LintContext, directive_ranges: &[TodoComment]) { + for TodoComment { directive, .. } in directive_ranges { + match directive.kind { + // FIX001 + TodoDirectiveKind::Fixme => { + context.report_diagnostic(LineContainsFixme, directive.range); + } + // FIX002 + TodoDirectiveKind::Hack => { + context.report_diagnostic(LineContainsHack, directive.range); + } + // FIX003 + TodoDirectiveKind::Todo => { + context.report_diagnostic(LineContainsTodo, directive.range); + } + // FIX004 + TodoDirectiveKind::Xxx => { + context.report_diagnostic(LineContainsXxx, directive.range); + } + } + } } diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index db3f798f248419..6b946933e29427 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -10,8 +10,9 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::settings::LinterSettings; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for implicitly concatenated strings on a single line. @@ -103,7 +104,7 @@ impl Violation for MultiLineImplicitStringConcatenation { /// ISC001, ISC002 pub(crate) fn implicit( - diagnostics: &mut Vec, + context: &LintContext, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -145,12 +146,12 @@ pub(crate) fn implicit( }; if locator.contains_line_break(TextRange::new(a_range.end(), b_range.start())) { - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic( MultiLineImplicitStringConcatenation, TextRange::new(a_range.start(), b_range.end()), - )); + ); } else { - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( SingleLineImplicitStringConcatenation, TextRange::new(a_range.start(), b_range.end()), ); @@ -158,8 +159,6 @@ pub(crate) fn implicit( if let Some(fix) = concatenate_strings(a_range, b_range, locator) { diagnostic.set_fix(fix); } - - diagnostics.push(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs index f5b6a4366ec4b4..0e78057e95e6b7 100644 --- a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs +++ b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs @@ -7,10 +7,11 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::comments::shebang::ShebangDirective; use crate::fs; use crate::package::PackageRoot; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for packages that are missing an `__init__.py` file. @@ -56,6 +57,7 @@ impl Violation for ImplicitNamespacePackage { } /// INP001 +#[expect(clippy::too_many_arguments)] pub(crate) fn implicit_namespace_package( path: &Path, package: Option>, @@ -64,7 +66,8 @@ pub(crate) fn implicit_namespace_package( project_root: &Path, src: &[PathBuf], allow_nested_roots: bool, -) -> Option { + context: &LintContext, +) { if package.is_none() // Ignore non-`.py` files, which don't require an `__init__.py`. && PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file) @@ -83,16 +86,14 @@ pub(crate) fn implicit_namespace_package( // Ignore PEP 723 scripts. && ScriptTag::parse(locator.contents().as_bytes()).is_none() { - return Some(OldDiagnostic::new( + context.report_diagnostic( ImplicitNamespacePackage { filename: fs::relativize_path(path), parent: None, }, TextRange::default(), - )); - } - - if allow_nested_roots { + ); + } else if allow_nested_roots { if let Some(PackageRoot::Nested { path: root }) = package.as_ref() { if path.ends_with("__init__.py") { // Identify the intermediary package that's missing the `__init__.py` file. @@ -100,17 +101,15 @@ pub(crate) fn implicit_namespace_package( .ancestors() .find(|parent| !parent.join("__init__.py").exists()) { - return Some(OldDiagnostic::new( + context.report_diagnostic( ImplicitNamespacePackage { filename: fs::relativize_path(path), parent: Some(fs::relativize_path(parent)), }, TextRange::default(), - )); + ); } } } } - - None } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs index 62177e18a6f14f..25f5e39ccb4551 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs @@ -6,7 +6,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use crate::Locator; -use crate::{OldDiagnostic, Violation}; +use crate::Violation; +use crate::checkers::ast::LintContext; /// ## What it does /// Checks for the use of type comments (e.g., `x = 1 # type: int`) in stub @@ -38,7 +39,7 @@ impl Violation for TypeCommentInStub { /// PYI033 pub(crate) fn type_comment_in_stub( - diagnostics: &mut Vec, + context: &LintContext, locator: &Locator, comment_ranges: &CommentRanges, ) { @@ -46,7 +47,7 @@ pub(crate) fn type_comment_in_stub( let comment = locator.slice(range); if TYPE_COMMENT_REGEX.is_match(comment) && !TYPE_IGNORE_REGEX.is_match(comment) { - diagnostics.push(OldDiagnostic::new(TypeCommentInStub, range)); + context.report_diagnostic(TypeCommentInStub, range); } } } diff --git a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs index 9ee015251c7f14..0c36eb3a46b1b2 100644 --- a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs @@ -7,8 +7,9 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::directives::{TodoComment, TodoDirective, TodoDirectiveKind}; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic, Violation}; +use crate::{AlwaysFixableViolation, Edit, Fix, Violation}; /// ## What it does /// Checks that a TODO comment is labelled with "TODO". @@ -248,7 +249,7 @@ static ISSUE_LINK_TODO_LINE_REGEX_SET: LazyLock = LazyLock::new(|| { }); pub(crate) fn todos( - diagnostics: &mut Vec, + context: &LintContext, todo_comments: &[TodoComment], locator: &Locator, comment_ranges: &CommentRanges, @@ -267,8 +268,8 @@ pub(crate) fn todos( continue; } - directive_errors(diagnostics, directive); - static_errors(diagnostics, content, range, directive); + directive_errors(context, directive); + static_errors(context, content, range, directive); let mut has_issue_link = false; // VSCode recommended links on same line are ok: @@ -307,20 +308,20 @@ pub(crate) fn todos( if !has_issue_link { // TD003 - diagnostics.push(OldDiagnostic::new(MissingTodoLink, directive.range)); + context.report_diagnostic(MissingTodoLink, directive.range); } } } /// Check that the directive itself is valid. This function modifies `diagnostics` in-place. -fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirective) { +fn directive_errors(context: &LintContext, directive: &TodoDirective) { if directive.content == "TODO" { return; } if directive.content.to_uppercase() == "TODO" { // TD006 - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( InvalidTodoCapitalization { tag: directive.content.to_string(), }, @@ -331,22 +332,20 @@ fn directive_errors(diagnostics: &mut Vec, directive: &TodoDirect "TODO".to_string(), directive.range, ))); - - diagnostics.push(diagnostic); } else { // TD001 - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic( InvalidTodoTag { tag: directive.content.to_string(), }, directive.range, - )); + ); } } /// Checks for "static" errors in the comment: missing colon, missing author, etc. -fn static_errors( - diagnostics: &mut Vec, +pub(crate) fn static_errors( + context: &LintContext, comment: &str, comment_range: TextRange, directive: &TodoDirective, @@ -367,13 +366,13 @@ fn static_errors( TextSize::try_from(end_index).unwrap() } else { // TD002 - diagnostics.push(OldDiagnostic::new(MissingTodoAuthor, directive.range)); + context.report_diagnostic(MissingTodoAuthor, directive.range); TextSize::new(0) } } else { // TD002 - diagnostics.push(OldDiagnostic::new(MissingTodoAuthor, directive.range)); + context.report_diagnostic(MissingTodoAuthor, directive.range); TextSize::new(0) }; @@ -382,21 +381,18 @@ fn static_errors( if let Some(after_colon) = after_author.strip_prefix(':') { if after_colon.is_empty() { // TD005 - diagnostics.push(OldDiagnostic::new(MissingTodoDescription, directive.range)); + context.report_diagnostic(MissingTodoDescription, directive.range); } else if !after_colon.starts_with(char::is_whitespace) { // TD007 - diagnostics.push(OldDiagnostic::new( - MissingSpaceAfterTodoColon, - directive.range, - )); + context.report_diagnostic(MissingSpaceAfterTodoColon, directive.range); } } else { // TD004 - diagnostics.push(OldDiagnostic::new(MissingTodoColon, directive.range)); + context.report_diagnostic(MissingTodoColon, directive.range); if after_author.is_empty() { // TD005 - diagnostics.push(OldDiagnostic::new(MissingTodoDescription, directive.range)); + context.report_diagnostic(MissingTodoDescription, directive.range); } } } diff --git a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs index 0f263ba83cade4..3948202883dfbd 100644 --- a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs @@ -7,9 +7,10 @@ use ruff_python_semantic::{FutureImport, NameImport}; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::importer::Importer; use crate::settings::LinterSettings; -use crate::{AlwaysFixableViolation, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Fix}; /// ## What it does /// Adds any required imports, as specified by the user, to the top of the @@ -91,15 +92,16 @@ fn add_required_import( locator: &Locator, stylist: &Stylist, source_type: PySourceType, -) -> Option { + context: &LintContext, +) { // Don't add imports to semantically-empty files. if parsed.suite().iter().all(is_docstring_stmt) { - return None; + return; } // We don't need to add `__future__` imports to stubs. if source_type.is_stub() && required_import.is_future_import() { - return None; + return; } // If the import is already present in a top-level block, don't add it. @@ -108,18 +110,17 @@ fn add_required_import( .iter() .any(|stmt| includes_import(stmt, required_import)) { - return None; + return; } // Always insert the diagnostic at top-of-file. - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( MissingRequiredImport(required_import.to_string()), TextRange::default(), ); diagnostic.set_fix(Fix::safe_edit( Importer::new(parsed, locator, stylist).add_import(required_import, TextSize::default()), )); - Some(diagnostic) } /// I002 @@ -129,13 +130,16 @@ pub(crate) fn add_required_imports( stylist: &Stylist, settings: &LinterSettings, source_type: PySourceType, -) -> Vec { - settings - .isort - .required_imports - .iter() - .filter_map(|required_import| { - add_required_import(required_import, parsed, locator, stylist, source_type) - }) - .collect() + context: &LintContext, +) { + for required_import in &settings.isort.required_imports { + add_required_import( + required_import, + parsed, + locator, + stylist, + source_type, + context, + ); + } } diff --git a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index 6fdd6e0794f637..1fe06598648ddf 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -13,12 +13,13 @@ use ruff_text_size::{Ranged, TextRange}; use super::super::block::Block; use super::super::{comments, format_imports}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::line_width::LineWidthBuilder; use crate::package::PackageRoot; use crate::preview::is_full_path_match_source_strategy_enabled; use crate::rules::isort::categorize::MatchSourceStrategy; use crate::settings::LinterSettings; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// De-duplicates, groups, and sorts imports based on the provided `isort` settings. @@ -98,7 +99,8 @@ pub(crate) fn organize_imports( source_type: PySourceType, tokens: &Tokens, target_version: PythonVersion, -) -> Option { + context: &LintContext, +) { let indentation = locator.slice(extract_indentation_range(&block.imports, locator)); let indentation = leading_indentation(indentation); @@ -110,7 +112,8 @@ pub(crate) fn organize_imports( || indexer .followed_by_multi_statement_line(block.imports.last().unwrap(), locator.contents()) { - return Some(OldDiagnostic::new(UnsortedImports, range)); + context.report_diagnostic(UnsortedImports, range); + return; } // Extract comments. Take care to grab any inline comments from the last line. @@ -153,12 +156,11 @@ pub(crate) fn organize_imports( let fix_range = TextRange::new(locator.line_start(range.start()), trailing_line_end); let actual = locator.slice(fix_range); if matches_ignoring_indentation(actual, &expected) { - return None; + return; } - let mut diagnostic = OldDiagnostic::new(UnsortedImports, range); + let mut diagnostic = context.report_diagnostic(UnsortedImports, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( indent(&expected, indentation).to_string(), fix_range, ))); - Some(diagnostic) } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs index d2d60c6a4be4e9..6e7e6992826595 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -7,9 +7,10 @@ use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name}; use ruff_python_stdlib::path::is_module_file; use ruff_text_size::TextRange; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::package::PackageRoot; use crate::rules::pep8_naming::settings::IgnoreNames; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for module names that do not follow the `snake_case` naming @@ -54,9 +55,10 @@ pub(crate) fn invalid_module_name( path: &Path, package: Option>, ignore_names: &IgnoreNames, -) -> Option { + context: &LintContext, +) { if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) { - return None; + return; } if let Some(package) = package { @@ -78,18 +80,16 @@ pub(crate) fn invalid_module_name( if !is_valid_module_name { // Ignore any explicitly-allowed names. if ignore_names.matches(&module_name) { - return None; + return; } - return Some(OldDiagnostic::new( + context.report_diagnostic( InvalidModuleName { name: module_name.to_string(), }, TextRange::default(), - )); + ); } } - - None } /// Return `true` if a [`Path`] refers to a migration file. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 723ccbf49d2976..4075a245c33e84 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -21,7 +21,7 @@ use crate::AlwaysFixableViolation; use crate::Edit; use crate::Fix; use crate::Locator; -use crate::OldDiagnostic; +use crate::checkers::ast::LintContext; use crate::checkers::logical_lines::expand_indent; use crate::line_width::IndentWidth; use crate::rules::pycodestyle::helpers::is_non_logical_token; @@ -690,8 +690,7 @@ impl Status { } /// Contains variables used for the linting of blank lines. -#[derive(Debug)] -pub(crate) struct BlankLinesChecker<'a> { +pub(crate) struct BlankLinesChecker<'a, 'b> { stylist: &'a Stylist<'a>, locator: &'a Locator<'a>, indent_width: IndentWidth, @@ -699,16 +698,18 @@ pub(crate) struct BlankLinesChecker<'a> { lines_between_types: usize, source_type: PySourceType, cell_offsets: Option<&'a CellOffsets>, + context: &'a LintContext<'b>, } -impl<'a> BlankLinesChecker<'a> { +impl<'a, 'b> BlankLinesChecker<'a, 'b> { pub(crate) fn new( locator: &'a Locator<'a>, stylist: &'a Stylist<'a>, settings: &crate::settings::LinterSettings, source_type: PySourceType, cell_offsets: Option<&'a CellOffsets>, - ) -> BlankLinesChecker<'a> { + context: &'a LintContext<'b>, + ) -> BlankLinesChecker<'a, 'b> { BlankLinesChecker { stylist, locator, @@ -717,11 +718,12 @@ impl<'a> BlankLinesChecker<'a> { lines_between_types: settings.isort.lines_between_types, source_type, cell_offsets, + context, } } /// E301, E302, E303, E304, E305, E306 - pub(crate) fn check_lines(&self, tokens: &Tokens, diagnostics: &mut Vec) { + pub(crate) fn check_lines(&self, tokens: &Tokens) { let mut prev_indent_length: Option = None; let mut prev_logical_line: Option = None; let mut state = BlankLinesState::default(); @@ -762,7 +764,7 @@ impl<'a> BlankLinesChecker<'a> { state.class_status.update(&logical_line); state.fn_status.update(&logical_line); - self.check_line(&logical_line, &state, prev_indent_length, diagnostics); + self.check_line(&logical_line, &state, prev_indent_length); match logical_line.kind { LogicalLineKind::Class => { @@ -824,7 +826,6 @@ impl<'a> BlankLinesChecker<'a> { line: &LogicalLineInfo, state: &BlankLinesState, prev_indent_length: Option, - diagnostics: &mut Vec, ) { if line.preceding_blank_lines == 0 // Only applies to methods. @@ -842,14 +843,13 @@ impl<'a> BlankLinesChecker<'a> { && !self.source_type.is_stub() { // E301 - let mut diagnostic = - OldDiagnostic::new(BlankLineBetweenMethods, line.first_token_range); + let mut diagnostic = self + .context + .report_diagnostic(BlankLineBetweenMethods, line.first_token_range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( self.stylist.line_ending().to_string(), self.locator.line_start(state.last_non_comment_line_end), ))); - - diagnostics.push(diagnostic); } // Blank lines in stub files are used to group definitions. Don't enforce blank lines. @@ -897,7 +897,7 @@ impl<'a> BlankLinesChecker<'a> { && !line.is_beginning_of_cell { // E302 - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = self.context.report_diagnostic( BlankLinesTopLevel { actual_blank_lines: line.preceding_blank_lines.count(), expected_blank_lines: expected_blank_lines_before_definition, @@ -921,8 +921,6 @@ impl<'a> BlankLinesChecker<'a> { self.locator.line_start(state.last_non_comment_line_end), ))); } - - diagnostics.push(diagnostic); } // If between `import` and `from .. import ..` or the other way round, @@ -941,7 +939,7 @@ impl<'a> BlankLinesChecker<'a> { if line.blank_lines > max_blank_lines { // E303 - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = self.context.report_diagnostic( TooManyBlankLines { actual_blank_lines: line.blank_lines.count(), }, @@ -958,8 +956,6 @@ impl<'a> BlankLinesChecker<'a> { ))); } } - - diagnostics.push(diagnostic); } if matches!(state.follows, Follows::Decorator) @@ -967,7 +963,7 @@ impl<'a> BlankLinesChecker<'a> { && line.preceding_blank_lines > 0 { // E304 - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = self.context.report_diagnostic( BlankLineAfterDecorator { actual_blank_lines: line.preceding_blank_lines.count(), }, @@ -997,8 +993,6 @@ impl<'a> BlankLinesChecker<'a> { }; diagnostic.set_fix(fix); - - diagnostics.push(diagnostic); } if line.preceding_blank_lines < BLANK_LINES_TOP_LEVEL @@ -1014,7 +1008,7 @@ impl<'a> BlankLinesChecker<'a> { && !line.is_beginning_of_cell { // E305 - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = self.context.report_diagnostic( BlankLinesAfterFunctionOrClass { actual_blank_lines: line.preceding_blank_lines.count(), }, @@ -1036,8 +1030,6 @@ impl<'a> BlankLinesChecker<'a> { self.locator.line_start(state.last_non_comment_line_end), ))); } - - diagnostics.push(diagnostic); } if line.preceding_blank_lines == 0 @@ -1057,15 +1049,14 @@ impl<'a> BlankLinesChecker<'a> { && !self.source_type.is_stub() { // E306 - let mut diagnostic = - OldDiagnostic::new(BlankLinesBeforeNestedDefinition, line.first_token_range); + let mut diagnostic = self + .context + .report_diagnostic(BlankLinesBeforeNestedDefinition, line.first_token_range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( self.stylist.line_ending().to_string(), self.locator.line_start(line.first_token_range.start()), ))); - - diagnostics.push(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs index 4a5e166fce88fd..d6762adee584b2 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs @@ -6,8 +6,9 @@ use ruff_python_parser::{TokenIterWithContext, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextSize}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::{AlwaysFixableViolation, Violation}; -use crate::{Edit, Fix, OldDiagnostic}; +use crate::{Edit, Fix}; /// ## What it does /// Checks for compound statements (multiple statements on the same line). @@ -98,7 +99,7 @@ impl AlwaysFixableViolation for UselessSemicolon { /// E701, E702, E703 pub(crate) fn compound_statements( - diagnostics: &mut Vec, + context: &LintContext, tokens: &Tokens, locator: &Locator, indexer: &Indexer, @@ -167,14 +168,14 @@ pub(crate) fn compound_statements( !has_non_trivia_tokens_till(token_iter.clone(), cell_range.end()) })) { - let mut diagnostic = OldDiagnostic::new(UselessSemicolon, range); - diagnostic.set_fix(Fix::safe_edit(Edit::deletion( - indexer - .preceded_by_continuations(range.start(), locator.contents()) - .unwrap_or(range.start()), - range.end(), - ))); - diagnostics.push(diagnostic); + context + .report_diagnostic(UselessSemicolon, range) + .set_fix(Fix::safe_edit(Edit::deletion( + indexer + .preceded_by_continuations(range.start(), locator.contents()) + .unwrap_or(range.start()), + range.end(), + ))); } } @@ -224,10 +225,7 @@ pub(crate) fn compound_statements( | TokenKind::NonLogicalNewline => {} _ => { if let Some(range) = semi { - diagnostics.push(OldDiagnostic::new( - MultipleStatementsOnOneLineSemicolon, - range, - )); + context.report_diagnostic(MultipleStatementsOnOneLineSemicolon, range); // Reset. semi = None; @@ -235,7 +233,7 @@ pub(crate) fn compound_statements( } if let Some(range) = colon { - diagnostics.push(OldDiagnostic::new(MultipleStatementsOnOneLineColon, range)); + context.report_diagnostic(MultipleStatementsOnOneLineColon, range); // Reset. colon = None; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs index c669a9c7e2663e..c9f0ae0753ab71 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -2,9 +2,10 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::Line; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for doc lines that exceed the specified maximum character length. @@ -86,9 +87,13 @@ pub(crate) fn doc_line_too_long( line: &Line, comment_ranges: &CommentRanges, settings: &LinterSettings, -) -> Option { - let limit = settings.pycodestyle.max_doc_length?; - Overlong::try_from_line( + context: &LintContext, +) { + let Some(limit) = settings.pycodestyle.max_doc_length else { + return; + }; + + if let Some(overlong) = Overlong::try_from_line( line, comment_ranges, limit, @@ -98,11 +103,10 @@ pub(crate) fn doc_line_too_long( &[] }, settings.tab_size, - ) - .map(|overlong| { - OldDiagnostic::new( + ) { + context.report_diagnostic( DocLineTooLong(overlong.width(), limit.value() as usize), overlong.range(), - ) - }) + ); + } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs index dd93733790e075..9a03e0f98a2e5e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs @@ -2,9 +2,10 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::CommentRanges; use ruff_source_file::Line; +use crate::Violation; +use crate::checkers::ast::LintContext; use crate::rules::pycodestyle::overlong::Overlong; use crate::settings::LinterSettings; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for lines that exceed the specified maximum character length. @@ -84,10 +85,11 @@ pub(crate) fn line_too_long( line: &Line, comment_ranges: &CommentRanges, settings: &LinterSettings, -) -> Option { + context: &LintContext, +) { let limit = settings.pycodestyle.max_line_length; - Overlong::try_from_line( + if let Some(overlong) = Overlong::try_from_line( line, comment_ranges, limit, @@ -97,11 +99,10 @@ pub(crate) fn line_too_long( &[] }, settings.tab_size, - ) - .map(|overlong| { - OldDiagnostic::new( + ) { + context.report_diagnostic( LineTooLong(overlong.width(), limit.value() as usize), overlong.range(), - ) - }) + ); + } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs index d05d154e17c203..cf259832bbe417 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs @@ -5,7 +5,6 @@ use ruff_text_size::{Ranged, TextRange}; use crate::AlwaysFixableViolation; use crate::Edit; use crate::Fix; -use crate::OldDiagnostic; use crate::checkers::logical_lines::LogicalLinesContext; use super::{LogicalLine, Whitespace}; @@ -165,13 +164,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin BracketOrPunctuation::OpenBracket(symbol) if symbol != '{' || fstrings == 0 => { let (trailing, trailing_len) = line.trailing_whitespace(token); if !matches!(trailing, Whitespace::None) { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceAfterOpenBracket { symbol }, TextRange::at(token.end(), trailing_len), - ); - diagnostic - .set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range()))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); + } } } BracketOrPunctuation::CloseBracket(symbol) if symbol != '}' || fstrings == 0 => { @@ -179,13 +178,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) = line.leading_whitespace(token) { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforeCloseBracket { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic - .set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range()))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); + } } } } @@ -205,14 +204,14 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // If we're in the second half of a double colon, disallow // any whitespace (e.g., `foo[1: :2]` or `foo[1 : : 2]`). if matches!(prev_token, Some(TokenKind::Colon)) { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion( - diagnostic.range(), - ))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic + .set_fix(Fix::safe_edit(Edit::range_deletion(range))); + } } else if iter.peek().is_some_and(|token| { matches!(token.kind(), TokenKind::Rsqb | TokenKind::Comma) }) { @@ -220,14 +219,15 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // Or `foo[index :, 2]`, but not `foo[index :, 2]`. if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion( - diagnostic.range(), - ))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit( + Edit::range_deletion(range), + )); + } } } else if iter.peek().is_some_and(|token| { matches!( @@ -245,15 +245,19 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // whitespace before the colon and so should the fix if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edits( - Edit::range_deletion(diagnostic.range()), - [Edit::insertion(" ".into(), token.start() - offset)], - )); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edits( + Edit::range_deletion(range), + [Edit::insertion( + " ".into(), + token.start() - offset, + )], + )); + } } } else { // Allow, e.g., `foo[1:2]` or `foo[1 : 2]` or `foo[1 :: 2]`. @@ -262,14 +266,15 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin .filter(|next| matches!(next.kind(), TokenKind::Colon)) .unwrap_or(&token); if line.trailing_whitespace(token) != whitespace { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion( - diagnostic.range(), - ))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit( + Edit::range_deletion(range), + )); + } } } } else { @@ -280,14 +285,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin // Avoid removing any whitespace for f-string debug expressions. continue; } - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion( - diagnostic.range(), - ))); - context.push_diagnostic(diagnostic); + ) { + let range = diagnostic.range(); + diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); + } } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs index de7349d059b6e4..a4e07cee1fe739 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/indentation.rs @@ -2,8 +2,9 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::TextRange; -use crate::OldDiagnostic; use crate::Violation; +use crate::checkers::ast::LintContext; +use crate::settings::LinterSettings; use super::LogicalLine; @@ -256,6 +257,7 @@ impl Violation for OverIndented { } /// E111, E112, E113, E114, E115, E116, E117 +#[expect(clippy::too_many_arguments)] pub(crate) fn indentation( logical_line: &LogicalLine, prev_logical_line: Option<&LogicalLine>, @@ -264,57 +266,58 @@ pub(crate) fn indentation( prev_indent_level: Option, indent_size: usize, range: TextRange, -) -> Vec { - let mut diagnostics = vec![]; - + context: &LintContext, + settings: &LinterSettings, +) { if indent_level % indent_size != 0 { - diagnostics.push(if logical_line.is_comment_only() { - OldDiagnostic::new( + if logical_line.is_comment_only() { + context.report_diagnostic_if_enabled( IndentationWithInvalidMultipleComment { indent_width: indent_size, }, range, - ) + settings, + ); } else { - OldDiagnostic::new( + context.report_diagnostic_if_enabled( IndentationWithInvalidMultiple { indent_width: indent_size, }, range, - ) - }); + settings, + ); + } } let indent_expect = prev_logical_line .and_then(|prev_logical_line| prev_logical_line.tokens_trimmed().last()) .is_some_and(|t| t.kind() == TokenKind::Colon); if indent_expect && indent_level <= prev_indent_level.unwrap_or(0) { - diagnostics.push(if logical_line.is_comment_only() { - OldDiagnostic::new(NoIndentedBlockComment, range) + if logical_line.is_comment_only() { + context.report_diagnostic_if_enabled(NoIndentedBlockComment, range, settings); } else { - OldDiagnostic::new(NoIndentedBlock, range) - }); + context.report_diagnostic_if_enabled(NoIndentedBlock, range, settings); + } } else if !indent_expect && prev_indent_level.is_some_and(|prev_indent_level| indent_level > prev_indent_level) { - diagnostics.push(if logical_line.is_comment_only() { - OldDiagnostic::new(UnexpectedIndentationComment, range) + if logical_line.is_comment_only() { + context.report_diagnostic_if_enabled(UnexpectedIndentationComment, range, settings); } else { - OldDiagnostic::new(UnexpectedIndentation, range) - }); + context.report_diagnostic_if_enabled(UnexpectedIndentation, range, settings); + } } if indent_expect { let expected_indent_amount = if indent_char == '\t' { 8 } else { 4 }; let expected_indent_level = prev_indent_level.unwrap_or(0) + expected_indent_amount; if indent_level > expected_indent_level { - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic_if_enabled( OverIndented { is_comment: logical_line.is_comment_only(), }, range, - )); + settings, + ); } } - - diagnostics } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs index e572022bd79146..165bd07c9c197b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs @@ -4,7 +4,7 @@ use ruff_text_size::Ranged; use crate::Edit; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Fix}; use super::{DefinitionState, LogicalLine}; @@ -103,10 +103,14 @@ pub(crate) fn missing_whitespace(line: &LogicalLine, context: &mut LogicalLinesC } } - let diagnostic = - OldDiagnostic::new(MissingWhitespace { token: kind }, token.range()); - let fix = Fix::safe_edit(Edit::insertion(" ".to_string(), token.end())); - context.push_diagnostic(diagnostic.with_fix(fix)); + if let Some(mut diagnostic) = + context.report_diagnostic(MissingWhitespace { token: kind }, token.range()) + { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion( + " ".to_string(), + token.end(), + ))); + } } } _ => {} diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs index 9883e34e7ad33f..214b509f654ec7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_after_keyword.rs @@ -4,7 +4,7 @@ use ruff_text_size::Ranged; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for missing whitespace after keywords. @@ -71,9 +71,11 @@ pub(crate) fn missing_whitespace_after_keyword( )) && tok0.end() == tok1.start() { - let mut diagnostic = OldDiagnostic::new(MissingWhitespaceAfterKeyword, tok0.range()); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion(" ".to_string(), tok0.end()))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(MissingWhitespaceAfterKeyword, tok0.range()) + { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion(" ".to_string(), tok0.end()))); + } } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs index 1eaf0d7cb1497d..8433aec2161d63 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs @@ -2,10 +2,11 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; +use crate::checkers::ast::DiagnosticGuard; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::helpers::is_non_logical_token; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for missing whitespace around all operators. @@ -252,31 +253,37 @@ pub(crate) fn missing_whitespace_around_operator( match (has_leading_trivia, has_trailing_trivia) { // Operator with trailing but no leading space, enforce consistent spacing. (false, true) => { - context.push_diagnostic( - diagnostic_kind_for_operator(kind, token.range()).with_fix(Fix::safe_edit( - Edit::insertion(" ".to_string(), token.start()), - )), - ); + if let Some(mut diagnostic) = + diagnostic_kind_for_operator(kind, token.range(), context) + { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion( + " ".to_string(), + token.start(), + ))); + } } // Operator with leading but no trailing space, enforce consistent spacing. (true, false) => { - context.push_diagnostic( - diagnostic_kind_for_operator(kind, token.range()).with_fix(Fix::safe_edit( - Edit::insertion(" ".to_string(), token.end()), - )), - ); + if let Some(mut diagnostic) = + diagnostic_kind_for_operator(kind, token.range(), context) + { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion( + " ".to_string(), + token.end(), + ))); + } } // Operator with no space, require spaces if it is required by the operator. (false, false) => { if needs_space == NeedsSpace::Yes { - context.push_diagnostic( - diagnostic_kind_for_operator(kind, token.range()).with_fix( - Fix::safe_edits( - Edit::insertion(" ".to_string(), token.start()), - [Edit::insertion(" ".to_string(), token.end())], - ), - ), - ); + if let Some(mut diagnostic) = + diagnostic_kind_for_operator(kind, token.range(), context) + { + diagnostic.set_fix(Fix::safe_edits( + Edit::insertion(" ".to_string(), token.start()), + [Edit::insertion(" ".to_string(), token.end())], + )); + } } } (true, true) => { @@ -314,15 +321,19 @@ impl From for NeedsSpace { } } -fn diagnostic_kind_for_operator(operator: TokenKind, range: TextRange) -> OldDiagnostic { +fn diagnostic_kind_for_operator<'a, 'b>( + operator: TokenKind, + range: TextRange, + context: &'a mut LogicalLinesContext<'b, '_>, +) -> Option> { if operator == TokenKind::Percent { - OldDiagnostic::new(MissingWhitespaceAroundModuloOperator, range) + context.report_diagnostic(MissingWhitespaceAroundModuloOperator, range) } else if operator.is_bitwise_or_shift() { - OldDiagnostic::new(MissingWhitespaceAroundBitwiseOrShiftOperator, range) + context.report_diagnostic(MissingWhitespaceAroundBitwiseOrShiftOperator, range) } else if operator.is_arithmetic() { - OldDiagnostic::new(MissingWhitespaceAroundArithmeticOperator, range) + context.report_diagnostic(MissingWhitespaceAroundArithmeticOperator, range) } else { - OldDiagnostic::new(MissingWhitespaceAroundOperator, range) + context.report_diagnostic(MissingWhitespaceAroundOperator, range) } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs index 4726c28fdf8bf0..66353bdb93e105 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs @@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::LogicalLine; @@ -75,15 +75,15 @@ pub(crate) fn redundant_backslash( for continuation_line in &continuation_lines[start_index..end_index] { let backslash_end = locator.line_end(*continuation_line); let backslash_start = backslash_end - TextSize::new(1); - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( RedundantBackslash, TextRange::new(backslash_start, backslash_end), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::deletion( - backslash_start, - backslash_end, - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::deletion( + backslash_start, + backslash_end, + ))); + } } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs index 7194333e17140d..8381792d7ee636 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/space_around_operator.rs @@ -3,7 +3,7 @@ use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::{LogicalLine, Whitespace}; @@ -206,26 +206,26 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin if !after_operator { match line.leading_whitespace(token) { (Whitespace::Tab, offset) => { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( TabBeforeOperator, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.start() - offset, offset), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.start() - offset, offset), + ))); + } } (Whitespace::Many, offset) => { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MultipleSpacesBeforeOperator, TextRange::at(token.start() - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.start() - offset, offset), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.start() - offset, offset), + ))); + } } _ => {} } @@ -233,24 +233,25 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { - let mut diagnostic = - OldDiagnostic::new(TabAfterOperator, TextRange::at(token.end(), len)); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(TabAfterOperator, TextRange::at(token.end(), len)) + { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } (Whitespace::Many, len) => { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MultipleSpacesAfterOperator, TextRange::at(token.end(), len), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } _ => {} } @@ -266,24 +267,25 @@ pub(crate) fn space_after_comma(line: &LogicalLine, context: &mut LogicalLinesCo if matches!(token.kind(), TokenKind::Comma) { match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { - let mut diagnostic = - OldDiagnostic::new(TabAfterComma, TextRange::at(token.end(), len)); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(TabAfterComma, TextRange::at(token.end(), len)) + { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } (Whitespace::Many, len) => { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MultipleSpacesAfterComma, TextRange::at(token.end(), len), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } _ => {} } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs index 2a1d5055f7bc63..2d3ca6e0723843 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_keywords.rs @@ -2,7 +2,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::logical_lines::LogicalLinesContext; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; use super::{LogicalLine, Whitespace}; @@ -133,27 +133,27 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic match line.leading_whitespace(token) { (Whitespace::Tab, offset) => { let start = token.start(); - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( TabBeforeKeyword, TextRange::at(start - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(start - offset, offset), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(start - offset, offset), + ))); + } } (Whitespace::Many, offset) => { let start = token.start(); - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MultipleSpacesBeforeKeyword, TextRange::at(start - offset, offset), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(start - offset, offset), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(start - offset, offset), + ))); + } } _ => {} } @@ -161,24 +161,25 @@ pub(crate) fn whitespace_around_keywords(line: &LogicalLine, context: &mut Logic match line.trailing_whitespace(token) { (Whitespace::Tab, len) => { - let mut diagnostic = - OldDiagnostic::new(TabAfterKeyword, TextRange::at(token.end(), len)); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(TabAfterKeyword, TextRange::at(token.end(), len)) + { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } (Whitespace::Many, len) => { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MultipleSpacesAfterKeyword, TextRange::at(token.end(), len), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::at(token.end(), len), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::at(token.end(), len), + ))); + } } _ => {} } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs index 5aa5b0332776a9..f714e60dae5694 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_around_named_parameter_equals.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine}; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for missing whitespace around the equals sign in an unannotated @@ -125,13 +125,14 @@ pub(crate) fn whitespace_around_named_parameter_equals( if definition_state.in_type_params() || (annotated_func_arg && parens == 1) { let start = token.start(); if start == prev_end && prev_end != TextSize::new(0) { - let mut diagnostic = - OldDiagnostic::new(MissingWhitespaceAroundParameterEquals, token.range); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - " ".to_string(), - token.start(), - ))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = context + .report_diagnostic(MissingWhitespaceAroundParameterEquals, token.range) + { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion( + " ".to_string(), + token.start(), + ))); + } } while let Some(next) = iter.peek() { @@ -141,15 +142,15 @@ pub(crate) fn whitespace_around_named_parameter_equals( let next_start = next.start(); if next_start == token.end() { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( MissingWhitespaceAroundParameterEquals, token.range, - ); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - " ".to_string(), - token.end(), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::insertion( + " ".to_string(), + token.end(), + ))); + } } break; } @@ -157,12 +158,13 @@ pub(crate) fn whitespace_around_named_parameter_equals( } else { // If there's space between the preceding token and the equals sign, report it. if token.start() != prev_end { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( UnexpectedSpacesAroundKeywordParameterEquals, TextRange::new(prev_end, token.start()), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::deletion(prev_end, token.start()))); - context.push_diagnostic(diagnostic); + ) { + diagnostic + .set_fix(Fix::safe_edit(Edit::deletion(prev_end, token.start()))); + } } // If there's space between the equals sign and the following token, report it. @@ -171,15 +173,15 @@ pub(crate) fn whitespace_around_named_parameter_equals( iter.next(); } else { if next.start() != token.end() { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( UnexpectedSpacesAroundKeywordParameterEquals, TextRange::new(token.end(), next.start()), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::deletion( - token.end(), - next.start(), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::deletion( + token.end(), + next.start(), + ))); + } } break; } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs index abd6522eb04bce..73f4dd58992047 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs @@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks if inline comments are separated by at least two spaces. @@ -185,15 +185,15 @@ pub(crate) fn whitespace_before_comment( let is_inline_comment = !line_text.trim_whitespace().is_empty(); if is_inline_comment { if range.start() - prev_end < " ".text_len() { - let mut diagnostic = OldDiagnostic::new( + if let Some(mut diagnostic) = context.report_diagnostic( TooFewSpacesBeforeInlineComment, TextRange::new(prev_end, range.start()), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - " ".to_string(), - TextRange::new(prev_end, range.start()), - ))); - context.push_diagnostic(diagnostic); + ) { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + " ".to_string(), + TextRange::new(prev_end, range.start()), + ))); + } } } @@ -210,30 +210,35 @@ pub(crate) fn whitespace_before_comment( if is_inline_comment { if bad_prefix.is_some() || comment.chars().next().is_some_and(char::is_whitespace) { - let mut diagnostic = OldDiagnostic::new(NoSpaceAfterInlineComment, range); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - format_leading_space(token_text), - range, - ))); - context.push_diagnostic(diagnostic); - } - } else if let Some(bad_prefix) = bad_prefix { - if bad_prefix != '!' || !line.is_start_of_file() { - if bad_prefix != '#' { - let mut diagnostic = OldDiagnostic::new(NoSpaceAfterBlockComment, range); + if let Some(mut diagnostic) = + context.report_diagnostic(NoSpaceAfterInlineComment, range) + { diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format_leading_space(token_text), range, ))); - context.push_diagnostic(diagnostic); + } + } + } else if let Some(bad_prefix) = bad_prefix { + if bad_prefix != '!' || !line.is_start_of_file() { + if bad_prefix != '#' { + if let Some(mut diagnostic) = + context.report_diagnostic(NoSpaceAfterBlockComment, range) + { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + format_leading_space(token_text), + range, + ))); + } } else if !comment.is_empty() { - let mut diagnostic = - OldDiagnostic::new(MultipleLeadingHashesForBlockComment, range); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - format_leading_hashes(token_text), - range, - ))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(MultipleLeadingHashesForBlockComment, range) + { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + format_leading_hashes(token_text), + range, + ))); + } } } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs index 17806ef42d9550..baf0d4b1f37d90 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_parameters.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for extraneous whitespace immediately preceding an open parenthesis @@ -76,9 +76,11 @@ pub(crate) fn whitespace_before_parameters(line: &LogicalLine, context: &mut Log let end = token.end() - TextSize::from(1); let kind: WhitespaceBeforeParameters = WhitespaceBeforeParameters { bracket: kind }; - let mut diagnostic = OldDiagnostic::new(kind, TextRange::new(start, end)); - diagnostic.set_fix(Fix::safe_edit(Edit::deletion(start, end))); - context.push_diagnostic(diagnostic); + if let Some(mut diagnostic) = + context.report_diagnostic(kind, TextRange::new(start, end)) + { + diagnostic.set_fix(Fix::safe_edit(Edit::deletion(start, end))); + } } pre_pre_kind = Some(prev_token); prev_token = kind; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs index f256c58b4d19c7..f8301cbc94a6ba 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs @@ -3,7 +3,8 @@ use ruff_python_codegen::Stylist; use ruff_text_size::{TextLen, TextRange}; use crate::Locator; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::checkers::ast::LintContext; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for files missing a new line at the end of the file. @@ -40,24 +41,22 @@ impl AlwaysFixableViolation for MissingNewlineAtEndOfFile { pub(crate) fn no_newline_at_end_of_file( locator: &Locator, stylist: &Stylist, -) -> Option { + context: &LintContext, +) { let source = locator.contents(); // Ignore empty and BOM only files. if source.is_empty() || source == "\u{feff}" { - return None; + return; } if !source.ends_with(['\n', '\r']) { let range = TextRange::empty(locator.contents().text_len()); - let mut diagnostic = OldDiagnostic::new(MissingNewlineAtEndOfFile, range); + let mut diagnostic = context.report_diagnostic(MissingNewlineAtEndOfFile, range); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( stylist.line_ending().to_string(), range.start(), ))); - return Some(diagnostic); } - - None } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs index 128844f98b7bb4..649fc972fcd93a 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs @@ -4,7 +4,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_trivia::leading_indentation; use ruff_source_file::Line; -use crate::{OldDiagnostic, Violation}; +use crate::{Violation, checkers::ast::LintContext}; /// ## What it does /// Checks for mixed tabs and spaces in indentation. @@ -37,15 +37,13 @@ impl Violation for MixedSpacesAndTabs { } /// E101 -pub(crate) fn mixed_spaces_and_tabs(line: &Line) -> Option { +pub(crate) fn mixed_spaces_and_tabs(line: &Line, context: &LintContext) { let indent = leading_indentation(line.as_str()); if indent.contains(' ') && indent.contains('\t') { - Some(OldDiagnostic::new( + context.report_diagnostic( MixedSpacesAndTabs, TextRange::at(line.start(), indent.text_len()), - )) - } else { - None + ); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs index 10fb88ac8ddef7..4e75efb2b8f0f9 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs @@ -4,7 +4,8 @@ use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{OldDiagnostic, Violation}; +use crate::Violation; +use crate::checkers::ast::LintContext; /// ## What it does /// Checks for indentation that uses tabs. @@ -33,11 +34,7 @@ impl Violation for TabIndentation { } /// W191 -pub(crate) fn tab_indentation( - diagnostics: &mut Vec, - locator: &Locator, - indexer: &Indexer, -) { +pub(crate) fn tab_indentation(context: &LintContext, locator: &Locator, indexer: &Indexer) { let contents = locator.contents().as_bytes(); let mut offset = 0; while let Some(index) = memchr::memchr(b'\t', &contents[offset..]) { @@ -46,7 +43,7 @@ pub(crate) fn tab_indentation( // Determine whether the tab is part of the line's indentation. if let Some(indent) = tab_indentation_at_line_start(range.start(), locator, indexer) { - diagnostics.push(OldDiagnostic::new(TabIndentation, indent)); + context.report_diagnostic(TabIndentation, indent); } // Advance to the next line. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs index 4b41734f7f740c..d7facdf9e12273 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/too_many_newlines_at_end_of_file.rs @@ -6,7 +6,7 @@ use ruff_notebook::CellOffsets; use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix, checkers::ast::LintContext}; /// ## What it does /// Checks for files with multiple trailing blank lines. @@ -59,16 +59,16 @@ impl AlwaysFixableViolation for TooManyNewlinesAtEndOfFile { /// W391 pub(crate) fn too_many_newlines_at_end_of_file( - diagnostics: &mut Vec, + context: &LintContext, tokens: &Tokens, cell_offsets: Option<&CellOffsets>, ) { let mut tokens_iter = tokens.iter().rev().peekable(); if let Some(cell_offsets) = cell_offsets { - diagnostics.extend(notebook_newline_diagnostics(tokens_iter, cell_offsets)); - } else if let Some(diagnostic) = newline_diagnostic(&mut tokens_iter, false) { - diagnostics.push(diagnostic); + notebook_newline_diagnostics(tokens_iter, cell_offsets, context); + } else { + newline_diagnostic(&mut tokens_iter, false, context); } } @@ -76,8 +76,8 @@ pub(crate) fn too_many_newlines_at_end_of_file( fn notebook_newline_diagnostics<'a>( mut tokens_iter: Peekable>, cell_offsets: &CellOffsets, -) -> Vec { - let mut results = Vec::new(); + context: &LintContext, +) { let offset_iter = cell_offsets.iter().rev(); // NB: When interpreting the below, recall that the iterators @@ -88,20 +88,16 @@ fn notebook_newline_diagnostics<'a>( .peeking_take_while(|tok| tok.end() >= offset) .for_each(drop); - let Some(diagnostic) = newline_diagnostic(&mut tokens_iter, true) else { - continue; - }; - - results.push(diagnostic); + newline_diagnostic(&mut tokens_iter, true, context); } - results } /// Possible diagnostic, with fix, for too many newlines in cell or source file fn newline_diagnostic<'a>( tokens_iter: &mut Peekable>, in_notebook: bool, -) -> Option { + context: &LintContext, +) { let mut num_trailing_newlines: u32 = 0; let mut newline_range_start: Option = None; let mut newline_range_end: Option = None; @@ -127,23 +123,24 @@ fn newline_diagnostic<'a>( } if num_trailing_newlines == 0 || num_trailing_newlines == 1 { - return None; + return; } - let (start, end) = (match (newline_range_start, newline_range_end) { + let Some((start, end)) = (match (newline_range_start, newline_range_end) { (Some(s), Some(e)) => Some((s, e)), _ => None, - })?; + }) else { + return; + }; let diagnostic_range = TextRange::new(start, end); - Some( - OldDiagnostic::new( + context + .report_diagnostic( TooManyNewlinesAtEndOfFile { num_trailing_newlines, in_notebook, }, diagnostic_range, ) - .with_fix(Fix::safe_edit(Edit::range_deletion(diagnostic_range))), - ) + .set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic_range))); } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs index 7db1304722435e..1da5f5ba17b354 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -4,9 +4,10 @@ use ruff_source_file::Line; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::registry::Rule; use crate::settings::LinterSettings; -use crate::{AlwaysFixableViolation, Applicability, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for superfluous trailing whitespace. @@ -78,7 +79,8 @@ pub(crate) fn trailing_whitespace( locator: &Locator, indexer: &Indexer, settings: &LinterSettings, -) -> Option { + context: &LintContext, +) { let whitespace_len: TextSize = line .chars() .rev() @@ -95,7 +97,7 @@ pub(crate) fn trailing_whitespace( }; if range == line.range() { if settings.rules.enabled(Rule::BlankLineWithWhitespace) { - let mut diagnostic = OldDiagnostic::new(BlankLineWithWhitespace, range); + let mut diagnostic = context.report_diagnostic(BlankLineWithWhitespace, range); // Remove any preceding continuations, to avoid introducing a potential // syntax error. diagnostic.set_fix(Fix::applicable_edit( @@ -107,16 +109,13 @@ pub(crate) fn trailing_whitespace( )), applicability, )); - return Some(diagnostic); } } else if settings.rules.enabled(Rule::TrailingWhitespace) { - let mut diagnostic = OldDiagnostic::new(TrailingWhitespace, range); + let mut diagnostic = context.report_diagnostic(TrailingWhitespace, range); diagnostic.set_fix(Fix::applicable_edit( Edit::range_deletion(range), applicability, )); - return Some(diagnostic); } } - None } diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index b3f63e584b1891..e60fd420721c90 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,8 +3,9 @@ use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::noqa::{self, Directive, FileNoqaDirectives, NoqaDirectives}; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Check for `noqa` annotations that suppress all diagnostics, as opposed to @@ -74,20 +75,20 @@ impl Violation for BlanketNOQA { /// PGH004 pub(crate) fn blanket_noqa( - diagnostics: &mut Vec, + context: &LintContext, noqa_directives: &NoqaDirectives, locator: &Locator, file_noqa_directives: &FileNoqaDirectives, ) { for line in file_noqa_directives.lines() { if let Directive::All(_) = line.parsed_file_exemption { - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic( BlanketNOQA { missing_colon: false, file_exemption: true, }, line.range(), - )); + ); } } @@ -105,7 +106,7 @@ pub(crate) fn blanket_noqa( // Ex) `# noqa F401` let start = all.end(); let end = start + cursor.token_len(); - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( BlanketNOQA { missing_colon: true, file_exemption: false, @@ -113,16 +114,15 @@ pub(crate) fn blanket_noqa( TextRange::new(all.start(), end), ); diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(':'.to_string(), start))); - diagnostics.push(diagnostic); } else { // Otherwise, it looks like an intentional blanket `noqa` annotation. - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic( BlanketNOQA { missing_colon: false, file_exemption: false, }, all.range(), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs index 8388acd8e6a960..bf0d5346c1a847 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs @@ -9,7 +9,8 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::TextSize; use crate::Locator; -use crate::{OldDiagnostic, Violation}; +use crate::Violation; +use crate::checkers::ast::LintContext; /// ## What it does /// Check for `type: ignore` annotations that suppress all type warnings, as @@ -52,7 +53,7 @@ impl Violation for BlanketTypeIgnore { /// PGH003 pub(crate) fn blanket_type_ignore( - diagnostics: &mut Vec, + context: &LintContext, comment_ranges: &CommentRanges, locator: &Locator, ) { @@ -92,10 +93,10 @@ pub(crate) fn blanket_type_ignore( // Match the optional `[...]` tag. if let Ok(codes) = parse_type_ignore_tag(comment) { if codes.is_empty() { - diagnostics.push(OldDiagnostic::new( + context.report_diagnostic( BlanketTypeIgnore, range.add_start(TextSize::try_from(start).unwrap()), - )); + ); } } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs index 4f3566850cbf47..e5ed24bc189afc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs @@ -1,7 +1,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; -use crate::{OldDiagnostic, Violation}; +use crate::{Violation, checkers::ast::LintContext}; const BIDI_UNICODE: [char; 10] = [ '\u{202A}', //{LEFT-TO-RIGHT EMBEDDING} @@ -53,10 +53,8 @@ impl Violation for BidirectionalUnicode { } /// PLE2502 -pub(crate) fn bidirectional_unicode(line: &Line) -> Vec { - let mut diagnostics = Vec::new(); +pub(crate) fn bidirectional_unicode(line: &Line, context: &LintContext) { if line.contains(BIDI_UNICODE) { - diagnostics.push(OldDiagnostic::new(BidirectionalUnicode, line.full_range())); + context.report_diagnostic(BidirectionalUnicode, line.full_range()); } - diagnostics } diff --git a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs index 2dd0ac29b7d031..deb3543f2130f4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs @@ -4,7 +4,8 @@ use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; use crate::Locator; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::checkers::ast::LintContext; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for a # symbol appearing on a line not followed by an actual comment. @@ -45,7 +46,7 @@ impl Violation for EmptyComment { /// PLR2044 pub(crate) fn empty_comments( - diagnostics: &mut Vec, + context: &LintContext, comment_ranges: &CommentRanges, locator: &Locator, ) { @@ -58,14 +59,12 @@ pub(crate) fn empty_comments( } // If the line contains an empty comment, add a diagnostic. - if let Some(diagnostic) = empty_comment(range, locator) { - diagnostics.push(diagnostic); - } + empty_comment(context, range, locator); } } /// Return a [`Diagnostic`] if the comment at the given [`TextRange`] is empty. -fn empty_comment(range: TextRange, locator: &Locator) -> Option { +fn empty_comment(context: &LintContext, range: TextRange, locator: &Locator) { // Check: is the comment empty? if !locator .slice(range) @@ -73,7 +72,7 @@ fn empty_comment(range: TextRange, locator: &Locator) -> Option { .skip(1) .all(is_python_whitespace) { - return None; + return; } // Find the location of the `#`. @@ -96,13 +95,13 @@ fn empty_comment(range: TextRange, locator: &Locator) -> Option { } }); - Some( - OldDiagnostic::new(EmptyComment, TextRange::new(first_hash_col, line.end())).with_fix( - Fix::safe_edit(if let Some(deletion_start_col) = deletion_start_col { + context + .report_diagnostic(EmptyComment, TextRange::new(first_hash_col, line.end())) + .set_fix(Fix::safe_edit( + if let Some(deletion_start_col) = deletion_start_col { Edit::deletion(line.start() + deletion_start_col, line.end()) } else { Edit::range_deletion(locator.full_line_range(first_hash_col)) - }), - ), - ) + }, + )); } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index 8bbc5541a752ea..1e13bb733da5f0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -3,7 +3,8 @@ use ruff_python_parser::{Token, TokenKind}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::checkers::ast::LintContext; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for strings that contain the control character `BS`. @@ -180,11 +181,7 @@ impl Violation for InvalidCharacterZeroWidthSpace { } /// PLE2510, PLE2512, PLE2513, PLE2514, PLE2515 -pub(crate) fn invalid_string_characters( - diagnostics: &mut Vec, - token: &Token, - locator: &Locator, -) { +pub(crate) fn invalid_string_characters(context: &LintContext, token: &Token, locator: &Locator) { let text = match token.kind() { // We can't use the `value` field since it's decoded and e.g. for f-strings removed a curly // brace that escaped another curly brace, which would gives us wrong column information. @@ -197,13 +194,22 @@ pub(crate) fn invalid_string_characters( let c = match_.chars().next().unwrap(); let range = TextRange::at(location, c.text_len()); let (replacement, mut diagnostic) = match c { - '\x08' => ("\\b", OldDiagnostic::new(InvalidCharacterBackspace, range)), - '\x1A' => ("\\x1A", OldDiagnostic::new(InvalidCharacterSub, range)), - '\x1B' => ("\\x1B", OldDiagnostic::new(InvalidCharacterEsc, range)), - '\0' => ("\\0", OldDiagnostic::new(InvalidCharacterNul, range)), + '\x08' => ( + "\\b", + context.report_diagnostic(InvalidCharacterBackspace, range), + ), + '\x1A' => ( + "\\x1A", + context.report_diagnostic(InvalidCharacterSub, range), + ), + '\x1B' => ( + "\\x1B", + context.report_diagnostic(InvalidCharacterEsc, range), + ), + '\0' => ("\\0", context.report_diagnostic(InvalidCharacterNul, range)), '\u{200b}' => ( "\\u200b", - OldDiagnostic::new(InvalidCharacterZeroWidthSpace, range), + context.report_diagnostic(InvalidCharacterZeroWidthSpace, range), ), _ => { continue; @@ -214,7 +220,5 @@ pub(crate) fn invalid_string_characters( let edit = Edit::range_replacement(replacement.to_string(), range); diagnostic.set_fix(Fix::safe_edit(edit)); } - - diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs index 94dde654dd6ab8..5ee814f9f825f2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -5,7 +5,8 @@ use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::checkers::ast::LintContext; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for extraneous parentheses. @@ -114,11 +115,7 @@ fn match_extraneous_parentheses(tokens: &mut Iter<'_, Token>) -> Option<(TextRan } /// UP034 -pub(crate) fn extraneous_parentheses( - diagnostics: &mut Vec, - tokens: &Tokens, - locator: &Locator, -) { +pub(crate) fn extraneous_parentheses(context: &LintContext, tokens: &Tokens, locator: &Locator) { let mut token_iter = tokens.iter(); while let Some(token) = token_iter.next() { if !matches!(token.kind(), TokenKind::Lpar) { @@ -129,7 +126,7 @@ pub(crate) fn extraneous_parentheses( continue; }; - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( ExtraneousParentheses, TextRange::new(start_range.start(), end_range.end()), ); @@ -139,6 +136,5 @@ pub(crate) fn extraneous_parentheses( start_range.start(), end_range.end(), ))); - diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs index cadf274f081dbd..3ee47fc94c5010 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs @@ -9,7 +9,8 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Locator; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::checkers::ast::LintContext; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for unnecessary UTF-8 encoding declarations. @@ -66,7 +67,7 @@ struct CodingCommentRange { /// UP009 pub(crate) fn unnecessary_coding_comment( - diagnostics: &mut Vec, + context: &LintContext, locator: &Locator, comment_ranges: &CommentRanges, ) { @@ -106,9 +107,9 @@ pub(crate) fn unnecessary_coding_comment( } let fix = Fix::safe_edit(Edit::range_deletion(range.line)); - let diagnostic = OldDiagnostic::new(UTF8EncodingDeclaration, range.comment); - - diagnostics.push(diagnostic.with_fix(fix)); + context + .report_diagnostic(UTF8EncodingDeclaration, range.comment) + .set_fix(fix); } struct CodingCommentIterator<'a> { diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 242a6f2d0e06ea..39cab6081954a3 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -463,7 +463,7 @@ mod tests { let contents = fs::read_to_string(path)?; let source_file = SourceFileBuilder::new("pyproject.toml", contents).finish(); let messages = lint_pyproject_toml( - source_file, + &source_file, &settings::LinterSettings::for_rule(Rule::InvalidPyprojectToml), ); assert_messages!(snapshot, messages); diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index a8c1d89fcdaa32..d1b1ef67edc77f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -7,13 +7,12 @@ use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; -use crate::checkers::ast::Checker; +use crate::Violation; +use crate::checkers::ast::{Checker, LintContext}; use crate::preview::is_unicode_to_unicode_confusables_enabled; -use crate::registry::AsRule; use crate::rules::ruff::rules::Context; use crate::rules::ruff::rules::confusables::confusable; use crate::settings::LinterSettings; -use crate::{OldDiagnostic, Violation}; /// ## What it does /// Checks for ambiguous Unicode characters in strings. @@ -176,14 +175,14 @@ impl Violation for AmbiguousUnicodeCharacterComment { /// RUF003 pub(crate) fn ambiguous_unicode_character_comment( - diagnostics: &mut Vec, + context: &LintContext, locator: &Locator, range: TextRange, settings: &LinterSettings, ) { let text = locator.slice(range); for candidate in ambiguous_unicode_character(text, range, settings) { - diagnostics.extend(candidate.into_diagnostic(Context::Comment, settings)); + candidate.into_diagnostic(Context::Comment, settings, context); } } @@ -342,37 +341,41 @@ impl Candidate { } } - fn into_diagnostic(self, context: Context, settings: &LinterSettings) -> Option { + fn into_diagnostic( + self, + context: Context, + settings: &LinterSettings, + lint_context: &LintContext, + ) { if !settings.allowed_confusables.contains(&self.confusable) { let char_range = TextRange::at(self.offset, self.confusable.text_len()); - let diagnostic = match context { - Context::String => OldDiagnostic::new( + match context { + Context::String => lint_context.report_diagnostic_if_enabled( AmbiguousUnicodeCharacterString { confusable: self.confusable, representant: self.representant, }, char_range, + settings, ), - Context::Docstring => OldDiagnostic::new( + Context::Docstring => lint_context.report_diagnostic_if_enabled( AmbiguousUnicodeCharacterDocstring { confusable: self.confusable, representant: self.representant, }, char_range, + settings, ), - Context::Comment => OldDiagnostic::new( + Context::Comment => lint_context.report_diagnostic_if_enabled( AmbiguousUnicodeCharacterComment { confusable: self.confusable, representant: self.representant, }, char_range, + settings, ), }; - if settings.rules.enabled(diagnostic.rule()) { - return Some(diagnostic); - } } - None } fn report_diagnostic(self, checker: &Checker, context: Context) { diff --git a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs index 1c95430ea72be6..b943c6553604d7 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs @@ -4,7 +4,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; use ruff_text_size::{TextRange, TextSize}; -use crate::{OldDiagnostic, Violation}; +use crate::{Violation, checkers::ast::LintContext}; /// ## What it does /// Checks for form feed characters preceded by either a space or a tab. @@ -49,11 +49,13 @@ const SPACE: u8 = b' '; const TAB: u8 = b'\t'; /// RUF054 -pub(crate) fn indented_form_feed(line: &Line) -> Option { - let index_relative_to_line = memchr(FORM_FEED, line.as_bytes())?; +pub(crate) fn indented_form_feed(line: &Line, context: &LintContext) { + let Some(index_relative_to_line) = memchr(FORM_FEED, line.as_bytes()) else { + return; + }; if index_relative_to_line == 0 { - return None; + return; } if line[..index_relative_to_line] @@ -61,12 +63,14 @@ pub(crate) fn indented_form_feed(line: &Line) -> Option { .iter() .any(|byte| *byte != SPACE && *byte != TAB) { - return None; + return; } - let relative_index = u32::try_from(index_relative_to_line).ok()?; + let Ok(relative_index) = u32::try_from(index_relative_to_line) else { + return; + }; let absolute_index = line.start() + TextSize::new(relative_index); let range = TextRange::at(absolute_index, 1.into()); - Some(OldDiagnostic::new(IndentedFormFeed, range)) + context.report_diagnostic(IndentedFormFeed, range); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs index 14f0a4e84d0ab2..734a544b726d7c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs @@ -2,10 +2,11 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::noqa::{Code, Directive}; use crate::noqa::{Codes, NoqaDirectives}; use crate::registry::Rule; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `noqa` codes that are invalid. @@ -48,7 +49,7 @@ impl AlwaysFixableViolation for InvalidRuleCode { /// RUF102 for invalid noqa codes pub(crate) fn invalid_noqa_code( - diagnostics: &mut Vec, + context: &LintContext, noqa_directives: &NoqaDirectives, locator: &Locator, external: &[String], @@ -69,11 +70,11 @@ pub(crate) fn invalid_noqa_code( .partition(|&code| code_is_valid(code, external)); if valid_codes.is_empty() { - diagnostics.push(all_codes_invalid_diagnostic(directive, invalid_codes)); + all_codes_invalid_diagnostic(directive, invalid_codes, context); } else { - diagnostics.extend(invalid_codes.into_iter().map(|invalid_code| { - some_codes_are_invalid_diagnostic(directive, invalid_code, locator) - })); + for invalid_code in invalid_codes { + some_codes_are_invalid_diagnostic(directive, invalid_code, locator, context); + } } } } @@ -86,36 +87,40 @@ fn code_is_valid(code: &Code, external: &[String]) -> bool { fn all_codes_invalid_diagnostic( directive: &Codes<'_>, invalid_codes: Vec<&Code<'_>>, -) -> OldDiagnostic { - OldDiagnostic::new( - InvalidRuleCode { - rule_code: invalid_codes - .into_iter() - .map(Code::as_str) - .collect::>() - .join(", "), - }, - directive.range(), - ) - .with_fix(Fix::safe_edit(Edit::range_deletion(directive.range()))) + context: &LintContext, +) { + context + .report_diagnostic( + InvalidRuleCode { + rule_code: invalid_codes + .into_iter() + .map(Code::as_str) + .collect::>() + .join(", "), + }, + directive.range(), + ) + .set_fix(Fix::safe_edit(Edit::range_deletion(directive.range()))); } fn some_codes_are_invalid_diagnostic( codes: &Codes, invalid_code: &Code, locator: &Locator, -) -> OldDiagnostic { - let diagnostic = OldDiagnostic::new( - InvalidRuleCode { - rule_code: invalid_code.to_string(), - }, - invalid_code.range(), - ); - diagnostic.with_fix(Fix::safe_edit(remove_invalid_noqa( - codes, - invalid_code, - locator, - ))) + context: &LintContext, +) { + context + .report_diagnostic( + InvalidRuleCode { + rule_code: invalid_code.to_string(), + }, + invalid_code.range(), + ) + .set_fix(Fix::safe_edit(remove_invalid_noqa( + codes, + invalid_code, + locator, + ))); } fn remove_invalid_noqa(codes: &Codes, invalid_code: &Code, locator: &Locator) -> Edit { diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index 5093883f29c3bd..e11a9a7ad19351 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -1,9 +1,10 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_text_size::Ranged; +use crate::checkers::ast::LintContext; use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::rule_redirects::get_redirect_target; -use crate::{AlwaysFixableViolation, Edit, Fix, OldDiagnostic}; +use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does /// Checks for `noqa` directives that use redirected rule codes. @@ -43,38 +44,32 @@ impl AlwaysFixableViolation for RedirectedNOQA { } /// RUF101 for in-line noqa directives -pub(crate) fn redirected_noqa( - diagnostics: &mut Vec, - noqa_directives: &NoqaDirectives, -) { +pub(crate) fn redirected_noqa(context: &LintContext, noqa_directives: &NoqaDirectives) { for line in noqa_directives.lines() { let Directive::Codes(directive) = &line.directive else { continue; }; - build_diagnostics(diagnostics, directive); + build_diagnostics(context, directive); } } /// RUF101 for file noqa directives -pub(crate) fn redirected_file_noqa( - diagnostics: &mut Vec, - noqa_directives: &FileNoqaDirectives, -) { +pub(crate) fn redirected_file_noqa(context: &LintContext, noqa_directives: &FileNoqaDirectives) { for line in noqa_directives.lines() { let Directive::Codes(codes) = &line.parsed_file_exemption else { continue; }; - build_diagnostics(diagnostics, codes); + build_diagnostics(context, codes); } } /// Convert a sequence of [Codes] into [Diagnostic]s and append them to `diagnostics`. -fn build_diagnostics(diagnostics: &mut Vec, codes: &Codes<'_>) { +pub(crate) fn build_diagnostics(context: &LintContext, codes: &Codes<'_>) { for code in codes.iter() { if let Some(redirected) = get_redirect_target(code.as_str()) { - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = context.report_diagnostic( RedirectedNOQA { original: code.to_string(), target: redirected.to_string(), @@ -85,7 +80,6 @@ fn build_diagnostics(diagnostics: &mut Vec, codes: &Codes<'_>) { redirected.to_string(), code.range(), ))); - diagnostics.push(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs index d3a43f21f54188..3fc282ad3a09a5 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs @@ -18,8 +18,9 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::TextSize; use crate::Locator; +use crate::checkers::ast::LintContext; use crate::registry::Rule; -use crate::{Edit, Fix, FixAvailability, OldDiagnostic, Violation}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// Check if a comment exists anywhere in a given file fn comment_exists(text: &str, locator: &Locator, comment_ranges: &CommentRanges) -> bool { @@ -48,7 +49,7 @@ pub(crate) const TEST_RULES: &[Rule] = &[ ]; pub(crate) trait TestRule { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option; + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges, context: &LintContext); } /// ## What it does @@ -79,11 +80,8 @@ impl Violation for StableTestRule { } impl TestRule for StableTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - StableTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(StableTestRule, ruff_text_size::TextRange::default()); } } @@ -115,15 +113,12 @@ impl Violation for StableTestRuleSafeFix { } impl TestRule for StableTestRuleSafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges, context: &LintContext) { let comment = "# fix from stable-test-rule-safe-fix\n".to_string(); - if comment_exists(&comment, locator, comment_ranges) { - None - } else { - Some( - OldDiagnostic::new(StableTestRuleSafeFix, ruff_text_size::TextRange::default()) - .with_fix(Fix::safe_edit(Edit::insertion(comment, TextSize::new(0)))), - ) + if !comment_exists(&comment, locator, comment_ranges) { + context + .report_diagnostic(StableTestRuleSafeFix, ruff_text_size::TextRange::default()) + .set_fix(Fix::safe_edit(Edit::insertion(comment, TextSize::new(0)))); } } } @@ -156,18 +151,15 @@ impl Violation for StableTestRuleUnsafeFix { } impl TestRule for StableTestRuleUnsafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges, context: &LintContext) { let comment = "# fix from stable-test-rule-unsafe-fix\n".to_string(); - if comment_exists(&comment, locator, comment_ranges) { - None - } else { - Some( - OldDiagnostic::new( + if !comment_exists(&comment, locator, comment_ranges) { + context + .report_diagnostic( StableTestRuleUnsafeFix, ruff_text_size::TextRange::default(), ) - .with_fix(Fix::unsafe_edit(Edit::insertion(comment, TextSize::new(0)))), - ) + .set_fix(Fix::unsafe_edit(Edit::insertion(comment, TextSize::new(0)))); } } } @@ -200,21 +192,18 @@ impl Violation for StableTestRuleDisplayOnlyFix { } impl TestRule for StableTestRuleDisplayOnlyFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges, context: &LintContext) { let comment = "# fix from stable-test-rule-display-only-fix\n".to_string(); - if comment_exists(&comment, locator, comment_ranges) { - None - } else { - Some( - OldDiagnostic::new( + if !comment_exists(&comment, locator, comment_ranges) { + context + .report_diagnostic( StableTestRuleDisplayOnlyFix, ruff_text_size::TextRange::default(), ) - .with_fix(Fix::display_only_edit(Edit::insertion( + .set_fix(Fix::display_only_edit(Edit::insertion( comment, TextSize::new(0), - ))), - ) + ))); } } } @@ -247,11 +236,8 @@ impl Violation for PreviewTestRule { } impl TestRule for PreviewTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - PreviewTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(PreviewTestRule, ruff_text_size::TextRange::default()); } } @@ -283,11 +269,8 @@ impl Violation for DeprecatedTestRule { } impl TestRule for DeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - DeprecatedTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(DeprecatedTestRule, ruff_text_size::TextRange::default()); } } @@ -319,11 +302,11 @@ impl Violation for AnotherDeprecatedTestRule { } impl TestRule for AnotherDeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic( AnotherDeprecatedTestRule, ruff_text_size::TextRange::default(), - )) + ); } } @@ -355,11 +338,8 @@ impl Violation for RemovedTestRule { } impl TestRule for RemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - RemovedTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(RemovedTestRule, ruff_text_size::TextRange::default()); } } @@ -391,11 +371,8 @@ impl Violation for AnotherRemovedTestRule { } impl TestRule for AnotherRemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - AnotherRemovedTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(AnotherRemovedTestRule, ruff_text_size::TextRange::default()); } } @@ -427,11 +404,8 @@ impl Violation for RedirectedFromTestRule { } impl TestRule for RedirectedFromTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - RedirectedFromTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(RedirectedFromTestRule, ruff_text_size::TextRange::default()); } } @@ -463,11 +437,8 @@ impl Violation for RedirectedToTestRule { } impl TestRule for RedirectedToTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( - RedirectedToTestRule, - ruff_text_size::TextRange::default(), - )) + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic(RedirectedToTestRule, ruff_text_size::TextRange::default()); } } @@ -499,10 +470,10 @@ impl Violation for RedirectedFromPrefixTestRule { } impl TestRule for RedirectedFromPrefixTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { - Some(OldDiagnostic::new( + fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges, context: &LintContext) { + context.report_diagnostic( RedirectedFromPrefixTestRule, ruff_text_size::TextRange::default(), - )) + ); } } From d65bd69963e8b6ec05e465b1ecbdd391883645ef Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 30 May 2025 21:36:20 +0800 Subject: [PATCH 287/487] [`airflow`] Add unsafe fix for module moved cases (`AIR312`) (#18363) ## Summary Follow up on https://github.com/astral-sh/ruff/pull/18093 and apply it to AIR312 ## Test Plan The existing test fixtures have been updated --- .../resources/test/fixtures/airflow/AIR312.py | 82 +- .../suggested_to_move_to_provider_in_3.rs | 41 +- ...les__airflow__tests__AIR312_AIR312.py.snap | 769 ++++++++++++++---- 3 files changed, 659 insertions(+), 233 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR312.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR312.py index 84a1f5f5fcaf19..0752511706ced3 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR312.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR312.py @@ -7,49 +7,71 @@ from airflow.operators.datetime import BranchDateTimeOperator from airflow.operators.empty import EmptyOperator from airflow.operators.latest_only import LatestOnlyOperator +from airflow.operators.trigger_dagrun import TriggerDagRunOperator +from airflow.operators.weekday import BranchDayOfWeekOperator +from airflow.sensors.date_time import DateTimeSensor + +FSHook() +PackageIndexHook() +SubprocessHook() + +BashOperator() +BranchDateTimeOperator() +TriggerDagRunOperator() +EmptyOperator() + +LatestOnlyOperator() +BranchDayOfWeekOperator() +DateTimeSensor() + from airflow.operators.python import ( BranchPythonOperator, PythonOperator, PythonVirtualenvOperator, ShortCircuitOperator, ) -from airflow.operators.trigger_dagrun import TriggerDagRunOperator -from airflow.operators.weekday import BranchDayOfWeekOperator -from airflow.sensors.date_time import DateTimeSensor, DateTimeSensorAsync +from airflow.sensors.date_time import DateTimeSensorAsync from airflow.sensors.external_task import ( ExternalTaskMarker, ExternalTaskSensor, - +) +from airflow.sensors.time_sensor import ( + TimeSensor, + TimeSensorAsync, ) from airflow.sensors.filesystem import FileSensor -from airflow.sensors.time_delta import TimeDeltaSensor, TimeDeltaSensorAsync -from airflow.sensors.time_sensor import TimeSensor, TimeSensorAsync + +BranchPythonOperator() +PythonOperator() +PythonVirtualenvOperator() +ShortCircuitOperator() +DateTimeSensorAsync() +ExternalTaskMarker() +ExternalTaskSensor() +FileSensor() +TimeSensor() +TimeSensorAsync() + +from airflow.sensors.time_delta import ( + TimeDeltaSensor, + TimeDeltaSensorAsync, +) from airflow.sensors.weekday import DayOfWeekSensor -from airflow.triggers.external_task import DagStateTrigger, WorkflowTrigger +from airflow.triggers.external_task import ( + DagStateTrigger, + WorkflowTrigger, +) from airflow.triggers.file import FileTrigger -from airflow.triggers.temporal import DateTimeTrigger, TimeDeltaTrigger - -FSHook() -PackageIndexHook() -SubprocessHook() -BashOperator() -BranchDateTimeOperator() -TriggerDagRunOperator() -EmptyOperator() -LatestOnlyOperator() -( - BranchPythonOperator(), - PythonOperator(), - PythonVirtualenvOperator(), - ShortCircuitOperator(), +from airflow.triggers.temporal import ( + DateTimeTrigger, + TimeDeltaTrigger, ) -BranchDayOfWeekOperator() -DateTimeSensor(), DateTimeSensorAsync() -ExternalTaskMarker(), ExternalTaskSensor() -FileSensor() -TimeSensor(), TimeSensorAsync() -TimeDeltaSensor(), TimeDeltaSensorAsync() + +TimeDeltaSensor() +TimeDeltaSensorAsync() DayOfWeekSensor() -DagStateTrigger(), WorkflowTrigger() +DagStateTrigger() +WorkflowTrigger() FileTrigger() -DateTimeTrigger(), TimeDeltaTrigger() +DateTimeTrigger() +TimeDeltaTrigger() diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs index 1177e9581820c6..c9f991b94d7847 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_move_to_provider_in_3.rs @@ -1,7 +1,9 @@ -use crate::importer::ImportRequest; - -use crate::rules::airflow::helpers::{ProviderReplacement, is_guarded_by_try_except}; -use crate::{Edit, Fix, FixAvailability, Violation}; +use crate::checkers::ast::Checker; +use crate::rules::airflow::helpers::{ + ProviderReplacement, generate_import_edit, generate_remove_and_runtime_import_edit, + is_guarded_by_try_except, +}; +use crate::{FixAvailability, Violation}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{Expr, ExprAttribute}; @@ -9,8 +11,6 @@ use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use ruff_text_size::TextRange; -use crate::checkers::ast::Checker; - /// ## What it does /// Checks for uses of Airflow functions and values that have been moved to its providers /// but still have a compatibility layer (e.g., `apache-airflow-providers-standard`). @@ -302,22 +302,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan if is_guarded_by_try_except(expr, module, name, checker.semantic()) { return; } + let mut diagnostic = checker.report_diagnostic( + Airflow3SuggestedToMoveToProvider { + deprecated: qualified_name, + replacement: replacement.clone(), + }, + ranged, + ); - checker - .report_diagnostic( - Airflow3SuggestedToMoveToProvider { - deprecated: qualified_name, - replacement: replacement.clone(), - }, - ranged.range(), - ) - .try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from(module, name), - expr.start(), - checker.semantic(), - )?; - let replacement_edit = Edit::range_replacement(binding, ranged.range()); - Ok(Fix::safe_edits(import_edit, [replacement_edit])) - }); + if let Some(fix) = generate_import_edit(expr, checker, module, name, ranged) + .or_else(|| generate_remove_and_runtime_import_edit(expr, checker, module, name)) + { + diagnostic.set_fix(fix); + } } diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap index f30928c6c35193..e59605546d9cba 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR312_AIR312.py.snap @@ -1,304 +1,713 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR312.py:32:1: AIR312 `airflow.hooks.filesystem.FSHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:14:1: AIR312 [*] `airflow.hooks.filesystem.FSHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -30 | from airflow.triggers.temporal import DateTimeTrigger, TimeDeltaTrigger -31 | -32 | FSHook() +12 | from airflow.sensors.date_time import DateTimeSensor +13 | +14 | FSHook() | ^^^^^^ AIR312 -33 | PackageIndexHook() -34 | SubprocessHook() +15 | PackageIndexHook() +16 | SubprocessHook() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `FSHook` from `airflow.providers.standard.hooks.filesystem` instead. -AIR312.py:33:1: AIR312 `airflow.hooks.package_index.PackageIndexHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from airflow.hooks.filesystem import FSHook +4 3 | from airflow.hooks.package_index import PackageIndexHook +5 4 | from airflow.hooks.subprocess import SubprocessHook +6 5 | from airflow.operators.bash import BashOperator +-------------------------------------------------------------------------------- +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.hooks.filesystem import FSHook +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:15:1: AIR312 [*] `airflow.hooks.package_index.PackageIndexHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -32 | FSHook() -33 | PackageIndexHook() +14 | FSHook() +15 | PackageIndexHook() | ^^^^^^^^^^^^^^^^ AIR312 -34 | SubprocessHook() -35 | BashOperator() +16 | SubprocessHook() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PackageIndexHook` from `airflow.providers.standard.hooks.package_index` instead. -AIR312.py:34:1: AIR312 `airflow.hooks.subprocess.SubprocessHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 3 | from airflow.hooks.filesystem import FSHook +4 |-from airflow.hooks.package_index import PackageIndexHook +5 4 | from airflow.hooks.subprocess import SubprocessHook +6 5 | from airflow.operators.bash import BashOperator +7 6 | from airflow.operators.datetime import BranchDateTimeOperator +-------------------------------------------------------------------------------- +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.hooks.package_index import PackageIndexHook +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:16:1: AIR312 [*] `airflow.hooks.subprocess.SubprocessHook` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -32 | FSHook() -33 | PackageIndexHook() -34 | SubprocessHook() +14 | FSHook() +15 | PackageIndexHook() +16 | SubprocessHook() | ^^^^^^^^^^^^^^ AIR312 -35 | BashOperator() -36 | BranchDateTimeOperator() +17 | +18 | BashOperator() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `SubprocessHook` from `airflow.providers.standard.hooks.subprocess` instead. -AIR312.py:35:1: AIR312 `airflow.operators.bash.BashOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +2 2 | +3 3 | from airflow.hooks.filesystem import FSHook +4 4 | from airflow.hooks.package_index import PackageIndexHook +5 |-from airflow.hooks.subprocess import SubprocessHook +6 5 | from airflow.operators.bash import BashOperator +7 6 | from airflow.operators.datetime import BranchDateTimeOperator +8 7 | from airflow.operators.empty import EmptyOperator +-------------------------------------------------------------------------------- +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.hooks.subprocess import SubprocessHook +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:18:1: AIR312 [*] `airflow.operators.bash.BashOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -33 | PackageIndexHook() -34 | SubprocessHook() -35 | BashOperator() +16 | SubprocessHook() +17 | +18 | BashOperator() | ^^^^^^^^^^^^ AIR312 -36 | BranchDateTimeOperator() -37 | TriggerDagRunOperator() +19 | BranchDateTimeOperator() +20 | TriggerDagRunOperator() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BashOperator` from `airflow.providers.standard.operators.bash` instead. -AIR312.py:36:1: AIR312 `airflow.operators.datetime.BranchDateTimeOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +3 3 | from airflow.hooks.filesystem import FSHook +4 4 | from airflow.hooks.package_index import PackageIndexHook +5 5 | from airflow.hooks.subprocess import SubprocessHook +6 |-from airflow.operators.bash import BashOperator +7 6 | from airflow.operators.datetime import BranchDateTimeOperator +8 7 | from airflow.operators.empty import EmptyOperator +9 8 | from airflow.operators.latest_only import LatestOnlyOperator +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.bash import BashOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:19:1: AIR312 [*] `airflow.operators.datetime.BranchDateTimeOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -34 | SubprocessHook() -35 | BashOperator() -36 | BranchDateTimeOperator() +18 | BashOperator() +19 | BranchDateTimeOperator() | ^^^^^^^^^^^^^^^^^^^^^^ AIR312 -37 | TriggerDagRunOperator() -38 | EmptyOperator() +20 | TriggerDagRunOperator() +21 | EmptyOperator() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchDateTimeOperator` from `airflow.providers.standard.operators.datetime` instead. -AIR312.py:37:1: AIR312 `airflow.operators.trigger_dagrun.TriggerDagRunOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +4 4 | from airflow.hooks.package_index import PackageIndexHook +5 5 | from airflow.hooks.subprocess import SubprocessHook +6 6 | from airflow.operators.bash import BashOperator +7 |-from airflow.operators.datetime import BranchDateTimeOperator +8 7 | from airflow.operators.empty import EmptyOperator +9 8 | from airflow.operators.latest_only import LatestOnlyOperator +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.datetime import BranchDateTimeOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:20:1: AIR312 [*] `airflow.operators.trigger_dagrun.TriggerDagRunOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -35 | BashOperator() -36 | BranchDateTimeOperator() -37 | TriggerDagRunOperator() +18 | BashOperator() +19 | BranchDateTimeOperator() +20 | TriggerDagRunOperator() | ^^^^^^^^^^^^^^^^^^^^^ AIR312 -38 | EmptyOperator() -39 | LatestOnlyOperator() +21 | EmptyOperator() | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `TriggerDagRunOperator` from `airflow.providers.standard.operators.trigger_dagrun` instead. -AIR312.py:38:1: AIR312 `airflow.operators.empty.EmptyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +7 7 | from airflow.operators.datetime import BranchDateTimeOperator +8 8 | from airflow.operators.empty import EmptyOperator +9 9 | from airflow.operators.latest_only import LatestOnlyOperator +10 |-from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:21:1: AIR312 [*] `airflow.operators.empty.EmptyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -36 | BranchDateTimeOperator() -37 | TriggerDagRunOperator() -38 | EmptyOperator() +19 | BranchDateTimeOperator() +20 | TriggerDagRunOperator() +21 | EmptyOperator() | ^^^^^^^^^^^^^ AIR312 -39 | LatestOnlyOperator() -40 | ( +22 | +23 | LatestOnlyOperator() | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `EmptyOperator` from `airflow.providers.standard.operators.empty` instead. -AIR312.py:39:1: AIR312 `airflow.operators.latest_only.LatestOnlyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +5 5 | from airflow.hooks.subprocess import SubprocessHook +6 6 | from airflow.operators.bash import BashOperator +7 7 | from airflow.operators.datetime import BranchDateTimeOperator +8 |-from airflow.operators.empty import EmptyOperator +9 8 | from airflow.operators.latest_only import LatestOnlyOperator +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.empty import EmptyOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:23:1: AIR312 [*] `airflow.operators.latest_only.LatestOnlyOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -37 | TriggerDagRunOperator() -38 | EmptyOperator() -39 | LatestOnlyOperator() +21 | EmptyOperator() +22 | +23 | LatestOnlyOperator() | ^^^^^^^^^^^^^^^^^^ AIR312 -40 | ( -41 | BranchPythonOperator(), +24 | BranchDayOfWeekOperator() +25 | DateTimeSensor() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `LatestOnlyOperator` from `airflow.providers.standard.operators.latest_only` instead. -AIR312.py:41:5: AIR312 `airflow.operators.python.BranchPythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +6 6 | from airflow.operators.bash import BashOperator +7 7 | from airflow.operators.datetime import BranchDateTimeOperator +8 8 | from airflow.operators.empty import EmptyOperator +9 |-from airflow.operators.latest_only import LatestOnlyOperator +10 9 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 10 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.latest_only import LatestOnlyOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() + +AIR312.py:24:1: AIR312 [*] `airflow.operators.weekday.BranchDayOfWeekOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -39 | LatestOnlyOperator() -40 | ( -41 | BranchPythonOperator(), - | ^^^^^^^^^^^^^^^^^^^^ AIR312 -42 | PythonOperator(), -43 | PythonVirtualenvOperator(), +23 | LatestOnlyOperator() +24 | BranchDayOfWeekOperator() + | ^^^^^^^^^^^^^^^^^^^^^^^ AIR312 +25 | DateTimeSensor() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchPythonOperator` from `airflow.providers.standard.operators.python` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchDayOfWeekOperator` from `airflow.providers.standard.operators.weekday` instead. + +ℹ Unsafe fix +8 8 | from airflow.operators.empty import EmptyOperator +9 9 | from airflow.operators.latest_only import LatestOnlyOperator +10 10 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 |-from airflow.operators.weekday import BranchDayOfWeekOperator +12 11 | from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.operators.weekday import BranchDayOfWeekOperator +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() -AIR312.py:42:5: AIR312 `airflow.operators.python.PythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:25:1: AIR312 [*] `airflow.sensors.date_time.DateTimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -40 | ( -41 | BranchPythonOperator(), -42 | PythonOperator(), - | ^^^^^^^^^^^^^^ AIR312 -43 | PythonVirtualenvOperator(), -44 | ShortCircuitOperator(), +23 | LatestOnlyOperator() +24 | BranchDayOfWeekOperator() +25 | DateTimeSensor() + | ^^^^^^^^^^^^^^ AIR312 +26 | +27 | from airflow.operators.python import ( | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonOperator` from `airflow.providers.standard.operators.python` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DateTimeSensor` from `airflow.providers.standard.sensors.date_time` instead. + +ℹ Unsafe fix +9 9 | from airflow.operators.latest_only import LatestOnlyOperator +10 10 | from airflow.operators.trigger_dagrun import TriggerDagRunOperator +11 11 | from airflow.operators.weekday import BranchDayOfWeekOperator +12 |-from airflow.sensors.date_time import DateTimeSensor + 12 |+from airflow.providers.standard.sensors.date_time import DateTimeSensor +13 13 | +14 14 | FSHook() +15 15 | PackageIndexHook() -AIR312.py:43:5: AIR312 `airflow.operators.python.PythonVirtualenvOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:44:1: AIR312 [*] `airflow.operators.python.BranchPythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -41 | BranchPythonOperator(), -42 | PythonOperator(), -43 | PythonVirtualenvOperator(), - | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR312 -44 | ShortCircuitOperator(), -45 | ) +42 | from airflow.sensors.filesystem import FileSensor +43 | +44 | BranchPythonOperator() + | ^^^^^^^^^^^^^^^^^^^^ AIR312 +45 | PythonOperator() +46 | PythonVirtualenvOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonVirtualenvOperator` from `airflow.providers.standard.operators.python` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchPythonOperator` from `airflow.providers.standard.operators.python` instead. + +ℹ Unsafe fix +25 25 | DateTimeSensor() +26 26 | +27 27 | from airflow.operators.python import ( +28 |- BranchPythonOperator, +29 28 | PythonOperator, +30 29 | PythonVirtualenvOperator, +31 30 | ShortCircuitOperator, +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.operators.python import BranchPythonOperator +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() -AIR312.py:44:5: AIR312 `airflow.operators.python.ShortCircuitOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:45:1: AIR312 [*] `airflow.operators.python.PythonOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -42 | PythonOperator(), -43 | PythonVirtualenvOperator(), -44 | ShortCircuitOperator(), - | ^^^^^^^^^^^^^^^^^^^^ AIR312 -45 | ) -46 | BranchDayOfWeekOperator() +44 | BranchPythonOperator() +45 | PythonOperator() + | ^^^^^^^^^^^^^^ AIR312 +46 | PythonVirtualenvOperator() +47 | ShortCircuitOperator() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `ShortCircuitOperator` from `airflow.providers.standard.operators.python` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonOperator` from `airflow.providers.standard.operators.python` instead. -AIR312.py:46:1: AIR312 `airflow.operators.weekday.BranchDayOfWeekOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +26 26 | +27 27 | from airflow.operators.python import ( +28 28 | BranchPythonOperator, +29 |- PythonOperator, +30 29 | PythonVirtualenvOperator, +31 30 | ShortCircuitOperator, +32 31 | ) +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.operators.python import PythonOperator +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:46:1: AIR312 [*] `airflow.operators.python.PythonVirtualenvOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -44 | ShortCircuitOperator(), -45 | ) -46 | BranchDayOfWeekOperator() - | ^^^^^^^^^^^^^^^^^^^^^^^ AIR312 -47 | DateTimeSensor(), DateTimeSensorAsync() -48 | ExternalTaskMarker(), ExternalTaskSensor() +44 | BranchPythonOperator() +45 | PythonOperator() +46 | PythonVirtualenvOperator() + | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR312 +47 | ShortCircuitOperator() +48 | DateTimeSensorAsync() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `BranchDayOfWeekOperator` from `airflow.providers.standard.operators.weekday` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `PythonVirtualenvOperator` from `airflow.providers.standard.operators.python` instead. + +ℹ Unsafe fix +27 27 | from airflow.operators.python import ( +28 28 | BranchPythonOperator, +29 29 | PythonOperator, +30 |- PythonVirtualenvOperator, +31 30 | ShortCircuitOperator, +32 31 | ) +33 32 | from airflow.sensors.date_time import DateTimeSensorAsync +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.operators.python import PythonVirtualenvOperator +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() -AIR312.py:47:1: AIR312 `airflow.sensors.date_time.DateTimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:47:1: AIR312 [*] `airflow.operators.python.ShortCircuitOperator` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -45 | ) -46 | BranchDayOfWeekOperator() -47 | DateTimeSensor(), DateTimeSensorAsync() - | ^^^^^^^^^^^^^^ AIR312 -48 | ExternalTaskMarker(), ExternalTaskSensor() -49 | FileSensor() +45 | PythonOperator() +46 | PythonVirtualenvOperator() +47 | ShortCircuitOperator() + | ^^^^^^^^^^^^^^^^^^^^ AIR312 +48 | DateTimeSensorAsync() +49 | ExternalTaskMarker() | - = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DateTimeSensor` from `airflow.providers.standard.sensors.date_time` instead. + = help: Install `apache-airflow-providers-standard>=0.0.1` and use `ShortCircuitOperator` from `airflow.providers.standard.operators.python` instead. + +ℹ Unsafe fix +28 28 | BranchPythonOperator, +29 29 | PythonOperator, +30 30 | PythonVirtualenvOperator, +31 |- ShortCircuitOperator, +32 31 | ) +33 32 | from airflow.sensors.date_time import DateTimeSensorAsync +34 33 | from airflow.sensors.external_task import ( +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.operators.python import ShortCircuitOperator +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() -AIR312.py:47:19: AIR312 `airflow.sensors.date_time.DateTimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +AIR312.py:48:1: AIR312 [*] `airflow.sensors.date_time.DateTimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -45 | ) -46 | BranchDayOfWeekOperator() -47 | DateTimeSensor(), DateTimeSensorAsync() - | ^^^^^^^^^^^^^^^^^^^ AIR312 -48 | ExternalTaskMarker(), ExternalTaskSensor() -49 | FileSensor() +46 | PythonVirtualenvOperator() +47 | ShortCircuitOperator() +48 | DateTimeSensorAsync() + | ^^^^^^^^^^^^^^^^^^^ AIR312 +49 | ExternalTaskMarker() +50 | ExternalTaskSensor() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DateTimeSensorAsync` from `airflow.providers.standard.sensors.date_time` instead. -AIR312.py:48:1: AIR312 `airflow.sensors.external_task.ExternalTaskMarker` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +30 30 | PythonVirtualenvOperator, +31 31 | ShortCircuitOperator, +32 32 | ) +33 |-from airflow.sensors.date_time import DateTimeSensorAsync +34 33 | from airflow.sensors.external_task import ( +35 34 | ExternalTaskMarker, +36 35 | ExternalTaskSensor, +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.date_time import DateTimeSensorAsync +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:49:1: AIR312 [*] `airflow.sensors.external_task.ExternalTaskMarker` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -46 | BranchDayOfWeekOperator() -47 | DateTimeSensor(), DateTimeSensorAsync() -48 | ExternalTaskMarker(), ExternalTaskSensor() +47 | ShortCircuitOperator() +48 | DateTimeSensorAsync() +49 | ExternalTaskMarker() | ^^^^^^^^^^^^^^^^^^ AIR312 -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() +50 | ExternalTaskSensor() +51 | FileSensor() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskMarker` from `airflow.providers.standard.sensors.external_task` instead. -AIR312.py:48:23: AIR312 `airflow.sensors.external_task.ExternalTaskSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +32 32 | ) +33 33 | from airflow.sensors.date_time import DateTimeSensorAsync +34 34 | from airflow.sensors.external_task import ( +35 |- ExternalTaskMarker, +36 35 | ExternalTaskSensor, +37 36 | ) +38 37 | from airflow.sensors.time_sensor import ( +-------------------------------------------------------------------------------- +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.external_task import ExternalTaskMarker +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:50:1: AIR312 [*] `airflow.sensors.external_task.ExternalTaskSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -46 | BranchDayOfWeekOperator() -47 | DateTimeSensor(), DateTimeSensorAsync() -48 | ExternalTaskMarker(), ExternalTaskSensor() - | ^^^^^^^^^^^^^^^^^^ AIR312 -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() +48 | DateTimeSensorAsync() +49 | ExternalTaskMarker() +50 | ExternalTaskSensor() + | ^^^^^^^^^^^^^^^^^^ AIR312 +51 | FileSensor() +52 | TimeSensor() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `ExternalTaskSensor` from `airflow.providers.standard.sensors.external_task` instead. -AIR312.py:49:1: AIR312 `airflow.sensors.filesystem.FileSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +33 33 | from airflow.sensors.date_time import DateTimeSensorAsync +34 34 | from airflow.sensors.external_task import ( +35 35 | ExternalTaskMarker, +36 |- ExternalTaskSensor, +37 36 | ) +38 37 | from airflow.sensors.time_sensor import ( +39 38 | TimeSensor, +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.external_task import ExternalTaskSensor +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:51:1: AIR312 [*] `airflow.sensors.filesystem.FileSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -47 | DateTimeSensor(), DateTimeSensorAsync() -48 | ExternalTaskMarker(), ExternalTaskSensor() -49 | FileSensor() +49 | ExternalTaskMarker() +50 | ExternalTaskSensor() +51 | FileSensor() | ^^^^^^^^^^ AIR312 -50 | TimeSensor(), TimeSensorAsync() -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() +52 | TimeSensor() +53 | TimeSensorAsync() | = help: Install `apache-airflow-providers-standard>=0.0.2` and use `FileSensor` from `airflow.providers.standard.sensors.filesystem` instead. -AIR312.py:50:1: AIR312 `airflow.sensors.time_sensor.TimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +39 39 | TimeSensor, +40 40 | TimeSensorAsync, +41 41 | ) +42 |-from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.filesystem import FileSensor +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:52:1: AIR312 [*] `airflow.sensors.time_sensor.TimeSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -48 | ExternalTaskMarker(), ExternalTaskSensor() -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() +50 | ExternalTaskSensor() +51 | FileSensor() +52 | TimeSensor() | ^^^^^^^^^^ AIR312 -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() -52 | DayOfWeekSensor() +53 | TimeSensorAsync() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeSensor` from `airflow.providers.standard.sensors.time` instead. -AIR312.py:50:15: AIR312 `airflow.sensors.time_sensor.TimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +36 36 | ExternalTaskSensor, +37 37 | ) +38 38 | from airflow.sensors.time_sensor import ( +39 |- TimeSensor, +40 39 | TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.time import TimeSensor +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:53:1: AIR312 [*] `airflow.sensors.time_sensor.TimeSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -48 | ExternalTaskMarker(), ExternalTaskSensor() -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() - | ^^^^^^^^^^^^^^^ AIR312 -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() -52 | DayOfWeekSensor() +51 | FileSensor() +52 | TimeSensor() +53 | TimeSensorAsync() + | ^^^^^^^^^^^^^^^ AIR312 +54 | +55 | from airflow.sensors.time_delta import ( | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeSensorAsync` from `airflow.providers.standard.sensors.time` instead. -AIR312.py:51:1: AIR312 `airflow.sensors.time_delta.TimeDeltaSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +37 37 | ) +38 38 | from airflow.sensors.time_sensor import ( +39 39 | TimeSensor, +40 |- TimeSensorAsync, +41 40 | ) +42 41 | from airflow.sensors.filesystem import FileSensor + 42 |+from airflow.providers.standard.sensors.time import TimeSensorAsync +43 43 | +44 44 | BranchPythonOperator() +45 45 | PythonOperator() + +AIR312.py:70:1: AIR312 [*] `airflow.sensors.time_delta.TimeDeltaSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() +68 | ) +69 | +70 | TimeDeltaSensor() | ^^^^^^^^^^^^^^^ AIR312 -52 | DayOfWeekSensor() -53 | DagStateTrigger(), WorkflowTrigger() +71 | TimeDeltaSensorAsync() +72 | DayOfWeekSensor() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeDeltaSensor` from `airflow.providers.standard.sensors.time_delta` instead. -AIR312.py:51:20: AIR312 `airflow.sensors.time_delta.TimeDeltaSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +53 53 | TimeSensorAsync() +54 54 | +55 55 | from airflow.sensors.time_delta import ( +56 |- TimeDeltaSensor, +57 56 | TimeDeltaSensorAsync, +58 57 | ) +59 58 | from airflow.sensors.weekday import DayOfWeekSensor +-------------------------------------------------------------------------------- +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.sensors.time_delta import TimeDeltaSensor +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:71:1: AIR312 [*] `airflow.sensors.time_delta.TimeDeltaSensorAsync` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -49 | FileSensor() -50 | TimeSensor(), TimeSensorAsync() -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() - | ^^^^^^^^^^^^^^^^^^^^ AIR312 -52 | DayOfWeekSensor() -53 | DagStateTrigger(), WorkflowTrigger() +70 | TimeDeltaSensor() +71 | TimeDeltaSensorAsync() + | ^^^^^^^^^^^^^^^^^^^^ AIR312 +72 | DayOfWeekSensor() +73 | DagStateTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `TimeDeltaSensorAsync` from `airflow.providers.standard.sensors.time_delta` instead. -AIR312.py:52:1: AIR312 `airflow.sensors.weekday.DayOfWeekSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +54 54 | +55 55 | from airflow.sensors.time_delta import ( +56 56 | TimeDeltaSensor, +57 |- TimeDeltaSensorAsync, +58 57 | ) +59 58 | from airflow.sensors.weekday import DayOfWeekSensor +60 59 | from airflow.triggers.external_task import ( +-------------------------------------------------------------------------------- +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.sensors.time_delta import TimeDeltaSensorAsync +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:72:1: AIR312 [*] `airflow.sensors.weekday.DayOfWeekSensor` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -50 | TimeSensor(), TimeSensorAsync() -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() -52 | DayOfWeekSensor() +70 | TimeDeltaSensor() +71 | TimeDeltaSensorAsync() +72 | DayOfWeekSensor() | ^^^^^^^^^^^^^^^ AIR312 -53 | DagStateTrigger(), WorkflowTrigger() -54 | FileTrigger() +73 | DagStateTrigger() +74 | WorkflowTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.1` and use `DayOfWeekSensor` from `airflow.providers.standard.sensors.weekday` instead. -AIR312.py:53:1: AIR312 `airflow.triggers.external_task.DagStateTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +56 56 | TimeDeltaSensor, +57 57 | TimeDeltaSensorAsync, +58 58 | ) +59 |-from airflow.sensors.weekday import DayOfWeekSensor +60 59 | from airflow.triggers.external_task import ( +61 60 | DagStateTrigger, +62 61 | WorkflowTrigger, +-------------------------------------------------------------------------------- +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.sensors.weekday import DayOfWeekSensor +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:73:1: AIR312 [*] `airflow.triggers.external_task.DagStateTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() -52 | DayOfWeekSensor() -53 | DagStateTrigger(), WorkflowTrigger() +71 | TimeDeltaSensorAsync() +72 | DayOfWeekSensor() +73 | DagStateTrigger() | ^^^^^^^^^^^^^^^ AIR312 -54 | FileTrigger() -55 | DateTimeTrigger(), TimeDeltaTrigger() +74 | WorkflowTrigger() +75 | FileTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `DagStateTrigger` from `airflow.providers.standard.triggers.external_task` instead. -AIR312.py:53:20: AIR312 `airflow.triggers.external_task.WorkflowTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +58 58 | ) +59 59 | from airflow.sensors.weekday import DayOfWeekSensor +60 60 | from airflow.triggers.external_task import ( +61 |- DagStateTrigger, +62 61 | WorkflowTrigger, +63 62 | ) +64 63 | from airflow.triggers.file import FileTrigger +-------------------------------------------------------------------------------- +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.triggers.external_task import DagStateTrigger +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:74:1: AIR312 [*] `airflow.triggers.external_task.WorkflowTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -51 | TimeDeltaSensor(), TimeDeltaSensorAsync() -52 | DayOfWeekSensor() -53 | DagStateTrigger(), WorkflowTrigger() - | ^^^^^^^^^^^^^^^ AIR312 -54 | FileTrigger() -55 | DateTimeTrigger(), TimeDeltaTrigger() +72 | DayOfWeekSensor() +73 | DagStateTrigger() +74 | WorkflowTrigger() + | ^^^^^^^^^^^^^^^ AIR312 +75 | FileTrigger() +76 | DateTimeTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `WorkflowTrigger` from `airflow.providers.standard.triggers.external_task` instead. -AIR312.py:54:1: AIR312 `airflow.triggers.file.FileTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +59 59 | from airflow.sensors.weekday import DayOfWeekSensor +60 60 | from airflow.triggers.external_task import ( +61 61 | DagStateTrigger, +62 |- WorkflowTrigger, +63 62 | ) +64 63 | from airflow.triggers.file import FileTrigger +65 64 | from airflow.triggers.temporal import ( +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.triggers.external_task import WorkflowTrigger +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:75:1: AIR312 [*] `airflow.triggers.file.FileTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -52 | DayOfWeekSensor() -53 | DagStateTrigger(), WorkflowTrigger() -54 | FileTrigger() +73 | DagStateTrigger() +74 | WorkflowTrigger() +75 | FileTrigger() | ^^^^^^^^^^^ AIR312 -55 | DateTimeTrigger(), TimeDeltaTrigger() +76 | DateTimeTrigger() +77 | TimeDeltaTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `FileTrigger` from `airflow.providers.standard.triggers.file` instead. -AIR312.py:55:1: AIR312 `airflow.triggers.temporal.DateTimeTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +61 61 | DagStateTrigger, +62 62 | WorkflowTrigger, +63 63 | ) +64 |-from airflow.triggers.file import FileTrigger +65 64 | from airflow.triggers.temporal import ( +66 65 | DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.triggers.file import FileTrigger +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:76:1: AIR312 [*] `airflow.triggers.temporal.DateTimeTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -53 | DagStateTrigger(), WorkflowTrigger() -54 | FileTrigger() -55 | DateTimeTrigger(), TimeDeltaTrigger() +74 | WorkflowTrigger() +75 | FileTrigger() +76 | DateTimeTrigger() | ^^^^^^^^^^^^^^^ AIR312 +77 | TimeDeltaTrigger() | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `DateTimeTrigger` from `airflow.providers.standard.triggers.temporal` instead. -AIR312.py:55:20: AIR312 `airflow.triggers.temporal.TimeDeltaTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. +ℹ Unsafe fix +63 63 | ) +64 64 | from airflow.triggers.file import FileTrigger +65 65 | from airflow.triggers.temporal import ( +66 |- DateTimeTrigger, +67 66 | TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.triggers.temporal import DateTimeTrigger +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() + +AIR312.py:77:1: AIR312 [*] `airflow.triggers.temporal.TimeDeltaTrigger` is deprecated and moved into `standard` provider in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. | -53 | DagStateTrigger(), WorkflowTrigger() -54 | FileTrigger() -55 | DateTimeTrigger(), TimeDeltaTrigger() - | ^^^^^^^^^^^^^^^^ AIR312 +75 | FileTrigger() +76 | DateTimeTrigger() +77 | TimeDeltaTrigger() + | ^^^^^^^^^^^^^^^^ AIR312 | = help: Install `apache-airflow-providers-standard>=0.0.3` and use `TimeDeltaTrigger` from `airflow.providers.standard.triggers.temporal` instead. + +ℹ Unsafe fix +64 64 | from airflow.triggers.file import FileTrigger +65 65 | from airflow.triggers.temporal import ( +66 66 | DateTimeTrigger, +67 |- TimeDeltaTrigger, +68 67 | ) + 68 |+from airflow.providers.standard.triggers.temporal import TimeDeltaTrigger +69 69 | +70 70 | TimeDeltaSensor() +71 71 | TimeDeltaSensorAsync() From e730f27f80c02592dce3e699487c9bb3c0c5ff58 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 30 May 2025 17:24:20 +0200 Subject: [PATCH 288/487] [ty] List available members for a given type (#18251) This PR adds initial support for listing all attributes of an object. It is exposed through a new `all_members` routine in `ty_extensions`, which is in turn used to test the functionality. The purpose of listing all members is for code completion. That is, given a `object.`, we would like to list all available attributes on `object`. --- .../resources/mdtest/binary/in.md | 53 ++ .../mdtest/ide_support/all_members.md | 488 ++++++++++++++++++ ...asic_functionality_(6b9531a70334bfad).snap | 44 ++ .../ty_python_semantic/src/semantic_index.rs | 36 +- crates/ty_python_semantic/src/types.rs | 9 +- .../ty_python_semantic/src/types/call/bind.rs | 16 +- .../src/types/ide_support.rs | 183 +++++++ crates/ty_python_semantic/src/types/infer.rs | 55 ++ .../ty_extensions/ty_extensions.pyi | 10 + 9 files changed, 882 insertions(+), 12 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/binary/in.md create mode 100644 crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap create mode 100644 crates/ty_python_semantic/src/types/ide_support.rs diff --git a/crates/ty_python_semantic/resources/mdtest/binary/in.md b/crates/ty_python_semantic/resources/mdtest/binary/in.md new file mode 100644 index 00000000000000..508ccdb67c25e0 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/binary/in.md @@ -0,0 +1,53 @@ +# Static binary operations using `in` + +## Basic functionality + +This demonstrates type inference support for ` in `: + +```py +from ty_extensions import static_assert + +static_assert("foo" in ("quux", "foo", "baz")) +static_assert("foo" not in ("quux", "bar", "baz")) +``` + +## With variables + +```py +from ty_extensions import static_assert + +x = ("quux", "foo", "baz") +static_assert("foo" in x) + +x = ("quux", "bar", "baz") +static_assert("foo" not in x) +``` + +## Statically unknown results in a type error + +```py +from ty_extensions import static_assert + +def _(a: str, b: str): + static_assert("foo" in (a, b)) # error: [static-assert-error] +``` + +## Values being unknown doesn't mean the result is unknown + +For example, when the types are completely disjoint: + +```py +from ty_extensions import static_assert + +def _(a: int, b: int): + static_assert("foo" not in (a, b)) +``` + +## Failure cases + +```py +from ty_extensions import static_assert + +# We don't support byte strings. +static_assert(b"foo" not in (b"quux", b"foo", b"baz")) # error: [static-assert-error] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md new file mode 100644 index 00000000000000..63a0c21c30fe60 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md @@ -0,0 +1,488 @@ +# List all members + +## Basic functionality + + + +The `ty_extensions.all_members` function allows access to a list of accessible members/attributes on +a given object. For example, all member functions of `str` are available on `"a"`: + +```py +from ty_extensions import all_members, static_assert + +members_of_str = all_members("a") + +static_assert("replace" in members_of_str) +static_assert("startswith" in members_of_str) +static_assert("isupper" in members_of_str) +``` + +Similarly, special members such as `__add__` are also available: + +```py +static_assert("__add__" in members_of_str) +static_assert("__gt__" in members_of_str) +``` + +Members of base classes are also included (these dunder methods are defined on `object`): + +```py +static_assert("__doc__" in members_of_str) +static_assert("__repr__" in members_of_str) +``` + +Non-existent members are not included: + +```py +static_assert("non_existent" not in members_of_str) +``` + +Note: The full list of all members is relatively long, but `reveal_type` can theoretically be used +to see them all: + +```py +from typing_extensions import reveal_type + +reveal_type(members_of_str) # error: [revealed-type] +``` + +## Kinds of types + +### Class instances + +For instances of classes, `all_members` returns class members and implicit instance members of all +classes in the MRO: + +```py +from ty_extensions import all_members, static_assert + +class Base: + base_class_attr: int = 1 + + def f_base(self): + self.base_instance_attr: str = "Base" + +class Intermediate(Base): + intermediate_attr: int = 2 + + def f_intermediate(self): + self.intermediate_instance_attr: str = "Intermediate" + +class C(Intermediate): + class_attr: int = 3 + + def f_c(self): + self.instance_attr = "C" + + @property + def property_attr(self) -> int: + return 1 + + @classmethod + def class_method(cls) -> int: + return 1 + + @staticmethod + def static_method() -> int: + return 1 + +members_of_instance = all_members(C()) + +static_assert("base_class_attr" in members_of_instance) +static_assert("intermediate_attr" in members_of_instance) +static_assert("class_attr" in members_of_instance) + +static_assert("base_instance_attr" in members_of_instance) +static_assert("intermediate_instance_attr" in members_of_instance) +static_assert("instance_attr" in members_of_instance) + +static_assert("f_base" in members_of_instance) +static_assert("f_intermediate" in members_of_instance) +static_assert("f_c" in members_of_instance) + +static_assert("property_attr" in members_of_instance) +static_assert("class_method" in members_of_instance) +static_assert("static_method" in members_of_instance) + +static_assert("non_existent" not in members_of_instance) +``` + +### Class objects + +Class-level attributes can also be accessed through the class itself: + +```py +from ty_extensions import all_members, static_assert + +class Base: + base_attr: int = 1 + +class C(Base): + class_attr: str = "c" + + def f(self): + self.instance_attr = True + +members_of_class = all_members(C) + +static_assert("class_attr" in members_of_class) +static_assert("base_attr" in members_of_class) + +static_assert("non_existent" not in members_of_class) +``` + +But instance attributes can not be accessed this way: + +```py +static_assert("instance_attr" not in members_of_class) +``` + +When a class has a metaclass, members of that metaclass (and bases of that metaclass) are also +accessible: + +```py +class MetaBase(type): + meta_base_attr = 1 + +class Meta(MetaBase): + meta_attr = 2 + +class D(Base, metaclass=Meta): + class_attr = 3 + +static_assert("meta_base_attr" in all_members(D)) +static_assert("meta_attr" in all_members(D)) +static_assert("base_attr" in all_members(D)) +static_assert("class_attr" in all_members(D)) +``` + +### Generic classes + +```py +from ty_extensions import all_members, static_assert +from typing import Generic, TypeVar + +T = TypeVar("T") + +class C(Generic[T]): + base_attr: T + +static_assert("base_attr" in all_members(C[int])) +static_assert("base_attr" in all_members(C[int]())) +``` + +### Other instance-like types + +```py +from ty_extensions import all_members, static_assert +from typing_extensions import LiteralString + +static_assert("__xor__" in all_members(True)) +static_assert("bit_length" in all_members(1)) +static_assert("startswith" in all_members("a")) +static_assert("__buffer__" in all_members(b"a")) +static_assert("is_integer" in all_members(3.14)) + +def _(literal_string: LiteralString): + static_assert("startswith" in all_members(literal_string)) + +static_assert("count" in all_members(("some", "tuple", 1, 2))) + +static_assert("__doc__" in all_members(len)) +static_assert("__doc__" in all_members("a".startswith)) +``` + +### Unions + +For unions, `all_members` will only return members that are available on all elements of the union. + +```py +from ty_extensions import all_members, static_assert + +class A: + on_both: int = 1 + only_on_a: str = "a" + +class B: + on_both: int = 2 + only_on_b: str = "b" + +def f(union: A | B): + static_assert("on_both" in all_members(union)) + static_assert("only_on_a" not in all_members(union)) + static_assert("only_on_b" not in all_members(union)) +``` + +### Intersections + +#### Only positive types + +Conversely, for intersections, `all_members` will list members that are available on any of the +elements: + +```py +from ty_extensions import all_members, static_assert + +class A: + on_both: int = 1 + only_on_a: str = "a" + +class B: + on_both: int = 2 + only_on_b: str = "b" + +def f(intersection: object): + if isinstance(intersection, A): + if isinstance(intersection, B): + static_assert("on_both" in all_members(intersection)) + static_assert("only_on_a" in all_members(intersection)) + static_assert("only_on_b" in all_members(intersection)) +``` + +#### With negative types + +It also works when negative types are introduced: + +```py +from ty_extensions import all_members, static_assert + +class A: + on_all: int = 1 + only_on_a: str = "a" + only_on_ab: str = "a" + only_on_ac: str = "a" + +class B: + on_all: int = 2 + only_on_b: str = "b" + only_on_ab: str = "b" + only_on_bc: str = "b" + +class C: + on_all: int = 3 + only_on_c: str = "c" + only_on_ac: str = "c" + only_on_bc: str = "c" + +def f(intersection: object): + if isinstance(intersection, A): + if isinstance(intersection, B): + if not isinstance(intersection, C): + reveal_type(intersection) # revealed: A & B & ~C + static_assert("on_all" in all_members(intersection)) + static_assert("only_on_a" in all_members(intersection)) + static_assert("only_on_b" in all_members(intersection)) + static_assert("only_on_c" not in all_members(intersection)) + static_assert("only_on_ab" in all_members(intersection)) + static_assert("only_on_ac" in all_members(intersection)) + static_assert("only_on_bc" in all_members(intersection)) +``` + +## Modules + +### Basic support with sub-modules + +`all_members` can also list attributes on modules: + +```py +from ty_extensions import all_members, static_assert +import math + +static_assert("pi" in all_members(math)) +static_assert("cos" in all_members(math)) +``` + +This also works for submodules: + +```py +import os + +static_assert("path" in all_members(os)) + +import os.path + +static_assert("join" in all_members(os.path)) +``` + +Special members available on all modules are also included: + +```py +static_assert("__name__" in all_members(math)) +static_assert("__doc__" in all_members(math)) +``` + +### `__all__` is not respected for direct module access + +`foo.py`: + +```py +from ty_extensions import all_members, static_assert + +import bar + +static_assert("lion" in all_members(bar)) +static_assert("tiger" in all_members(bar)) +``` + +`bar.py`: + +```py +__all__ = ["lion"] + +lion = 1 +tiger = 1 +``` + +### `__all__` is respected for glob imports + +`foo.py`: + +```py +from ty_extensions import all_members, static_assert + +import bar + +static_assert("lion" in all_members(bar)) +static_assert("tiger" not in all_members(bar)) +``` + +`bar.py`: + +```py +from quux import * +``` + +`quux.py`: + +```py +__all__ = ["lion"] + +lion = 1 +tiger = 1 +``` + +### `__all__` is respected for stub files + +`module.py`: + +```py +def evaluate(x=None): + if x is None: + return 0 + return x +``` + +`module.pyi`: + +```pyi +from typing import Optional + +__all__ = ["evaluate"] + +def evaluate(x: Optional[int] = None) -> int: ... +``` + +`play.py`: + +```py +from ty_extensions import all_members, static_assert + +import module + +static_assert("evaluate" in all_members(module)) +static_assert("Optional" not in all_members(module)) +``` + +## Conditionally available members + +Some members are only conditionally available. For example, `int.bit_count` was only introduced in +Python 3.10: + +### 3.9 + +```toml +[environment] +python-version = "3.9" +``` + +```py +from ty_extensions import all_members, static_assert + +static_assert("bit_count" not in all_members(42)) +``` + +### 3.10 + +```toml +[environment] +python-version = "3.10" +``` + +```py +from ty_extensions import all_members, static_assert + +static_assert("bit_count" in all_members(42)) +``` + +## Failures cases + +### Dynamically added members + +Dynamically added members can not be accessed: + +```py +from ty_extensions import all_members, static_assert + +class C: + static_attr = 1 + + def __setattr__(self, name: str, value: str) -> None: + pass + + def __getattr__(self, name: str) -> str: + return "a" + +c = C() +c.dynamic_attr = "a" + +static_assert("static_attr" in all_members(c)) +static_assert("dynamic_attr" not in all_members(c)) +``` + +### Dataclasses + +So far, we do not include synthetic members of dataclasses. + +```py +from ty_extensions import all_members, static_assert +from dataclasses import dataclass + +@dataclass(order=True) +class Person: + name: str + age: int + +static_assert("name" in all_members(Person)) +static_assert("age" in all_members(Person)) + +# These are always available, since they are also defined on `object`: +static_assert("__init__" in all_members(Person)) +static_assert("__repr__" in all_members(Person)) +static_assert("__eq__" in all_members(Person)) + +# TODO: this should ideally be available: +static_assert("__lt__" in all_members(Person)) # error: [static-assert-error] +``` + +### Attributes not available at runtime + +Typeshed includes some attributes in `object` that are not available for some (builtin) types. For +example, `__annotations__` does not exist on `int` at runtime, but it is available as an attribute +on `object` in typeshed: + +```py +from ty_extensions import all_members, static_assert + +# TODO: this should ideally not be available: +static_assert("__annotations__" not in all_members(3)) # error: [static-assert-error] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap new file mode 100644 index 00000000000000..55a377e0c33cb3 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap @@ -0,0 +1,44 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: all_members.md - List all members - Basic functionality +mdtest path: crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from ty_extensions import all_members, static_assert + 2 | + 3 | members_of_str = all_members("a") + 4 | + 5 | static_assert("replace" in members_of_str) + 6 | static_assert("startswith" in members_of_str) + 7 | static_assert("isupper" in members_of_str) + 8 | static_assert("__add__" in members_of_str) + 9 | static_assert("__gt__" in members_of_str) +10 | static_assert("__doc__" in members_of_str) +11 | static_assert("__repr__" in members_of_str) +12 | static_assert("non_existent" not in members_of_str) +13 | from typing_extensions import reveal_type +14 | +15 | reveal_type(members_of_str) # error: [revealed-type] +``` + +# Diagnostics + +``` +info[revealed-type]: Revealed type + --> src/mdtest_snippet.py:15:13 + | +13 | from typing_extensions import reveal_type +14 | +15 | reveal_type(members_of_str) # error: [revealed-type] + | ^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]` + | + +``` diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index 7f23fab1632dbd..6d57a8515c8790 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -99,7 +99,9 @@ pub(crate) fn use_def_map<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc( @@ -109,6 +111,28 @@ pub(crate) fn attribute_assignments<'db, 's>( ) -> impl Iterator, FileScopeId)> + use<'s, 'db> { let file = class_body_scope.file(db); let index = semantic_index(db, file); + + attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| { + let attribute_table = index.instance_attribute_table(function_scope_id); + let symbol = attribute_table.symbol_id_by_name(name)?; + let use_def = &index.use_def_maps[function_scope_id]; + Some(( + use_def.instance_attribute_bindings(symbol), + function_scope_id, + )) + }) +} + +/// Returns all attribute assignments as scope IDs for a specific class body scope. +/// +/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it +/// introduces a direct dependency on that file's AST. +pub(crate) fn attribute_scopes<'db, 's>( + db: &'db dyn Db, + class_body_scope: ScopeId<'db>, +) -> impl Iterator + use<'s, 'db> { + let file = class_body_scope.file(db); + let index = semantic_index(db, file); let class_scope_id = class_body_scope.file_scope_id(db); ChildrenIter::new(index, class_scope_id).filter_map(|(child_scope_id, scope)| { @@ -124,13 +148,7 @@ pub(crate) fn attribute_assignments<'db, 's>( }; function_scope.node().as_function()?; - let attribute_table = index.instance_attribute_table(function_scope_id); - let symbol = attribute_table.symbol_id_by_name(name)?; - let use_def = &index.use_def_maps[function_scope_id]; - Some(( - use_def.instance_attribute_bindings(symbol), - function_scope_id, - )) + Some(function_scope_id) }) } @@ -519,7 +537,7 @@ pub struct ChildrenIter<'a> { } impl<'a> ChildrenIter<'a> { - fn new(module_symbol_table: &'a SemanticIndex, parent: FileScopeId) -> Self { + pub(crate) fn new(module_symbol_table: &'a SemanticIndex, parent: FileScopeId) -> Self { let descendants = DescendantsIter::new(module_symbol_table, parent); Self { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6665ddb7a8a651..f885b45f58656c 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -65,6 +65,7 @@ mod context; mod diagnostic; mod display; mod generics; +mod ide_support; mod infer; mod instance; mod mro; @@ -7662,6 +7663,8 @@ pub enum KnownFunction { GenericContext, /// `ty_extensions.dunder_all_names` DunderAllNames, + /// `ty_extensions.all_members` + AllMembers, } impl KnownFunction { @@ -7721,7 +7724,8 @@ impl KnownFunction { | Self::IsSubtypeOf | Self::GenericContext | Self::DunderAllNames - | Self::StaticAssert => module.is_ty_extensions(), + | Self::StaticAssert + | Self::AllMembers => module.is_ty_extensions(), } } } @@ -9390,7 +9394,8 @@ pub(crate) mod tests { | KnownFunction::IsSingleValued | KnownFunction::IsAssignableTo | KnownFunction::IsEquivalentTo - | KnownFunction::IsGradualEquivalentTo => KnownModule::TyExtensions, + | KnownFunction::IsGradualEquivalentTo + | KnownFunction::AllMembers => KnownModule::TyExtensions, }; let function_definition = known_module_symbol(&db, module, function_name) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index fa52bcd960b374..51ab005c1b8d57 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,6 +3,7 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. +use itertools::Itertools; use smallvec::{SmallVec, smallvec}; use super::{ @@ -22,7 +23,8 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, + SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support, + todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -656,6 +658,18 @@ impl<'db> Bindings<'db> { } } + Some(KnownFunction::AllMembers) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(TupleType::from_elements( + db, + ide_support::all_members(db, *ty) + .into_iter() + .sorted() + .map(|member| Type::string_literal(db, &member)), + )); + } + } + Some(KnownFunction::Len) => { if let [Some(first_arg)] = overload.parameter_types() { if let Some(len_ty) = first_arg.len(db) { diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs new file mode 100644 index 00000000000000..2ba4606d6a8dff --- /dev/null +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -0,0 +1,183 @@ +use crate::Db; +use crate::semantic_index::symbol::ScopeId; +use crate::semantic_index::{ + attribute_scopes, global_scope, semantic_index, symbol_table, use_def_map, +}; +use crate::symbol::{imported_symbol, symbol_from_bindings, symbol_from_declarations}; +use crate::types::{ClassBase, ClassLiteral, KnownClass, Type}; +use ruff_python_ast::name::Name; +use rustc_hash::FxHashSet; + +struct AllMembers { + members: FxHashSet, +} + +impl AllMembers { + fn of<'db>(db: &'db dyn Db, ty: Type<'db>) -> Self { + let mut all_members = Self { + members: FxHashSet::default(), + }; + all_members.extend_with_type(db, ty); + all_members + } + + fn extend_with_type<'db>(&mut self, db: &'db dyn Db, ty: Type<'db>) { + match ty { + Type::Union(union) => self.members.extend( + union + .elements(db) + .iter() + .map(|ty| AllMembers::of(db, *ty).members) + .reduce(|acc, members| acc.intersection(&members).cloned().collect()) + .unwrap_or_default(), + ), + + Type::Intersection(intersection) => self.members.extend( + intersection + .positive(db) + .iter() + .map(|ty| AllMembers::of(db, *ty).members) + .reduce(|acc, members| acc.union(&members).cloned().collect()) + .unwrap_or_default(), + ), + + Type::NominalInstance(instance) => { + let (class_literal, _specialization) = instance.class.class_literal(db); + self.extend_with_class_members(db, class_literal); + self.extend_with_instance_members(db, class_literal); + } + + Type::ClassLiteral(class_literal) => { + self.extend_with_class_members(db, class_literal); + + if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) { + self.extend_with_class_members(db, meta_class_literal); + } + } + + Type::GenericAlias(generic_alias) => { + let class_literal = generic_alias.origin(db); + self.extend_with_class_members(db, class_literal); + } + + Type::SubclassOf(subclass_of_type) => { + if let Some(class_literal) = subclass_of_type.subclass_of().into_class() { + self.extend_with_class_members(db, class_literal.class_literal(db).0); + } + } + + Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy | Type::AlwaysFalsy => {} + + Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::StringLiteral(_) + | Type::BytesLiteral(_) + | Type::LiteralString + | Type::Tuple(_) + | Type::PropertyInstance(_) + | Type::FunctionLiteral(_) + | Type::BoundMethod(_) + | Type::MethodWrapper(_) + | Type::WrapperDescriptor(_) + | Type::DataclassDecorator(_) + | Type::DataclassTransformer(_) + | Type::Callable(_) + | Type::ProtocolInstance(_) + | Type::SpecialForm(_) + | Type::KnownInstance(_) + | Type::TypeVar(_) + | Type::BoundSuper(_) => { + if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) { + self.extend_with_class_members(db, class_literal); + } + } + + Type::ModuleLiteral(literal) => { + self.extend_with_type(db, KnownClass::ModuleType.to_instance(db)); + + let Some(file) = literal.module(db).file() else { + return; + }; + + let module_scope = global_scope(db, file); + let use_def_map = use_def_map(db, module_scope); + let symbol_table = symbol_table(db, module_scope); + + for (symbol_id, _) in use_def_map.all_public_declarations() { + let symbol_name = symbol_table.symbol(symbol_id).name(); + if !imported_symbol(db, file, symbol_name, None) + .symbol + .is_unbound() + { + self.members + .insert(symbol_table.symbol(symbol_id).name().clone()); + } + } + } + } + } + + fn extend_with_declarations_and_bindings(&mut self, db: &dyn Db, scope_id: ScopeId) { + let use_def_map = use_def_map(db, scope_id); + let symbol_table = symbol_table(db, scope_id); + + for (symbol_id, declarations) in use_def_map.all_public_declarations() { + if symbol_from_declarations(db, declarations) + .is_ok_and(|result| !result.symbol.is_unbound()) + { + self.members + .insert(symbol_table.symbol(symbol_id).name().clone()); + } + } + + for (symbol_id, bindings) in use_def_map.all_public_bindings() { + if !symbol_from_bindings(db, bindings).is_unbound() { + self.members + .insert(symbol_table.symbol(symbol_id).name().clone()); + } + } + } + + fn extend_with_class_members<'db>( + &mut self, + db: &'db dyn Db, + class_literal: ClassLiteral<'db>, + ) { + for parent in class_literal + .iter_mro(db, None) + .filter_map(ClassBase::into_class) + .map(|class| class.class_literal(db).0) + { + let parent_scope = parent.body_scope(db); + self.extend_with_declarations_and_bindings(db, parent_scope); + } + } + + fn extend_with_instance_members<'db>( + &mut self, + db: &'db dyn Db, + class_literal: ClassLiteral<'db>, + ) { + for parent in class_literal + .iter_mro(db, None) + .filter_map(ClassBase::into_class) + .map(|class| class.class_literal(db).0) + { + let class_body_scope = parent.body_scope(db); + let file = class_body_scope.file(db); + let index = semantic_index(db, file); + for function_scope_id in attribute_scopes(db, class_body_scope) { + let attribute_table = index.instance_attribute_table(function_scope_id); + for symbol in attribute_table.symbols() { + self.members.insert(symbol.name().clone()); + } + } + } + } +} + +/// List all members of a given type: anything that would be valid when accessed +/// as an attribute on an object of the given type. +pub(crate) fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet { + AllMembers::of(db, ty).members +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 50964ded3daaf1..73aefafb3c2172 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -6719,6 +6719,37 @@ impl<'db> TypeInferenceBuilder<'db> { right: Type<'db>, range: TextRange, ) -> Result, CompareUnsupportedError<'db>> { + let is_str_literal_in_tuple = |literal: Type<'db>, tuple: TupleType<'db>| { + // Protect against doing a lot of work for pathologically large + // tuples. + // + // Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311 + if tuple.len(self.db()) > 1 << 12 { + return None; + } + + let mut definitely_true = false; + let mut definitely_false = true; + for element in tuple.elements(self.db()) { + if element.is_string_literal() { + if literal == *element { + definitely_true = true; + definitely_false = false; + } + } else if !literal.is_disjoint_from(self.db(), *element) { + definitely_false = false; + } + } + + if definitely_true { + Some(true) + } else if definitely_false { + Some(false) + } else { + None + } + }; + // Note: identity (is, is not) for equal builtin types is unreliable and not part of the // language spec. // - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal @@ -6850,6 +6881,30 @@ impl<'db> TypeInferenceBuilder<'db> { } } } + (Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::In => { + if let Some(answer) = is_str_literal_in_tuple(left, tuple) { + return Ok(Type::BooleanLiteral(answer)); + } + + self.infer_binary_type_comparison( + KnownClass::Str.to_instance(self.db()), + op, + right, + range, + ) + } + (Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::NotIn => { + if let Some(answer) = is_str_literal_in_tuple(left, tuple) { + return Ok(Type::BooleanLiteral(!answer)); + } + + self.infer_binary_type_comparison( + KnownClass::Str.to_instance(self.db()), + op, + right, + range, + ) + } (Type::StringLiteral(_), _) => self.infer_binary_type_comparison( KnownClass::Str.to_instance(self.db()), op, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 127f97c3c572c0..0b98b7a55e7227 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -43,3 +43,13 @@ def generic_context(type: Any) -> Any: ... # Returns the `__all__` names of a module as a tuple of sorted strings, or `None` if # either the module does not have `__all__` or it has invalid elements. def dunder_all_names(module: Any) -> Any: ... + +# Returns a tuple of all members of the given object, similar to `dir(obj)` and +# `inspect.getmembers(obj)`, with at least the following differences: +# +# * `dir` and `inspect.getmembers` may use runtime mutable state to construct +# the list of attributes returned. In contrast, this routine is limited to +# static information only. +# * `dir` will respect an object's `__dir__` implementation, if present, but +# this method (currently) does not. +def all_members(obj: Any) -> tuple[str, ...]: ... From 77c8ddf10189aff5511dcf60fb427ea6a6e711fb Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 30 May 2025 16:49:20 +0100 Subject: [PATCH 289/487] [ty] Fix broken property tests for disjointness (#18384) --- .../type_properties/is_disjoint_from.md | 11 ++++++++++ crates/ty_python_semantic/src/types.rs | 21 +++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index 46cf9c907483e5..a39826671be6f4 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -454,15 +454,23 @@ static_assert(is_disjoint_from(bool, Callable[..., Any])) static_assert(is_disjoint_from(C, Callable[..., Any])) static_assert(is_disjoint_from(bool | C, Callable[..., Any])) +static_assert(is_disjoint_from(Callable[..., Any], bool)) +static_assert(is_disjoint_from(Callable[..., Any], C)) +static_assert(is_disjoint_from(Callable[..., Any], bool | C)) + static_assert(not is_disjoint_from(str, Callable[..., Any])) static_assert(not is_disjoint_from(bool | str, Callable[..., Any])) +static_assert(not is_disjoint_from(Callable[..., Any], str)) +static_assert(not is_disjoint_from(Callable[..., Any], bool | str)) + def bound_with_valid_type(): @final class D: def __call__(self, *args: Any, **kwargs: Any) -> Any: ... static_assert(not is_disjoint_from(D, Callable[..., Any])) + static_assert(not is_disjoint_from(Callable[..., Any], D)) def possibly_unbound_with_valid_type(flag: bool): @final @@ -471,6 +479,7 @@ def possibly_unbound_with_valid_type(flag: bool): def __call__(self, *args: Any, **kwargs: Any) -> Any: ... static_assert(not is_disjoint_from(E, Callable[..., Any])) + static_assert(not is_disjoint_from(Callable[..., Any], E)) def bound_with_invalid_type(): @final @@ -478,6 +487,7 @@ def bound_with_invalid_type(): __call__: int = 1 static_assert(is_disjoint_from(F, Callable[..., Any])) + static_assert(is_disjoint_from(Callable[..., Any], F)) def possibly_unbound_with_invalid_type(flag: bool): @final @@ -486,4 +496,5 @@ def possibly_unbound_with_invalid_type(flag: bool): __call__: int = 1 static_assert(is_disjoint_from(G, Callable[..., Any])) + static_assert(is_disjoint_from(Callable[..., Any], G)) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f885b45f58656c..f125540688ad18 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2180,23 +2180,22 @@ impl<'db> Type<'db> { ( Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), - Type::NominalInstance(instance), + instance @ Type::NominalInstance(NominalInstanceType { class, .. }), ) | ( - Type::NominalInstance(instance), + instance @ Type::NominalInstance(NominalInstanceType { class, .. }), Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), - ) if instance.class.is_final(db) => { - let member = self.member_lookup_with_policy( + ) if class.is_final(db) => instance + .member_lookup_with_policy( db, Name::new_static("__call__"), MemberLookupPolicy::NO_INSTANCE_FALLBACK, - ); - match member.symbol { - // TODO: ideally this would check disjointness of the `__call__` signature and the callable signature - Symbol::Type(ty, _) => !ty.is_assignable_to(db, CallableType::unknown(db)), - Symbol::Unbound => true, - } - } + ) + .symbol + .ignore_possibly_unbound() + .is_none_or(|dunder_call| { + !dunder_call.is_assignable_to(db, CallableType::unknown(db)) + }), ( Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), From fc549bda94aeb05b9d9fa4d5d98a844638eb26bc Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Fri, 30 May 2025 13:36:57 -0400 Subject: [PATCH 290/487] [ty] Minor tweaks to "list all members" docs and tests (#18388) Ref https://github.com/astral-sh/ruff/pull/18251#pullrequestreview-2881810681 --- .../ty_python_semantic/resources/mdtest/binary/in.md | 10 +++------- .../resources/mdtest/ide_support/all_members.md | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/binary/in.md b/crates/ty_python_semantic/resources/mdtest/binary/in.md index 508ccdb67c25e0..b85fef5f1d8276 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/in.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/in.md @@ -23,13 +23,11 @@ x = ("quux", "bar", "baz") static_assert("foo" not in x) ``` -## Statically unknown results in a type error +## Statically unknown results in a `bool` ```py -from ty_extensions import static_assert - def _(a: str, b: str): - static_assert("foo" in (a, b)) # error: [static-assert-error] + reveal_type("foo" in (a, b)) # revealed: bool ``` ## Values being unknown doesn't mean the result is unknown @@ -46,8 +44,6 @@ def _(a: int, b: int): ## Failure cases ```py -from ty_extensions import static_assert - # We don't support byte strings. -static_assert(b"foo" not in (b"quux", b"foo", b"baz")) # error: [static-assert-error] +reveal_type(b"foo" not in (b"quux", b"foo", b"baz")) # revealed: bool ``` diff --git a/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md index 63a0c21c30fe60..05b4ceb75a7852 100644 --- a/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md +++ b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md @@ -4,8 +4,8 @@ -The `ty_extensions.all_members` function allows access to a list of accessible members/attributes on -a given object. For example, all member functions of `str` are available on `"a"`: +The `ty_extensions.all_members` function allows access to a tuple of accessible members/attributes +on a given object. For example, all member functions of `str` are available on `"a"`: ```py from ty_extensions import all_members, static_assert From ad024f9a09484beb17e194ea3ac877a45e6efaa7 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 30 May 2025 12:01:51 -0700 Subject: [PATCH 291/487] [ty] support callability of bound/constrained typevars (#18389) ## Summary Allow a typevar to be callable if it is bound to a callable type, or constrained to callable types. I spent some time digging into why this support didn't fall out naturally, and ultimately the reason is that we look up `__call__` on the meta type (since its a dunder), and our implementation of `Type::to_meta_type` for `Type::Callable` does not return a type with `__call__`. A more general solution here would be to have `Type::to_meta_type` for `Type::Callable` synthesize a protocol with `__call__` and return an intersection with that protocol (since for a type to be callable, we know its meta-type must have `__call__`). That solution could in principle also replace the special-case handling of `Type::Callable` itself, here in `Type::bindings`. But that more general approach would also be slower, and our protocol support isn't quite ready for that yet, and handling this directly in `Type::bindings` is really not bad. Fixes https://github.com/astral-sh/ty/issues/480 ## Test Plan Added mdtests. --- .../mdtest/generics/legacy/variables.md | 24 +++++++++++++++++++ .../mdtest/generics/pep695/variables.md | 20 ++++++++++++++++ crates/ty_python_semantic/src/types.rs | 10 +++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 35cadfd39968bd..b58507f43c743f 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -172,4 +172,28 @@ T = TypeVar("T", covariant=cond()) U = TypeVar("U", contravariant=cond()) ``` +## Callability + +A typevar bound to a Callable type is callable: + +```py +from typing import Callable, TypeVar + +T = TypeVar("T", bound=Callable[[], int]) + +def bound(f: T): + reveal_type(f) # revealed: T + reveal_type(f()) # revealed: int +``` + +Same with a constrained typevar, as long as all constraints are callable: + +```py +T = TypeVar("T", Callable[[], int], Callable[[], str]) + +def constrained(f: T): + reveal_type(f) # revealed: T + reveal_type(f()) # revealed: int | str +``` + [generics]: https://typing.python.org/en/latest/spec/generics.html diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index 15a7e651e6c640..ecee87ec86fc19 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -746,4 +746,24 @@ def h[T: (P, None)](t: T) -> None: p: P = t ``` +## Callability + +A typevar bound to a Callable type is callable: + +```py +from typing import Callable + +def bound[T: Callable[[], int]](f: T): + reveal_type(f) # revealed: T + reveal_type(f()) # revealed: int +``` + +Same with a constrained typevar, as long as all constraints are callable: + +```py +def constrained[T: (Callable[[], int], Callable[[], str])](f: T): + reveal_type(f) # revealed: T + reveal_type(f()) # revealed: int | str +``` + [pep 695]: https://peps.python.org/pep-0695/ diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f125540688ad18..7704d133c250a6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3608,6 +3608,15 @@ impl<'db> Type<'db> { .into() } + Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) { + None => CallableBinding::not_callable(self).into(), + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db), + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => Bindings::from_union( + self, + constraints.elements(db).iter().map(|ty| ty.bindings(db)), + ), + }, + Type::BoundMethod(bound_method) => { let signature = bound_method.function(db).signature(db); CallableBinding::from_overloads(self, signature.overloads.iter().cloned()) @@ -4422,7 +4431,6 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::Tuple(_) | Type::BoundSuper(_) - | Type::TypeVar(_) | Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(), } } From 9bbf4987e86690b7181aaa7992d7c757e2fac64d Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 30 May 2025 15:00:56 -0500 Subject: [PATCH 292/487] Implement template strings (#17851) This PR implements template strings (t-strings) in the parser and formatter for Ruff. Minimal changes necessary to compile were made in other parts of the code (e.g. ty, the linter, etc.). These will be covered properly in follow-up PRs. --- .../test/fixtures/flake8_bandit/S104.py | 5 + .../test/fixtures/flake8_bandit/S108.py | 4 + .../test/fixtures/flake8_bandit/S608.py | 10 + .../test/fixtures/flake8_pyi/PYI053.py | 2 + .../test/fixtures/flake8_pyi/PYI053.pyi | 4 + .../fixtures/flake8_quotes/doubles_escaped.py | 24 + .../fixtures/flake8_quotes/singles_escaped.py | 22 + .../test/fixtures/pycodestyle/W605_1.py | 71 +- crates/ruff_linter/src/checkers/ast/mod.rs | 21 +- .../rules/hardcoded_bind_all_interfaces.rs | 3 + .../rules/hardcoded_sql_expression.rs | 11 +- .../rules/hardcoded_tmp_directory.rs | 3 + .../rules/suspicious_function_call.rs | 6 +- ...s__flake8_bandit__tests__S104_S104.py.snap | 7 +- ...s__flake8_bandit__tests__S608_S608.py.snap | 9 + .../rules/string_or_bytes_too_long.rs | 11 +- .../flake8_pytest_style/rules/helpers.rs | 16 +- .../rules/avoidable_escaped_quote.rs | 90 +- .../rules/check_string_quotes.rs | 5 +- .../rules/unnecessary_escaped_quote.rs | 35 +- ...quire_doubles_over_singles_escaped.py.snap | 183 + ...re_doubles_over_singles_escaped_py311.snap | 179 +- ...quire_singles_over_doubles_escaped.py.snap | 204 ++ ...re_singles_over_doubles_escaped_py311.snap | 200 +- crates/ruff_linter/src/rules/flynt/helpers.rs | 30 +- .../flynt/rules/static_join_to_fstring.rs | 4 +- .../rules/invalid_escape_sequence.rs | 83 +- ...s__pycodestyle__tests__W605_W605_1.py.snap | 345 +- .../rules/f_string_missing_placeholders.rs | 2 +- .../pylint/rules/assert_on_string_literal.rs | 16 +- .../pyupgrade/rules/use_pep604_annotation.rs | 1 + .../src/rules/refurb/rules/bit_count.rs | 1 + .../ruff/rules/ambiguous_unicode_character.rs | 7 +- .../ruff/rules/assert_with_print_message.rs | 50 +- .../explicit_f_string_type_conversion.rs | 4 +- .../invalid_formatter_suppression_comment.rs | 8 +- .../ruff/rules/missing_fstring_syntax.rs | 2 +- crates/ruff_python_ast/ast.toml | 22 +- crates/ruff_python_ast/generate.py | 5 +- crates/ruff_python_ast/src/comparable.rs | 200 +- crates/ruff_python_ast/src/expression.rs | 59 +- crates/ruff_python_ast/src/generated.rs | 296 +- crates/ruff_python_ast/src/helpers.rs | 111 +- crates/ruff_python_ast/src/node.rs | 56 +- crates/ruff_python_ast/src/nodes.rs | 518 ++- .../src/operator_precedence.rs | 3 +- crates/ruff_python_ast/src/python_version.rs | 7 + crates/ruff_python_ast/src/relocate.rs | 3 + crates/ruff_python_ast/src/str.rs | 2 +- crates/ruff_python_ast/src/str_prefix.rs | 47 + crates/ruff_python_ast/src/visitor.rs | 49 +- .../src/visitor/source_order.rs | 41 +- .../src/visitor/transformer.rs | 49 +- .../tests/comparable.rs | 87 +- .../snapshots/source_order__f_strings.snap | 13 +- .../snapshots/source_order__t_strings.snap | 17 + .../tests/snapshots/visitor__f_strings.snap | 13 +- .../tests/snapshots/visitor__t_strings.snap | 16 + .../tests/source_order.rs | 9 + .../tests/visitor.rs | 28 +- crates/ruff_python_codegen/src/generator.rs | 64 +- crates/ruff_python_formatter/generate.py | 6 +- .../test/fixtures/ruff/expression/binary.py | 13 + .../join_implicit_concatenated_string.py | 65 + ...implicit_concatenated_string_assignment.py | 151 +- .../ruff/expression/tstring.options.json | 1 + .../test/fixtures/ruff/expression/tstring.py | 731 ++++ crates/ruff_python_formatter/src/builders.rs | 4 +- .../src/comments/placement.rs | 19 +- crates/ruff_python_formatter/src/context.rs | 49 +- .../src/expression/expr_f_string.rs | 8 +- .../src/expression/expr_t_string.rs | 59 + .../src/expression/mod.rs | 8 + crates/ruff_python_formatter/src/generated.rs | 64 + .../src/other/f_string.rs | 88 +- .../src/other/interpolated_string.rs | 73 + ...ment.rs => interpolated_string_element.rs} | 77 +- crates/ruff_python_formatter/src/other/mod.rs | 4 +- .../src/other/t_string.rs | 40 + .../ruff_python_formatter/src/pattern/mod.rs | 3 +- crates/ruff_python_formatter/src/range.rs | 8 +- .../src/statement/stmt_assign.rs | 194 +- .../src/string/implicit.rs | 104 +- .../ruff_python_formatter/src/string/mod.rs | 90 +- .../src/string/normalize.rs | 219 +- .../ruff_python_formatter/tests/normalizer.rs | 26 +- .../format@expression__binary.py.snap | 31 + ..._join_implicit_concatenated_string.py.snap | 127 +- ...cit_concatenated_string_assignment.py.snap | 318 +- .../format@expression__tstring.py.snap | 1536 ++++++++ .../inline/err/t_string_empty_expression.py | 3 + ...string_invalid_conversion_flag_name_tok.py | 2 + ...tring_invalid_conversion_flag_other_tok.py | 3 + .../err/t_string_invalid_starred_expr.py | 5 + .../t_string_lambda_without_parentheses.py | 2 + .../inline/err/t_string_unclosed_lbrace.py | 6 + ...t_string_unclosed_lbrace_in_format_spec.py | 3 + .../inline/ok/param_with_annotation.py | 1 - .../inline/ok/pep750_t_string_py314.py | 10 + .../resources/valid/expressions/t_string.py | 74 + crates/ruff_python_parser/src/error.rs | 58 +- crates/ruff_python_parser/src/lexer.rs | 384 +- .../{fstring.rs => interpolated_string.rs} | 68 +- crates/ruff_python_parser/src/lib.rs | 4 +- .../src/parser/expression.rs | 339 +- .../ruff_python_parser/src/parser/helpers.rs | 1 + crates/ruff_python_parser/src/parser/mod.rs | 61 +- .../ruff_python_parser/src/parser/pattern.rs | 2 +- .../src/parser/statement.rs | 2 +- ..._parser__lexer__tests__empty_tstrings.snap | 98 + ..._python_parser__lexer__tests__fstring.snap | 9 +- ...arser__lexer__tests__fstring_comments.snap | 5 +- ...ser__lexer__tests__fstring_conversion.snap | 9 +- ..._parser__lexer__tests__fstring_escape.snap | 7 +- ...__lexer__tests__fstring_escape_braces.snap | 9 +- ...ser__lexer__tests__fstring_escape_raw.snap | 7 +- ...__tests__fstring_expression_multiline.snap | 5 +- ...rser__lexer__tests__fstring_multiline.snap | 11 +- ...__lexer__tests__fstring_named_unicode.snap | 3 +- ...xer__tests__fstring_named_unicode_raw.snap | 5 +- ..._parser__lexer__tests__fstring_nested.snap | 15 +- ...er__lexer__tests__fstring_parentheses.snap | 17 +- ...__fstring_single_quote_escape_mac_eol.snap | 3 +- ..._fstring_single_quote_escape_unix_eol.snap | 3 +- ...tring_single_quote_escape_windows_eol.snap | 3 +- ...exer__tests__fstring_with_format_spec.snap | 17 +- ...ests__fstring_with_ipy_escape_command.snap | 5 +- ...s__fstring_with_multiline_format_spec.snap | 25 +- ..._tests__fstring_with_named_expression.snap | 9 +- ...__lexer__tests__fstring_with_nul_char.snap | 3 +- ...r__lexer__tests__nested_t_and_fstring.snap | 226 ++ ..._python_parser__lexer__tests__tstring.snap | 105 + ...arser__lexer__tests__tstring_comments.snap | 71 + ...ser__lexer__tests__tstring_conversion.snap | 133 + ..._parser__lexer__tests__tstring_escape.snap | 86 + ...__lexer__tests__tstring_escape_braces.snap | 133 + ...ser__lexer__tests__tstring_escape_raw.snap | 86 + ...__tests__tstring_expression_multiline.snap | 85 + ...rser__lexer__tests__tstring_multiline.snap | 136 + ...__lexer__tests__tstring_named_unicode.snap | 36 + ...xer__tests__tstring_named_unicode_raw.snap | 59 + ..._parser__lexer__tests__tstring_nested.snap | 216 ++ ...er__lexer__tests__tstring_parentheses.snap | 209 ++ ..._parser__lexer__tests__tstring_prefix.snap | 153 + ...__tstring_single_quote_escape_mac_eol.snap | 36 + ..._tstring_single_quote_escape_unix_eol.snap | 36 + ...tring_single_quote_escape_windows_eol.snap | 36 + ...exer__tests__tstring_with_format_spec.snap | 289 ++ ...ests__tstring_with_ipy_escape_command.snap | 63 + ...tests__tstring_with_lambda_expression.snap | 125 + ...s__tstring_with_multiline_format_spec.snap | 295 ++ ..._tests__tstring_with_named_expression.snap | 187 + ...__lexer__tests__tstring_with_nul_char.snap | 36 + ...string__tests__fstring_constant_range.snap | 15 +- ...ing__tests__fstring_escaped_character.snap | 7 +- ...tring__tests__fstring_escaped_newline.snap | 7 +- ...ing__tests__fstring_line_continuation.snap | 7 +- ...__fstring_parse_self_documenting_base.snap | 5 +- ...ring_parse_self_documenting_base_more.snap | 13 +- ...fstring_parse_self_documenting_format.snap | 9 +- ...ing__tests__fstring_unescaped_newline.snap | 7 +- ...r__string__tests__parse_empty_fstring.snap | 1 - ...r__string__tests__parse_empty_tstring.snap | 31 + ...tring__tests__parse_f_string_concat_1.snap | 3 +- ...tring__tests__parse_f_string_concat_2.snap | 3 +- ...tring__tests__parse_f_string_concat_3.snap | 7 +- ...tring__tests__parse_f_string_concat_4.snap | 7 +- ...ing__tests__parse_f_t_string_concat_1.snap | 58 + ...ing__tests__parse_f_t_string_concat_2.snap | 69 + ..._parser__string__tests__parse_fstring.snap | 11 +- ...__string__tests__parse_fstring_equals.snap | 5 +- ...ring_nested_concatenation_string_spec.snap | 11 +- ...ing__tests__parse_fstring_nested_spec.snap | 11 +- ...sts__parse_fstring_nested_string_spec.snap | 11 +- ...ring__tests__parse_fstring_not_equals.snap | 5 +- ..._tests__parse_fstring_not_nested_spec.snap | 9 +- ...ts__parse_fstring_self_doc_prec_space.snap | 5 +- ...parse_fstring_self_doc_trailing_space.snap | 5 +- ...ring__tests__parse_fstring_yield_expr.snap | 5 +- ...tring__tests__parse_t_string_concat_1.snap | 51 + ...tring__tests__parse_t_string_concat_2.snap | 51 + ...tring__tests__parse_t_string_concat_3.snap | 77 + ...tring__tests__parse_t_string_concat_4.snap | 88 + ..._parser__string__tests__parse_tstring.snap | 68 + ...__string__tests__parse_tstring_equals.snap | 66 + ...ring_nested_concatenation_string_spec.snap | 93 + ...ing__tests__parse_tstring_nested_spec.snap | 68 + ...sts__parse_tstring_nested_string_spec.snap | 79 + ...ring__tests__parse_tstring_not_equals.snap | 66 + ..._tests__parse_tstring_not_nested_spec.snap | 59 + ...ts__parse_tstring_self_doc_prec_space.snap | 52 + ...parse_tstring_self_doc_trailing_space.snap | 52 + ...ring__tests__parse_tstring_yield_expr.snap | 46 + ...ing__tests__parse_u_f_string_concat_1.snap | 3 +- ...ing__tests__parse_u_f_string_concat_2.snap | 3 +- ...ing__tests__parse_u_t_string_concat_1.snap | 51 + ...ing__tests__parse_u_t_string_concat_2.snap | 62 + ...on_parser__string__tests__raw_fstring.snap | 5 +- ...on_parser__string__tests__raw_tstring.snap | 49 + ...ing__tests__triple_quoted_raw_fstring.snap | 5 +- ...ing__tests__triple_quoted_raw_tstring.snap | 49 + ...string__tests__tstring_constant_range.snap | 80 + ...ing__tests__tstring_escaped_character.snap | 53 + ...tring__tests__tstring_escaped_newline.snap | 53 + ...ing__tests__tstring_line_continuation.snap | 55 + ...__tstring_parse_self_documenting_base.snap | 52 + ...ring_parse_self_documenting_base_more.snap | 84 + ...tstring_parse_self_documenting_format.snap | 64 + ...ing__tests__tstring_unescaped_newline.snap | 53 + crates/ruff_python_parser/src/string.rs | 313 +- crates/ruff_python_parser/src/token.rs | 62 +- crates/ruff_python_parser/tests/fixtures.rs | 4 +- ...ann_assign_stmt_invalid_annotation.py.snap | 21 +- ...d_syntax@f_string_empty_expression.py.snap | 9 +- ...g_invalid_conversion_flag_name_tok.py.snap | 5 +- ..._invalid_conversion_flag_other_tok.py.snap | 9 +- ...ntax@f_string_invalid_starred_expr.py.snap | 13 +- ..._string_lambda_without_parentheses.py.snap | 9 +- ...id_syntax@f_string_unclosed_lbrace.py.snap | 20 +- ...ing_unclosed_lbrace_in_format_spec.py.snap | 19 +- ...x@function_def_invalid_return_expr.py.snap | 10 + ...y_concatenated_unterminated_string.py.snap | 6 +- ...ated_unterminated_string_multiline.py.snap | 10 +- ...syntax@invalid_annotation_function.py.snap | 16 +- ...ax@invalid_fstring_literal_element.py.snap | 5 +- ...mixed_bytes_and_non_bytes_literals.py.snap | 5 +- ...ntax@param_with_invalid_annotation.py.snap | 10 + ...param_with_invalid_star_annotation.py.snap | 11 + ...valid_syntax@pep701_f_string_py311.py.snap | 72 +- ...nvalid_syntax@re_lex_logical_token.py.snap | 6 +- ...x@re_lexing__fstring_format_spec_1.py.snap | 30 +- ...re_lexing__triple_quoted_fstring_1.py.snap | 6 +- ...re_lexing__triple_quoted_fstring_2.py.snap | 9 +- ...re_lexing__triple_quoted_fstring_3.py.snap | 9 +- ...ements__invalid_assignment_targets.py.snap | 14 +- ...nvalid_augmented_assignment_target.py.snap | 14 +- ...d_syntax@t_string_empty_expression.py.snap | 113 + ...g_invalid_conversion_flag_name_tok.py.snap | 63 + ..._invalid_conversion_flag_other_tok.py.snap | 113 + ...ntax@t_string_invalid_starred_expr.py.snap | 205 ++ ..._string_lambda_without_parentheses.py.snap | 118 + ...id_syntax@t_string_unclosed_lbrace.py.snap | 359 ++ ...ing_unclosed_lbrace_in_format_spec.py.snap | 142 + ...erminated_fstring_newline_recovery.py.snap | 20 +- ...invalid_syntax@write_to_debug_expr.py.snap | 4 +- ...valid_syntax@expressions__f_string.py.snap | 275 +- ...valid_syntax@expressions__t_string.py.snap | 3127 +++++++++++++++++ ...tax@fstring_format_spec_terminator.py.snap | 23 +- ...syntax@match_classify_as_keyword_1.py.snap | 7 +- ...valid_syntax@param_with_annotation.py.snap | 68 +- ...valid_syntax@pep701_f_string_py311.py.snap | 70 +- ...valid_syntax@pep701_f_string_py312.py.snap | 60 +- ...valid_syntax@pep750_t_string_py314.py.snap | 584 +++ ...atement__ambiguous_lpar_with_items.py.snap | 13 +- .../valid_syntax@statement__match.py.snap | 4 +- .../valid_syntax@statement__try.py.snap | 37 +- .../src/analyze/type_inference.rs | 1 + crates/ruff_python_semantic/src/model.rs | 20 +- .../src/semantic_index/re_exports.rs | 2 + .../ty_python_semantic/src/semantic_model.rs | 2 + crates/ty_python_semantic/src/types/infer.rs | 70 +- 261 files changed, 18022 insertions(+), 1801 deletions(-) create mode 100644 crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__t_strings.snap create mode 100644 crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__t_strings.snap create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.options.json create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py create mode 100644 crates/ruff_python_formatter/src/expression/expr_t_string.rs create mode 100644 crates/ruff_python_formatter/src/other/interpolated_string.rs rename crates/ruff_python_formatter/src/other/{f_string_element.rs => interpolated_string_element.rs} (80%) create mode 100644 crates/ruff_python_formatter/src/other/t_string.rs create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_empty_expression.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_name_tok.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_other_tok.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_invalid_starred_expr.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py create mode 100644 crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace_in_format_spec.py create mode 100644 crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314.py create mode 100644 crates/ruff_python_parser/resources/valid/expressions/t_string.py rename crates/ruff_python_parser/src/lexer/{fstring.rs => interpolated_string.rs} (61%) create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__empty_tstrings.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__nested_t_and_fstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_comments.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_conversion.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_braces.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_raw.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_expression_multiline.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_multiline.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode_raw.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_nested.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_parentheses.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_prefix.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_mac_eol.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_unix_eol.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_windows_eol.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_format_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_ipy_escape_command.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_lambda_expression.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_multiline_format_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_named_expression.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_nul_char.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S104.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S104.py index d7f9716ef1a6cb..9dcd08ecec25b5 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S104.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S104.py @@ -22,3 +22,8 @@ def my_func(): # Implicit string concatenation "0.0.0.0" f"0.0.0.0{expr}0.0.0.0" + +# t-strings - all ok +t"0.0.0.0" +"0.0.0.0" t"0.0.0.0{expr}0.0.0.0" +"0.0.0.0" f"0.0.0.0{expr}0.0.0.0" t"0.0.0.0{expr}0.0.0.0" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S108.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S108.py index 610a6700cdba70..ca73cd6879d7c7 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S108.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S108.py @@ -40,3 +40,7 @@ with TemporaryDirectory(dir="/tmp") as d: pass + +# ok (runtime error from t-string) +with open(t"/foo/bar", "w") as f: + f.write("def") diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py index 447e46dcf25c01..620a18c038849b 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py @@ -169,3 +169,13 @@ def query54(): # https://github.com/astral-sh/ruff/issues/17967 query61 = f"SELECT * FROM table" # skip expressionless f-strings + +# t-strings +query62 = t"SELECT * FROM table" +query63 = t""" + SELECT *, + foo + FROM ({user_input}) raw +""" +query64 = f"update {t"{table}"} set var = {t"{var}"}" +query65 = t"update {f"{table}"} set var = {f"{var}"}" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py index 12cac66d132c93..b3fbf5ab9e4463 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py @@ -72,3 +72,5 @@ def not_warnings_dot_deprecated( @not_warnings_dot_deprecated("Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!") def not_a_deprecated_function() -> None: ... + +baz: str = t"51 character stringgggggggggggggggggggggggggggggggg" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi index 5cb3585c577605..caf9f55e97ea70 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi @@ -80,3 +80,7 @@ x: TypeAlias = Literal["fooooooooooooooooooooooooooooooooooooooooooooooooooooooo # Ok y: TypeAlias = Annotated[int, "metadataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] + +ttoo: str = t"50 character stringggggggggggggggggggggggggggggggg" # OK + +tbar: str = t"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped.py b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped.py index 7f789a22fbad9c..92a2744f4bf88c 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped.py @@ -39,3 +39,27 @@ f'\'normal\' {f'nested'} "double quotes"' f'\'normal\' {f'\'nested\' {'other'} normal'} "double quotes"' # Q003 f'\'normal\' {f'\'nested\' {'other'} "double quotes"'} normal' # Q00l + + + +# Same as above, but with t-strings +t'This is a \'string\'' # Q003 +t'This is \\ a \\\'string\'' # Q003 +t'"This" is a \'string\'' +f"This is a 'string'" +f"\"This\" is a 'string'" +fr'This is a \'string\'' +fR'This is a \'string\'' +foo = ( + t'This is a' + t'\'string\'' # Q003 +) +t'\'foo\' {'nested'}' # Q003 +t'\'foo\' {t'nested'}' # Q003 +t'\'foo\' {t'\'nested\''} \'\'' # Q003 + +t'normal {t'nested'} normal' +t'\'normal\' {t'nested'} normal' # Q003 +t'\'normal\' {t'nested'} "double quotes"' +t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped.py b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped.py index 815db5bdb7af98..d68017f3805f54 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped.py @@ -37,3 +37,25 @@ f"\"normal\" {f"nested"} 'single quotes'" f"\"normal\" {f"\"nested\" {"other"} normal"} 'single quotes'" # Q003 f"\"normal\" {f"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + + +# Same as above, but with t-strings +t"This is a \"string\"" +t"'This' is a \"string\"" +f'This is a "string"' +f'\'This\' is a "string"' +fr"This is a \"string\"" +fR"This is a \"string\"" +foo = ( + t"This is a" + t"\"string\"" +) +t"\"foo\" {"foo"}" # Q003 +t"\"foo\" {t"foo"}" # Q003 +t"\"foo\" {t"\"foo\""} \"\"" # Q003 + +t"normal {t"nested"} normal" +t"\"normal\" {t"nested"} normal" # Q003 +t"\"normal\" {t"nested"} 'single quotes'" +t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py index 735881301ec5b9..c29b7d53ec741e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py @@ -1,4 +1,4 @@ -# Same as `W605_0.py` but using f-strings instead. +# Same as `W605_0.py` but using f-strings and t-strings instead. #: W605:1:10 regex = f'\.png$' @@ -66,3 +66,72 @@ # Debug text (should trigger) t = f"{'\InHere'=}" + + + +#: W605:1:10 +regex = t'\.png$' + +#: W605:2:1 +regex = t''' +\.png$ +''' + +#: W605:2:6 +f( + t'\_' +) + +#: W605:4:6 +t""" +multi-line +literal +with \_ somewhere +in the middle +""" + +#: W605:1:38 +value = t'new line\nand invalid escape \_ here' + + +#: Okay +regex = fr'\.png$' +regex = t'\\.png$' +regex = fr''' +\.png$ +''' +regex = fr''' +\\.png$ +''' +s = t'\\' +regex = t'\w' # noqa +regex = t''' +\w +''' # noqa + +regex = t'\\\_' +value = t'\{{1}}' +value = t'\{1}' +value = t'{1:\}' +value = t"{t"\{1}"}" +value = rt"{t"\{1}"}" + +# Okay +value = rt'\{{1}}' +value = rt'\{1}' +value = rt'{1:\}' +value = t"{rt"\{1}"}" + +# Regression tests for https://github.com/astral-sh/ruff/issues/10434 +t"{{}}+-\d" +t"\n{{}}+-\d+" +t"\n{{}}�+-\d+" + +# See https://github.com/astral-sh/ruff/issues/11491 +total = 10 +ok = 7 +incomplete = 3 +s = t"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n" + +# Debug text (should trigger) +t = t"{'\InHere'=}" diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index db57ecc87fa1d3..3ca0b6ed5bf091 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -37,8 +37,8 @@ use ruff_python_ast::str::Quote; use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern}; use ruff_python_ast::{ self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr, - ExprContext, FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern, - PythonVersion, Stmt, Suite, UnaryOp, + ExprContext, InterpolatedStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, + Pattern, PythonVersion, Stmt, Suite, UnaryOp, }; use ruff_python_ast::{PySourceType, helpers, str, visitor}; use ruff_python_codegen::{Generator, Stylist}; @@ -338,6 +338,7 @@ impl<'a> Checker<'a> { ast::BytesLiteralFlags::empty().with_quote_style(self.preferred_quote()) } + // TODO(dylan) add similar method for t-strings /// Return the default f-string flags a generated `FString` node should use, given where we are /// in the AST. pub(crate) fn default_fstring_flags(&self) -> ast::FStringFlags { @@ -1897,6 +1898,10 @@ impl<'a> Visitor<'a> for Checker<'a> { self.semantic.flags |= SemanticModelFlags::F_STRING; visitor::walk_expr(self, expr); } + Expr::TString(_) => { + self.semantic.flags |= SemanticModelFlags::T_STRING; + visitor::walk_expr(self, expr); + } Expr::Named(ast::ExprNamed { target, value, @@ -1930,6 +1935,7 @@ impl<'a> Visitor<'a> for Checker<'a> { } Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self), Expr::FString(f_string) => analyze::string_like(f_string.into(), self), + Expr::TString(t_string) => analyze::string_like(t_string.into(), self), _ => {} } @@ -2119,12 +2125,15 @@ impl<'a> Visitor<'a> for Checker<'a> { } } - fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) { + fn visit_interpolated_string_element( + &mut self, + interpolated_string_element: &'a InterpolatedStringElement, + ) { let snapshot = self.semantic.flags; - if f_string_element.is_expression() { - self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD; + if interpolated_string_element.is_interpolation() { + self.semantic.flags |= SemanticModelFlags::INTERPOLATED_STRING_REPLACEMENT_FIELD; } - visitor::walk_f_string_element(self, f_string_element); + visitor::walk_interpolated_string_element(self, interpolated_string_element); self.semantic.flags = snapshot; } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs index 9b9628bb0358cf..843dd5c3b61436 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_bind_all_interfaces.rs @@ -63,6 +63,9 @@ pub(crate) fn hardcoded_bind_all_interfaces(checker: &Checker, string: StringLik } } } + StringLike::Bytes(_) => (), + // TODO(dylan): decide whether to trigger here + StringLike::TString(_) => (), } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index 7080cb57f49063..76baa9c47988a4 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -101,10 +101,11 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) { // f"select * from table where val = {val}" Expr::FString(f_string) - if f_string - .value - .f_strings() - .any(|fs| fs.elements.iter().any(ast::FStringElement::is_expression)) => + if f_string.value.f_strings().any(|fs| { + fs.elements + .iter() + .any(ast::InterpolatedStringElement::is_interpolation) + }) => { concatenated_f_string(f_string, checker.locator()) } @@ -175,6 +176,8 @@ fn is_explicit_concatenation(expr: &Expr) -> Option { Expr::DictComp(_) => Some(false), Expr::Compare(_) => Some(false), Expr::FString(_) => Some(true), + // TODO(dylan): decide whether to trigger here + Expr::TString(_) => Some(false), Expr::StringLiteral(_) => Some(true), Expr::BytesLiteral(_) => Some(false), Expr::NoneLiteral(_) => Some(false), diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs index 963314df64e2a2..03df078819c778 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs @@ -75,7 +75,10 @@ pub(crate) fn hardcoded_tmp_directory(checker: &Checker, string: StringLike) { } } } + // These are not actually strings StringLike::Bytes(_) => (), + // TODO(dylan) - verify that we should skip these + StringLike::TString(_) => (), } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index 28944882b4832c..f558707fec23c1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -1006,9 +1006,9 @@ fn suspicious_function( // Ex) f"foo" Expr::FString(ast::ExprFString { value, .. }) => { value.elements().next().and_then(|element| { - if let ast::FStringElement::Literal(ast::FStringLiteralElement { - value, .. - }) = element + if let ast::InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { value, .. }, + ) = element { Some(Either::Right(value.chars())) } else { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S104_S104.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S104_S104.py.snap index 15fec6bd706465..bcad262f2fb657 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S104_S104.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S104_S104.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs -snapshot_kind: text --- S104.py:9:1: S104 Possible binding to all interfaces | @@ -48,6 +47,8 @@ S104.py:24:1: S104 Possible binding to all interfaces 23 | # Implicit string concatenation 24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0" | ^^^^^^^^^ S104 +25 | +26 | # t-strings - all ok | S104.py:24:13: S104 Possible binding to all interfaces @@ -55,6 +56,8 @@ S104.py:24:13: S104 Possible binding to all interfaces 23 | # Implicit string concatenation 24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0" | ^^^^^^^ S104 +25 | +26 | # t-strings - all ok | S104.py:24:26: S104 Possible binding to all interfaces @@ -62,4 +65,6 @@ S104.py:24:26: S104 Possible binding to all interfaces 23 | # Implicit string concatenation 24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0" | ^^^^^^^ S104 +25 | +26 | # t-strings - all ok | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap index 2170539c1747ff..b19b9631f606e8 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap @@ -604,3 +604,12 @@ S608.py:164:11: S608 Possible SQL injection vector through string-based query co 169 | 170 | # https://github.com/astral-sh/ruff/issues/17967 | + +S608.py:180:11: S608 Possible SQL injection vector through string-based query construction + | +178 | FROM ({user_input}) raw +179 | """ +180 | query64 = f"update {t"{table}"} set var = {t"{var}"}" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S608 +181 | query65 = t"update {f"{table}"} set var = {f"{var}"}" + | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index 76ae0e1e0b09c2..a739f91d361932 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -67,6 +67,11 @@ pub(crate) fn string_or_bytes_too_long(checker: &Checker, string: StringLike) { StringLike::String(ast::ExprStringLiteral { value, .. }) => value.chars().count(), StringLike::Bytes(ast::ExprBytesLiteral { value, .. }) => value.len(), StringLike::FString(node) => count_f_string_chars(node), + // TODO(dylan): decide how to count chars, especially + // if interpolations are of different type than `str` + StringLike::TString(_) => { + return; + } }; if length <= 50 { return; @@ -91,8 +96,10 @@ fn count_f_string_chars(f_string: &ast::ExprFString) -> usize { .elements .iter() .map(|element| match element { - ast::FStringElement::Literal(string) => string.chars().count(), - ast::FStringElement::Expression(expr) => expr.range().len().to_usize(), + ast::InterpolatedStringElement::Literal(string) => string.chars().count(), + ast::InterpolatedStringElement::Interpolation(expr) => { + expr.range().len().to_usize() + } }) .sum(), }) diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs index 7b886dd5cc6536..24d3a987a6e3ec 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs @@ -106,19 +106,23 @@ pub(super) fn is_empty_or_null_string(expr: &Expr) -> bool { ast::FStringPart::FString(f_string) => f_string .elements .iter() - .all(is_empty_or_null_fstring_element), + .all(is_empty_or_null_interpolated_string_element), }) } _ => false, } } -fn is_empty_or_null_fstring_element(element: &ast::FStringElement) -> bool { +fn is_empty_or_null_interpolated_string_element(element: &ast::InterpolatedStringElement) -> bool { match element { - ast::FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => value.is_empty(), - ast::FStringElement::Expression(ast::FStringExpressionElement { expression, .. }) => { - is_empty_or_null_string(expression) - } + ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { + value, + .. + }) => value.is_empty(), + ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { + expression, + .. + }) => is_empty_or_null_string(expression), } } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs index b5b0ceadab0207..47972e53aff466 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs @@ -1,7 +1,7 @@ use flake8_quotes::helpers::{contains_escaped_quote, raw_contents, unescape_string}; use flake8_quotes::settings::Quote; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::visitor::{Visitor, walk_f_string}; +use ruff_python_ast::visitor::{Visitor, walk_f_string, walk_t_string}; use ruff_python_ast::{self as ast, AnyStringFlags, PythonVersion, StringFlags, StringLike}; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -54,7 +54,7 @@ pub(crate) fn avoidable_escaped_quote(checker: &Checker, string_like: StringLike // This rule has support for strings nested inside another f-strings but they're checked // via the outermost f-string. This means that we shouldn't be checking any nested string // or f-string. - || checker.semantic().in_f_string_replacement_field() + || checker.semantic().in_interpolated_string_replacement_field() { return; } @@ -70,6 +70,7 @@ pub(crate) fn avoidable_escaped_quote(checker: &Checker, string_like: StringLike rule_checker.visit_bytes_literal(bytes_literal); } ast::StringLikePart::FString(f_string) => rule_checker.visit_f_string(f_string), + ast::StringLikePart::TString(t_string) => rule_checker.visit_t_string(t_string), } } } @@ -179,25 +180,70 @@ impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> { .literals() .any(|literal| contains_quote(literal, opposite_quote_char)) { - check_f_string(self.checker, self.quotes_settings, f_string); + check_interpolated_string( + self.checker, + self.quotes_settings, + AnyStringFlags::from(f_string.flags), + &f_string.elements, + f_string.range, + ); } walk_f_string(self, f_string); } + + fn visit_t_string(&mut self, t_string: &'_ ast::TString) { + let opposite_quote_char = self.quotes_settings.inline_quotes.opposite().as_char(); + + // If any literal part of this t-string contains the quote character which is opposite to + // the configured inline quotes, we can't change the quote style for this t-string. For + // example: + // + // ```py + // t"\"hello\" {x} 'world'" + // ``` + // + // If we try to fix the above example, the t-string will end in the middle and "world" will + // be considered as a variable which is outside this t-string: + // + // ```py + // t'"hello" {x} 'world'' + // # ^ + // # t-string ends here now + // ``` + // + // The check is local to this t-string and it shouldn't check for any literal parts of any + // nested t-string. + if !t_string + .elements + .literals() + .any(|literal| contains_quote(literal, opposite_quote_char)) + { + check_interpolated_string( + self.checker, + self.quotes_settings, + AnyStringFlags::from(t_string.flags), + &t_string.elements, + t_string.range, + ); + } + + walk_t_string(self, t_string); + } } /// Checks for unnecessary escaped quotes in a string or bytes literal. /// /// # Panics /// -/// If the string kind is an f-string. +/// If the string kind is an f-string or a t-string. fn check_string_or_bytes( checker: &Checker, quotes_settings: &flake8_quotes::settings::Settings, range: TextRange, flags: AnyStringFlags, ) { - assert!(!flags.is_f_string()); + assert!(!flags.is_interpolated_string()); let locator = checker.locator(); @@ -231,16 +277,14 @@ fn check_string_or_bytes( ))); } -/// Checks for unnecessary escaped quotes in an f-string. -fn check_f_string( +/// Checks for unnecessary escaped quotes in an f-string or t-string. +fn check_interpolated_string( checker: &Checker, quotes_settings: &flake8_quotes::settings::Settings, - f_string: &ast::FString, + flags: ast::AnyStringFlags, + elements: &ast::InterpolatedStringElements, + range: TextRange, ) { - let locator = checker.locator(); - - let ast::FString { flags, range, .. } = f_string; - if flags.is_triple_quoted() || flags.prefix().is_raw() { return; } @@ -254,8 +298,8 @@ fn check_f_string( let opposite_quote_char = quotes_settings.inline_quotes.opposite().as_char(); let mut edits = vec![]; - for literal in f_string.elements.literals() { - let content = locator.slice(literal); + for literal in elements.literals() { + let content = checker.locator().slice(literal); if !contains_escaped_quote(content, quote_char) { continue; } @@ -269,10 +313,10 @@ fn check_f_string( return; } - // Replacement for the f-string opening quote. We don't perform the check for raw and + // Replacement for the f/t-string opening quote. We don't perform the check for raw and // triple-quoted f-strings, so no need to account for them. let start_edit = Edit::range_replacement( - format!("f{opposite_quote_char}"), + format!("{}{opposite_quote_char}", flags.prefix()), TextRange::at( range.start(), // Prefix + quote char @@ -280,16 +324,15 @@ fn check_f_string( ), ); - // Replacement for the f-string ending quote. We don't perform the check for triple-quoted + // Replacement for the f/t-string ending quote. We don't perform the check for triple-quoted // f-string, so no need to account for them. edits.push(Edit::range_replacement( opposite_quote_char.to_string(), TextRange::at( // Offset would either be the end offset of the start edit in case there are no - // elements in the f-string (e.g., `f""`) or the end offset of the last f-string + // elements in the f/t-string (e.g., `f""`) or the end offset of the last f/t-string // element (e.g., `f"hello"`). - f_string - .elements + elements .last() .map_or_else(|| start_edit.end(), Ranged::end), // Quote char @@ -298,7 +341,7 @@ fn check_f_string( )); checker - .report_diagnostic(AvoidableEscapedQuote, *range) + .report_diagnostic(AvoidableEscapedQuote, range) .set_fix(Fix::safe_edits(start_edit, edits)); } @@ -320,6 +363,11 @@ impl Visitor<'_> for ContainsAnyString { self.result = true; // We don't need to recurse into this f-string now that we already know the result. } + + fn visit_t_string(&mut self, _: &'_ ast::TString) { + self.result = true; + // We don't need to recurse into this t-string now that we already know the result. + } } /// Return `true` if the haystack contains the quote. diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index 6d8ae3237e989a..2607d3d2d19046 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -444,7 +444,10 @@ pub(crate) fn check_string_quotes(checker: &Checker, string_like: StringLike) { } // TODO(dhruvmanila): Support checking for escaped quotes in f-strings. - if checker.semantic().in_f_string_replacement_field() { + if checker + .semantic() + .in_interpolated_string_replacement_field() + { return; } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs index 32ea80a749a0fa..5c7d6c91d04f53 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs @@ -1,5 +1,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::{self as ast, AnyStringFlags, StringFlags, StringLike}; +use ruff_python_ast::{ + self as ast, AnyStringFlags, InterpolatedStringElements, StringFlags, StringLike, +}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -62,7 +64,20 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi bytes_literal.range(), AnyStringFlags::from(bytes_literal.flags), ), - ast::StringLikePart::FString(f_string) => check_f_string(checker, f_string), + ast::StringLikePart::FString(ast::FString { + elements, + range, + flags, + }) => { + check_interpolated_string(checker, AnyStringFlags::from(*flags), *range, elements); + } + ast::StringLikePart::TString(ast::TString { + elements, + range, + flags, + }) => { + check_interpolated_string(checker, AnyStringFlags::from(*flags), *range, elements); + } } } } @@ -73,7 +88,7 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi /// /// If the string kind is an f-string. fn check_string_or_bytes(checker: &Checker, range: TextRange, flags: AnyStringFlags) { - assert!(!flags.is_f_string()); + assert!(!flags.is_interpolated_string()); if flags.is_triple_quoted() || flags.is_raw_string() { return; @@ -96,9 +111,13 @@ fn check_string_or_bytes(checker: &Checker, range: TextRange, flags: AnyStringFl ))); } -/// Checks for unnecessary escaped quotes in an f-string. -fn check_f_string(checker: &Checker, f_string: &ast::FString) { - let ast::FString { flags, range, .. } = f_string; +/// Checks for unnecessary escaped quotes in an f-string or t-string. +fn check_interpolated_string( + checker: &Checker, + flags: AnyStringFlags, + range: TextRange, + elements: &InterpolatedStringElements, +) { if flags.is_triple_quoted() || flags.prefix().is_raw() { return; } @@ -106,7 +125,7 @@ fn check_f_string(checker: &Checker, f_string: &ast::FString) { let opposite_quote_char = flags.quote_style().opposite().as_char(); let mut edits = vec![]; - for literal in f_string.elements.literals() { + for literal in elements.literals() { let content = checker.locator().slice(literal); if !contains_escaped_quote(content, opposite_quote_char) { continue; @@ -122,6 +141,6 @@ fn check_f_string(checker: &Checker, f_string: &ast::FString) { return; }; - let mut diagnostic = checker.report_diagnostic(UnnecessaryEscapedQuote, *range); + let mut diagnostic = checker.report_diagnostic(UnnecessaryEscapedQuote, range); diagnostic.set_fix(Fix::safe_edits(first, edits_iter)); } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped.py.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped.py.snap index a1099a8e0b323f..260805955b156b 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped.py.snap +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped.py.snap @@ -197,6 +197,8 @@ singles_escaped.py:38:15: Q003 [*] Change outer quotes to avoid escaping inner q 38 |-f"\"normal\" {f"\"nested\" {"other"} normal"} 'single quotes'" # Q003 38 |+f"\"normal\" {f'"nested" {"other"} normal'} 'single quotes'" # Q003 39 39 | f"\"normal\" {f"\"nested\" {"other"} 'single quotes'"} normal" # Q003 +40 40 | +41 41 | singles_escaped.py:39:1: Q003 [*] Change outer quotes to avoid escaping inner quotes | @@ -213,3 +215,184 @@ singles_escaped.py:39:1: Q003 [*] Change outer quotes to avoid escaping inner qu 38 38 | f"\"normal\" {f"\"nested\" {"other"} normal"} 'single quotes'" # Q003 39 |-f"\"normal\" {f"\"nested\" {"other"} 'single quotes'"} normal" # Q003 39 |+f'"normal" {f"\"nested\" {"other"} 'single quotes'"} normal' # Q003 +40 40 | +41 41 | +42 42 | # Same as above, but with t-strings + +singles_escaped.py:43:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +42 | # Same as above, but with t-strings +43 | t"This is a \"string\"" + | ^^^^^^^^^^^^^^^^^^^^^^^ Q003 +44 | t"'This' is a \"string\"" +45 | f'This is a "string"' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +40 40 | +41 41 | +42 42 | # Same as above, but with t-strings +43 |-t"This is a \"string\"" + 43 |+t'This is a "string"' +44 44 | t"'This' is a \"string\"" +45 45 | f'This is a "string"' +46 46 | f'\'This\' is a "string"' + +singles_escaped.py:51:5: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +49 | foo = ( +50 | t"This is a" +51 | t"\"string\"" + | ^^^^^^^^^^^^^ Q003 +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +48 48 | fR"This is a \"string\"" +49 49 | foo = ( +50 50 | t"This is a" +51 |- t"\"string\"" + 51 |+ t'"string"' +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 + +singles_escaped.py:53:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +51 | t"\"string\"" +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 + | ^^^^^^^^^^^^^^^^^^ Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +50 50 | t"This is a" +51 51 | t"\"string\"" +52 52 | ) +53 |-t"\"foo\" {"foo"}" # Q003 + 53 |+t'"foo" {"foo"}' # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | + +singles_escaped.py:54:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 + | ^^^^^^^^^^^^^^^^^^^ Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +51 51 | t"\"string\"" +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 |-t"\"foo\" {t"foo"}" # Q003 + 54 |+t'"foo" {t"foo"}' # Q003 +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" + +singles_escaped.py:55:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +56 | +57 | t"normal {t"nested"} normal" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 |-t"\"foo\" {t"\"foo\""} \"\"" # Q003 + 55 |+t'"foo" {t"\"foo\""} ""' # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 + +singles_escaped.py:55:12: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | ^^^^^^^^^^ Q003 +56 | +57 | t"normal {t"nested"} normal" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 |-t"\"foo\" {t"\"foo\""} \"\"" # Q003 + 55 |+t"\"foo\" {t'"foo"'} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 + +singles_escaped.py:58:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t"normal {t"nested"} normal" +58 | t"\"normal\" {t"nested"} normal" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 |-t"\"normal\" {t"nested"} normal" # Q003 + 58 |+t'"normal" {t"nested"} normal' # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + +singles_escaped.py:60:15: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +58 | t"\"normal\" {t"nested"} normal" # Q003 +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 |-t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + 60 |+t"\"normal\" {t'"nested" {"other"} normal'} 'single quotes'" # Q003 +61 61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + +singles_escaped.py:61:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +58 58 | t"\"normal\" {t"nested"} normal" # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 |-t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + 61 |+t'"normal" {t"\"nested\" {"other"} 'single quotes'"} normal' # Q003 diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped_py311.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped_py311.snap index 0ca32efbb57025..cf7a8b3c70e42e 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped_py311.snap +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_doubles_over_singles_escaped_py311.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs -snapshot_kind: text --- singles_escaped.py:1:26: Q003 [*] Change outer quotes to avoid escaping inner quotes | @@ -77,3 +76,181 @@ singles_escaped.py:21:5: Q003 [*] Change outer quotes to avoid escaping inner qu 22 22 | ) 23 23 | 24 24 | # Nested f-strings (Python 3.12+) + +singles_escaped.py:43:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +42 | # Same as above, but with t-strings +43 | t"This is a \"string\"" + | ^^^^^^^^^^^^^^^^^^^^^^^ Q003 +44 | t"'This' is a \"string\"" +45 | f'This is a "string"' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +40 40 | +41 41 | +42 42 | # Same as above, but with t-strings +43 |-t"This is a \"string\"" + 43 |+t'This is a "string"' +44 44 | t"'This' is a \"string\"" +45 45 | f'This is a "string"' +46 46 | f'\'This\' is a "string"' + +singles_escaped.py:51:5: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +49 | foo = ( +50 | t"This is a" +51 | t"\"string\"" + | ^^^^^^^^^^^^^ Q003 +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +48 48 | fR"This is a \"string\"" +49 49 | foo = ( +50 50 | t"This is a" +51 |- t"\"string\"" + 51 |+ t'"string"' +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 + +singles_escaped.py:53:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +51 | t"\"string\"" +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 + | ^^^^^^^^^^^^^^^^^^ Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +50 50 | t"This is a" +51 51 | t"\"string\"" +52 52 | ) +53 |-t"\"foo\" {"foo"}" # Q003 + 53 |+t'"foo" {"foo"}' # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | + +singles_escaped.py:54:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +52 | ) +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 + | ^^^^^^^^^^^^^^^^^^^ Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +51 51 | t"\"string\"" +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 |-t"\"foo\" {t"foo"}" # Q003 + 54 |+t'"foo" {t"foo"}' # Q003 +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" + +singles_escaped.py:55:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +56 | +57 | t"normal {t"nested"} normal" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 |-t"\"foo\" {t"\"foo\""} \"\"" # Q003 + 55 |+t'"foo" {t"\"foo\""} ""' # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 + +singles_escaped.py:55:12: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | t"\"foo\" {"foo"}" # Q003 +54 | t"\"foo\" {t"foo"}" # Q003 +55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 + | ^^^^^^^^^^ Q003 +56 | +57 | t"normal {t"nested"} normal" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | ) +53 53 | t"\"foo\" {"foo"}" # Q003 +54 54 | t"\"foo\" {t"foo"}" # Q003 +55 |-t"\"foo\" {t"\"foo\""} \"\"" # Q003 + 55 |+t"\"foo\" {t'"foo"'} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 + +singles_escaped.py:58:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t"normal {t"nested"} normal" +58 | t"\"normal\" {t"nested"} normal" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +55 55 | t"\"foo\" {t"\"foo\""} \"\"" # Q003 +56 56 | +57 57 | t"normal {t"nested"} normal" +58 |-t"\"normal\" {t"nested"} normal" # Q003 + 58 |+t'"normal" {t"nested"} normal' # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + +singles_escaped.py:60:15: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +58 | t"\"normal\" {t"nested"} normal" # Q003 +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +57 57 | t"normal {t"nested"} normal" +58 58 | t"\"normal\" {t"nested"} normal" # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 |-t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 + 60 |+t"\"normal\" {t'"nested" {"other"} normal'} 'single quotes'" # Q003 +61 61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + +singles_escaped.py:61:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +59 | t"\"normal\" {t"nested"} 'single quotes'" +60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 | t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +58 58 | t"\"normal\" {t"nested"} normal" # Q003 +59 59 | t"\"normal\" {t"nested"} 'single quotes'" +60 60 | t"\"normal\" {t"\"nested\" {"other"} normal"} 'single quotes'" # Q003 +61 |-t"\"normal\" {t"\"nested\" {"other"} 'single quotes'"} normal" # Q003 + 61 |+t'"normal" {t"\"nested\" {"other"} 'single quotes'"} normal' # Q003 diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped.py.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped.py.snap index feff195fb7e75c..014b383162502a 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped.py.snap +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped.py.snap @@ -236,6 +236,8 @@ doubles_escaped.py:40:15: Q003 [*] Change outer quotes to avoid escaping inner q 40 |-f'\'normal\' {f'\'nested\' {'other'} normal'} "double quotes"' # Q003 40 |+f'\'normal\' {f"'nested' {'other'} normal"} "double quotes"' # Q003 41 41 | f'\'normal\' {f'\'nested\' {'other'} "double quotes"'} normal' # Q00l +42 42 | +43 43 | doubles_escaped.py:41:1: Q003 [*] Change outer quotes to avoid escaping inner quotes | @@ -252,3 +254,205 @@ doubles_escaped.py:41:1: Q003 [*] Change outer quotes to avoid escaping inner qu 40 40 | f'\'normal\' {f'\'nested\' {'other'} normal'} "double quotes"' # Q003 41 |-f'\'normal\' {f'\'nested\' {'other'} "double quotes"'} normal' # Q00l 41 |+f"'normal' {f'\'nested\' {'other'} "double quotes"'} normal" # Q00l +42 42 | +43 43 | +44 44 | + +doubles_escaped.py:46:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +45 | # Same as above, but with t-strings +46 | t'This is a \'string\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^ Q003 +47 | t'This is \\ a \\\'string\'' # Q003 +48 | t'"This" is a \'string\'' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +43 43 | +44 44 | +45 45 | # Same as above, but with t-strings +46 |-t'This is a \'string\'' # Q003 + 46 |+t"This is a 'string'" # Q003 +47 47 | t'This is \\ a \\\'string\'' # Q003 +48 48 | t'"This" is a \'string\'' +49 49 | f"This is a 'string'" + +doubles_escaped.py:47:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +45 | # Same as above, but with t-strings +46 | t'This is a \'string\'' # Q003 +47 | t'This is \\ a \\\'string\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +48 | t'"This" is a \'string\'' +49 | f"This is a 'string'" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +44 44 | +45 45 | # Same as above, but with t-strings +46 46 | t'This is a \'string\'' # Q003 +47 |-t'This is \\ a \\\'string\'' # Q003 + 47 |+t"This is \\ a \\'string'" # Q003 +48 48 | t'"This" is a \'string\'' +49 49 | f"This is a 'string'" +50 50 | f"\"This\" is a 'string'" + +doubles_escaped.py:55:5: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | foo = ( +54 | t'This is a' +55 | t'\'string\'' # Q003 + | ^^^^^^^^^^^^^ Q003 +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | fR'This is a \'string\'' +53 53 | foo = ( +54 54 | t'This is a' +55 |- t'\'string\'' # Q003 + 55 |+ t"'string'" # Q003 +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 + +doubles_escaped.py:57:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +55 | t'\'string\'' # Q003 +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^ Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +54 54 | t'This is a' +55 55 | t'\'string\'' # Q003 +56 56 | ) +57 |-t'\'foo\' {'nested'}' # Q003 + 57 |+t"'foo' {'nested'}" # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | + +doubles_escaped.py:58:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^ Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +55 55 | t'\'string\'' # Q003 +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 |-t'\'foo\' {t'nested'}' # Q003 + 58 |+t"'foo' {t'nested'}" # Q003 +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' + +doubles_escaped.py:59:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +60 | +61 | t'normal {t'nested'} normal' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 |-t'\'foo\' {t'\'nested\''} \'\'' # Q003 + 59 |+t"'foo' {t'\'nested\''} ''" # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 + +doubles_escaped.py:59:12: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | ^^^^^^^^^^^^^ Q003 +60 | +61 | t'normal {t'nested'} normal' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 |-t'\'foo\' {t'\'nested\''} \'\'' # Q003 + 59 |+t'\'foo\' {t"'nested'"} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 + +doubles_escaped.py:62:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +61 | t'normal {t'nested'} normal' +62 | t'\'normal\' {t'nested'} normal' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 |-t'\'normal\' {t'nested'} normal' # Q003 + 62 |+t"'normal' {t'nested'} normal" # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + +doubles_escaped.py:64:15: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +62 | t'\'normal\' {t'nested'} normal' # Q003 +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 |-t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + 64 |+t'\'normal\' {t"'nested' {'other'} normal"} "double quotes"' # Q003 +65 65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + +doubles_escaped.py:65:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +62 62 | t'\'normal\' {t'nested'} normal' # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 |-t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + 65 |+t"'normal' {t'\'nested\' {'other'} "double quotes"'} normal" # Q00l diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped_py311.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped_py311.snap index f6768a4b6fde0c..6b0b2348cac627 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped_py311.snap +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__require_singles_over_doubles_escaped_py311.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs -snapshot_kind: text --- doubles_escaped.py:1:26: Q003 [*] Change outer quotes to avoid escaping inner quotes | @@ -116,3 +115,202 @@ doubles_escaped.py:23:5: Q003 [*] Change outer quotes to avoid escaping inner qu 24 24 | ) 25 25 | 26 26 | # Nested f-strings (Python 3.12+) + +doubles_escaped.py:46:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +45 | # Same as above, but with t-strings +46 | t'This is a \'string\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^ Q003 +47 | t'This is \\ a \\\'string\'' # Q003 +48 | t'"This" is a \'string\'' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +43 43 | +44 44 | +45 45 | # Same as above, but with t-strings +46 |-t'This is a \'string\'' # Q003 + 46 |+t"This is a 'string'" # Q003 +47 47 | t'This is \\ a \\\'string\'' # Q003 +48 48 | t'"This" is a \'string\'' +49 49 | f"This is a 'string'" + +doubles_escaped.py:47:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +45 | # Same as above, but with t-strings +46 | t'This is a \'string\'' # Q003 +47 | t'This is \\ a \\\'string\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +48 | t'"This" is a \'string\'' +49 | f"This is a 'string'" + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +44 44 | +45 45 | # Same as above, but with t-strings +46 46 | t'This is a \'string\'' # Q003 +47 |-t'This is \\ a \\\'string\'' # Q003 + 47 |+t"This is \\ a \\'string'" # Q003 +48 48 | t'"This" is a \'string\'' +49 49 | f"This is a 'string'" +50 50 | f"\"This\" is a 'string'" + +doubles_escaped.py:55:5: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +53 | foo = ( +54 | t'This is a' +55 | t'\'string\'' # Q003 + | ^^^^^^^^^^^^^ Q003 +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +52 52 | fR'This is a \'string\'' +53 53 | foo = ( +54 54 | t'This is a' +55 |- t'\'string\'' # Q003 + 55 |+ t"'string'" # Q003 +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 + +doubles_escaped.py:57:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +55 | t'\'string\'' # Q003 +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^ Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +54 54 | t'This is a' +55 55 | t'\'string\'' # Q003 +56 56 | ) +57 |-t'\'foo\' {'nested'}' # Q003 + 57 |+t"'foo' {'nested'}" # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | + +doubles_escaped.py:58:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +56 | ) +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^ Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +55 55 | t'\'string\'' # Q003 +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 |-t'\'foo\' {t'nested'}' # Q003 + 58 |+t"'foo' {t'nested'}" # Q003 +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' + +doubles_escaped.py:59:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +60 | +61 | t'normal {t'nested'} normal' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 |-t'\'foo\' {t'\'nested\''} \'\'' # Q003 + 59 |+t"'foo' {t'\'nested\''} ''" # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 + +doubles_escaped.py:59:12: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +57 | t'\'foo\' {'nested'}' # Q003 +58 | t'\'foo\' {t'nested'}' # Q003 +59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 + | ^^^^^^^^^^^^^ Q003 +60 | +61 | t'normal {t'nested'} normal' + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +56 56 | ) +57 57 | t'\'foo\' {'nested'}' # Q003 +58 58 | t'\'foo\' {t'nested'}' # Q003 +59 |-t'\'foo\' {t'\'nested\''} \'\'' # Q003 + 59 |+t'\'foo\' {t"'nested'"} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 + +doubles_escaped.py:62:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +61 | t'normal {t'nested'} normal' +62 | t'\'normal\' {t'nested'} normal' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +59 59 | t'\'foo\' {t'\'nested\''} \'\'' # Q003 +60 60 | +61 61 | t'normal {t'nested'} normal' +62 |-t'\'normal\' {t'nested'} normal' # Q003 + 62 |+t"'normal' {t'nested'} normal" # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + +doubles_escaped.py:64:15: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +62 | t'\'normal\' {t'nested'} normal' # Q003 +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 +65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +61 61 | t'normal {t'nested'} normal' +62 62 | t'\'normal\' {t'nested'} normal' # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 |-t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 + 64 |+t'\'normal\' {t"'nested' {'other'} normal"} "double quotes"' # Q003 +65 65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + +doubles_escaped.py:65:1: Q003 [*] Change outer quotes to avoid escaping inner quotes + | +63 | t'\'normal\' {t'nested'} "double quotes"' +64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 | t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q003 + | + = help: Change outer quotes to avoid escaping inner quotes + +ℹ Safe fix +62 62 | t'\'normal\' {t'nested'} normal' # Q003 +63 63 | t'\'normal\' {t'nested'} "double quotes"' +64 64 | t'\'normal\' {t'\'nested\' {'other'} normal'} "double quotes"' # Q003 +65 |-t'\'normal\' {t'\'nested\' {'other'} "double quotes"'} normal' # Q00l + 65 |+t"'normal' {t'\'nested\' {'other'} "double quotes"'} normal" # Q00l diff --git a/crates/ruff_linter/src/rules/flynt/helpers.rs b/crates/ruff_linter/src/rules/flynt/helpers.rs index a71b369b6987f4..4afb764c4ce062 100644 --- a/crates/ruff_linter/src/rules/flynt/helpers.rs +++ b/crates/ruff_linter/src/rules/flynt/helpers.rs @@ -2,8 +2,8 @@ use ruff_python_ast::{self as ast, Arguments, ConversionFlag, Expr}; use ruff_text_size::TextRange; /// Wrap an expression in a [`ast::FStringElement::Expression`] with no special formatting. -fn to_f_string_expression_element(inner: &Expr) -> ast::FStringElement { - ast::FStringElement::Expression(ast::FStringExpressionElement { +fn to_interpolated_string_interpolation_element(inner: &Expr) -> ast::InterpolatedStringElement { + ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression: Box::new(inner.clone()), debug_text: None, conversion: ConversionFlag::None, @@ -12,9 +12,9 @@ fn to_f_string_expression_element(inner: &Expr) -> ast::FStringElement { }) } -/// Convert a string to a [`ast::FStringElement::Literal`]. -pub(super) fn to_f_string_literal_element(s: &str) -> ast::FStringElement { - ast::FStringElement::Literal(ast::FStringLiteralElement { +/// Convert a string to a [`ast::InterpolatedStringLiteralElement `]. +pub(super) fn to_interpolated_string_literal_element(s: &str) -> ast::InterpolatedStringElement { + ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { value: Box::from(s), range: TextRange::default(), }) @@ -48,20 +48,24 @@ fn is_simple_callee(func: &Expr) -> bool { } } -/// Convert an expression to a f-string element (if it looks like a good idea). -pub(super) fn to_f_string_element(expr: &Expr) -> Option { +/// Convert an expression to an f-string or t-string element (if it looks like a good idea). +pub(super) fn to_interpolated_string_element( + expr: &Expr, +) -> Option { match expr { - Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => { - Some(ast::FStringElement::Literal(ast::FStringLiteralElement { + Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => Some( + ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { value: value.to_string().into_boxed_str(), range: *range, - })) - } + }), + ), // These should be pretty safe to wrap in a formatted value. Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) | Expr::Name(_) | Expr::Attribute(_) => { - Some(to_f_string_expression_element(expr)) + Some(to_interpolated_string_interpolation_element(expr)) + } + Expr::Call(_) if is_simple_call(expr) => { + Some(to_interpolated_string_interpolation_element(expr)) } - Expr::Call(_) if is_simple_call(expr) => Some(to_f_string_expression_element(expr)), _ => None, } } diff --git a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs index 968c386b514707..aa07cb58af8615 100644 --- a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs @@ -105,9 +105,9 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option< return None; } if !std::mem::take(&mut first) { - f_string_elements.push(helpers::to_f_string_literal_element(joiner)); + f_string_elements.push(helpers::to_interpolated_string_literal_element(joiner)); } - f_string_elements.push(helpers::to_f_string_element(expr)?); + f_string_elements.push(helpers::to_interpolated_string_element(expr)?); } let node = ast::FString { diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs index d5e0107e162c40..1e0fe8507a9932 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs @@ -1,7 +1,10 @@ use memchr::memchr_iter; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::{AnyStringFlags, FStringElement, StringLike, StringLikePart}; +use ruff_python_ast::{ + AnyStringFlags, InterpolatedStringElement, InterpolatedStringElements, StringLike, + StringLikePart, +}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; @@ -70,39 +73,16 @@ pub(crate) fn invalid_escape_sequence(checker: &Checker, string_like: StringLike StringLikePart::String(_) | StringLikePart::Bytes(_) => { analyze_escape_chars(locator, part.range(), part.flags()) } - StringLikePart::FString(f_string) => { - let flags = AnyStringFlags::from(f_string.flags); - let mut escape_chars_state = EscapeCharsState::default(); - // Whether we suggest converting to a raw string or - // adding backslashes depends on the presence of valid - // escape characters in the entire f-string. Therefore, - // we must analyze escape characters in each f-string - // element before pushing a diagnostic and fix. - for element in &f_string.elements { - match element { - FStringElement::Literal(literal) => { - escape_chars_state.update(analyze_escape_chars( - locator, - literal.range(), - flags, - )); - } - FStringElement::Expression(expression) => { - let Some(format_spec) = expression.format_spec.as_ref() else { - continue; - }; - for literal in format_spec.elements.literals() { - escape_chars_state.update(analyze_escape_chars( - locator, - literal.range(), - flags, - )); - } - } - } - } - escape_chars_state - } + StringLikePart::FString(f_string) => analyze_escape_chars_in_interpolated_string( + AnyStringFlags::from(f_string.flags), + &f_string.elements, + locator, + ), + StringLikePart::TString(t_string) => analyze_escape_chars_in_interpolated_string( + AnyStringFlags::from(t_string.flags), + &t_string.elements, + locator, + ), }; check(checker, locator, part.start(), part.flags(), state); } @@ -146,7 +126,7 @@ fn analyze_escape_chars( let next_char = match source[i + 1..].chars().next() { Some(next_char) => next_char, - None if flags.is_f_string() => { + None if flags.is_interpolated_string() => { // If we're at the end of a f-string middle token, the next character // is actually emitted as a different token. For example, // @@ -230,6 +210,39 @@ fn analyze_escape_chars( } } +fn analyze_escape_chars_in_interpolated_string( + flags: AnyStringFlags, + elements: &InterpolatedStringElements, + locator: &Locator, +) -> EscapeCharsState { + let mut escape_chars_state = EscapeCharsState::default(); + // Whether we suggest converting to a raw string or + // adding backslashes depends on the presence of valid + // escape characters in the entire f/t-string. Therefore, + // we must analyze escape characters in each f/t-string + // element before pushing a diagnostic and fix. + for element in elements { + match element { + InterpolatedStringElement::Literal(literal) => { + escape_chars_state.update(analyze_escape_chars(locator, literal.range(), flags)); + } + InterpolatedStringElement::Interpolation(interpolation) => { + let Some(format_spec) = interpolation.format_spec.as_ref() else { + continue; + }; + for literal in format_spec.elements.literals() { + escape_chars_state.update(analyze_escape_chars( + locator, + literal.range(), + flags, + )); + } + } + } + } + escape_chars_state +} + /// Pushes a diagnostic and fix depending on escape characters seen so far. /// /// If we have not seen any valid escape characters, we convert to diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__W605_W605_1.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__W605_W605_1.py.snap index 8d763a16a3516e..4ca58ba4ec2e84 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__W605_W605_1.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__W605_W605_1.py.snap @@ -12,7 +12,7 @@ W605_1.py:4:11: W605 [*] Invalid escape sequence: `\.` = help: Use a raw string literal ℹ Safe fix -1 1 | # Same as `W605_0.py` but using f-strings instead. +1 1 | # Same as `W605_0.py` but using f-strings and t-strings instead. 2 2 | 3 3 | #: W605:1:10 4 |-regex = f'\.png$' @@ -320,3 +320,346 @@ W605_1.py:68:9: W605 [*] Invalid escape sequence: `\I` 67 67 | # Debug text (should trigger) 68 |-t = f"{'\InHere'=}" 68 |+t = f"{r'\InHere'=}" +69 69 | +70 70 | +71 71 | + +W605_1.py:73:11: W605 [*] Invalid escape sequence: `\.` + | +72 | #: W605:1:10 +73 | regex = t'\.png$' + | ^^ W605 +74 | +75 | #: W605:2:1 + | + = help: Use a raw string literal + +ℹ Safe fix +70 70 | +71 71 | +72 72 | #: W605:1:10 +73 |-regex = t'\.png$' + 73 |+regex = rt'\.png$' +74 74 | +75 75 | #: W605:2:1 +76 76 | regex = t''' + +W605_1.py:77:1: W605 [*] Invalid escape sequence: `\.` + | +75 | #: W605:2:1 +76 | regex = t''' +77 | \.png$ + | ^^ W605 +78 | ''' + | + = help: Use a raw string literal + +ℹ Safe fix +73 73 | regex = t'\.png$' +74 74 | +75 75 | #: W605:2:1 +76 |-regex = t''' + 76 |+regex = rt''' +77 77 | \.png$ +78 78 | ''' +79 79 | + +W605_1.py:82:7: W605 [*] Invalid escape sequence: `\_` + | +80 | #: W605:2:6 +81 | f( +82 | t'\_' + | ^^ W605 +83 | ) + | + = help: Use a raw string literal + +ℹ Safe fix +79 79 | +80 80 | #: W605:2:6 +81 81 | f( +82 |- t'\_' + 82 |+ rt'\_' +83 83 | ) +84 84 | +85 85 | #: W605:4:6 + +W605_1.py:89:6: W605 [*] Invalid escape sequence: `\_` + | +87 | multi-line +88 | literal +89 | with \_ somewhere + | ^^ W605 +90 | in the middle +91 | """ + | + = help: Use a raw string literal + +ℹ Safe fix +83 83 | ) +84 84 | +85 85 | #: W605:4:6 +86 |-t""" + 86 |+rt""" +87 87 | multi-line +88 88 | literal +89 89 | with \_ somewhere + +W605_1.py:94:40: W605 [*] Invalid escape sequence: `\_` + | +93 | #: W605:1:38 +94 | value = t'new line\nand invalid escape \_ here' + | ^^ W605 + | + = help: Add backslash to escape sequence + +ℹ Safe fix +91 91 | """ +92 92 | +93 93 | #: W605:1:38 +94 |-value = t'new line\nand invalid escape \_ here' + 94 |+value = t'new line\nand invalid escape \\_ here' +95 95 | +96 96 | +97 97 | #: Okay + +W605_1.py:109:1: W605 [*] Invalid escape sequence: `\w` + | +107 | regex = t'\w' # noqa +108 | regex = t''' +109 | \w + | ^^ W605 +110 | ''' # noqa + | + = help: Use a raw string literal + +ℹ Safe fix +105 105 | ''' +106 106 | s = t'\\' +107 107 | regex = t'\w' # noqa +108 |-regex = t''' + 108 |+regex = rt''' +109 109 | \w +110 110 | ''' # noqa +111 111 | + +W605_1.py:112:13: W605 [*] Invalid escape sequence: `\_` + | +110 | ''' # noqa +111 | +112 | regex = t'\\\_' + | ^^ W605 +113 | value = t'\{{1}}' +114 | value = t'\{1}' + | + = help: Add backslash to escape sequence + +ℹ Safe fix +109 109 | \w +110 110 | ''' # noqa +111 111 | +112 |-regex = t'\\\_' + 112 |+regex = t'\\\\_' +113 113 | value = t'\{{1}}' +114 114 | value = t'\{1}' +115 115 | value = t'{1:\}' + +W605_1.py:113:11: W605 [*] Invalid escape sequence: `\{` + | +112 | regex = t'\\\_' +113 | value = t'\{{1}}' + | ^^ W605 +114 | value = t'\{1}' +115 | value = t'{1:\}' + | + = help: Use a raw string literal + +ℹ Safe fix +110 110 | ''' # noqa +111 111 | +112 112 | regex = t'\\\_' +113 |-value = t'\{{1}}' + 113 |+value = rt'\{{1}}' +114 114 | value = t'\{1}' +115 115 | value = t'{1:\}' +116 116 | value = t"{t"\{1}"}" + +W605_1.py:114:11: W605 [*] Invalid escape sequence: `\{` + | +112 | regex = t'\\\_' +113 | value = t'\{{1}}' +114 | value = t'\{1}' + | ^^ W605 +115 | value = t'{1:\}' +116 | value = t"{t"\{1}"}" + | + = help: Use a raw string literal + +ℹ Safe fix +111 111 | +112 112 | regex = t'\\\_' +113 113 | value = t'\{{1}}' +114 |-value = t'\{1}' + 114 |+value = rt'\{1}' +115 115 | value = t'{1:\}' +116 116 | value = t"{t"\{1}"}" +117 117 | value = rt"{t"\{1}"}" + +W605_1.py:115:14: W605 [*] Invalid escape sequence: `\}` + | +113 | value = t'\{{1}}' +114 | value = t'\{1}' +115 | value = t'{1:\}' + | ^^ W605 +116 | value = t"{t"\{1}"}" +117 | value = rt"{t"\{1}"}" + | + = help: Use a raw string literal + +ℹ Safe fix +112 112 | regex = t'\\\_' +113 113 | value = t'\{{1}}' +114 114 | value = t'\{1}' +115 |-value = t'{1:\}' + 115 |+value = rt'{1:\}' +116 116 | value = t"{t"\{1}"}" +117 117 | value = rt"{t"\{1}"}" +118 118 | + +W605_1.py:116:14: W605 [*] Invalid escape sequence: `\{` + | +114 | value = t'\{1}' +115 | value = t'{1:\}' +116 | value = t"{t"\{1}"}" + | ^^ W605 +117 | value = rt"{t"\{1}"}" + | + = help: Use a raw string literal + +ℹ Safe fix +113 113 | value = t'\{{1}}' +114 114 | value = t'\{1}' +115 115 | value = t'{1:\}' +116 |-value = t"{t"\{1}"}" + 116 |+value = t"{rt"\{1}"}" +117 117 | value = rt"{t"\{1}"}" +118 118 | +119 119 | # Okay + +W605_1.py:117:15: W605 [*] Invalid escape sequence: `\{` + | +115 | value = t'{1:\}' +116 | value = t"{t"\{1}"}" +117 | value = rt"{t"\{1}"}" + | ^^ W605 +118 | +119 | # Okay + | + = help: Use a raw string literal + +ℹ Safe fix +114 114 | value = t'\{1}' +115 115 | value = t'{1:\}' +116 116 | value = t"{t"\{1}"}" +117 |-value = rt"{t"\{1}"}" + 117 |+value = rt"{rt"\{1}"}" +118 118 | +119 119 | # Okay +120 120 | value = rt'\{{1}}' + +W605_1.py:126:9: W605 [*] Invalid escape sequence: `\d` + | +125 | # Regression tests for https://github.com/astral-sh/ruff/issues/10434 +126 | t"{{}}+-\d" + | ^^ W605 +127 | t"\n{{}}+-\d+" +128 | t"\n{{}}�+-\d+" + | + = help: Use a raw string literal + +ℹ Safe fix +123 123 | value = t"{rt"\{1}"}" +124 124 | +125 125 | # Regression tests for https://github.com/astral-sh/ruff/issues/10434 +126 |-t"{{}}+-\d" + 126 |+rt"{{}}+-\d" +127 127 | t"\n{{}}+-\d+" +128 128 | t"\n{{}}�+-\d+" +129 129 | + +W605_1.py:127:11: W605 [*] Invalid escape sequence: `\d` + | +125 | # Regression tests for https://github.com/astral-sh/ruff/issues/10434 +126 | t"{{}}+-\d" +127 | t"\n{{}}+-\d+" + | ^^ W605 +128 | t"\n{{}}�+-\d+" + | + = help: Add backslash to escape sequence + +ℹ Safe fix +124 124 | +125 125 | # Regression tests for https://github.com/astral-sh/ruff/issues/10434 +126 126 | t"{{}}+-\d" +127 |-t"\n{{}}+-\d+" + 127 |+t"\n{{}}+-\\d+" +128 128 | t"\n{{}}�+-\d+" +129 129 | +130 130 | # See https://github.com/astral-sh/ruff/issues/11491 + +W605_1.py:128:12: W605 [*] Invalid escape sequence: `\d` + | +126 | t"{{}}+-\d" +127 | t"\n{{}}+-\d+" +128 | t"\n{{}}�+-\d+" + | ^^ W605 +129 | +130 | # See https://github.com/astral-sh/ruff/issues/11491 + | + = help: Add backslash to escape sequence + +ℹ Safe fix +125 125 | # Regression tests for https://github.com/astral-sh/ruff/issues/10434 +126 126 | t"{{}}+-\d" +127 127 | t"\n{{}}+-\d+" +128 |-t"\n{{}}�+-\d+" + 128 |+t"\n{{}}�+-\\d+" +129 129 | +130 130 | # See https://github.com/astral-sh/ruff/issues/11491 +131 131 | total = 10 + +W605_1.py:134:31: W605 [*] Invalid escape sequence: `\I` + | +132 | ok = 7 +133 | incomplete = 3 +134 | s = t"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n" + | ^^ W605 +135 | +136 | # Debug text (should trigger) + | + = help: Add backslash to escape sequence + +ℹ Safe fix +131 131 | total = 10 +132 132 | ok = 7 +133 133 | incomplete = 3 +134 |-s = t"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n" + 134 |+s = t"TOTAL: {total}\nOK: {ok}\\INCOMPLETE: {incomplete}\n" +135 135 | +136 136 | # Debug text (should trigger) +137 137 | t = t"{'\InHere'=}" + +W605_1.py:137:9: W605 [*] Invalid escape sequence: `\I` + | +136 | # Debug text (should trigger) +137 | t = t"{'\InHere'=}" + | ^^ W605 + | + = help: Use a raw string literal + +ℹ Safe fix +134 134 | s = t"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n" +135 135 | +136 136 | # Debug text (should trigger) +137 |-t = t"{'\InHere'=}" + 137 |+t = t"{r'\InHere'=}" diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs index 4262a7baa9f88c..3e15f4cfe334a1 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs @@ -73,7 +73,7 @@ pub(crate) fn f_string_missing_placeholders(checker: &Checker, expr: &ast::ExprF f_string .elements .iter() - .any(ast::FStringElement::is_expression) + .any(ast::InterpolatedStringElement::is_interpolation) }) { return; } diff --git a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs index 3e752543092fc0..64db3f1e400bc1 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/assert_on_string_literal.rs @@ -77,10 +77,10 @@ pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { ast::FStringPart::Literal(literal) => literal.is_empty(), ast::FStringPart::FString(f_string) => { f_string.elements.iter().all(|element| match element { - ast::FStringElement::Literal(ast::FStringLiteralElement { - value, .. - }) => value.is_empty(), - ast::FStringElement::Expression(_) => false, + ast::InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { value, .. }, + ) => value.is_empty(), + ast::InterpolatedStringElement::Interpolation(_) => false, }) } }) { @@ -89,10 +89,10 @@ pub(crate) fn assert_on_string_literal(checker: &Checker, test: &Expr) { ast::FStringPart::Literal(literal) => !literal.is_empty(), ast::FStringPart::FString(f_string) => { f_string.elements.iter().any(|element| match element { - ast::FStringElement::Literal(ast::FStringLiteralElement { - value, .. - }) => !value.is_empty(), - ast::FStringElement::Expression(_) => false, + ast::InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { value, .. }, + ) => !value.is_empty(), + ast::InterpolatedStringElement::Interpolation(_) => false, }) } }) { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 12a7798ad3f62d..b1b888cf4c719a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -252,6 +252,7 @@ fn is_allowed_value(expr: &Expr) -> bool { | Expr::Compare(_) | Expr::Call(_) | Expr::FString(_) + | Expr::TString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) diff --git a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs index 93946dcb1ff220..1dd8d9c89e8c2e 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs @@ -137,6 +137,7 @@ pub(crate) fn bit_count(checker: &Checker, call: &ExprCall) { Expr::StringLiteral(inner) => inner.value.is_implicit_concatenated(), Expr::BytesLiteral(inner) => inner.value.is_implicit_concatenated(), Expr::FString(inner) => inner.value.is_implicit_concatenated(), + Expr::TString(inner) => inner.value.is_implicit_concatenated(), Expr::Await(_) | Expr::Starred(_) diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index d1b1ef67edc77f..19228605b85a25 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -3,7 +3,7 @@ use std::fmt; use bitflags::bitflags; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::{self as ast, StringLike}; +use ruff_python_ast::{self as ast, FString, StringLike, TString}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; @@ -211,8 +211,9 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like: } } ast::StringLikePart::Bytes(_) => {} - ast::StringLikePart::FString(f_string) => { - for literal in f_string.elements.literals() { + ast::StringLikePart::FString(FString { elements, .. }) + | ast::StringLikePart::TString(TString { elements, .. }) => { + for literal in elements.literals() { let text = checker.locator().slice(literal); for candidate in ambiguous_unicode_character(text, literal.range(), checker.settings) diff --git a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs index 5c956340e14aec..8b3fad38dcaa05 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs @@ -88,9 +88,9 @@ pub(crate) fn assert_with_print_message(checker: &Checker, stmt: &ast::StmtAsser mod print_arguments { use itertools::Itertools; use ruff_python_ast::{ - Arguments, ConversionFlag, Expr, ExprFString, FString, FStringElement, FStringElements, - FStringExpressionElement, FStringFlags, FStringLiteralElement, FStringValue, StringLiteral, - StringLiteralFlags, + Arguments, ConversionFlag, Expr, ExprFString, FString, FStringFlags, FStringValue, + InterpolatedElement, InterpolatedStringElement, InterpolatedStringElements, + InterpolatedStringLiteralElement, StringLiteral, StringLiteralFlags, }; use ruff_text_size::TextRange; @@ -103,14 +103,14 @@ mod print_arguments { /// `FStringLiteralElement`. /// - if the expression is an f-string, the elements will be returned as-is. /// - otherwise, the expression will be wrapped in a `FStringExpressionElement`. - fn expr_to_fstring_elements(expr: &Expr) -> Vec { + fn expr_to_fstring_elements(expr: &Expr) -> Vec { match expr { // If the expression is a string literal, convert each part to a `FStringLiteralElement`. Expr::StringLiteral(string) => string .value .iter() .map(|part| { - FStringElement::Literal(FStringLiteralElement { + InterpolatedStringElement::Literal(InterpolatedStringLiteralElement { value: part.value.clone(), range: TextRange::default(), }) @@ -122,13 +122,15 @@ mod print_arguments { // Otherwise, return the expression as a single `FStringExpressionElement` wrapping // the expression. - expr => vec![FStringElement::Expression(FStringExpressionElement { - expression: Box::new(expr.clone()), - debug_text: None, - conversion: ConversionFlag::None, - format_spec: None, - range: TextRange::default(), - })], + expr => vec![InterpolatedStringElement::Interpolation( + InterpolatedElement { + expression: Box::new(expr.clone()), + debug_text: None, + conversion: ConversionFlag::None, + format_spec: None, + range: TextRange::default(), + }, + )], } } @@ -140,11 +142,11 @@ mod print_arguments { /// checking if the `sep` and `args` arguments to `print` are all string /// literals. fn fstring_elements_to_string_literals<'a>( - mut elements: impl ExactSizeIterator, + mut elements: impl ExactSizeIterator, flags: StringLiteralFlags, ) -> Option> { elements.try_fold(Vec::with_capacity(elements.len()), |mut acc, element| { - if let FStringElement::Literal(literal) = element { + if let InterpolatedStringElement::Literal(literal) = element { acc.push(StringLiteral { value: literal.value.clone(), flags, @@ -162,8 +164,8 @@ mod print_arguments { /// This function will return [`None`] if any of the arguments are not string literals, /// or if there are no arguments at all. fn args_to_string_literal_expr<'a>( - args: impl ExactSizeIterator>, - sep: impl ExactSizeIterator, + args: impl ExactSizeIterator>, + sep: impl ExactSizeIterator, flags: StringLiteralFlags, ) -> Option { // If there are no arguments, short-circuit and return `None` @@ -220,8 +222,8 @@ mod print_arguments { /// Also note that the iterator arguments of this function are consumed, /// as opposed to the references taken by [`args_to_string_literal_expr`]. fn args_to_fstring_expr( - mut args: impl ExactSizeIterator>, - sep: impl ExactSizeIterator, + mut args: impl ExactSizeIterator>, + sep: impl ExactSizeIterator, flags: FStringFlags, ) -> Option { // If there are no arguments, short-circuit and return `None` @@ -236,7 +238,7 @@ mod print_arguments { Some(Expr::FString(ExprFString { value: FStringValue::single(FString { - elements: FStringElements::from(fstring_elements), + elements: InterpolatedStringElements::from(fstring_elements), flags, range: TextRange::default(), }), @@ -273,10 +275,12 @@ mod print_arguments { ) .map(expr_to_fstring_elements) .unwrap_or_else(|| { - vec![FStringElement::Literal(FStringLiteralElement { - range: TextRange::default(), - value: " ".into(), - })] + vec![InterpolatedStringElement::Literal( + InterpolatedStringLiteralElement { + range: TextRange::default(), + value: " ".into(), + }, + )] }); let args = arguments diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs index 7318e8e683b6e4..fa88d0b0836b02 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs @@ -54,11 +54,11 @@ impl AlwaysFixableViolation for ExplicitFStringTypeConversion { /// RUF010 pub(crate) fn explicit_f_string_type_conversion(checker: &Checker, f_string: &ast::FString) { for (index, element) in f_string.elements.iter().enumerate() { - let Some(ast::FStringExpressionElement { + let Some(ast::InterpolatedElement { expression, conversion, .. - }) = element.as_expression() + }) = element.as_interpolation() else { continue; }; diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs index f5152cf1af74a4..f691b3b9af904f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs @@ -303,10 +303,11 @@ const fn is_valid_enclosing_node(node: AnyNodeRef) -> bool { | AnyNodeRef::ExprYieldFrom(_) | AnyNodeRef::ExprCompare(_) | AnyNodeRef::ExprCall(_) - | AnyNodeRef::FStringExpressionElement(_) - | AnyNodeRef::FStringLiteralElement(_) - | AnyNodeRef::FStringFormatSpec(_) + | AnyNodeRef::InterpolatedElement(_) + | AnyNodeRef::InterpolatedStringLiteralElement(_) + | AnyNodeRef::InterpolatedStringFormatSpec(_) | AnyNodeRef::ExprFString(_) + | AnyNodeRef::ExprTString(_) | AnyNodeRef::ExprStringLiteral(_) | AnyNodeRef::ExprBytesLiteral(_) | AnyNodeRef::ExprNumberLiteral(_) @@ -344,6 +345,7 @@ const fn is_valid_enclosing_node(node: AnyNodeRef) -> bool { | AnyNodeRef::TypeParamTypeVarTuple(_) | AnyNodeRef::TypeParamParamSpec(_) | AnyNodeRef::FString(_) + | AnyNodeRef::TString(_) | AnyNodeRef::StringLiteral(_) | AnyNodeRef::BytesLiteral(_) | AnyNodeRef::Identifier(_) => false, diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 359b8472f806f8..e13cb03dd54b80 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -214,7 +214,7 @@ fn should_be_fstring( for f_string in value.f_strings() { let mut has_name = false; - for element in f_string.elements.expressions() { + for element in f_string.elements.interpolations() { if let ast::Expr::Name(ast::ExprName { id, .. }) = element.expression.as_ref() { if arg_names.contains(id) { return false; diff --git a/crates/ruff_python_ast/ast.toml b/crates/ruff_python_ast/ast.toml index 8d2b9e067d4d01..38a9415515d256 100644 --- a/crates/ruff_python_ast/ast.toml +++ b/crates/ruff_python_ast/ast.toml @@ -433,6 +433,18 @@ See also [JoinedStr](https://docs.python.org/3/library/ast.html#ast.JoinedStr)"" fields = [{ name = "value", type = "FStringValue" }] custom_source_order = true +[Expr.nodes.ExprTString] +doc = """An AST node that represents either a single-part t-string literal +or an implicitly concatenated t-string literal. + +This type differs from the original Python AST `TemplateStr` in that it +doesn't join the implicitly concatenated parts into a single string. Instead, +it keeps them separate and provide various methods to access the parts. + +See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateStr)""" +fields = [{ name = "value", type = "TStringValue" }] +custom_source_order = true + [Expr.nodes.ExprStringLiteral] doc = """An AST node that represents either a single-part string literal or an implicitly concatenated string literal.""" @@ -539,9 +551,10 @@ doc = "See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.e [ExceptHandler.nodes] ExceptHandlerExceptHandler = {} -[FStringElement.nodes] -FStringExpressionElement = { variant = "Expression" } -FStringLiteralElement = { variant = "Literal" } +[InterpolatedStringElement.nodes] +InterpolatedElement = { variant = "Interpolation" } +InterpolatedStringLiteralElement = { variant = "Literal" } + [Pattern] doc = "See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern)" @@ -565,7 +578,7 @@ TypeParamTypeVarTuple = {} TypeParamParamSpec = {} [ungrouped.nodes] -FStringFormatSpec = {} +InterpolatedStringFormatSpec = {} PatternArguments = {} PatternKeyword = {} Comprehension = {} @@ -581,6 +594,7 @@ Decorator = {} ElifElseClause = {} TypeParams = {} FString = {} +TString = {} StringLiteral = {} BytesLiteral = {} Identifier = {} diff --git a/crates/ruff_python_ast/generate.py b/crates/ruff_python_ast/generate.py index f6afab68cc1747..981e23fa095633 100644 --- a/crates/ruff_python_ast/generate.py +++ b/crates/ruff_python_ast/generate.py @@ -15,7 +15,7 @@ import tomllib # Types that require `crate::`. We can slowly remove these types as we move them to generate scripts. -types_requiring_create_prefix = { +types_requiring_crate_prefix = { "IpyEscapeKind", "ExprContext", "Identifier", @@ -23,6 +23,7 @@ "BytesLiteralValue", "StringLiteralValue", "FStringValue", + "TStringValue", "Arguments", "CmpOp", "Comprehension", @@ -762,7 +763,7 @@ def write_node(out: list[str], ast: Ast) -> None: ty = field.parsed_ty rust_ty = f"{field.parsed_ty.name}" - if ty.name in types_requiring_create_prefix: + if ty.name in types_requiring_crate_prefix: rust_ty = f"crate::{rust_ty}" if ty.slice_: rust_ty = f"[{rust_ty}]" diff --git a/crates/ruff_python_ast/src/comparable.rs b/crates/ruff_python_ast/src/comparable.rs index 5197bfd38ac7bb..a759ef208af2ea 100644 --- a/crates/ruff_python_ast/src/comparable.rs +++ b/crates/ruff_python_ast/src/comparable.rs @@ -512,48 +512,57 @@ impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> { } #[derive(Debug, PartialEq, Eq, Hash)] -pub enum ComparableFStringElement<'a> { +pub enum ComparableInterpolatedStringElement<'a> { Literal(Cow<'a, str>), - FStringExpressionElement(FStringExpressionElement<'a>), + InterpolatedElement(InterpolatedElement<'a>), } #[derive(Debug, PartialEq, Eq, Hash)] -pub struct FStringExpressionElement<'a> { +pub struct InterpolatedElement<'a> { expression: ComparableExpr<'a>, debug_text: Option<&'a ast::DebugText>, conversion: ast::ConversionFlag, - format_spec: Option>>, + format_spec: Option>>, } -impl<'a> From<&'a ast::FStringElement> for ComparableFStringElement<'a> { - fn from(fstring_element: &'a ast::FStringElement) -> Self { - match fstring_element { - ast::FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => { - Self::Literal(value.as_ref().into()) +impl<'a> From<&'a ast::InterpolatedStringElement> for ComparableInterpolatedStringElement<'a> { + fn from(interpolated_string_element: &'a ast::InterpolatedStringElement) -> Self { + match interpolated_string_element { + ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { + value, + .. + }) => Self::Literal(value.as_ref().into()), + ast::InterpolatedStringElement::Interpolation(formatted_value) => { + formatted_value.into() } - ast::FStringElement::Expression(formatted_value) => formatted_value.into(), } } } -impl<'a> From<&'a ast::FStringExpressionElement> for ComparableFStringElement<'a> { - fn from(fstring_expression_element: &'a ast::FStringExpressionElement) -> Self { - let ast::FStringExpressionElement { +impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> { + fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self { + let ast::InterpolatedElement { expression, debug_text, conversion, format_spec, range: _, - } = fstring_expression_element; + } = interpolated_element; - Self::FStringExpressionElement(FStringExpressionElement { + Self { expression: (expression).into(), debug_text: debug_text.as_ref(), conversion: *conversion, format_spec: format_spec .as_ref() .map(|spec| spec.elements.iter().map(Into::into).collect()), - }) + } + } +} + +impl<'a> From<&'a ast::InterpolatedElement> for ComparableInterpolatedStringElement<'a> { + fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self { + Self::InterpolatedElement(interpolated_element.into()) } } @@ -610,7 +619,7 @@ impl<'a> From> for ComparableLiteral<'a> { #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableFString<'a> { - elements: Box<[ComparableFStringElement<'a>]>, + elements: Box<[ComparableInterpolatedStringElement<'a>]>, } impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { @@ -637,7 +646,7 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { fn from(value: &'a ast::FStringValue) -> Self { #[derive(Default)] struct Collector<'a> { - elements: Vec>, + elements: Vec>, } impl<'a> Collector<'a> { @@ -647,17 +656,17 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { // `elements` vector, while subsequent strings // are concatenated onto this top string. fn push_literal(&mut self, literal: &'a str) { - if let Some(ComparableFStringElement::Literal(existing_literal)) = + if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) = self.elements.last_mut() { existing_literal.to_mut().push_str(literal); } else { self.elements - .push(ComparableFStringElement::Literal(literal.into())); + .push(ComparableInterpolatedStringElement::Literal(literal.into())); } } - fn push_expression(&mut self, expression: &'a ast::FStringExpressionElement) { + fn push_expression(&mut self, expression: &'a ast::InterpolatedElement) { self.elements.push(expression.into()); } } @@ -672,10 +681,10 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { ast::FStringPart::FString(fstring) => { for element in &fstring.elements { match element { - ast::FStringElement::Literal(literal) => { + ast::InterpolatedStringElement::Literal(literal) => { collector.push_literal(&literal.value); } - ast::FStringElement::Expression(expression) => { + ast::InterpolatedStringElement::Interpolation(expression) => { collector.push_expression(expression); } } @@ -690,6 +699,133 @@ impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { } } +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ComparableTString<'a> { + strings: Box<[ComparableInterpolatedStringElement<'a>]>, + interpolations: Box<[InterpolatedElement<'a>]>, +} + +impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> { + // The approach taken below necessarily deviates from the + // corresponding implementation for [`ast::FStringValue`]. + // The reason is that a t-string value is composed of _three_ + // non-comparable parts: literals, f-string expressions, and + // t-string interpolations. Since we have merged the AST nodes + // that capture f-string expressions and t-string interpolations + // into the shared [`ast::InterpolatedElement`], we must + // be careful to distinguish between them here. + // + // Consequently, we model a [`ComparableTString`] on the actual + // [CPython implementation] of a `string.templatelib.Template` object: + // it is composed of `strings` and `interpolations`. In CPython, + // the `strings` field is a tuple of honest strings (since f-strings + // are evaluated). Our `strings` field will house both f-string + // expressions and string literals. + // + // Finally, as in CPython, we must be careful to ensure that the length + // of `strings` is always one more than the length of `interpolations` - + // that way we can recover the original reading order by interleaving + // starting with `strings`. This is how we can tell the + // difference between, e.g. `t"{foo}bar"` and `t"bar{foo}"`. + // + // - [CPython implementation](https://github.com/python/cpython/blob/c91ad5da9d92eac4718e4da8d53689c3cc24535e/Python/codegen.c#L4052-L4103) + fn from(value: &'a ast::TStringValue) -> Self { + struct Collector<'a> { + strings: Vec>, + interpolations: Vec>, + } + + impl Default for Collector<'_> { + fn default() -> Self { + Self { + strings: vec![ComparableInterpolatedStringElement::Literal("".into())], + interpolations: vec![], + } + } + } + + impl<'a> Collector<'a> { + // The logic for concatenating adjacent string literals + // occurs here, implicitly: when we encounter a sequence + // of string literals, the first gets pushed to the + // `strings` vector, while subsequent strings + // are concatenated onto this top string. + fn push_literal(&mut self, literal: &'a str) { + if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) = + self.strings.last_mut() + { + existing_literal.to_mut().push_str(literal); + } else { + self.strings + .push(ComparableInterpolatedStringElement::Literal(literal.into())); + } + } + + fn start_new_literal(&mut self) { + self.strings + .push(ComparableInterpolatedStringElement::Literal("".into())); + } + + fn push_fstring_expression(&mut self, expression: &'a ast::InterpolatedElement) { + if let Some(ComparableInterpolatedStringElement::Literal(last_literal)) = + self.strings.last() + { + // Recall that we insert empty strings after + // each interpolation. If we encounter an f-string + // expression, we replace the empty string with it. + if last_literal.is_empty() { + self.strings.pop(); + } + } + self.strings.push(expression.into()); + } + fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) { + self.interpolations.push(expression.into()); + self.start_new_literal(); + } + } + + let mut collector = Collector::default(); + + for part in value { + match part { + ast::TStringPart::Literal(string_literal) => { + collector.push_literal(&string_literal.value); + } + ast::TStringPart::TString(fstring) => { + for element in &fstring.elements { + match element { + ast::InterpolatedStringElement::Literal(literal) => { + collector.push_literal(&literal.value); + } + ast::InterpolatedStringElement::Interpolation(interpolation) => { + collector.push_tstring_interpolation(interpolation); + } + } + } + } + ast::TStringPart::FString(fstring) => { + for element in &fstring.elements { + match element { + ast::InterpolatedStringElement::Literal(literal) => { + collector.push_literal(&literal.value); + } + ast::InterpolatedStringElement::Interpolation(expression) => { + collector.push_fstring_expression(expression); + } + } + } + } + } + } + + Self { + strings: collector.strings.into_boxed_slice(), + interpolations: collector.interpolations.into_boxed_slice(), + } + } +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableStringLiteral<'a> { value: &'a str, @@ -833,11 +969,11 @@ pub struct ExprCall<'a> { } #[derive(Debug, PartialEq, Eq, Hash)] -pub struct ExprFStringExpressionElement<'a> { +pub struct ExprInterpolatedElement<'a> { value: Box>, debug_text: Option<&'a ast::DebugText>, conversion: ast::ConversionFlag, - format_spec: Vec>, + format_spec: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] @@ -845,6 +981,11 @@ pub struct ExprFString<'a> { value: ComparableFString<'a>, } +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ExprTString<'a> { + value: ComparableTString<'a>, +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprStringLiteral<'a> { value: ComparableStringLiteral<'a>, @@ -929,8 +1070,10 @@ pub enum ComparableExpr<'a> { YieldFrom(ExprYieldFrom<'a>), Compare(ExprCompare<'a>), Call(ExprCall<'a>), - FStringExpressionElement(ExprFStringExpressionElement<'a>), + FStringExpressionElement(ExprInterpolatedElement<'a>), FString(ExprFString<'a>), + TStringInterpolationElement(ExprInterpolatedElement<'a>), + TString(ExprTString<'a>), StringLiteral(ExprStringLiteral<'a>), BytesLiteral(ExprBytesLiteral<'a>), NumberLiteral(ExprNumberLiteral<'a>), @@ -1089,6 +1232,11 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { value: value.into(), }) } + ast::Expr::TString(ast::ExprTString { value, range: _ }) => { + Self::TString(ExprTString { + value: value.into(), + }) + } ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) => { Self::StringLiteral(ExprStringLiteral { value: ComparableStringLiteral { diff --git a/crates/ruff_python_ast/src/expression.rs b/crates/ruff_python_ast/src/expression.rs index 48a0342971e5b4..14fe4e11701037 100644 --- a/crates/ruff_python_ast/src/expression.rs +++ b/crates/ruff_python_ast/src/expression.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::{ self as ast, AnyNodeRef, AnyStringFlags, Expr, ExprBytesLiteral, ExprFString, ExprRef, - ExprStringLiteral, StringFlags, + ExprStringLiteral, ExprTString, StringFlags, }; impl<'a> From<&'a Box> for ExprRef<'a> { @@ -80,17 +80,18 @@ impl LiteralExpressionRef<'_> { } /// An enum that holds a reference to a string-like expression from the AST. This includes string -/// literals, bytes literals, and f-strings. +/// literals, bytes literals, f-strings, and t-strings. #[derive(Copy, Clone, Debug, PartialEq)] pub enum StringLike<'a> { String(&'a ast::ExprStringLiteral), Bytes(&'a ast::ExprBytesLiteral), FString(&'a ast::ExprFString), + TString(&'a ast::ExprTString), } impl<'a> StringLike<'a> { - pub const fn is_fstring(self) -> bool { - matches!(self, Self::FString(_)) + pub const fn is_interpolated_string(self) -> bool { + matches!(self, Self::TString(_) | Self::FString(_)) } /// Returns an iterator over the [`StringLikePart`] contained in this string-like expression. @@ -99,6 +100,7 @@ impl<'a> StringLike<'a> { StringLike::String(expr) => StringLikePartIter::String(expr.value.iter()), StringLike::Bytes(expr) => StringLikePartIter::Bytes(expr.value.iter()), StringLike::FString(expr) => StringLikePartIter::FString(expr.value.iter()), + StringLike::TString(expr) => StringLikePartIter::TString(expr.value.iter()), } } @@ -108,6 +110,7 @@ impl<'a> StringLike<'a> { Self::String(ExprStringLiteral { value, .. }) => value.is_implicit_concatenated(), Self::Bytes(ExprBytesLiteral { value, .. }) => value.is_implicit_concatenated(), Self::FString(ExprFString { value, .. }) => value.is_implicit_concatenated(), + Self::TString(ExprTString { value, .. }) => value.is_implicit_concatenated(), } } @@ -116,6 +119,7 @@ impl<'a> StringLike<'a> { StringLike::String(expr) => ExprRef::StringLiteral(expr), StringLike::Bytes(expr) => ExprRef::BytesLiteral(expr), StringLike::FString(expr) => ExprRef::FString(expr), + StringLike::TString(expr) => ExprRef::TString(expr), } } } @@ -138,12 +142,19 @@ impl<'a> From<&'a ast::ExprFString> for StringLike<'a> { } } +impl<'a> From<&'a ast::ExprTString> for StringLike<'a> { + fn from(value: &'a ast::ExprTString) -> Self { + StringLike::TString(value) + } +} + impl<'a> From<&StringLike<'a>> for ExprRef<'a> { fn from(value: &StringLike<'a>) -> Self { match value { StringLike::String(expr) => ExprRef::StringLiteral(expr), StringLike::Bytes(expr) => ExprRef::BytesLiteral(expr), StringLike::FString(expr) => ExprRef::FString(expr), + StringLike::TString(expr) => ExprRef::TString(expr), } } } @@ -160,6 +171,7 @@ impl<'a> From<&StringLike<'a>> for AnyNodeRef<'a> { StringLike::String(expr) => AnyNodeRef::ExprStringLiteral(expr), StringLike::Bytes(expr) => AnyNodeRef::ExprBytesLiteral(expr), StringLike::FString(expr) => AnyNodeRef::ExprFString(expr), + StringLike::TString(expr) => AnyNodeRef::ExprTString(expr), } } } @@ -172,6 +184,7 @@ impl<'a> TryFrom<&'a Expr> for StringLike<'a> { Expr::StringLiteral(value) => Ok(Self::String(value)), Expr::BytesLiteral(value) => Ok(Self::Bytes(value)), Expr::FString(value) => Ok(Self::FString(value)), + Expr::TString(value) => Ok(Self::TString(value)), _ => Err(()), } } @@ -185,6 +198,7 @@ impl<'a> TryFrom> for StringLike<'a> { AnyNodeRef::ExprStringLiteral(value) => Ok(Self::String(value)), AnyNodeRef::ExprBytesLiteral(value) => Ok(Self::Bytes(value)), AnyNodeRef::ExprFString(value) => Ok(Self::FString(value)), + AnyNodeRef::ExprTString(value) => Ok(Self::TString(value)), _ => Err(()), } } @@ -196,6 +210,7 @@ impl Ranged for StringLike<'_> { StringLike::String(literal) => literal.range(), StringLike::Bytes(literal) => literal.range(), StringLike::FString(literal) => literal.range(), + StringLike::TString(literal) => literal.range(), } } } @@ -206,6 +221,7 @@ pub enum StringLikePart<'a> { String(&'a ast::StringLiteral), Bytes(&'a ast::BytesLiteral), FString(&'a ast::FString), + TString(&'a ast::TString), } impl<'a> StringLikePart<'a> { @@ -215,6 +231,7 @@ impl<'a> StringLikePart<'a> { StringLikePart::String(string) => AnyStringFlags::from(string.flags), StringLikePart::Bytes(bytes) => AnyStringFlags::from(bytes.flags), StringLikePart::FString(f_string) => AnyStringFlags::from(f_string.flags), + StringLikePart::TString(t_string) => AnyStringFlags::from(t_string.flags), } } @@ -238,8 +255,8 @@ impl<'a> StringLikePart<'a> { } } - pub const fn is_fstring(self) -> bool { - matches!(self, Self::FString(_)) + pub const fn is_interpolated_string(self) -> bool { + matches!(self, Self::FString(_) | Self::TString(_)) } } @@ -261,6 +278,12 @@ impl<'a> From<&'a ast::FString> for StringLikePart<'a> { } } +impl<'a> From<&'a ast::TString> for StringLikePart<'a> { + fn from(value: &'a ast::TString) -> Self { + StringLikePart::TString(value) + } +} + impl<'a> From<&StringLikePart<'a>> for AnyNodeRef<'a> { fn from(value: &StringLikePart<'a>) -> Self { AnyNodeRef::from(*value) @@ -273,6 +296,7 @@ impl<'a> From> for AnyNodeRef<'a> { StringLikePart::String(part) => AnyNodeRef::StringLiteral(part), StringLikePart::Bytes(part) => AnyNodeRef::BytesLiteral(part), StringLikePart::FString(part) => AnyNodeRef::FString(part), + StringLikePart::TString(part) => AnyNodeRef::TString(part), } } } @@ -283,6 +307,7 @@ impl Ranged for StringLikePart<'_> { StringLikePart::String(part) => part.range(), StringLikePart::Bytes(part) => part.range(), StringLikePart::FString(part) => part.range(), + StringLikePart::TString(part) => part.range(), } } } @@ -295,6 +320,7 @@ pub enum StringLikePartIter<'a> { String(std::slice::Iter<'a, ast::StringLiteral>), Bytes(std::slice::Iter<'a, ast::BytesLiteral>), FString(std::slice::Iter<'a, ast::FStringPart>), + TString(std::slice::Iter<'a, ast::TStringPart>), } impl<'a> Iterator for StringLikePartIter<'a> { @@ -313,6 +339,16 @@ impl<'a> Iterator for StringLikePartIter<'a> { ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string), } } + StringLikePartIter::TString(inner) => { + let part = inner.next()?; + match part { + ast::TStringPart::Literal(string_literal) => { + StringLikePart::String(string_literal) + } + ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string), + ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string), + } + } }; Some(part) @@ -323,6 +359,7 @@ impl<'a> Iterator for StringLikePartIter<'a> { StringLikePartIter::String(inner) => inner.size_hint(), StringLikePartIter::Bytes(inner) => inner.size_hint(), StringLikePartIter::FString(inner) => inner.size_hint(), + StringLikePartIter::TString(inner) => inner.size_hint(), } } } @@ -341,6 +378,16 @@ impl DoubleEndedIterator for StringLikePartIter<'_> { ast::FStringPart::FString(f_string) => StringLikePart::FString(f_string), } } + StringLikePartIter::TString(inner) => { + let part = inner.next_back()?; + match part { + ast::TStringPart::Literal(string_literal) => { + StringLikePart::String(string_literal) + } + ast::TStringPart::TString(t_string) => StringLikePart::TString(t_string), + ast::TStringPart::FString(f_string) => StringLikePart::FString(f_string), + } + } }; Some(part) diff --git a/crates/ruff_python_ast/src/generated.rs b/crates/ruff_python_ast/src/generated.rs index 166878d973d321..7fd7db50724a62 100644 --- a/crates/ruff_python_ast/src/generated.rs +++ b/crates/ruff_python_ast/src/generated.rs @@ -1270,6 +1270,7 @@ pub enum Expr { Compare(crate::ExprCompare), Call(crate::ExprCall), FString(crate::ExprFString), + TString(crate::ExprTString), StringLiteral(crate::ExprStringLiteral), BytesLiteral(crate::ExprBytesLiteral), NumberLiteral(crate::ExprNumberLiteral), @@ -1394,6 +1395,12 @@ impl From for Expr { } } +impl From for Expr { + fn from(node: crate::ExprTString) -> Self { + Self::TString(node) + } +} + impl From for Expr { fn from(node: crate::ExprStringLiteral) -> Self { Self::StringLiteral(node) @@ -1499,6 +1506,7 @@ impl ruff_text_size::Ranged for Expr { Self::Compare(node) => node.range(), Self::Call(node) => node.range(), Self::FString(node) => node.range(), + Self::TString(node) => node.range(), Self::StringLiteral(node) => node.range(), Self::BytesLiteral(node) => node.range(), Self::NumberLiteral(node) => node.range(), @@ -2185,6 +2193,43 @@ impl Expr { } } + #[inline] + pub const fn is_t_string_expr(&self) -> bool { + matches!(self, Self::TString(_)) + } + + #[inline] + pub fn t_string_expr(self) -> Option { + match self { + Self::TString(val) => Some(val), + _ => None, + } + } + + #[inline] + pub fn expect_t_string_expr(self) -> crate::ExprTString { + match self { + Self::TString(val) => val, + _ => panic!("called expect on {self:?}"), + } + } + + #[inline] + pub fn as_t_string_expr_mut(&mut self) -> Option<&mut crate::ExprTString> { + match self { + Self::TString(val) => Some(val), + _ => None, + } + } + + #[inline] + pub fn as_t_string_expr(&self) -> Option<&crate::ExprTString> { + match self { + Self::TString(val) => Some(val), + _ => None, + } + } + #[inline] pub const fn is_string_literal_expr(&self) -> bool { matches!(self, Self::StringLiteral(_)) @@ -2761,67 +2806,67 @@ impl ExceptHandler { } #[derive(Clone, Debug, PartialEq)] -pub enum FStringElement { - Expression(crate::FStringExpressionElement), - Literal(crate::FStringLiteralElement), +pub enum InterpolatedStringElement { + Interpolation(crate::InterpolatedElement), + Literal(crate::InterpolatedStringLiteralElement), } -impl From for FStringElement { - fn from(node: crate::FStringExpressionElement) -> Self { - Self::Expression(node) +impl From for InterpolatedStringElement { + fn from(node: crate::InterpolatedElement) -> Self { + Self::Interpolation(node) } } -impl From for FStringElement { - fn from(node: crate::FStringLiteralElement) -> Self { +impl From for InterpolatedStringElement { + fn from(node: crate::InterpolatedStringLiteralElement) -> Self { Self::Literal(node) } } -impl ruff_text_size::Ranged for FStringElement { +impl ruff_text_size::Ranged for InterpolatedStringElement { fn range(&self) -> ruff_text_size::TextRange { match self { - Self::Expression(node) => node.range(), + Self::Interpolation(node) => node.range(), Self::Literal(node) => node.range(), } } } #[allow(dead_code, clippy::match_wildcard_for_single_variants)] -impl FStringElement { +impl InterpolatedStringElement { #[inline] - pub const fn is_expression(&self) -> bool { - matches!(self, Self::Expression(_)) + pub const fn is_interpolation(&self) -> bool { + matches!(self, Self::Interpolation(_)) } #[inline] - pub fn expression(self) -> Option { + pub fn interpolation(self) -> Option { match self { - Self::Expression(val) => Some(val), + Self::Interpolation(val) => Some(val), _ => None, } } #[inline] - pub fn expect_expression(self) -> crate::FStringExpressionElement { + pub fn expect_interpolation(self) -> crate::InterpolatedElement { match self { - Self::Expression(val) => val, + Self::Interpolation(val) => val, _ => panic!("called expect on {self:?}"), } } #[inline] - pub fn as_expression_mut(&mut self) -> Option<&mut crate::FStringExpressionElement> { + pub fn as_interpolation_mut(&mut self) -> Option<&mut crate::InterpolatedElement> { match self { - Self::Expression(val) => Some(val), + Self::Interpolation(val) => Some(val), _ => None, } } #[inline] - pub fn as_expression(&self) -> Option<&crate::FStringExpressionElement> { + pub fn as_interpolation(&self) -> Option<&crate::InterpolatedElement> { match self { - Self::Expression(val) => Some(val), + Self::Interpolation(val) => Some(val), _ => None, } } @@ -2832,7 +2877,7 @@ impl FStringElement { } #[inline] - pub fn literal(self) -> Option { + pub fn literal(self) -> Option { match self { Self::Literal(val) => Some(val), _ => None, @@ -2840,7 +2885,7 @@ impl FStringElement { } #[inline] - pub fn expect_literal(self) -> crate::FStringLiteralElement { + pub fn expect_literal(self) -> crate::InterpolatedStringLiteralElement { match self { Self::Literal(val) => val, _ => panic!("called expect on {self:?}"), @@ -2848,7 +2893,7 @@ impl FStringElement { } #[inline] - pub fn as_literal_mut(&mut self) -> Option<&mut crate::FStringLiteralElement> { + pub fn as_literal_mut(&mut self) -> Option<&mut crate::InterpolatedStringLiteralElement> { match self { Self::Literal(val) => Some(val), _ => None, @@ -2856,7 +2901,7 @@ impl FStringElement { } #[inline] - pub fn as_literal(&self) -> Option<&crate::FStringLiteralElement> { + pub fn as_literal(&self) -> Option<&crate::InterpolatedStringLiteralElement> { match self { Self::Literal(val) => Some(val), _ => None, @@ -3659,6 +3704,12 @@ impl ruff_text_size::Ranged for crate::ExprFString { } } +impl ruff_text_size::Ranged for crate::ExprTString { + fn range(&self) -> ruff_text_size::TextRange { + self.range + } +} + impl ruff_text_size::Ranged for crate::ExprStringLiteral { fn range(&self) -> ruff_text_size::TextRange { self.range @@ -3749,13 +3800,13 @@ impl ruff_text_size::Ranged for crate::ExceptHandlerExceptHandler { } } -impl ruff_text_size::Ranged for crate::FStringExpressionElement { +impl ruff_text_size::Ranged for crate::InterpolatedElement { fn range(&self) -> ruff_text_size::TextRange { self.range } } -impl ruff_text_size::Ranged for crate::FStringLiteralElement { +impl ruff_text_size::Ranged for crate::InterpolatedStringLiteralElement { fn range(&self) -> ruff_text_size::TextRange { self.range } @@ -3827,7 +3878,7 @@ impl ruff_text_size::Ranged for crate::TypeParamParamSpec { } } -impl ruff_text_size::Ranged for crate::FStringFormatSpec { +impl ruff_text_size::Ranged for crate::InterpolatedStringFormatSpec { fn range(&self) -> ruff_text_size::TextRange { self.range } @@ -3923,6 +3974,12 @@ impl ruff_text_size::Ranged for crate::FString { } } +impl ruff_text_size::Ranged for crate::TString { + fn range(&self) -> ruff_text_size::TextRange { + self.range + } +} + impl ruff_text_size::Ranged for crate::StringLiteral { fn range(&self) -> ruff_text_size::TextRange { self.range @@ -4015,6 +4072,7 @@ impl Expr { Expr::Compare(node) => node.visit_source_order(visitor), Expr::Call(node) => node.visit_source_order(visitor), Expr::FString(node) => node.visit_source_order(visitor), + Expr::TString(node) => node.visit_source_order(visitor), Expr::StringLiteral(node) => node.visit_source_order(visitor), Expr::BytesLiteral(node) => node.visit_source_order(visitor), Expr::NumberLiteral(node) => node.visit_source_order(visitor), @@ -4045,15 +4103,15 @@ impl ExceptHandler { } } -impl FStringElement { +impl InterpolatedStringElement { #[allow(unused)] pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) where V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, { match self { - FStringElement::Expression(node) => node.visit_source_order(visitor), - FStringElement::Literal(node) => node.visit_source_order(visitor), + InterpolatedStringElement::Interpolation(node) => node.visit_source_order(visitor), + InterpolatedStringElement::Literal(node) => node.visit_source_order(visitor), } } } @@ -4436,6 +4494,8 @@ pub enum ExprRef<'a> { Call(&'a crate::ExprCall), #[is(name = "f_string_expr")] FString(&'a crate::ExprFString), + #[is(name = "t_string_expr")] + TString(&'a crate::ExprTString), #[is(name = "string_literal_expr")] StringLiteral(&'a crate::ExprStringLiteral), #[is(name = "bytes_literal_expr")] @@ -4487,6 +4547,7 @@ impl<'a> From<&'a Expr> for ExprRef<'a> { Expr::Compare(node) => ExprRef::Compare(node), Expr::Call(node) => ExprRef::Call(node), Expr::FString(node) => ExprRef::FString(node), + Expr::TString(node) => ExprRef::TString(node), Expr::StringLiteral(node) => ExprRef::StringLiteral(node), Expr::BytesLiteral(node) => ExprRef::BytesLiteral(node), Expr::NumberLiteral(node) => ExprRef::NumberLiteral(node), @@ -4613,6 +4674,12 @@ impl<'a> From<&'a crate::ExprFString> for ExprRef<'a> { } } +impl<'a> From<&'a crate::ExprTString> for ExprRef<'a> { + fn from(node: &'a crate::ExprTString) -> Self { + Self::TString(node) + } +} + impl<'a> From<&'a crate::ExprStringLiteral> for ExprRef<'a> { fn from(node: &'a crate::ExprStringLiteral) -> Self { Self::StringLiteral(node) @@ -4718,6 +4785,7 @@ impl ruff_text_size::Ranged for ExprRef<'_> { Self::Compare(node) => node.range(), Self::Call(node) => node.range(), Self::FString(node) => node.range(), + Self::TString(node) => node.range(), Self::StringLiteral(node) => node.range(), Self::BytesLiteral(node) => node.range(), Self::NumberLiteral(node) => node.range(), @@ -4765,36 +4833,38 @@ impl ruff_text_size::Ranged for ExceptHandlerRef<'_> { } #[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum FStringElementRef<'a> { - Expression(&'a crate::FStringExpressionElement), - Literal(&'a crate::FStringLiteralElement), +pub enum InterpolatedStringElementRef<'a> { + Interpolation(&'a crate::InterpolatedElement), + Literal(&'a crate::InterpolatedStringLiteralElement), } -impl<'a> From<&'a FStringElement> for FStringElementRef<'a> { - fn from(node: &'a FStringElement) -> Self { +impl<'a> From<&'a InterpolatedStringElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a InterpolatedStringElement) -> Self { match node { - FStringElement::Expression(node) => FStringElementRef::Expression(node), - FStringElement::Literal(node) => FStringElementRef::Literal(node), + InterpolatedStringElement::Interpolation(node) => { + InterpolatedStringElementRef::Interpolation(node) + } + InterpolatedStringElement::Literal(node) => InterpolatedStringElementRef::Literal(node), } } } -impl<'a> From<&'a crate::FStringExpressionElement> for FStringElementRef<'a> { - fn from(node: &'a crate::FStringExpressionElement) -> Self { - Self::Expression(node) +impl<'a> From<&'a crate::InterpolatedElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a crate::InterpolatedElement) -> Self { + Self::Interpolation(node) } } -impl<'a> From<&'a crate::FStringLiteralElement> for FStringElementRef<'a> { - fn from(node: &'a crate::FStringLiteralElement) -> Self { +impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a crate::InterpolatedStringLiteralElement) -> Self { Self::Literal(node) } } -impl ruff_text_size::Ranged for FStringElementRef<'_> { +impl ruff_text_size::Ranged for InterpolatedStringElementRef<'_> { fn range(&self) -> ruff_text_size::TextRange { match self { - Self::Expression(node) => node.range(), + Self::Interpolation(node) => node.range(), Self::Literal(node) => node.range(), } } @@ -4984,6 +5054,7 @@ pub enum AnyNodeRef<'a> { ExprCompare(&'a crate::ExprCompare), ExprCall(&'a crate::ExprCall), ExprFString(&'a crate::ExprFString), + ExprTString(&'a crate::ExprTString), ExprStringLiteral(&'a crate::ExprStringLiteral), ExprBytesLiteral(&'a crate::ExprBytesLiteral), ExprNumberLiteral(&'a crate::ExprNumberLiteral), @@ -4999,8 +5070,8 @@ pub enum AnyNodeRef<'a> { ExprSlice(&'a crate::ExprSlice), ExprIpyEscapeCommand(&'a crate::ExprIpyEscapeCommand), ExceptHandlerExceptHandler(&'a crate::ExceptHandlerExceptHandler), - FStringExpressionElement(&'a crate::FStringExpressionElement), - FStringLiteralElement(&'a crate::FStringLiteralElement), + InterpolatedElement(&'a crate::InterpolatedElement), + InterpolatedStringLiteralElement(&'a crate::InterpolatedStringLiteralElement), PatternMatchValue(&'a crate::PatternMatchValue), PatternMatchSingleton(&'a crate::PatternMatchSingleton), PatternMatchSequence(&'a crate::PatternMatchSequence), @@ -5012,7 +5083,7 @@ pub enum AnyNodeRef<'a> { TypeParamTypeVar(&'a crate::TypeParamTypeVar), TypeParamTypeVarTuple(&'a crate::TypeParamTypeVarTuple), TypeParamParamSpec(&'a crate::TypeParamParamSpec), - FStringFormatSpec(&'a crate::FStringFormatSpec), + InterpolatedStringFormatSpec(&'a crate::InterpolatedStringFormatSpec), PatternArguments(&'a crate::PatternArguments), PatternKeyword(&'a crate::PatternKeyword), Comprehension(&'a crate::Comprehension), @@ -5028,6 +5099,7 @@ pub enum AnyNodeRef<'a> { ElifElseClause(&'a crate::ElifElseClause), TypeParams(&'a crate::TypeParams), FString(&'a crate::FString), + TString(&'a crate::TString), StringLiteral(&'a crate::StringLiteral), BytesLiteral(&'a crate::BytesLiteral), Identifier(&'a crate::Identifier), @@ -5181,6 +5253,7 @@ impl<'a> From<&'a Expr> for AnyNodeRef<'a> { Expr::Compare(node) => AnyNodeRef::ExprCompare(node), Expr::Call(node) => AnyNodeRef::ExprCall(node), Expr::FString(node) => AnyNodeRef::ExprFString(node), + Expr::TString(node) => AnyNodeRef::ExprTString(node), Expr::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), Expr::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), Expr::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), @@ -5220,6 +5293,7 @@ impl<'a> From> for AnyNodeRef<'a> { ExprRef::Compare(node) => AnyNodeRef::ExprCompare(node), ExprRef::Call(node) => AnyNodeRef::ExprCall(node), ExprRef::FString(node) => AnyNodeRef::ExprFString(node), + ExprRef::TString(node) => AnyNodeRef::ExprTString(node), ExprRef::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), ExprRef::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), ExprRef::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), @@ -5259,6 +5333,7 @@ impl<'a> AnyNodeRef<'a> { Self::ExprCompare(node) => Some(ExprRef::Compare(node)), Self::ExprCall(node) => Some(ExprRef::Call(node)), Self::ExprFString(node) => Some(ExprRef::FString(node)), + Self::ExprTString(node) => Some(ExprRef::TString(node)), Self::ExprStringLiteral(node) => Some(ExprRef::StringLiteral(node)), Self::ExprBytesLiteral(node) => Some(ExprRef::BytesLiteral(node)), Self::ExprNumberLiteral(node) => Some(ExprRef::NumberLiteral(node)), @@ -5305,29 +5380,39 @@ impl<'a> AnyNodeRef<'a> { } } -impl<'a> From<&'a FStringElement> for AnyNodeRef<'a> { - fn from(node: &'a FStringElement) -> AnyNodeRef<'a> { +impl<'a> From<&'a InterpolatedStringElement> for AnyNodeRef<'a> { + fn from(node: &'a InterpolatedStringElement) -> AnyNodeRef<'a> { match node { - FStringElement::Expression(node) => AnyNodeRef::FStringExpressionElement(node), - FStringElement::Literal(node) => AnyNodeRef::FStringLiteralElement(node), + InterpolatedStringElement::Interpolation(node) => AnyNodeRef::InterpolatedElement(node), + InterpolatedStringElement::Literal(node) => { + AnyNodeRef::InterpolatedStringLiteralElement(node) + } } } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: FStringElementRef<'a>) -> AnyNodeRef<'a> { +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: InterpolatedStringElementRef<'a>) -> AnyNodeRef<'a> { match node { - FStringElementRef::Expression(node) => AnyNodeRef::FStringExpressionElement(node), - FStringElementRef::Literal(node) => AnyNodeRef::FStringLiteralElement(node), + InterpolatedStringElementRef::Interpolation(node) => { + AnyNodeRef::InterpolatedElement(node) + } + InterpolatedStringElementRef::Literal(node) => { + AnyNodeRef::InterpolatedStringLiteralElement(node) + } } } } impl<'a> AnyNodeRef<'a> { - pub fn as_f_string_element_ref(self) -> Option> { + pub fn as_interpolated_string_element_ref(self) -> Option> { match self { - Self::FStringExpressionElement(node) => Some(FStringElementRef::Expression(node)), - Self::FStringLiteralElement(node) => Some(FStringElementRef::Literal(node)), + Self::InterpolatedElement(node) => { + Some(InterpolatedStringElementRef::Interpolation(node)) + } + Self::InterpolatedStringLiteralElement(node) => { + Some(InterpolatedStringElementRef::Literal(node)) + } _ => None, } @@ -5683,6 +5768,12 @@ impl<'a> From<&'a crate::ExprFString> for AnyNodeRef<'a> { } } +impl<'a> From<&'a crate::ExprTString> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprTString) -> AnyNodeRef<'a> { + AnyNodeRef::ExprTString(node) + } +} + impl<'a> From<&'a crate::ExprStringLiteral> for AnyNodeRef<'a> { fn from(node: &'a crate::ExprStringLiteral) -> AnyNodeRef<'a> { AnyNodeRef::ExprStringLiteral(node) @@ -5773,15 +5864,15 @@ impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for AnyNodeRef<'a> { } } -impl<'a> From<&'a crate::FStringExpressionElement> for AnyNodeRef<'a> { - fn from(node: &'a crate::FStringExpressionElement) -> AnyNodeRef<'a> { - AnyNodeRef::FStringExpressionElement(node) +impl<'a> From<&'a crate::InterpolatedElement> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedElement) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedElement(node) } } -impl<'a> From<&'a crate::FStringLiteralElement> for AnyNodeRef<'a> { - fn from(node: &'a crate::FStringLiteralElement) -> AnyNodeRef<'a> { - AnyNodeRef::FStringLiteralElement(node) +impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedStringLiteralElement) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedStringLiteralElement(node) } } @@ -5851,9 +5942,9 @@ impl<'a> From<&'a crate::TypeParamParamSpec> for AnyNodeRef<'a> { } } -impl<'a> From<&'a crate::FStringFormatSpec> for AnyNodeRef<'a> { - fn from(node: &'a crate::FStringFormatSpec) -> AnyNodeRef<'a> { - AnyNodeRef::FStringFormatSpec(node) +impl<'a> From<&'a crate::InterpolatedStringFormatSpec> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedStringFormatSpec) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedStringFormatSpec(node) } } @@ -5947,6 +6038,12 @@ impl<'a> From<&'a crate::FString> for AnyNodeRef<'a> { } } +impl<'a> From<&'a crate::TString> for AnyNodeRef<'a> { + fn from(node: &'a crate::TString) -> AnyNodeRef<'a> { + AnyNodeRef::TString(node) + } +} + impl<'a> From<&'a crate::StringLiteral> for AnyNodeRef<'a> { fn from(node: &'a crate::StringLiteral) -> AnyNodeRef<'a> { AnyNodeRef::StringLiteral(node) @@ -6013,6 +6110,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::ExprCompare(node) => node.range(), AnyNodeRef::ExprCall(node) => node.range(), AnyNodeRef::ExprFString(node) => node.range(), + AnyNodeRef::ExprTString(node) => node.range(), AnyNodeRef::ExprStringLiteral(node) => node.range(), AnyNodeRef::ExprBytesLiteral(node) => node.range(), AnyNodeRef::ExprNumberLiteral(node) => node.range(), @@ -6028,8 +6126,8 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::ExprSlice(node) => node.range(), AnyNodeRef::ExprIpyEscapeCommand(node) => node.range(), AnyNodeRef::ExceptHandlerExceptHandler(node) => node.range(), - AnyNodeRef::FStringExpressionElement(node) => node.range(), - AnyNodeRef::FStringLiteralElement(node) => node.range(), + AnyNodeRef::InterpolatedElement(node) => node.range(), + AnyNodeRef::InterpolatedStringLiteralElement(node) => node.range(), AnyNodeRef::PatternMatchValue(node) => node.range(), AnyNodeRef::PatternMatchSingleton(node) => node.range(), AnyNodeRef::PatternMatchSequence(node) => node.range(), @@ -6041,7 +6139,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::TypeParamTypeVar(node) => node.range(), AnyNodeRef::TypeParamTypeVarTuple(node) => node.range(), AnyNodeRef::TypeParamParamSpec(node) => node.range(), - AnyNodeRef::FStringFormatSpec(node) => node.range(), + AnyNodeRef::InterpolatedStringFormatSpec(node) => node.range(), AnyNodeRef::PatternArguments(node) => node.range(), AnyNodeRef::PatternKeyword(node) => node.range(), AnyNodeRef::Comprehension(node) => node.range(), @@ -6057,6 +6155,7 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::ElifElseClause(node) => node.range(), AnyNodeRef::TypeParams(node) => node.range(), AnyNodeRef::FString(node) => node.range(), + AnyNodeRef::TString(node) => node.range(), AnyNodeRef::StringLiteral(node) => node.range(), AnyNodeRef::BytesLiteral(node) => node.range(), AnyNodeRef::Identifier(node) => node.range(), @@ -6112,6 +6211,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::ExprCompare(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprCall(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprFString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprTString(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprStringLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprBytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprNumberLiteral(node) => std::ptr::NonNull::from(*node).cast(), @@ -6127,8 +6227,10 @@ impl AnyNodeRef<'_> { AnyNodeRef::ExprSlice(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExprIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ExceptHandlerExceptHandler(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::FStringExpressionElement(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::FStringLiteralElement(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedElement(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedStringLiteralElement(node) => { + std::ptr::NonNull::from(*node).cast() + } AnyNodeRef::PatternMatchValue(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::PatternMatchSingleton(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::PatternMatchSequence(node) => std::ptr::NonNull::from(*node).cast(), @@ -6140,7 +6242,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::TypeParamTypeVar(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::TypeParamTypeVarTuple(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::TypeParamParamSpec(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::FStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::PatternArguments(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::PatternKeyword(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Comprehension(node) => std::ptr::NonNull::from(*node).cast(), @@ -6156,6 +6258,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::ElifElseClause(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::TypeParams(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::FString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TString(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::StringLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::BytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Identifier(node) => std::ptr::NonNull::from(*node).cast(), @@ -6215,6 +6318,7 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::ExprCompare(node) => node.visit_source_order(visitor), AnyNodeRef::ExprCall(node) => node.visit_source_order(visitor), AnyNodeRef::ExprFString(node) => node.visit_source_order(visitor), + AnyNodeRef::ExprTString(node) => node.visit_source_order(visitor), AnyNodeRef::ExprStringLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::ExprBytesLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::ExprNumberLiteral(node) => node.visit_source_order(visitor), @@ -6230,8 +6334,8 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::ExprSlice(node) => node.visit_source_order(visitor), AnyNodeRef::ExprIpyEscapeCommand(node) => node.visit_source_order(visitor), AnyNodeRef::ExceptHandlerExceptHandler(node) => node.visit_source_order(visitor), - AnyNodeRef::FStringExpressionElement(node) => node.visit_source_order(visitor), - AnyNodeRef::FStringLiteralElement(node) => node.visit_source_order(visitor), + AnyNodeRef::InterpolatedElement(node) => node.visit_source_order(visitor), + AnyNodeRef::InterpolatedStringLiteralElement(node) => node.visit_source_order(visitor), AnyNodeRef::PatternMatchValue(node) => node.visit_source_order(visitor), AnyNodeRef::PatternMatchSingleton(node) => node.visit_source_order(visitor), AnyNodeRef::PatternMatchSequence(node) => node.visit_source_order(visitor), @@ -6243,7 +6347,7 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::TypeParamTypeVar(node) => node.visit_source_order(visitor), AnyNodeRef::TypeParamTypeVarTuple(node) => node.visit_source_order(visitor), AnyNodeRef::TypeParamParamSpec(node) => node.visit_source_order(visitor), - AnyNodeRef::FStringFormatSpec(node) => node.visit_source_order(visitor), + AnyNodeRef::InterpolatedStringFormatSpec(node) => node.visit_source_order(visitor), AnyNodeRef::PatternArguments(node) => node.visit_source_order(visitor), AnyNodeRef::PatternKeyword(node) => node.visit_source_order(visitor), AnyNodeRef::Comprehension(node) => node.visit_source_order(visitor), @@ -6259,6 +6363,7 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::ElifElseClause(node) => node.visit_source_order(visitor), AnyNodeRef::TypeParams(node) => node.visit_source_order(visitor), AnyNodeRef::FString(node) => node.visit_source_order(visitor), + AnyNodeRef::TString(node) => node.visit_source_order(visitor), AnyNodeRef::StringLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::BytesLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::Identifier(node) => node.visit_source_order(visitor), @@ -6330,6 +6435,7 @@ impl AnyNodeRef<'_> { | AnyNodeRef::ExprCompare(_) | AnyNodeRef::ExprCall(_) | AnyNodeRef::ExprFString(_) + | AnyNodeRef::ExprTString(_) | AnyNodeRef::ExprStringLiteral(_) | AnyNodeRef::ExprBytesLiteral(_) | AnyNodeRef::ExprNumberLiteral(_) @@ -6355,10 +6461,10 @@ impl AnyNodeRef<'_> { } impl AnyNodeRef<'_> { - pub const fn is_f_string_element(self) -> bool { + pub const fn is_interpolated_string_element(self) -> bool { matches!( self, - AnyNodeRef::FStringExpressionElement(_) | AnyNodeRef::FStringLiteralElement(_) + AnyNodeRef::InterpolatedElement(_) | AnyNodeRef::InterpolatedStringLiteralElement(_) ) } } @@ -6437,6 +6543,7 @@ pub enum NodeKind { ExprCompare, ExprCall, ExprFString, + ExprTString, ExprStringLiteral, ExprBytesLiteral, ExprNumberLiteral, @@ -6452,8 +6559,8 @@ pub enum NodeKind { ExprSlice, ExprIpyEscapeCommand, ExceptHandlerExceptHandler, - FStringExpressionElement, - FStringLiteralElement, + InterpolatedElement, + InterpolatedStringLiteralElement, PatternMatchValue, PatternMatchSingleton, PatternMatchSequence, @@ -6465,7 +6572,7 @@ pub enum NodeKind { TypeParamTypeVar, TypeParamTypeVarTuple, TypeParamParamSpec, - FStringFormatSpec, + InterpolatedStringFormatSpec, PatternArguments, PatternKeyword, Comprehension, @@ -6481,6 +6588,7 @@ pub enum NodeKind { ElifElseClause, TypeParams, FString, + TString, StringLiteral, BytesLiteral, Identifier, @@ -6534,6 +6642,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::ExprCompare(_) => NodeKind::ExprCompare, AnyNodeRef::ExprCall(_) => NodeKind::ExprCall, AnyNodeRef::ExprFString(_) => NodeKind::ExprFString, + AnyNodeRef::ExprTString(_) => NodeKind::ExprTString, AnyNodeRef::ExprStringLiteral(_) => NodeKind::ExprStringLiteral, AnyNodeRef::ExprBytesLiteral(_) => NodeKind::ExprBytesLiteral, AnyNodeRef::ExprNumberLiteral(_) => NodeKind::ExprNumberLiteral, @@ -6549,8 +6658,10 @@ impl AnyNodeRef<'_> { AnyNodeRef::ExprSlice(_) => NodeKind::ExprSlice, AnyNodeRef::ExprIpyEscapeCommand(_) => NodeKind::ExprIpyEscapeCommand, AnyNodeRef::ExceptHandlerExceptHandler(_) => NodeKind::ExceptHandlerExceptHandler, - AnyNodeRef::FStringExpressionElement(_) => NodeKind::FStringExpressionElement, - AnyNodeRef::FStringLiteralElement(_) => NodeKind::FStringLiteralElement, + AnyNodeRef::InterpolatedElement(_) => NodeKind::InterpolatedElement, + AnyNodeRef::InterpolatedStringLiteralElement(_) => { + NodeKind::InterpolatedStringLiteralElement + } AnyNodeRef::PatternMatchValue(_) => NodeKind::PatternMatchValue, AnyNodeRef::PatternMatchSingleton(_) => NodeKind::PatternMatchSingleton, AnyNodeRef::PatternMatchSequence(_) => NodeKind::PatternMatchSequence, @@ -6562,7 +6673,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::TypeParamTypeVar(_) => NodeKind::TypeParamTypeVar, AnyNodeRef::TypeParamTypeVarTuple(_) => NodeKind::TypeParamTypeVarTuple, AnyNodeRef::TypeParamParamSpec(_) => NodeKind::TypeParamParamSpec, - AnyNodeRef::FStringFormatSpec(_) => NodeKind::FStringFormatSpec, + AnyNodeRef::InterpolatedStringFormatSpec(_) => NodeKind::InterpolatedStringFormatSpec, AnyNodeRef::PatternArguments(_) => NodeKind::PatternArguments, AnyNodeRef::PatternKeyword(_) => NodeKind::PatternKeyword, AnyNodeRef::Comprehension(_) => NodeKind::Comprehension, @@ -6578,6 +6689,7 @@ impl AnyNodeRef<'_> { AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause, AnyNodeRef::TypeParams(_) => NodeKind::TypeParams, AnyNodeRef::FString(_) => NodeKind::FString, + AnyNodeRef::TString(_) => NodeKind::TString, AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral, AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral, AnyNodeRef::Identifier(_) => NodeKind::Identifier, @@ -7023,6 +7135,20 @@ pub struct ExprFString { pub value: crate::FStringValue, } +/// An AST node that represents either a single-part t-string literal +/// or an implicitly concatenated t-string literal. +/// +/// This type differs from the original Python AST `TemplateStr` in that it +/// doesn't join the implicitly concatenated parts into a single string. Instead, +/// it keeps them separate and provide various methods to access the parts. +/// +/// See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateStr) +#[derive(Clone, Debug, PartialEq)] +pub struct ExprTString { + pub range: ruff_text_size::TextRange, + pub value: crate::TStringValue, +} + /// An AST node that represents either a single-part string literal /// or an implicitly concatenated string literal. #[derive(Clone, Debug, PartialEq)] diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 0464c636f43c49..ba0b08bfe680df 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -12,8 +12,8 @@ use crate::parenthesize::parenthesized_range; use crate::statement_visitor::StatementVisitor; use crate::visitor::Visitor; use crate::{ - self as ast, Arguments, CmpOp, DictItem, ExceptHandler, Expr, FStringElement, MatchCase, - Operator, Pattern, Stmt, TypeParam, + self as ast, Arguments, CmpOp, DictItem, ExceptHandler, Expr, InterpolatedStringElement, + MatchCase, Operator, Pattern, Stmt, TypeParam, }; use crate::{AnyNodeRef, ExprContext}; @@ -138,7 +138,10 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { } Expr::FString(ast::ExprFString { value, .. }) => value .elements() - .any(|expr| any_over_f_string_element(expr, func)), + .any(|expr| any_over_interpolated_string_element(expr, func)), + Expr::TString(ast::ExprTString { value, .. }) => value + .elements() + .any(|expr| any_over_interpolated_string_element(expr, func)), Expr::Named(ast::ExprNamed { target, value, @@ -315,22 +318,22 @@ pub fn any_over_pattern(pattern: &Pattern, func: &dyn Fn(&Expr) -> bool) -> bool } } -pub fn any_over_f_string_element( - element: &ast::FStringElement, +pub fn any_over_interpolated_string_element( + element: &ast::InterpolatedStringElement, func: &dyn Fn(&Expr) -> bool, ) -> bool { match element { - ast::FStringElement::Literal(_) => false, - ast::FStringElement::Expression(ast::FStringExpressionElement { + ast::InterpolatedStringElement::Literal(_) => false, + ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, format_spec, .. }) => { any_over_expr(expression, func) || format_spec.as_ref().is_some_and(|spec| { - spec.elements - .iter() - .any(|spec_element| any_over_f_string_element(spec_element, func)) + spec.elements.iter().any(|spec_element| { + any_over_interpolated_string_element(spec_element, func) + }) }) } } @@ -1304,6 +1307,8 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool { // These literals may or may not be empty. Expr::FString(f_string) => is_non_empty_f_string(f_string), + // These literals may or may not be empty. + Expr::TString(f_string) => is_non_empty_t_string(f_string), Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(), Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(), } @@ -1313,8 +1318,78 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool { ast::FStringPart::Literal(string_literal) => !string_literal.is_empty(), ast::FStringPart::FString(f_string) => { f_string.elements.iter().all(|element| match element { - FStringElement::Literal(string_literal) => !string_literal.is_empty(), - FStringElement::Expression(f_string) => inner(&f_string.expression), + InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(), + InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression), + }) + } + }) +} + +/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an +/// f-string expression, or `false` if the expression may resolve to an empty string. +fn is_non_empty_t_string(expr: &ast::ExprTString) -> bool { + fn inner(expr: &Expr) -> bool { + match expr { + // When stringified, these expressions are always non-empty. + Expr::Lambda(_) => true, + Expr::Dict(_) => true, + Expr::Set(_) => true, + Expr::ListComp(_) => true, + Expr::SetComp(_) => true, + Expr::DictComp(_) => true, + Expr::Compare(_) => true, + Expr::NumberLiteral(_) => true, + Expr::BooleanLiteral(_) => true, + Expr::NoneLiteral(_) => true, + Expr::EllipsisLiteral(_) => true, + Expr::List(_) => true, + Expr::Tuple(_) => true, + + // These expressions must resolve to the inner expression. + Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse), + Expr::Named(ast::ExprNamed { value, .. }) => inner(value), + + // These expressions are complex. We can't determine whether they're empty or not. + Expr::BoolOp(ast::ExprBoolOp { .. }) => false, + Expr::BinOp(ast::ExprBinOp { .. }) => false, + Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false, + Expr::Generator(_) => false, + Expr::Await(_) => false, + Expr::Yield(_) => false, + Expr::YieldFrom(_) => false, + Expr::Call(_) => false, + Expr::Attribute(_) => false, + Expr::Subscript(_) => false, + Expr::Starred(_) => false, + Expr::Name(_) => false, + Expr::Slice(_) => false, + Expr::IpyEscapeCommand(_) => false, + + // These literals may or may not be empty. + Expr::FString(f_string) => is_non_empty_f_string(f_string), + // These literals may or may not be empty. + Expr::TString(t_string) => is_non_empty_t_string(t_string), + Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(), + Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => !value.is_empty(), + } + } + + expr.value.iter().any(|part| match part { + ast::TStringPart::Literal(string_literal) => !string_literal.is_empty(), + ast::TStringPart::TString(t_string) => { + t_string.elements.iter().all(|element| match element { + ast::InterpolatedStringElement::Literal(string_literal) => { + !string_literal.is_empty() + } + ast::InterpolatedStringElement::Interpolation(t_string) => { + inner(&t_string.expression) + } + }) + } + ast::TStringPart::FString(f_string) => { + f_string.elements.iter().all(|element| match element { + InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(), + InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression), }) } }) @@ -1331,10 +1406,10 @@ fn is_empty_f_string(expr: &ast::ExprFString) -> bool { value .elements() .all(|f_string_element| match f_string_element { - FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => { - value.is_empty() - } - FStringElement::Expression(ast::FStringExpressionElement { + InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { value, .. }, + ) => value.is_empty(), + InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, .. }) => inner(expression), @@ -1348,8 +1423,8 @@ fn is_empty_f_string(expr: &ast::ExprFString) -> bool { ast::FStringPart::Literal(string_literal) => string_literal.is_empty(), ast::FStringPart::FString(f_string) => { f_string.elements.iter().all(|element| match element { - FStringElement::Literal(string_literal) => string_literal.is_empty(), - FStringElement::Expression(f_string) => inner(&f_string.expression), + InterpolatedStringElement::Literal(string_literal) => string_literal.is_empty(), + InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression), }) } }) diff --git a/crates/ruff_python_ast/src/node.rs b/crates/ruff_python_ast/src/node.rs index 8e7a10bc528f74..52912c6d66c64f 100644 --- a/crates/ruff_python_ast/src/node.rs +++ b/crates/ruff_python_ast/src/node.rs @@ -85,23 +85,23 @@ impl ast::ExprCompare { } } -impl ast::FStringFormatSpec { +impl ast::InterpolatedStringFormatSpec { pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) where V: SourceOrderVisitor<'a> + ?Sized, { for element in &self.elements { - visitor.visit_f_string_element(element); + visitor.visit_interpolated_string_element(element); } } } -impl ast::FStringExpressionElement { +impl ast::InterpolatedElement { pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::FStringExpressionElement { + let ast::InterpolatedElement { expression, format_spec, .. @@ -110,18 +110,18 @@ impl ast::FStringExpressionElement { if let Some(format_spec) = format_spec { for spec_part in &format_spec.elements { - visitor.visit_f_string_element(spec_part); + visitor.visit_interpolated_string_element(spec_part); } } } } -impl ast::FStringLiteralElement { +impl ast::InterpolatedStringLiteralElement { pub(crate) fn visit_source_order<'a, V>(&'a self, _visitor: &mut V) where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::FStringLiteralElement { range: _, value: _ } = self; + let ast::InterpolatedStringLiteralElement { range: _, value: _ } = self; } } @@ -145,6 +145,29 @@ impl ast::ExprFString { } } +impl ast::ExprTString { + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: SourceOrderVisitor<'a> + ?Sized, + { + let ast::ExprTString { value, range: _ } = self; + + for t_string_part in value { + match t_string_part { + ast::TStringPart::Literal(string_literal) => { + visitor.visit_string_literal(string_literal); + } + ast::TStringPart::FString(f_string) => { + visitor.visit_f_string(f_string); + } + ast::TStringPart::TString(t_string) => { + visitor.visit_t_string(t_string); + } + } + } + } +} + impl ast::ExprStringLiteral { pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) where @@ -615,7 +638,24 @@ impl ast::FString { } = self; for fstring_element in elements { - visitor.visit_f_string_element(fstring_element); + visitor.visit_interpolated_string_element(fstring_element); + } + } +} + +impl ast::TString { + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: SourceOrderVisitor<'a> + ?Sized, + { + let ast::TString { + elements, + range: _, + flags: _, + } = self; + + for tstring_element in elements { + visitor.visit_interpolated_string_element(tstring_element); } } } diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 40e3dd8f00d643..54235b2bbe91a3 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -2,7 +2,7 @@ use crate::generated::{ ExprBytesLiteral, ExprDict, ExprFString, ExprList, ExprName, ExprSet, ExprStringLiteral, - ExprTuple, StmtClassDef, + ExprTString, ExprTuple, StmtClassDef, }; use std::borrow::Cow; use std::fmt; @@ -17,10 +17,12 @@ use itertools::Itertools; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use crate::str_prefix::{AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix}; +use crate::str_prefix::{ + AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, TStringPrefix, +}; use crate::{ - Expr, ExprRef, FStringElement, LiteralExpressionRef, OperatorPrecedence, Pattern, Stmt, - TypeParam, int, + Expr, ExprRef, InterpolatedStringElement, LiteralExpressionRef, OperatorPrecedence, Pattern, + Stmt, TypeParam, int, name::Name, str::{Quote, TripleQuotes}, }; @@ -312,35 +314,35 @@ impl<'a> IntoIterator for &'a ExprSet { } #[derive(Clone, Debug, PartialEq)] -pub struct FStringFormatSpec { +pub struct InterpolatedStringFormatSpec { pub range: TextRange, - pub elements: FStringElements, + pub elements: InterpolatedStringElements, } /// See also [FormattedValue](https://docs.python.org/3/library/ast.html#ast.FormattedValue) #[derive(Clone, Debug, PartialEq)] -pub struct FStringExpressionElement { +pub struct InterpolatedElement { pub range: TextRange, pub expression: Box, pub debug_text: Option, pub conversion: ConversionFlag, - pub format_spec: Option>, + pub format_spec: Option>, } /// An `FStringLiteralElement` with an empty `value` is an invalid f-string element. #[derive(Clone, Debug, PartialEq)] -pub struct FStringLiteralElement { +pub struct InterpolatedStringLiteralElement { pub range: TextRange, pub value: Box, } -impl FStringLiteralElement { +impl InterpolatedStringLiteralElement { pub fn is_valid(&self) -> bool { !self.value.is_empty() } } -impl Deref for FStringLiteralElement { +impl Deref for InterpolatedStringLiteralElement { type Target = str; fn deref(&self) -> &Self::Target { @@ -483,7 +485,7 @@ impl FStringValue { self.iter().filter_map(|part| part.as_f_string()) } - /// Returns an iterator over all the [`FStringElement`] contained in this value. + /// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value. /// /// An f-string element is what makes up an [`FString`] i.e., it is either a /// string literal or an expression. In the following example, @@ -494,7 +496,7 @@ impl FStringValue { /// /// The f-string elements returned would be string literal (`"bar "`), /// expression (`x`) and string literal (`"qux"`). - pub fn elements(&self) -> impl Iterator { + pub fn elements(&self) -> impl Iterator { self.f_strings().flat_map(|fstring| fstring.elements.iter()) } } @@ -554,6 +556,181 @@ impl Ranged for FStringPart { } } +impl ExprTString { + /// Returns the single [`TString`] if the t-string isn't implicitly concatenated, [`None`] + /// otherwise. + pub const fn as_single_part_tstring(&self) -> Option<&TString> { + match &self.value.inner { + TStringValueInner::Single(TStringPart::TString(tstring)) => Some(tstring), + _ => None, + } + } +} + +/// The value representing an [`ExprTString`]. +#[derive(Clone, Debug, PartialEq)] +pub struct TStringValue { + inner: TStringValueInner, +} + +impl TStringValue { + /// Creates a new t-string literal with a single [`TString`] part. + pub fn single(value: TString) -> Self { + Self { + inner: TStringValueInner::Single(TStringPart::TString(value)), + } + } + + /// Creates a new t-string with the given values that represents an implicitly + /// concatenated t-string. + /// + /// # Panics + /// + /// Panics if `values` has less than 2 elements. + /// Use [`TStringValue::single`] instead. + pub fn concatenated(values: Vec) -> Self { + assert!( + values.len() > 1, + "Use `TStringValue::single` to create single-part t-strings" + ); + Self { + inner: TStringValueInner::Concatenated(values), + } + } + + /// Returns `true` if the t-string is implicitly concatenated, `false` otherwise. + pub fn is_implicit_concatenated(&self) -> bool { + matches!(self.inner, TStringValueInner::Concatenated(_)) + } + + /// Returns a slice of all the [`TStringPart`]s contained in this value. + pub fn as_slice(&self) -> &[TStringPart] { + match &self.inner { + TStringValueInner::Single(part) => std::slice::from_ref(part), + TStringValueInner::Concatenated(parts) => parts, + } + } + + /// Returns a mutable slice of all the [`TStringPart`]s contained in this value. + fn as_mut_slice(&mut self) -> &mut [TStringPart] { + match &mut self.inner { + TStringValueInner::Single(part) => std::slice::from_mut(part), + TStringValueInner::Concatenated(parts) => parts, + } + } + + /// Returns an iterator over all the [`TStringPart`]s contained in this value. + pub fn iter(&self) -> Iter { + self.as_slice().iter() + } + + /// Returns an iterator over all the [`TStringPart`]s contained in this value + /// that allows modification. + pub fn iter_mut(&mut self) -> IterMut { + self.as_mut_slice().iter_mut() + } + + /// Returns an iterator over the [`StringLiteral`] parts contained in this value. + /// + /// Note that this doesn't recurse into the t-string parts. For example, + /// + /// ```python + /// "foo" t"bar {x}" "baz" t"qux" + /// ``` + /// + /// Here, the string literal parts returned would be `"foo"` and `"baz"`. + pub fn literals(&self) -> impl Iterator { + self.iter().filter_map(|part| part.as_literal()) + } + + /// Returns an iterator over the [`TString`] parts contained in this value. + /// + /// Note that this doesn't recurse into the t-string parts. For example, + /// + /// ```python + /// "foo" t"bar {x}" "baz" t"qux" + /// ``` + /// + /// Here, the t-string parts returned would be `f"bar {x}"` and `f"qux"`. + pub fn t_strings(&self) -> impl Iterator { + self.iter().filter_map(|part| part.as_t_string()) + } + + /// Returns an iterator over all the [`InterpolatedStringElement`] contained in this value. + /// + /// An t-string element is what makes up an [`TString`] i.e., it is either a + /// string literal or an interpolation. In the following example, + /// + /// ```python + /// "foo" t"bar {x}" "baz" t"qux" + /// ``` + /// + /// The t-string elements returned would be string literal (`"bar "`), + /// interpolation (`x`) and string literal (`"qux"`). + pub fn elements(&self) -> impl Iterator { + self.t_strings().flat_map(|fstring| fstring.elements.iter()) + } +} + +impl<'a> IntoIterator for &'a TStringValue { + type Item = &'a TStringPart; + type IntoIter = Iter<'a, TStringPart>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut TStringValue { + type Item = &'a mut TStringPart; + type IntoIter = IterMut<'a, TStringPart>; + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// An internal representation of [`TStringValue`]. +#[derive(Clone, Debug, PartialEq)] +enum TStringValueInner { + /// A single t-string i.e., `t"foo"`. + /// + /// This is always going to be `TStringPart::TString` variant which is + /// maintained by the `TStringValue::single` constructor. + Single(TStringPart), + + /// An implicitly concatenated t-string i.e., `"foo" t"bar {x}"`. + Concatenated(Vec), +} + +/// An t-string part which is either a string literal, an f-string, +/// or a t-string. +#[derive(Clone, Debug, PartialEq, is_macro::Is)] +pub enum TStringPart { + Literal(StringLiteral), + FString(FString), + TString(TString), +} + +impl TStringPart { + pub fn quote_style(&self) -> Quote { + match self { + Self::Literal(string_literal) => string_literal.flags.quote_style(), + Self::FString(f_string) => f_string.flags.quote_style(), + Self::TString(t_string) => t_string.flags.quote_style(), + } + } +} + +impl Ranged for TStringPart { + fn range(&self) -> TextRange { + match self { + TStringPart::Literal(string_literal) => string_literal.range(), + TStringPart::FString(f_string) => f_string.range(), + TStringPart::TString(t_string) => t_string.range(), + } + } +} + pub trait StringFlags: Copy { /// Does the string use single or double quotes in its opener and closer? fn quote_style(self) -> Quote; @@ -635,7 +812,7 @@ impl std::fmt::Display for DisplayFlags<'_> { bitflags! { #[derive(Default, Copy, Clone, PartialEq, Eq, Hash)] - struct FStringFlagsInner: u8 { + struct InterpolatedStringFlagsInner: u8 { /// The f-string uses double quotes (`"`) for its opener and closer. /// If this flag is not set, the f-string uses single quotes (`'`) /// for its opener and closer. @@ -662,6 +839,11 @@ bitflags! { /// Flags that can be queried to obtain information /// regarding the prefixes and quotes used for an f-string. /// +/// Note: This is identical to [`TStringFlags`] except that +/// the implementation of the `prefix` method of the +/// [`StringFlags`] trait returns a variant of +/// `AnyStringPrefix::Format`. +/// /// ## Notes on usage /// /// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix @@ -671,7 +853,7 @@ bitflags! { /// will properly handle nested f-strings. For usage that doesn't fit into one of these categories, /// the public constructor [`FStringFlags::empty`] can be used. #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct FStringFlags(FStringFlagsInner); +pub struct FStringFlags(InterpolatedStringFlagsInner); impl FStringFlags { /// Construct a new [`FStringFlags`] with **no flags set**. @@ -684,42 +866,60 @@ impl FStringFlags { /// situations in which alternative ways to construct this struct should be used, especially /// when writing lint rules. pub fn empty() -> Self { - Self(FStringFlagsInner::empty()) + Self(InterpolatedStringFlagsInner::empty()) } #[must_use] pub fn with_quote_style(mut self, quote_style: Quote) -> Self { - self.0 - .set(FStringFlagsInner::DOUBLE, quote_style.is_double()); + self.0.set( + InterpolatedStringFlagsInner::DOUBLE, + quote_style.is_double(), + ); self } #[must_use] pub fn with_triple_quotes(mut self, triple_quotes: TripleQuotes) -> Self { - self.0 - .set(FStringFlagsInner::TRIPLE_QUOTED, triple_quotes.is_yes()); + self.0.set( + InterpolatedStringFlagsInner::TRIPLE_QUOTED, + triple_quotes.is_yes(), + ); self } #[must_use] pub fn with_prefix(mut self, prefix: FStringPrefix) -> Self { match prefix { - FStringPrefix::Regular => { - Self(self.0 - FStringFlagsInner::R_PREFIX_LOWER - FStringFlagsInner::R_PREFIX_UPPER) - } + FStringPrefix::Regular => Self( + self.0 + - InterpolatedStringFlagsInner::R_PREFIX_LOWER + - InterpolatedStringFlagsInner::R_PREFIX_UPPER, + ), FStringPrefix::Raw { uppercase_r } => { - self.0.set(FStringFlagsInner::R_PREFIX_UPPER, uppercase_r); - self.0.set(FStringFlagsInner::R_PREFIX_LOWER, !uppercase_r); + self.0 + .set(InterpolatedStringFlagsInner::R_PREFIX_UPPER, uppercase_r); + self.0 + .set(InterpolatedStringFlagsInner::R_PREFIX_LOWER, !uppercase_r); self } } } pub const fn prefix(self) -> FStringPrefix { - if self.0.contains(FStringFlagsInner::R_PREFIX_LOWER) { - debug_assert!(!self.0.contains(FStringFlagsInner::R_PREFIX_UPPER)); + if self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_LOWER) + { + debug_assert!( + !self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER) + ); FStringPrefix::Raw { uppercase_r: false } - } else if self.0.contains(FStringFlagsInner::R_PREFIX_UPPER) { + } else if self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER) + { FStringPrefix::Raw { uppercase_r: true } } else { FStringPrefix::Regular @@ -727,12 +927,108 @@ impl FStringFlags { } } +// TODO(dylan): the documentation about using +// `Checker::default_tstring_flags` is not yet +// correct. This method does not yet exist because +// introducing it would emit a dead code warning +// until we call it in lint rules. +/// Flags that can be queried to obtain information +/// regarding the prefixes and quotes used for an f-string. +/// +/// Note: This is identical to [`FStringFlags`] except that +/// the implementation of the `prefix` method of the +/// [`StringFlags`] trait returns a variant of +/// `AnyStringPrefix::Template`. +/// +/// ## Notes on usage +/// +/// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix +/// from an existing t-string literal, consider passing along the [`FString::flags`] field. If you +/// don't have an existing literal but have a `Checker` from the `ruff_linter` crate available, +/// consider using `Checker::default_tstring_flags` to create instances of this struct; this method +/// will properly handle nested t-strings. For usage that doesn't fit into one of these categories, +/// the public constructor [`TStringFlags::empty`] can be used. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct TStringFlags(InterpolatedStringFlagsInner); + +impl TStringFlags { + /// Construct a new [`TStringFlags`] with **no flags set**. + /// + /// See [`TStringFlags::with_quote_style`], [`TStringFlags::with_triple_quotes`], and + /// [`TStringFlags::with_prefix`] for ways of setting the quote style (single or double), + /// enabling triple quotes, and adding prefixes (such as `r`), respectively. + /// + /// See the documentation for [`TStringFlags`] for additional caveats on this constructor, and + /// situations in which alternative ways to construct this struct should be used, especially + /// when writing lint rules. + pub fn empty() -> Self { + Self(InterpolatedStringFlagsInner::empty()) + } + + #[must_use] + pub fn with_quote_style(mut self, quote_style: Quote) -> Self { + self.0.set( + InterpolatedStringFlagsInner::DOUBLE, + quote_style.is_double(), + ); + self + } + + #[must_use] + pub fn with_triple_quotes(mut self, triple_quotes: TripleQuotes) -> Self { + self.0.set( + InterpolatedStringFlagsInner::TRIPLE_QUOTED, + triple_quotes.is_yes(), + ); + self + } + + #[must_use] + pub fn with_prefix(mut self, prefix: TStringPrefix) -> Self { + match prefix { + TStringPrefix::Regular => Self( + self.0 + - InterpolatedStringFlagsInner::R_PREFIX_LOWER + - InterpolatedStringFlagsInner::R_PREFIX_UPPER, + ), + TStringPrefix::Raw { uppercase_r } => { + self.0 + .set(InterpolatedStringFlagsInner::R_PREFIX_UPPER, uppercase_r); + self.0 + .set(InterpolatedStringFlagsInner::R_PREFIX_LOWER, !uppercase_r); + self + } + } + } + + pub const fn prefix(self) -> TStringPrefix { + if self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_LOWER) + { + debug_assert!( + !self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER) + ); + TStringPrefix::Raw { uppercase_r: false } + } else if self + .0 + .contains(InterpolatedStringFlagsInner::R_PREFIX_UPPER) + { + TStringPrefix::Raw { uppercase_r: true } + } else { + TStringPrefix::Regular + } + } +} + impl StringFlags for FStringFlags { /// Return `true` if the f-string is triple-quoted, i.e., /// it begins and ends with three consecutive quote characters. /// For example: `f"""{bar}"""` fn triple_quotes(self) -> TripleQuotes { - if self.0.contains(FStringFlagsInner::TRIPLE_QUOTED) { + if self.0.contains(InterpolatedStringFlagsInner::TRIPLE_QUOTED) { TripleQuotes::Yes } else { TripleQuotes::No @@ -744,7 +1040,7 @@ impl StringFlags for FStringFlags { /// - `f"{"a"}"` -> `QuoteStyle::Double` /// - `f'{"a"}'` -> `QuoteStyle::Single` fn quote_style(self) -> Quote { - if self.0.contains(FStringFlagsInner::DOUBLE) { + if self.0.contains(InterpolatedStringFlagsInner::DOUBLE) { Quote::Double } else { Quote::Single @@ -766,11 +1062,50 @@ impl fmt::Debug for FStringFlags { } } +impl StringFlags for TStringFlags { + /// Return `true` if the t-string is triple-quoted, i.e., + /// it begins and ends with three consecutive quote characters. + /// For example: `t"""{bar}"""` + fn triple_quotes(self) -> TripleQuotes { + if self.0.contains(InterpolatedStringFlagsInner::TRIPLE_QUOTED) { + TripleQuotes::Yes + } else { + TripleQuotes::No + } + } + + /// Return the quoting style (single or double quotes) + /// used by the t-string's opener and closer: + /// - `t"{"a"}"` -> `QuoteStyle::Double` + /// - `t'{"a"}'` -> `QuoteStyle::Single` + fn quote_style(self) -> Quote { + if self.0.contains(InterpolatedStringFlagsInner::DOUBLE) { + Quote::Double + } else { + Quote::Single + } + } + + fn prefix(self) -> AnyStringPrefix { + AnyStringPrefix::Template(self.prefix()) + } +} + +impl fmt::Debug for TStringFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TStringFlags") + .field("quote_style", &self.quote_style()) + .field("prefix", &self.prefix()) + .field("triple_quoted", &self.is_triple_quoted()) + .finish() + } +} + /// An AST node that represents a single f-string which is part of an [`ExprFString`]. #[derive(Clone, Debug, PartialEq)] pub struct FString { pub range: TextRange, - pub elements: FStringElements, + pub elements: InterpolatedStringElements, pub flags: FStringFlags, } @@ -784,66 +1119,84 @@ impl From for Expr { } } -/// A newtype wrapper around a list of [`FStringElement`]. +/// A newtype wrapper around a list of [`InterpolatedStringElement`]. #[derive(Clone, Default, PartialEq)] -pub struct FStringElements(Vec); +pub struct InterpolatedStringElements(Vec); -impl FStringElements { - /// Returns an iterator over all the [`FStringLiteralElement`] nodes contained in this f-string. - pub fn literals(&self) -> impl Iterator { +impl InterpolatedStringElements { + /// Returns an iterator over all the [`InterpolatedStringLiteralElement`] nodes contained in this f-string. + pub fn literals(&self) -> impl Iterator { self.iter().filter_map(|element| element.as_literal()) } - /// Returns an iterator over all the [`FStringExpressionElement`] nodes contained in this f-string. - pub fn expressions(&self) -> impl Iterator { - self.iter().filter_map(|element| element.as_expression()) + /// Returns an iterator over all the [`InterpolatedElement`] nodes contained in this f-string. + pub fn interpolations(&self) -> impl Iterator { + self.iter().filter_map(|element| element.as_interpolation()) } } -impl From> for FStringElements { - fn from(elements: Vec) -> Self { - FStringElements(elements) +impl From> for InterpolatedStringElements { + fn from(elements: Vec) -> Self { + InterpolatedStringElements(elements) } } -impl<'a> IntoIterator for &'a FStringElements { - type IntoIter = Iter<'a, FStringElement>; - type Item = &'a FStringElement; +impl<'a> IntoIterator for &'a InterpolatedStringElements { + type IntoIter = Iter<'a, InterpolatedStringElement>; + type Item = &'a InterpolatedStringElement; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl<'a> IntoIterator for &'a mut FStringElements { - type IntoIter = IterMut<'a, FStringElement>; - type Item = &'a mut FStringElement; +impl<'a> IntoIterator for &'a mut InterpolatedStringElements { + type IntoIter = IterMut<'a, InterpolatedStringElement>; + type Item = &'a mut InterpolatedStringElement; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } -impl Deref for FStringElements { - type Target = [FStringElement]; +impl Deref for InterpolatedStringElements { + type Target = [InterpolatedStringElement]; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for FStringElements { +impl DerefMut for InterpolatedStringElements { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl fmt::Debug for FStringElements { +impl fmt::Debug for InterpolatedStringElements { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } +/// An AST node that represents a single t-string which is part of an [`ExprTString`]. +#[derive(Clone, Debug, PartialEq)] +pub struct TString { + pub range: TextRange, + pub elements: InterpolatedStringElements, + pub flags: TStringFlags, +} + +impl From for Expr { + fn from(payload: TString) -> Self { + ExprTString { + range: payload.range, + value: TStringValue::single(payload), + } + .into() + } +} + impl ExprStringLiteral { /// Return `Some(literal)` if the string only consists of a single `StringLiteral` part /// (indicating that it is not implicitly concatenated). Otherwise, return `None`. @@ -1662,18 +2015,23 @@ bitflags! { /// but can have no other prefixes. const F_PREFIX = 1 << 4; + /// The string has a `t` or `T` prefix, meaning it is a t-string. + /// T-strings can also be raw strings, + /// but can have no other prefixes. + const T_PREFIX = 1 << 5; + /// The string has an `r` prefix, meaning it is a raw string. /// F-strings and byte-strings can be raw, /// as can strings with no other prefixes. /// U-strings cannot be raw. - const R_PREFIX_LOWER = 1 << 5; + const R_PREFIX_LOWER = 1 << 6; /// The string has an `R` prefix, meaning it is a raw string. /// The casing of the `r`/`R` has no semantic significance at runtime; /// see https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#r-strings-and-r-strings /// for why we track the casing of the `r` prefix, /// but not for any other prefix - const R_PREFIX_UPPER = 1 << 6; + const R_PREFIX_UPPER = 1 << 7; } } @@ -1711,6 +2069,15 @@ impl AnyStringFlags { AnyStringPrefix::Format(FStringPrefix::Raw { uppercase_r: true }) => { AnyStringFlagsInner::F_PREFIX.union(AnyStringFlagsInner::R_PREFIX_UPPER) } + + // t-strings + AnyStringPrefix::Template(TStringPrefix::Regular) => AnyStringFlagsInner::T_PREFIX, + AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: false }) => { + AnyStringFlagsInner::T_PREFIX.union(AnyStringFlagsInner::R_PREFIX_LOWER) + } + AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: true }) => { + AnyStringFlagsInner::T_PREFIX.union(AnyStringFlagsInner::R_PREFIX_UPPER) + } }; self } @@ -1734,9 +2101,10 @@ impl AnyStringFlags { ) } - /// Does the string have an `f` or `F` prefix? - pub const fn is_f_string(self) -> bool { - self.0.contains(AnyStringFlagsInner::F_PREFIX) + /// Does the string have an `f`,`F`,`t`, or `T` prefix? + pub const fn is_interpolated_string(self) -> bool { + self.0 + .intersects(AnyStringFlagsInner::F_PREFIX.union(AnyStringFlagsInner::T_PREFIX)) } /// Does the string have a `b` or `B` prefix? @@ -1793,6 +2161,17 @@ impl StringFlags for AnyStringFlags { return AnyStringPrefix::Format(FStringPrefix::Regular); } + // t-strings + if flags.contains(AnyStringFlagsInner::T_PREFIX) { + if flags.contains(AnyStringFlagsInner::R_PREFIX_LOWER) { + return AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: false }); + } + if flags.contains(AnyStringFlagsInner::R_PREFIX_UPPER) { + return AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: true }); + } + return AnyStringPrefix::Template(TStringPrefix::Regular); + } + // bytestrings if flags.contains(AnyStringFlagsInner::B_PREFIX) { if flags.contains(AnyStringFlagsInner::R_PREFIX_LOWER) { @@ -1872,7 +2251,7 @@ impl From for AnyStringFlags { impl From for FStringFlags { fn from(value: AnyStringFlags) -> FStringFlags { - let AnyStringPrefix::Format(fstring_prefix) = value.prefix() else { + let AnyStringPrefix::Format(prefix) = value.prefix() else { unreachable!( "Should never attempt to convert {} into an f-string", value.prefix() @@ -1880,7 +2259,7 @@ impl From for FStringFlags { }; FStringFlags::empty() .with_quote_style(value.quote_style()) - .with_prefix(fstring_prefix) + .with_prefix(prefix) .with_triple_quotes(value.triple_quotes()) } } @@ -1891,6 +2270,27 @@ impl From for AnyStringFlags { } } +impl From for TStringFlags { + fn from(value: AnyStringFlags) -> TStringFlags { + let AnyStringPrefix::Template(prefix) = value.prefix() else { + unreachable!( + "Should never attempt to convert {} into a t-string", + value.prefix() + ) + }; + TStringFlags::empty() + .with_quote_style(value.quote_style()) + .with_prefix(prefix) + .with_triple_quotes(value.triple_quotes()) + } +} + +impl From for AnyStringFlags { + fn from(value: TStringFlags) -> Self { + value.as_any_string_flags() + } +} + #[derive(Clone, Debug, PartialEq, is_macro::Is)] pub enum Number { Int(int::Int), diff --git a/crates/ruff_python_ast/src/operator_precedence.rs b/crates/ruff_python_ast/src/operator_precedence.rs index 750ef7f719d3cc..6b652847e9a574 100644 --- a/crates/ruff_python_ast/src/operator_precedence.rs +++ b/crates/ruff_python_ast/src/operator_precedence.rs @@ -72,7 +72,8 @@ impl OperatorPrecedence { | ExprRef::BooleanLiteral(_) | ExprRef::NoneLiteral(_) | ExprRef::EllipsisLiteral(_) - | ExprRef::FString(_) => Self::Atomic, + | ExprRef::FString(_) + | ExprRef::TString(_) => Self::Atomic, // Subscription, slicing, call, attribute reference ExprRef::Attribute(_) | ExprRef::Subscript(_) diff --git a/crates/ruff_python_ast/src/python_version.rs b/crates/ruff_python_ast/src/python_version.rs index 0658bc8dee1dfe..bc5a2689de1412 100644 --- a/crates/ruff_python_ast/src/python_version.rs +++ b/crates/ruff_python_ast/src/python_version.rs @@ -59,6 +59,13 @@ impl PythonVersion { Self::PY313 } + /// The latest Python version supported in preview + pub fn latest_preview() -> Self { + let latest_preview = Self::PY314; + debug_assert!(latest_preview >= Self::latest()); + latest_preview + } + pub const fn latest_ty() -> Self { // Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version. Self::PY313 diff --git a/crates/ruff_python_ast/src/relocate.rs b/crates/ruff_python_ast/src/relocate.rs index 7410b9fc5eb8c6..c985003069bd38 100644 --- a/crates/ruff_python_ast/src/relocate.rs +++ b/crates/ruff_python_ast/src/relocate.rs @@ -72,6 +72,9 @@ impl Transformer for Relocator { Expr::FString(ast::ExprFString { range, .. }) => { *range = self.range; } + Expr::TString(ast::ExprTString { range, .. }) => { + *range = self.range; + } Expr::StringLiteral(ast::ExprStringLiteral { range, .. }) => { *range = self.range; } diff --git a/crates/ruff_python_ast/src/str.rs b/crates/ruff_python_ast/src/str.rs index 5a8dd1093e3b5e..a9096a5218a007 100644 --- a/crates/ruff_python_ast/src/str.rs +++ b/crates/ruff_python_ast/src/str.rs @@ -5,7 +5,7 @@ use std::sync::LazyLock; use ruff_text_size::{TextLen, TextRange}; /// Enumeration of the two kinds of quotes that can be used -/// for Python string/f-string/bytestring literals +/// for Python string/f/t-string/bytestring literals #[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, is_macro::Is)] pub enum Quote { /// E.g. `'` diff --git a/crates/ruff_python_ast/src/str_prefix.rs b/crates/ruff_python_ast/src/str_prefix.rs index 37f8421711da8f..a00b02fb46c285 100644 --- a/crates/ruff_python_ast/src/str_prefix.rs +++ b/crates/ruff_python_ast/src/str_prefix.rs @@ -91,6 +91,47 @@ impl fmt::Display for FStringPrefix { } } +/// Enumeration of the valid prefixes a t-string literal can have. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum TStringPrefix { + /// Just a regular t-string with no other prefixes, e.g. t"{bar}" + Regular, + + /// A "raw" template string, that has an `r` or `R` prefix, + /// e.g. `rt"{bar}"` or `Rt"{bar}"` + Raw { uppercase_r: bool }, +} + +impl TStringPrefix { + /// Return a `str` representation of the prefix + pub const fn as_str(self) -> &'static str { + match self { + Self::Regular => "t", + Self::Raw { uppercase_r: true } => "Rt", + Self::Raw { uppercase_r: false } => "rt", + } + } + + pub const fn text_len(self) -> TextSize { + match self { + Self::Regular => TextSize::new(1), + Self::Raw { .. } => TextSize::new(2), + } + } + + /// Return true if this prefix indicates a "raw t-string", + /// e.g. `rt"{bar}"` or `Rt"{bar}"` + pub const fn is_raw(self) -> bool { + matches!(self, Self::Raw { .. }) + } +} + +impl fmt::Display for TStringPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// Enumeration of the valid prefixes a bytestring literal can have. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ByteStringPrefix { @@ -151,6 +192,9 @@ pub enum AnyStringPrefix { /// Prefixes that indicate the string is an f-string Format(FStringPrefix), + /// Prefixes that indicate the string is a t-string + Template(TStringPrefix), + /// All other prefixes Regular(StringLiteralPrefix), } @@ -161,6 +205,7 @@ impl AnyStringPrefix { Self::Regular(regular_prefix) => regular_prefix.as_str(), Self::Bytes(bytestring_prefix) => bytestring_prefix.as_str(), Self::Format(fstring_prefix) => fstring_prefix.as_str(), + Self::Template(tstring_prefix) => tstring_prefix.as_str(), } } @@ -169,6 +214,7 @@ impl AnyStringPrefix { Self::Regular(regular_prefix) => regular_prefix.text_len(), Self::Bytes(bytestring_prefix) => bytestring_prefix.text_len(), Self::Format(fstring_prefix) => fstring_prefix.text_len(), + Self::Template(tstring_prefix) => tstring_prefix.text_len(), } } @@ -177,6 +223,7 @@ impl AnyStringPrefix { Self::Regular(regular_prefix) => regular_prefix.is_raw(), Self::Bytes(bytestring_prefix) => bytestring_prefix.is_raw(), Self::Format(fstring_prefix) => fstring_prefix.is_raw(), + Self::Template(tstring_prefix) => tstring_prefix.is_raw(), } } } diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index 8bb2013efc07b2..1e8c5ebf36c387 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -5,10 +5,10 @@ pub mod transformer; use crate::{ self as ast, Alias, AnyParameterRef, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, - Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement, - FStringPart, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments, - PatternKeyword, Stmt, StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar, - TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, + Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringPart, + InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, + PatternArguments, PatternKeyword, Stmt, StringLiteral, TString, TStringPart, TypeParam, + TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, }; /// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order. @@ -99,8 +99,14 @@ pub trait Visitor<'a> { fn visit_f_string(&mut self, f_string: &'a FString) { walk_f_string(self, f_string); } - fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) { - walk_f_string_element(self, f_string_element); + fn visit_interpolated_string_element( + &mut self, + interpolated_string_element: &'a InterpolatedStringElement, + ) { + walk_interpolated_string_element(self, interpolated_string_element); + } + fn visit_t_string(&mut self, t_string: &'a TString) { + walk_t_string(self, t_string); } fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) { walk_string_literal(self, string_literal); @@ -484,6 +490,17 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { } } } + Expr::TString(ast::ExprTString { value, .. }) => { + for part in value { + match part { + TStringPart::Literal(string_literal) => { + visitor.visit_string_literal(string_literal); + } + TStringPart::FString(f_string) => visitor.visit_f_string(f_string), + TStringPart::TString(t_string) => visitor.visit_t_string(t_string), + } + } + } Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { for string_literal in value { visitor.visit_string_literal(string_literal); @@ -739,30 +756,36 @@ pub fn walk_pattern_keyword<'a, V: Visitor<'a> + ?Sized>( } pub fn walk_f_string<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, f_string: &'a FString) { - for f_string_element in &f_string.elements { - visitor.visit_f_string_element(f_string_element); + for interpolated_string_element in &f_string.elements { + visitor.visit_interpolated_string_element(interpolated_string_element); } } -pub fn walk_f_string_element<'a, V: Visitor<'a> + ?Sized>( +pub fn walk_interpolated_string_element<'a, V: Visitor<'a> + ?Sized>( visitor: &mut V, - f_string_element: &'a FStringElement, + interpolated_string_element: &'a InterpolatedStringElement, ) { - if let ast::FStringElement::Expression(ast::FStringExpressionElement { + if let ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, format_spec, .. - }) = f_string_element + }) = interpolated_string_element { visitor.visit_expr(expression); if let Some(format_spec) = format_spec { for spec_element in &format_spec.elements { - visitor.visit_f_string_element(spec_element); + visitor.visit_interpolated_string_element(spec_element); } } } } +pub fn walk_t_string<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, t_string: &'a TString) { + for t_string_element in &t_string.elements { + visitor.visit_interpolated_string_element(t_string_element); + } +} + pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>( _visitor: &V, _expr_context: &'a ExprContext, diff --git a/crates/ruff_python_ast/src/visitor/source_order.rs b/crates/ruff_python_ast/src/visitor/source_order.rs index 5e6ca022a06001..af57ee48dbdcbc 100644 --- a/crates/ruff_python_ast/src/visitor/source_order.rs +++ b/crates/ruff_python_ast/src/visitor/source_order.rs @@ -1,8 +1,8 @@ use crate::{ Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator, ElifElseClause, - ExceptHandler, Expr, FString, FStringElement, Keyword, MatchCase, Mod, Operator, Parameter, - ParameterWithDefault, Parameters, Pattern, PatternArguments, PatternKeyword, Singleton, Stmt, - StringLiteral, TypeParam, TypeParams, UnaryOp, WithItem, + ExceptHandler, Expr, FString, InterpolatedStringElement, Keyword, MatchCase, Mod, Operator, + Parameter, ParameterWithDefault, Parameters, Pattern, PatternArguments, PatternKeyword, + Singleton, Stmt, StringLiteral, TString, TypeParam, TypeParams, UnaryOp, WithItem, }; use crate::{AnyNodeRef, Identifier}; @@ -157,8 +157,16 @@ pub trait SourceOrderVisitor<'a> { } #[inline] - fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) { - walk_f_string_element(self, f_string_element); + fn visit_interpolated_string_element( + &mut self, + interpolated_string_element: &'a InterpolatedStringElement, + ) { + walk_interpolated_string_element(self, interpolated_string_element); + } + + #[inline] + fn visit_t_string(&mut self, t_string: &'a TString) { + walk_t_string(self, t_string); } #[inline] @@ -272,6 +280,7 @@ where Expr::Compare(expr) => expr.visit_source_order(visitor), Expr::Call(expr) => expr.visit_source_order(visitor), Expr::FString(expr) => expr.visit_source_order(visitor), + Expr::TString(expr) => expr.visit_source_order(visitor), Expr::StringLiteral(expr) => expr.visit_source_order(visitor), Expr::BytesLiteral(expr) => expr.visit_source_order(visitor), Expr::NumberLiteral(expr) => expr.visit_source_order(visitor), @@ -497,15 +506,17 @@ where visitor.leave_node(node); } -pub fn walk_f_string_element<'a, V: SourceOrderVisitor<'a> + ?Sized>( +pub fn walk_interpolated_string_element<'a, V: SourceOrderVisitor<'a> + ?Sized>( visitor: &mut V, - f_string_element: &'a FStringElement, + f_string_element: &'a InterpolatedStringElement, ) { let node = AnyNodeRef::from(f_string_element); if visitor.enter_node(node).is_traverse() { match f_string_element { - FStringElement::Expression(element) => element.visit_source_order(visitor), - FStringElement::Literal(element) => element.visit_source_order(visitor), + InterpolatedStringElement::Interpolation(element) => { + element.visit_source_order(visitor); + } + InterpolatedStringElement::Literal(element) => element.visit_source_order(visitor), } } visitor.leave_node(node); @@ -550,6 +561,18 @@ where visitor.leave_node(node); } +#[inline] +pub fn walk_t_string<'a, V>(visitor: &mut V, t_string: &'a TString) +where + V: SourceOrderVisitor<'a> + ?Sized, +{ + let node = AnyNodeRef::from(t_string); + if visitor.enter_node(node).is_traverse() { + t_string.visit_source_order(visitor); + } + visitor.leave_node(node); +} + #[inline] pub fn walk_string_literal<'a, V>(visitor: &mut V, string_literal: &'a StringLiteral) where diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index f3e1c30bbc2ae3..07b098eb43f2c1 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -1,8 +1,8 @@ use crate::{ self as ast, Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator, - ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement, Keyword, MatchCase, - Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt, - StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, + ElifElseClause, ExceptHandler, Expr, ExprContext, FString, InterpolatedStringElement, Keyword, + MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword, Stmt, + StringLiteral, TString, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, }; @@ -86,8 +86,14 @@ pub trait Transformer { fn visit_f_string(&self, f_string: &mut FString) { walk_f_string(self, f_string); } - fn visit_f_string_element(&self, f_string_element: &mut FStringElement) { - walk_f_string_element(self, f_string_element); + fn visit_interpolated_string_element( + &self, + interpolated_string_element: &mut InterpolatedStringElement, + ) { + walk_interpolated_string_element(self, interpolated_string_element); + } + fn visit_t_string(&self, t_string: &mut TString) { + walk_t_string(self, t_string); } fn visit_string_literal(&self, string_literal: &mut StringLiteral) { walk_string_literal(self, string_literal); @@ -470,6 +476,21 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { } } } + Expr::TString(ast::ExprTString { value, .. }) => { + for t_string_part in value.iter_mut() { + match t_string_part { + ast::TStringPart::Literal(string_literal) => { + visitor.visit_string_literal(string_literal); + } + ast::TStringPart::FString(f_string) => { + visitor.visit_f_string(f_string); + } + ast::TStringPart::TString(t_string) => { + visitor.visit_t_string(t_string); + } + } + } + } Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { for string_literal in value.iter_mut() { visitor.visit_string_literal(string_literal); @@ -744,29 +765,35 @@ pub fn walk_pattern_keyword( pub fn walk_f_string(visitor: &V, f_string: &mut FString) { for element in &mut f_string.elements { - visitor.visit_f_string_element(element); + visitor.visit_interpolated_string_element(element); } } -pub fn walk_f_string_element( +pub fn walk_interpolated_string_element( visitor: &V, - f_string_element: &mut FStringElement, + interpolated_string_element: &mut InterpolatedStringElement, ) { - if let ast::FStringElement::Expression(ast::FStringExpressionElement { + if let ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, format_spec, .. - }) = f_string_element + }) = interpolated_string_element { visitor.visit_expr(expression); if let Some(format_spec) = format_spec { for spec_element in &mut format_spec.elements { - visitor.visit_f_string_element(spec_element); + visitor.visit_interpolated_string_element(spec_element); } } } } +pub fn walk_t_string(visitor: &V, t_string: &mut TString) { + for element in &mut t_string.elements { + visitor.visit_interpolated_string_element(element); + } +} + pub fn walk_expr_context(_visitor: &V, _expr_context: &mut ExprContext) {} pub fn walk_bool_op(_visitor: &V, _bool_op: &mut BoolOp) {} diff --git a/crates/ruff_python_ast_integration_tests/tests/comparable.rs b/crates/ruff_python_ast_integration_tests/tests/comparable.rs index 89155a58a059ad..8f792f1bddb01e 100644 --- a/crates/ruff_python_ast_integration_tests/tests/comparable.rs +++ b/crates/ruff_python_ast_integration_tests/tests/comparable.rs @@ -1,19 +1,36 @@ use ruff_python_ast::comparable::ComparableExpr; use ruff_python_parser::{ParseError, parse_expression}; +#[track_caller] +fn assert_comparable(left: &str, right: &str) -> Result<(), ParseError> { + let left_parsed = parse_expression(left)?; + let right_parsed = parse_expression(right)?; + + let left_compr = ComparableExpr::from(left_parsed.expr()); + let right_compr = ComparableExpr::from(right_parsed.expr()); + + assert_eq!(left_compr, right_compr); + Ok(()) +} + +#[track_caller] +fn assert_noncomparable(left: &str, right: &str) -> Result<(), ParseError> { + let left_parsed = parse_expression(left)?; + let right_parsed = parse_expression(right)?; + + let left_compr = ComparableExpr::from(left_parsed.expr()); + let right_compr = ComparableExpr::from(right_parsed.expr()); + + assert_ne!(left_compr, right_compr); + Ok(()) +} + #[test] fn concatenated_strings_compare_equal() -> Result<(), ParseError> { let split_contents = r#"'a' 'b' r'\n raw'"#; let value_contents = r#"'ab\\n raw'"#; - let split_parsed = parse_expression(split_contents)?; - let value_parsed = parse_expression(value_contents)?; - - let split_compr = ComparableExpr::from(split_parsed.expr()); - let value_compr = ComparableExpr::from(value_parsed.expr()); - - assert_eq!(split_compr, value_compr); - Ok(()) + assert_comparable(split_contents, value_contents) } #[test] @@ -21,14 +38,7 @@ fn concatenated_bytes_compare_equal() -> Result<(), ParseError> { let split_contents = r#"b'a' b'b'"#; let value_contents = r#"b'ab'"#; - let split_parsed = parse_expression(split_contents)?; - let value_parsed = parse_expression(value_contents)?; - - let split_compr = ComparableExpr::from(split_parsed.expr()); - let value_compr = ComparableExpr::from(value_parsed.expr()); - - assert_eq!(split_compr, value_compr); - Ok(()) + assert_comparable(split_contents, value_contents) } #[test] @@ -36,12 +46,45 @@ fn concatenated_fstrings_compare_equal() -> Result<(), ParseError> { let split_contents = r#"f"{foo!r} this" r"\n raw" f" and {bar!s} that""#; let value_contents = r#"f"{foo!r} this\\n raw and {bar!s} that""#; - let split_parsed = parse_expression(split_contents)?; - let value_parsed = parse_expression(value_contents)?; + assert_comparable(split_contents, value_contents) +} - let split_compr = ComparableExpr::from(split_parsed.expr()); - let value_compr = ComparableExpr::from(value_parsed.expr()); +#[test] +fn concatenated_tstrings_compare_equal() -> Result<(), ParseError> { + let split_contents = r#"t"{foo!r} this" r"\n raw" t" and {bar!s} that""#; + let value_contents = r#"t"{foo!r} this\\n raw and {bar!s} that""#; - assert_eq!(split_compr, value_compr); - Ok(()) + assert_comparable(split_contents, value_contents) +} + +#[test] +fn concatenated_f_and_t_strings_interwoven_compare_equal() -> Result<(), ParseError> { + let split_contents = r#"f"{foo} this " t"{bar}" "baz""#; + let value_contents = r#"f"{foo}" t" this {bar}" "baz""#; + + assert_comparable(split_contents, value_contents) +} + +#[test] +fn concatenated_f_and_t_strings_compare_unequal_when_swapped() -> Result<(), ParseError> { + let f_then_t_contents = r#"f"{foo!r} this" r"\n raw" t" and {bar!s} that""#; + let t_then_f_contents = r#"t"{foo!r} this" r"\n raw" f" and {bar!s} that""#; + + assert_noncomparable(f_then_t_contents, t_then_f_contents) +} + +#[test] +fn t_strings_literal_order_matters_compare_unequal() -> Result<(), ParseError> { + let interp_then_literal_contents = r#"t"{foo}bar""#; + let literal_then_interp_contents = r#"t"bar{foo}""#; + + assert_noncomparable(interp_then_literal_contents, literal_then_interp_contents) +} + +#[test] +fn t_strings_empty_concat_equal() -> Result<(), ParseError> { + let empty_literal = r#""" t"hey{foo}""#; + let empty_f_string = r#"f""t"hey{foo}""#; + + assert_comparable(empty_literal, empty_f_string) } diff --git a/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__f_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__f_strings.snap index 84266e3745e02e..87e9af87a6b995 100644 --- a/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__f_strings.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__f_strings.snap @@ -1,18 +1,17 @@ --- source: crates/ruff_python_ast_integration_tests/tests/source_order.rs expression: trace -snapshot_kind: text --- - ModModule - StmtExpr - ExprFString - StringLiteral - FString - - FStringLiteralElement - - FStringExpressionElement + - InterpolatedStringLiteralElement + - InterpolatedElement - ExprName - - FStringLiteralElement - - FStringExpressionElement + - InterpolatedStringLiteralElement + - InterpolatedElement - ExprName - - FStringLiteralElement - - FStringLiteralElement + - InterpolatedStringLiteralElement + - InterpolatedStringLiteralElement diff --git a/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__t_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__t_strings.snap new file mode 100644 index 00000000000000..75e4f537b2f2a9 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__t_strings.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff_python_ast_integration_tests/tests/source_order.rs +expression: trace +--- +- ModModule + - StmtExpr + - ExprTString + - StringLiteral + - TString + - InterpolatedStringLiteralElement + - InterpolatedElement + - ExprName + - InterpolatedStringLiteralElement + - InterpolatedElement + - ExprName + - InterpolatedStringLiteralElement + - InterpolatedStringLiteralElement diff --git a/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap index f379b791d71869..4d81357f9ea627 100644 --- a/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap @@ -1,17 +1,16 @@ --- source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace -snapshot_kind: text --- - StmtExpr - ExprFString - StringLiteral - FString - - FStringLiteralElement - - FStringExpressionElement + - InterpolatedStringLiteralElement + - InterpolatedElement - ExprName - - FStringLiteralElement - - FStringExpressionElement + - InterpolatedStringLiteralElement + - InterpolatedElement - ExprName - - FStringLiteralElement - - FStringLiteralElement + - InterpolatedStringLiteralElement + - InterpolatedStringLiteralElement diff --git a/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__t_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__t_strings.snap new file mode 100644 index 00000000000000..58def387aa8cd6 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__t_strings.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs +expression: trace +--- +- StmtExpr + - ExprTString + - StringLiteral + - TString + - InterpolatedStringLiteralElement + - InterpolatedElement + - ExprName + - InterpolatedStringLiteralElement + - InterpolatedElement + - ExprName + - InterpolatedStringLiteralElement + - InterpolatedStringLiteralElement diff --git a/crates/ruff_python_ast_integration_tests/tests/source_order.rs b/crates/ruff_python_ast_integration_tests/tests/source_order.rs index 1f01d91c7f1f08..ea257eb2794e71 100644 --- a/crates/ruff_python_ast_integration_tests/tests/source_order.rs +++ b/crates/ruff_python_ast_integration_tests/tests/source_order.rs @@ -146,6 +146,15 @@ fn f_strings() { assert_snapshot!(trace); } +#[test] +fn t_strings() { + let source = r"'pre' t'foo {bar:.{x}f} baz'"; + + let trace = trace_source_order_visitation(source); + + assert_snapshot!(trace); +} + fn trace_source_order_visitation(source: &str) -> String { let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap(); diff --git a/crates/ruff_python_ast_integration_tests/tests/visitor.rs b/crates/ruff_python_ast_integration_tests/tests/visitor.rs index c99365898be3f9..9cdc7d998a505e 100644 --- a/crates/ruff_python_ast_integration_tests/tests/visitor.rs +++ b/crates/ruff_python_ast_integration_tests/tests/visitor.rs @@ -4,13 +4,14 @@ use insta::assert_snapshot; use ruff_python_ast::visitor::{ Visitor, walk_alias, walk_bytes_literal, walk_comprehension, walk_except_handler, walk_expr, - walk_f_string, walk_f_string_element, walk_keyword, walk_match_case, walk_parameter, - walk_parameters, walk_pattern, walk_stmt, walk_string_literal, walk_type_param, walk_with_item, + walk_f_string, walk_interpolated_string_element, walk_keyword, walk_match_case, walk_parameter, + walk_parameters, walk_pattern, walk_stmt, walk_string_literal, walk_t_string, walk_type_param, + walk_with_item, }; use ruff_python_ast::{ self as ast, Alias, AnyNodeRef, BoolOp, BytesLiteral, CmpOp, Comprehension, ExceptHandler, - Expr, FString, FStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, - Stmt, StringLiteral, TypeParam, UnaryOp, WithItem, + Expr, FString, InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, + Pattern, Stmt, StringLiteral, TString, TypeParam, UnaryOp, WithItem, }; use ruff_python_parser::{Mode, ParseOptions, parse}; @@ -154,6 +155,15 @@ fn f_strings() { assert_snapshot!(trace); } +#[test] +fn t_strings() { + let source = r"'pre' t'foo {bar:.{x}f} baz'"; + + let trace = trace_visitation(source); + + assert_snapshot!(trace); +} + fn trace_visitation(source: &str) -> String { let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap(); @@ -318,9 +328,15 @@ impl Visitor<'_> for RecordVisitor { self.exit_node(); } - fn visit_f_string_element(&mut self, f_string_element: &FStringElement) { + fn visit_interpolated_string_element(&mut self, f_string_element: &InterpolatedStringElement) { self.enter_node(f_string_element); - walk_f_string_element(self, f_string_element); + walk_interpolated_string_element(self, f_string_element); + self.exit_node(); + } + + fn visit_t_string(&mut self, t_string: &TString) { + self.enter_node(t_string); + walk_t_string(self, t_string); self.exit_node(); } } diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index d435ff5c2234fa..89332057312860 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -5,9 +5,9 @@ use std::ops::Deref; use ruff_python_ast::{ self as ast, Alias, AnyStringFlags, ArgOrKeyword, BoolOp, BytesLiteralFlags, CmpOp, - Comprehension, ConversionFlag, DebugText, ExceptHandler, Expr, FStringFlags, Identifier, - MatchCase, Operator, Parameter, Parameters, Pattern, Singleton, Stmt, StringFlags, Suite, - TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, WithItem, + Comprehension, ConversionFlag, DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, + Parameter, Parameters, Pattern, Singleton, Stmt, StringFlags, Suite, TypeParam, + TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, WithItem, }; use ruff_python_ast::{ParameterWithDefault, TypeParams}; use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape}; @@ -1112,6 +1112,9 @@ impl<'a> Generator<'a> { Expr::FString(ast::ExprFString { value, .. }) => { self.unparse_f_string_value(value); } + Expr::TString(ast::ExprTString { value, .. }) => { + self.unparse_t_string_value(value); + } Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { self.unparse_string_literal_value(value); } @@ -1326,24 +1329,24 @@ impl<'a> Generator<'a> { self.unparse_string_literal(string_literal); } ast::FStringPart::FString(f_string) => { - self.unparse_f_string(&f_string.elements, f_string.flags); + self.unparse_interpolated_string(&f_string.elements, f_string.flags.into()); } } } } - fn unparse_f_string_body(&mut self, values: &[ast::FStringElement]) { + fn unparse_interpolated_string_body(&mut self, values: &[ast::InterpolatedStringElement]) { for value in values { - self.unparse_f_string_element(value); + self.unparse_interpolated_string_element(value); } } - fn unparse_f_string_expression_element( + fn unparse_interpolated_element( &mut self, val: &Expr, debug_text: Option<&DebugText>, conversion: ConversionFlag, - spec: Option<&ast::FStringFormatSpec>, + spec: Option<&ast::InterpolatedStringFormatSpec>, ) { let mut generator = Generator::new(self.indent, self.line_ending); generator.unparse_expr(val, precedence::FORMATTED_VALUE); @@ -1379,18 +1382,21 @@ impl<'a> Generator<'a> { self.p("}"); } - fn unparse_f_string_element(&mut self, element: &ast::FStringElement) { + fn unparse_interpolated_string_element(&mut self, element: &ast::InterpolatedStringElement) { match element { - ast::FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => { - self.unparse_f_string_literal_element(value); + ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { + value, + .. + }) => { + self.unparse_interpolated_string_literal_element(value); } - ast::FStringElement::Expression(ast::FStringExpressionElement { + ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, debug_text, conversion, format_spec, range: _, - }) => self.unparse_f_string_expression_element( + }) => self.unparse_interpolated_element( expression, debug_text.as_ref(), *conversion, @@ -1399,24 +1405,46 @@ impl<'a> Generator<'a> { } } - fn unparse_f_string_literal_element(&mut self, s: &str) { + fn unparse_interpolated_string_literal_element(&mut self, s: &str) { let s = s.replace('{', "{{").replace('}', "}}"); self.p(&s); } - fn unparse_f_string_specifier(&mut self, values: &[ast::FStringElement]) { - self.unparse_f_string_body(values); + fn unparse_f_string_specifier(&mut self, values: &[ast::InterpolatedStringElement]) { + self.unparse_interpolated_string_body(values); } /// Unparse `values` with [`Generator::unparse_f_string_body`], using `quote` as the preferred /// surrounding quote style. - fn unparse_f_string(&mut self, values: &[ast::FStringElement], flags: FStringFlags) { + fn unparse_interpolated_string( + &mut self, + values: &[ast::InterpolatedStringElement], + flags: AnyStringFlags, + ) { let mut generator = Generator::new(self.indent, self.line_ending); - generator.unparse_f_string_body(values); + generator.unparse_interpolated_string_body(values); let body = &generator.buffer; self.p_str_repr(body, flags); } + fn unparse_t_string_value(&mut self, value: &ast::TStringValue) { + let mut first = true; + for t_string_part in value { + self.p_delim(&mut first, " "); + match t_string_part { + ast::TStringPart::Literal(string_literal) => { + self.unparse_string_literal(string_literal); + } + ast::TStringPart::FString(f_string) => { + self.unparse_interpolated_string(&f_string.elements, f_string.flags.into()); + } + ast::TStringPart::TString(t_string) => { + self.unparse_interpolated_string(&t_string.elements, t_string.flags.into()); + } + } + } + } + fn unparse_alias(&mut self, alias: &Alias) { self.p_id(&alias.name); if let Some(asname) = &alias.asname { diff --git a/crates/ruff_python_formatter/generate.py b/crates/ruff_python_formatter/generate.py index ba8354c2aab6ba..ded6ae5b007188 100755 --- a/crates/ruff_python_formatter/generate.py +++ b/crates/ruff_python_formatter/generate.py @@ -38,9 +38,9 @@ def rustfmt(code: str) -> str: # `FStringLiteralElement`, `FStringFormatSpec` and `FStringExpressionElement` are # handled by the `FString` implementation. if node in ( - "FStringLiteralElement", - "FStringExpressionElement", - "FStringFormatSpec", + "InterpolatedStringLiteralElement", + "InterpolatedElement", + "InterpolatedStringFormatSpec", "Identifier", ): continue diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary.py index 8b27e3cd9f1a4f..439d0f1cc1459e 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary.py @@ -431,3 +431,16 @@ worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) aaaaaaaaaaa = f"hellooooooooooooooooooooooo \ worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) + +# This t-string should be flattened +xxxxxxxxxxxxxxxx = t"aaaaaaaaaaaaaaaaaaaaa { + expression } bbbbbbbbbbbbbbbbbbbbbbbb" + ( + yyyyyyyyyyyyyy + zzzzzzzzzzz +) + +# This is not a multiline t-string, but the expression is too long so it should be +# wrapped in parentheses. +t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) +aaaaaaaaaaa = t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string.py index ede789e9979782..e945211becb3bc 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string.py @@ -100,6 +100,55 @@ f"{10 + len('bar')=}" f'{10 + len("bar")=}' +############################################################################## +# T-strings +############################################################################## + +# Escape `{` and `}` when merging a t-string with a string +"a {not_a_variable}" t"b {10}" "c" + +# Join, and break expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{ +expression +}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more" + +# Join, but don't break the expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more" + +t"test{ +expression +}flat" t"can be { +joined +} together" + +aaaaaaaaaaa = t"test{ +expression +}flat" t"cean beeeeeeee { +joined +} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + + +t"single quoted '{x}'" t'double quoted "{x}"' # Same number of quotes => use preferred quote style +t"single quote ' {x}" t'double quoted "{x}"' # More double quotes => use single quotes +t"single quoted '{x}'" t'double quote " {x}"' # More single quotes => use double quotes + +# Different triple quoted strings +t"{'''test'''}" t'{"""other"""}' + +# Now with inner quotes +t"{'''test ' '''}" t'{"""other " """}' +t"{some_where_nested('''test ' ''')}" t'{"""other " """ + "more"}' +t"{b'''test ' '''}" t'{b"""other " """}' +t"{t'''test ' '''}" t'{t"""other " """}' + +# debug expressions containing quotes +t"{10 + len('bar')=}" t"{10 + len('bar')=}" +t"{10 + len('bar')=}" t'no debug{10}' t"{10 + len('bar')=}" + +# We can't safely merge this pre Python 3.12 without altering the debug expression. +t"{10 + len('bar')=}" t'{10 + len("bar")=}' + + ############################################################################## # Don't join raw strings ############################################################################## @@ -110,6 +159,9 @@ f"test" fr"test" f"test" fR"test" +t"test" tr"test" +t"test" tR"test" + ############################################################################## # Don't join triple quoted strings @@ -119,9 +171,22 @@ "single" f""""single""" +"single" t""""single""" + b"single" b"""triple""" +############################################################################## +# Don't join t-strings and f-strings +############################################################################## + +t"{interp}" f"{expr}" + +f"{expr}" t"{interp}" + +f"{expr}" "string" t"{interp}" + + ############################################################################## # Join strings in with statements ############################################################################## diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py index 642ad83c274446..c70233ea52fe1b 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py @@ -293,6 +293,155 @@ ) +############################################################# +# T-Strings +############################################################# + +# Flatten and join the t-string +aaaaaaaaaaa = t"test{ +expression}flat" t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeee" # inline + +# Parenthesize the value and join it, inline the comment +aaaaaaaaaaa = t"test{ +expression}flat" t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# Parenthesize the t-string and keep it multiline because it doesn't fit on a single line including the comment +aaaaaaaaaaa = t"test{ +expression +}flat" t"cean beeeeeeee { +joined +} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + + +# The target splits because of a magic trailing comma +# The string is joined and not parenthesized because it just fits into the line length (including comment). +a[ + aaaaaaa, + b, +] = t"ccccc{ +expression}ccccccccccc" t"cccccccccccccccccccccccccccccccccccccccccc" # comment + + +# Same but starting with a joined string. They should both result in the same formatting. +[ + aaaaaaa, + b, +] = t"ccccc{ +expression}ccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment + +# The target splits because of the magic trailing comma +# The string is **not** joined because it with the inlined comment exceeds the line length limit. +a[ + aaaaaaa, + b, +] = t"ccccc{ +expression}cccccccccccccccccccc" t"cccccccccccccccccccccccccccccccccccccccccc" # comment + + +# The target should be flat +# The string should be joined because it fits into the line length +a[ + aaaaaaa, + b +] = ( + t"ccccc{ + expression}ccccccccccc" "cccccccccccccccccccccccc" # comment +) + +# Same but starting with a joined string. They should both result in the same formatting. +a[ + aaaaaaa, + b +] = t"ccccc{ +expression}ccccccccccccccccccccccccccccccccccc" # comment + +# The target should be flat +# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit. +a[ + aaaaaaa, + b +] = t"ccccc{ +expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment + + +# Split an overlong target, but join the string if it fits +a[ + aaaaaaa, + b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{ + expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment +) + +# Split both if necessary and keep multiline +a[ + aaaaaaa, + b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{ + expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment +) + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment +) + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment + +aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment +) + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +# Don't inline t-strings with multiline debug expressions: +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + + # Trailing last-part comments a = ( @@ -374,4 +523,4 @@ return ( f"Exception in {call_back_name} when handling msg on " f"'{msg.topic}': '{msg.payload}'" # type: ignore[str-bytes-safe] -) \ No newline at end of file +) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.options.json new file mode 100644 index 00000000000000..c485014b9b9bd3 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.options.json @@ -0,0 +1 @@ +[{"target_version": "3.14"}] diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py new file mode 100644 index 00000000000000..31087f16108a65 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py @@ -0,0 +1,731 @@ +( + t'{one}' + t'{two}' +) + + +rt"Not-so-tricky \"quote" + +# Regression test for tstrings dropping comments +result_f = ( + 'Traceback (most recent call last):\n' + t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + # XXX: The following line changes depending on whether the tests + # are run through the interactive interpreter or with -m + # It also varies depending on the platform (stack size) + # Fortunately, we don't care about exactness here, so we use regex + r' \[Previous line repeated (\d+) more times\]' '\n' + 'RecursionError: maximum recursion depth exceeded\n' +) + + +# Regression for tstring dropping comments that were accidentally attached to +# an expression inside a formatted value +( + t'{1}' + # comment 1 + '' +) + +( + t'{1}' # comment 2 + t'{2}' +) + +( + t'{1}' + t'{2}' # comment 3 +) + +( + 1, ( # comment 4 + t'{2}' + ) +) + +( + ( + t'{1}' + # comment 5 + ), + 2 +) + +# https://github.com/astral-sh/ruff/issues/6841 +x = t'''a{""}b''' +y = t'''c{1}d"""e''' +z = t'''a{""}b''' t'''c{1}d"""e''' + +# T-String formatting test cases (Preview) + +# Simple expression with a mix of debug expression and comments. +x = t"{a}" +x = t"{ + a = }" +x = t"{ # comment 6 + a }" +x = t"{ # comment 7 + a = }" + +# Remove the parentheses as adding them doesn't make then fit within the line length limit. +# This is similar to how we format it before t-string formatting. +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd } cccccccccc" +) +# Here, we would use the best fit layout to put the t-string indented on the next line +# similar to the next example. +aaaaaaaaaaa = t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc" +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc" +) + +# This should never add the optional parentheses because even after adding them, the +# t-string exceeds the line length limit. +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 8 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb' = } ccccccccccccccc" + +# Multiple larger expressions which exceeds the line length limit. Here, we need to decide +# whether to split at the first or second expression. This should work similarly to the +# assignment statement formatting where we split from right to left in preview mode. +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" + +# The above example won't split but when we start introducing line breaks: +x = t"aaaaaaaaaaaa { + bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb + } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { + ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd + } eeeeeeeeeeeeee" + +# But, in case comments are present, we would split at the expression containing the +# comments: +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb # comment 10 + } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb + } cccccccccccccccccccc { # comment 11 + ddddddddddddddd } eeeeeeeeeeeeee" + +# Here, the expression part itself starts with a curly brace so we need to add an extra +# space between the opening curly brace and the expression. +x = t"{ {'x': 1, 'y': 2} }" +# Although the extra space isn't required before the ending curly brace, we add it for +# consistency. +x = t"{ {'x': 1, 'y': 2}}" +x = t"{ {'x': 1, 'y': 2} = }" +x = t"{ # comment 12 + {'x': 1, 'y': 2} }" +x = t"{ # comment 13 + {'x': 1, 'y': 2} = }" +# But, if there's a format specifier or a conversion flag then we don't need to add +# any whitespace at the end +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}!s} bbbbbb" +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}:.3f} bbbbbb" + +# But, in this case, we would split the expression itself because it exceeds the line +# length limit so we need not add the extra space. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbb', 'ccccccccccccccccccccc'} +}" +# And, split the expression itself because it exceeds the line length. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +############################################################################################# +# Quotes +############################################################################################# +t"foo 'bar' {x}" +t"foo \"bar\" {x}" +t'foo "bar" {x}' +t'foo \'bar\' {x}' +t"foo {"bar"}" + +t"single quoted '{x}' double quoted \"{x}\"" # Same number of quotes => use preferred quote style +t"single quote ' {x} double quoted \"{x}\"" # More double quotes => use single quotes +t"single quoted '{x}' double quote \" {x}" # More single quotes => use double quotes + +fr"single quotes ' {x}" # Keep double because `'` can't be escaped +fr'double quotes " {x}' # Keep single because `"` can't be escaped +fr'flip quotes {x}' # Use preferred quotes, because raw string contains now quotes. + +# Here, the formatter will remove the escapes +t"foo {'\'bar\''}" +t"foo {'\"bar\"'}" + +# Quotes inside the expressions have no impact on the quote selection of the outer string. +# Required so that the following two examples result in the same formatting. +t'foo {10 + len("bar")}' +t"foo {10 + len('bar')}" + +# Pre 312, preserve the outer quotes if the t-string contains quotes in the debug expression +t'foo {10 + len("bar")=}' +t'''foo {10 + len('''bar''')=}''' +t'''foo {10 + len('bar')=}''' # Fine to change the quotes because it uses triple quotes + +# Triple-quoted strings +# It's ok to use the same quote char for the inner string if it's single-quoted. +t"""test {'inner'}""" +t"""test {"inner"}""" +# But if the inner string is also triple-quoted then we should preserve the existing quotes. +t"""test {'''inner'''}""" + +# It's not okay to change the quote style if the inner string is triple quoted and contains a quote. +t'{"""other " """}' +t'{"""other " """ + "more"}' +t'{b"""other " """}' +t'{t"""other " """}' + +t"""test {t'inner {'''inner inner'''}'}""" +t"""test {t'''inner {"""inner inner"""}'''}""" + +# Magic trailing comma +# +# The expression formatting will result in breaking it across multiple lines with a +# trailing comma but as the expression isn't already broken, we will remove all the line +# breaks which results in the trailing comma being present. This test case makes sure +# that the trailing comma is removed as well. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee']} aaaaaaa" + +# And, if the trailing comma is already present, we still need to remove it. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee',]} aaaaaaa" + +# Keep this Multiline by breaking it at the square brackets. +t"""aaaaaa {[ + xxxxxxxx, + yyyyyyyy, +]} ccc""" + +# Add the magic trailing comma because the elements don't fit within the line length limit +# when collapsed. +t"aaaaaa {[ + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + yyyyyyyyyyyy +]} ccccccc" + +# Remove the parentheses because they aren't required +xxxxxxxxxxxxxxx = ( + t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbb { + xxxxxxxxxxx # comment 14 + + yyyyyyyyyy + } dddddddddd" +) + +# Comments + +# No comments should be dropped! +t"{ # comment 15 + # comment 16 + foo # comment 17 + # comment 18 +}" # comment 19 +# comment 20 + +# Single-quoted t-strings with a format specificer can be multiline +t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee" + +# But, if it's triple-quoted then we can't or the format specificer will have a +# trailing newline +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee""" + +# But, we can break the ones which don't have a format specifier +t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { + xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" + +# Throw in a random comment in it but surprise, this is not a comment but just a text +# which is part of the format specifier +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment +} cccccccccc""" +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment} cccccccccc""" + +# Conversion flags +# +# This is not a valid Python code because of the additional whitespace between the `!` +# and conversion type. But, our parser isn't strict about this. This should probably be +# removed once we have a strict parser. +x = t"aaaaaaaaa { x ! r }" + +# Even in the case of debug expressions, we only need to preserve the whitespace within +# the expression part of the replacement field. +x = t"aaaaaaaaa { x = ! r }" + +# Combine conversion flags with format specifiers +x = t"{x = ! s + :>0 + + }" +# This is interesting. There can be a comment after the format specifier but only if it's +# on it's own line. Refer to https://github.com/astral-sh/ruff/pull/7787 for more details. +# We'll format is as trailing comments. +x = t"{x !s + :>0 + # comment 21 + }" + +x = t""" +{ # comment 22 + x = :.0{y # comment 23 + }f}""" + +# Here, the debug expression is in a nested t-string so we should start preserving +# whitespaces from that point onwards. This means we should format the outer t-string. +x = t"""{"foo " + # comment 24 + t"{ x = + + }" # comment 25 + } + """ + +# Mix of various features. +t"{ # comment 26 + foo # after foo + :>{ + x # after x + } + # comment 27 + # comment 28 +} woah {x}" + +# Assignment statement + +# Even though this t-string has multiline expression, thus allowing us to break it at the +# curly braces, the t-string fits on a single line if it's moved inside the parentheses. +# We should prefer doing that instead. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" + +# Same as above +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +# Similar to the previous example, but the t-string will exceed the line length limit, +# we shouldn't add any parentheses here. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +# Same as above but with an inline comment. The t-string should be formatted inside the +# parentheses and the comment should be part of the line inside the parentheses. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment + +# Similar to the previous example but this time parenthesizing doesn't work because it +# exceeds the line length. So, avoid parenthesizing this t-string. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment loooooooong + +# Similar to the previous example but we start with the parenthesized layout. This should +# remove the parentheses and format the t-string on a single line. This shows that the +# final layout for the formatter is same for this and the previous case. The only +# difference is that in the previous case the expression is already mulitline which means +# the formatter can break it further at the curly braces. +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" # comment loooooooong +) + +# The following t-strings are going to break because of the trailing comma so we should +# avoid using the best fit layout and instead use the default layout. +# left-to-right +aaaa = t"aaaa {[ + 1, 2, +]} bbbb" +# right-to-left +aaaa, bbbb = t"aaaa {[ + 1, 2, +]} bbbb" + +# Using the right-to-left assignment statement variant. +aaaaaaaaaaaaaaaaaa, bbbbbbbbbbb = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment + +# Here, the t-string layout is flat but it exceeds the line length limit. This shouldn't +# try the custom best fit layout because the t-string doesn't have any split points. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) +# Same as above but without the parentheses to test that it gets formatted to the same +# layout as the previous example. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" + +# But, the following t-string does have a split point because of the multiline expression. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccc + dddddddddddddddddddddddddddd} ddddddddddddddddddd" +) + +# This is an implicitly concatenated t-string but it cannot be joined because otherwise +# it'll exceed the line length limit. So, the two t-strings will be inside parentheses +# instead and the inline comment should be outside the parentheses. +a = t"test{ + expression +}flat" t"can be { + joined +} togethereeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# Similar to the above example but this fits within the line length limit. +a = t"test{ + expression +}flat" t"can be { + joined +} togethereeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# The following test cases are adopted from implicit string concatenation but for a +# single t-string instead. + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.g. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment +) + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment +) + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}moreeeeeeeeeeeeeeeeeetest" # comment +) + +# Don't inline t-strings with multiline debug expressions or format specifiers +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment +) + +# This is not a multiline t-string even though it has a newline after the format specifier. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest" # comment +) + +# The newline is only considered when it's a tripled-quoted t-string. +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment +) + +# Remove the parentheses here +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{[a, b, + # comment + ]}moee" # comment +) +# ... but not here because of the ownline comment +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{[a, b, + ]}moee" + # comment +) + +# t-strings in other positions + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if ( + t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + }" +): pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { # comment + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if ( + t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + }" +): + pass + +if t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +# For loops +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeee": + pass + +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +for a in ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# With statements +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeee": + pass + +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +with ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# Assert statements +assert t"aaaaaaaaa{ + expression}bbbbbbbbbbbb", t"cccccccccc{ + expression}dddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccccccccc{ + expression}dddddddddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccccccccc{expression}dddddddddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccc{expression}dddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccc{expression}dddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccccccccccccccccc { + expression} dddddddddddddddddddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"cccccccccccccccccccccccccccccccc {expression} ddddddddddddddddddddddddddddddddddddd" + +# t-strings as a single argument to a call expression to test whether it's huggable or not. +call(t"{ + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"{ + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"{ # comment + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"""aaaaaaaaaaaaaaaa bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}""") + +call(t"""aaaaaaaaaaaaaaaa bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }""") + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }""") + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment + }""") + +call( + t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment + }""" +) + +call(t"{ + aaaaaa + + '''test + more''' +}") + +# Indentation + +# What should be the indentation? +# https://github.com/astral-sh/ruff/discussions/9785#discussioncomment-8470590 +if indent0: + if indent1: + if indent2: + foo = t"""hello world +hello { + t"aaaaaaa { + [ + 'aaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbb', + 'ccccccccccccccccccccc', + 'ddddddddddddddddddddd' + ] + } bbbbbbbb" + + [ + 'aaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbb', + 'ccccccccccccccccccccc', + 'ddddddddddddddddddddd' + ] + } -------- +""" + + +# Implicit concatenated t-string containing quotes +_ = ( + 'This string should change its quotes to double quotes' + t'This string uses double quotes in an expression {"it's a quote"}' + t'This t-string does not use any quotes.' +) + +# Regression test for https://github.com/astral-sh/ruff/issues/14487 +t"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc" + +# Regression test for https://github.com/astral-sh/ruff/issues/14778 +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a' if True else ""}" +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a' if True else ""}" + +# Quotes reuse +t"{'a'}" + +# 312+, it's okay to change the outer quotes even when there's a debug expression using the same quotes +t'foo {10 + len("bar")=}' +t'''foo {10 + len("""bar""")=}''' + +# 312+, it's okay to change the quotes here without creating an invalid t-string +t'{"""other " """}' +t'{"""other " """ + "more"}' +t'{b"""other " """}' +t'{t"""other " """}' + + +# Regression tests for https://github.com/astral-sh/ruff/issues/13935 +t'{1: hy "user"}' +t'{1:hy "user"}' +t'{1: abcd "{1}" }' +t'{1: abcd "{'aa'}" }' +t'{1=: "abcd {'aa'}}' +t'{x:a{z:hy "user"}} \'\'\'' + +# Changing the outer quotes is fine because the format-spec is in a nested expression. +t'{t'{z=:hy "user"}'} \'\'\'' + + +# We have to be careful about changing the quotes if the t-string has a debug expression because it is inserted verbatim. +t'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error +t'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes +t'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped +# Don't change the quotes in the following cases: +t'{x=:hy "user"} \'\'\'' +t'{x=:a{y:hy "user"}} \'\'\'' +t'{x=:a{y:{z:hy "user"}}} \'\'\'' +t'{x:a{y=:{z:hy "user"}}} \'\'\'' + +# This is fine because the debug expression and format spec are in a nested expression + +t"""{1=: "this" is fine}""" +t'''{1=: "this" is fine}''' # Change quotes to double quotes because they're preferred +t'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part. + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15459 +print(t"{ {1, 2, 3} - {2} }") +print(t"{ {1: 2}.keys() }") +print(t"{({1, 2, 3}) - ({2})}") +print(t"{1, 2, {3} }") +print(t"{(1, 2, {3})}") + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(t"{ {}, }") # A single item tuple gets parenthesized +print(t"{ {}.values(), }") +print(t"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print(t"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + {}, 1, +}") + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15536 +print(t"{ {}, 1, }") diff --git a/crates/ruff_python_formatter/src/builders.rs b/crates/ruff_python_formatter/src/builders.rs index 6d7c79e33dce10..8d7aeb502b230f 100644 --- a/crates/ruff_python_formatter/src/builders.rs +++ b/crates/ruff_python_formatter/src/builders.rs @@ -205,14 +205,14 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> { pub(crate) fn finish(&mut self) -> FormatResult<()> { self.result.and_then(|()| { - // Don't add a magic trailing comma when formatting an f-string expression + // Don't add a magic trailing comma when formatting an f-string or t-string expression // that always must be flat because the `expand_parent` forces enclosing // groups to expand, e.g. `print(f"{(a,)} ")` would format the f-string in // flat mode but the `print` call gets expanded because of the `expand_parent`. if self .fmt .context() - .f_string_state() + .interpolated_string_state() .can_contain_line_breaks() == Some(false) { diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 43ebbfa68e8790..23cc3ee9966489 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -314,15 +314,14 @@ fn handle_enclosed_comment<'a>( AnyNodeRef::StmtImportFrom(import_from) => handle_import_from_comment(comment, import_from), AnyNodeRef::StmtWith(with_) => handle_with_comment(comment, with_), AnyNodeRef::ExprCall(_) => handle_call_comment(comment), - AnyNodeRef::ExprStringLiteral(_) => { - if let Some(AnyNodeRef::FString(fstring)) = comment.enclosing_parent() { - CommentPlacement::dangling(fstring, comment) - } else { - CommentPlacement::Default(comment) - } - } + AnyNodeRef::ExprStringLiteral(_) => match comment.enclosing_parent() { + Some(AnyNodeRef::FString(fstring)) => CommentPlacement::dangling(fstring, comment), + Some(AnyNodeRef::TString(tstring)) => CommentPlacement::dangling(tstring, comment), + _ => CommentPlacement::Default(comment), + }, AnyNodeRef::FString(fstring) => CommentPlacement::dangling(fstring, comment), - AnyNodeRef::FStringExpressionElement(_) => { + AnyNodeRef::TString(tstring) => CommentPlacement::dangling(tstring, comment), + AnyNodeRef::InterpolatedElement(_) => { // Handle comments after the format specifier (should be rare): // // ```python @@ -336,7 +335,8 @@ fn handle_enclosed_comment<'a>( if matches!( comment.preceding_node(), Some( - AnyNodeRef::FStringExpressionElement(_) | AnyNodeRef::FStringLiteralElement(_) + AnyNodeRef::InterpolatedElement(_) + | AnyNodeRef::InterpolatedStringLiteralElement(_) ) ) { CommentPlacement::trailing(comment.enclosing_node(), comment) @@ -344,6 +344,7 @@ fn handle_enclosed_comment<'a>( handle_bracketed_end_of_line_comment(comment, source) } } + AnyNodeRef::ExprList(_) | AnyNodeRef::ExprSet(_) | AnyNodeRef::ExprListComp(_) diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 946deaf1a6f954..b1b3a7941a3ec9 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -7,7 +7,7 @@ use ruff_python_parser::Tokens; use crate::PyFormatOptions; use crate::comments::Comments; -use crate::other::f_string_element::FStringExpressionElementContext; +use crate::other::interpolated_string_element::InterpolatedElementContext; pub struct PyFormatContext<'a> { options: PyFormatOptions, @@ -25,8 +25,8 @@ pub struct PyFormatContext<'a> { /// quote style that is inverted from the one here in order to ensure that /// the formatted Python code will be valid. docstring: Option, - /// The state of the formatter with respect to f-strings. - f_string_state: FStringState, + /// The state of the formatter with respect to f-strings and t-strings. + interpolated_string_state: InterpolatedStringState, } impl<'a> PyFormatContext<'a> { @@ -44,7 +44,7 @@ impl<'a> PyFormatContext<'a> { node_level: NodeLevel::TopLevel(TopLevelStatementPosition::Other), indent_level: IndentLevel::new(0), docstring: None, - f_string_state: FStringState::Outside, + interpolated_string_state: InterpolatedStringState::Outside, } } @@ -97,12 +97,15 @@ impl<'a> PyFormatContext<'a> { } } - pub(crate) fn f_string_state(&self) -> FStringState { - self.f_string_state + pub(crate) fn interpolated_string_state(&self) -> InterpolatedStringState { + self.interpolated_string_state } - pub(crate) fn set_f_string_state(&mut self, f_string_state: FStringState) { - self.f_string_state = f_string_state; + pub(crate) fn set_interpolated_string_state( + &mut self, + interpolated_string_state: InterpolatedStringState, + ) { + self.interpolated_string_state = interpolated_string_state; } /// Returns `true` if preview mode is enabled. @@ -135,24 +138,24 @@ impl Debug for PyFormatContext<'_> { } #[derive(Clone, Copy, Debug, Default)] -pub(crate) enum FStringState { +pub(crate) enum InterpolatedStringState { /// The formatter is inside an f-string expression element i.e., between the /// curly brace in `f"foo {x}"`. /// /// The containing `FStringContext` is the surrounding f-string context. - InsideExpressionElement(FStringExpressionElementContext), + InsideInterpolatedElement(InterpolatedElementContext), /// The formatter is outside an f-string. #[default] Outside, } -impl FStringState { +impl InterpolatedStringState { pub(crate) fn can_contain_line_breaks(self) -> Option { match self { - FStringState::InsideExpressionElement(context) => { + InterpolatedStringState::InsideInterpolatedElement(context) => { Some(context.can_contain_line_breaks()) } - FStringState::Outside => None, + InterpolatedStringState::Outside => None, } } } @@ -375,25 +378,25 @@ where } } -pub(crate) struct WithFStringState<'a, B, D> +pub(crate) struct WithInterpolatedStringState<'a, B, D> where D: DerefMut, B: Buffer>, { buffer: D, - saved_location: FStringState, + saved_location: InterpolatedStringState, } -impl<'a, B, D> WithFStringState<'a, B, D> +impl<'a, B, D> WithInterpolatedStringState<'a, B, D> where D: DerefMut, B: Buffer>, { - pub(crate) fn new(expr_location: FStringState, mut buffer: D) -> Self { + pub(crate) fn new(expr_location: InterpolatedStringState, mut buffer: D) -> Self { let context = buffer.state_mut().context_mut(); - let saved_location = context.f_string_state(); + let saved_location = context.interpolated_string_state(); - context.set_f_string_state(expr_location); + context.set_interpolated_string_state(expr_location); Self { buffer, @@ -402,7 +405,7 @@ where } } -impl<'a, B, D> Deref for WithFStringState<'a, B, D> +impl<'a, B, D> Deref for WithInterpolatedStringState<'a, B, D> where D: DerefMut, B: Buffer>, @@ -414,7 +417,7 @@ where } } -impl<'a, B, D> DerefMut for WithFStringState<'a, B, D> +impl<'a, B, D> DerefMut for WithInterpolatedStringState<'a, B, D> where D: DerefMut, B: Buffer>, @@ -424,7 +427,7 @@ where } } -impl<'a, B, D> Drop for WithFStringState<'a, B, D> +impl<'a, B, D> Drop for WithInterpolatedStringState<'a, B, D> where D: DerefMut, B: Buffer>, @@ -433,6 +436,6 @@ where self.buffer .state_mut() .context_mut() - .set_f_string_state(self.saved_location); + .set_interpolated_string_state(self.saved_location); } } diff --git a/crates/ruff_python_formatter/src/expression/expr_f_string.rs b/crates/ruff_python_formatter/src/expression/expr_f_string.rs index 90db025694443e..ad559e102aced5 100644 --- a/crates/ruff_python_formatter/src/expression/expr_f_string.rs +++ b/crates/ruff_python_formatter/src/expression/expr_f_string.rs @@ -3,7 +3,7 @@ use ruff_python_ast::{AnyNodeRef, ExprFString, StringLike}; use crate::expression::parentheses::{ NeedsParentheses, OptionalParentheses, in_parentheses_only_group, }; -use crate::other::f_string::FStringLayout; +use crate::other::interpolated_string::InterpolatedStringLayout; use crate::prelude::*; use crate::string::StringLikeExtensions; use crate::string::implicit::{ @@ -41,7 +41,11 @@ impl NeedsParentheses for ExprFString { if let Some(fstring_part) = self.as_single_part_fstring() { // The f-string is not implicitly concatenated if StringLike::FString(self).is_multiline(context) - || FStringLayout::from_f_string(fstring_part, context.source()).is_multiline() + || InterpolatedStringLayout::from_interpolated_string_elements( + &fstring_part.elements, + context.source(), + ) + .is_multiline() { OptionalParentheses::Never } else { diff --git a/crates/ruff_python_formatter/src/expression/expr_t_string.rs b/crates/ruff_python_formatter/src/expression/expr_t_string.rs new file mode 100644 index 00000000000000..d937338baf6f4b --- /dev/null +++ b/crates/ruff_python_formatter/src/expression/expr_t_string.rs @@ -0,0 +1,59 @@ +use ruff_python_ast::{AnyNodeRef, ExprTString, StringLike}; + +use crate::expression::parentheses::{ + NeedsParentheses, OptionalParentheses, in_parentheses_only_group, +}; +use crate::other::interpolated_string::InterpolatedStringLayout; +use crate::prelude::*; +use crate::string::StringLikeExtensions; +use crate::string::implicit::{ + FormatImplicitConcatenatedString, FormatImplicitConcatenatedStringFlat, +}; + +#[derive(Default)] +pub struct FormatExprTString; + +impl FormatNodeRule for FormatExprTString { + fn fmt_fields(&self, item: &ExprTString, f: &mut PyFormatter) -> FormatResult<()> { + if let Some(t_string) = item.as_single_part_tstring() { + t_string.format().fmt(f) + } else { + // Always join tstrings that aren't parenthesized and thus, are always on a single line. + if !f.context().node_level().is_parenthesized() { + if let Some(format_flat) = + FormatImplicitConcatenatedStringFlat::new(item.into(), f.context()) + { + return format_flat.fmt(f); + } + } + + in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f) + } + } +} + +impl NeedsParentheses for ExprTString { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + context: &PyFormatContext, + ) -> OptionalParentheses { + if let Some(tstring_part) = self.as_single_part_tstring() { + // The t-string is not implicitly concatenated + if StringLike::TString(self).is_multiline(context) + || InterpolatedStringLayout::from_interpolated_string_elements( + &tstring_part.elements, + context.source(), + ) + .is_multiline() + { + OptionalParentheses::Never + } else { + OptionalParentheses::BestFit + } + } else { + // The t-string is implicitly concatenated + OptionalParentheses::Multiline + } + } +} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index aa5be138135a12..025c4e75139fe7 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -50,6 +50,7 @@ pub(crate) mod expr_slice; pub(crate) mod expr_starred; pub(crate) mod expr_string_literal; pub(crate) mod expr_subscript; +pub(crate) mod expr_t_string; pub(crate) mod expr_tuple; pub(crate) mod expr_unary_op; pub(crate) mod expr_yield; @@ -94,6 +95,7 @@ impl FormatRule> for FormatExpr { Expr::Compare(expr) => expr.format().fmt(f), Expr::Call(expr) => expr.format().fmt(f), Expr::FString(expr) => expr.format().fmt(f), + Expr::TString(expr) => expr.format().fmt(f), Expr::StringLiteral(expr) => expr.format().fmt(f), Expr::BytesLiteral(expr) => expr.format().fmt(f), Expr::NumberLiteral(expr) => expr.format().fmt(f), @@ -282,6 +284,7 @@ fn format_with_parentheses_comments( Expr::Compare(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Call(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::FString(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), + Expr::TString(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::StringLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::BytesLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::NumberLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), @@ -480,6 +483,7 @@ impl NeedsParentheses for Expr { Expr::Compare(expr) => expr.needs_parentheses(parent, context), Expr::Call(expr) => expr.needs_parentheses(parent, context), Expr::FString(expr) => expr.needs_parentheses(parent, context), + Expr::TString(expr) => expr.needs_parentheses(parent, context), Expr::StringLiteral(expr) => expr.needs_parentheses(parent, context), Expr::BytesLiteral(expr) => expr.needs_parentheses(parent, context), Expr::NumberLiteral(expr) => expr.needs_parentheses(parent, context), @@ -775,6 +779,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { // Terminal nodes or nodes that wrap a sub-expression (where the sub expression can never be at the end). Expr::FString(_) + | Expr::TString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) @@ -1126,6 +1131,7 @@ pub(crate) fn is_expression_huggable(expr: &Expr, context: &PyFormatContext) -> | Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::FString(_) + | Expr::TString(_) | Expr::EllipsisLiteral(_) => false, } } @@ -1221,6 +1227,7 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) - // String like literals can expand if they are implicit concatenated. Expr::FString(fstring) => fstring.value.is_implicit_concatenated(), + Expr::TString(tstring) => tstring.value.is_implicit_concatenated(), Expr::StringLiteral(string) => string.value.is_implicit_concatenated(), Expr::BytesLiteral(bytes) => bytes.value.is_implicit_concatenated(), @@ -1278,6 +1285,7 @@ pub(crate) fn left_most<'expr>( | Expr::Name(_) | Expr::Starred(_) | Expr::FString(_) + | Expr::TString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) diff --git a/crates/ruff_python_formatter/src/generated.rs b/crates/ruff_python_formatter/src/generated.rs index 9bb79d80accaff..3abc77a5388196 100644 --- a/crates/ruff_python_formatter/src/generated.rs +++ b/crates/ruff_python_formatter/src/generated.rs @@ -1562,6 +1562,42 @@ impl<'ast> IntoFormat> for ast::ExprFString { } } +impl FormatRule> + for crate::expression::expr_t_string::FormatExprTString +{ + #[inline] + fn fmt(&self, node: &ast::ExprTString, f: &mut PyFormatter) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl<'ast> AsFormat> for ast::ExprTString { + type Format<'a> = FormatRefWithRule< + 'a, + ast::ExprTString, + crate::expression::expr_t_string::FormatExprTString, + PyFormatContext<'ast>, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::expression::expr_t_string::FormatExprTString::default(), + ) + } +} +impl<'ast> IntoFormat> for ast::ExprTString { + type Format = FormatOwnedWithRule< + ast::ExprTString, + crate::expression::expr_t_string::FormatExprTString, + PyFormatContext<'ast>, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::expression::expr_t_string::FormatExprTString::default(), + ) + } +} + impl FormatRule> for crate::expression::expr_string_literal::FormatExprStringLiteral { @@ -2963,6 +2999,34 @@ impl<'ast> IntoFormat> for ast::FString { } } +impl FormatRule> for crate::other::t_string::FormatTString { + #[inline] + fn fmt(&self, node: &ast::TString, f: &mut PyFormatter) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl<'ast> AsFormat> for ast::TString { + type Format<'a> = FormatRefWithRule< + 'a, + ast::TString, + crate::other::t_string::FormatTString, + PyFormatContext<'ast>, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, crate::other::t_string::FormatTString::default()) + } +} +impl<'ast> IntoFormat> for ast::TString { + type Format = FormatOwnedWithRule< + ast::TString, + crate::other::t_string::FormatTString, + PyFormatContext<'ast>, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, crate::other::t_string::FormatTString::default()) + } +} + impl FormatRule> for crate::other::string_literal::FormatStringLiteral { diff --git a/crates/ruff_python_formatter/src/other/f_string.rs b/crates/ruff_python_formatter/src/other/f_string.rs index c423f39f34b0d0..a02cd4a0867a9c 100644 --- a/crates/ruff_python_formatter/src/other/f_string.rs +++ b/crates/ruff_python_formatter/src/other/f_string.rs @@ -1,12 +1,9 @@ -use ruff_formatter::write; -use ruff_python_ast::{AnyStringFlags, FString, StringFlags}; -use ruff_source_file::LineRanges; -use ruff_text_size::Ranged; - +use super::interpolated_string_element::FormatInterpolatedStringElement; +use crate::other::interpolated_string::{InterpolatedStringContext, InterpolatedStringLayout}; use crate::prelude::*; use crate::string::{StringNormalizer, StringQuotes}; - -use super::f_string_element::FormatFStringElement; +use ruff_formatter::write; +use ruff_python_ast::{FString, StringFlags}; /// Formats an f-string which is part of a larger f-string expression. /// @@ -21,9 +18,12 @@ impl FormatNodeRule for FormatFString { let string_kind = normalizer.choose_quotes(item.into()).flags(); - let context = FStringContext::new( + let context = InterpolatedStringContext::new( string_kind, - FStringLayout::from_f_string(item, f.context().source()), + InterpolatedStringLayout::from_interpolated_string_elements( + &item.elements, + f.context().source(), + ), ); // Starting prefix and quote @@ -31,78 +31,10 @@ impl FormatNodeRule for FormatFString { write!(f, [string_kind.prefix(), quotes])?; for element in &item.elements { - FormatFStringElement::new(element, context).fmt(f)?; + FormatInterpolatedStringElement::new(element, context).fmt(f)?; } // Ending quote quotes.fmt(f) } } - -#[derive(Clone, Copy, Debug)] -pub(crate) struct FStringContext { - /// The string flags of the enclosing f-string part. - enclosing_flags: AnyStringFlags, - layout: FStringLayout, -} - -impl FStringContext { - pub(crate) const fn new(flags: AnyStringFlags, layout: FStringLayout) -> Self { - Self { - enclosing_flags: flags, - layout, - } - } - - pub(crate) fn flags(self) -> AnyStringFlags { - self.enclosing_flags - } - - pub(crate) const fn layout(self) -> FStringLayout { - self.layout - } -} - -#[derive(Copy, Clone, Debug)] -pub(crate) enum FStringLayout { - /// Original f-string is flat. - /// Don't break expressions to keep the string flat. - Flat, - /// Original f-string has multiline expressions in the replacement fields. - /// Allow breaking expressions across multiple lines. - Multiline, -} - -impl FStringLayout { - pub(crate) fn from_f_string(f_string: &FString, source: &str) -> Self { - // Heuristic: Allow breaking the f-string expressions across multiple lines - // only if there already is at least one multiline expression. This puts the - // control in the hands of the user to decide if they want to break the - // f-string expressions across multiple lines or not. This is similar to - // how Prettier does it for template literals in JavaScript. - // - // If it's single quoted f-string and it contains a multiline expression, then we - // assume that the target version of Python supports it (3.12+). If there are comments - // used in any of the expression of the f-string, then it's always going to be multiline - // and we assume that the target version of Python supports it (3.12+). - // - // Reference: https://prettier.io/docs/en/next/rationale.html#template-literals - if f_string - .elements - .expressions() - .any(|expr| source.contains_line_break(expr.range())) - { - Self::Multiline - } else { - Self::Flat - } - } - - pub(crate) const fn is_flat(self) -> bool { - matches!(self, FStringLayout::Flat) - } - - pub(crate) const fn is_multiline(self) -> bool { - matches!(self, FStringLayout::Multiline) - } -} diff --git a/crates/ruff_python_formatter/src/other/interpolated_string.rs b/crates/ruff_python_formatter/src/other/interpolated_string.rs new file mode 100644 index 00000000000000..7a0c8b3c1c116d --- /dev/null +++ b/crates/ruff_python_formatter/src/other/interpolated_string.rs @@ -0,0 +1,73 @@ +use ruff_python_ast::{AnyStringFlags, InterpolatedStringElements}; +use ruff_source_file::LineRanges; +use ruff_text_size::Ranged; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct InterpolatedStringContext { + /// The string flags of the enclosing f/t-string part. + enclosing_flags: AnyStringFlags, + layout: InterpolatedStringLayout, +} + +impl InterpolatedStringContext { + pub(crate) const fn new(flags: AnyStringFlags, layout: InterpolatedStringLayout) -> Self { + Self { + enclosing_flags: flags, + layout, + } + } + + pub(crate) fn flags(self) -> AnyStringFlags { + self.enclosing_flags + } + + pub(crate) const fn layout(self) -> InterpolatedStringLayout { + self.layout + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) enum InterpolatedStringLayout { + /// Original f/t-string is flat. + /// Don't break expressions to keep the string flat. + Flat, + /// Original f/t-string has multiline expressions in the replacement fields. + /// Allow breaking expressions across multiple lines. + Multiline, +} + +impl InterpolatedStringLayout { + // Heuristic: Allow breaking the f/t-string expressions across multiple lines + // only if there already is at least one multiline expression. This puts the + // control in the hands of the user to decide if they want to break the + // f/t-string expressions across multiple lines or not. This is similar to + // how Prettier does it for template literals in JavaScript. + // + // If it's single quoted f-string and it contains a multiline expression, then we + // assume that the target version of Python supports it (3.12+). If there are comments + // used in any of the expression of the f-string, then it's always going to be multiline + // and we assume that the target version of Python supports it (3.12+). + // + // Reference: https://prettier.io/docs/en/next/rationale.html#template-literals + pub(crate) fn from_interpolated_string_elements( + elements: &InterpolatedStringElements, + source: &str, + ) -> Self { + if elements + .interpolations() + .any(|expr| source.contains_line_break(expr.range())) + { + Self::Multiline + } else { + Self::Flat + } + } + + pub(crate) const fn is_flat(self) -> bool { + matches!(self, InterpolatedStringLayout::Flat) + } + + pub(crate) const fn is_multiline(self) -> bool { + matches!(self, InterpolatedStringLayout::Multiline) + } +} diff --git a/crates/ruff_python_formatter/src/other/f_string_element.rs b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs similarity index 80% rename from crates/ruff_python_formatter/src/other/f_string_element.rs rename to crates/ruff_python_formatter/src/other/interpolated_string_element.rs index 8418d5edf079b5..e0b53331eac599 100644 --- a/crates/ruff_python_formatter/src/other/f_string_element.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs @@ -2,42 +2,47 @@ use std::borrow::Cow; use ruff_formatter::{Buffer, RemoveSoftLinesBuffer, format_args, write}; use ruff_python_ast::{ - AnyStringFlags, ConversionFlag, Expr, FStringElement, FStringExpressionElement, - FStringLiteralElement, StringFlags, + AnyStringFlags, ConversionFlag, Expr, InterpolatedElement, InterpolatedStringElement, + InterpolatedStringLiteralElement, StringFlags, }; use ruff_text_size::{Ranged, TextSlice}; use crate::comments::{dangling_open_parenthesis_comments, trailing_comments}; -use crate::context::{FStringState, NodeLevel, WithFStringState, WithNodeLevel}; +use crate::context::{ + InterpolatedStringState, NodeLevel, WithInterpolatedStringState, WithNodeLevel, +}; use crate::expression::left_most; use crate::prelude::*; use crate::string::normalize_string; use crate::verbatim::verbatim_text; -use super::f_string::FStringContext; +use super::interpolated_string::InterpolatedStringContext; /// Formats an f-string element which is either a literal or a formatted expression. /// /// This delegates the actual formatting to the appropriate formatter. -pub(crate) struct FormatFStringElement<'a> { - element: &'a FStringElement, - context: FStringContext, +pub(crate) struct FormatInterpolatedStringElement<'a> { + element: &'a InterpolatedStringElement, + context: InterpolatedStringContext, } -impl<'a> FormatFStringElement<'a> { - pub(crate) fn new(element: &'a FStringElement, context: FStringContext) -> Self { +impl<'a> FormatInterpolatedStringElement<'a> { + pub(crate) fn new( + element: &'a InterpolatedStringElement, + context: InterpolatedStringContext, + ) -> Self { Self { element, context } } } -impl Format> for FormatFStringElement<'_> { +impl Format> for FormatInterpolatedStringElement<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { match self.element { - FStringElement::Literal(string_literal) => { + InterpolatedStringElement::Literal(string_literal) => { FormatFStringLiteralElement::new(string_literal, self.context.flags()).fmt(f) } - FStringElement::Expression(expression) => { - FormatFStringExpressionElement::new(expression, self.context).fmt(f) + InterpolatedStringElement::Interpolation(expression) => { + FormatInterpolatedElement::new(expression, self.context).fmt(f) } } } @@ -45,13 +50,16 @@ impl Format> for FormatFStringElement<'_> { /// Formats an f-string literal element. pub(crate) struct FormatFStringLiteralElement<'a> { - element: &'a FStringLiteralElement, + element: &'a InterpolatedStringLiteralElement, /// Flags of the enclosing F-string part fstring_flags: AnyStringFlags, } impl<'a> FormatFStringLiteralElement<'a> { - pub(crate) fn new(element: &'a FStringLiteralElement, fstring_flags: AnyStringFlags) -> Self { + pub(crate) fn new( + element: &'a InterpolatedStringLiteralElement, + fstring_flags: AnyStringFlags, + ) -> Self { Self { element, fstring_flags, @@ -72,16 +80,16 @@ impl Format> for FormatFStringLiteralElement<'_> { /// Context representing an f-string expression element. #[derive(Clone, Copy, Debug)] -pub(crate) struct FStringExpressionElementContext { +pub(crate) struct InterpolatedElementContext { /// The context of the parent f-string containing this expression element. - parent_context: FStringContext, + parent_context: InterpolatedStringContext, /// Indicates whether this expression element has format specifier or not. has_format_spec: bool, } -impl FStringExpressionElementContext { - /// Returns the [`FStringContext`] containing this expression element. - pub(crate) fn f_string(self) -> FStringContext { +impl InterpolatedElementContext { + /// Returns the [`InterpolatedStringContext`] containing this expression element. + pub(crate) fn interpolated_string(self) -> InterpolatedStringContext { self.parent_context } @@ -113,16 +121,19 @@ impl FStringExpressionElementContext { } /// Formats an f-string expression element. -pub(crate) struct FormatFStringExpressionElement<'a> { - element: &'a FStringExpressionElement, - context: FStringExpressionElementContext, +pub(crate) struct FormatInterpolatedElement<'a> { + element: &'a InterpolatedElement, + context: InterpolatedElementContext, } -impl<'a> FormatFStringExpressionElement<'a> { - pub(crate) fn new(element: &'a FStringExpressionElement, context: FStringContext) -> Self { +impl<'a> FormatInterpolatedElement<'a> { + pub(crate) fn new( + element: &'a InterpolatedElement, + context: InterpolatedStringContext, + ) -> Self { Self { element, - context: FStringExpressionElementContext { + context: InterpolatedElementContext { parent_context: context, has_format_spec: element.format_spec.is_some(), }, @@ -130,9 +141,9 @@ impl<'a> FormatFStringExpressionElement<'a> { } } -impl Format> for FormatFStringExpressionElement<'_> { +impl Format> for FormatInterpolatedElement<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let FStringExpressionElement { + let InterpolatedElement { expression, debug_text, conversion, @@ -214,8 +225,8 @@ impl Format> for FormatFStringExpressionElement<'_> { let item = format_with(|f: &mut PyFormatter| { // Update the context to be inside the f-string expression element. - let f = &mut WithFStringState::new( - FStringState::InsideExpressionElement(self.context), + let f = &mut WithInterpolatedStringState::new( + InterpolatedStringState::InsideInterpolatedElement(self.context), f, ); @@ -233,7 +244,11 @@ impl Format> for FormatFStringExpressionElement<'_> { token(":").fmt(f)?; for element in &format_spec.elements { - FormatFStringElement::new(element, self.context.f_string()).fmt(f)?; + FormatInterpolatedStringElement::new( + element, + self.context.interpolated_string(), + ) + .fmt(f)?; } // These trailing comments can only occur if the format specifier is diff --git a/crates/ruff_python_formatter/src/other/mod.rs b/crates/ruff_python_formatter/src/other/mod.rs index b55b1a70f6e594..33f14c4f7bd410 100644 --- a/crates/ruff_python_formatter/src/other/mod.rs +++ b/crates/ruff_python_formatter/src/other/mod.rs @@ -7,12 +7,14 @@ pub(crate) mod decorator; pub(crate) mod elif_else_clause; pub(crate) mod except_handler_except_handler; pub(crate) mod f_string; -pub(crate) mod f_string_element; pub(crate) mod identifier; +pub(crate) mod interpolated_string; +pub(crate) mod interpolated_string_element; pub(crate) mod keyword; pub(crate) mod match_case; pub(crate) mod parameter; pub(crate) mod parameter_with_default; pub(crate) mod parameters; pub(crate) mod string_literal; +pub(crate) mod t_string; pub(crate) mod with_item; diff --git a/crates/ruff_python_formatter/src/other/t_string.rs b/crates/ruff_python_formatter/src/other/t_string.rs new file mode 100644 index 00000000000000..098c668c51ad84 --- /dev/null +++ b/crates/ruff_python_formatter/src/other/t_string.rs @@ -0,0 +1,40 @@ +use super::interpolated_string_element::FormatInterpolatedStringElement; +use crate::other::interpolated_string::{InterpolatedStringContext, InterpolatedStringLayout}; +use crate::prelude::*; +use crate::string::{StringNormalizer, StringQuotes}; +use ruff_formatter::write; +use ruff_python_ast::{StringFlags, TString}; + +/// Formats a t-string which is part of a larger t-string expression. +/// +/// For example, this would be used to format the t-string part in `"foo" t"bar {x}"` +/// or the standalone t-string in `t"foo {x} bar"`. +#[derive(Default)] +pub struct FormatTString; + +impl FormatNodeRule for FormatTString { + fn fmt_fields(&self, item: &TString, f: &mut PyFormatter) -> FormatResult<()> { + let normalizer = StringNormalizer::from_context(f.context()); + + let string_kind = normalizer.choose_quotes(item.into()).flags(); + + let context = InterpolatedStringContext::new( + string_kind, + InterpolatedStringLayout::from_interpolated_string_elements( + &item.elements, + f.context().source(), + ), + ); + + // Starting prefix and quote + let quotes = StringQuotes::from(string_kind); + write!(f, [string_kind.prefix(), quotes])?; + + for element in &item.elements { + FormatInterpolatedStringElement::new(element, context).fmt(f)?; + } + + // Ending quote + quotes.fmt(f) + } +} diff --git a/crates/ruff_python_formatter/src/pattern/mod.rs b/crates/ruff_python_formatter/src/pattern/mod.rs index fa197378a112e6..e255d593595528 100644 --- a/crates/ruff_python_formatter/src/pattern/mod.rs +++ b/crates/ruff_python_formatter/src/pattern/mod.rs @@ -293,6 +293,7 @@ impl<'a> CanOmitOptionalParenthesesVisitor<'a> { // F-strings are allowed according to python's grammar but fail with a syntax error at runtime. // That's why we need to support them for formatting. Expr::FString(_) | + Expr::TString(_)| Expr::NumberLiteral(_) | Expr::Attribute(_) | Expr::UnaryOp(_) => { // require no state update other than visit_pattern does. } @@ -306,7 +307,7 @@ impl<'a> CanOmitOptionalParenthesesVisitor<'a> { _ => { debug_assert!( false, - "Unsupported expression in pattern mach value: {:?}", + "Unsupported expression in pattern match value: {:?}", value.value ); } diff --git a/crates/ruff_python_formatter/src/range.rs b/crates/ruff_python_formatter/src/range.rs index 2f2f12644ad6b5..61043061b5dd4f 100644 --- a/crates/ruff_python_formatter/src/range.rs +++ b/crates/ruff_python_formatter/src/range.rs @@ -659,10 +659,11 @@ impl Format> for FormatEnclosingNode<'_> { | AnyNodeRef::ExprYieldFrom(_) | AnyNodeRef::ExprCompare(_) | AnyNodeRef::ExprCall(_) - | AnyNodeRef::FStringExpressionElement(_) - | AnyNodeRef::FStringLiteralElement(_) - | AnyNodeRef::FStringFormatSpec(_) + | AnyNodeRef::InterpolatedElement(_) + | AnyNodeRef::InterpolatedStringLiteralElement(_) + | AnyNodeRef::InterpolatedStringFormatSpec(_) | AnyNodeRef::ExprFString(_) + | AnyNodeRef::ExprTString(_) | AnyNodeRef::ExprStringLiteral(_) | AnyNodeRef::ExprBytesLiteral(_) | AnyNodeRef::ExprNumberLiteral(_) @@ -679,6 +680,7 @@ impl Format> for FormatEnclosingNode<'_> { | AnyNodeRef::ExprIpyEscapeCommand(_) | AnyNodeRef::FString(_) | AnyNodeRef::StringLiteral(_) + | AnyNodeRef::TString(_) | AnyNodeRef::PatternMatchValue(_) | AnyNodeRef::PatternMatchSingleton(_) | AnyNodeRef::PatternMatchSequence(_) diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index 4aad6a47a2a23e..54a2eec8bb71c7 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -1,6 +1,6 @@ use ruff_formatter::{FormatError, RemoveSoftLinesBuffer, format_args, write}; use ruff_python_ast::{ - AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, Operator, StmtAssign, StringLike, + AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, Operator, StmtAssign, StringLike, TString, TypeParams, }; @@ -17,7 +17,7 @@ use crate::expression::{ can_omit_optional_parentheses, has_own_parentheses, has_parentheses, maybe_parenthesize_expression, }; -use crate::other::f_string::FStringLayout; +use crate::other::interpolated_string::InterpolatedStringLayout; use crate::statement::trailing_semicolon; use crate::string::StringLikeExtensions; use crate::string::implicit::{ @@ -291,15 +291,16 @@ impl Format> for FormatStatementsLastExpression<'_> { let can_inline_comment = should_inline_comments(value, *statement, f.context()); let string_like = StringLike::try_from(*value).ok(); - let format_f_string = - string_like.and_then(|string| format_f_string_assignment(string, f.context())); + let format_interpolated_string = string_like + .and_then(|string| format_interpolated_string_assignment(string, f.context())); + let format_implicit_flat = string_like.and_then(|string| { FormatImplicitConcatenatedStringFlat::new(string, f.context()) }); if !can_inline_comment && format_implicit_flat.is_none() - && format_f_string.is_none() + && format_interpolated_string.is_none() { return maybe_parenthesize_expression( value, @@ -351,7 +352,7 @@ impl Format> for FormatStatementsLastExpression<'_> { let string = flat.string(); let flat = format_with(|f| { - if string.is_fstring() { + if string.is_interpolated_string() { let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); write!(buffer, [flat]) @@ -361,7 +362,7 @@ impl Format> for FormatStatementsLastExpression<'_> { }) .memoized(); - // F-String containing an expression with a magic trailing comma, a comment, or a + // F-string or T-string containing an expression with a magic trailing comma, a comment, or a // multiline debug expression should never be joined. Use the default layout. // ```python // aaaa = f"abcd{[ @@ -369,7 +370,7 @@ impl Format> for FormatStatementsLastExpression<'_> { // 2, // ]}" "more" // ``` - if string.is_fstring() && flat.inspect(f)?.will_break() { + if string.is_interpolated_string() && flat.inspect(f)?.will_break() { inline_comments.mark_unformatted(); return write!( @@ -446,24 +447,23 @@ impl Format> for FormatStatementsLastExpression<'_> { best_fitting![single_line, joined_parenthesized, implicit_expanded] .with_mode(BestFittingMode::AllLines) .fmt(f)?; - } else if let Some(format_f_string) = format_f_string { + } else if let Some(format_interpolated_string) = format_interpolated_string { inline_comments.mark_formatted(); - let f_string_flat = format_with(|f| { + let interpolated_string_flat = format_with(|f| { let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); - write!(buffer, [format_f_string.format()]) + write!(buffer, [format_interpolated_string]) }) .memoized(); - - // F-String containing an expression with a magic trailing comma, a comment, or a - // multiline debug expression should never be joined. Use the default layout. + // F/T-String containing an interpolation with a magic trailing comma, a comment, or a + // multiline debug interpolation should never be joined. Use the default layout. // ```python // aaaa = f"aaaa {[ // 1, 2, // ]} bbbb" // ``` - if f_string_flat.inspect(f)?.will_break() { + if interpolated_string_flat.inspect(f)?.will_break() { inline_comments.mark_unformatted(); return write!( @@ -482,23 +482,26 @@ impl Format> for FormatStatementsLastExpression<'_> { // expression}moreeeeeeeeeeeeeeeee" // ``` - // Flatten the f-string. + // Flatten the f/t-string. // ```python // aaaaaaaaaaaaaaaaaa = f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" // ``` let single_line = - format_with(|f| write!(f, [f_string_flat, inline_comments])); + format_with(|f| write!(f, [interpolated_string_flat, inline_comments])); - // Parenthesize the f-string and flatten the f-string. + // Parenthesize the t-string and flatten the t-string. // ```python // aaaaaaaaaaaaaaaaaa = ( - // f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" + // t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" // ) // ``` let joined_parenthesized = format_with(|f| { group(&format_args![ token("("), - soft_block_indent(&format_args![f_string_flat, inline_comments]), + soft_block_indent(&format_args![ + interpolated_string_flat, + inline_comments + ]), token(")"), ]) .with_id(Some(group_id)) @@ -506,19 +509,24 @@ impl Format> for FormatStatementsLastExpression<'_> { .fmt(f) }); - // Avoid flattening or parenthesizing the f-string, keep the original - // f-string formatting. + // Avoid flattening or parenthesizing the f/t-string, keep the original + // f/t-string formatting. // ```python - // aaaaaaaaaaaaaaaaaa = f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + // aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ // expression // }moreeeeeeeeeeeeeeeee" // ``` - let format_f_string = - format_with(|f| write!(f, [format_f_string.format(), inline_comments])); + let format_interpolated_string = format_with(|f| { + write!(f, [format_interpolated_string, inline_comments]) + }); - best_fitting![single_line, joined_parenthesized, format_f_string] - .with_mode(BestFittingMode::AllLines) - .fmt(f)?; + best_fitting![ + single_line, + joined_parenthesized, + format_interpolated_string + ] + .with_mode(BestFittingMode::AllLines) + .fmt(f)?; } else { best_fit_parenthesize(&format_once(|f| { inline_comments.mark_formatted(); @@ -559,17 +567,16 @@ impl Format> for FormatStatementsLastExpression<'_> { let should_inline_comments = should_inline_comments(value, *statement, f.context()); let string_like = StringLike::try_from(*value).ok(); - let format_f_string = - string_like.and_then(|string| format_f_string_assignment(string, f.context())); + let format_interpolated_string = string_like + .and_then(|string| format_interpolated_string_assignment(string, f.context())); let format_implicit_flat = string_like.and_then(|string| { FormatImplicitConcatenatedStringFlat::new(string, f.context()) }); - // Use the normal `maybe_parenthesize_layout` for splittable `value`s. if !should_inline_comments && !should_non_inlineable_use_best_fit(value, *statement, f.context()) && format_implicit_flat.is_none() - && format_f_string.is_none() + && format_interpolated_string.is_none() { return write!( f, @@ -593,7 +600,7 @@ impl Format> for FormatStatementsLastExpression<'_> { // Don't inline comments for attribute and call expressions for black compatibility let inline_comments = if should_inline_comments || format_implicit_flat.is_some() - || format_f_string.is_some() + || format_interpolated_string.is_some() { OptionalParenthesesInlinedComments::new( &expression_comments, @@ -633,7 +640,9 @@ impl Format> for FormatStatementsLastExpression<'_> { // This is mainly a performance optimisation that avoids unnecessary memoization // and using the costly `BestFitting` layout if it is already known that only the last variant // can ever fit because the left breaks. - if format_implicit_flat.is_none() && format_f_string.is_none() && last_target_breaks + if format_implicit_flat.is_none() + && format_interpolated_string.is_none() + && last_target_breaks { return write!( f, @@ -650,7 +659,7 @@ impl Format> for FormatStatementsLastExpression<'_> { let format_value = format_with(|f| { if let Some(format_implicit_flat) = format_implicit_flat.as_ref() { - if format_implicit_flat.string().is_fstring() { + if format_implicit_flat.string().is_interpolated_string() { // Remove any soft line breaks emitted by the f-string formatting. // This is important when formatting f-strings as part of an assignment right side // because `best_fit_parenthesize` will otherwise still try to break inner @@ -660,11 +669,13 @@ impl Format> for FormatStatementsLastExpression<'_> { } else { format_implicit_flat.fmt(f) } - } else if let Some(format_f_string) = format_f_string.as_ref() { + } else if let Some(format_interpolated_string) = + format_interpolated_string.as_ref() + { // Similar to above, remove any soft line breaks emitted by the f-string // formatting. let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); - write!(buffer, [format_f_string.format()]) + write!(buffer, [format_interpolated_string]) } else { value.format().with_options(Parentheses::Never).fmt(f) } @@ -766,7 +777,7 @@ impl Format> for FormatStatementsLastExpression<'_> { // 2, // ]}" "more" // ``` - if format_implicit_flat.string().is_fstring() + if format_implicit_flat.string().is_interpolated_string() && format_value.inspect(f)?.will_break() { inline_comments.mark_unformatted(); @@ -905,12 +916,12 @@ impl Format> for FormatStatementsLastExpression<'_> { .with_mode(BestFittingMode::AllLines) .fmt(f) } - } else if let Some(format_f_string) = &format_f_string { - // F-String containing an expression with a magic trailing comma, a comment, or a + } else if let Some(format_interpolated_string) = &format_interpolated_string { + // F/T-String containing an interpolation with a magic trailing comma, a comment, or a // multiline debug expression should never be joined. Use the default layout. // // ```python - // aaaa, bbbb = f"aaaa {[ + // aaaa, bbbb = t"aaaa {[ // 1, 2, // ]} bbbb" // ``` @@ -933,40 +944,46 @@ impl Format> for FormatStatementsLastExpression<'_> { ); } - let format_f_string = - format_with(|f| write!(f, [format_f_string.format(), inline_comments])) + let format_interpolated_string = + format_with(|f| write!(f, [format_interpolated_string, inline_comments])) .memoized(); // Considering the following initial source: // // ```python // aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( - // f"aaaaaaaaaaaaaaaaaaa { + // t"aaaaaaaaaaaaaaaaaaa { // aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" // ) // ``` // - // Keep the target flat, and use the regular f-string formatting. + // Keep the target flat, and use the regular f/t-string formatting. // // ```python - // aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = f"aaaaaaaaaaaaaaaaaaa { + // aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = t"aaaaaaaaaaaaaaaaaaa { // aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc // } ddddddddddddddddddd" // ``` - let flat_target_regular_f_string = format_with(|f| { + let flat_target_regular_interpolated_string = format_with(|f| { write!( f, - [last_target, space(), operator, space(), format_f_string] + [ + last_target, + space(), + operator, + space(), + format_interpolated_string + ] ) }); - // Expand the parent and parenthesize the flattened f-string. + // Expand the parent and parenthesize the flattened f/t-string. // // ```python // aaaaaaaaaaaa[ // "bbbbbbbbbbbbbbbb" // ] = ( - // f"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" + // t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" // ) // ``` let split_target_value_parenthesized_flat = format_with(|f| { @@ -988,16 +1005,16 @@ impl Format> for FormatStatementsLastExpression<'_> { ) }); - // Expand the parent, and use the regular f-string formatting. + // Expand the parent, and use the regular f/t-string formatting. // // ```python // aaaaaaaaaaaa[ // "bbbbbbbbbbbbbbbb" - // ] = f"aaaaaaaaaaaaaaaaaaa { + // ] = t"aaaaaaaaaaaaaaaaaaa { // aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc // } ddddddddddddddddddd" // ``` - let split_target_regular_f_string = format_with(|f| { + let split_target_regular_interpolated_string = format_with(|f| { write!( f, [ @@ -1005,7 +1022,7 @@ impl Format> for FormatStatementsLastExpression<'_> { space(), operator, space(), - format_f_string, + format_interpolated_string, ] ) }); @@ -1016,7 +1033,7 @@ impl Format> for FormatStatementsLastExpression<'_> { best_fitting![ split_target_flat_value, split_target_value_parenthesized_flat, - split_target_regular_f_string, + split_target_regular_interpolated_string, ] .with_mode(BestFittingMode::AllLines) .fmt(f) @@ -1024,10 +1041,10 @@ impl Format> for FormatStatementsLastExpression<'_> { best_fitting![ single_line, flat_target_parenthesize_value, - flat_target_regular_f_string, + flat_target_regular_interpolated_string, split_target_flat_value, split_target_value_parenthesized_flat, - split_target_regular_f_string, + split_target_regular_interpolated_string, ] .with_mode(BestFittingMode::AllLines) .fmt(f) @@ -1045,13 +1062,31 @@ impl Format> for FormatStatementsLastExpression<'_> { } } -/// Formats an f-string that is at the value position of an assignment statement. +#[derive(Debug, Copy, Clone)] +enum InterpolatedString<'a> { + FString(&'a FString), + TString(&'a TString), +} + +impl Format> for InterpolatedString<'_> { + fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { + match self { + InterpolatedString::FString(string) => string.format().fmt(f), + InterpolatedString::TString(string) => string.format().fmt(f), + } + } +} + +/// Formats an f/t-string that is at the value position of an assignment statement. /// -/// This is just a wrapper around [`FormatFString`] while considering a special case when the -/// f-string is at an assignment statement's value position. +/// For legibility, we discuss only the case of f-strings below, but the +/// same comments apply to t-strings. /// -/// This is necessary to prevent an instability where an f-string contains a multiline expression -/// and the f-string fits on the line, but only when it's surrounded by parentheses. +/// This is just a wrapper around [`FormatFString`] while considering a special +/// case when the f-string is at an assignment statement's value position. +/// This is necessary to prevent an instability where an f-string contains a +/// multiline expression and the f-string fits on the line, but only when it's +/// surrounded by parentheses. /// /// ```python /// aaaaaaaaaaaaaaaaaa = f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ @@ -1099,30 +1134,40 @@ impl Format> for FormatStatementsLastExpression<'_> { /// The reason for this is because (a) f-string already has a multiline expression thus it tries to /// break the expression and (b) the `BestFit` layout doesn't considers the layout where the /// multiline f-string isn't surrounded by parentheses. -fn format_f_string_assignment<'a>( +fn format_interpolated_string_assignment<'a>( string: StringLike<'a>, context: &PyFormatContext, -) -> Option<&'a FString> { - let StringLike::FString(expr) = string else { - return None; +) -> Option> { + let (interpolated_string, elements) = match string { + StringLike::TString(expr) => { + let t_string = expr.as_single_part_tstring()?; + (InterpolatedString::TString(t_string), &t_string.elements) + } + StringLike::FString(expr) => { + let f_string = expr.as_single_part_fstring()?; + (InterpolatedString::FString(f_string), &f_string.elements) + } + _ => { + return None; + } }; - let f_string = expr.as_single_part_fstring()?; - - // If the f-string is flat, there are no breakpoints from which it can be made multiline. - // This is the case when the f-string has no expressions or if it does then the expressions + // If the f/t-string is flat, there are no breakpoints from which it can be made multiline. + // This is the case when the f/t-string has no expressions or if it does then the expressions // are flat (no newlines). - if FStringLayout::from_f_string(f_string, context.source()).is_flat() { + if InterpolatedStringLayout::from_interpolated_string_elements(elements, context.source()) + .is_flat() + { return None; } - // This checks whether the f-string is multi-line and it can *never* be flattened. Thus, + // This checks whether the f/t-string is multi-line and it can *never* be flattened. Thus, // it's useless to try the flattened layout. if string.is_multiline(context) { return None; } - Some(f_string) + Some(interpolated_string) } #[derive(Debug, Default)] @@ -1277,6 +1322,9 @@ fn should_inline_comments( Expr::FString(fstring) => { fstring.needs_parentheses(parent, context) == OptionalParentheses::BestFit } + Expr::TString(tstring) => { + tstring.needs_parentheses(parent, context) == OptionalParentheses::BestFit + } _ => false, } } diff --git a/crates/ruff_python_formatter/src/string/implicit.rs b/crates/ruff_python_formatter/src/string/implicit.rs index fd6145c03342c6..fc25d030aa8c24 100644 --- a/crates/ruff_python_formatter/src/string/implicit.rs +++ b/crates/ruff_python_formatter/src/string/implicit.rs @@ -2,28 +2,31 @@ use itertools::Itertools; use ruff_formatter::{FormatContext, format_args, write}; use ruff_python_ast::str::{Quote, TripleQuotes}; use ruff_python_ast::str_prefix::{ - AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, + AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, TStringPrefix, +}; +use ruff_python_ast::{ + AnyStringFlags, FString, InterpolatedStringElement, StringFlags, StringLike, StringLikePart, + TString, }; -use ruff_python_ast::{AnyStringFlags, FStringElement, StringFlags, StringLike, StringLikePart}; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use std::borrow::Cow; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space; -use crate::other::f_string::{FStringContext, FStringLayout}; -use crate::other::f_string_element::FormatFStringExpressionElement; +use crate::other::interpolated_string::{InterpolatedStringContext, InterpolatedStringLayout}; +use crate::other::interpolated_string_element::FormatInterpolatedElement; use crate::prelude::*; use crate::string::docstring::needs_chaperone_space; use crate::string::normalize::{ QuoteMetadata, is_fstring_with_quoted_debug_expression, - is_fstring_with_quoted_format_spec_and_debug, is_fstring_with_triple_quoted_literal_expression_containing_quotes, + is_interpolated_string_with_quoted_format_spec_and_debug, }; use crate::string::{StringLikeExtensions, StringNormalizer, StringQuotes, normalize_string}; /// Formats any implicitly concatenated string. This could be any valid combination -/// of string, bytes or f-string literals. +/// of string, bytes, f-string, or t-string literals. pub(crate) struct FormatImplicitConcatenatedString<'a> { string: StringLike<'a>, } @@ -98,6 +101,7 @@ impl Format> for FormatImplicitConcatenatedStringExpanded<'_ StringLikePart::String(part) => part.format().fmt(f), StringLikePart::Bytes(bytes_literal) => bytes_literal.format().fmt(f), StringLikePart::FString(part) => part.format().fmt(f), + StringLikePart::TString(part) => part.format().fmt(f), }); let part_comments = comments.leading_dangling_trailing(part); @@ -138,7 +142,7 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { let first_part = string.parts().next()?; - // The string is either a regular string, f-string, or bytes string. + // The string is either a regular string, f-string, t-string, or bytes string. let normalizer = StringNormalizer::from_context(context); // Some if a part requires preserving its quotes. @@ -164,9 +168,34 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { return None; } - if let StringLikePart::FString(fstring) = part { - if context.options().target_version().supports_pep_701() { - if is_fstring_with_quoted_format_spec_and_debug(fstring, context) { + match part { + StringLikePart::FString(fstring) => { + if matches!(string, StringLike::TString(_)) { + // Don't concatenate t-strings and f-strings + return None; + } + if context.options().target_version().supports_pep_701() { + if is_interpolated_string_with_quoted_format_spec_and_debug( + &fstring.elements, + fstring.flags.into(), + context, + ) { + if preserve_quotes_requirement + .is_some_and(|quote| quote != part.flags().quote_style()) + { + return None; + } + preserve_quotes_requirement = Some(part.flags().quote_style()); + } + } + // Avoid invalid syntax for pre Python 312: + // * When joining parts that have debug expressions with quotes: `f"{10 + len('bar')=}" f'{10 + len("bar")=}' + // * When joining parts that contain triple quoted strings with quotes: `f"{'''test ' '''}" f'{"""other " """}'` + else if is_fstring_with_quoted_debug_expression(fstring, context) + || is_fstring_with_triple_quoted_literal_expression_containing_quotes( + fstring, context, + ) + { if preserve_quotes_requirement .is_some_and(|quote| quote != part.flags().quote_style()) { @@ -175,21 +204,21 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { preserve_quotes_requirement = Some(part.flags().quote_style()); } } - // Avoid invalid syntax for pre Python 312: - // * When joining parts that have debug expressions with quotes: `f"{10 + len('bar')=}" f'{10 + len("bar")=}' - // * When joining parts that contain triple quoted strings with quotes: `f"{'''test ' '''}" f'{"""other " """}'` - else if is_fstring_with_quoted_debug_expression(fstring, context) - || is_fstring_with_triple_quoted_literal_expression_containing_quotes( - fstring, context, - ) - { - if preserve_quotes_requirement - .is_some_and(|quote| quote != part.flags().quote_style()) - { - return None; + StringLikePart::TString(tstring) => { + if is_interpolated_string_with_quoted_format_spec_and_debug( + &tstring.elements, + tstring.flags.into(), + context, + ) { + if preserve_quotes_requirement + .is_some_and(|quote| quote != part.flags().quote_style()) + { + return None; + } + preserve_quotes_requirement = Some(part.flags().quote_style()); } - preserve_quotes_requirement = Some(part.flags().quote_style()); } + StringLikePart::Bytes(_) | StringLikePart::String(_) => {} } } @@ -203,6 +232,7 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { StringLike::String(_) => AnyStringPrefix::Regular(StringLiteralPrefix::Empty), StringLike::Bytes(_) => AnyStringPrefix::Bytes(ByteStringPrefix::Regular), StringLike::FString(_) => AnyStringPrefix::Format(FStringPrefix::Regular), + StringLike::TString(_) => AnyStringPrefix::Template(TStringPrefix::Regular), }; let quote = if let Some(quote) = preserve_quotes_requirement { @@ -287,7 +317,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { FormatLiteralContent { range: part.content_range(), flags: self.flags, - is_fstring: false, + is_interpolated_string: false, trim_start: first_non_empty && self.docstring, trim_end: self.docstring && parts.peek().is_none(), } @@ -300,28 +330,32 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { } } - StringLikePart::FString(f_string) => { - for element in &f_string.elements { + StringLikePart::FString(FString { elements, .. }) + | StringLikePart::TString(TString { elements, .. }) => { + for element in elements { match element { - FStringElement::Literal(literal) => { + InterpolatedStringElement::Literal(literal) => { FormatLiteralContent { range: literal.range(), flags: self.flags, - is_fstring: true, + is_interpolated_string: true, trim_end: false, trim_start: false, } .fmt(f)?; } // Formatting the expression here and in the expanded version is safe **only** - // because we assert that the f-string never contains any comments. - FStringElement::Expression(expression) => { - let context = FStringContext::new( + // because we assert that the f/t-string never contains any comments. + InterpolatedStringElement::Interpolation(expression) => { + let context = InterpolatedStringContext::new( self.flags, - FStringLayout::from_f_string(f_string, f.context().source()), + InterpolatedStringLayout::from_interpolated_string_elements( + elements, + f.context().source(), + ), ); - FormatFStringExpressionElement::new(expression, context).fmt(f)?; + FormatInterpolatedElement::new(expression, context).fmt(f)?; } } } @@ -336,7 +370,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { struct FormatLiteralContent { range: TextRange, flags: AnyStringFlags, - is_fstring: bool, + is_interpolated_string: bool, trim_start: bool, trim_end: bool, } @@ -348,7 +382,7 @@ impl Format> for FormatLiteralContent { content, 0, self.flags, - self.flags.is_f_string() && !self.is_fstring, + self.flags.is_interpolated_string() && !self.is_interpolated_string, ); // Trim the start and end of the string if it's the first or last part of a docstring. diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index 52fd92ad80249f..6f9fc5b33e1dab 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -85,57 +85,55 @@ pub(crate) trait StringLikeExtensions { impl StringLikeExtensions for ast::StringLike<'_> { fn is_multiline(&self, context: &PyFormatContext) -> bool { + // Helper for f-string and t-string parts + fn contains_line_break_or_comments( + elements: &ast::InterpolatedStringElements, + context: &PyFormatContext, + triple_quotes: TripleQuotes, + ) -> bool { + elements.iter().any(|element| match element { + ast::InterpolatedStringElement::Literal(literal) => { + triple_quotes.is_yes() && context.source().contains_line_break(literal.range()) + } + ast::InterpolatedStringElement::Interpolation(expression) => { + // Expressions containing comments can't be joined. + // + // Format specifiers needs to be checked as well. For example, the + // following should be considered multiline because the literal + // part of the format specifier contains a newline at the end + // (`.3f\n`): + // + // ```py + // x = f"hello {a + b + c + d:.3f + // } world" + // ``` + context.comments().contains_comments(expression.into()) + || expression.format_spec.as_deref().is_some_and(|spec| { + contains_line_break_or_comments(&spec.elements, context, triple_quotes) + }) + || expression.debug_text.as_ref().is_some_and(|debug_text| { + memchr2(b'\n', b'\r', debug_text.leading.as_bytes()).is_some() + || memchr2(b'\n', b'\r', debug_text.trailing.as_bytes()).is_some() + }) + } + }) + } + self.parts().any(|part| match part { StringLikePart::String(_) | StringLikePart::Bytes(_) => { part.flags().is_triple_quoted() && context.source().contains_line_break(part.range()) } - StringLikePart::FString(f_string) => { - fn contains_line_break_or_comments( - elements: &ast::FStringElements, - context: &PyFormatContext, - triple_quotes: TripleQuotes, - ) -> bool { - elements.iter().any(|element| match element { - ast::FStringElement::Literal(literal) => { - triple_quotes.is_yes() - && context.source().contains_line_break(literal.range()) - } - ast::FStringElement::Expression(expression) => { - // Expressions containing comments can't be joined. - // - // Format specifiers needs to be checked as well. For example, the - // following should be considered multiline because the literal - // part of the format specifier contains a newline at the end - // (`.3f\n`): - // - // ```py - // x = f"hello {a + b + c + d:.3f - // } world" - // ``` - context.comments().contains_comments(expression.into()) - || expression.format_spec.as_deref().is_some_and(|spec| { - contains_line_break_or_comments( - &spec.elements, - context, - triple_quotes, - ) - }) - || expression.debug_text.as_ref().is_some_and(|debug_text| { - memchr2(b'\n', b'\r', debug_text.leading.as_bytes()).is_some() - || memchr2(b'\n', b'\r', debug_text.trailing.as_bytes()) - .is_some() - }) - } - }) - } - - contains_line_break_or_comments( - &f_string.elements, - context, - f_string.flags.triple_quotes(), - ) - } + StringLikePart::FString(f_string) => contains_line_break_or_comments( + &f_string.elements, + context, + f_string.flags.triple_quotes(), + ), + StringLikePart::TString(t_string) => contains_line_break_or_comments( + &t_string.elements, + context, + t_string.flags.triple_quotes(), + ), }) } } diff --git a/crates/ruff_python_formatter/src/string/normalize.rs b/crates/ruff_python_formatter/src/string/normalize.rs index b8f2902d85aa1d..1e694a66ff1508 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -5,16 +5,15 @@ use std::iter::FusedIterator; use ruff_formatter::FormatContext; use ruff_python_ast::visitor::source_order::SourceOrderVisitor; use ruff_python_ast::{ - AnyStringFlags, BytesLiteral, FString, FStringElement, FStringElements, FStringFlags, + AnyStringFlags, BytesLiteral, FString, InterpolatedStringElement, InterpolatedStringElements, StringFlags, StringLikePart, StringLiteral, - str::{Quote, TripleQuotes}, }; use ruff_text_size::{Ranged, TextRange, TextSlice}; use crate::QuoteStyle; -use crate::context::FStringState; +use crate::context::InterpolatedStringState; use crate::prelude::*; -use crate::string::StringQuotes; +use crate::string::{Quote, StringQuotes, TripleQuotes}; pub(crate) struct StringNormalizer<'a, 'src> { preferred_quote_style: Option, @@ -47,11 +46,11 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { .unwrap_or(self.context.options().quote_style()); let supports_pep_701 = self.context.options().target_version().supports_pep_701(); - // For f-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't. - if let FStringState::InsideExpressionElement(parent_context) = self.context.f_string_state() + // For f-strings and t-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't. + if let InterpolatedStringState::InsideInterpolatedElement(parent_context) = + self.context.interpolated_string_state() { - let parent_flags = parent_context.f_string().flags(); - + let parent_flags = parent_context.interpolated_string().flags(); if !parent_flags.is_triple_quoted() || string.flags().is_triple_quoted() { // This logic is even necessary when using preserve and the target python version doesn't support PEP701 because // we might end up joining two f-strings that have different quote styles, in which case we need to alternate the quotes @@ -67,33 +66,49 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { return QuoteStyle::Preserve; } - // There are cases where it is necessary to preserve the quotes to prevent an invalid f-string. - if let StringLikePart::FString(fstring) = string { - // There are two cases where it's necessary to preserve the quotes if the - // target version is pre 3.12 and the part is an f-string. - if !supports_pep_701 { - // An f-string expression contains a debug text with a quote character - // because the formatter will emit the debug expression **exactly** the - // same as in the source text. - if is_fstring_with_quoted_debug_expression(fstring, self.context) { - return QuoteStyle::Preserve; + // There are cases where it is necessary to preserve the quotes to prevent an invalid f-string or t-string. + match string { + StringLikePart::FString(fstring) => { + // There are two cases where it's necessary to preserve the quotes if the + // target version is pre 3.12 and the part is an f-string. + if !supports_pep_701 { + // An f-string expression contains a debug text with a quote character + // because the formatter will emit the debug expression **exactly** the + // same as in the source text. + if is_fstring_with_quoted_debug_expression(fstring, self.context) { + return QuoteStyle::Preserve; + } + + // An f-string expression that contains a triple quoted string literal + // expression that contains a quote. + if is_fstring_with_triple_quoted_literal_expression_containing_quotes( + fstring, + self.context, + ) { + return QuoteStyle::Preserve; + } } - // An f-string expression that contains a triple quoted string literal - // expression that contains a quote. - if is_fstring_with_triple_quoted_literal_expression_containing_quotes( - fstring, + // An f-string expression element contains a debug text and the corresponding + // format specifier has a literal element with a quote character. + if is_interpolated_string_with_quoted_format_spec_and_debug( + &fstring.elements, + fstring.flags.into(), self.context, ) { return QuoteStyle::Preserve; } } - - // An f-string expression element contains a debug text and the corresponding - // format specifier has a literal element with a quote character. - if is_fstring_with_quoted_format_spec_and_debug(fstring, self.context) { - return QuoteStyle::Preserve; + StringLikePart::TString(tstring) => { + if is_interpolated_string_with_quoted_format_spec_and_debug( + &tstring.elements, + tstring.flags.into(), + self.context, + ) { + return QuoteStyle::Preserve; + } } + _ => {} } // Per PEP 8, always prefer double quotes for triple-quoted strings. @@ -172,7 +187,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { // The preferred quote style is single or double quotes, and the string contains a quote or // another character that may require escaping (Ok(preferred_quote), Some(first_quote_or_normalized_char_offset)) => { - let metadata = if string.is_fstring() { + let metadata = if string.is_interpolated_string() { QuoteMetadata::from_part(string, self.context, preferred_quote) } else { QuoteMetadata::from_str( @@ -262,9 +277,19 @@ impl QuoteMetadata { StringLikePart::FString(fstring) => { let metadata = QuoteMetadata::from_str("", part.flags(), preferred_quote); - metadata.merge_fstring_elements( + metadata.merge_interpolated_string_elements( &fstring.elements, - fstring.flags, + fstring.flags.into(), + context, + preferred_quote, + ) + } + StringLikePart::TString(tstring) => { + let metadata = QuoteMetadata::from_str("", part.flags(), preferred_quote); + + metadata.merge_interpolated_string_elements( + &tstring.elements, + tstring.flags.into(), context, preferred_quote, ) @@ -369,7 +394,7 @@ impl QuoteMetadata { }) } - /// For f-strings, only consider the quotes inside string-literals but ignore + /// For f-strings and t-strings, only consider the quotes inside string-literals but ignore /// quotes inside expressions (except inside the format spec). This allows both the outer and the nested literals /// to make the optimal local-choice to reduce the total number of quotes necessary. /// This doesn't require any pre 312 special handling because an expression @@ -377,10 +402,10 @@ impl QuoteMetadata { /// ```python /// f"{'escaping a quote like this \" is a syntax error pre 312'}" /// ``` - fn merge_fstring_elements( + fn merge_interpolated_string_elements( self, - elements: &FStringElements, - flags: FStringFlags, + elements: &InterpolatedStringElements, + flags: AnyStringFlags, context: &PyFormatContext, preferred_quote: Quote, ) -> Self { @@ -388,19 +413,19 @@ impl QuoteMetadata { for element in elements { match element { - FStringElement::Literal(literal) => { + InterpolatedStringElement::Literal(literal) => { merged = merged .merge(&QuoteMetadata::from_str( context.source().slice(literal), - flags.into(), + flags, preferred_quote, )) .expect("Merge to succeed because all parts have the same flags"); } - FStringElement::Expression(expression) => { + InterpolatedStringElement::Interpolation(expression) => { if let Some(spec) = expression.format_spec.as_deref() { if expression.debug_text.is_none() { - merged = merged.merge_fstring_elements( + merged = merged.merge_interpolated_string_elements( &spec.elements, flags, context, @@ -879,7 +904,7 @@ pub(super) fn is_fstring_with_quoted_debug_expression( fstring: &FString, context: &PyFormatContext, ) -> bool { - fstring.elements.expressions().any(|expression| { + fstring.elements.interpolations().any(|expression| { if expression.debug_text.is_some() { let content = context.source().slice(expression); contains_opposite_quote(content, fstring.flags.into()) @@ -889,58 +914,6 @@ pub(super) fn is_fstring_with_quoted_debug_expression( }) } -/// Returns `true` if `string` has any f-string expression element (direct or nested) with a debug expression and a format spec -/// that contains the opposite quote. It's important to preserve the quote style for those f-strings -/// because changing the quote style would result in invalid syntax. -/// -/// ```python -/// f'{1=: "abcd \'\'}' -/// f'{x=:a{y:"abcd"}}' -/// f'{x=:a{y:{z:"abcd"}}}' -/// ``` -pub(super) fn is_fstring_with_quoted_format_spec_and_debug( - fstring: &FString, - context: &PyFormatContext, -) -> bool { - fn has_format_spec_with_opposite_quote( - elements: &FStringElements, - flags: FStringFlags, - context: &PyFormatContext, - in_debug: bool, - ) -> bool { - elements.iter().any(|element| match element { - FStringElement::Literal(literal) => { - let content = context.source().slice(literal); - - in_debug && contains_opposite_quote(content, flags.into()) - } - FStringElement::Expression(expression) => { - expression.format_spec.as_deref().is_some_and(|spec| { - has_format_spec_with_opposite_quote( - &spec.elements, - flags, - context, - in_debug || expression.debug_text.is_some(), - ) - }) - } - }) - } - - fstring.elements.expressions().any(|expression| { - if let Some(spec) = expression.format_spec.as_deref() { - return has_format_spec_with_opposite_quote( - &spec.elements, - fstring.flags, - context, - expression.debug_text.is_some(), - ); - } - - false - }) -} - /// Tests if the `fstring` contains any triple quoted string, byte, or f-string literal that /// contains a quote character opposite to its own quote character. /// @@ -980,6 +953,17 @@ pub(super) fn is_fstring_with_triple_quoted_literal_expression_containing_quotes } } + contains_quotes + } + StringLikePart::TString(tstring) => { + let mut contains_quotes = false; + for literal in tstring.elements.literals() { + if self.contains_quote(literal.range(), tstring.flags.into()) { + contains_quotes = true; + break; + } + } + contains_quotes } }; @@ -1018,6 +1002,59 @@ pub(super) fn is_fstring_with_triple_quoted_literal_expression_containing_quotes visitor.found } +/// Returns `true` if `string` has any f/t-string interpolation element (direct or nested) with a debug expression and a format spec +/// that contains the opposite quote. It's important to preserve the quote style for those f/t-strings +/// because changing the quote style would result in invalid syntax. +/// +/// ```python +/// t'{1=: "abcd \'\'}' +/// t'{x=:a{y:"abcd"}}' +/// t'{x=:a{y:{z:"abcd"}}}' +/// ``` +pub(super) fn is_interpolated_string_with_quoted_format_spec_and_debug( + elements: &InterpolatedStringElements, + flags: AnyStringFlags, + context: &PyFormatContext, +) -> bool { + fn has_format_spec_with_opposite_quote( + elements: &InterpolatedStringElements, + flags: AnyStringFlags, + context: &PyFormatContext, + in_debug: bool, + ) -> bool { + elements.iter().any(|element| match element { + InterpolatedStringElement::Literal(literal) => { + let content = context.source().slice(literal); + + in_debug && contains_opposite_quote(content, flags) + } + InterpolatedStringElement::Interpolation(expression) => { + expression.format_spec.as_deref().is_some_and(|spec| { + has_format_spec_with_opposite_quote( + &spec.elements, + flags, + context, + in_debug || expression.debug_text.is_some(), + ) + }) + } + }) + } + + elements.interpolations().any(|expression| { + if let Some(spec) = expression.format_spec.as_deref() { + return has_format_spec_with_opposite_quote( + &spec.elements, + flags, + context, + expression.debug_text.is_some(), + ); + } + + false + }) +} + fn contains_opposite_quote(content: &str, flags: AnyStringFlags) -> bool { if flags.is_triple_quoted() { match flags.quote_style() { diff --git a/crates/ruff_python_formatter/tests/normalizer.rs b/crates/ruff_python_formatter/tests/normalizer.rs index 2786082cfefd7d..8bed90221c56b9 100644 --- a/crates/ruff_python_formatter/tests/normalizer.rs +++ b/crates/ruff_python_formatter/tests/normalizer.rs @@ -6,8 +6,8 @@ use { use ruff_python_ast::visitor::transformer::Transformer; use ruff_python_ast::{ - self as ast, BytesLiteralFlags, Expr, FStringElement, FStringFlags, FStringLiteralElement, - FStringPart, Stmt, StringFlags, + self as ast, BytesLiteralFlags, Expr, FStringFlags, FStringPart, InterpolatedStringElement, + InterpolatedStringLiteralElement, Stmt, StringFlags, }; use ruff_python_ast::{StringLiteralFlags, visitor::transformer}; use ruff_text_size::{Ranged, TextRange}; @@ -117,7 +117,7 @@ impl Transformer for Normalizer { if can_join { #[derive(Default)] struct Collector { - elements: Vec, + elements: Vec, } impl Collector { @@ -127,7 +127,7 @@ impl Transformer for Normalizer { // `elements` vector, while subsequent strings // are concatenated onto this top string. fn push_literal(&mut self, literal: &str, range: TextRange) { - if let Some(FStringElement::Literal(existing_literal)) = + if let Some(InterpolatedStringElement::Literal(existing_literal)) = self.elements.last_mut() { let value = std::mem::take(&mut existing_literal.value); @@ -137,8 +137,8 @@ impl Transformer for Normalizer { existing_literal.range = TextRange::new(existing_literal.start(), range.end()); } else { - self.elements.push(FStringElement::Literal( - FStringLiteralElement { + self.elements.push(InterpolatedStringElement::Literal( + InterpolatedStringLiteralElement { range, value: literal.into(), }, @@ -146,11 +146,9 @@ impl Transformer for Normalizer { } } - fn push_expression( - &mut self, - expression: ast::FStringExpressionElement, - ) { - self.elements.push(FStringElement::Expression(expression)); + fn push_expression(&mut self, expression: ast::InterpolatedElement) { + self.elements + .push(InterpolatedStringElement::Interpolation(expression)); } } @@ -165,11 +163,13 @@ impl Transformer for Normalizer { ast::FStringPart::FString(fstring) => { for element in &fstring.elements { match element { - ast::FStringElement::Literal(literal) => { + ast::InterpolatedStringElement::Literal(literal) => { collector .push_literal(&literal.value, literal.range); } - ast::FStringElement::Expression(expression) => { + ast::InterpolatedStringElement::Interpolation( + expression, + ) => { collector.push_expression(expression.clone()); } } diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap index 9a5b9d18820013..a5f34628115c6c 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap @@ -437,6 +437,19 @@ f"hellooooooooooooooooooooooo \ worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) aaaaaaaaaaa = f"hellooooooooooooooooooooooo \ worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) + +# This t-string should be flattened +xxxxxxxxxxxxxxxx = t"aaaaaaaaaaaaaaaaaaaaa { + expression } bbbbbbbbbbbbbbbbbbbbbbbb" + ( + yyyyyyyyyyyyyy + zzzzzzzzzzz +) + +# This is not a multiline t-string, but the expression is too long so it should be +# wrapped in parentheses. +t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) +aaaaaaaaaaa = t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) ``` ## Output @@ -927,4 +940,22 @@ aaaaaaaaaaa = ( worlddddddddddddddddddddddddddddddddd" + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) ) + +# This t-string should be flattened +xxxxxxxxxxxxxxxx = t"aaaaaaaaaaaaaaaaaaaaa {expression} bbbbbbbbbbbbbbbbbbbbbbbb" + ( + yyyyyyyyyyyyyy + zzzzzzzzzzz +) + +# This is not a multiline t-string, but the expression is too long so it should be +# wrapped in parentheses. +( + t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) +) +aaaaaaaaaaa = ( + t"hellooooooooooooooooooooooo \ + worlddddddddddddddddddddddddddddddddd" + + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb) +) ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string.py.snap index c2e7f51ca16cc6..6c577df6bb73ef 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string.py.snap @@ -106,6 +106,55 @@ f"{10 + len('bar')=}" f'no debug{10}' f"{10 + len('bar')=}" f"{10 + len('bar')=}" f'{10 + len("bar")=}' +############################################################################## +# T-strings +############################################################################## + +# Escape `{` and `}` when merging a t-string with a string +"a {not_a_variable}" t"b {10}" "c" + +# Join, and break expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{ +expression +}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more" + +# Join, but don't break the expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" t"cccccccccccccccccccc {20999}" "more" + +t"test{ +expression +}flat" t"can be { +joined +} together" + +aaaaaaaaaaa = t"test{ +expression +}flat" t"cean beeeeeeee { +joined +} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + + +t"single quoted '{x}'" t'double quoted "{x}"' # Same number of quotes => use preferred quote style +t"single quote ' {x}" t'double quoted "{x}"' # More double quotes => use single quotes +t"single quoted '{x}'" t'double quote " {x}"' # More single quotes => use double quotes + +# Different triple quoted strings +t"{'''test'''}" t'{"""other"""}' + +# Now with inner quotes +t"{'''test ' '''}" t'{"""other " """}' +t"{some_where_nested('''test ' ''')}" t'{"""other " """ + "more"}' +t"{b'''test ' '''}" t'{b"""other " """}' +t"{t'''test ' '''}" t'{t"""other " """}' + +# debug expressions containing quotes +t"{10 + len('bar')=}" t"{10 + len('bar')=}" +t"{10 + len('bar')=}" t'no debug{10}' t"{10 + len('bar')=}" + +# We can't safely merge this pre Python 3.12 without altering the debug expression. +t"{10 + len('bar')=}" t'{10 + len("bar")=}' + + ############################################################################## # Don't join raw strings ############################################################################## @@ -116,6 +165,9 @@ R"a" "normal" f"test" fr"test" f"test" fR"test" +t"test" tr"test" +t"test" tR"test" + ############################################################################## # Don't join triple quoted strings @@ -125,9 +177,22 @@ f"test" fR"test" "single" f""""single""" +"single" t""""single""" + b"single" b"""triple""" +############################################################################## +# Don't join t-strings and f-strings +############################################################################## + +t"{interp}" f"{expr}" + +f"{expr}" t"{interp}" + +f"{expr}" "string" t"{interp}" + + ############################################################################## # Join strings in with statements ############################################################################## @@ -452,6 +517,50 @@ f"{10 + len('bar')=}no debug{10}{10 + len('bar')=}" f"{10 + len('bar')=}" f'{10 + len("bar")=}' +############################################################################## +# T-strings +############################################################################## + +# Escape `{` and `}` when merging a t-string with a string +t"a {{not_a_variable}}b {10}c" + +# Join, and break expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression +}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccc {20999}more" + +# Join, but don't break the expressions +t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccc {20999}more" + +t"test{expression}flatcan be {joined} together" + +aaaaaaaaaaa = ( + t"test{expression}flat" + t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" +) # inline + + +t"single quoted '{x}'double quoted \"{x}\"" # Same number of quotes => use preferred quote style +t'single quote \' {x}double quoted "{x}"' # More double quotes => use single quotes +t"single quoted '{x}'double quote \" {x}\"" # More single quotes => use double quotes + +# Different triple quoted strings +t"{'''test'''}{'''other'''}" + +# Now with inner quotes +t"{'''test ' '''}{'''other " '''}" +t"{some_where_nested('''test ' ''')}{'''other " ''' + 'more'}" +t"{b'''test ' '''}{b'''other " '''}" +t"{t'''test ' '''}{t'''other " '''}" + +# debug expressions containing quotes +t"{10 + len('bar')=}{10 + len('bar')=}" +t"{10 + len('bar')=}no debug{10}{10 + len('bar')=}" + +# We can't safely merge this pre Python 3.12 without altering the debug expression. +t"{10 + len('bar')=}{10 + len("bar")=}" + + ############################################################################## # Don't join raw strings ############################################################################## @@ -462,6 +571,9 @@ R"a" "normal" f"test" rf"test" f"test" Rf"test" +t"test" rt"test" +t"test" Rt"test" + ############################################################################## # Don't join triple quoted strings @@ -471,9 +583,22 @@ f"test" Rf"test" "single" f""""single""" +"single" t""""single""" + b"single" b"""triple""" +############################################################################## +# Don't join t-strings and f-strings +############################################################################## + +t"{interp}" f"{expr}" + +f"{expr}" t"{interp}" + +f"{expr}" "string" t"{interp}" + + ############################################################################## # Join strings in with statements ############################################################################## @@ -780,7 +905,7 @@ f"aaaaaaaaaaaaaaaa \ ```diff --- Stable +++ Preview -@@ -242,9 +242,12 @@ +@@ -302,9 +302,12 @@ ############################################################################## # Use can_omit_optional_parentheses layout to avoid an instability where the formatter # picks the can_omit_optional_parentheses layout when the strings are joined. diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string_assignment.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string_assignment.py.snap index 7133eb0b8c1173..c5237dcb54a020 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string_assignment.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__join_implicit_concatenated_string_assignment.py.snap @@ -299,6 +299,155 @@ aaaaa[aaaaaaaaaaa] = ( ) +############################################################# +# T-Strings +############################################################# + +# Flatten and join the t-string +aaaaaaaaaaa = t"test{ +expression}flat" t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeee" # inline + +# Parenthesize the value and join it, inline the comment +aaaaaaaaaaa = t"test{ +expression}flat" t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# Parenthesize the t-string and keep it multiline because it doesn't fit on a single line including the comment +aaaaaaaaaaa = t"test{ +expression +}flat" t"cean beeeeeeee { +joined +} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + + +# The target splits because of a magic trailing comma +# The string is joined and not parenthesized because it just fits into the line length (including comment). +a[ + aaaaaaa, + b, +] = t"ccccc{ +expression}ccccccccccc" t"cccccccccccccccccccccccccccccccccccccccccc" # comment + + +# Same but starting with a joined string. They should both result in the same formatting. +[ + aaaaaaa, + b, +] = t"ccccc{ +expression}ccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment + +# The target splits because of the magic trailing comma +# The string is **not** joined because it with the inlined comment exceeds the line length limit. +a[ + aaaaaaa, + b, +] = t"ccccc{ +expression}cccccccccccccccccccc" t"cccccccccccccccccccccccccccccccccccccccccc" # comment + + +# The target should be flat +# The string should be joined because it fits into the line length +a[ + aaaaaaa, + b +] = ( + t"ccccc{ + expression}ccccccccccc" "cccccccccccccccccccccccc" # comment +) + +# Same but starting with a joined string. They should both result in the same formatting. +a[ + aaaaaaa, + b +] = t"ccccc{ +expression}ccccccccccccccccccccccccccccccccccc" # comment + +# The target should be flat +# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit. +a[ + aaaaaaa, + b +] = t"ccccc{ +expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment + + +# Split an overlong target, but join the string if it fits +a[ + aaaaaaa, + b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{ + expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment +) + +# Split both if necessary and keep multiline +a[ + aaaaaaa, + b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{ + expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment +) + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment +) + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment + +aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment +) + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +# Don't inline t-strings with multiline debug expressions: +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" "moreeeeeeeeeeeeeeeeeetest" # comment +) + + # Trailing last-part comments a = ( @@ -380,7 +529,8 @@ self._attr_unique_id = ( return ( f"Exception in {call_back_name} when handling msg on " f"'{msg.topic}': '{msg.payload}'" # type: ignore[str-bytes-safe] -)``` +) +``` ## Output ```python @@ -704,6 +854,172 @@ aaaaa[aaaaaaaaaaa] = ( ) +############################################################# +# T-Strings +############################################################# + +# Flatten and join the t-string +aaaaaaaaaaa = t"test{expression}flatcean beeeeeeee {joined} eeeeeeeeeeeeeeeee" # inline + +# Parenthesize the value and join it, inline the comment +aaaaaaaaaaa = ( + t"test{expression}flatcean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeee" # inline +) + +# Parenthesize the t-string and keep it multiline because it doesn't fit on a single line including the comment +aaaaaaaaaaa = ( + t"test{expression}flat" + t"cean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" +) # inline + + +# The target splits because of a magic trailing comma +# The string is joined and not parenthesized because it just fits into the line length (including comment). +a[ + aaaaaaa, + b, +] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment + + +# Same but starting with a joined string. They should both result in the same formatting. +[ + aaaaaaa, + b, +] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment + +# The target splits because of the magic trailing comma +# The string is **not** joined because it with the inlined comment exceeds the line length limit. +a[ + aaaaaaa, + b, +] = ( + t"ccccc{expression}cccccccccccccccccccc" + t"cccccccccccccccccccccccccccccccccccccccccc" +) # comment + + +# The target should be flat +# The string should be joined because it fits into the line length +a[aaaaaaa, b] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccc" # comment + +# Same but starting with a joined string. They should both result in the same formatting. +a[aaaaaaa, b] = t"ccccc{expression}ccccccccccccccccccccccccccccccccccc" # comment + +# The target should be flat +# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit. +a[aaaaaaa, b] = ( + t"ccccc{expression}ccccccccccc" + "ccccccccccccccccccccccccccccccccccccccccccc" +) # comment + + +# Split an overlong target, but join the string if it fits +a[ + aaaaaaa, b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{expression}ccccccccccccccccccccccccccccccccccccccccc" # comment +) + +# Split both if necessary and keep multiline +a[ + aaaaaaa, b +].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ( + t"ccccc{expression}cccccccccccccccccccccccccccccccc" + "ccccccccccccccccccccccccccccccc" +) # comment + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] + }" + "moreeeeeeeeeeeeeeeeeeee" + "test" +) # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] + }" + "moreeeeeeeeeeeeeeeeeeee" + "test" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] + }" + "moreeeeeeeeeeeeeeeeeeee" + "test" +) # comment + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] + }" + "moreeeeeeeeeeeeeeeeeeee" + "test" # comment +) + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a # comment + ] + }" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a # comment + ] + }" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +# Don't inline t-strings with multiline debug expressions: +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}" + "moreeeeeeeeeeeeeeeeeetest" # comment +) + + # Trailing last-part comments a = ( diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap new file mode 100644 index 00000000000000..5c08140e3e0344 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap @@ -0,0 +1,1536 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py +--- +## Input +```python +( + t'{one}' + t'{two}' +) + + +rt"Not-so-tricky \"quote" + +# Regression test for tstrings dropping comments +result_f = ( + 'Traceback (most recent call last):\n' + t' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + t' File "{__file__}", line {lineno_f+1}, in f\n' + ' f()\n' + # XXX: The following line changes depending on whether the tests + # are run through the interactive interpreter or with -m + # It also varies depending on the platform (stack size) + # Fortunately, we don't care about exactness here, so we use regex + r' \[Previous line repeated (\d+) more times\]' '\n' + 'RecursionError: maximum recursion depth exceeded\n' +) + + +# Regression for tstring dropping comments that were accidentally attached to +# an expression inside a formatted value +( + t'{1}' + # comment 1 + '' +) + +( + t'{1}' # comment 2 + t'{2}' +) + +( + t'{1}' + t'{2}' # comment 3 +) + +( + 1, ( # comment 4 + t'{2}' + ) +) + +( + ( + t'{1}' + # comment 5 + ), + 2 +) + +# https://github.com/astral-sh/ruff/issues/6841 +x = t'''a{""}b''' +y = t'''c{1}d"""e''' +z = t'''a{""}b''' t'''c{1}d"""e''' + +# T-String formatting test cases (Preview) + +# Simple expression with a mix of debug expression and comments. +x = t"{a}" +x = t"{ + a = }" +x = t"{ # comment 6 + a }" +x = t"{ # comment 7 + a = }" + +# Remove the parentheses as adding them doesn't make then fit within the line length limit. +# This is similar to how we format it before t-string formatting. +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd } cccccccccc" +) +# Here, we would use the best fit layout to put the t-string indented on the next line +# similar to the next example. +aaaaaaaaaaa = t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc" +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc" +) + +# This should never add the optional parentheses because even after adding them, the +# t-string exceeds the line length limit. +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 8 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb' = } ccccccccccccccc" + +# Multiple larger expressions which exceeds the line length limit. Here, we need to decide +# whether to split at the first or second expression. This should work similarly to the +# assignment statement formatting where we split from right to left in preview mode. +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" + +# The above example won't split but when we start introducing line breaks: +x = t"aaaaaaaaaaaa { + bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb + } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { + ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd + } eeeeeeeeeeeeee" + +# But, in case comments are present, we would split at the expression containing the +# comments: +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb # comment 10 + } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa { bbbbbbbbbbbbbb + } cccccccccccccccccccc { # comment 11 + ddddddddddddddd } eeeeeeeeeeeeee" + +# Here, the expression part itself starts with a curly brace so we need to add an extra +# space between the opening curly brace and the expression. +x = t"{ {'x': 1, 'y': 2} }" +# Although the extra space isn't required before the ending curly brace, we add it for +# consistency. +x = t"{ {'x': 1, 'y': 2}}" +x = t"{ {'x': 1, 'y': 2} = }" +x = t"{ # comment 12 + {'x': 1, 'y': 2} }" +x = t"{ # comment 13 + {'x': 1, 'y': 2} = }" +# But, if there's a format specifier or a conversion flag then we don't need to add +# any whitespace at the end +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}!s} bbbbbb" +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}:.3f} bbbbbb" + +# But, in this case, we would split the expression itself because it exceeds the line +# length limit so we need not add the extra space. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbb', 'ccccccccccccccccccccc'} +}" +# And, split the expression itself because it exceeds the line length. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +############################################################################################# +# Quotes +############################################################################################# +t"foo 'bar' {x}" +t"foo \"bar\" {x}" +t'foo "bar" {x}' +t'foo \'bar\' {x}' +t"foo {"bar"}" + +t"single quoted '{x}' double quoted \"{x}\"" # Same number of quotes => use preferred quote style +t"single quote ' {x} double quoted \"{x}\"" # More double quotes => use single quotes +t"single quoted '{x}' double quote \" {x}" # More single quotes => use double quotes + +fr"single quotes ' {x}" # Keep double because `'` can't be escaped +fr'double quotes " {x}' # Keep single because `"` can't be escaped +fr'flip quotes {x}' # Use preferred quotes, because raw string contains now quotes. + +# Here, the formatter will remove the escapes +t"foo {'\'bar\''}" +t"foo {'\"bar\"'}" + +# Quotes inside the expressions have no impact on the quote selection of the outer string. +# Required so that the following two examples result in the same formatting. +t'foo {10 + len("bar")}' +t"foo {10 + len('bar')}" + +# Pre 312, preserve the outer quotes if the t-string contains quotes in the debug expression +t'foo {10 + len("bar")=}' +t'''foo {10 + len('''bar''')=}''' +t'''foo {10 + len('bar')=}''' # Fine to change the quotes because it uses triple quotes + +# Triple-quoted strings +# It's ok to use the same quote char for the inner string if it's single-quoted. +t"""test {'inner'}""" +t"""test {"inner"}""" +# But if the inner string is also triple-quoted then we should preserve the existing quotes. +t"""test {'''inner'''}""" + +# It's not okay to change the quote style if the inner string is triple quoted and contains a quote. +t'{"""other " """}' +t'{"""other " """ + "more"}' +t'{b"""other " """}' +t'{t"""other " """}' + +t"""test {t'inner {'''inner inner'''}'}""" +t"""test {t'''inner {"""inner inner"""}'''}""" + +# Magic trailing comma +# +# The expression formatting will result in breaking it across multiple lines with a +# trailing comma but as the expression isn't already broken, we will remove all the line +# breaks which results in the trailing comma being present. This test case makes sure +# that the trailing comma is removed as well. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee']} aaaaaaa" + +# And, if the trailing comma is already present, we still need to remove it. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee',]} aaaaaaa" + +# Keep this Multiline by breaking it at the square brackets. +t"""aaaaaa {[ + xxxxxxxx, + yyyyyyyy, +]} ccc""" + +# Add the magic trailing comma because the elements don't fit within the line length limit +# when collapsed. +t"aaaaaa {[ + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + yyyyyyyyyyyy +]} ccccccc" + +# Remove the parentheses because they aren't required +xxxxxxxxxxxxxxx = ( + t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbb { + xxxxxxxxxxx # comment 14 + + yyyyyyyyyy + } dddddddddd" +) + +# Comments + +# No comments should be dropped! +t"{ # comment 15 + # comment 16 + foo # comment 17 + # comment 18 +}" # comment 19 +# comment 20 + +# Single-quoted t-strings with a format specificer can be multiline +t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee" + +# But, if it's triple-quoted then we can't or the format specificer will have a +# trailing newline +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee""" + +# But, we can break the ones which don't have a format specifier +t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { + xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" + +# Throw in a random comment in it but surprise, this is not a comment but just a text +# which is part of the format specifier +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment +} cccccccccc""" +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment} cccccccccc""" + +# Conversion flags +# +# This is not a valid Python code because of the additional whitespace between the `!` +# and conversion type. But, our parser isn't strict about this. This should probably be +# removed once we have a strict parser. +x = t"aaaaaaaaa { x ! r }" + +# Even in the case of debug expressions, we only need to preserve the whitespace within +# the expression part of the replacement field. +x = t"aaaaaaaaa { x = ! r }" + +# Combine conversion flags with format specifiers +x = t"{x = ! s + :>0 + + }" +# This is interesting. There can be a comment after the format specifier but only if it's +# on it's own line. Refer to https://github.com/astral-sh/ruff/pull/7787 for more details. +# We'll format is as trailing comments. +x = t"{x !s + :>0 + # comment 21 + }" + +x = t""" +{ # comment 22 + x = :.0{y # comment 23 + }f}""" + +# Here, the debug expression is in a nested t-string so we should start preserving +# whitespaces from that point onwards. This means we should format the outer t-string. +x = t"""{"foo " + # comment 24 + t"{ x = + + }" # comment 25 + } + """ + +# Mix of various features. +t"{ # comment 26 + foo # after foo + :>{ + x # after x + } + # comment 27 + # comment 28 +} woah {x}" + +# Assignment statement + +# Even though this t-string has multiline expression, thus allowing us to break it at the +# curly braces, the t-string fits on a single line if it's moved inside the parentheses. +# We should prefer doing that instead. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" + +# Same as above +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +# Similar to the previous example, but the t-string will exceed the line length limit, +# we shouldn't add any parentheses here. +xxxxxxx = t"{ + {'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} +}" + +# Same as above but with an inline comment. The t-string should be formatted inside the +# parentheses and the comment should be part of the line inside the parentheses. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment + +# Similar to the previous example but this time parenthesizing doesn't work because it +# exceeds the line length. So, avoid parenthesizing this t-string. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment loooooooong + +# Similar to the previous example but we start with the parenthesized layout. This should +# remove the parentheses and format the t-string on a single line. This shows that the +# final layout for the formatter is same for this and the previous case. The only +# difference is that in the previous case the expression is already mulitline which means +# the formatter can break it further at the curly braces. +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" # comment loooooooong +) + +# The following t-strings are going to break because of the trailing comma so we should +# avoid using the best fit layout and instead use the default layout. +# left-to-right +aaaa = t"aaaa {[ + 1, 2, +]} bbbb" +# right-to-left +aaaa, bbbb = t"aaaa {[ + 1, 2, +]} bbbb" + +# Using the right-to-left assignment statement variant. +aaaaaaaaaaaaaaaaaa, bbbbbbbbbbb = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeee" # comment + +# Here, the t-string layout is flat but it exceeds the line length limit. This shouldn't +# try the custom best fit layout because the t-string doesn't have any split points. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) +# Same as above but without the parentheses to test that it gets formatted to the same +# layout as the previous example. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" + +# But, the following t-string does have a split point because of the multiline expression. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccc + dddddddddddddddddddddddddddd} ddddddddddddddddddd" +) + +# This is an implicitly concatenated t-string but it cannot be joined because otherwise +# it'll exceed the line length limit. So, the two t-strings will be inside parentheses +# instead and the inline comment should be outside the parentheses. +a = t"test{ + expression +}flat" t"can be { + joined +} togethereeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# Similar to the above example but this fits within the line length limit. +a = t"test{ + expression +}flat" t"can be { + joined +} togethereeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# The following test cases are adopted from implicit string concatenation but for a +# single t-string instead. + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.g. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment +) + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaa[aaaaaaaaaaa] = (t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ +[a,] +}moreeeeeeeeeeeeeeeeeeee" # comment +) + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{[ + a # comment + ]}moreeeeeeeeeeeeeeeeeetest" # comment +) + +# Don't inline t-strings with multiline debug expressions or format specifiers +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaa[aaaaaaaaaaa] = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment +) + +# This is not a multiline t-string even though it has a newline after the format specifier. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest" # comment +) + +# The newline is only considered when it's a tripled-quoted t-string. +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment + +aaaaaaaaaaaaaaaaaa = ( + t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment +) + +# Remove the parentheses here +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{[a, b, + # comment + ]}moee" # comment +) +# ... but not here because of the ownline comment +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{[a, b, + ]}moee" + # comment +) + +# t-strings in other positions + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if ( + t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + }" +): pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { # comment + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": pass + +if t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if ( + t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + }" +): + pass + +if t"aaaaaaaaaaa {[ttttteeeeeeeeest,]} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +# For loops +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeee": + pass + +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +for a in ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# With statements +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeee": + pass + +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +with ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# Assert statements +assert t"aaaaaaaaa{ + expression}bbbbbbbbbbbb", t"cccccccccc{ + expression}dddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccccccccc{ + expression}dddddddddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccccccccc{expression}dddddddddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccc{expression}dddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccc{expression}dddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccccccccccccccccc { + expression} dddddddddddddddddddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"cccccccccccccccccccccccccccccccc {expression} ddddddddddddddddddddddddddddddddddddd" + +# t-strings as a single argument to a call expression to test whether it's huggable or not. +call(t"{ + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"{ + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"{ # comment + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +}") + +call(t"""aaaaaaaaaaaaaaaa bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}""") + +call(t"""aaaaaaaaaaaaaaaa bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }""") + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }""") + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment + }""") + +call( + t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment + }""" +) + +call(t"{ + aaaaaa + + '''test + more''' +}") + +# Indentation + +# What should be the indentation? +# https://github.com/astral-sh/ruff/discussions/9785#discussioncomment-8470590 +if indent0: + if indent1: + if indent2: + foo = t"""hello world +hello { + t"aaaaaaa { + [ + 'aaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbb', + 'ccccccccccccccccccccc', + 'ddddddddddddddddddddd' + ] + } bbbbbbbb" + + [ + 'aaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbb', + 'ccccccccccccccccccccc', + 'ddddddddddddddddddddd' + ] + } -------- +""" + + +# Implicit concatenated t-string containing quotes +_ = ( + 'This string should change its quotes to double quotes' + t'This string uses double quotes in an expression {"it's a quote"}' + t'This t-string does not use any quotes.' +) + +# Regression test for https://github.com/astral-sh/ruff/issues/14487 +t"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc" + +# Regression test for https://github.com/astral-sh/ruff/issues/14778 +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a' if True else ""}" +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a' if True else ""}" + +# Quotes reuse +t"{'a'}" + +# 312+, it's okay to change the outer quotes even when there's a debug expression using the same quotes +t'foo {10 + len("bar")=}' +t'''foo {10 + len("""bar""")=}''' + +# 312+, it's okay to change the quotes here without creating an invalid t-string +t'{"""other " """}' +t'{"""other " """ + "more"}' +t'{b"""other " """}' +t'{t"""other " """}' + + +# Regression tests for https://github.com/astral-sh/ruff/issues/13935 +t'{1: hy "user"}' +t'{1:hy "user"}' +t'{1: abcd "{1}" }' +t'{1: abcd "{'aa'}" }' +t'{1=: "abcd {'aa'}}' +t'{x:a{z:hy "user"}} \'\'\'' + +# Changing the outer quotes is fine because the format-spec is in a nested expression. +t'{t'{z=:hy "user"}'} \'\'\'' + + +# We have to be careful about changing the quotes if the t-string has a debug expression because it is inserted verbatim. +t'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error +t'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes +t'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped +# Don't change the quotes in the following cases: +t'{x=:hy "user"} \'\'\'' +t'{x=:a{y:hy "user"}} \'\'\'' +t'{x=:a{y:{z:hy "user"}}} \'\'\'' +t'{x:a{y=:{z:hy "user"}}} \'\'\'' + +# This is fine because the debug expression and format spec are in a nested expression + +t"""{1=: "this" is fine}""" +t'''{1=: "this" is fine}''' # Change quotes to double quotes because they're preferred +t'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part. + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15459 +print(t"{ {1, 2, 3} - {2} }") +print(t"{ {1: 2}.keys() }") +print(t"{({1, 2, 3}) - ({2})}") +print(t"{1, 2, {3} }") +print(t"{(1, 2, {3})}") + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(t"{ {}, }") # A single item tuple gets parenthesized +print(t"{ {}.values(), }") +print(t"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print(t"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + {}, 1, +}") + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15536 +print(t"{ {}, 1, }") +``` + +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +docstring-code-line-width = "dynamic" +preview = Disabled +target_version = 3.14 +source_type = Python +``` + +```python +(t"{one}{two}") + + +rt"Not-so-tricky \"quote" + +# Regression test for tstrings dropping comments +result_f = ( + "Traceback (most recent call last):\n" + t' File "{__file__}", line {lineno_f + 5}, in _check_recursive_traceback_display\n' + " f()\n" + t' File "{__file__}", line {lineno_f + 1}, in f\n' + " f()\n" + t' File "{__file__}", line {lineno_f + 1}, in f\n' + " f()\n" + t' File "{__file__}", line {lineno_f + 1}, in f\n' + " f()\n" + # XXX: The following line changes depending on whether the tests + # are run through the interactive interpreter or with -m + # It also varies depending on the platform (stack size) + # Fortunately, we don't care about exactness here, so we use regex + r" \[Previous line repeated (\d+) more times\]" + "\n" + "RecursionError: maximum recursion depth exceeded\n" +) + + +# Regression for tstring dropping comments that were accidentally attached to +# an expression inside a formatted value +( + t"{1}" + # comment 1 + "" +) + +( + t"{1}" # comment 2 + t"{2}" +) + +( + t"{1}{2}" # comment 3 +) + +( + 1, + ( # comment 4 + t"{2}" + ), +) + +( + ( + t"{1}" + # comment 5 + ), + 2, +) + +# https://github.com/astral-sh/ruff/issues/6841 +x = t"""a{""}b""" +y = t'''c{1}d"""e''' +z = t"""a{""}b""" t'''c{1}d"""e''' + +# T-String formatting test cases (Preview) + +# Simple expression with a mix of debug expression and comments. +x = t"{a}" +x = t"{ + a = }" +x = t"{ # comment 6 + a +}" +x = t"{ # comment 7 + a = }" + +# Remove the parentheses as adding them doesn't make then fit within the line length limit. +# This is similar to how we format it before t-string formatting. +aaaaaaaaaaa = t"asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd} cccccccccc" +# Here, we would use the best fit layout to put the t-string indented on the next line +# similar to the next example. +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc} cccccccccc" +) +aaaaaaaaaaa = ( + t"asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc} cccccccccc" +) + +# This should never add the optional parentheses because even after adding them, the +# t-string exceeds the line length limit. +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa {'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb'} ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 8 + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb' +} ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc" +x = t"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9 + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb' = } ccccccccccccccc" + +# Multiple larger expressions which exceeds the line length limit. Here, we need to decide +# whether to split at the first or second expression. This should work similarly to the +# assignment statement formatting where we split from right to left in preview mode. +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc {ddddddddddddddd} eeeeeeeeeeeeee" + +# The above example won't split but when we start introducing line breaks: +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc { + ddddddddddddddd +} eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc { + ddddddddddddddd +} eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc { + ddddddddddddddd +} eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc { + ddddddddddddddd +} eeeeeeeeeeeeee" + +# But, in case comments are present, we would split at the expression containing the +# comments: +x = t"aaaaaaaaaaaa { + bbbbbbbbbbbbbb # comment 10 +} cccccccccccccccccccc {ddddddddddddddd} eeeeeeeeeeeeee" +x = t"aaaaaaaaaaaa {bbbbbbbbbbbbbb} cccccccccccccccccccc { # comment 11 + ddddddddddddddd +} eeeeeeeeeeeeee" + +# Here, the expression part itself starts with a curly brace so we need to add an extra +# space between the opening curly brace and the expression. +x = t"{ {'x': 1, 'y': 2} }" +# Although the extra space isn't required before the ending curly brace, we add it for +# consistency. +x = t"{ {'x': 1, 'y': 2} }" +x = t"{ {'x': 1, 'y': 2} = }" +x = t"{ # comment 12 + {'x': 1, 'y': 2} +}" +x = t"{ # comment 13 + {'x': 1, 'y': 2} = }" +# But, if there's a format specifier or a conversion flag then we don't need to add +# any whitespace at the end +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}!s} bbbbbb" +x = t"aaaaa { {'aaaaaa', 'bbbbbbb', 'ccccccccc'}:.3f} bbbbbb" + +# But, in this case, we would split the expression itself because it exceeds the line +# length limit so we need not add the extra space. +xxxxxxx = ( + t"{ {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbb', 'ccccccccccccccccccccc'} }" +) +# And, split the expression itself because it exceeds the line length. +xxxxxxx = t"{ + { + 'aaaaaaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'cccccccccccccccccccccccccc', + } +}" + +############################################################################################# +# Quotes +############################################################################################# +t"foo 'bar' {x}" +t'foo "bar" {x}' +t'foo "bar" {x}' +t"foo 'bar' {x}" +t"foo {'bar'}" + +t"single quoted '{x}' double quoted \"{x}\"" # Same number of quotes => use preferred quote style +t'single quote \' {x} double quoted "{x}"' # More double quotes => use single quotes +t"single quoted '{x}' double quote \" {x}" # More single quotes => use double quotes + +rf"single quotes ' {x}" # Keep double because `'` can't be escaped +rf'double quotes " {x}' # Keep single because `"` can't be escaped +rf"flip quotes {x}" # Use preferred quotes, because raw string contains now quotes. + +# Here, the formatter will remove the escapes +t"foo {"'bar'"}" +t"foo {'"bar"'}" + +# Quotes inside the expressions have no impact on the quote selection of the outer string. +# Required so that the following two examples result in the same formatting. +t"foo {10 + len('bar')}" +t"foo {10 + len('bar')}" + +# Pre 312, preserve the outer quotes if the t-string contains quotes in the debug expression +t"foo {10 + len("bar")=}" +t"""foo {10 + len('''bar''')=}""" +t"""foo {10 + len('bar')=}""" # Fine to change the quotes because it uses triple quotes + +# Triple-quoted strings +# It's ok to use the same quote char for the inner string if it's single-quoted. +t"""test {"inner"}""" +t"""test {"inner"}""" +# But if the inner string is also triple-quoted then we should preserve the existing quotes. +t"""test {'''inner'''}""" + +# It's not okay to change the quote style if the inner string is triple quoted and contains a quote. +t"{'''other " '''}" +t"{'''other " ''' + 'more'}" +t"{b'''other " '''}" +t"{t'''other " '''}" + +t"""test {t"inner {'''inner inner'''}"}""" +t"""test {t'''inner {"""inner inner"""}'''}""" + +# Magic trailing comma +# +# The expression formatting will result in breaking it across multiple lines with a +# trailing comma but as the expression isn't already broken, we will remove all the line +# breaks which results in the trailing comma being present. This test case makes sure +# that the trailing comma is removed as well. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee']} aaaaaaa" + +# And, if the trailing comma is already present, we still need to remove it. +t"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee']} aaaaaaa" + +# Keep this Multiline by breaking it at the square brackets. +t"""aaaaaa { + [ + xxxxxxxx, + yyyyyyyy, + ] +} ccc""" + +# Add the magic trailing comma because the elements don't fit within the line length limit +# when collapsed. +t"aaaaaa { + [ + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + xxxxxxxxxxxx, + yyyyyyyyyyyy, + ] +} ccccccc" + +# Remove the parentheses because they aren't required +xxxxxxxxxxxxxxx = t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbb { + xxxxxxxxxxx # comment 14 + + yyyyyyyyyy +} dddddddddd" + +# Comments + +# No comments should be dropped! +t"{ # comment 15 + # comment 16 + foo # comment 17 + # comment 18 +}" # comment 19 +# comment 20 + +# Single-quoted t-strings with a format specificer can be multiline +t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f +} ddddddddddddddd eeeeeeee" + +# But, if it's triple-quoted then we can't or the format specificer will have a +# trailing newline +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f} ddddddddddddddd eeeeeeee""" + +# But, we can break the ones which don't have a format specifier +t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr {xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { + xxxxxxxxxxxxxxxxxxxx +} bbbbbbbbbbbb""" + +# Throw in a random comment in it but surprise, this is not a comment but just a text +# which is part of the format specifier +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment +} cccccccccc""" +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f + # comment} cccccccccc""" + +# Conversion flags +# +# This is not a valid Python code because of the additional whitespace between the `!` +# and conversion type. But, our parser isn't strict about this. This should probably be +# removed once we have a strict parser. +x = t"aaaaaaaaa {x!r}" + +# Even in the case of debug expressions, we only need to preserve the whitespace within +# the expression part of the replacement field. +x = t"aaaaaaaaa { x = !r}" + +# Combine conversion flags with format specifiers +x = t"{x = !s:>0}" +# This is interesting. There can be a comment after the format specifier but only if it's +# on it's own line. Refer to https://github.com/astral-sh/ruff/pull/7787 for more details. +# We'll format is as trailing comments. +x = t"{ + x!s:>0 + # comment 21 +}" + +x = t""" +{ # comment 22 + x = :.0{y # comment 23 + }f}""" + +# Here, the debug expression is in a nested t-string so we should start preserving +# whitespaces from that point onwards. This means we should format the outer t-string. +x = t"""{ + "foo " # comment 24 + + t"{ x = + + }" # comment 25 +} + """ + +# Mix of various features. +t"{ # comment 26 + foo:>{ # after foo + x # after x + } + # comment 27 + # comment 28 +} woah {x}" + +# Assignment statement + +# Even though this t-string has multiline expression, thus allowing us to break it at the +# curly braces, the t-string fits on a single line if it's moved inside the parentheses. +# We should prefer doing that instead. +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" +) + +# Same as above +xxxxxxx = ( + t"{ {'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'} }" +) + +# Similar to the previous example, but the t-string will exceed the line length limit, +# we shouldn't add any parentheses here. +xxxxxxx = t"{ + { + 'aaaaaaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'cccccccccccccccccccccccccc', + } +}" + +# Same as above but with an inline comment. The t-string should be formatted inside the +# parentheses and the comment should be part of the line inside the parentheses. +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" # comment +) + +# Similar to the previous example but this time parenthesizing doesn't work because it +# exceeds the line length. So, avoid parenthesizing this t-string. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression +}moreeeeeeeeeeeeeeeee" # comment loooooooong + +# Similar to the previous example but we start with the parenthesized layout. This should +# remove the parentheses and format the t-string on a single line. This shows that the +# final layout for the formatter is same for this and the previous case. The only +# difference is that in the previous case the expression is already mulitline which means +# the formatter can break it further at the curly braces. +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" # comment loooooooong + +# The following t-strings are going to break because of the trailing comma so we should +# avoid using the best fit layout and instead use the default layout. +# left-to-right +aaaa = t"aaaa { + [ + 1, + 2, + ] +} bbbb" +# right-to-left +aaaa, bbbb = t"aaaa { + [ + 1, + 2, + ] +} bbbb" + +# Using the right-to-left assignment statement variant. +aaaaaaaaaaaaaaaaaa, bbbbbbbbbbb = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeee" # comment +) + +# Here, the t-string layout is flat but it exceeds the line length limit. This shouldn't +# try the custom best fit layout because the t-string doesn't have any split points. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) +# Same as above but without the parentheses to test that it gets formatted to the same +# layout as the previous example. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = ( + t"aaaaaaaaaaaaaaaaaaa {aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc} ddddddddddddddddddd" +) + +# But, the following t-string does have a split point because of the multiline expression. +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaa + bbbbbbbbbbb + cccccccccccccc +} ddddddddddddddddddd" +aaaaaaaaaaaa["bbbbbbbbbbbbbbbb"] = t"aaaaaaaaaaaaaaaaaaa { + aaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbb + + cccccccccccccccccccccc + + dddddddddddddddddddddddddddd +} ddddddddddddddddddd" + +# This is an implicitly concatenated t-string but it cannot be joined because otherwise +# it'll exceed the line length limit. So, the two t-strings will be inside parentheses +# instead and the inline comment should be outside the parentheses. +a = ( + t"test{expression}flat" + t"can be {joined} togethereeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +) # inline + +# Similar to the above example but this fits within the line length limit. +a = t"test{expression}flatcan be {joined} togethereeeeeeeeeeeeeeeeeeeeeeeeeee" # inline + +# The following test cases are adopted from implicit string concatenation but for a +# single t-string instead. + +# Don't inline t-strings that contain expressions that are guaranteed to split, e.g. because of a magic trailing comma +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] +}moreeeeeeeeeeeeeeeeeeee" # comment + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + ] +}moreeeeeeeeeeeeeeeeeeee" # comment + +# Don't inline t-strings that contain commented expressions +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a # comment + ] +}moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a # comment + ] +}moreeeeeeeeeeeeeeeeeetest" # comment + +# Don't inline t-strings with multiline debug expressions or format specifiers +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + + b=}moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeee{ + a=}moreeeeeeeeeeeeeeeeeetest" # comment + +aaaaa[aaaaaaaaaaa] = t"testeeeeeeeeeeeeeeeeeeeeeeeee{a + =}moreeeeeeeeeeeeeeeeeetest" # comment + +# This is not a multiline t-string even though it has a newline after the format specifier. +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f}moreeeeeeeeeeeeeeeeeetest" # comment +) + +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f}moreeeeeeeeeeeeeeeeeetest" # comment +) + +# The newline is only considered when it's a tripled-quoted t-string. +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment + +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f + }moreeeeeeeeeeeeeeeeeetest""" # comment + +# Remove the parentheses here +aaaaaaaaaaaaaaaaaa = t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + b, + # comment + ] +}moee" # comment +# ... but not here because of the ownline comment +aaaaaaaaaaaaaaaaaa = ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + [ + a, + b, + ] + }moee" + # comment +) + +# t-strings in other positions + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}": + pass + +if t"aaaaaaaaaaa {ttttteeeeeeeeest} more { # comment + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if t"aaaaaaaaaaa { + [ + ttttteeeeeeeeest, + ] +} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}": + pass + +if t"aaaaaaaaaaa { + [ + ttttteeeeeeeeest, + ] +} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +if t"aaaaaaaaaaa { + [ + ttttteeeeeeeeest, + ] +} more { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +}": + pass + +# For loops +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeee": + pass + +for a in ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +for a in t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression +}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +for a in ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# With statements +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeee": + pass + +with ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +with t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{ + expression +}moreeeeeeeeeeeeeeeeeeeeeeeeeeeeee": + pass + +with ( + t"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{expression}moreeeeeeeeeeeeeeeeeeeeeeeeeeeee" +): + pass + +# Assert statements +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccc{expression}dddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", t"cccccccccccccccc{ + expression +}dddddddddddddddd" + +assert t"aaaaaaaaa{expression}bbbbbbbbbbbb", ( + t"cccccccccccccccc{expression}dddddddddddddddd" +) + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression +}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccc{expression}dddddddddd" + +assert ( + t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +), t"ccccccc{expression}dddddddddd" + +assert t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{ + expression +}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", t"ccccccccccccccccccccc { + expression +} dddddddddddddddddddddddddd" + +assert ( + t"aaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +), ( + t"cccccccccccccccccccccccccccccccc {expression} ddddddddddddddddddddddddddddddddddddd" +) + +# t-strings as a single argument to a call expression to test whether it's huggable or not. +call(t"{testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}") + +call( + t"{testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}" +) + +call( + t"{ # comment + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }" +) + +call( + t"""aaaaaaaaaaaaaaaa bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}""" +) + +call( + t"""aaaaaaaaaaaaaaaa bbbbbbbbbb { + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + }""" +) + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb {testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee}""") + +call(t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb { + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment +}""") + +call( + t"""aaaaaaaaaaaaaaaa + bbbbbbbbbb { + testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee # comment + }""" +) + +call( + t"{ + aaaaaa + + '''test + more''' + }" +) + +# Indentation + +# What should be the indentation? +# https://github.com/astral-sh/ruff/discussions/9785#discussioncomment-8470590 +if indent0: + if indent1: + if indent2: + foo = t"""hello world +hello { + t"aaaaaaa { + [ + 'aaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbb', + 'ccccccccccccccccccccc', + 'ddddddddddddddddddddd', + ] + } bbbbbbbb" + + [ + "aaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbb", + "ccccccccccccccccccccc", + "ddddddddddddddddddddd", + ] + } -------- +""" + + +# Implicit concatenated t-string containing quotes +_ = ( + "This string should change its quotes to double quotes" + t"This string uses double quotes in an expression {"it's a quote"}" + t"This t-string does not use any quotes." +) + +# Regression test for https://github.com/astral-sh/ruff/issues/14487 +t"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc" + +# Regression test for https://github.com/astral-sh/ruff/issues/14778 +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' if True else ''}" +t"{'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' if True else ''}" + +# Quotes reuse +t"{'a'}" + +# 312+, it's okay to change the outer quotes even when there's a debug expression using the same quotes +t"foo {10 + len("bar")=}" +t"""foo {10 + len("""bar""")=}""" + +# 312+, it's okay to change the quotes here without creating an invalid t-string +t"{'''other " '''}" +t"{'''other " ''' + 'more'}" +t"{b'''other " '''}" +t"{t'''other " '''}" + + +# Regression tests for https://github.com/astral-sh/ruff/issues/13935 +t'{1: hy "user"}' +t'{1:hy "user"}' +t'{1: abcd "{1}" }' +t'{1: abcd "{"aa"}" }' +t'{1=: "abcd {'aa'}}' +t"{x:a{z:hy \"user\"}} '''" + +# Changing the outer quotes is fine because the format-spec is in a nested expression. +t"{t'{z=:hy "user"}'} '''" + + +# We have to be careful about changing the quotes if the t-string has a debug expression because it is inserted verbatim. +t'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error +t"{1=: abcd \'\'}" # Changing the quotes here is fine because the inner quotes aren't the opposite quotes +t"{1=: abcd \"\"}" # Changing the quotes here is fine because the inner quotes are escaped +# Don't change the quotes in the following cases: +t'{x=:hy "user"} \'\'\'' +t'{x=:a{y:hy "user"}} \'\'\'' +t'{x=:a{y:{z:hy "user"}}} \'\'\'' +t'{x:a{y=:{z:hy "user"}}} \'\'\'' + +# This is fine because the debug expression and format spec are in a nested expression + +t"""{1=: "this" is fine}""" +t"""{1=: "this" is fine}""" # Change quotes to double quotes because they're preferred +t"{1=: {'ab"cd"'}}" # It's okay if the quotes are in an expression part. + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15459 +print(t"{ {1, 2, 3} - {2} }") +print(t"{ {1: 2}.keys() }") +print(t"{({1, 2, 3}) - ({2})}") +print(t"{1, 2, {3}}") +print(t"{(1, 2, {3})}") + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(t"{({},)}") # A single item tuple gets parenthesized +print(t"{({}.values(),)}") +print(t"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print( + t"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + ( + {}, + 1, + ) + }" +) + + +# Regression tests for https://github.com/astral-sh/ruff/issues/15536 +print(t"{ {}, 1 }") +``` diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_empty_expression.py b/crates/ruff_python_parser/resources/inline/err/t_string_empty_expression.py new file mode 100644 index 00000000000000..257a0e1209468e --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_empty_expression.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.14"} +t"{}" +t"{ }" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_name_tok.py b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_name_tok.py new file mode 100644 index 00000000000000..dcea20f590b2c6 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_name_tok.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.14"} +t"{x!z}" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_other_tok.py b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_other_tok.py new file mode 100644 index 00000000000000..61fb5815ba89ca --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_other_tok.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.14"} +t"{x!123}" +t"{x!'a'}" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_invalid_starred_expr.py b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_starred_expr.py new file mode 100644 index 00000000000000..77bf4eb55f4605 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_invalid_starred_expr.py @@ -0,0 +1,5 @@ +# parse_options: {"target-version": "3.14"} +# Starred expression inside t-string has a minimum precedence of bitwise or. +t"{*}" +t"{*x and y}" +t"{*yield x}" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py b/crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py new file mode 100644 index 00000000000000..0d9e70011cabd5 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.14"} +t"{lambda x: x}" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py b/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py new file mode 100644 index 00000000000000..b943b533e525f0 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py @@ -0,0 +1,6 @@ +# parse_options: {"target-version": "3.14"} +t"{" +t"{foo!r" +t"{foo=" +t"{" +t"""{""" diff --git a/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace_in_format_spec.py b/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace_in_format_spec.py new file mode 100644 index 00000000000000..cced3bb064bede --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace_in_format_spec.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.14"} +t"hello {x:" +t"hello {x:.3f" diff --git a/crates/ruff_python_parser/resources/inline/ok/param_with_annotation.py b/crates/ruff_python_parser/resources/inline/ok/param_with_annotation.py index e109ecaebc34f8..c8c0425ce6f94e 100644 --- a/crates/ruff_python_parser/resources/inline/ok/param_with_annotation.py +++ b/crates/ruff_python_parser/resources/inline/ok/param_with_annotation.py @@ -1,3 +1,2 @@ def foo(arg: int): ... def foo(arg: lambda x: x): ... -def foo(arg: (x := int)): ... diff --git a/crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314.py b/crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314.py new file mode 100644 index 00000000000000..18cfc9a08f339c --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314.py @@ -0,0 +1,10 @@ +# parse_options: {"target-version": "3.14"} +t'Magic wand: { bag['wand'] }' # nested quotes +t"{'\n'.join(a)}" # escape sequence +t'''A complex trick: { + bag['bag'] # comment +}''' +t"{t"{t"{t"{t"{t"{1+1}"}"}"}"}"}" # arbitrary nesting +t"{t'''{"nested"} inner'''} outer" # nested (triple) quotes +t"test {a \ + } more" # line continuation diff --git a/crates/ruff_python_parser/resources/valid/expressions/t_string.py b/crates/ruff_python_parser/resources/valid/expressions/t_string.py new file mode 100644 index 00000000000000..0bb6278492490d --- /dev/null +++ b/crates/ruff_python_parser/resources/valid/expressions/t_string.py @@ -0,0 +1,74 @@ +# Empty t-strings +t"" +t"" +t'' +t"""""" +t'''''' + +t"{" t"}" +t"{foo!s}" +t"{3,}" +t"{3!=4:}" +t'{3:{"}"}>10}' +t'{3:{"{"}>10}' +t"{ foo = }" +t"{ foo = :.3f }" +t"{ foo = !s }" +t"{ 1, 2 = }" +t'{t"{3.1415=:.1f}":*^20}' + +{"foo " t"bar {x + y} " "baz": 10} +match foo: + case "one": + pass + case "implicitly " "concatenated": + pass + +t"\{foo}\{bar:\}" +t"\\{{foo\\}}" +t"""{ + foo:x + y + z +}""" +t"{ ( foo ) = }" + +t"normal {foo} {{another}} {bar} {{{three}}}" +t"normal {foo!a} {bar!s} {baz!r} {foobar}" +t"normal {x:y + 2}" +t"{x:{{1}.pop()}}" +t"{(lambda x:{x})}" +t"{x =}" +t"{ x = }" +t"{x=!a}" +t"{x:.3f!r =}" +t"{x = !r :.3f}" +t"{x:.3f=!r}" +"hello" t"{x}" +t"{x}" t"{y}" +t"{x}" "world" +t"Invalid args in command: {command, *args}" +"foo" t"{x}" "bar" +( + t"a" + t"b" + "c" + rt"d" + fr"e" +) + +# With unicode strings +u"foo" t"{bar}" "baz" " some" +"foo" t"{bar}" u"baz" " some" +"foo" t"{bar}" "baz" u" some" +u"foo" t"bar {baz} really" u"bar" "no" + + +# With f-strings +f"{this}" t"{that}" +t"{this}"f"{that}" +t"{this}" "that" f"{other}" +f"one {this} two" "that" t"three {other} four" + +# Nesting +t"{f"{t"{this}"}"}" diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 2620f67c77ba64..b7bacffb57d57a 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Display}; use ruff_python_ast::PythonVersion; use ruff_text_size::{Ranged, TextRange}; -use crate::TokenKind; +use crate::{TokenKind, string::InterpolatedStringKind}; /// Represents represent errors that occur during parsing and are /// returned by the `parse_*` functions. @@ -48,9 +48,9 @@ impl ParseError { } } -/// Represents the different types of errors that can occur during parsing of an f-string. +/// Represents the different types of errors that can occur during parsing of an f-string or t-string. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum FStringErrorType { +pub enum InterpolatedStringErrorType { /// Expected a right brace after an opened left brace. UnclosedLbrace, /// An invalid conversion flag was encountered. @@ -65,9 +65,9 @@ pub enum FStringErrorType { LambdaWithoutParentheses, } -impl std::fmt::Display for FStringErrorType { +impl std::fmt::Display for InterpolatedStringErrorType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use FStringErrorType::{ + use InterpolatedStringErrorType::{ InvalidConversionFlag, LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace, UnterminatedString, UnterminatedTripleQuotedString, }; @@ -177,12 +177,26 @@ pub enum ParseErrorType { /// An unexpected token was found at the end of an expression parsing UnexpectedExpressionToken, - /// An f-string error containing the [`FStringErrorType`]. - FStringError(FStringErrorType), + /// An f-string error containing the [`InterpolatedStringErrorType`]. + FStringError(InterpolatedStringErrorType), + /// A t-string error containing the [`InterpolatedStringErrorType`]. + TStringError(InterpolatedStringErrorType), /// Parser encountered an error during lexing. Lexical(LexicalErrorType), } +impl ParseErrorType { + pub(crate) fn from_interpolated_string_error( + error: InterpolatedStringErrorType, + string_kind: InterpolatedStringKind, + ) -> Self { + match string_kind { + InterpolatedStringKind::FString => Self::FStringError(error), + InterpolatedStringKind::TString => Self::TStringError(error), + } + } +} + impl std::error::Error for ParseErrorType {} impl std::fmt::Display for ParseErrorType { @@ -292,6 +306,9 @@ impl std::fmt::Display for ParseErrorType { ParseErrorType::FStringError(fstring_error) => { write!(f, "f-string: {fstring_error}") } + ParseErrorType::TStringError(tstring_error) => { + write!(f, "t-string: {tstring_error}") + } ParseErrorType::UnexpectedExpressionToken => { write!(f, "Unexpected token at the end of an expression") } @@ -375,8 +392,10 @@ pub enum LexicalErrorType { IndentationError, /// An unrecognized token was encountered. UnrecognizedToken { tok: char }, - /// An f-string error containing the [`FStringErrorType`]. - FStringError(FStringErrorType), + /// An f-string error containing the [`InterpolatedStringErrorType`]. + FStringError(InterpolatedStringErrorType), + /// A t-string error containing the [`InterpolatedStringErrorType`]. + TStringError(InterpolatedStringErrorType), /// Invalid character encountered in a byte literal. InvalidByteLiteral, /// An unexpected character was encountered after a line continuation. @@ -389,11 +408,24 @@ pub enum LexicalErrorType { impl std::error::Error for LexicalErrorType {} +impl LexicalErrorType { + pub(crate) fn from_interpolated_string_error( + error: InterpolatedStringErrorType, + string_kind: InterpolatedStringKind, + ) -> Self { + match string_kind { + InterpolatedStringKind::FString => Self::FStringError(error), + InterpolatedStringKind::TString => Self::TStringError(error), + } + } +} + impl std::fmt::Display for LexicalErrorType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { LexicalErrorType::StringError => write!(f, "Got unexpected string"), LexicalErrorType::FStringError(error) => write!(f, "f-string: {error}"), + LexicalErrorType::TStringError(error) => write!(f, "t-string: {error}"), LexicalErrorType::InvalidByteLiteral => { write!(f, "bytes can only contain ASCII literal characters") } @@ -848,6 +880,12 @@ pub enum UnsupportedSyntaxErrorKind { /// /// [PEP 758]: https://peps.python.org/pep-0758/ UnparenthesizedExceptionTypes, + /// Represents the use of a template string (t-string) + /// literal prior to the implementation of [PEP 750] + /// in Python 3.14. + /// + /// [PEP 750]: https://peps.python.org/pep-0750/ + TemplateStrings, } impl Display for UnsupportedSyntaxError { @@ -928,6 +966,7 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => { "Multiple exception types must be parenthesized" } + UnsupportedSyntaxErrorKind::TemplateStrings => "Cannot use t-strings", }; write!( @@ -998,6 +1037,7 @@ impl UnsupportedSyntaxErrorKind { UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => { Change::Added(PythonVersion::PY314) } + UnsupportedSyntaxErrorKind::TemplateStrings => Change::Added(PythonVersion::PY314), } } diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index 9e3d47f89007ea..d04f377678a988 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -18,15 +18,17 @@ use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Mode; -use crate::error::{FStringErrorType, LexicalError, LexicalErrorType}; +use crate::error::{InterpolatedStringErrorType, LexicalError, LexicalErrorType}; use crate::lexer::cursor::{Cursor, EOF_CHAR}; -use crate::lexer::fstring::{FStringContext, FStrings, FStringsCheckpoint}; use crate::lexer::indentation::{Indentation, Indentations, IndentationsCheckpoint}; +use crate::lexer::interpolated_string::{ + InterpolatedStringContext, InterpolatedStrings, InterpolatedStringsCheckpoint, +}; use crate::token::{TokenFlags, TokenKind, TokenValue}; mod cursor; -mod fstring; mod indentation; +mod interpolated_string; const BOM: char = '\u{feff}'; @@ -65,8 +67,8 @@ pub struct Lexer<'src> { /// Lexer mode. mode: Mode, - /// F-string contexts. - fstrings: FStrings, + /// F-string and t-string contexts. + interpolated_strings: InterpolatedStrings, /// Errors encountered while lexing. errors: Vec, @@ -102,7 +104,7 @@ impl<'src> Lexer<'src> { indentations: Indentations::default(), pending_indentation: None, mode, - fstrings: FStrings::default(), + interpolated_strings: InterpolatedStrings::default(), errors: Vec::new(), }; @@ -162,11 +164,11 @@ impl<'src> Lexer<'src> { } fn lex_token(&mut self) -> TokenKind { - if let Some(fstring) = self.fstrings.current() { - if !fstring.is_in_expression(self.nesting) { - if let Some(token) = self.lex_fstring_middle_or_end() { - if matches!(token, TokenKind::FStringEnd) { - self.fstrings.pop(); + if let Some(interpolated_string) = self.interpolated_strings.current() { + if !interpolated_string.is_in_interpolation(self.nesting) { + if let Some(token) = self.lex_interpolated_string_middle_or_end() { + if token.is_interpolated_string_end() { + self.interpolated_strings.pop(); } return token; } @@ -506,23 +508,26 @@ impl<'src> Lexer<'src> { TokenKind::Lbrace } '}' => { - if let Some(fstring) = self.fstrings.current_mut() { - if fstring.nesting() == self.nesting { - return self.push_error(LexicalError::new( - LexicalErrorType::FStringError(FStringErrorType::SingleRbrace), - self.token_range(), - )); + if let Some(interpolated_string) = self.interpolated_strings.current_mut() { + if interpolated_string.nesting() == self.nesting { + let error_type = LexicalErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::SingleRbrace, + interpolated_string.kind(), + ); + return self.push_error(LexicalError::new(error_type, self.token_range())); } - fstring.try_end_format_spec(self.nesting); + interpolated_string.try_end_format_spec(self.nesting); } self.nesting = self.nesting.saturating_sub(1); TokenKind::Rbrace } ':' => { if self - .fstrings + .interpolated_strings .current_mut() - .is_some_and(|fstring| fstring.try_start_format_spec(self.nesting)) + .is_some_and(|interpolated_string| { + interpolated_string.try_start_format_spec(self.nesting) + }) { TokenKind::Colon } else if self.cursor.eat_char('=') { @@ -573,8 +578,8 @@ impl<'src> Lexer<'src> { self.state = State::AfterNewline; TokenKind::Newline } else { - if let Some(fstring) = self.fstrings.current_mut() { - fstring.try_end_format_spec(self.nesting); + if let Some(interpolated_string) = self.interpolated_strings.current_mut() { + interpolated_string.try_end_format_spec(self.nesting); } TokenKind::NonLogicalNewline }; @@ -586,8 +591,8 @@ impl<'src> Lexer<'src> { self.state = State::AfterNewline; TokenKind::Newline } else { - if let Some(fstring) = self.fstrings.current_mut() { - fstring.try_end_format_spec(self.nesting); + if let Some(interpolated_string) = self.interpolated_strings.current_mut() { + interpolated_string.try_end_format_spec(self.nesting); } TokenKind::NonLogicalNewline }; @@ -610,7 +615,7 @@ impl<'src> Lexer<'src> { /// Lex an identifier. Also used for keywords and string/bytes literals with a prefix. fn lex_identifier(&mut self, first: char) -> TokenKind { - // Detect potential string like rb'' b'' f'' u'' r'' + // Detect potential string like rb'' b'' f'' t'' u'' r'' let quote = match (first, self.cursor.first()) { (_, quote @ ('\'' | '"')) => self.try_single_char_prefix(first).then(|| { self.cursor.bump(); @@ -627,8 +632,10 @@ impl<'src> Lexer<'src> { }; if let Some(quote) = quote { - if self.current_flags.is_f_string() { - return self.lex_fstring_start(quote); + if self.current_flags.is_interpolated_string() { + if let Some(kind) = self.lex_interpolated_string_start(quote) { + return kind; + } } return self.lex_string(quote); @@ -711,6 +718,7 @@ impl<'src> Lexer<'src> { fn try_single_char_prefix(&mut self, first: char) -> bool { match first { 'f' | 'F' => self.current_flags |= TokenFlags::F_STRING, + 't' | 'T' => self.current_flags |= TokenFlags::T_STRING, 'u' | 'U' => self.current_flags |= TokenFlags::UNICODE_STRING, 'b' | 'B' => self.current_flags |= TokenFlags::BYTE_STRING, 'r' => self.current_flags |= TokenFlags::RAW_STRING_LOWERCASE, @@ -730,6 +738,12 @@ impl<'src> Lexer<'src> { ['R', 'f' | 'F'] | ['f' | 'F', 'R'] => { self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_UPPERCASE; } + ['r', 't' | 'T'] | ['t' | 'T', 'r'] => { + self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_LOWERCASE; + } + ['R', 't' | 'T'] | ['t' | 'T', 'R'] => { + self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_UPPERCASE; + } ['r', 'b' | 'B'] | ['b' | 'B', 'r'] => { self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_LOWERCASE; } @@ -741,8 +755,8 @@ impl<'src> Lexer<'src> { true } - /// Lex a f-string start token. - fn lex_fstring_start(&mut self, quote: char) -> TokenKind { + /// Lex a f-string or t-string start token if positioned at the start of an f-string or t-string. + fn lex_interpolated_string_start(&mut self, quote: char) -> Option { #[cfg(debug_assertions)] debug_assert_eq!(self.cursor.previous(), quote); @@ -754,27 +768,31 @@ impl<'src> Lexer<'src> { self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING; } - self.fstrings - .push(FStringContext::new(self.current_flags, self.nesting)); + let ftcontext = InterpolatedStringContext::new(self.current_flags, self.nesting)?; + + let kind = ftcontext.kind(); + + self.interpolated_strings.push(ftcontext); - TokenKind::FStringStart + Some(kind.start_token()) } - /// Lex a f-string middle or end token. - fn lex_fstring_middle_or_end(&mut self) -> Option { + /// Lex an f-string or t-string middle or end token. + fn lex_interpolated_string_middle_or_end(&mut self) -> Option { // SAFETY: Safe because the function is only called when `self.fstrings` is not empty. - let fstring = self.fstrings.current().unwrap(); + let interpolated_string = self.interpolated_strings.current().unwrap(); + let string_kind = interpolated_string.kind(); // Check if we're at the end of the f-string. - if fstring.is_triple_quoted() { - let quote_char = fstring.quote_char(); + if interpolated_string.is_triple_quoted() { + let quote_char = interpolated_string.quote_char(); if self.cursor.eat_char3(quote_char, quote_char, quote_char) { - self.current_flags = fstring.flags(); - return Some(TokenKind::FStringEnd); + self.current_flags = interpolated_string.flags(); + return Some(string_kind.end_token()); } - } else if self.cursor.eat_char(fstring.quote_char()) { - self.current_flags = fstring.flags(); - return Some(TokenKind::FStringEnd); + } else if self.cursor.eat_char(interpolated_string.quote_char()) { + self.current_flags = interpolated_string.flags(); + return Some(string_kind.end_token()); } // We have to decode `{{` and `}}` into `{` and `}` respectively. As an @@ -786,7 +804,7 @@ impl<'src> Lexer<'src> { let mut last_offset = self.offset(); // This isn't going to change for the duration of the loop. - let in_format_spec = fstring.is_in_format_spec(self.nesting); + let in_format_spec = interpolated_string.is_in_format_spec(self.nesting); let mut in_named_unicode = false; @@ -796,18 +814,18 @@ impl<'src> Lexer<'src> { // in the source code and the one returned by `self.cursor.first()` when // we reach the end of the source code. EOF_CHAR if self.cursor.is_eof() => { - let error = if fstring.is_triple_quoted() { - FStringErrorType::UnterminatedTripleQuotedString + let error = if interpolated_string.is_triple_quoted() { + InterpolatedStringErrorType::UnterminatedTripleQuotedString } else { - FStringErrorType::UnterminatedString + InterpolatedStringErrorType::UnterminatedString }; - self.fstrings.pop(); + self.interpolated_strings.pop(); return Some(self.push_error(LexicalError::new( - LexicalErrorType::FStringError(error), + LexicalErrorType::from_interpolated_string_error(error, string_kind), self.token_range(), ))); } - '\n' | '\r' if !fstring.is_triple_quoted() => { + '\n' | '\r' if !interpolated_string.is_triple_quoted() => { // If we encounter a newline while we're in a format spec, then // we stop here and let the lexer emit the newline token. // @@ -815,9 +833,12 @@ impl<'src> Lexer<'src> { if in_format_spec { break; } - self.fstrings.pop(); + self.interpolated_strings.pop(); return Some(self.push_error(LexicalError::new( - LexicalErrorType::FStringError(FStringErrorType::UnterminatedString), + LexicalErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::UnterminatedString, + string_kind, + ), self.token_range(), ))); } @@ -827,7 +848,7 @@ impl<'src> Lexer<'src> { // Don't consume `{` or `}` as we want them to be emitted as tokens. // They will be handled in the next iteration. continue; - } else if !fstring.is_raw_string() { + } else if !interpolated_string.is_raw_string() { if self.cursor.eat_char2('N', '{') { in_named_unicode = true; continue; @@ -840,8 +861,8 @@ impl<'src> Lexer<'src> { self.cursor.bump(); } } - quote @ ('\'' | '"') if quote == fstring.quote_char() => { - if let Some(triple_quotes) = fstring.triple_quotes() { + quote @ ('\'' | '"') if quote == interpolated_string.quote_char() => { + if let Some(triple_quotes) = interpolated_string.triple_quotes() { if self.cursor.rest().starts_with(triple_quotes) { break; } @@ -892,10 +913,10 @@ impl<'src> Lexer<'src> { normalized }; - self.current_value = TokenValue::FStringMiddle(value.into_boxed_str()); + self.current_value = TokenValue::InterpolatedStringMiddle(value.into_boxed_str()); - self.current_flags = fstring.flags(); - Some(TokenKind::FStringMiddle) + self.current_flags = interpolated_string.flags(); + Some(string_kind.middle_token()) } /// Lex a string literal. @@ -1403,9 +1424,9 @@ impl<'src> Lexer<'src> { // i.e., it recovered from an unclosed parenthesis (`(`, `[`, or `{`). self.nesting -= 1; - // The lexer can't be moved back for a triple-quoted f-string because the newlines are - // part of the f-string itself, so there is no newline token to be emitted. - if self.current_flags.is_triple_quoted_fstring() { + // The lexer can't be moved back for a triple-quoted f/t-string because the newlines are + // part of the f/t-string itself, so there is no newline token to be emitted. + if self.current_flags.is_triple_quoted_interpolated_string() { return false; } @@ -1478,7 +1499,7 @@ impl<'src> Lexer<'src> { nesting: self.nesting, indentations_checkpoint: self.indentations.checkpoint(), pending_indentation: self.pending_indentation, - fstrings_checkpoint: self.fstrings.checkpoint(), + interpolated_strings_checkpoint: self.interpolated_strings.checkpoint(), errors_position: self.errors.len(), } } @@ -1495,7 +1516,7 @@ impl<'src> Lexer<'src> { nesting, indentations_checkpoint, pending_indentation, - fstrings_checkpoint, + interpolated_strings_checkpoint, errors_position, } = checkpoint; @@ -1512,7 +1533,8 @@ impl<'src> Lexer<'src> { self.nesting = nesting; self.indentations.rewind(indentations_checkpoint); self.pending_indentation = pending_indentation; - self.fstrings.rewind(fstrings_checkpoint); + self.interpolated_strings + .rewind(interpolated_strings_checkpoint); self.errors.truncate(errors_position); } @@ -1531,7 +1553,7 @@ pub(crate) struct LexerCheckpoint { nesting: u32, indentations_checkpoint: IndentationsCheckpoint, pending_indentation: Option, - fstrings_checkpoint: FStringsCheckpoint, + interpolated_strings_checkpoint: InterpolatedStringsCheckpoint, errors_position: usize, } @@ -2450,6 +2472,190 @@ f"{(lambda x:{x})}" assert_snapshot!(lex_source(source)); } + #[test] + fn test_empty_tstrings() { + let source = r#"t"" "" t"" t'' '' t"""""" t''''''"#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_prefix() { + let source = r#"t"" t"" rt"" rt"" Rt"" Rt"" tr"" Tr"" tR"" TR"""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring() { + let source = r#"t"normal {foo} {{another}} {bar} {{{three}}}""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_parentheses() { + let source = r#"t"{}" t"{{}}" t" {}" t"{{{}}}" t"{{{{}}}}" t" {} {{}} {{{}}} {{{{}}}} ""#; + assert_snapshot!(lex_source(source)); + } + + fn tstring_single_quote_escape_eol(eol: &str) -> LexerOutput { + let source = format!(r"t'text \{eol} more text'"); + lex_source(&source) + } + + #[test] + fn test_tstring_single_quote_escape_unix_eol() { + assert_snapshot!(tstring_single_quote_escape_eol(UNIX_EOL)); + } + + #[test] + fn test_tstring_single_quote_escape_mac_eol() { + assert_snapshot!(tstring_single_quote_escape_eol(MAC_EOL)); + } + + #[test] + fn test_tstring_single_quote_escape_windows_eol() { + assert_snapshot!(tstring_single_quote_escape_eol(WINDOWS_EOL)); + } + + #[test] + fn test_tstring_escape() { + let source = r#"t"\{x:\"\{x}} \"\"\ + end""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_escape_braces() { + let source = r"t'\{foo}' t'\\{foo}' t'\{{foo}}' t'\\{{foo}}'"; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_escape_raw() { + let source = r#"rt"\{x:\"\{x}} \"\"\ + end""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_named_unicode() { + let source = r#"t"\N{BULLET} normal \Nope \N""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_named_unicode_raw() { + let source = r#"rt"\N{BULLET} normal""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_named_expression() { + let source = r#"t"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_format_spec() { + let source = r#"t"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_multiline_format_spec() { + // The last t-string is invalid syntactically but we should still lex it. + // Note that the `b` is a `Name` token and not a `TStringMiddle` token. + let source = r"t'''__{ + x:d +}__''' +t'''__{ + x:a + b + c +}__''' +t'__{ + x:d +}__' +t'__{ + x:a + b +}__' +"; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_conversion() { + let source = r#"t"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_nested() { + let source = r#"t"foo {t"bar {x + t"{wow}"}"} baz" t'foo {t'bar'} some {t"another"}'"#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_expression_multiline() { + let source = r#"t"first { + x + * + y +} second""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_multiline() { + let source = r#"t""" +hello + world +""" t''' + world +hello +''' t"some {t"""multiline +allowed {x}"""} string""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_comments() { + let source = r#"t""" +# not a comment { # comment { + x +} # not a comment +""""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_ipy_escape_command() { + let source = r#"t"foo {!pwd} bar""#; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_lambda_expression() { + let source = r#" +t"{lambda x:{x}}" +t"{(lambda x:{x})}" +"# + .trim(); + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_tstring_with_nul_char() { + let source = r"t'\0'"; + assert_snapshot!(lex_source(source)); + } + + #[test] + fn test_nested_t_and_fstring() { + let source = r#"t"foo {f"bar {x + t"{wow}"}"} baz" f'foo {t'bar'!r} some {f"another"}'"#; + assert_snapshot!(lex_source(source)); + } + #[test] fn test_match_softkeyword_in_notebook() { let source = r"match foo: @@ -2458,7 +2664,7 @@ f"{(lambda x:{x})}" assert_snapshot!(lex_jupyter_source(source)); } - fn lex_fstring_error(source: &str) -> FStringErrorType { + fn lex_fstring_error(source: &str) -> InterpolatedStringErrorType { let output = lex(source, Mode::Module, TextSize::default()); match output .errors @@ -2474,7 +2680,9 @@ f"{(lambda x:{x})}" #[test] fn test_fstring_error() { - use FStringErrorType::{SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString}; + use InterpolatedStringErrorType::{ + SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString, + }; assert_eq!(lex_fstring_error("f'}'"), SingleRbrace); assert_eq!(lex_fstring_error("f'{{}'"), SingleRbrace); @@ -2499,4 +2707,48 @@ f"{(lambda x:{x})}" UnterminatedTripleQuotedString ); } + + fn lex_tstring_error(source: &str) -> InterpolatedStringErrorType { + let output = lex(source, Mode::Module, TextSize::default()); + match output + .errors + .into_iter() + .next() + .expect("lexer should give at least one error") + .into_error() + { + LexicalErrorType::TStringError(error) => error, + err => panic!("Expected TStringError: {err:?}"), + } + } + + #[test] + fn test_tstring_error() { + use InterpolatedStringErrorType::{ + SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString, + }; + + assert_eq!(lex_tstring_error("t'}'"), SingleRbrace); + assert_eq!(lex_tstring_error("t'{{}'"), SingleRbrace); + assert_eq!(lex_tstring_error("t'{{}}}'"), SingleRbrace); + assert_eq!(lex_tstring_error("t'foo}'"), SingleRbrace); + assert_eq!(lex_tstring_error(r"t'\u007b}'"), SingleRbrace); + assert_eq!(lex_tstring_error("t'{a:b}}'"), SingleRbrace); + assert_eq!(lex_tstring_error("t'{3:}}>10}'"), SingleRbrace); + assert_eq!(lex_tstring_error(r"t'\{foo}\}'"), SingleRbrace); + + assert_eq!(lex_tstring_error(r#"t""#), UnterminatedString); + assert_eq!(lex_tstring_error(r"t'"), UnterminatedString); + + assert_eq!(lex_tstring_error(r#"t""""#), UnterminatedTripleQuotedString); + assert_eq!(lex_tstring_error(r"t'''"), UnterminatedTripleQuotedString); + assert_eq!( + lex_tstring_error(r#"t"""""#), + UnterminatedTripleQuotedString + ); + assert_eq!( + lex_tstring_error(r#"t""""""#), + UnterminatedTripleQuotedString + ); + } } diff --git a/crates/ruff_python_parser/src/lexer/fstring.rs b/crates/ruff_python_parser/src/lexer/interpolated_string.rs similarity index 61% rename from crates/ruff_python_parser/src/lexer/fstring.rs rename to crates/ruff_python_parser/src/lexer/interpolated_string.rs index 7b702a77b72696..826edfa79642e4 100644 --- a/crates/ruff_python_parser/src/lexer/fstring.rs +++ b/crates/ruff_python_parser/src/lexer/interpolated_string.rs @@ -1,31 +1,45 @@ use ruff_python_ast::StringFlags; +use crate::string::InterpolatedStringKind; + use super::TokenFlags; -/// The context representing the current f-string that the lexer is in. +/// The context representing the current f-string or t-string that the lexer is in. #[derive(Clone, Debug)] -pub(crate) struct FStringContext { +pub(crate) struct InterpolatedStringContext { flags: TokenFlags, - /// The level of nesting for the lexer when it entered the current f-string. + /// The level of nesting for the lexer when it entered the current f/t-string. /// The nesting level includes all kinds of parentheses i.e., round, square, /// and curly. nesting: u32, - /// The current depth of format spec for the current f-string. This is because + /// The current depth of format spec for the current f/t-string. This is because /// there can be multiple format specs nested for the same f-string. /// For example, `{a:{b:{c}}}` has 3 format specs. format_spec_depth: u32, } -impl FStringContext { - pub(crate) const fn new(flags: TokenFlags, nesting: u32) -> Self { - assert!(flags.is_f_string()); +impl InterpolatedStringContext { + pub(crate) const fn new(flags: TokenFlags, nesting: u32) -> Option { + if flags.is_interpolated_string() { + Some(Self { + flags, + nesting, + format_spec_depth: 0, + }) + } else { + None + } + } - Self { - flags, - nesting, - format_spec_depth: 0, + pub(crate) fn kind(&self) -> InterpolatedStringKind { + if self.flags.is_f_string() { + InterpolatedStringKind::FString + } else if self.flags.is_t_string() { + InterpolatedStringKind::TString + } else { + unreachable!("Can only be constructed when f-string or t-string flag is present") } } @@ -68,15 +82,15 @@ impl FStringContext { current_nesting.saturating_sub(self.nesting) } - /// Returns `true` if the lexer is in a f-string expression i.e., between + /// Returns `true` if the lexer is in an f-string expression or t-string interpolation i.e., between /// two curly braces. - pub(crate) const fn is_in_expression(&self, current_nesting: u32) -> bool { + pub(crate) const fn is_in_interpolation(&self, current_nesting: u32) -> bool { self.open_parentheses_count(current_nesting) > self.format_spec_depth } /// Returns `true` if the lexer is in a f-string format spec i.e., after a colon. pub(crate) const fn is_in_format_spec(&self, current_nesting: u32) -> bool { - self.format_spec_depth > 0 && !self.is_in_expression(current_nesting) + self.format_spec_depth > 0 && !self.is_in_interpolation(current_nesting) } /// Returns `true` if the context is in a valid position to start format spec @@ -106,38 +120,38 @@ impl FStringContext { } } -/// The f-strings stack is used to keep track of all the f-strings that the -/// lexer encounters. This is necessary because f-strings can be nested. +/// The interpolated strings stack is used to keep track of all the f-strings and t-strings that the +/// lexer encounters. This is necessary because f-strings and t-strings can be nested. #[derive(Debug, Default)] -pub(crate) struct FStrings { - stack: Vec, +pub(crate) struct InterpolatedStrings { + stack: Vec, } -impl FStrings { - pub(crate) fn push(&mut self, context: FStringContext) { +impl InterpolatedStrings { + pub(crate) fn push(&mut self, context: InterpolatedStringContext) { self.stack.push(context); } - pub(crate) fn pop(&mut self) -> Option { + pub(crate) fn pop(&mut self) -> Option { self.stack.pop() } - pub(crate) fn current(&self) -> Option<&FStringContext> { + pub(crate) fn current(&self) -> Option<&InterpolatedStringContext> { self.stack.last() } - pub(crate) fn current_mut(&mut self) -> Option<&mut FStringContext> { + pub(crate) fn current_mut(&mut self) -> Option<&mut InterpolatedStringContext> { self.stack.last_mut() } - pub(crate) fn checkpoint(&self) -> FStringsCheckpoint { - FStringsCheckpoint(self.stack.clone()) + pub(crate) fn checkpoint(&self) -> InterpolatedStringsCheckpoint { + InterpolatedStringsCheckpoint(self.stack.clone()) } - pub(crate) fn rewind(&mut self, checkpoint: FStringsCheckpoint) { + pub(crate) fn rewind(&mut self, checkpoint: InterpolatedStringsCheckpoint) { self.stack = checkpoint.0; } } #[derive(Debug, Clone)] -pub(crate) struct FStringsCheckpoint(Vec); +pub(crate) struct InterpolatedStringsCheckpoint(Vec); diff --git a/crates/ruff_python_parser/src/lib.rs b/crates/ruff_python_parser/src/lib.rs index fa5230a01677fc..346bd89aa81b90 100644 --- a/crates/ruff_python_parser/src/lib.rs +++ b/crates/ruff_python_parser/src/lib.rs @@ -67,8 +67,8 @@ use std::iter::FusedIterator; use std::ops::Deref; pub use crate::error::{ - FStringErrorType, LexicalErrorType, ParseError, ParseErrorType, UnsupportedSyntaxError, - UnsupportedSyntaxErrorKind, + InterpolatedStringErrorType, LexicalErrorType, ParseError, ParseErrorType, + UnsupportedSyntaxError, UnsupportedSyntaxErrorKind, }; pub use crate::parser::ParseOptions; pub use crate::token::{Token, TokenKind}; diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 3a3d91ce7540b9..73ce20666472b6 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -6,22 +6,27 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; use ruff_python_ast::name::Name; use ruff_python_ast::{ - self as ast, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FStringElement, FStringElements, - IpyEscapeKind, Number, Operator, OperatorPrecedence, StringFlags, UnaryOp, + self as ast, AnyStringFlags, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FString, + InterpolatedStringElement, InterpolatedStringElements, IpyEscapeKind, Number, Operator, + OperatorPrecedence, StringFlags, TString, UnaryOp, }; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::error::{FStringKind, StarTupleKind, UnparenthesizedNamedExprKind}; use crate::parser::progress::ParserProgress; use crate::parser::{FunctionKind, Parser, helpers}; -use crate::string::{StringType, parse_fstring_literal_element, parse_string_literal}; +use crate::string::{ + InterpolatedStringKind, StringType, parse_interpolated_string_literal_element, + parse_string_literal, +}; use crate::token::{TokenKind, TokenValue}; use crate::token_set::TokenSet; use crate::{ - FStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxError, UnsupportedSyntaxErrorKind, + InterpolatedStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxError, + UnsupportedSyntaxErrorKind, }; -use super::{FStringElementsKind, Parenthesized, RecoveryContextKind}; +use super::{InterpolatedStringElementsKind, Parenthesized, RecoveryContextKind}; /// A token set consisting of a newline or end of file. const NEWLINE_EOF_SET: TokenSet = TokenSet::new([TokenKind::Newline, TokenKind::EndOfFile]); @@ -54,6 +59,7 @@ pub(super) const EXPR_SET: TokenSet = TokenSet::new([ TokenKind::Not, TokenKind::Yield, TokenKind::FStringStart, + TokenKind::TStringStart, TokenKind::IpyEscapeCommand, ]) .union(LITERAL_SET); @@ -581,7 +587,9 @@ impl<'src> Parser<'src> { TokenKind::IpyEscapeCommand => { Expr::IpyEscapeCommand(self.parse_ipython_escape_command_expression()) } - TokenKind::String | TokenKind::FStringStart => self.parse_strings(), + TokenKind::String | TokenKind::FStringStart | TokenKind::TStringStart => { + self.parse_strings() + } TokenKind::Lpar => { return self.parse_parenthesized_expression(); } @@ -1177,12 +1185,15 @@ impl<'src> Parser<'src> { /// /// # Panics /// - /// If the parser isn't positioned at a `String` or `FStringStart` token. + /// If the parser isn't positioned at a `String`, `FStringStart`, or `TStringStart` token. /// /// See: (Search "strings:") pub(super) fn parse_strings(&mut self) -> Expr { - const STRING_START_SET: TokenSet = - TokenSet::new([TokenKind::String, TokenKind::FStringStart]); + const STRING_START_SET: TokenSet = TokenSet::new([ + TokenKind::String, + TokenKind::FStringStart, + TokenKind::TStringStart, + ]); let start = self.node_start(); let mut strings = vec![]; @@ -1194,8 +1205,16 @@ impl<'src> Parser<'src> { if self.at(TokenKind::String) { strings.push(self.parse_string_or_byte_literal()); - } else { - strings.push(StringType::FString(self.parse_fstring())); + } else if self.at(TokenKind::FStringStart) { + strings.push(StringType::FString( + self.parse_interpolated_string(InterpolatedStringKind::FString) + .into(), + )); + } else if self.at(TokenKind::TStringStart) { + strings.push(StringType::TString( + self.parse_interpolated_string(InterpolatedStringKind::TString) + .into(), + )); } } @@ -1219,6 +1238,10 @@ impl<'src> Parser<'src> { value: ast::FStringValue::single(fstring), range, }), + StringType::TString(tstring) => Expr::TString(ast::ExprTString { + value: ast::TStringValue::single(tstring), + range, + }), }, _ => self.handle_implicitly_concatenated_strings(strings, range), } @@ -1236,11 +1259,13 @@ impl<'src> Parser<'src> { ) -> Expr { assert!(strings.len() > 1); + let mut has_tstring = false; let mut has_fstring = false; let mut byte_literal_count = 0; for string in &strings { match string { StringType::FString(_) => has_fstring = true, + StringType::TString(_) => has_tstring = true, StringType::Bytes(_) => byte_literal_count += 1, StringType::Str(_) => {} } @@ -1269,7 +1294,7 @@ impl<'src> Parser<'src> { ); } // Only construct a byte expression if all the literals are bytes - // otherwise, we'll try either string or f-string. This is to retain + // otherwise, we'll try either string, t-string, or f-string. This is to retain // as much information as possible. Ordering::Equal => { let mut values = Vec::with_capacity(strings.len()); @@ -1310,7 +1335,7 @@ impl<'src> Parser<'src> { // ) // 2 + 2 - if !has_fstring { + if !has_fstring && !has_tstring { let mut values = Vec::with_capacity(strings.len()); for string in strings { values.push(match string { @@ -1324,10 +1349,34 @@ impl<'src> Parser<'src> { }); } + if has_tstring { + let mut parts = Vec::with_capacity(strings.len()); + for string in strings { + match string { + StringType::TString(tstring) => parts.push(ast::TStringPart::TString(tstring)), + StringType::FString(fstring) => { + parts.push(ruff_python_ast::TStringPart::FString(fstring)); + } + StringType::Str(string) => parts.push(ast::TStringPart::Literal(string)), + StringType::Bytes(bytes) => parts.push(ast::TStringPart::Literal( + ast::StringLiteral::invalid(bytes.range()), + )), + } + } + + return Expr::from(ast::ExprTString { + value: ast::TStringValue::concatenated(parts), + range, + }); + } + let mut parts = Vec::with_capacity(strings.len()); for string in strings { match string { StringType::FString(fstring) => parts.push(ast::FStringPart::FString(fstring)), + StringType::TString(_) => { + unreachable!("expected no tstring parts by this point") + } StringType::Str(string) => parts.push(ast::FStringPart::Literal(string)), StringType::Bytes(bytes) => parts.push(ast::FStringPart::Literal( ast::StringLiteral::invalid(bytes.range()), @@ -1388,24 +1437,32 @@ impl<'src> Parser<'src> { } } - /// Parses a f-string. + /// Parses an f/t-string. /// /// This does not handle implicitly concatenated strings. /// /// # Panics /// - /// If the parser isn't positioned at a `FStringStart` token. + /// If the parser isn't positioned at an `FStringStart` or + /// `TStringStart` token. /// - /// See: (Search "fstring:") + /// See: (Search "fstring:" or "tstring:") /// See: - fn parse_fstring(&mut self) -> ast::FString { + fn parse_interpolated_string( + &mut self, + kind: InterpolatedStringKind, + ) -> InterpolatedStringData { let start = self.node_start(); let flags = self.tokens.current_flags().as_any_string_flags(); - self.bump(TokenKind::FStringStart); - let elements = self.parse_fstring_elements(flags, FStringElementsKind::Regular); + self.bump(kind.start_token()); + let elements = self.parse_interpolated_string_elements( + flags, + InterpolatedStringElementsKind::Regular, + kind, + ); - self.expect(TokenKind::FStringEnd); + self.expect(kind.end_token()); // test_ok pep701_f_string_py312 // # parse_options: {"target-version": "3.12"} @@ -1419,6 +1476,18 @@ impl<'src> Parser<'src> { // f"test {a \ // } more" # line continuation + // test_ok pep750_t_string_py314 + // # parse_options: {"target-version": "3.14"} + // t'Magic wand: { bag['wand'] }' # nested quotes + // t"{'\n'.join(a)}" # escape sequence + // t'''A complex trick: { + // bag['bag'] # comment + // }''' + // t"{t"{t"{t"{t"{t"{1+1}"}"}"}"}"}" # arbitrary nesting + // t"{t'''{"nested"} inner'''} outer" # nested (triple) quotes + // t"test {a \ + // } more" # line continuation + // test_ok pep701_f_string_py311 // # parse_options: {"target-version": "3.11"} // f"outer {'# not a comment'}" @@ -1444,10 +1513,12 @@ impl<'src> Parser<'src> { let range = self.node_range(start); - if !self.options.target_version.supports_pep_701() { + if !self.options.target_version.supports_pep_701() + && matches!(kind, InterpolatedStringKind::FString) + { let quote_bytes = flags.quote_str().as_bytes(); let quote_len = flags.quote_len(); - for expr in elements.expressions() { + for expr in elements.interpolations() { for slash_position in memchr::memchr_iter(b'\\', self.source[expr.range].as_bytes()) { let slash_position = TextSize::try_from(slash_position).unwrap(); @@ -1471,10 +1542,10 @@ impl<'src> Parser<'src> { self.check_fstring_comments(range); } - ast::FString { + InterpolatedStringData { elements, range, - flags: ast::FStringFlags::from(flags), + flags, } } @@ -1490,80 +1561,87 @@ impl<'src> Parser<'src> { })); } - /// Parses a list of f-string elements. + /// Parses a list of f/t-string elements. /// /// # Panics /// - /// If the parser isn't positioned at a `{` or `FStringMiddle` token. - fn parse_fstring_elements( + /// If the parser isn't positioned at a `{`, `FStringMiddle`, + /// or `TStringMiddle` token. + fn parse_interpolated_string_elements( &mut self, flags: ast::AnyStringFlags, - kind: FStringElementsKind, - ) -> FStringElements { + elements_kind: InterpolatedStringElementsKind, + string_kind: InterpolatedStringKind, + ) -> ast::InterpolatedStringElements { let mut elements = vec![]; + let middle_token_kind = string_kind.middle_token(); + + self.parse_list( + RecoveryContextKind::InterpolatedStringElements(elements_kind), + |parser| { + let element = match parser.current_token_kind() { + TokenKind::Lbrace => ast::InterpolatedStringElement::from( + parser.parse_interpolated_element(flags, string_kind), + ), + tok if tok == middle_token_kind => { + let range = parser.current_token_range(); + let TokenValue::InterpolatedStringMiddle(value) = + parser.bump_value(middle_token_kind) + else { + unreachable!() + }; + InterpolatedStringElement::Literal( + parse_interpolated_string_literal_element(value, flags, range) + .unwrap_or_else(|lex_error| { + // test_err invalid_fstring_literal_element + // f'hello \N{INVALID} world' + // f"""hello \N{INVALID} world""" + let location = lex_error.location(); + parser.add_error( + ParseErrorType::Lexical(lex_error.into_error()), + location, + ); + ast::InterpolatedStringLiteralElement { + value: "".into(), + range, + } + }), + ) + } + // `Invalid` tokens are created when there's a lexical error, so + // we ignore it here to avoid creating unexpected token errors + TokenKind::Unknown => { + parser.bump_any(); + return; + } + tok => { + // This should never happen because the list parsing will only + // call this closure for the above token kinds which are the same + // as in the FIRST set. + unreachable!( + "{}: unexpected token `{tok:?}` at {:?}", + string_kind, + parser.current_token_range() + ); + } + }; + elements.push(element); + }, + ); - self.parse_list(RecoveryContextKind::FStringElements(kind), |parser| { - let element = match parser.current_token_kind() { - TokenKind::Lbrace => { - FStringElement::Expression(parser.parse_fstring_expression_element(flags)) - } - TokenKind::FStringMiddle => { - let range = parser.current_token_range(); - let TokenValue::FStringMiddle(value) = - parser.bump_value(TokenKind::FStringMiddle) - else { - unreachable!() - }; - FStringElement::Literal( - parse_fstring_literal_element(value, flags, range).unwrap_or_else( - |lex_error| { - // test_err invalid_fstring_literal_element - // f'hello \N{INVALID} world' - // f"""hello \N{INVALID} world""" - let location = lex_error.location(); - parser.add_error( - ParseErrorType::Lexical(lex_error.into_error()), - location, - ); - ast::FStringLiteralElement { - value: "".into(), - range, - } - }, - ), - ) - } - // `Invalid` tokens are created when there's a lexical error, so - // we ignore it here to avoid creating unexpected token errors - TokenKind::Unknown => { - parser.bump_any(); - return; - } - tok => { - // This should never happen because the list parsing will only - // call this closure for the above token kinds which are the same - // as in the FIRST set. - unreachable!( - "f-string: unexpected token `{tok:?}` at {:?}", - parser.current_token_range() - ); - } - }; - elements.push(element); - }); - - FStringElements::from(elements) + ast::InterpolatedStringElements::from(elements) } - /// Parses a f-string expression element. + /// Parses an f/t-string expression element. /// /// # Panics /// /// If the parser isn't positioned at a `{` token. - fn parse_fstring_expression_element( + fn parse_interpolated_element( &mut self, flags: ast::AnyStringFlags, - ) -> ast::FStringExpressionElement { + string_kind: InterpolatedStringKind, + ) -> ast::InterpolatedElement { let start = self.node_start(); self.bump(TokenKind::Lbrace); @@ -1571,11 +1649,23 @@ impl<'src> Parser<'src> { // f"{}" // f"{ }" + // test_err t_string_empty_expression + // # parse_options: {"target-version": "3.14"} + // t"{}" + // t"{ }" + // test_err f_string_invalid_starred_expr // # Starred expression inside f-string has a minimum precedence of bitwise or. // f"{*}" // f"{*x and y}" // f"{*yield x}" + + // test_err t_string_invalid_starred_expr + // # parse_options: {"target-version": "3.14"} + // # Starred expression inside t-string has a minimum precedence of bitwise or. + // t"{*}" + // t"{*x and y}" + // t"{*yield x}" let value = self.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or()); if !value.is_parenthesized && value.expr.is_lambda_expr() { @@ -1585,8 +1675,15 @@ impl<'src> Parser<'src> { // test_err f_string_lambda_without_parentheses // f"{lambda x: x}" + + // test_err t_string_lambda_without_parentheses + // # parse_options: {"target-version": "3.14"} + // t"{lambda x: x}" self.add_error( - ParseErrorType::FStringError(FStringErrorType::LambdaWithoutParentheses), + ParseErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::LambdaWithoutParentheses, + string_kind, + ), value.range(), ); } @@ -1614,8 +1711,15 @@ impl<'src> Parser<'src> { _ => { // test_err f_string_invalid_conversion_flag_name_tok // f"{x!z}" + + // test_err t_string_invalid_conversion_flag_name_tok + // # parse_options: {"target-version": "3.14"} + // t"{x!z}" self.add_error( - ParseErrorType::FStringError(FStringErrorType::InvalidConversionFlag), + ParseErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::InvalidConversionFlag, + string_kind, + ), conversion_flag_range, ); ConversionFlag::None @@ -1625,8 +1729,16 @@ impl<'src> Parser<'src> { // test_err f_string_invalid_conversion_flag_other_tok // f"{x!123}" // f"{x!'a'}" + + // test_err t_string_invalid_conversion_flag_other_tok + // # parse_options: {"target-version": "3.14"} + // t"{x!123}" + // t"{x!'a'}" self.add_error( - ParseErrorType::FStringError(FStringErrorType::InvalidConversionFlag), + ParseErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::InvalidConversionFlag, + string_kind, + ), conversion_flag_range, ); // TODO(dhruvmanila): Avoid dropping this token @@ -1639,8 +1751,12 @@ impl<'src> Parser<'src> { let format_spec = if self.eat(TokenKind::Colon) { let spec_start = self.node_start(); - let elements = self.parse_fstring_elements(flags, FStringElementsKind::FormatSpec); - Some(Box::new(ast::FStringFormatSpec { + let elements = self.parse_interpolated_string_elements( + flags, + InterpolatedStringElementsKind::FormatSpec, + string_kind, + ); + Some(Box::new(ast::InterpolatedStringFormatSpec { range: self.node_range(spec_start), elements, })) @@ -1661,18 +1777,34 @@ impl<'src> Parser<'src> { // f"{" // f"""{""" + // test_err t_string_unclosed_lbrace + // # parse_options: {"target-version": "3.14"} + // t"{" + // t"{foo!r" + // t"{foo=" + // t"{" + // t"""{""" + // The lexer does emit `FStringEnd` for the following test cases: // test_err f_string_unclosed_lbrace_in_format_spec // f"hello {x:" // f"hello {x:.3f" + + // test_err t_string_unclosed_lbrace_in_format_spec + // # parse_options: {"target-version": "3.14"} + // t"hello {x:" + // t"hello {x:.3f" self.add_error( - ParseErrorType::FStringError(FStringErrorType::UnclosedLbrace), + ParseErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::UnclosedLbrace, + string_kind, + ), self.current_token_range(), ); } - ast::FStringExpressionElement { + ast::InterpolatedElement { expression: Box::new(value.expr), debug_text, conversion, @@ -2755,3 +2887,30 @@ impl ExpressionContext { } } } + +#[derive(Debug)] +struct InterpolatedStringData { + elements: InterpolatedStringElements, + range: TextRange, + flags: AnyStringFlags, +} + +impl From for FString { + fn from(value: InterpolatedStringData) -> Self { + Self { + elements: value.elements, + range: value.range, + flags: value.flags.into(), + } + } +} + +impl From for TString { + fn from(value: InterpolatedStringData) -> Self { + Self { + elements: value.elements, + range: value.range, + flags: value.flags.into(), + } + } +} diff --git a/crates/ruff_python_parser/src/parser/helpers.rs b/crates/ruff_python_parser/src/parser/helpers.rs index e7a0e426c244c8..de89746333d4fa 100644 --- a/crates/ruff_python_parser/src/parser/helpers.rs +++ b/crates/ruff_python_parser/src/parser/helpers.rs @@ -94,6 +94,7 @@ pub(super) fn detect_invalid_pre_py39_decorator_node( Expr::YieldFrom(_) => "`yield from` expression", Expr::Compare(_) => "comparison expression", Expr::FString(_) => "f-string", + Expr::TString(_) => "t-string", Expr::Named(_) => "assignment expression", Expr::Subscript(_) => "subscript expression", Expr::IpyEscapeCommand(_) => "IPython escape command", diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs index 19596364a8347b..0668f18b2954ee 100644 --- a/crates/ruff_python_parser/src/parser/mod.rs +++ b/crates/ruff_python_parser/src/parser/mod.rs @@ -798,7 +798,7 @@ impl WithItemKind { } #[derive(Debug, PartialEq, Copy, Clone)] -enum FStringElementsKind { +enum InterpolatedStringElementsKind { /// The regular f-string elements. /// /// For example, the `"hello "`, `x`, and `" world"` elements in: @@ -816,14 +816,16 @@ enum FStringElementsKind { FormatSpec, } -impl FStringElementsKind { - const fn list_terminator(self) -> TokenKind { +impl InterpolatedStringElementsKind { + const fn list_terminators(self) -> TokenSet { match self { - FStringElementsKind::Regular => TokenKind::FStringEnd, + InterpolatedStringElementsKind::Regular => { + TokenSet::new([TokenKind::FStringEnd, TokenKind::TStringEnd]) + } // test_ok fstring_format_spec_terminator // f"hello {x:} world" // f"hello {x:.3f} world" - FStringElementsKind::FormatSpec => TokenKind::Rbrace, + InterpolatedStringElementsKind::FormatSpec => TokenSet::new([TokenKind::Rbrace]), } } } @@ -931,9 +933,8 @@ enum RecoveryContextKind { /// When parsing a list of items in a `with` statement WithItems(WithItemKind), - /// When parsing a list of f-string elements which are either literal elements - /// or expressions. - FStringElements(FStringElementsKind), + /// When parsing a list of f-string or t-string elements which are either literal elements, expressions, or interpolations. + InterpolatedStringElements(InterpolatedStringElementsKind), } impl RecoveryContextKind { @@ -1117,8 +1118,8 @@ impl RecoveryContextKind { .at(TokenKind::Colon) .then_some(ListTerminatorKind::Regular), }, - RecoveryContextKind::FStringElements(kind) => { - if p.at(kind.list_terminator()) { + RecoveryContextKind::InterpolatedStringElements(kind) => { + if p.at_ts(kind.list_terminators()) { Some(ListTerminatorKind::Regular) } else { // test_err unterminated_fstring_newline_recovery @@ -1174,10 +1175,10 @@ impl RecoveryContextKind { ) || p.at_name_or_soft_keyword() } RecoveryContextKind::WithItems(_) => p.at_expr(), - RecoveryContextKind::FStringElements(_) => matches!( + RecoveryContextKind::InterpolatedStringElements(_) => matches!( p.current_token_kind(), // Literal element - TokenKind::FStringMiddle + TokenKind::FStringMiddle | TokenKind::TStringMiddle // Expression element | TokenKind::Lbrace ), @@ -1268,13 +1269,13 @@ impl RecoveryContextKind { "Expected an expression or the end of the with item list".to_string(), ), }, - RecoveryContextKind::FStringElements(kind) => match kind { - FStringElementsKind::Regular => ParseErrorType::OtherError( - "Expected an f-string element or the end of the f-string".to_string(), + RecoveryContextKind::InterpolatedStringElements(kind) => match kind { + InterpolatedStringElementsKind::Regular => ParseErrorType::OtherError( + "Expected an f-string or t-string element or the end of the f-string or t-string".to_string(), + ), + InterpolatedStringElementsKind::FormatSpec => ParseErrorType::OtherError( + "Expected an f-string or t-string element or a '}'".to_string(), ), - FStringElementsKind::FormatSpec => { - ParseErrorType::OtherError("Expected an f-string element or a '}'".to_string()) - } }, } } @@ -1313,8 +1314,8 @@ bitflags! { const WITH_ITEMS_PARENTHESIZED = 1 << 25; const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26; const WITH_ITEMS_UNPARENTHESIZED = 1 << 28; - const F_STRING_ELEMENTS = 1 << 29; - const F_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30; + const FT_STRING_ELEMENTS = 1 << 29; + const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30; } } @@ -1367,10 +1368,10 @@ impl RecoveryContext { } WithItemKind::Unparenthesized => RecoveryContext::WITH_ITEMS_UNPARENTHESIZED, }, - RecoveryContextKind::FStringElements(kind) => match kind { - FStringElementsKind::Regular => RecoveryContext::F_STRING_ELEMENTS, - FStringElementsKind::FormatSpec => { - RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC + RecoveryContextKind::InterpolatedStringElements(kind) => match kind { + InterpolatedStringElementsKind::Regular => RecoveryContext::FT_STRING_ELEMENTS, + InterpolatedStringElementsKind::FormatSpec => { + RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC } }, } @@ -1439,11 +1440,13 @@ impl RecoveryContext { RecoveryContext::WITH_ITEMS_UNPARENTHESIZED => { RecoveryContextKind::WithItems(WithItemKind::Unparenthesized) } - RecoveryContext::F_STRING_ELEMENTS => { - RecoveryContextKind::FStringElements(FStringElementsKind::Regular) - } - RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC => { - RecoveryContextKind::FStringElements(FStringElementsKind::FormatSpec) + RecoveryContext::FT_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements( + InterpolatedStringElementsKind::Regular, + ), + RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => { + RecoveryContextKind::InterpolatedStringElements( + InterpolatedStringElementsKind::FormatSpec, + ) } _ => return None, }) diff --git a/crates/ruff_python_parser/src/parser/pattern.rs b/crates/ruff_python_parser/src/parser/pattern.rs index ced1627461ba28..461b859c78ad36 100644 --- a/crates/ruff_python_parser/src/parser/pattern.rs +++ b/crates/ruff_python_parser/src/parser/pattern.rs @@ -390,7 +390,7 @@ impl Parser<'_> { range: self.node_range(start), }) } - TokenKind::String | TokenKind::FStringStart => { + TokenKind::String | TokenKind::FStringStart | TokenKind::TStringStart => { let str = self.parse_strings(); Pattern::MatchValue(ast::PatternMatchValue { diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 1eb59b25b6a570..6a44192746a34e 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3012,7 +3012,6 @@ impl<'src> Parser<'src> { // test_ok param_with_annotation // def foo(arg: int): ... // def foo(arg: lambda x: x): ... - // def foo(arg: (x := int)): ... // test_err param_with_invalid_annotation // def foo(arg: *int): ... @@ -3703,6 +3702,7 @@ impl<'src> Parser<'src> { | TokenKind::Complex | TokenKind::String | TokenKind::FStringStart + | TokenKind::TStringStart | TokenKind::Lbrace | TokenKind::Tilde | TokenKind::Ellipsis diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__empty_tstrings.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__empty_tstrings.snap new file mode 100644 index 00000000000000..d23fee4ae87167 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__empty_tstrings.snap @@ -0,0 +1,98 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 2..3, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + String( + "", + ), + 4..6, + TokenFlags( + DOUBLE_QUOTES, + ), + ), + ( + TStringStart, + 7..9, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 9..10, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 11..13, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 13..14, + TokenFlags( + T_STRING, + ), + ), + ( + String( + "", + ), + 15..17, + ), + ( + TStringStart, + 18..22, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 22..25, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringStart, + 26..30, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 30..33, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Newline, + 33..33, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring.snap index 3a56937bcc65a0..c515b59ec0f0dc 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "normal ", ), 2..9, @@ -37,7 +36,7 @@ snapshot_kind: text 13..14, ), ( - FStringMiddle( + InterpolatedStringMiddle( " {another} ", ), 14..27, @@ -60,7 +59,7 @@ snapshot_kind: text 31..32, ), ( - FStringMiddle( + InterpolatedStringMiddle( " {", ), 32..35, @@ -83,7 +82,7 @@ snapshot_kind: text 41..42, ), ( - FStringMiddle( + InterpolatedStringMiddle( "}", ), 42..44, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_comments.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_comments.snap index dae04a5f0ca857..93e0b88bd9a3cc 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_comments.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_comments.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\n# not a comment ", ), 4..21, @@ -49,7 +48,7 @@ snapshot_kind: text 41..42, ), ( - FStringMiddle( + InterpolatedStringMiddle( " # not a comment\n", ), 42..59, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_conversion.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_conversion.snap index 80a868327786f1..cff7b14e12fed3 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_conversion.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_conversion.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -38,7 +37,7 @@ snapshot_kind: text 6..7, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 7..8, @@ -75,7 +74,7 @@ snapshot_kind: text 13..14, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 14..15, @@ -98,7 +97,7 @@ snapshot_kind: text 17..18, ), ( - FStringMiddle( + InterpolatedStringMiddle( ".3f!r", ), 18..23, @@ -111,7 +110,7 @@ snapshot_kind: text 23..24, ), ( - FStringMiddle( + InterpolatedStringMiddle( " {x!r}", ), 24..32, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape.snap index 7aae96b72fc6a6..899139162ddb58 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\", ), 2..3, @@ -37,7 +36,7 @@ snapshot_kind: text 5..6, ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\\"\\", ), 6..9, @@ -64,7 +63,7 @@ snapshot_kind: text 12..13, ), ( - FStringMiddle( + InterpolatedStringMiddle( " \\\"\\\"\\\n end", ), 13..24, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_braces.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_braces.snap index 3cfba863a21c01..a792cfee1117e1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_braces.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_braces.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\", ), 2..3, @@ -51,7 +50,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\\\", ), 12..14, @@ -88,7 +87,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\{foo}", ), 23..31, @@ -111,7 +110,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\\\{foo}", ), 35..44, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_raw.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_raw.snap index 0e14fbb35de4fe..5fe4b168373dc6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_raw.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_escape_raw.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\", ), 3..4, @@ -37,7 +36,7 @@ snapshot_kind: text 6..7, ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\\"\\", ), 7..10, @@ -64,7 +63,7 @@ snapshot_kind: text 13..14, ), ( - FStringMiddle( + InterpolatedStringMiddle( " \\\"\\\"\\\n end", ), 14..25, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_expression_multiline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_expression_multiline.snap index c7fd18b79a8c35..5987a41f676e76 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_expression_multiline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_expression_multiline.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "first ", ), 2..8, @@ -63,7 +62,7 @@ snapshot_kind: text 40..41, ), ( - FStringMiddle( + InterpolatedStringMiddle( " second", ), 41..48, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_multiline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_multiline.snap index 95c43f76d1184b..15a765a45ae2cd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_multiline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_multiline.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\nhello\n world\n", ), 4..21, @@ -37,7 +36,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\n world\nhello\n", ), 29..46, @@ -60,7 +59,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "some ", ), 52..57, @@ -80,7 +79,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "multiline\nallowed ", ), 62..80, @@ -114,7 +113,7 @@ snapshot_kind: text 86..87, ), ( - FStringMiddle( + InterpolatedStringMiddle( " string", ), 87..94, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode.snap index 2ae41093603b1a..5571d867a125ef 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\N{BULLET} normal \\Nope \\N", ), 2..28, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode_raw.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode_raw.snap index b37611f0dab32b..974d2cf9c355cd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode_raw.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_named_unicode_raw.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\N", ), 3..5, @@ -37,7 +36,7 @@ snapshot_kind: text 12..13, ), ( - FStringMiddle( + InterpolatedStringMiddle( " normal", ), 13..20, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_nested.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_nested.snap index de3e6d60f2777b..8e1dc7e8d2f1df 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_nested.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_nested.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "foo ", ), 2..6, @@ -34,7 +33,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "bar ", ), 9..13, @@ -100,7 +99,7 @@ snapshot_kind: text 28..29, ), ( - FStringMiddle( + InterpolatedStringMiddle( " baz", ), 29..33, @@ -123,7 +122,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "foo ", ), 37..41, @@ -143,7 +142,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "bar", ), 44..47, @@ -163,7 +162,7 @@ snapshot_kind: text 48..49, ), ( - FStringMiddle( + InterpolatedStringMiddle( " some ", ), 49..55, @@ -183,7 +182,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "another", ), 58..65, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_parentheses.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_parentheses.snap index 287d62d08a42c9..381aa8e626ba75 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_parentheses.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_parentheses.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -36,7 +35,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "{}", ), 8..12, @@ -59,7 +58,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 16..17, @@ -90,7 +89,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "{", ), 23..25, @@ -107,7 +106,7 @@ snapshot_kind: text 26..27, ), ( - FStringMiddle( + InterpolatedStringMiddle( "}", ), 27..29, @@ -130,7 +129,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "{{}}", ), 33..41, @@ -153,7 +152,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 45..46, @@ -170,7 +169,7 @@ snapshot_kind: text 47..48, ), ( - FStringMiddle( + InterpolatedStringMiddle( " {} {", ), 48..56, @@ -187,7 +186,7 @@ snapshot_kind: text 57..58, ), ( - FStringMiddle( + InterpolatedStringMiddle( "} {{}} ", ), 58..71, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap index 5476c1fa02ab08..53a3fe79087635 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: fstring_single_quote_escape_eol(MAC_EOL) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "text \\\r more text", ), 2..19, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap index 19e0346f43483b..d8e27f0661ab51 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: fstring_single_quote_escape_eol(UNIX_EOL) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "text \\\n more text", ), 2..19, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap index c4f595a38927c7..ba73b4a09d1946 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: fstring_single_quote_escape_eol(WINDOWS_EOL) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "text \\\r\n more text", ), 2..20, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_format_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_format_spec.snap index 400f81636f8dd4..26380715dc7309 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_format_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_format_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -32,7 +31,7 @@ snapshot_kind: text 7..8, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 8..9, @@ -69,7 +68,7 @@ snapshot_kind: text 14..15, ), ( - FStringMiddle( + InterpolatedStringMiddle( ".3f", ), 15..18, @@ -82,7 +81,7 @@ snapshot_kind: text 18..19, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 19..20, @@ -105,7 +104,7 @@ snapshot_kind: text 22..23, ), ( - FStringMiddle( + InterpolatedStringMiddle( ".", ), 23..24, @@ -128,7 +127,7 @@ snapshot_kind: text 26..27, ), ( - FStringMiddle( + InterpolatedStringMiddle( "f", ), 27..28, @@ -141,7 +140,7 @@ snapshot_kind: text 28..29, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 29..30, @@ -164,7 +163,7 @@ snapshot_kind: text 33..34, ), ( - FStringMiddle( + InterpolatedStringMiddle( "*^", ), 34..36, @@ -209,7 +208,7 @@ snapshot_kind: text 43..44, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 44..45, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_ipy_escape_command.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_ipy_escape_command.snap index f48c742e6d26a1..523b5d61333f8c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_ipy_escape_command.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_ipy_escape_command.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "foo ", ), 2..6, @@ -41,7 +40,7 @@ snapshot_kind: text 11..12, ), ( - FStringMiddle( + InterpolatedStringMiddle( " bar", ), 12..16, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_multiline_format_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_multiline_format_spec.snap index 341455e1f23266..6a0909bcdbf4b1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_multiline_format_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_multiline_format_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 4..6, @@ -41,7 +40,7 @@ snapshot_kind: text 13..14, ), ( - FStringMiddle( + InterpolatedStringMiddle( "d\n", ), 14..16, @@ -54,7 +53,7 @@ snapshot_kind: text 16..17, ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 17..19, @@ -81,7 +80,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 27..29, @@ -108,7 +107,7 @@ snapshot_kind: text 36..37, ), ( - FStringMiddle( + InterpolatedStringMiddle( "a\n b\n c\n", ), 37..61, @@ -121,7 +120,7 @@ snapshot_kind: text 61..62, ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 62..64, @@ -148,7 +147,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 70..72, @@ -175,7 +174,7 @@ snapshot_kind: text 79..80, ), ( - FStringMiddle( + InterpolatedStringMiddle( "d", ), 80..81, @@ -192,7 +191,7 @@ snapshot_kind: text 82..83, ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 83..85, @@ -219,7 +218,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 89..91, @@ -246,7 +245,7 @@ snapshot_kind: text 98..99, ), ( - FStringMiddle( + InterpolatedStringMiddle( "a", ), 99..100, @@ -273,7 +272,7 @@ snapshot_kind: text 111..112, ), ( - FStringMiddle( + InterpolatedStringMiddle( "__", ), 112..114, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_named_expression.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_named_expression.snap index 8f83f01d571ece..bf3571a289e516 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_named_expression.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_named_expression.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -28,7 +27,7 @@ snapshot_kind: text 4..5, ), ( - FStringMiddle( + InterpolatedStringMiddle( "=10", ), 5..8, @@ -41,7 +40,7 @@ snapshot_kind: text 8..9, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 9..10, @@ -82,7 +81,7 @@ snapshot_kind: text 18..19, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 19..20, @@ -133,7 +132,7 @@ snapshot_kind: text 30..31, ), ( - FStringMiddle( + InterpolatedStringMiddle( " ", ), 31..32, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_nul_char.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_nul_char.snap index 73b431eccc1c58..377acaf33d86f5 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_nul_char.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_with_nul_char.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/lexer.rs expression: lex_source(source) -snapshot_kind: text --- ## Tokens ``` @@ -14,7 +13,7 @@ snapshot_kind: text ), ), ( - FStringMiddle( + InterpolatedStringMiddle( "\\0", ), 2..4, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__nested_t_and_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__nested_t_and_fstring.snap new file mode 100644 index 00000000000000..f2d59004e55aff --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__nested_t_and_fstring.snap @@ -0,0 +1,226 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "foo ", + ), + 2..6, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 6..7, + ), + ( + FStringStart, + 7..9, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "bar ", + ), + 9..13, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + Lbrace, + 13..14, + ), + ( + Name( + Name("x"), + ), + 14..15, + ), + ( + Plus, + 16..17, + ), + ( + TStringStart, + 18..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 20..21, + ), + ( + Name( + Name("wow"), + ), + 21..24, + ), + ( + Rbrace, + 24..25, + ), + ( + TStringEnd, + 25..26, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 26..27, + ), + ( + FStringEnd, + 27..28, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + Rbrace, + 28..29, + ), + ( + InterpolatedStringMiddle( + " baz", + ), + 29..33, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 33..34, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + FStringStart, + 35..37, + TokenFlags( + F_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "foo ", + ), + 37..41, + TokenFlags( + F_STRING, + ), + ), + ( + Lbrace, + 41..42, + ), + ( + TStringStart, + 42..44, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "bar", + ), + 44..47, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 47..48, + TokenFlags( + T_STRING, + ), + ), + ( + Exclamation, + 48..49, + ), + ( + Name( + Name("r"), + ), + 49..50, + ), + ( + Rbrace, + 50..51, + ), + ( + InterpolatedStringMiddle( + " some ", + ), + 51..57, + TokenFlags( + F_STRING, + ), + ), + ( + Lbrace, + 57..58, + ), + ( + FStringStart, + 58..60, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "another", + ), + 60..67, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + FStringEnd, + 67..68, + TokenFlags( + DOUBLE_QUOTES | F_STRING, + ), + ), + ( + Rbrace, + 68..69, + ), + ( + FStringEnd, + 69..70, + TokenFlags( + F_STRING, + ), + ), + ( + Newline, + 70..70, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring.snap new file mode 100644 index 00000000000000..dde7870ae82ecc --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring.snap @@ -0,0 +1,105 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "normal ", + ), + 2..9, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 9..10, + ), + ( + Name( + Name("foo"), + ), + 10..13, + ), + ( + Rbrace, + 13..14, + ), + ( + InterpolatedStringMiddle( + " {another} ", + ), + 14..27, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 27..28, + ), + ( + Name( + Name("bar"), + ), + 28..31, + ), + ( + Rbrace, + 31..32, + ), + ( + InterpolatedStringMiddle( + " {", + ), + 32..35, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 35..36, + ), + ( + Name( + Name("three"), + ), + 36..41, + ), + ( + Rbrace, + 41..42, + ), + ( + InterpolatedStringMiddle( + "}", + ), + 42..44, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 44..45, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 45..45, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_comments.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_comments.snap new file mode 100644 index 00000000000000..5cbdf8897975b6 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_comments.snap @@ -0,0 +1,71 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..4, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\n# not a comment ", + ), + 4..21, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Lbrace, + 21..22, + ), + ( + Comment, + 23..34, + ), + ( + NonLogicalNewline, + 34..35, + ), + ( + Name( + Name("x"), + ), + 39..40, + ), + ( + NonLogicalNewline, + 40..41, + ), + ( + Rbrace, + 41..42, + ), + ( + InterpolatedStringMiddle( + " # not a comment\n", + ), + 42..59, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 59..62, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Newline, + 62..62, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_conversion.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_conversion.snap new file mode 100644 index 00000000000000..2e911b3250016d --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_conversion.snap @@ -0,0 +1,133 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 2..3, + ), + ( + Name( + Name("x"), + ), + 3..4, + ), + ( + Exclamation, + 4..5, + ), + ( + Name( + Name("s"), + ), + 5..6, + ), + ( + Rbrace, + 6..7, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 7..8, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 8..9, + ), + ( + Name( + Name("x"), + ), + 9..10, + ), + ( + Equal, + 10..11, + ), + ( + Exclamation, + 11..12, + ), + ( + Name( + Name("r"), + ), + 12..13, + ), + ( + Rbrace, + 13..14, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 14..15, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 15..16, + ), + ( + Name( + Name("x"), + ), + 16..17, + ), + ( + Colon, + 17..18, + ), + ( + InterpolatedStringMiddle( + ".3f!r", + ), + 18..23, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 23..24, + ), + ( + InterpolatedStringMiddle( + " {x!r}", + ), + 24..32, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 32..33, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 33..33, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape.snap new file mode 100644 index 00000000000000..a69d1b8d9786be --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape.snap @@ -0,0 +1,86 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\", + ), + 2..3, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 3..4, + ), + ( + Name( + Name("x"), + ), + 4..5, + ), + ( + Colon, + 5..6, + ), + ( + InterpolatedStringMiddle( + "\\\"\\", + ), + 6..9, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 9..10, + ), + ( + Name( + Name("x"), + ), + 10..11, + ), + ( + Rbrace, + 11..12, + ), + ( + Rbrace, + 12..13, + ), + ( + InterpolatedStringMiddle( + " \\\"\\\"\\\n end", + ), + 13..24, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 24..25, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 25..25, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_braces.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_braces.snap new file mode 100644 index 00000000000000..2cf409eae54c6e --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_braces.snap @@ -0,0 +1,133 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\", + ), + 2..3, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 3..4, + ), + ( + Name( + Name("foo"), + ), + 4..7, + ), + ( + Rbrace, + 7..8, + ), + ( + TStringEnd, + 8..9, + TokenFlags( + T_STRING, + ), + ), + ( + TStringStart, + 10..12, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\\\", + ), + 12..14, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 14..15, + ), + ( + Name( + Name("foo"), + ), + 15..18, + ), + ( + Rbrace, + 18..19, + ), + ( + TStringEnd, + 19..20, + TokenFlags( + T_STRING, + ), + ), + ( + TStringStart, + 21..23, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\{foo}", + ), + 23..31, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 31..32, + TokenFlags( + T_STRING, + ), + ), + ( + TStringStart, + 33..35, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\\\{foo}", + ), + 35..44, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 44..45, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 45..45, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_raw.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_raw.snap new file mode 100644 index 00000000000000..f7b2b27cb8bf83 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_escape_raw.snap @@ -0,0 +1,86 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..3, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + InterpolatedStringMiddle( + "\\", + ), + 3..4, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + Lbrace, + 4..5, + ), + ( + Name( + Name("x"), + ), + 5..6, + ), + ( + Colon, + 6..7, + ), + ( + InterpolatedStringMiddle( + "\\\"\\", + ), + 7..10, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + Lbrace, + 10..11, + ), + ( + Name( + Name("x"), + ), + 11..12, + ), + ( + Rbrace, + 12..13, + ), + ( + Rbrace, + 13..14, + ), + ( + InterpolatedStringMiddle( + " \\\"\\\"\\\n end", + ), + 14..25, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 25..26, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + Newline, + 26..26, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_expression_multiline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_expression_multiline.snap new file mode 100644 index 00000000000000..13894db5649713 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_expression_multiline.snap @@ -0,0 +1,85 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "first ", + ), + 2..8, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 8..9, + ), + ( + NonLogicalNewline, + 9..10, + ), + ( + Name( + Name("x"), + ), + 14..15, + ), + ( + NonLogicalNewline, + 15..16, + ), + ( + Star, + 24..25, + ), + ( + NonLogicalNewline, + 25..26, + ), + ( + Name( + Name("y"), + ), + 38..39, + ), + ( + NonLogicalNewline, + 39..40, + ), + ( + Rbrace, + 40..41, + ), + ( + InterpolatedStringMiddle( + " second", + ), + 41..48, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 48..49, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 49..49, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_multiline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_multiline.snap new file mode 100644 index 00000000000000..5f4f4496d1af92 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_multiline.snap @@ -0,0 +1,136 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..4, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\nhello\n world\n", + ), + 4..21, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 21..24, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringStart, + 25..29, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\n world\nhello\n", + ), + 29..46, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 46..49, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringStart, + 50..52, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "some ", + ), + 52..57, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 57..58, + ), + ( + TStringStart, + 58..62, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "multiline\nallowed ", + ), + 62..80, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Lbrace, + 80..81, + ), + ( + Name( + Name("x"), + ), + 81..82, + ), + ( + Rbrace, + 82..83, + ), + ( + TStringEnd, + 83..86, + TokenFlags( + DOUBLE_QUOTES | TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Rbrace, + 86..87, + ), + ( + InterpolatedStringMiddle( + " string", + ), + 87..94, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 94..95, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 95..95, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode.snap new file mode 100644 index 00000000000000..f900cbf95b6db0 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\N{BULLET} normal \\Nope \\N", + ), + 2..28, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 28..29, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 29..29, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode_raw.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode_raw.snap new file mode 100644 index 00000000000000..73b022ad3b0823 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_named_unicode_raw.snap @@ -0,0 +1,59 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..3, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + InterpolatedStringMiddle( + "\\N", + ), + 3..5, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + Lbrace, + 5..6, + ), + ( + Name( + Name("BULLET"), + ), + 6..12, + ), + ( + Rbrace, + 12..13, + ), + ( + InterpolatedStringMiddle( + " normal", + ), + 13..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 20..21, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + Newline, + 21..21, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_nested.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_nested.snap new file mode 100644 index 00000000000000..f786acf7d87db7 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_nested.snap @@ -0,0 +1,216 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "foo ", + ), + 2..6, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 6..7, + ), + ( + TStringStart, + 7..9, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "bar ", + ), + 9..13, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 13..14, + ), + ( + Name( + Name("x"), + ), + 14..15, + ), + ( + Plus, + 16..17, + ), + ( + TStringStart, + 18..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 20..21, + ), + ( + Name( + Name("wow"), + ), + 21..24, + ), + ( + Rbrace, + 24..25, + ), + ( + TStringEnd, + 25..26, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 26..27, + ), + ( + TStringEnd, + 27..28, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 28..29, + ), + ( + InterpolatedStringMiddle( + " baz", + ), + 29..33, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 33..34, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 35..37, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "foo ", + ), + 37..41, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 41..42, + ), + ( + TStringStart, + 42..44, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "bar", + ), + 44..47, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 47..48, + TokenFlags( + T_STRING, + ), + ), + ( + Rbrace, + 48..49, + ), + ( + InterpolatedStringMiddle( + " some ", + ), + 49..55, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 55..56, + ), + ( + TStringStart, + 56..58, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "another", + ), + 58..65, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 65..66, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 66..67, + ), + ( + TStringEnd, + 67..68, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 68..68, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_parentheses.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_parentheses.snap new file mode 100644 index 00000000000000..fcccc68b40a048 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_parentheses.snap @@ -0,0 +1,209 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 2..3, + ), + ( + Rbrace, + 3..4, + ), + ( + TStringEnd, + 4..5, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 6..8, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "{}", + ), + 8..12, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 12..13, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 14..16, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + " ", + ), + 16..17, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 17..18, + ), + ( + Rbrace, + 18..19, + ), + ( + TStringEnd, + 19..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 21..23, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "{", + ), + 23..25, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 25..26, + ), + ( + Rbrace, + 26..27, + ), + ( + InterpolatedStringMiddle( + "}", + ), + 27..29, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 29..30, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 31..33, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "{{}}", + ), + 33..41, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 41..42, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 43..45, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + " ", + ), + 45..46, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 46..47, + ), + ( + Rbrace, + 47..48, + ), + ( + InterpolatedStringMiddle( + " {} {", + ), + 48..56, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 56..57, + ), + ( + Rbrace, + 57..58, + ), + ( + InterpolatedStringMiddle( + "} {{}} ", + ), + 58..71, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 71..72, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 72..72, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_prefix.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_prefix.snap new file mode 100644 index 00000000000000..8a285e58fb5366 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_prefix.snap @@ -0,0 +1,153 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 2..3, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 4..6, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 6..7, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringStart, + 8..11, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 11..12, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringStart, + 13..16, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 16..17, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringStart, + 18..21, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringEnd, + 21..22, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringStart, + 23..26, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringEnd, + 26..27, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringStart, + 28..31, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 31..32, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringStart, + 33..36, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringEnd, + 36..37, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_LOWERCASE, + ), + ), + ( + TStringStart, + 38..41, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringEnd, + 41..42, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringStart, + 43..46, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + TStringEnd, + 46..47, + TokenFlags( + DOUBLE_QUOTES | T_STRING | RAW_STRING_UPPERCASE, + ), + ), + ( + Newline, + 47..47, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_mac_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_mac_eol.snap new file mode 100644 index 00000000000000..515c486b9bd659 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_mac_eol.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: tstring_single_quote_escape_eol(MAC_EOL) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "text \\\r more text", + ), + 2..19, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 19..20, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 20..20, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_unix_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_unix_eol.snap new file mode 100644 index 00000000000000..eed02e6ab6cd91 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_unix_eol.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: tstring_single_quote_escape_eol(UNIX_EOL) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "text \\\n more text", + ), + 2..19, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 19..20, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 20..20, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_windows_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_windows_eol.snap new file mode 100644 index 00000000000000..424092796db078 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_single_quote_escape_windows_eol.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: tstring_single_quote_escape_eol(WINDOWS_EOL) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "text \\\r\n more text", + ), + 2..20, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 20..21, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 21..21, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_format_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_format_spec.snap new file mode 100644 index 00000000000000..d8760b764d29d2 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_format_spec.snap @@ -0,0 +1,289 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 2..3, + ), + ( + Name( + Name("foo"), + ), + 3..6, + ), + ( + Colon, + 6..7, + ), + ( + Rbrace, + 7..8, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 8..9, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 9..10, + ), + ( + Name( + Name("x"), + ), + 10..11, + ), + ( + Equal, + 11..12, + ), + ( + Exclamation, + 12..13, + ), + ( + Name( + Name("s"), + ), + 13..14, + ), + ( + Colon, + 14..15, + ), + ( + InterpolatedStringMiddle( + ".3f", + ), + 15..18, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 18..19, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 19..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 20..21, + ), + ( + Name( + Name("x"), + ), + 21..22, + ), + ( + Colon, + 22..23, + ), + ( + InterpolatedStringMiddle( + ".", + ), + 23..24, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 24..25, + ), + ( + Name( + Name("y"), + ), + 25..26, + ), + ( + Rbrace, + 26..27, + ), + ( + InterpolatedStringMiddle( + "f", + ), + 27..28, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 28..29, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 29..30, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 30..31, + ), + ( + String( + "", + ), + 31..33, + ), + ( + Colon, + 33..34, + ), + ( + InterpolatedStringMiddle( + "*^", + ), + 34..36, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 36..37, + ), + ( + Int( + 1, + ), + 37..38, + ), + ( + Colon, + 38..39, + ), + ( + Lbrace, + 39..40, + ), + ( + Int( + 1, + ), + 40..41, + ), + ( + Rbrace, + 41..42, + ), + ( + Rbrace, + 42..43, + ), + ( + Rbrace, + 43..44, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 44..45, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 45..46, + ), + ( + Name( + Name("x"), + ), + 46..47, + ), + ( + Colon, + 47..48, + ), + ( + Lbrace, + 48..49, + ), + ( + Lbrace, + 49..50, + ), + ( + Int( + 1, + ), + 50..51, + ), + ( + Rbrace, + 51..52, + ), + ( + Dot, + 52..53, + ), + ( + Name( + Name("pop"), + ), + 53..56, + ), + ( + Lpar, + 56..57, + ), + ( + Rpar, + 57..58, + ), + ( + Rbrace, + 58..59, + ), + ( + Rbrace, + 59..60, + ), + ( + TStringEnd, + 60..61, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 61..61, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_ipy_escape_command.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_ipy_escape_command.snap new file mode 100644 index 00000000000000..739930ef4230b9 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_ipy_escape_command.snap @@ -0,0 +1,63 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "foo ", + ), + 2..6, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 6..7, + ), + ( + Exclamation, + 7..8, + ), + ( + Name( + Name("pwd"), + ), + 8..11, + ), + ( + Rbrace, + 11..12, + ), + ( + InterpolatedStringMiddle( + " bar", + ), + 12..16, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + TStringEnd, + 16..17, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 17..17, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_lambda_expression.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_lambda_expression.snap new file mode 100644 index 00000000000000..679142f387a460 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_lambda_expression.snap @@ -0,0 +1,125 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 2..3, + ), + ( + Lambda, + 3..9, + ), + ( + Name( + Name("x"), + ), + 10..11, + ), + ( + Colon, + 11..12, + ), + ( + Lbrace, + 12..13, + ), + ( + Name( + Name("x"), + ), + 13..14, + ), + ( + Rbrace, + 14..15, + ), + ( + Rbrace, + 15..16, + ), + ( + TStringEnd, + 16..17, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 17..18, + ), + ( + TStringStart, + 18..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 20..21, + ), + ( + Lpar, + 21..22, + ), + ( + Lambda, + 22..28, + ), + ( + Name( + Name("x"), + ), + 29..30, + ), + ( + Colon, + 30..31, + ), + ( + Lbrace, + 31..32, + ), + ( + Name( + Name("x"), + ), + 32..33, + ), + ( + Rbrace, + 33..34, + ), + ( + Rpar, + 34..35, + ), + ( + Rbrace, + 35..36, + ), + ( + TStringEnd, + 36..37, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 37..37, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_multiline_format_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_multiline_format_spec.snap new file mode 100644 index 00000000000000..1525f2a0ebe6a5 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_multiline_format_spec.snap @@ -0,0 +1,295 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..4, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "__", + ), + 4..6, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Lbrace, + 6..7, + ), + ( + NonLogicalNewline, + 7..8, + ), + ( + Name( + Name("x"), + ), + 12..13, + ), + ( + Colon, + 13..14, + ), + ( + InterpolatedStringMiddle( + "d\n", + ), + 14..16, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Rbrace, + 16..17, + ), + ( + InterpolatedStringMiddle( + "__", + ), + 17..19, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 19..22, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Newline, + 22..23, + ), + ( + TStringStart, + 23..27, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "__", + ), + 27..29, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Lbrace, + 29..30, + ), + ( + NonLogicalNewline, + 30..31, + ), + ( + Name( + Name("x"), + ), + 35..36, + ), + ( + Colon, + 36..37, + ), + ( + InterpolatedStringMiddle( + "a\n b\n c\n", + ), + 37..61, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Rbrace, + 61..62, + ), + ( + InterpolatedStringMiddle( + "__", + ), + 62..64, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + TStringEnd, + 64..67, + TokenFlags( + TRIPLE_QUOTED_STRING | T_STRING, + ), + ), + ( + Newline, + 67..68, + ), + ( + TStringStart, + 68..70, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "__", + ), + 70..72, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 72..73, + ), + ( + NonLogicalNewline, + 73..74, + ), + ( + Name( + Name("x"), + ), + 78..79, + ), + ( + Colon, + 79..80, + ), + ( + InterpolatedStringMiddle( + "d", + ), + 80..81, + TokenFlags( + T_STRING, + ), + ), + ( + NonLogicalNewline, + 81..82, + ), + ( + Rbrace, + 82..83, + ), + ( + InterpolatedStringMiddle( + "__", + ), + 83..85, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 85..86, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 86..87, + ), + ( + TStringStart, + 87..89, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "__", + ), + 89..91, + TokenFlags( + T_STRING, + ), + ), + ( + Lbrace, + 91..92, + ), + ( + NonLogicalNewline, + 92..93, + ), + ( + Name( + Name("x"), + ), + 97..98, + ), + ( + Colon, + 98..99, + ), + ( + InterpolatedStringMiddle( + "a", + ), + 99..100, + TokenFlags( + T_STRING, + ), + ), + ( + NonLogicalNewline, + 100..101, + ), + ( + Name( + Name("b"), + ), + 109..110, + ), + ( + NonLogicalNewline, + 110..111, + ), + ( + Rbrace, + 111..112, + ), + ( + InterpolatedStringMiddle( + "__", + ), + 112..114, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 114..115, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 115..116, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_named_expression.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_named_expression.snap new file mode 100644 index 00000000000000..7ab9d20f3f8f88 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_named_expression.snap @@ -0,0 +1,187 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 2..3, + ), + ( + Name( + Name("x"), + ), + 3..4, + ), + ( + Colon, + 4..5, + ), + ( + InterpolatedStringMiddle( + "=10", + ), + 5..8, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Rbrace, + 8..9, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 9..10, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 10..11, + ), + ( + Lpar, + 11..12, + ), + ( + Name( + Name("x"), + ), + 12..13, + ), + ( + ColonEqual, + 13..15, + ), + ( + Int( + 10, + ), + 15..17, + ), + ( + Rpar, + 17..18, + ), + ( + Rbrace, + 18..19, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 19..20, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 20..21, + ), + ( + Name( + Name("x"), + ), + 21..22, + ), + ( + Comma, + 22..23, + ), + ( + Lbrace, + 23..24, + ), + ( + Name( + Name("y"), + ), + 24..25, + ), + ( + ColonEqual, + 25..27, + ), + ( + Int( + 10, + ), + 27..29, + ), + ( + Rbrace, + 29..30, + ), + ( + Rbrace, + 30..31, + ), + ( + InterpolatedStringMiddle( + " ", + ), + 31..32, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Lbrace, + 32..33, + ), + ( + Lsqb, + 33..34, + ), + ( + Name( + Name("x"), + ), + 34..35, + ), + ( + ColonEqual, + 35..37, + ), + ( + Int( + 10, + ), + 37..39, + ), + ( + Rsqb, + 39..40, + ), + ( + Rbrace, + 40..41, + ), + ( + TStringEnd, + 41..42, + TokenFlags( + DOUBLE_QUOTES | T_STRING, + ), + ), + ( + Newline, + 42..42, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_nul_char.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_nul_char.snap new file mode 100644 index 00000000000000..483ea5d6ec5ebd --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__tstring_with_nul_char.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: lex_source(source) +--- +## Tokens +``` +[ + ( + TStringStart, + 0..2, + TokenFlags( + T_STRING, + ), + ), + ( + InterpolatedStringMiddle( + "\\0", + ), + 2..4, + TokenFlags( + T_STRING, + ), + ), + ( + TStringEnd, + 4..5, + TokenFlags( + T_STRING, + ), + ), + ( + Newline, + 5..5, + ), +] +``` diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap index 99985450fdc248..78e450c4e2cfd3 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..22, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..5, value: "aaa", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 5..10, expression: Name( ExprName { @@ -38,13 +37,13 @@ snapshot_kind: text }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 10..13, value: "ccc", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 13..18, expression: Name( ExprName { @@ -59,7 +58,7 @@ snapshot_kind: text }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 18..21, value: "eee", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap index 2721f57e773e91..02db608ab1c030 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..8, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..4, value: "\\", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 4..7, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap index 44b3a5dcad767d..9482530d4afc78 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..8, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..4, value: "\n", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 4..7, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap index 898ff2d34766c8..6ccd6f466dcac6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..9, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 3..5, value: "\\\n", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 5..8, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap index 1f21cd2286ed3b..840873127a2a60 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..10, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..9, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap index 9a6ed3225841a6..3d5f1e79c0d831 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..38, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..6, value: "mix ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 6..13, expression: Name( ExprName { @@ -43,13 +42,13 @@ snapshot_kind: text }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 13..28, value: " with text and ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 28..37, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap index 8c786dbbd14ce4..d2063a00eceee1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..14, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..13, expression: Name( ExprName { @@ -34,11 +33,11 @@ snapshot_kind: text ), conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 9..12, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 9..12, value: ">10", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap index eedba2c8bcc06a..98be2ba27a7a9f 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -17,13 +16,13 @@ snapshot_kind: text range: 0..11, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 4..5, value: "\n", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 5..8, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap index ffd01915001768..1a57e2606a5874 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap new file mode 100644 index 00000000000000..3ccfef44042661 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap @@ -0,0 +1,31 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..3, + value: TString( + ExprTString { + range: 0..3, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..3, + elements: [], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap index 983da17da44fc9..e43a1b109c4be8 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,7 +28,7 @@ snapshot_kind: text range: 9..17, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 11..16, value: "world", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap index 983da17da44fc9..e43a1b109c4be8 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,7 +28,7 @@ snapshot_kind: text range: 9..17, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 11..16, value: "world", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap index 8479de9437c00c..a9f5a2c31fbe70 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,13 +28,13 @@ snapshot_kind: text range: 9..22, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 11..16, value: "world", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 16..21, expression: StringLiteral( ExprStringLiteral { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap index 56a819bd717cba..6b348cab6d4961 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,13 +28,13 @@ snapshot_kind: text range: 9..22, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 11..16, value: "world", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 16..21, expression: StringLiteral( ExprStringLiteral { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap new file mode 100644 index 00000000000000..c1432f9b0335e2 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap @@ -0,0 +1,58 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..18, + value: TString( + ExprTString { + range: 0..18, + value: TStringValue { + inner: Concatenated( + [ + FString( + FString { + range: 0..9, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..8, + value: "Hello ", + }, + ), + ], + flags: FStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 10..18, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 12..17, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap new file mode 100644 index 00000000000000..b3a9d72180d295 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap @@ -0,0 +1,69 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..22, + value: TString( + ExprTString { + range: 0..22, + value: TStringValue { + inner: Concatenated( + [ + FString( + FString { + range: 0..9, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..8, + value: "Hello ", + }, + ), + ], + flags: FStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 10..18, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 12..17, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 19..22, + value: "!", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap index 4ec8dc27becaf6..b4fbc87730c141 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..18, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..5, expression: Name( ExprName { @@ -31,8 +30,8 @@ snapshot_kind: text format_spec: None, }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 5..10, expression: Name( ExprName { @@ -47,7 +46,7 @@ snapshot_kind: text }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 10..17, value: "{foo}", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap index 58821196bd95ec..57e5d8296c8121 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..13, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..12, expression: Compare( ExprCompare { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap index 9e5b4a2fc88f7b..f0f88bc994cd2a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..16, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..15, expression: Name( ExprName { @@ -29,11 +28,11 @@ snapshot_kind: text debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 7..14, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 7..14, expression: StringLiteral( ExprStringLiteral { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap index 28e6e4a2a1d656..5b4aebba0663c1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..15, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..14, expression: Name( ExprName { @@ -29,11 +28,11 @@ snapshot_kind: text debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 7..13, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 7..13, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap index 9dae2882394765..c9ab599e07ce3c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..13, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..12, expression: Name( ExprName { @@ -29,11 +28,11 @@ snapshot_kind: text debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 7..11, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 7..11, expression: StringLiteral( ExprStringLiteral { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap index d5884e9d75424d..b631befca65b95 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..11, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..10, expression: Compare( ExprCompare { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap index 738b731a411e98..93308bd0ca7fd2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..13, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..12, expression: Name( ExprName { @@ -29,11 +28,11 @@ snapshot_kind: text debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 7..11, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 7..11, value: "spec", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap index e1d2941dc5f13a..2d72726265ed3c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..10, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..9, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap index e5857594c1b45b..e11b44be8ab883 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..10, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..9, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap index 3dca1cc84b74ad..bcd14124f194c1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..10, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..9, expression: Yield( ExprYield { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap new file mode 100644 index 00000000000000..cc79ec38df9696 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap @@ -0,0 +1,51 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..17, + value: TString( + ExprTString { + range: 0..17, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..8, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 9..17, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 11..16, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap new file mode 100644 index 00000000000000..cc79ec38df9696 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap @@ -0,0 +1,51 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..17, + value: TString( + ExprTString { + range: 0..17, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..8, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 9..17, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 11..16, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap new file mode 100644 index 00000000000000..080c41beec2151 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap @@ -0,0 +1,77 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..22, + value: TString( + ExprTString { + range: 0..22, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..8, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 9..22, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 11..16, + value: "world", + }, + ), + Interpolation( + InterpolatedElement { + range: 16..21, + expression: StringLiteral( + ExprStringLiteral { + range: 17..20, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 17..20, + value: "!", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap new file mode 100644 index 00000000000000..7d178f95699f8a --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap @@ -0,0 +1,88 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..31, + value: TString( + ExprTString { + range: 0..31, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..8, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 9..22, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 11..16, + value: "world", + }, + ), + Interpolation( + InterpolatedElement { + range: 16..21, + expression: StringLiteral( + ExprStringLiteral { + range: 17..20, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 17..20, + value: "!", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 23..31, + value: "again!", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap new file mode 100644 index 00000000000000..999de0e0e86ccc --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap @@ -0,0 +1,68 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..18, + value: TString( + ExprTString { + range: 0..18, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..18, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..5, + expression: Name( + ExprName { + range: 3..4, + id: Name("a"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Interpolation( + InterpolatedElement { + range: 5..10, + expression: Name( + ExprName { + range: 7..8, + id: Name("b"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 10..17, + value: "{foo}", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap new file mode 100644 index 00000000000000..2618c0abf757c8 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap @@ -0,0 +1,66 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..13, + value: TString( + ExprTString { + range: 0..13, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..13, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..12, + expression: Compare( + ExprCompare { + range: 3..11, + left: NumberLiteral( + ExprNumberLiteral { + range: 3..5, + value: Int( + 42, + ), + }, + ), + ops: [ + Eq, + ], + comparators: [ + NumberLiteral( + ExprNumberLiteral { + range: 9..11, + value: Int( + 42, + ), + }, + ), + ], + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap new file mode 100644 index 00000000000000..26719dcb0a0e75 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap @@ -0,0 +1,93 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..16, + value: TString( + ExprTString { + range: 0..16, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..16, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..15, + expression: Name( + ExprName { + range: 3..6, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 7..14, + elements: [ + Interpolation( + InterpolatedElement { + range: 7..14, + expression: StringLiteral( + ExprStringLiteral { + range: 8..13, + value: StringLiteralValue { + inner: Concatenated( + ConcatenatedStringLiteral { + strings: [ + StringLiteral { + range: 8..10, + value: "", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + StringLiteral { + range: 11..13, + value: "", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ], + value: "", + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap new file mode 100644 index 00000000000000..0bd592171b7184 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap @@ -0,0 +1,68 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..15, + value: TString( + ExprTString { + range: 0..15, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..15, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..14, + expression: Name( + ExprName { + range: 3..6, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 7..13, + elements: [ + Interpolation( + InterpolatedElement { + range: 7..13, + expression: Name( + ExprName { + range: 8..12, + id: Name("spec"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap new file mode 100644 index 00000000000000..cfa89174c8c6b2 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap @@ -0,0 +1,79 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..13, + value: TString( + ExprTString { + range: 0..13, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..13, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..12, + expression: Name( + ExprName { + range: 3..6, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 7..11, + elements: [ + Interpolation( + InterpolatedElement { + range: 7..11, + expression: StringLiteral( + ExprStringLiteral { + range: 8..10, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 8..10, + value: "", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap new file mode 100644 index 00000000000000..96bc26e6fc47ae --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap @@ -0,0 +1,66 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..11, + value: TString( + ExprTString { + range: 0..11, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..11, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..10, + expression: Compare( + ExprCompare { + range: 3..9, + left: NumberLiteral( + ExprNumberLiteral { + range: 3..4, + value: Int( + 1, + ), + }, + ), + ops: [ + NotEq, + ], + comparators: [ + NumberLiteral( + ExprNumberLiteral { + range: 8..9, + value: Int( + 2, + ), + }, + ), + ], + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap new file mode 100644 index 00000000000000..b77e560ea214d1 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap @@ -0,0 +1,59 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..13, + value: TString( + ExprTString { + range: 0..13, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..13, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..12, + expression: Name( + ExprName { + range: 3..6, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 7..11, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 7..11, + value: "spec", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap new file mode 100644 index 00000000000000..dc1558c8d23bfd --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap @@ -0,0 +1,52 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..10, + value: TString( + ExprTString { + range: 0..10, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..10, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..9, + expression: Name( + ExprName { + range: 3..4, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: " =", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap new file mode 100644 index 00000000000000..a6c8b8849b3493 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap @@ -0,0 +1,52 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..10, + value: TString( + ExprTString { + range: 0..10, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..10, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..9, + expression: Name( + ExprName { + range: 3..4, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "= ", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap new file mode 100644 index 00000000000000..7693375a34292f --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap @@ -0,0 +1,46 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..10, + value: TString( + ExprTString { + range: 0..10, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..10, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..9, + expression: Yield( + ExprYield { + range: 3..8, + value: None, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap index 2d864494e595c0..31a9098fde576a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,7 +28,7 @@ snapshot_kind: text range: 10..18, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 12..17, value: "world", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap index e3bafd8a1f4619..04c02e6462f910 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -29,7 +28,7 @@ snapshot_kind: text range: 10..18, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 12..17, value: "world", }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap new file mode 100644 index 00000000000000..0fd3e39703c8d2 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap @@ -0,0 +1,51 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..18, + value: TString( + ExprTString { + range: 0..18, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..9, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 10..18, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 12..17, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap new file mode 100644 index 00000000000000..ae5721a93181b2 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap @@ -0,0 +1,62 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..22, + value: TString( + ExprTString { + range: 0..22, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 0..9, + value: "Hello ", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 10..18, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 12..17, + value: "world", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 19..22, + value: "!", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap index ff531b735e42ad..d835921a9ae08e 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..7, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 3..6, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap new file mode 100644 index 00000000000000..5f16d7cc6bdf9c --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap @@ -0,0 +1,49 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..7, + value: TString( + ExprTString { + range: 0..7, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..7, + elements: [ + Interpolation( + InterpolatedElement { + range: 3..6, + expression: Name( + ExprName { + range: 4..5, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Raw { + uppercase_r: false, + }, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap index 1d0ed68bc340b0..2c0adae7d11126 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( @@ -16,8 +15,8 @@ snapshot_kind: text FString { range: 0..11, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 5..8, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap new file mode 100644 index 00000000000000..7a383de39c53e1 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap @@ -0,0 +1,49 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..11, + value: TString( + ExprTString { + range: 0..11, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..11, + elements: [ + Interpolation( + InterpolatedElement { + range: 5..8, + expression: Name( + ExprName { + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Raw { + uppercase_r: false, + }, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap new file mode 100644 index 00000000000000..6227dea0fee749 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap @@ -0,0 +1,80 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..22, + value: TString( + ExprTString { + range: 0..22, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..22, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..5, + value: "aaa", + }, + ), + Interpolation( + InterpolatedElement { + range: 5..10, + expression: Name( + ExprName { + range: 6..9, + id: Name("bbb"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 10..13, + value: "ccc", + }, + ), + Interpolation( + InterpolatedElement { + range: 13..18, + expression: Name( + ExprName { + range: 14..17, + id: Name("ddd"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 18..21, + value: "eee", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap new file mode 100644 index 00000000000000..b72088efc89763 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..8, + value: TString( + ExprTString { + range: 0..8, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..8, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..4, + value: "\\", + }, + ), + Interpolation( + InterpolatedElement { + range: 4..7, + expression: Name( + ExprName { + range: 5..6, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap new file mode 100644 index 00000000000000..d34b25231ff518 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..8, + value: TString( + ExprTString { + range: 0..8, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..8, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..4, + value: "\n", + }, + ), + Interpolation( + InterpolatedElement { + range: 4..7, + expression: Name( + ExprName { + range: 5..6, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap new file mode 100644 index 00000000000000..396755f9857ef5 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..9, + value: TString( + ExprTString { + range: 0..9, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..9, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 3..5, + value: "\\\n", + }, + ), + Interpolation( + InterpolatedElement { + range: 5..8, + expression: Name( + ExprName { + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Raw { + uppercase_r: false, + }, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap new file mode 100644 index 00000000000000..99398a48b1fc11 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap @@ -0,0 +1,52 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..10, + value: TString( + ExprTString { + range: 0..10, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..10, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..9, + expression: Name( + ExprName { + range: 3..7, + id: Name("user"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap new file mode 100644 index 00000000000000..a745f0f639bd90 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap @@ -0,0 +1,84 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..38, + value: TString( + ExprTString { + range: 0..38, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..38, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 2..6, + value: "mix ", + }, + ), + Interpolation( + InterpolatedElement { + range: 6..13, + expression: Name( + ExprName { + range: 7..11, + id: Name("user"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 13..28, + value: " with text and ", + }, + ), + Interpolation( + InterpolatedElement { + range: 28..37, + expression: Name( + ExprName { + range: 29..35, + id: Name("second"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap new file mode 100644 index 00000000000000..a401f11e744023 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap @@ -0,0 +1,64 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..14, + value: TString( + ExprTString { + range: 0..14, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..14, + elements: [ + Interpolation( + InterpolatedElement { + range: 2..13, + expression: Name( + ExprName { + range: 3..7, + id: Name("user"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 9..12, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 9..12, + value: ">10", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap new file mode 100644 index 00000000000000..b14e366fb44086 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: suite +--- +[ + Expr( + StmtExpr { + range: 0..11, + value: TString( + ExprTString { + range: 0..11, + value: TStringValue { + inner: Single( + TString( + TString { + range: 0..11, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 4..5, + value: "\n", + }, + ), + Interpolation( + InterpolatedElement { + range: 5..8, + expression: Name( + ExprName { + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/string.rs b/crates/ruff_python_parser/src/string.rs index 7ae24798e31943..6642fbc25a5506 100644 --- a/crates/ruff_python_parser/src/string.rs +++ b/crates/ruff_python_parser/src/string.rs @@ -1,17 +1,22 @@ //! Parsing of string literals, bytes literals, and implicit string concatenation. use bstr::ByteSlice; +use std::fmt; use ruff_python_ast::{self as ast, AnyStringFlags, Expr, StringFlags}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::error::{LexicalError, LexicalErrorType}; +use crate::{ + TokenKind, + error::{LexicalError, LexicalErrorType}, +}; #[derive(Debug)] pub(crate) enum StringType { Str(ast::StringLiteral), Bytes(ast::BytesLiteral), FString(ast::FString), + TString(ast::TString), } impl Ranged for StringType { @@ -20,6 +25,7 @@ impl Ranged for StringType { Self::Str(node) => node.range(), Self::Bytes(node) => node.range(), Self::FString(node) => node.range(), + Self::TString(node) => node.range(), } } } @@ -30,6 +36,48 @@ impl From for Expr { StringType::Str(node) => Expr::from(node), StringType::Bytes(node) => Expr::from(node), StringType::FString(node) => Expr::from(node), + StringType::TString(node) => Expr::from(node), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum InterpolatedStringKind { + FString, + TString, +} + +impl InterpolatedStringKind { + #[inline] + pub(crate) const fn start_token(self) -> TokenKind { + match self { + InterpolatedStringKind::FString => TokenKind::FStringStart, + InterpolatedStringKind::TString => TokenKind::TStringStart, + } + } + + #[inline] + pub(crate) const fn middle_token(self) -> TokenKind { + match self { + InterpolatedStringKind::FString => TokenKind::FStringMiddle, + InterpolatedStringKind::TString => TokenKind::TStringMiddle, + } + } + + #[inline] + pub(crate) const fn end_token(self) -> TokenKind { + match self { + InterpolatedStringKind::FString => TokenKind::FStringEnd, + InterpolatedStringKind::TString => TokenKind::TStringEnd, + } + } +} + +impl fmt::Display for InterpolatedStringKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InterpolatedStringKind::FString => f.write_str("f-string"), + InterpolatedStringKind::TString => f.write_str("t-string"), } } } @@ -231,10 +279,12 @@ impl StringParser { Ok(Some(EscapedChar::Literal(new_char))) } - fn parse_fstring_middle(mut self) -> Result { - // Fast-path: if the f-string doesn't contain any escape sequences, return the literal. + fn parse_interpolated_string_middle( + mut self, + ) -> Result { + // Fast-path: if the f-string or t-string doesn't contain any escape sequences, return the literal. let Some(mut index) = memchr::memchr3(b'{', b'}', b'\\', self.source.as_bytes()) else { - return Ok(ast::FStringLiteralElement { + return Ok(ast::InterpolatedStringLiteralElement { value: self.source, range: self.range, }); @@ -249,7 +299,7 @@ impl StringParser { // Add the escaped character to the string. match &self.source.as_bytes()[self.cursor - 1] { - // If there are any curly braces inside a `FStringMiddle` token, + // If there are any curly braces inside a `F/TStringMiddle` token, // then they were escaped (i.e. `{{` or `}}`). This means that // we need increase the location by 2 instead of 1. b'{' => { @@ -260,7 +310,7 @@ impl StringParser { self.offset += TextSize::from(1); value.push('}'); } - // We can encounter a `\` as the last character in a `FStringMiddle` + // We can encounter a `\` as the last character in a `F/TStringMiddle` // token which is valid in this context. For example, // // ```python @@ -268,7 +318,7 @@ impl StringParser { // # ^ ^^ ^ // ``` // - // Here, the `FStringMiddle` token content will be "\" and " \" + // Here, the `F/TStringMiddle` token content will be "\" and " \" // which is invalid if we look at the content in isolation: // // ```python @@ -276,7 +326,7 @@ impl StringParser { // ``` // // However, the content is syntactically valid in the context of - // the f-string because it's a substring of the entire f-string. + // the f/t-string because it's a substring of the entire f/t-string. // This is still an invalid escape sequence, but we don't want to // raise a syntax error as is done by the CPython parser. It might // be supported in the future, refer to point 3: https://peps.python.org/pep-0701/#rejected-ideas @@ -311,7 +361,7 @@ impl StringParser { index = next_index; } - Ok(ast::FStringLiteralElement { + Ok(ast::InterpolatedStringLiteralElement { value: value.into_boxed_str(), range: self.range, }) @@ -458,12 +508,12 @@ pub(crate) fn parse_string_literal( } // TODO(dhruvmanila): Move this to the new parser -pub(crate) fn parse_fstring_literal_element( +pub(crate) fn parse_interpolated_string_literal_element( source: Box, flags: AnyStringFlags, range: TextRange, -) -> Result { - StringParser::new(source, flags, range.start(), range).parse_fstring_middle() +) -> Result { + StringParser::new(source, flags, range.start(), range).parse_interpolated_string_middle() } #[cfg(test)] @@ -471,7 +521,7 @@ mod tests { use ruff_python_ast::Suite; use crate::error::LexicalErrorType; - use crate::{FStringErrorType, ParseError, ParseErrorType, Parsed, parse_module}; + use crate::{InterpolatedStringErrorType, ParseError, ParseErrorType, Parsed, parse_module}; const WINDOWS_EOL: &str = "\r\n"; const MAC_EOL: &str = "\r"; @@ -553,7 +603,7 @@ mod tests { insta::assert_debug_snapshot!(suite); } - fn parse_fstring_error(source: &str) -> FStringErrorType { + fn parse_fstring_error(source: &str) -> InterpolatedStringErrorType { parse_suite(source) .map_err(|e| match e.error { ParseErrorType::Lexical(LexicalErrorType::FStringError(e)) => e, @@ -565,7 +615,7 @@ mod tests { #[test] fn test_parse_invalid_fstring() { - use FStringErrorType::{InvalidConversionFlag, LambdaWithoutParentheses}; + use InterpolatedStringErrorType::{InvalidConversionFlag, LambdaWithoutParentheses}; assert_eq!(parse_fstring_error(r#"f"{5!x}""#), InvalidConversionFlag); assert_eq!( @@ -616,6 +666,118 @@ mod tests { insta::assert_debug_snapshot!(suite); } + #[test] + fn test_parse_tstring() { + let source = r#"t"{a}{ b }{{foo}}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_nested_spec() { + let source = r#"t"{foo:{spec}}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_not_nested_spec() { + let source = r#"t"{foo:spec}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_empty_tstring() { + let source = r#"t"""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_parse_self_documenting_base() { + let source = r#"t"{user=}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_parse_self_documenting_base_more() { + let source = r#"t"mix {user=} with text and {second=}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_parse_self_documenting_format() { + let source = r#"t"{user=:>10}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + fn parse_tstring_error(source: &str) -> InterpolatedStringErrorType { + parse_suite(source) + .map_err(|e| match e.error { + ParseErrorType::Lexical(LexicalErrorType::TStringError(e)) => e, + ParseErrorType::TStringError(e) => e, + e => unreachable!("Expected TStringError: {:?}", e), + }) + .expect_err("Expected error") + } + + #[test] + fn test_parse_invalid_tstring() { + use InterpolatedStringErrorType::{InvalidConversionFlag, LambdaWithoutParentheses}; + + assert_eq!(parse_tstring_error(r#"t"{5!x}""#), InvalidConversionFlag); + assert_eq!( + parse_tstring_error("t'{lambda x:{x}}'"), + LambdaWithoutParentheses + ); + // NOTE: The parser produces the `LambdaWithoutParentheses` for this case, but + // since the parser only return the first error to maintain compatibility with + // the rest of the codebase, this test case fails. The `LambdaWithoutParentheses` + // error appears after the unexpected `tStringMiddle` token, which is between the + // `:` and the `{`. + // assert_eq!(parse_tstring_error("f'{lambda x: {x}}'"), LambdaWithoutParentheses); + assert!(parse_suite(r#"t"{class}""#).is_err()); + } + + #[test] + fn test_parse_tstring_not_equals() { + let source = r#"t"{1 != 2}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_equals() { + let source = r#"t"{42 == 42}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_self_doc_prec_space() { + let source = r#"t"{x =}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_self_doc_trailing_space() { + let source = r#"t"{x= }""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_yield_expr() { + let source = r#"t"{yield}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + #[test] fn test_parse_string_concat() { let source = "'Hello ' 'world'"; @@ -679,6 +841,62 @@ mod tests { insta::assert_debug_snapshot!(suite); } + #[test] + fn test_parse_t_string_concat_1() { + let source = "'Hello ' t'world'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_t_string_concat_2() { + let source = "'Hello ' t'world'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_t_string_concat_3() { + let source = "'Hello ' t'world{\"!\"}'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_t_string_concat_4() { + let source = "'Hello ' t'world{\"!\"}' 'again!'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_u_t_string_concat_1() { + let source = "u'Hello ' t'world'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_u_t_string_concat_2() { + let source = "u'Hello ' t'world' '!'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_f_t_string_concat_1() { + let source = "f'Hello ' t'world'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_f_t_string_concat_2() { + let source = "f'Hello ' t'world' '!'"; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + #[test] fn test_parse_string_triple_quotes_with_kind() { let source = "u'''Hello, world!'''"; @@ -796,6 +1014,71 @@ mod tests { insta::assert_debug_snapshot!(suite); } + #[test] + fn test_tstring_escaped_newline() { + let source = r#"t"\n{x}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_constant_range() { + let source = r#"t"aaa{bbb}ccc{ddd}eee""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_unescaped_newline() { + let source = r#"t""" +{x}""""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_escaped_character() { + let source = r#"t"\\{x}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_raw_tstring() { + let source = r#"rt"{x}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_triple_quoted_raw_tstring() { + let source = r#"rt"""{x}""""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_tstring_line_continuation() { + let source = r#"rt"\ +{x}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_nested_string_spec() { + let source = r#"t"{foo:{''}}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + + #[test] + fn test_parse_tstring_nested_concatenation_string_spec() { + let source = r#"t"{foo:{'' ''}}""#; + let suite = parse_suite(source).unwrap(); + insta::assert_debug_snapshot!(suite); + } + /// #[test] fn test_dont_panic_on_8_in_octal_escape() { diff --git a/crates/ruff_python_parser/src/token.rs b/crates/ruff_python_parser/src/token.rs index 193aecac512661..59e4c0581cca4f 100644 --- a/crates/ruff_python_parser/src/token.rs +++ b/crates/ruff_python_parser/src/token.rs @@ -12,7 +12,7 @@ use bitflags::bitflags; use ruff_python_ast::name::Name; use ruff_python_ast::str::{Quote, TripleQuotes}; use ruff_python_ast::str_prefix::{ - AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, + AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, TStringPrefix, }; use ruff_python_ast::{AnyStringFlags, BoolOp, Int, IpyEscapeKind, Operator, StringFlags, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; @@ -48,7 +48,7 @@ impl Token { /// /// # Panics /// - /// If it isn't a string or any f-string tokens. + /// If it isn't a string or any f/t-string tokens. pub fn is_triple_quoted_string(self) -> bool { self.unwrap_string_flags().is_triple_quoted() } @@ -57,7 +57,7 @@ impl Token { /// /// # Panics /// - /// If it isn't a string or any f-string tokens. + /// If it isn't a string or any f/t-string tokens. pub fn string_quote_style(self) -> Quote { self.unwrap_string_flags().quote_style() } @@ -66,7 +66,7 @@ impl Token { /// /// # Panics /// - /// If it isn't a string or any f-string tokens. + /// If it isn't a string or any f/t-string tokens. pub fn unwrap_string_flags(self) -> AnyStringFlags { self.string_flags() .unwrap_or_else(|| panic!("token to be a string")) @@ -81,7 +81,8 @@ impl Token { } } - /// Returns `true` if this is any kind of string token. + /// Returns `true` if this is any kind of string token - including + /// tokens in t-strings (which do not have type `str`). const fn is_any_string(self) -> bool { matches!( self.kind, @@ -89,6 +90,9 @@ impl Token { | TokenKind::FStringStart | TokenKind::FStringMiddle | TokenKind::FStringEnd + | TokenKind::TStringStart + | TokenKind::TStringMiddle + | TokenKind::TStringEnd ) } } @@ -140,6 +144,14 @@ pub enum TokenKind { FStringMiddle, /// Token kind for the end of an f-string. This includes the closing quote. FStringEnd, + /// Token kind for the start of a t-string. This includes the `t`/`T`/`tr` prefix + /// and the opening quote(s). + TStringStart, + /// Token kind that includes the portion of text inside the t-string that's not + /// part of the interpolation part and isn't an opening or closing brace. + TStringMiddle, + /// Token kind for the end of a t-string. This includes the closing quote. + TStringEnd, /// Token kind for a IPython escape command. IpyEscapeCommand, /// Token kind for a comment. These are filtered out of the token stream prior to parsing. @@ -462,6 +474,11 @@ impl TokenKind { matches!(self, TokenKind::Plus | TokenKind::Minus) } + #[inline] + pub const fn is_interpolated_string_end(self) -> bool { + matches!(self, TokenKind::FStringEnd | TokenKind::TStringEnd) + } + /// Returns the [`UnaryOp`] that corresponds to this token kind, if it is a unary arithmetic /// operator, otherwise return [None]. /// @@ -613,6 +630,9 @@ impl fmt::Display for TokenKind { TokenKind::FStringStart => "FStringStart", TokenKind::FStringMiddle => "FStringMiddle", TokenKind::FStringEnd => "FStringEnd", + TokenKind::TStringStart => "TStringStart", + TokenKind::TStringMiddle => "TStringMiddle", + TokenKind::TStringEnd => "TStringEnd", TokenKind::IpyEscapeCommand => "IPython escape command", TokenKind::Comment => "comment", TokenKind::Question => "'?'", @@ -722,10 +742,12 @@ bitflags! { const BYTE_STRING = 1 << 3; /// The token is an f-string i.e., prefixed with `f` or `F` const F_STRING = 1 << 4; + /// The token is a t-string i.e., prefixed with `t` or `T` + const T_STRING = 1 << 5; /// The token is a raw string and the prefix character is in lowercase. - const RAW_STRING_LOWERCASE = 1 << 5; + const RAW_STRING_LOWERCASE = 1 << 6; /// The token is a raw string and the prefix character is in uppercase. - const RAW_STRING_UPPERCASE = 1 << 6; + const RAW_STRING_UPPERCASE = 1 << 7; /// The token is a raw string i.e., prefixed with `r` or `R` const RAW_STRING = Self::RAW_STRING_LOWERCASE.bits() | Self::RAW_STRING_UPPERCASE.bits(); @@ -758,6 +780,14 @@ impl StringFlags for TokenFlags { } else { AnyStringPrefix::Format(FStringPrefix::Regular) } + } else if self.intersects(TokenFlags::T_STRING) { + if self.intersects(TokenFlags::RAW_STRING_LOWERCASE) { + AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: false }) + } else if self.intersects(TokenFlags::RAW_STRING_UPPERCASE) { + AnyStringPrefix::Template(TStringPrefix::Raw { uppercase_r: true }) + } else { + AnyStringPrefix::Template(TStringPrefix::Regular) + } } else if self.intersects(TokenFlags::BYTE_STRING) { if self.intersects(TokenFlags::RAW_STRING_LOWERCASE) { AnyStringPrefix::Bytes(ByteStringPrefix::Raw { uppercase_r: false }) @@ -784,9 +814,19 @@ impl TokenFlags { self.intersects(TokenFlags::F_STRING) } - /// Returns `true` if the token is a triple-quoted f-string. - pub(crate) fn is_triple_quoted_fstring(self) -> bool { - self.contains(TokenFlags::F_STRING | TokenFlags::TRIPLE_QUOTED_STRING) + /// Returns `true` if the token is a t-string. + pub(crate) const fn is_t_string(self) -> bool { + self.intersects(TokenFlags::T_STRING) + } + + /// Returns `true` if the token is a t-string. + pub(crate) const fn is_interpolated_string(self) -> bool { + self.intersects(TokenFlags::T_STRING.union(TokenFlags::F_STRING)) + } + + /// Returns `true` if the token is a triple-quoted t-string. + pub(crate) fn is_triple_quoted_interpolated_string(self) -> bool { + self.intersects(TokenFlags::TRIPLE_QUOTED_STRING) && self.is_interpolated_string() } /// Returns `true` if the token is a raw string. @@ -819,7 +859,7 @@ pub(crate) enum TokenValue { String(Box), /// Token value that includes the portion of text inside the f-string that's not /// part of the expression part and isn't an opening or closing brace. - FStringMiddle(Box), + InterpolatedStringMiddle(Box), /// Token value for IPython escape commands. These are recognized by the lexer /// only when the mode is [`Mode::Ipython`]. IpyEscapeCommand { diff --git a/crates/ruff_python_parser/tests/fixtures.rs b/crates/ruff_python_parser/tests/fixtures.rs index 9284e312dbacb6..7aa7a6f2e2fc6f 100644 --- a/crates/ruff_python_parser/tests/fixtures.rs +++ b/crates/ruff_python_parser/tests/fixtures.rs @@ -40,7 +40,7 @@ fn inline_err() { fn test_valid_syntax(input_path: &Path) { let source = fs::read_to_string(input_path).expect("Expected test file to exist"); let options = extract_options(&source).unwrap_or_else(|| { - ParseOptions::from(Mode::Module).with_target_version(PythonVersion::latest()) + ParseOptions::from(Mode::Module).with_target_version(PythonVersion::latest_preview()) }); let parsed = parse_unchecked(&source, options.clone()); @@ -133,7 +133,7 @@ fn test_valid_syntax(input_path: &Path) { fn test_invalid_syntax(input_path: &Path) { let source = fs::read_to_string(input_path).expect("Expected test file to exist"); let options = extract_options(&source).unwrap_or_else(|| { - ParseOptions::from(Mode::Module).with_target_version(PythonVersion::latest()) + ParseOptions::from(Mode::Module).with_target_version(PythonVersion::PY314) }); let parsed = parse_unchecked(&source, options.clone()); diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap index 76f99c4e880207..b758b7438b649c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_invalid_annotation.py -snapshot_kind: text --- ## AST @@ -199,3 +198,23 @@ Module( 4 | x: y := int = 1 | ^^ Syntax Error: Expected a statement | + + +## Semantic Syntax Errors + + | +1 | x: *int = 1 +2 | x: yield a = 1 + | ^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation +3 | x: yield from b = 1 +4 | x: y := int = 1 + | + + + | +1 | x: *int = 1 +2 | x: yield a = 1 +3 | x: yield from b = 1 + | ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation +4 | x: y := int = 1 + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap index 76a48f21972719..c8b4ec3c122939 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_empty_expression.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 0..5, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..4, expression: Name( ExprName { @@ -63,8 +62,8 @@ Module( FString { range: 6..13, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 8..12, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap index 4a635ad3450869..36b66627ad3995 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_conversion_flag_name_tok.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 0..8, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..7, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap index 2f36142537edc8..0ee7e5fe73f6d1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_conversion_flag_other_tok.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 0..10, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..9, expression: Name( ExprName { @@ -63,8 +62,8 @@ Module( FString { range: 11..21, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 13..20, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap index e02a5c22b872e9..f47c692d58e6ae 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_starred_expr.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 77..83, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 79..82, expression: Starred( ExprStarred { @@ -69,8 +68,8 @@ Module( FString { range: 84..97, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 86..96, expression: Starred( ExprStarred { @@ -131,8 +130,8 @@ Module( FString { range: 98..111, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 100..110, expression: Starred( ExprStarred { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap index 05f1e2e3ff7795..121ca20d3c71c4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_lambda_without_parentheses.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 0..16, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..12, expression: Lambda( ExprLambda { @@ -66,7 +65,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 12..14, value: " x", }, @@ -111,5 +110,5 @@ Module( | 1 | f"{lambda x: x}" - | ^ Syntax Error: Expected an f-string element or the end of the f-string + | ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap index 7714d9e16286f1..9fd2f1b9b238bb 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap @@ -21,8 +21,8 @@ Module( FString { range: 0..4, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2..3, expression: Name( ExprName { @@ -62,8 +62,8 @@ Module( FString { range: 5..14, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 7..14, expression: Name( ExprName { @@ -103,8 +103,8 @@ Module( FString { range: 15..23, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 17..22, expression: Name( ExprName { @@ -150,8 +150,8 @@ Module( FString { range: 24..28, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 26..27, expression: Name( ExprName { @@ -177,8 +177,8 @@ Module( FString { range: 29..37, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 33..34, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap index c72b2029e95726..81f2c236a9bde1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/f_string_unclosed_lbrace_in_format_spec.py -snapshot_kind: text --- ## AST @@ -23,13 +22,13 @@ Module( range: 0..12, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..8, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 8..11, expression: Name( ExprName { @@ -41,7 +40,7 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 11..11, elements: [], }, @@ -75,13 +74,13 @@ Module( range: 13..28, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 15..21, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 21..27, expression: Name( ExprName { @@ -93,11 +92,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 24..27, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 24..27, value: ".3f", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap index 0d1731ead85aec..f1968dedf92e51 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap @@ -179,3 +179,13 @@ Module( 3 | def foo() -> yield x: ... | ^^^^^^^ Syntax Error: Yield expression cannot be used here | + + +## Semantic Syntax Errors + + | +1 | def foo() -> *int: ... +2 | def foo() -> (*int): ... +3 | def foo() -> yield x: ... + | ^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap index 2ba54bef2f7d58..5488502486f4c6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap @@ -84,13 +84,13 @@ Module( range: 29..40, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 31..37, value: "world ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 37..40, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap index cdfccb44422a79..672216d159feb3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap @@ -34,13 +34,13 @@ Module( range: 18..31, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 20..26, value: "world ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 26..29, expression: Name( ExprName { @@ -132,7 +132,7 @@ Module( range: 68..76, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 70..75, value: "third", }, @@ -198,7 +198,7 @@ Module( 2 | 'hello' 3 | f'world {x} 4 | ) - | ^ Syntax Error: Expected an f-string element or the end of the f-string + | ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string 5 | 1 + 1 6 | ( | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap index b21adb9b16ba28..66d0def077a544 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap @@ -1397,7 +1397,7 @@ Module( | 1 | def d[T]() -> (await 1): ... - | ^^^^^^^ Syntax Error: await expression cannot be used within a generic definition + | ^^^^^^^ Syntax Error: await expression cannot be used within a type annotation 2 | def e[T](arg: (await 1)): ... 3 | def f[T]() -> (y := 3): ... | @@ -1406,7 +1406,7 @@ Module( | 1 | def d[T]() -> (await 1): ... 2 | def e[T](arg: (await 1)): ... - | ^^^^^^^ Syntax Error: await expression cannot be used within a generic definition + | ^^^^^^^ Syntax Error: await expression cannot be used within a type annotation 3 | def f[T]() -> (y := 3): ... 4 | def g[T](arg: (x := 1)): ... | @@ -1416,7 +1416,7 @@ Module( 1 | def d[T]() -> (await 1): ... 2 | def e[T](arg: (await 1)): ... 3 | def f[T]() -> (y := 3): ... - | ^^^^^^ Syntax Error: named expression cannot be used within a generic definition + | ^^^^^^ Syntax Error: named expression cannot be used within a type annotation 4 | def g[T](arg: (x := 1)): ... 5 | def h[T](x: (yield 1)): ... | @@ -1426,7 +1426,7 @@ Module( 2 | def e[T](arg: (await 1)): ... 3 | def f[T]() -> (y := 3): ... 4 | def g[T](arg: (x := 1)): ... - | ^^^^^^ Syntax Error: named expression cannot be used within a generic definition + | ^^^^^^ Syntax Error: named expression cannot be used within a type annotation 5 | def h[T](x: (yield 1)): ... 6 | def j[T]() -> (yield 1): ... | @@ -1436,7 +1436,7 @@ Module( 3 | def f[T]() -> (y := 3): ... 4 | def g[T](arg: (x := 1)): ... 5 | def h[T](x: (yield 1)): ... - | ^^^^^^^ Syntax Error: yield expression cannot be used within a generic definition + | ^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation 6 | def j[T]() -> (yield 1): ... 7 | def l[T](x: (yield from 1)): ... | @@ -1446,7 +1446,7 @@ Module( 4 | def g[T](arg: (x := 1)): ... 5 | def h[T](x: (yield 1)): ... 6 | def j[T]() -> (yield 1): ... - | ^^^^^^^ Syntax Error: yield expression cannot be used within a generic definition + | ^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation 7 | def l[T](x: (yield from 1)): ... 8 | def n[T]() -> (yield from 1): ... | @@ -1456,7 +1456,7 @@ Module( 5 | def h[T](x: (yield 1)): ... 6 | def j[T]() -> (yield 1): ... 7 | def l[T](x: (yield from 1)): ... - | ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a generic definition + | ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation 8 | def n[T]() -> (yield from 1): ... 9 | def p[T: (yield 1)](): ... # yield in TypeVar bound | @@ -1466,7 +1466,7 @@ Module( 6 | def j[T]() -> (yield 1): ... 7 | def l[T](x: (yield from 1)): ... 8 | def n[T]() -> (yield from 1): ... - | ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a generic definition + | ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation 9 | def p[T: (yield 1)](): ... # yield in TypeVar bound 10 | def q[T = (yield 1)](): ... # yield in TypeVar default | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap index 790a2e91ca9db7..9c628a81d1d7f8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/invalid_fstring_literal_element.py -snapshot_kind: text --- ## AST @@ -23,7 +22,7 @@ Module( range: 0..26, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..25, value: "", }, @@ -55,7 +54,7 @@ Module( range: 27..57, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 31..54, value: "", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap index 07d4518fcc44f3..35384a51a4b42f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/mixed_bytes_and_non_bytes_literals.py -snapshot_kind: text --- ## AST @@ -61,7 +60,7 @@ Module( range: 18..26, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 20..25, value: "first", }, @@ -117,7 +116,7 @@ Module( range: 45..54, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 47..53, value: "second", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap index c63c0fdb25a9e4..d799a7893971dd 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap @@ -224,3 +224,13 @@ Module( 3 | def foo(arg: x := int): ... | ^^ Syntax Error: Expected ',', found ':=' | + + +## Semantic Syntax Errors + + | +1 | def foo(arg: *int): ... +2 | def foo(arg: yield int): ... + | ^^^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation +3 | def foo(arg: x := int): ... + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap index f036adb4b76915..13eeeed68978f2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap @@ -308,3 +308,14 @@ Module( | ^^^^^^^ Syntax Error: Yield expression cannot be used here 5 | # def foo(*args: **int): ... | + + +## Semantic Syntax Errors + + | +2 | def foo(*args: (*tuple[int])): ... +3 | def foo(*args: *int or str): ... +4 | def foo(*args: *yield x): ... + | ^^^^^^^ Syntax Error: yield expression cannot be used within a type annotation +5 | # def foo(*args: **int): ... + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap index 5ac816ecc872b7..34308fc5976051 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap @@ -22,13 +22,13 @@ Module( range: 44..74, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 46..58, value: "Magic wand: ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 58..73, expression: Subscript( ExprSubscript { @@ -92,8 +92,8 @@ Module( FString { range: 95..112, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 97..111, expression: Call( ExprCall { @@ -173,13 +173,13 @@ Module( range: 148..220, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 152..169, value: "A complex trick: ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 169..217, expression: Subscript( ExprSubscript { @@ -243,8 +243,8 @@ Module( FString { range: 221..254, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 223..253, expression: FString( ExprFString { @@ -255,8 +255,8 @@ Module( FString { range: 224..252, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 226..251, expression: FString( ExprFString { @@ -267,8 +267,8 @@ Module( FString { range: 227..250, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 229..249, expression: FString( ExprFString { @@ -279,8 +279,8 @@ Module( FString { range: 230..248, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 232..247, expression: FString( ExprFString { @@ -291,8 +291,8 @@ Module( FString { range: 233..246, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 235..245, expression: FString( ExprFString { @@ -303,8 +303,8 @@ Module( FString { range: 236..244, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 238..243, expression: BinOp( ExprBinOp { @@ -444,8 +444,8 @@ Module( FString { range: 276..310, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 278..303, expression: FString( ExprFString { @@ -456,8 +456,8 @@ Module( FString { range: 279..302, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 283..293, expression: StringLiteral( ExprStringLiteral { @@ -483,7 +483,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 293..299, value: " inner", }, @@ -506,7 +506,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 303..309, value: " outer", }, @@ -538,13 +538,13 @@ Module( range: 336..359, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 338..343, value: "test ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 343..353, expression: Name( ExprName { @@ -559,7 +559,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 353..358, value: " more", }, @@ -590,8 +590,8 @@ Module( FString { range: 403..422, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 407..419, expression: FString( ExprFString { @@ -602,8 +602,8 @@ Module( FString { range: 408..418, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 412..415, expression: Name( ExprName { @@ -660,8 +660,8 @@ Module( FString { range: 468..502, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 470..501, expression: Call( ExprCall { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap index 48dc7f7866b8dc..9db9481b224dae 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap @@ -527,13 +527,13 @@ Module( range: 895..905, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 897..903, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 903..905, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap index dbc237b1d3ac06..bb98cde3115c02 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap @@ -22,13 +22,13 @@ Module( range: 162..192, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 164..171, value: "middle ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 171..191, expression: StringLiteral( ExprStringLiteral { @@ -51,11 +51,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 181..191, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 181..191, value: " ", }, @@ -116,13 +116,13 @@ Module( range: 207..228, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 209..216, value: "middle ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 216..228, expression: StringLiteral( ExprStringLiteral { @@ -145,11 +145,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 226..228, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 226..228, value: "\\", }, @@ -209,13 +209,13 @@ Module( range: 253..285, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 255..262, value: "middle ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 262..284, expression: StringLiteral( ExprStringLiteral { @@ -238,11 +238,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 272..284, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 272..284, value: "\\ ", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap index 472a8579cc596c..efb2fecf820ca3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap @@ -22,13 +22,13 @@ Module( range: 166..178, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 170..176, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 176..178, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap index ccf98eb430ac18..c8431a27c9d175 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/re_lexing/triple_quoted_fstring_2.py -snapshot_kind: text --- ## AST @@ -22,8 +21,8 @@ Module( FString { range: 167..183, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 171..180, expression: Name( ExprName { @@ -35,11 +34,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 176..180, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 176..180, value: ".3f\n", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap index 4e4ba407eeaa22..e59c846c0f85b5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/re_lexing/triple_quoted_fstring_3.py -snapshot_kind: text --- ## AST @@ -35,8 +34,8 @@ Module( FString { range: 239..253, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 243..250, expression: Name( ExprName { @@ -48,11 +47,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 246..250, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 246..250, value: ".3f\n", }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap index 153ed4c2512d1b..7159d351f2c3ed 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap @@ -782,8 +782,8 @@ Module( FString { range: 576..585, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 578..584, expression: Name( ExprName { @@ -833,8 +833,8 @@ Module( FString { range: 591..609, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 593..598, expression: Name( ExprName { @@ -849,13 +849,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 598..603, value: " and ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 603..608, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap index 4fa30fed7b61a3..ec4380012b752e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap @@ -688,8 +688,8 @@ Module( FString { range: 387..396, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 389..395, expression: Name( ExprName { @@ -738,8 +738,8 @@ Module( FString { range: 403..421, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 405..410, expression: Name( ExprName { @@ -754,13 +754,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 410..415, value: " and ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 415..420, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap new file mode 100644 index 00000000000000..423ad64c794f5d --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap @@ -0,0 +1,113 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_empty_expression.py +--- +## AST + +``` +Module( + ModModule { + range: 0..58, + body: [ + Expr( + StmtExpr { + range: 44..49, + value: TString( + ExprTString { + range: 44..49, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..49, + elements: [ + Interpolation( + InterpolatedElement { + range: 46..48, + expression: Name( + ExprName { + range: 47..47, + id: Name(""), + ctx: Invalid, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 50..57, + value: TString( + ExprTString { + range: 50..57, + value: TStringValue { + inner: Single( + TString( + TString { + range: 50..57, + elements: [ + Interpolation( + InterpolatedElement { + range: 52..56, + expression: Name( + ExprName { + range: 53..53, + id: Name(""), + ctx: Invalid, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{}" + | ^ Syntax Error: Expected an expression +3 | t"{ }" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{}" +3 | t"{ }" + | ^ Syntax Error: Expected an expression + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap new file mode 100644 index 00000000000000..b65770e8d804ee --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap @@ -0,0 +1,63 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_name_tok.py +--- +## AST + +``` +Module( + ModModule { + range: 0..53, + body: [ + Expr( + StmtExpr { + range: 44..52, + value: TString( + ExprTString { + range: 44..52, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..52, + elements: [ + Interpolation( + InterpolatedElement { + range: 46..51, + expression: Name( + ExprName { + range: 47..48, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{x!z}" + | ^ Syntax Error: t-string: invalid conversion character + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap new file mode 100644 index 00000000000000..10324a349f6d30 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap @@ -0,0 +1,113 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_conversion_flag_other_tok.py +--- +## AST + +``` +Module( + ModModule { + range: 0..66, + body: [ + Expr( + StmtExpr { + range: 44..54, + value: TString( + ExprTString { + range: 44..54, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..54, + elements: [ + Interpolation( + InterpolatedElement { + range: 46..53, + expression: Name( + ExprName { + range: 47..48, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 55..65, + value: TString( + ExprTString { + range: 55..65, + value: TStringValue { + inner: Single( + TString( + TString { + range: 55..65, + elements: [ + Interpolation( + InterpolatedElement { + range: 57..64, + expression: Name( + ExprName { + range: 58..59, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{x!123}" + | ^^^ Syntax Error: t-string: invalid conversion character +3 | t"{x!'a'}" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{x!123}" +3 | t"{x!'a'}" + | ^^^ Syntax Error: t-string: invalid conversion character + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap new file mode 100644 index 00000000000000..a2fb0fda2de48f --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap @@ -0,0 +1,205 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_starred_expr.py +--- +## AST + +``` +Module( + ModModule { + range: 0..156, + body: [ + Expr( + StmtExpr { + range: 121..127, + value: TString( + ExprTString { + range: 121..127, + value: TStringValue { + inner: Single( + TString( + TString { + range: 121..127, + elements: [ + Interpolation( + InterpolatedElement { + range: 123..126, + expression: Starred( + ExprStarred { + range: 124..125, + value: Name( + ExprName { + range: 125..125, + id: Name(""), + ctx: Invalid, + }, + ), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 128..141, + value: TString( + ExprTString { + range: 128..141, + value: TStringValue { + inner: Single( + TString( + TString { + range: 128..141, + elements: [ + Interpolation( + InterpolatedElement { + range: 130..140, + expression: Starred( + ExprStarred { + range: 131..139, + value: BoolOp( + ExprBoolOp { + range: 132..139, + op: And, + values: [ + Name( + ExprName { + range: 132..133, + id: Name("x"), + ctx: Load, + }, + ), + Name( + ExprName { + range: 138..139, + id: Name("y"), + ctx: Load, + }, + ), + ], + }, + ), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 142..155, + value: TString( + ExprTString { + range: 142..155, + value: TStringValue { + inner: Single( + TString( + TString { + range: 142..155, + elements: [ + Interpolation( + InterpolatedElement { + range: 144..154, + expression: Starred( + ExprStarred { + range: 145..153, + value: Yield( + ExprYield { + range: 146..153, + value: Some( + Name( + ExprName { + range: 152..153, + id: Name("x"), + ctx: Load, + }, + ), + ), + }, + ), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | # Starred expression inside t-string has a minimum precedence of bitwise or. +3 | t"{*}" + | ^ Syntax Error: Expected an expression +4 | t"{*x and y}" +5 | t"{*yield x}" + | + + + | +2 | # Starred expression inside t-string has a minimum precedence of bitwise or. +3 | t"{*}" +4 | t"{*x and y}" + | ^^^^^^^ Syntax Error: Boolean expression cannot be used here +5 | t"{*yield x}" + | + + + | +3 | t"{*}" +4 | t"{*x and y}" +5 | t"{*yield x}" + | ^^^^^^^ Syntax Error: Yield expression cannot be used here + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap new file mode 100644 index 00000000000000..1138fe8530d7a2 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap @@ -0,0 +1,118 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py +--- +## AST + +``` +Module( + ModModule { + range: 0..61, + body: [ + Expr( + StmtExpr { + range: 44..60, + value: TString( + ExprTString { + range: 44..60, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..60, + elements: [ + Interpolation( + InterpolatedElement { + range: 46..56, + expression: Lambda( + ExprLambda { + range: 47..56, + parameters: Some( + Parameters { + range: 54..55, + posonlyargs: [], + args: [ + ParameterWithDefault { + range: 54..55, + parameter: Parameter { + range: 54..55, + name: Identifier { + id: Name("x"), + range: 54..55, + }, + annotation: None, + }, + default: None, + }, + ], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + ), + body: Name( + ExprName { + range: 56..56, + id: Name(""), + ctx: Invalid, + }, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 56..58, + value: " x", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{lambda x: x}" + | ^^ Syntax Error: Expected an expression + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{lambda x: x}" + | ^^^^^^^^^ Syntax Error: t-string: lambda expressions are not allowed without parentheses + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{lambda x: x}" + | ^^ Syntax Error: t-string: expecting '}' + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{lambda x: x}" + | ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap new file mode 100644 index 00000000000000..b910137829a7e5 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap @@ -0,0 +1,359 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace.py +--- +## AST + +``` +Module( + ModModule { + range: 0..82, + body: [ + Expr( + StmtExpr { + range: 44..48, + value: TString( + ExprTString { + range: 44..48, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..48, + elements: [ + Interpolation( + InterpolatedElement { + range: 46..47, + expression: Name( + ExprName { + range: 47..47, + id: Name(""), + ctx: Invalid, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 49..58, + value: TString( + ExprTString { + range: 49..58, + value: TStringValue { + inner: Single( + TString( + TString { + range: 49..58, + elements: [ + Interpolation( + InterpolatedElement { + range: 51..58, + expression: Name( + ExprName { + range: 52..55, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 59..67, + value: TString( + ExprTString { + range: 59..67, + value: TStringValue { + inner: Single( + TString( + TString { + range: 59..67, + elements: [ + Interpolation( + InterpolatedElement { + range: 61..66, + expression: Name( + ExprName { + range: 62..65, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 68..81, + value: TString( + ExprTString { + range: 68..81, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 68..72, + elements: [ + Interpolation( + InterpolatedElement { + range: 70..71, + expression: Name( + ExprName { + range: 71..71, + id: Name(""), + ctx: Invalid, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 73..81, + elements: [ + Interpolation( + InterpolatedElement { + range: 77..78, + expression: Name( + ExprName { + range: 78..78, + id: Name(""), + ctx: Invalid, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" + | ^ Syntax Error: missing closing quote in string literal +3 | t"{foo!r" +4 | t"{foo=" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" + | ^ Syntax Error: t-string: unterminated string +3 | t"{foo!r" +4 | t"{foo=" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" + | ^ Syntax Error: t-string: unterminated string +3 | t"{foo!r" +4 | t"{foo=" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" +3 | t"{foo!r" + | ^^ Syntax Error: missing closing quote in string literal +4 | t"{foo=" +5 | t"{" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" +3 | t"{foo!r" + | ^ Syntax Error: t-string: unterminated string +4 | t"{foo=" +5 | t"{" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" +3 | t"{foo!r" + | ^ Syntax Error: t-string: unterminated string +4 | t"{foo=" +5 | t"{" + | + + + | +2 | t"{" +3 | t"{foo!r" +4 | t"{foo=" + | ^^ Syntax Error: t-string: expecting '}' +5 | t"{" +6 | t"""{""" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"{" +3 | t"{foo!r" + | ^ Syntax Error: Expected TStringEnd, found Unknown +4 | t"{foo=" +5 | t"{" + | + + + | +2 | t"{" +3 | t"{foo!r" +4 | t"{foo=" + | ^ Syntax Error: missing closing quote in string literal +5 | t"{" +6 | t"""{""" + | + + + | +2 | t"{" +3 | t"{foo!r" +4 | t"{foo=" + | ^ Syntax Error: t-string: unterminated string +5 | t"{" +6 | t"""{""" + | + + + | +2 | t"{" +3 | t"{foo!r" +4 | t"{foo=" + | ^ Syntax Error: t-string: unterminated string +5 | t"{" +6 | t"""{""" + | + + + | +3 | t"{foo!r" +4 | t"{foo=" +5 | t"{" + | ^ Syntax Error: missing closing quote in string literal +6 | t"""{""" + | + + + | +4 | t"{foo=" +5 | t"{" +6 | t"""{""" + | ^^^^ Syntax Error: Expected TStringEnd, found TStringStart + | + + + | +4 | t"{foo=" +5 | t"{" +6 | t"""{""" + | ^^^ Syntax Error: Expected an expression + | + + + | +5 | t"{" +6 | t"""{""" + | ^ Syntax Error: unexpected EOF while parsing + | + + + | +5 | t"{" +6 | t"""{""" + | ^ Syntax Error: t-string: unterminated string + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap new file mode 100644 index 00000000000000..3c16c4c1617842 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap @@ -0,0 +1,142 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbrace_in_format_spec.py +--- +## AST + +``` +Module( + ModModule { + range: 0..73, + body: [ + Expr( + StmtExpr { + range: 44..56, + value: TString( + ExprTString { + range: 44..56, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..56, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 46..52, + value: "hello ", + }, + ), + Interpolation( + InterpolatedElement { + range: 52..55, + expression: Name( + ExprName { + range: 53..54, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 55..55, + elements: [], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 57..72, + value: TString( + ExprTString { + range: 57..72, + value: TStringValue { + inner: Single( + TString( + TString { + range: 57..72, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 59..65, + value: "hello ", + }, + ), + Interpolation( + InterpolatedElement { + range: 65..71, + expression: Name( + ExprName { + range: 66..67, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 68..71, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 68..71, + value: ".3f", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"hello {x:" + | ^ Syntax Error: t-string: expecting '}' +3 | t"hello {x:.3f" + | + + + | +1 | # parse_options: {"target-version": "3.14"} +2 | t"hello {x:" +3 | t"hello {x:.3f" + | ^ Syntax Error: t-string: expecting '}' + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap index c2c29483fd5780..ad595e25bbbfef 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap @@ -74,13 +74,13 @@ Module( range: 14..24, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 16..22, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 22..24, expression: Name( ExprName { @@ -148,13 +148,13 @@ Module( range: 31..42, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 33..39, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 39..42, expression: Name( ExprName { @@ -166,7 +166,7 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 42..42, elements: [], }, @@ -227,13 +227,13 @@ Module( range: 49..60, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 51..57, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 57..60, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap index 617675786f5c5b..348aa344d8688e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap @@ -173,7 +173,7 @@ Module( | 1 | del __debug__ - | ^^^^^^^^^ Syntax Error: cannot delete `__debug__` on Python 3.13 (syntax was removed in 3.9) + | ^^^^^^^^^ Syntax Error: cannot delete `__debug__` on Python 3.14 (syntax was removed in 3.9) 2 | del x, y, __debug__, z 3 | __debug__ = 1 | @@ -182,7 +182,7 @@ Module( | 1 | del __debug__ 2 | del x, y, __debug__, z - | ^^^^^^^^^ Syntax Error: cannot delete `__debug__` on Python 3.13 (syntax was removed in 3.9) + | ^^^^^^^^^ Syntax Error: cannot delete `__debug__` on Python 3.14 (syntax was removed in 3.9) 3 | __debug__ = 1 4 | x, y, __debug__, z = 1, 2, 3, 4 | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap index 2fe8ea28556417..99067deeda0f23 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/f_string.py -snapshot_kind: text --- ## AST @@ -147,8 +146,8 @@ Module( FString { range: 47..56, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 49..55, expression: StringLiteral( ExprStringLiteral { @@ -199,8 +198,8 @@ Module( FString { range: 57..67, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 59..66, expression: Name( ExprName { @@ -240,8 +239,8 @@ Module( FString { range: 68..75, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 70..74, expression: Tuple( ExprTuple { @@ -291,8 +290,8 @@ Module( FString { range: 76..86, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 78..85, expression: Compare( ExprCompare { @@ -323,7 +322,7 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 84..84, elements: [], }, @@ -356,8 +355,8 @@ Module( FString { range: 87..102, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 89..101, expression: NumberLiteral( ExprNumberLiteral { @@ -370,11 +369,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 92..100, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 92..97, expression: StringLiteral( ExprStringLiteral { @@ -400,7 +399,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 97..100, value: ">10", }, @@ -436,8 +435,8 @@ Module( FString { range: 103..118, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 105..117, expression: NumberLiteral( ExprNumberLiteral { @@ -450,11 +449,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 108..116, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 108..113, expression: StringLiteral( ExprStringLiteral { @@ -480,7 +479,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 113..116, value: ">10", }, @@ -516,8 +515,8 @@ Module( FString { range: 119..133, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 121..132, expression: Name( ExprName { @@ -562,8 +561,8 @@ Module( FString { range: 134..154, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 136..153, expression: Name( ExprName { @@ -580,11 +579,11 @@ Module( ), conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 147..152, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 147..152, value: ".3f ", }, @@ -620,8 +619,8 @@ Module( FString { range: 155..173, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 157..172, expression: Name( ExprName { @@ -666,8 +665,8 @@ Module( FString { range: 174..190, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 176..189, expression: Tuple( ExprTuple { @@ -730,8 +729,8 @@ Module( FString { range: 191..217, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 193..216, expression: FString( ExprFString { @@ -742,8 +741,8 @@ Module( FString { range: 194..210, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 196..209, expression: NumberLiteral( ExprNumberLiteral { @@ -761,11 +760,11 @@ Module( ), conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 205..208, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 205..208, value: ".1f", }, @@ -790,11 +789,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 211..215, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 211..215, value: "*^20", }, @@ -849,13 +848,13 @@ Module( range: 227..242, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 229..233, value: "bar ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 233..240, expression: BinOp( ExprBinOp { @@ -883,7 +882,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 240..241, value: " ", }, @@ -1036,13 +1035,13 @@ Module( range: 347..364, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 349..350, value: "\\", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 350..355, expression: Name( ExprName { @@ -1057,13 +1056,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 355..356, value: "\\", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 356..363, expression: Name( ExprName { @@ -1075,11 +1074,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 361..362, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 361..362, value: "\\", }, @@ -1116,7 +1115,7 @@ Module( range: 365..379, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 367..378, value: "\\{foo\\}", }, @@ -1147,8 +1146,8 @@ Module( FString { range: 380..420, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 384..417, expression: Name( ExprName { @@ -1160,11 +1159,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 394..416, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 394..416, value: "x\n y\n z\n", }, @@ -1200,8 +1199,8 @@ Module( FString { range: 421..439, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 423..438, expression: Name( ExprName { @@ -1247,13 +1246,13 @@ Module( range: 441..486, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 443..450, value: "normal ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 450..455, expression: Name( ExprName { @@ -1268,13 +1267,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 455..468, value: " {another} ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 468..473, expression: Name( ExprName { @@ -1289,13 +1288,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 473..476, value: " {", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 476..483, expression: Name( ExprName { @@ -1310,7 +1309,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 483..485, value: "}", }, @@ -1342,13 +1341,13 @@ Module( range: 487..529, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 489..496, value: "normal ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 496..503, expression: Name( ExprName { @@ -1363,13 +1362,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 503..504, value: " ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 504..511, expression: Name( ExprName { @@ -1384,13 +1383,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 511..512, value: " ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 512..519, expression: Name( ExprName { @@ -1405,13 +1404,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 519..520, value: " ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 520..528, expression: Name( ExprName { @@ -1452,13 +1451,13 @@ Module( range: 530..549, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 532..539, value: "normal ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 539..548, expression: Name( ExprName { @@ -1470,11 +1469,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 542..547, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 542..547, value: "y + 2", }, @@ -1510,8 +1509,8 @@ Module( FString { range: 550..568, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 552..567, expression: Name( ExprName { @@ -1523,11 +1522,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 555..566, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 555..566, expression: Call( ExprCall { @@ -1600,8 +1599,8 @@ Module( FString { range: 569..588, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 571..587, expression: Lambda( ExprLambda { @@ -1676,8 +1675,8 @@ Module( FString { range: 589..597, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 591..596, expression: Name( ExprName { @@ -1722,8 +1721,8 @@ Module( FString { range: 598..611, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 600..610, expression: Name( ExprName { @@ -1768,8 +1767,8 @@ Module( FString { range: 612..621, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 614..620, expression: Name( ExprName { @@ -1814,8 +1813,8 @@ Module( FString { range: 622..636, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 624..635, expression: Name( ExprName { @@ -1827,11 +1826,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 627..634, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 627..634, value: ".3f!r =", }, @@ -1867,8 +1866,8 @@ Module( FString { range: 637..653, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 639..652, expression: Name( ExprName { @@ -1885,11 +1884,11 @@ Module( ), conversion: Repr, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 648..651, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 648..651, value: ".3f", }, @@ -1925,8 +1924,8 @@ Module( FString { range: 654..667, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 656..666, expression: Name( ExprName { @@ -1938,11 +1937,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 659..665, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 659..665, value: ".3f=!r", }, @@ -1990,8 +1989,8 @@ Module( FString { range: 676..682, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 678..681, expression: Name( ExprName { @@ -2033,8 +2032,8 @@ Module( FString { range: 683..689, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 685..688, expression: Name( ExprName { @@ -2060,8 +2059,8 @@ Module( FString { range: 690..696, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 692..695, expression: Name( ExprName { @@ -2103,8 +2102,8 @@ Module( FString { range: 697..703, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 699..702, expression: Name( ExprName { @@ -2157,13 +2156,13 @@ Module( range: 712..756, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 714..739, value: "Invalid args in command: ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 739..755, expression: Tuple( ExprTuple { @@ -2237,8 +2236,8 @@ Module( FString { range: 763..769, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 765..768, expression: Name( ExprName { @@ -2292,7 +2291,7 @@ Module( range: 782..786, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 784..785, value: "a", }, @@ -2310,7 +2309,7 @@ Module( range: 791..795, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 793..794, value: "b", }, @@ -2339,7 +2338,7 @@ Module( range: 808..813, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 811..812, value: "d", }, @@ -2359,7 +2358,7 @@ Module( range: 818..823, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 821..822, value: "e", }, @@ -2405,8 +2404,8 @@ Module( FString { range: 857..865, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 859..864, expression: Name( ExprName { @@ -2481,8 +2480,8 @@ Module( FString { range: 886..894, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 888..893, expression: Name( ExprName { @@ -2557,8 +2556,8 @@ Module( FString { range: 916..924, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 918..923, expression: Name( ExprName { @@ -2634,13 +2633,13 @@ Module( range: 947..966, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 949..953, value: "bar ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 953..958, expression: Name( ExprName { @@ -2655,7 +2654,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 958..965, value: " really", }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap new file mode 100644 index 00000000000000..99a7bcd3b9c1e0 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap @@ -0,0 +1,3127 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/valid/expressions/t_string.py +--- +## AST + +``` +Module( + ModModule { + range: 0..1143, + body: [ + Expr( + StmtExpr { + range: 18..21, + value: TString( + ExprTString { + range: 18..21, + value: TStringValue { + inner: Single( + TString( + TString { + range: 18..21, + elements: [], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 22..25, + value: TString( + ExprTString { + range: 22..25, + value: TStringValue { + inner: Single( + TString( + TString { + range: 22..25, + elements: [], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 26..29, + value: TString( + ExprTString { + range: 26..29, + value: TStringValue { + inner: Single( + TString( + TString { + range: 26..29, + elements: [], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 30..37, + value: TString( + ExprTString { + range: 30..37, + value: TStringValue { + inner: Single( + TString( + TString { + range: 30..37, + elements: [], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 38..45, + value: TString( + ExprTString { + range: 38..45, + value: TStringValue { + inner: Single( + TString( + TString { + range: 38..45, + elements: [], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 47..56, + value: TString( + ExprTString { + range: 47..56, + value: TStringValue { + inner: Single( + TString( + TString { + range: 47..56, + elements: [ + Interpolation( + InterpolatedElement { + range: 49..55, + expression: StringLiteral( + ExprStringLiteral { + range: 50..54, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 50..54, + value: " t", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 57..67, + value: TString( + ExprTString { + range: 57..67, + value: TStringValue { + inner: Single( + TString( + TString { + range: 57..67, + elements: [ + Interpolation( + InterpolatedElement { + range: 59..66, + expression: Name( + ExprName { + range: 60..63, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Str, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 68..75, + value: TString( + ExprTString { + range: 68..75, + value: TStringValue { + inner: Single( + TString( + TString { + range: 68..75, + elements: [ + Interpolation( + InterpolatedElement { + range: 70..74, + expression: Tuple( + ExprTuple { + range: 71..73, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 71..72, + value: Int( + 3, + ), + }, + ), + ], + ctx: Load, + parenthesized: false, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 76..86, + value: TString( + ExprTString { + range: 76..86, + value: TStringValue { + inner: Single( + TString( + TString { + range: 76..86, + elements: [ + Interpolation( + InterpolatedElement { + range: 78..85, + expression: Compare( + ExprCompare { + range: 79..83, + left: NumberLiteral( + ExprNumberLiteral { + range: 79..80, + value: Int( + 3, + ), + }, + ), + ops: [ + NotEq, + ], + comparators: [ + NumberLiteral( + ExprNumberLiteral { + range: 82..83, + value: Int( + 4, + ), + }, + ), + ], + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 84..84, + elements: [], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 87..102, + value: TString( + ExprTString { + range: 87..102, + value: TStringValue { + inner: Single( + TString( + TString { + range: 87..102, + elements: [ + Interpolation( + InterpolatedElement { + range: 89..101, + expression: NumberLiteral( + ExprNumberLiteral { + range: 90..91, + value: Int( + 3, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 92..100, + elements: [ + Interpolation( + InterpolatedElement { + range: 92..97, + expression: StringLiteral( + ExprStringLiteral { + range: 93..96, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 93..96, + value: "}", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 97..100, + value: ">10", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 103..118, + value: TString( + ExprTString { + range: 103..118, + value: TStringValue { + inner: Single( + TString( + TString { + range: 103..118, + elements: [ + Interpolation( + InterpolatedElement { + range: 105..117, + expression: NumberLiteral( + ExprNumberLiteral { + range: 106..107, + value: Int( + 3, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 108..116, + elements: [ + Interpolation( + InterpolatedElement { + range: 108..113, + expression: StringLiteral( + ExprStringLiteral { + range: 109..112, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 109..112, + value: "{", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 113..116, + value: ">10", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 119..133, + value: TString( + ExprTString { + range: 119..133, + value: TStringValue { + inner: Single( + TString( + TString { + range: 119..133, + elements: [ + Interpolation( + InterpolatedElement { + range: 121..132, + expression: Name( + ExprName { + range: 124..127, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: " ", + trailing: " = ", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 134..154, + value: TString( + ExprTString { + range: 134..154, + value: TStringValue { + inner: Single( + TString( + TString { + range: 134..154, + elements: [ + Interpolation( + InterpolatedElement { + range: 136..153, + expression: Name( + ExprName { + range: 139..142, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: " ", + trailing: " = ", + }, + ), + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 147..152, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 147..152, + value: ".3f ", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 155..173, + value: TString( + ExprTString { + range: 155..173, + value: TStringValue { + inner: Single( + TString( + TString { + range: 155..173, + elements: [ + Interpolation( + InterpolatedElement { + range: 157..172, + expression: Name( + ExprName { + range: 160..163, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: " ", + trailing: " = ", + }, + ), + conversion: Str, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 174..190, + value: TString( + ExprTString { + range: 174..190, + value: TStringValue { + inner: Single( + TString( + TString { + range: 174..190, + elements: [ + Interpolation( + InterpolatedElement { + range: 176..189, + expression: Tuple( + ExprTuple { + range: 179..183, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 179..180, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 182..183, + value: Int( + 2, + ), + }, + ), + ], + ctx: Load, + parenthesized: false, + }, + ), + debug_text: Some( + DebugText { + leading: " ", + trailing: " = ", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 191..217, + value: TString( + ExprTString { + range: 191..217, + value: TStringValue { + inner: Single( + TString( + TString { + range: 191..217, + elements: [ + Interpolation( + InterpolatedElement { + range: 193..216, + expression: TString( + ExprTString { + range: 194..210, + value: TStringValue { + inner: Single( + TString( + TString { + range: 194..210, + elements: [ + Interpolation( + InterpolatedElement { + range: 196..209, + expression: NumberLiteral( + ExprNumberLiteral { + range: 197..203, + value: Float( + 3.1415, + ), + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 205..208, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 205..208, + value: ".1f", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 211..215, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 211..215, + value: "*^20", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 219..253, + value: Dict( + ExprDict { + range: 219..253, + items: [ + DictItem { + key: Some( + TString( + ExprTString { + range: 220..248, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 220..226, + value: "foo ", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 227..242, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 229..233, + value: "bar ", + }, + ), + Interpolation( + InterpolatedElement { + range: 233..240, + expression: BinOp( + ExprBinOp { + range: 234..239, + left: Name( + ExprName { + range: 234..235, + id: Name("x"), + ctx: Load, + }, + ), + op: Add, + right: Name( + ExprName { + range: 238..239, + id: Name("y"), + ctx: Load, + }, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 240..241, + value: " ", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 243..248, + value: "baz", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 250..252, + value: Int( + 10, + ), + }, + ), + }, + ], + }, + ), + }, + ), + Match( + StmtMatch { + range: 254..345, + subject: Name( + ExprName { + range: 260..263, + id: Name("foo"), + ctx: Load, + }, + ), + cases: [ + MatchCase { + range: 269..293, + pattern: MatchValue( + PatternMatchValue { + range: 274..279, + value: StringLiteral( + ExprStringLiteral { + range: 274..279, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 274..279, + value: "one", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + }, + ), + guard: None, + body: [ + Pass( + StmtPass { + range: 289..293, + }, + ), + ], + }, + MatchCase { + range: 298..345, + pattern: MatchValue( + PatternMatchValue { + range: 303..331, + value: StringLiteral( + ExprStringLiteral { + range: 303..331, + value: StringLiteralValue { + inner: Concatenated( + ConcatenatedStringLiteral { + strings: [ + StringLiteral { + range: 303..316, + value: "implicitly ", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + StringLiteral { + range: 317..331, + value: "concatenated", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ], + value: "implicitly concatenated", + }, + ), + }, + }, + ), + }, + ), + guard: None, + body: [ + Pass( + StmtPass { + range: 341..345, + }, + ), + ], + }, + ], + }, + ), + Expr( + StmtExpr { + range: 347..364, + value: TString( + ExprTString { + range: 347..364, + value: TStringValue { + inner: Single( + TString( + TString { + range: 347..364, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 349..350, + value: "\\", + }, + ), + Interpolation( + InterpolatedElement { + range: 350..355, + expression: Name( + ExprName { + range: 351..354, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 355..356, + value: "\\", + }, + ), + Interpolation( + InterpolatedElement { + range: 356..363, + expression: Name( + ExprName { + range: 357..360, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 361..362, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 361..362, + value: "\\", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 365..379, + value: TString( + ExprTString { + range: 365..379, + value: TStringValue { + inner: Single( + TString( + TString { + range: 365..379, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 367..378, + value: "\\{foo\\}", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 380..420, + value: TString( + ExprTString { + range: 380..420, + value: TStringValue { + inner: Single( + TString( + TString { + range: 380..420, + elements: [ + Interpolation( + InterpolatedElement { + range: 384..417, + expression: Name( + ExprName { + range: 390..393, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 394..416, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 394..416, + value: "x\n y\n z\n", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 421..439, + value: TString( + ExprTString { + range: 421..439, + value: TStringValue { + inner: Single( + TString( + TString { + range: 421..439, + elements: [ + Interpolation( + InterpolatedElement { + range: 423..438, + expression: Name( + ExprName { + range: 428..431, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: " ( ", + trailing: " ) = ", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 441..486, + value: TString( + ExprTString { + range: 441..486, + value: TStringValue { + inner: Single( + TString( + TString { + range: 441..486, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 443..450, + value: "normal ", + }, + ), + Interpolation( + InterpolatedElement { + range: 450..455, + expression: Name( + ExprName { + range: 451..454, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 455..468, + value: " {another} ", + }, + ), + Interpolation( + InterpolatedElement { + range: 468..473, + expression: Name( + ExprName { + range: 469..472, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 473..476, + value: " {", + }, + ), + Interpolation( + InterpolatedElement { + range: 476..483, + expression: Name( + ExprName { + range: 477..482, + id: Name("three"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 483..485, + value: "}", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 487..529, + value: TString( + ExprTString { + range: 487..529, + value: TStringValue { + inner: Single( + TString( + TString { + range: 487..529, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 489..496, + value: "normal ", + }, + ), + Interpolation( + InterpolatedElement { + range: 496..503, + expression: Name( + ExprName { + range: 497..500, + id: Name("foo"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Ascii, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 503..504, + value: " ", + }, + ), + Interpolation( + InterpolatedElement { + range: 504..511, + expression: Name( + ExprName { + range: 505..508, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Str, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 511..512, + value: " ", + }, + ), + Interpolation( + InterpolatedElement { + range: 512..519, + expression: Name( + ExprName { + range: 513..516, + id: Name("baz"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Repr, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 519..520, + value: " ", + }, + ), + Interpolation( + InterpolatedElement { + range: 520..528, + expression: Name( + ExprName { + range: 521..527, + id: Name("foobar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 530..549, + value: TString( + ExprTString { + range: 530..549, + value: TStringValue { + inner: Single( + TString( + TString { + range: 530..549, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 532..539, + value: "normal ", + }, + ), + Interpolation( + InterpolatedElement { + range: 539..548, + expression: Name( + ExprName { + range: 540..541, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 542..547, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 542..547, + value: "y + 2", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 550..568, + value: TString( + ExprTString { + range: 550..568, + value: TStringValue { + inner: Single( + TString( + TString { + range: 550..568, + elements: [ + Interpolation( + InterpolatedElement { + range: 552..567, + expression: Name( + ExprName { + range: 553..554, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 555..566, + elements: [ + Interpolation( + InterpolatedElement { + range: 555..566, + expression: Call( + ExprCall { + range: 556..565, + func: Attribute( + ExprAttribute { + range: 556..563, + value: Set( + ExprSet { + range: 556..559, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 557..558, + value: Int( + 1, + ), + }, + ), + ], + }, + ), + attr: Identifier { + id: Name("pop"), + range: 560..563, + }, + ctx: Load, + }, + ), + arguments: Arguments { + range: 563..565, + args: [], + keywords: [], + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 569..588, + value: TString( + ExprTString { + range: 569..588, + value: TStringValue { + inner: Single( + TString( + TString { + range: 569..588, + elements: [ + Interpolation( + InterpolatedElement { + range: 571..587, + expression: Lambda( + ExprLambda { + range: 573..585, + parameters: Some( + Parameters { + range: 580..581, + posonlyargs: [], + args: [ + ParameterWithDefault { + range: 580..581, + parameter: Parameter { + range: 580..581, + name: Identifier { + id: Name("x"), + range: 580..581, + }, + annotation: None, + }, + default: None, + }, + ], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + ), + body: Set( + ExprSet { + range: 582..585, + elts: [ + Name( + ExprName { + range: 583..584, + id: Name("x"), + ctx: Load, + }, + ), + ], + }, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 589..597, + value: TString( + ExprTString { + range: 589..597, + value: TStringValue { + inner: Single( + TString( + TString { + range: 589..597, + elements: [ + Interpolation( + InterpolatedElement { + range: 591..596, + expression: Name( + ExprName { + range: 592..593, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: " =", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 598..611, + value: TString( + ExprTString { + range: 598..611, + value: TStringValue { + inner: Single( + TString( + TString { + range: 598..611, + elements: [ + Interpolation( + InterpolatedElement { + range: 600..610, + expression: Name( + ExprName { + range: 605..606, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: " ", + trailing: " = ", + }, + ), + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 612..621, + value: TString( + ExprTString { + range: 612..621, + value: TStringValue { + inner: Single( + TString( + TString { + range: 612..621, + elements: [ + Interpolation( + InterpolatedElement { + range: 614..620, + expression: Name( + ExprName { + range: 615..616, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: "=", + }, + ), + conversion: Ascii, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 622..636, + value: TString( + ExprTString { + range: 622..636, + value: TStringValue { + inner: Single( + TString( + TString { + range: 622..636, + elements: [ + Interpolation( + InterpolatedElement { + range: 624..635, + expression: Name( + ExprName { + range: 625..626, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 627..634, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 627..634, + value: ".3f!r =", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 637..653, + value: TString( + ExprTString { + range: 637..653, + value: TStringValue { + inner: Single( + TString( + TString { + range: 637..653, + elements: [ + Interpolation( + InterpolatedElement { + range: 639..652, + expression: Name( + ExprName { + range: 640..641, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: Some( + DebugText { + leading: "", + trailing: " = ", + }, + ), + conversion: Repr, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 648..651, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 648..651, + value: ".3f", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 654..667, + value: TString( + ExprTString { + range: 654..667, + value: TStringValue { + inner: Single( + TString( + TString { + range: 654..667, + elements: [ + Interpolation( + InterpolatedElement { + range: 656..666, + expression: Name( + ExprName { + range: 657..658, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + InterpolatedStringFormatSpec { + range: 659..665, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 659..665, + value: ".3f=!r", + }, + ), + ], + }, + ), + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 668..682, + value: TString( + ExprTString { + range: 668..682, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 668..675, + value: "hello", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 676..682, + elements: [ + Interpolation( + InterpolatedElement { + range: 678..681, + expression: Name( + ExprName { + range: 679..680, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 683..696, + value: TString( + ExprTString { + range: 683..696, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 683..689, + elements: [ + Interpolation( + InterpolatedElement { + range: 685..688, + expression: Name( + ExprName { + range: 686..687, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 690..696, + elements: [ + Interpolation( + InterpolatedElement { + range: 692..695, + expression: Name( + ExprName { + range: 693..694, + id: Name("y"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 697..711, + value: TString( + ExprTString { + range: 697..711, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 697..703, + elements: [ + Interpolation( + InterpolatedElement { + range: 699..702, + expression: Name( + ExprName { + range: 700..701, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 704..711, + value: "world", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 712..756, + value: TString( + ExprTString { + range: 712..756, + value: TStringValue { + inner: Single( + TString( + TString { + range: 712..756, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 714..739, + value: "Invalid args in command: ", + }, + ), + Interpolation( + InterpolatedElement { + range: 739..755, + expression: Tuple( + ExprTuple { + range: 740..754, + elts: [ + Name( + ExprName { + range: 740..747, + id: Name("command"), + ctx: Load, + }, + ), + Starred( + ExprStarred { + range: 749..754, + value: Name( + ExprName { + range: 750..754, + id: Name("args"), + ctx: Load, + }, + ), + ctx: Load, + }, + ), + ], + ctx: Load, + parenthesized: false, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 757..775, + value: TString( + ExprTString { + range: 757..775, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 757..762, + value: "foo", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 763..769, + elements: [ + Interpolation( + InterpolatedElement { + range: 765..768, + expression: Name( + ExprName { + range: 766..767, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 770..775, + value: "bar", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 776..825, + value: TString( + ExprTString { + range: 782..823, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 782..786, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 784..785, + value: "a", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 791..795, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 793..794, + value: "b", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 800..803, + value: "c", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 808..813, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 811..812, + value: "d", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Raw { + uppercase_r: false, + }, + triple_quoted: false, + }, + }, + ), + FString( + FString { + range: 818..823, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 821..822, + value: "e", + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Raw { + uppercase_r: false, + }, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 850..879, + value: TString( + ExprTString { + range: 850..879, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 850..856, + value: "foo", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 857..865, + elements: [ + Interpolation( + InterpolatedElement { + range: 859..864, + expression: Name( + ExprName { + range: 860..863, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 866..871, + value: "baz", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 872..879, + value: " some", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 880..909, + value: TString( + ExprTString { + range: 880..909, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 880..885, + value: "foo", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 886..894, + elements: [ + Interpolation( + InterpolatedElement { + range: 888..893, + expression: Name( + ExprName { + range: 889..892, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 895..901, + value: "baz", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 902..909, + value: " some", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 910..939, + value: TString( + ExprTString { + range: 910..939, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 910..915, + value: "foo", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 916..924, + elements: [ + Interpolation( + InterpolatedElement { + range: 918..923, + expression: Name( + ExprName { + range: 919..922, + id: Name("bar"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 925..930, + value: "baz", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 931..939, + value: " some", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 940..978, + value: TString( + ExprTString { + range: 940..978, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 940..946, + value: "foo", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 947..966, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 949..953, + value: "bar ", + }, + ), + Interpolation( + InterpolatedElement { + range: 953..958, + expression: Name( + ExprName { + range: 954..957, + id: Name("baz"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 958..965, + value: " really", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 967..973, + value: "bar", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Unicode, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 974..978, + value: "no", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 998..1017, + value: TString( + ExprTString { + range: 998..1017, + value: TStringValue { + inner: Concatenated( + [ + FString( + FString { + range: 998..1007, + elements: [ + Interpolation( + InterpolatedElement { + range: 1000..1006, + expression: Name( + ExprName { + range: 1001..1005, + id: Name("this"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 1008..1017, + elements: [ + Interpolation( + InterpolatedElement { + range: 1010..1016, + expression: Name( + ExprName { + range: 1011..1015, + id: Name("that"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 1018..1036, + value: TString( + ExprTString { + range: 1018..1036, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 1018..1027, + elements: [ + Interpolation( + InterpolatedElement { + range: 1020..1026, + expression: Name( + ExprName { + range: 1021..1025, + id: Name("this"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + FString( + FString { + range: 1027..1036, + elements: [ + Interpolation( + InterpolatedElement { + range: 1029..1035, + expression: Name( + ExprName { + range: 1030..1034, + id: Name("that"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 1037..1064, + value: TString( + ExprTString { + range: 1037..1064, + value: TStringValue { + inner: Concatenated( + [ + TString( + TString { + range: 1037..1046, + elements: [ + Interpolation( + InterpolatedElement { + range: 1039..1045, + expression: Name( + ExprName { + range: 1040..1044, + id: Name("this"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 1047..1053, + value: "that", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + FString( + FString { + range: 1054..1064, + elements: [ + Interpolation( + InterpolatedElement { + range: 1056..1063, + expression: Name( + ExprName { + range: 1057..1062, + id: Name("other"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 1065..1111, + value: TString( + ExprTString { + range: 1065..1111, + value: TStringValue { + inner: Concatenated( + [ + FString( + FString { + range: 1065..1082, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 1067..1071, + value: "one ", + }, + ), + Interpolation( + InterpolatedElement { + range: 1071..1077, + expression: Name( + ExprName { + range: 1072..1076, + id: Name("this"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 1077..1081, + value: " two", + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + Literal( + StringLiteral { + range: 1083..1089, + value: "that", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 1090..1111, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 1092..1098, + value: "three ", + }, + ), + Interpolation( + InterpolatedElement { + range: 1098..1105, + expression: Name( + ExprName { + range: 1099..1104, + id: Name("other"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 1105..1110, + value: " four", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 1123..1142, + value: TString( + ExprTString { + range: 1123..1142, + value: TStringValue { + inner: Single( + TString( + TString { + range: 1123..1142, + elements: [ + Interpolation( + InterpolatedElement { + range: 1125..1141, + expression: FString( + ExprFString { + range: 1126..1140, + value: FStringValue { + inner: Single( + FString( + FString { + range: 1126..1140, + elements: [ + Interpolation( + InterpolatedElement { + range: 1128..1139, + expression: TString( + ExprTString { + range: 1129..1138, + value: TStringValue { + inner: Single( + TString( + TString { + range: 1129..1138, + elements: [ + Interpolation( + InterpolatedElement { + range: 1131..1137, + expression: Name( + ExprName { + range: 1132..1136, + id: Name("this"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap index 1e672a1ce19450..34167d13a7014e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/fstring_format_spec_terminator.py -snapshot_kind: text --- ## AST @@ -23,13 +22,13 @@ Module( range: 0..19, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 2..8, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 8..12, expression: Name( ExprName { @@ -41,7 +40,7 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 11..11, elements: [], }, @@ -49,7 +48,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 12..18, value: " world", }, @@ -81,13 +80,13 @@ Module( range: 20..42, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 22..28, value: "hello ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 28..35, expression: Name( ExprName { @@ -99,11 +98,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 31..34, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 31..34, value: ".3f", }, @@ -114,7 +113,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 35..41, value: " world", }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap index 044de1e15736e5..16fc4e36e36706 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_keyword_1.py -snapshot_kind: text --- ## AST @@ -223,13 +222,13 @@ Module( range: 140..150, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 142..146, value: "foo ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 146..149, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap index c3d622ebb0e105..004ec6c603a774 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/param_with_annotation. ``` Module( ModModule { - range: 0..84, + range: 0..54, body: [ FunctionDef( StmtFunctionDef { @@ -145,72 +145,6 @@ Module( ], }, ), - FunctionDef( - StmtFunctionDef { - range: 54..83, - is_async: false, - decorator_list: [], - name: Identifier { - id: Name("foo"), - range: 58..61, - }, - type_params: None, - parameters: Parameters { - range: 61..78, - posonlyargs: [], - args: [ - ParameterWithDefault { - range: 62..77, - parameter: Parameter { - range: 62..77, - name: Identifier { - id: Name("arg"), - range: 62..65, - }, - annotation: Some( - Named( - ExprNamed { - range: 68..76, - target: Name( - ExprName { - range: 68..69, - id: Name("x"), - ctx: Store, - }, - ), - value: Name( - ExprName { - range: 73..76, - id: Name("int"), - ctx: Load, - }, - ), - }, - ), - ), - }, - default: None, - }, - ], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - returns: None, - body: [ - Expr( - StmtExpr { - range: 80..83, - value: EllipsisLiteral( - ExprEllipsisLiteral { - range: 80..83, - }, - ), - }, - ), - ], - }, - ), ], }, ) diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap index d4dcb42151531c..cbec152ee7579b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap @@ -22,13 +22,13 @@ Module( range: 44..72, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 46..52, value: "outer ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 52..71, expression: StringLiteral( ExprStringLiteral { @@ -80,13 +80,13 @@ Module( range: 73..106, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 75..81, value: "outer ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 81..105, expression: Name( ExprName { @@ -98,11 +98,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 84..104, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 84..103, expression: StringLiteral( ExprStringLiteral { @@ -128,7 +128,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 103..104, value: " ", }, @@ -164,8 +164,8 @@ Module( FString { range: 107..147, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 111..144, expression: FString( ExprFString { @@ -176,8 +176,8 @@ Module( FString { range: 112..143, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 116..140, expression: FString( ExprFString { @@ -188,8 +188,8 @@ Module( FString { range: 117..139, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 119..138, expression: StringLiteral( ExprStringLiteral { @@ -274,8 +274,8 @@ Module( FString { range: 148..230, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 152..208, expression: FString( ExprFString { @@ -287,13 +287,13 @@ Module( range: 153..207, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 157..177, value: "# before expression ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 177..204, expression: FString( ExprFString { @@ -305,13 +305,13 @@ Module( range: 178..203, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 180..185, value: "# aro", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 185..197, expression: FString( ExprFString { @@ -323,13 +323,13 @@ Module( range: 186..196, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 188..189, value: "#", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 189..194, expression: BinOp( ExprBinOp { @@ -359,7 +359,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 194..195, value: "#", }, @@ -382,7 +382,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 197..202, value: "und #", }, @@ -422,7 +422,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 208..227, value: " # after expression", }, @@ -454,13 +454,13 @@ Module( range: 231..263, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 233..254, value: "escape outside of \t ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 254..260, expression: Name( ExprName { @@ -475,7 +475,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 260..262, value: "\n", }, @@ -507,7 +507,7 @@ Module( range: 264..277, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 266..276, value: "test\"abcd", }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap index c9eea808228f5a..09817c82834226 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap @@ -22,13 +22,13 @@ Module( range: 44..74, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 46..58, value: "Magic wand: ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 58..73, expression: Subscript( ExprSubscript { @@ -92,8 +92,8 @@ Module( FString { range: 95..112, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 97..111, expression: Call( ExprCall { @@ -173,13 +173,13 @@ Module( range: 148..220, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 152..169, value: "A complex trick: ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 169..217, expression: Subscript( ExprSubscript { @@ -243,8 +243,8 @@ Module( FString { range: 221..254, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 223..253, expression: FString( ExprFString { @@ -255,8 +255,8 @@ Module( FString { range: 224..252, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 226..251, expression: FString( ExprFString { @@ -267,8 +267,8 @@ Module( FString { range: 227..250, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 229..249, expression: FString( ExprFString { @@ -279,8 +279,8 @@ Module( FString { range: 230..248, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 232..247, expression: FString( ExprFString { @@ -291,8 +291,8 @@ Module( FString { range: 233..246, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 235..245, expression: FString( ExprFString { @@ -303,8 +303,8 @@ Module( FString { range: 236..244, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 238..243, expression: BinOp( ExprBinOp { @@ -444,8 +444,8 @@ Module( FString { range: 276..310, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 278..303, expression: FString( ExprFString { @@ -456,8 +456,8 @@ Module( FString { range: 279..302, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 283..293, expression: StringLiteral( ExprStringLiteral { @@ -483,7 +483,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 293..299, value: " inner", }, @@ -506,7 +506,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 303..309, value: " outer", }, @@ -538,13 +538,13 @@ Module( range: 336..359, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 338..343, value: "test ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 343..353, expression: Name( ExprName { @@ -559,7 +559,7 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 353..358, value: " more", }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap new file mode 100644 index 00000000000000..3d322265abe67e --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap @@ -0,0 +1,584 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314.py +--- +## AST + +``` +Module( + ModModule { + range: 0..403, + body: [ + Expr( + StmtExpr { + range: 44..74, + value: TString( + ExprTString { + range: 44..74, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..74, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 46..58, + value: "Magic wand: ", + }, + ), + Interpolation( + InterpolatedElement { + range: 58..73, + expression: Subscript( + ExprSubscript { + range: 60..71, + value: Name( + ExprName { + range: 60..63, + id: Name("bag"), + ctx: Load, + }, + ), + slice: StringLiteral( + ExprStringLiteral { + range: 64..70, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 64..70, + value: "wand", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 95..112, + value: TString( + ExprTString { + range: 95..112, + value: TStringValue { + inner: Single( + TString( + TString { + range: 95..112, + elements: [ + Interpolation( + InterpolatedElement { + range: 97..111, + expression: Call( + ExprCall { + range: 98..110, + func: Attribute( + ExprAttribute { + range: 98..107, + value: StringLiteral( + ExprStringLiteral { + range: 98..102, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 98..102, + value: "\n", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + attr: Identifier { + id: Name("join"), + range: 103..107, + }, + ctx: Load, + }, + ), + arguments: Arguments { + range: 107..110, + args: [ + Name( + ExprName { + range: 108..109, + id: Name("a"), + ctx: Load, + }, + ), + ], + keywords: [], + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 148..220, + value: TString( + ExprTString { + range: 148..220, + value: TStringValue { + inner: Single( + TString( + TString { + range: 148..220, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 152..169, + value: "A complex trick: ", + }, + ), + Interpolation( + InterpolatedElement { + range: 169..217, + expression: Subscript( + ExprSubscript { + range: 175..185, + value: Name( + ExprName { + range: 175..178, + id: Name("bag"), + ctx: Load, + }, + ), + slice: StringLiteral( + ExprStringLiteral { + range: 179..184, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 179..184, + value: "bag", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 221..254, + value: TString( + ExprTString { + range: 221..254, + value: TStringValue { + inner: Single( + TString( + TString { + range: 221..254, + elements: [ + Interpolation( + InterpolatedElement { + range: 223..253, + expression: TString( + ExprTString { + range: 224..252, + value: TStringValue { + inner: Single( + TString( + TString { + range: 224..252, + elements: [ + Interpolation( + InterpolatedElement { + range: 226..251, + expression: TString( + ExprTString { + range: 227..250, + value: TStringValue { + inner: Single( + TString( + TString { + range: 227..250, + elements: [ + Interpolation( + InterpolatedElement { + range: 229..249, + expression: TString( + ExprTString { + range: 230..248, + value: TStringValue { + inner: Single( + TString( + TString { + range: 230..248, + elements: [ + Interpolation( + InterpolatedElement { + range: 232..247, + expression: TString( + ExprTString { + range: 233..246, + value: TStringValue { + inner: Single( + TString( + TString { + range: 233..246, + elements: [ + Interpolation( + InterpolatedElement { + range: 235..245, + expression: TString( + ExprTString { + range: 236..244, + value: TStringValue { + inner: Single( + TString( + TString { + range: 236..244, + elements: [ + Interpolation( + InterpolatedElement { + range: 238..243, + expression: BinOp( + ExprBinOp { + range: 239..242, + left: NumberLiteral( + ExprNumberLiteral { + range: 239..240, + value: Int( + 1, + ), + }, + ), + op: Add, + right: NumberLiteral( + ExprNumberLiteral { + range: 241..242, + value: Int( + 1, + ), + }, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 276..310, + value: TString( + ExprTString { + range: 276..310, + value: TStringValue { + inner: Single( + TString( + TString { + range: 276..310, + elements: [ + Interpolation( + InterpolatedElement { + range: 278..303, + expression: TString( + ExprTString { + range: 279..302, + value: TStringValue { + inner: Single( + TString( + TString { + range: 279..302, + elements: [ + Interpolation( + InterpolatedElement { + range: 283..293, + expression: StringLiteral( + ExprStringLiteral { + range: 284..292, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 284..292, + value: "nested", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 293..299, + value: " inner", + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 303..309, + value: " outer", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 336..359, + value: TString( + ExprTString { + range: 336..359, + value: TStringValue { + inner: Single( + TString( + TString { + range: 336..359, + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 338..343, + value: "test ", + }, + ), + Interpolation( + InterpolatedElement { + range: 343..353, + expression: Name( + ExprName { + range: 344..345, + id: Name("a"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + InterpolatedStringLiteralElement { + range: 353..358, + value: " more", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap index 48d299be48416f..b651e7c557e19e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/ambiguous_lpar_with_items.py -snapshot_kind: text --- ## AST @@ -945,8 +944,8 @@ Module( FString { range: 1186..1201, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 1188..1200, expression: Name( ExprName { @@ -958,11 +957,11 @@ Module( debug_text: None, conversion: None, format_spec: Some( - FStringFormatSpec { + InterpolatedStringFormatSpec { range: 1195..1199, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 1195..1199, value: "= 42", }, @@ -1017,8 +1016,8 @@ Module( FString { range: 1214..1231, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 1216..1230, expression: Named( ExprNamed { diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap index ec88a56393c916..eb24f71df19e0c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap @@ -3767,8 +3767,8 @@ Module( FString { range: 2932..2938, elements: [ - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 2934..2937, expression: Name( ExprName { diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap index 3f799c4d6b0deb..2413f26d906f0d 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/try.py -snapshot_kind: text --- ## AST @@ -577,13 +576,13 @@ Module( range: 505..524, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 507..514, value: "caught ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 514..523, expression: Call( ExprCall { @@ -682,13 +681,13 @@ Module( range: 557..576, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 559..566, value: "caught ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 566..575, expression: Call( ExprCall { @@ -955,13 +954,13 @@ Module( range: 704..750, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 706..713, value: "caught ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 713..722, expression: Call( ExprCall { @@ -994,13 +993,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 722..735, value: " with nested ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 735..749, expression: Attribute( ExprAttribute { @@ -1091,13 +1090,13 @@ Module( range: 784..830, elements: [ Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 786..793, value: "caught ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 793..802, expression: Call( ExprCall { @@ -1130,13 +1129,13 @@ Module( }, ), Literal( - FStringLiteralElement { + InterpolatedStringLiteralElement { range: 802..815, value: " with nested ", }, ), - Expression( - FStringExpressionElement { + Interpolation( + InterpolatedElement { range: 815..829, expression: Attribute( ExprAttribute { diff --git a/crates/ruff_python_semantic/src/analyze/type_inference.rs b/crates/ruff_python_semantic/src/analyze/type_inference.rs index d25e0f7e7a1d5a..c0f6f6d48220d5 100644 --- a/crates/ruff_python_semantic/src/analyze/type_inference.rs +++ b/crates/ruff_python_semantic/src/analyze/type_inference.rs @@ -78,6 +78,7 @@ impl From<&Expr> for ResolvedPythonType { Expr::Tuple(_) => ResolvedPythonType::Atom(PythonType::Tuple), Expr::Generator(_) => ResolvedPythonType::Atom(PythonType::Generator), Expr::FString(_) => ResolvedPythonType::Atom(PythonType::String), + Expr::TString(_) => ResolvedPythonType::Unknown, Expr::StringLiteral(_) => ResolvedPythonType::Atom(PythonType::String), Expr::BytesLiteral(_) => ResolvedPythonType::Atom(PythonType::Bytes), Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value { diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 76ef4a8617ce02..043d98cff5f2bf 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1939,10 +1939,15 @@ impl<'a> SemanticModel<'a> { self.flags.intersects(SemanticModelFlags::F_STRING) } + /// Return `true` if the model is in a t-string. + pub const fn in_t_string(&self) -> bool { + self.flags.intersects(SemanticModelFlags::T_STRING) + } + /// Return `true` if the model is in an f-string replacement field. - pub const fn in_f_string_replacement_field(&self) -> bool { + pub const fn in_interpolated_string_replacement_field(&self) -> bool { self.flags - .intersects(SemanticModelFlags::F_STRING_REPLACEMENT_FIELD) + .intersects(SemanticModelFlags::INTERPOLATED_STRING_REPLACEMENT_FIELD) } /// Return `true` if the model is in boolean test. @@ -2461,7 +2466,7 @@ bitflags! { /// ```python /// f"first {x} second {y}" /// ``` - const F_STRING_REPLACEMENT_FIELD = 1 << 21; + const INTERPOLATED_STRING_REPLACEMENT_FIELD = 1 << 21; /// The model is visiting the bases tuple of a class. /// @@ -2549,6 +2554,15 @@ bitflags! { /// [#13824]: https://github.com/astral-sh/ruff/issues/13824 const NO_TYPE_CHECK = 1 << 28; + /// The model is in a t-string. + /// + /// For example, the model could be visiting `x` in: + /// ```python + /// t'{x}' + /// ``` + const T_STRING = 1 << 29; + + /// The context is in any type annotation. const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); diff --git a/crates/ty_python_semantic/src/semantic_index/re_exports.rs b/crates/ty_python_semantic/src/semantic_index/re_exports.rs index b8002dbf0bd2a4..049e751bf0099d 100644 --- a/crates/ty_python_semantic/src/semantic_index/re_exports.rs +++ b/crates/ty_python_semantic/src/semantic_index/re_exports.rs @@ -325,6 +325,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { | ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) | ast::Expr::FString(_) + | ast::Expr::TString(_) | ast::Expr::Tuple(_) | ast::Expr::List(_) | ast::Expr::Slice(_) @@ -389,6 +390,7 @@ impl<'db> Visitor<'db> for WalrusFinder<'_, 'db> { | ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) | ast::Expr::FString(_) + | ast::Expr::TString(_) | ast::Expr::Tuple(_) | ast::Expr::List(_) | ast::Expr::Slice(_) diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 9ed888e603e004..e3e7b15cf86133 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -116,6 +116,7 @@ impl_expression_has_type!(ast::ExprYieldFrom); impl_expression_has_type!(ast::ExprCompare); impl_expression_has_type!(ast::ExprCall); impl_expression_has_type!(ast::ExprFString); +impl_expression_has_type!(ast::ExprTString); impl_expression_has_type!(ast::ExprStringLiteral); impl_expression_has_type!(ast::ExprBytesLiteral); impl_expression_has_type!(ast::ExprNumberLiteral); @@ -152,6 +153,7 @@ impl HasType for ast::Expr { Expr::Compare(inner) => inner.inferred_type(model), Expr::Call(inner) => inner.inferred_type(model), Expr::FString(inner) => inner.inferred_type(model), + Expr::TString(inner) => inner.inferred_type(model), Expr::StringLiteral(inner) => inner.inferred_type(model), Expr::BytesLiteral(inner) => inner.inferred_type(model), Expr::NumberLiteral(inner) => inner.inferred_type(model), diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 73aefafb3c2172..acbf27b8815ab8 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -4328,6 +4328,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_bytes_literal_expression(bytes_literal) } ast::Expr::FString(fstring) => self.infer_fstring_expression(fstring), + ast::Expr::TString(tstring) => self.infer_tstring_expression(tstring), ast::Expr::EllipsisLiteral(literal) => self.infer_ellipsis_literal_expression(literal), ast::Expr::Tuple(tuple) => self.infer_tuple_expression(tuple), ast::Expr::List(list) => self.infer_list_expression(list), @@ -4426,8 +4427,8 @@ impl<'db> TypeInferenceBuilder<'db> { ast::FStringPart::FString(fstring) => { for element in &fstring.elements { match element { - ast::FStringElement::Expression(expression) => { - let ast::FStringExpressionElement { + ast::InterpolatedStringElement::Interpolation(expression) => { + let ast::InterpolatedElement { range: _, expression, debug_text: _, @@ -4437,7 +4438,7 @@ impl<'db> TypeInferenceBuilder<'db> { let ty = self.infer_expression(expression); if let Some(format_spec) = format_spec { - for element in format_spec.elements.expressions() { + for element in format_spec.elements.interpolations() { self.infer_expression(&element.expression); } } @@ -4456,7 +4457,7 @@ impl<'db> TypeInferenceBuilder<'db> { } } } - ast::FStringElement::Literal(literal) => { + ast::InterpolatedStringElement::Literal(literal) => { collector.push_str(&literal.value); } } @@ -4467,6 +4468,59 @@ impl<'db> TypeInferenceBuilder<'db> { collector.string_type(self.db()) } + fn infer_tstring_expression(&mut self, tstring: &ast::ExprTString) -> Type<'db> { + let ast::ExprTString { value, .. } = tstring; + for part in value { + match part { + ast::TStringPart::Literal(_) => {} + ast::TStringPart::FString(fstring) => { + for element in &fstring.elements { + match element { + ast::InterpolatedStringElement::Interpolation(expression) => { + let ast::InterpolatedElement { + expression, + format_spec, + .. + } = expression; + self.infer_expression(expression); + + if let Some(format_spec) = format_spec { + for element in format_spec.elements.interpolations() { + self.infer_expression(&element.expression); + } + } + } + ast::InterpolatedStringElement::Literal(_) => {} + } + } + } + ast::TStringPart::TString(tstring) => { + for element in &tstring.elements { + match element { + ast::InterpolatedStringElement::Interpolation( + tstring_interpolation_element, + ) => { + let ast::InterpolatedElement { + expression, + format_spec, + .. + } = tstring_interpolation_element; + self.infer_expression(expression); + if let Some(format_spec) = format_spec { + for element in format_spec.elements.interpolations() { + self.infer_expression(&element.expression); + } + } + } + ast::InterpolatedStringElement::Literal(_) => {} + } + } + } + } + } + todo_type!("Template") + } + fn infer_ellipsis_literal_expression( &mut self, _literal: &ast::ExprEllipsisLiteral, @@ -8285,6 +8339,14 @@ impl<'db> TypeInferenceBuilder<'db> { ) } + ast::Expr::TString(tstring) => { + self.infer_tstring_expression(tstring); + self.report_invalid_type_expression( + expression, + format_args!("T-strings are not allowed in type expressions"), + ) + } + ast::Expr::Slice(slice) => { self.infer_slice_expression(slice); self.report_invalid_type_expression( From 88866f0048978c63a049d388034c1775e771f944 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 30 May 2025 16:22:51 -0500 Subject: [PATCH 293/487] [ty] Infer the Python version from the environment if feasible (#18057) Co-authored-by: Alex Waygood --- Cargo.lock | 1 + crates/ruff_graph/src/db.rs | 4 +- crates/ty/Cargo.toml | 1 + crates/ty/tests/cli.rs | 131 ++++++++- crates/ty_ide/src/inlay_hints.rs | 2 +- crates/ty_ide/src/lib.rs | 2 +- crates/ty_project/src/lib.rs | 2 +- crates/ty_project/src/metadata/options.rs | 13 +- crates/ty_python_semantic/src/db.rs | 4 +- crates/ty_python_semantic/src/lib.rs | 4 +- .../src/module_resolver/resolver.rs | 96 ++++--- .../src/module_resolver/testing.rs | 8 +- crates/ty_python_semantic/src/program.rs | 168 ++++++++++- .../ty_python_semantic/src/site_packages.rs | 262 +++++++++++++----- .../src/util/diagnostics.rs | 41 ++- crates/ty_test/src/assertion.rs | 2 +- crates/ty_test/src/lib.rs | 4 +- crates/ty_test/src/matcher.rs | 2 +- fuzz/fuzz_targets/ty_check_invalid_syntax.rs | 2 +- 19 files changed, 583 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94f7f1d6fd321b..447c014c21a7fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3889,6 +3889,7 @@ dependencies = [ "countme", "crossbeam", "ctrlc", + "dunce", "filetime", "indicatif", "insta", diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index 06b3eca96cbeb4..2e84254f366a19 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -44,10 +44,10 @@ impl ModuleDb { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: python_version, source: PythonVersionSource::default(), - }, + }), python_platform: PythonPlatform::default(), search_paths, }, diff --git a/crates/ty/Cargo.toml b/crates/ty/Cargo.toml index cd8f7b32ea5bc0..6cfc036f29f753 100644 --- a/crates/ty/Cargo.toml +++ b/crates/ty/Cargo.toml @@ -41,6 +41,7 @@ wild = { workspace = true } ruff_db = { workspace = true, features = ["testing"] } ruff_python_trivia = { workspace = true } +dunce = { workspace = true } insta = { workspace = true, features = ["filters"] } insta-cmd = { workspace = true } filetime = { workspace = true } diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index a2892e4f71ce0f..201781ff9d9f72 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -308,6 +308,125 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any Ok(()) } +#[test] +fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "venv" + "#, + ), + ( + "venv/pyvenv.cfg", + r#" + version = 3.8 + home = foo/bar/bin + "#, + ), + if cfg!(target_os = "windows") { + ("foo/bar/bin/python.exe", "") + } else { + ("foo/bar/bin/python", "") + }, + if cfg!(target_os = "windows") { + ("venv/Lib/site-packages/foo.py", "") + } else { + ("venv/lib/python3.8/site-packages/foo.py", "") + }, + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of your virtual environment + --> venv/pyvenv.cfg:2:11 + | + 2 | version = 3.8 + | ^^^ Python version inferred from virtual environment metadata file + 3 | home = foo/bar/bin + | + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "venv" + "#, + ), + ( + "venv/pyvenv.cfg", + r#"home = foo/bar/bin + + + version = 3.8"#, + ), + if cfg!(target_os = "windows") { + ("foo/bar/bin/python.exe", "") + } else { + ("foo/bar/bin/python", "") + }, + if cfg!(target_os = "windows") { + ("venv/Lib/site-packages/foo.py", "") + } else { + ("venv/lib/python3.8/site-packages/foo.py", "") + }, + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of your virtual environment + --> venv/pyvenv.cfg:4:23 + | + 4 | version = 3.8 + | ^^^ Python version inferred from virtual environment metadata file + | + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + #[test] fn config_file_annotation_showing_where_python_version_set_syntax_error() -> anyhow::Result<()> { let case = TestCase::with_files([ @@ -1772,10 +1891,14 @@ impl TestCase { // Canonicalize the tempdir path because macos uses symlinks for tempdirs // and that doesn't play well with our snapshot filtering. - let project_dir = temp_dir - .path() - .canonicalize() - .context("Failed to canonicalize project path")?; + // Simplify with dunce because otherwise we get UNC paths on Windows. + let project_dir = dunce::simplified( + &temp_dir + .path() + .canonicalize() + .context("Failed to canonicalize project path")?, + ) + .to_path_buf(); let mut settings = insta::Settings::clone_current(); settings.add_filter(&tempdir_filter(&project_dir), "/"); diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 4539b54308bef2..0ef71ddc05eff1 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -191,7 +191,7 @@ mod tests { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], diff --git a/crates/ty_ide/src/lib.rs b/crates/ty_ide/src/lib.rs index e403996788d1d4..99e3c123cf7b59 100644 --- a/crates/ty_ide/src/lib.rs +++ b/crates/ty_ide/src/lib.rs @@ -230,7 +230,7 @@ mod tests { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index 6c7b95c256fe05..ecd9501a47c5d6 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -677,7 +677,7 @@ mod tests { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![SystemPathBuf::from(".")]), }, diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 2e089d334f2fd4..a9a52a4b078097 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -11,8 +11,8 @@ use std::fmt::Debug; use thiserror::Error; use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection}; use ty_python_semantic::{ - ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource, PythonVersionWithSource, - SearchPathSettings, + ProgramSettings, PythonPath, PythonPlatform, PythonVersionFileSource, PythonVersionSource, + PythonVersionWithSource, SearchPathSettings, }; use super::settings::{Settings, TerminalSettings}; @@ -88,12 +88,11 @@ impl Options { version: **ranged_version, source: match ranged_version.source() { ValueSource::Cli => PythonVersionSource::Cli, - ValueSource::File(path) => { - PythonVersionSource::File(path.clone(), ranged_version.range()) - } + ValueSource::File(path) => PythonVersionSource::ConfigFile( + PythonVersionFileSource::new(path.clone(), ranged_version.range()), + ), }, - }) - .unwrap_or_default(); + }); let python_platform = self .environment .as_ref() diff --git a/crates/ty_python_semantic/src/db.rs b/crates/ty_python_semantic/src/db.rs index 99e560905b6d78..11c6e2922072ac 100644 --- a/crates/ty_python_semantic/src/db.rs +++ b/crates/ty_python_semantic/src/db.rs @@ -182,10 +182,10 @@ pub(crate) mod tests { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: self.python_version, source: PythonVersionSource::default(), - }, + }), python_platform: self.python_platform, search_paths: SearchPathSettings::new(vec![src_root]), }, diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index 11dfbae3a03aa0..1a204734c0f55f 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -8,8 +8,8 @@ pub use db::Db; pub use module_name::ModuleName; pub use module_resolver::{KnownModule, Module, resolve_module, system_module_search_paths}; pub use program::{ - Program, ProgramSettings, PythonPath, PythonVersionSource, PythonVersionWithSource, - SearchPathSettings, + Program, ProgramSettings, PythonPath, PythonVersionFileSource, PythonVersionSource, + PythonVersionWithSource, SearchPathSettings, }; pub use python_platform::PythonPlatform; pub use semantic_model::{HasType, SemanticModel}; diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 4013b54da12dc1..6447c203cfd04c 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -14,10 +14,8 @@ use ruff_python_ast::PythonVersion; use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions}; -use crate::site_packages::{ - PythonEnvironment, SitePackagesDiscoveryError, SitePackagesPaths, SysPrefixPathOrigin, -}; -use crate::{Program, PythonPath, SearchPathSettings}; +use crate::site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin}; +use crate::{Program, PythonPath, PythonVersionWithSource, SearchPathSettings}; use super::module::{Module, ModuleKind}; use super::path::{ModulePath, SearchPath, SearchPathValidationError}; @@ -165,6 +163,11 @@ pub struct SearchPaths { site_packages: Vec, typeshed_versions: TypeshedVersions, + + /// The Python version for the search paths, if any. + /// + /// This is read from the `pyvenv.cfg` if present. + python_version: Option, } impl SearchPaths { @@ -239,7 +242,7 @@ impl SearchPaths { static_paths.push(stdlib_path); - let site_packages_paths = match python_path { + let (site_packages_paths, python_version) = match python_path { PythonPath::SysPrefix(sys_prefix, origin) => { tracing::debug!( "Discovering site-packages paths from sys-prefix `{sys_prefix}` ({origin}')" @@ -248,8 +251,7 @@ impl SearchPaths { // than the one resolved in the program settings because it indicates // that the `target-version` is incorrectly configured or that the // venv is out of date. - PythonEnvironment::new(sys_prefix, *origin, system) - .and_then(|env| env.site_packages_directories(system))? + PythonEnvironment::new(sys_prefix, *origin, system)?.into_settings(system)? } PythonPath::Resolve(target, origin) => { @@ -275,45 +277,43 @@ impl SearchPaths { // handle the error. .unwrap_or(target); - PythonEnvironment::new(root, *origin, system) - .and_then(|venv| venv.site_packages_directories(system))? + PythonEnvironment::new(root, *origin, system)?.into_settings(system)? } PythonPath::Discover(root) => { tracing::debug!("Discovering virtual environment in `{root}`"); - let virtual_env_path = discover_venv_in(db.system(), root); - if let Some(virtual_env_path) = virtual_env_path { - tracing::debug!("Found `.venv` folder at `{}`", virtual_env_path); - - let handle_invalid_virtual_env = |error: SitePackagesDiscoveryError| { - tracing::debug!( - "Ignoring automatically detected virtual environment at `{}`: {}", - virtual_env_path, - error - ); - SitePackagesPaths::default() - }; - - match PythonEnvironment::new( - virtual_env_path.clone(), - SysPrefixPathOrigin::LocalVenv, - system, - ) { - Ok(venv) => venv - .site_packages_directories(system) - .unwrap_or_else(handle_invalid_virtual_env), - Err(error) => handle_invalid_virtual_env(error), - } - } else { - tracing::debug!("No virtual environment found"); - SitePackagesPaths::default() - } + discover_venv_in(db.system(), root) + .and_then(|virtual_env_path| { + tracing::debug!("Found `.venv` folder at `{}`", virtual_env_path); + + PythonEnvironment::new( + virtual_env_path.clone(), + SysPrefixPathOrigin::LocalVenv, + system, + ) + .and_then(|env| env.into_settings(system)) + .inspect_err(|err| { + tracing::debug!( + "Ignoring automatically detected virtual environment at `{}`: {}", + virtual_env_path, + err + ); + }) + .ok() + }) + .unwrap_or_else(|| { + tracing::debug!("No virtual environment found"); + (SitePackagesPaths::default(), None) + }) } - PythonPath::KnownSitePackages(paths) => paths - .iter() - .map(|path| canonicalize(path, system)) - .collect(), + PythonPath::KnownSitePackages(paths) => ( + paths + .iter() + .map(|path| canonicalize(path, system)) + .collect(), + None, + ), }; let mut site_packages: Vec<_> = Vec::with_capacity(site_packages_paths.len()); @@ -347,6 +347,7 @@ impl SearchPaths { static_paths, site_packages, typeshed_versions, + python_version, }) } @@ -371,6 +372,10 @@ impl SearchPaths { pub(super) fn typeshed_versions(&self) -> &TypeshedVersions { &self.typeshed_versions } + + pub fn python_version(&self) -> Option<&PythonVersionWithSource> { + self.python_version.as_ref() + } } /// Collect all dynamic search paths. For each `site-packages` path: @@ -389,6 +394,7 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec { static_paths, site_packages, typeshed_versions: _, + python_version: _, } = Program::get(db).search_paths(db); let mut dynamic_paths = Vec::new(); @@ -1472,10 +1478,10 @@ mod tests { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: PythonVersion::PY38, source: PythonVersionSource::default(), - }, + }), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], @@ -1991,7 +1997,7 @@ not_a_directory Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], @@ -2070,7 +2076,7 @@ not_a_directory Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], @@ -2113,7 +2119,7 @@ not_a_directory Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], diff --git a/crates/ty_python_semantic/src/module_resolver/testing.rs b/crates/ty_python_semantic/src/module_resolver/testing.rs index 5f1e9c4b6e7ef5..57f366c28c9a01 100644 --- a/crates/ty_python_semantic/src/module_resolver/testing.rs +++ b/crates/ty_python_semantic/src/module_resolver/testing.rs @@ -237,10 +237,10 @@ impl TestCaseBuilder { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: python_version, source: PythonVersionSource::default(), - }, + }), python_platform, search_paths: SearchPathSettings { extra_paths: vec![], @@ -298,10 +298,10 @@ impl TestCaseBuilder { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: python_version, source: PythonVersionSource::default(), - }, + }), python_platform, search_paths: SearchPathSettings { python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]), diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index ef059abff96388..6057b19419768b 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -6,6 +6,8 @@ use crate::python_platform::PythonPlatform; use crate::site_packages::SysPrefixPathOrigin; use anyhow::Context; +use ruff_db::diagnostic::Span; +use ruff_db::files::system_path_to_file; use ruff_db::system::{SystemPath, SystemPathBuf}; use ruff_python_ast::PythonVersion; use ruff_text_size::TextRange; @@ -32,14 +34,17 @@ impl Program { search_paths, } = settings; + let search_paths = SearchPaths::from_settings(db, &search_paths) + .with_context(|| "Invalid search path settings")?; + + let python_version_with_source = + Self::resolve_python_version(python_version_with_source, search_paths.python_version()); + tracing::info!( "Python version: Python {python_version}, platform: {python_platform}", python_version = python_version_with_source.version ); - let search_paths = SearchPaths::from_settings(db, &search_paths) - .with_context(|| "Invalid search path settings")?; - Ok( Program::builder(python_version_with_source, python_platform, search_paths) .durability(Durability::HIGH) @@ -51,32 +56,54 @@ impl Program { self.python_version_with_source(db).version } + fn resolve_python_version( + config_value: Option, + environment_value: Option<&PythonVersionWithSource>, + ) -> PythonVersionWithSource { + config_value + .or_else(|| environment_value.cloned()) + .unwrap_or_default() + } + pub fn update_from_settings( self, db: &mut dyn Db, settings: ProgramSettings, ) -> anyhow::Result<()> { let ProgramSettings { - python_version, + python_version: python_version_with_source, python_platform, search_paths, } = settings; + let search_paths = SearchPaths::from_settings(db, &search_paths)?; + + let new_python_version = + Self::resolve_python_version(python_version_with_source, search_paths.python_version()); + + if self.search_paths(db) != &search_paths { + tracing::debug!("Updating search paths"); + self.set_search_paths(db).to(search_paths); + } + if &python_platform != self.python_platform(db) { tracing::debug!("Updating python platform: `{python_platform:?}`"); self.set_python_platform(db).to(python_platform); } - if &python_version != self.python_version_with_source(db) { - tracing::debug!("Updating python version: `{python_version:?}`"); - self.set_python_version_with_source(db).to(python_version); + if &new_python_version != self.python_version_with_source(db) { + tracing::debug!( + "Updating python version: Python {version}", + version = new_python_version.version + ); + self.set_python_version_with_source(db) + .to(new_python_version); } - self.update_search_paths(db, &search_paths)?; - Ok(()) } + /// Update the search paths for the program. pub fn update_search_paths( self, db: &mut dyn Db, @@ -84,8 +111,21 @@ impl Program { ) -> anyhow::Result<()> { let search_paths = SearchPaths::from_settings(db, search_path_settings)?; + let current_python_version = self.python_version_with_source(db); + let python_version_from_environment = + search_paths.python_version().cloned().unwrap_or_default(); + + if current_python_version != &python_version_from_environment + && current_python_version.source.priority() + <= python_version_from_environment.source.priority() + { + tracing::debug!("Updating Python version from environment"); + self.set_python_version_with_source(db) + .to(python_version_from_environment); + } + if self.search_paths(db) != &search_paths { - tracing::debug!("Update search paths"); + tracing::debug!("Updating search paths"); self.set_search_paths(db).to(search_paths); } @@ -99,7 +139,7 @@ impl Program { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ProgramSettings { - pub python_version: PythonVersionWithSource, + pub python_version: Option, pub python_platform: PythonPlatform, pub search_paths: SearchPathSettings, } @@ -107,7 +147,11 @@ pub struct ProgramSettings { #[derive(Clone, Debug, Eq, PartialEq, Default)] pub enum PythonVersionSource { /// Value loaded from a project's configuration file. - File(Arc, Option), + ConfigFile(PythonVersionFileSource), + + /// Value loaded from the `pyvenv.cfg` file of the virtual environment. + /// The virtual environment might have been configured, activated or inferred. + PyvenvCfgFile(PythonVersionFileSource), /// The value comes from a CLI argument, while it's left open if specified using a short argument, /// long argument (`--extra-paths`) or `--config key=value`. @@ -118,6 +162,55 @@ pub enum PythonVersionSource { Default, } +impl PythonVersionSource { + fn priority(&self) -> PythonSourcePriority { + match self { + PythonVersionSource::Default => PythonSourcePriority::Default, + PythonVersionSource::PyvenvCfgFile(_) => PythonSourcePriority::PyvenvCfgFile, + PythonVersionSource::ConfigFile(_) => PythonSourcePriority::ConfigFile, + PythonVersionSource::Cli => PythonSourcePriority::Cli, + } + } +} + +/// The priority in which Python version sources are considered. +/// A higher value means a higher priority. +/// +/// For example, if a Python version is specified in a pyproject.toml file +/// but *also* via a CLI argument, the CLI argument will take precedence. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +#[cfg_attr(test, derive(strum_macros::EnumIter))] +enum PythonSourcePriority { + Default = 0, + PyvenvCfgFile = 1, + ConfigFile = 2, + Cli = 3, +} + +/// Information regarding the file and [`TextRange`] of the configuration +/// from which we inferred the Python version. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PythonVersionFileSource { + path: Arc, + range: Option, +} + +impl PythonVersionFileSource { + pub fn new(path: Arc, range: Option) -> Self { + Self { path, range } + } + + /// Attempt to resolve a [`Span`] that corresponds to the location of + /// the configuration setting that specified the Python version. + /// + /// Useful for subdiagnostics when informing the user + /// what the inferred Python version of their project is. + pub(crate) fn span(&self, db: &dyn Db) -> Option { + let file = system_path_to_file(db.upcast(), &*self.path).ok()?; + Some(Span::from(file).with_optional_range(self.range)) + } +} + #[derive(Eq, PartialEq, Debug, Clone)] pub struct PythonVersionWithSource { pub version: PythonVersion, @@ -210,3 +303,54 @@ impl PythonPath { Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag) } } + +#[cfg(test)] +mod tests { + use super::*; + use strum::IntoEnumIterator; + + #[test] + fn test_python_version_source_priority() { + for priority in PythonSourcePriority::iter() { + match priority { + // CLI source takes priority over all other sources. + PythonSourcePriority::Cli => { + for other in PythonSourcePriority::iter() { + assert!(priority >= other, "{other:?}"); + } + } + // Config files have lower priority than CLI arguments, + // but higher than pyvenv.cfg files and the fallback default. + PythonSourcePriority::ConfigFile => { + for other in PythonSourcePriority::iter() { + match other { + PythonSourcePriority::Cli => assert!(other > priority, "{other:?}"), + PythonSourcePriority::ConfigFile => assert_eq!(priority, other), + PythonSourcePriority::PyvenvCfgFile | PythonSourcePriority::Default => { + assert!(priority > other, "{other:?}"); + } + } + } + } + // Pyvenv.cfg files have lower priority than CLI flags and config files, + // but higher than the default fallback. + PythonSourcePriority::PyvenvCfgFile => { + for other in PythonSourcePriority::iter() { + match other { + PythonSourcePriority::Cli | PythonSourcePriority::ConfigFile => { + assert!(other > priority, "{other:?}"); + } + PythonSourcePriority::PyvenvCfgFile => assert_eq!(priority, other), + PythonSourcePriority::Default => assert!(priority > other, "{other:?}"), + } + } + } + PythonSourcePriority::Default => { + for other in PythonSourcePriority::iter() { + assert!(priority <= other, "{other:?}"); + } + } + } + } + } +} diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 7da9e9ca7c93d9..9f28d4380a1a6a 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -8,15 +8,19 @@ //! reasonably ask us to type-check code assuming that the code runs //! on Linux.) -use std::fmt; use std::fmt::Display; use std::io; use std::num::NonZeroUsize; use std::ops::Deref; +use std::{fmt, sync::Arc}; use indexmap::IndexSet; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_python_ast::PythonVersion; +use ruff_python_trivia::Cursor; +use ruff_text_size::{TextLen, TextRange}; + +use crate::{PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource}; type SitePackagesDiscoveryResult = Result; @@ -108,10 +112,23 @@ impl PythonEnvironment { } } - /// Returns the `site-packages` directories for this Python environment. + /// Returns the `site-packages` directories for this Python environment, + /// as well as the Python version that was used to create this environment + /// (the latter will only be available for virtual environments that specify + /// the metadata in their `pyvenv.cfg` files). /// /// See the documentation for [`site_packages_directory_from_sys_prefix`] for more details. - pub(crate) fn site_packages_directories( + pub(crate) fn into_settings( + self, + system: &dyn System, + ) -> SitePackagesDiscoveryResult<(SitePackagesPaths, Option)> { + Ok(match self { + Self::Virtual(venv) => (venv.site_packages_directories(system)?, venv.version), + Self::System(env) => (env.site_packages_directories(system)?, None), + }) + } + + fn site_packages_directories( &self, system: &dyn System, ) -> SitePackagesDiscoveryResult { @@ -126,13 +143,14 @@ impl PythonEnvironment { /// /// We only need to distinguish cases that change the on-disk layout. /// Everything else can be treated like CPython. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub(crate) enum PythonImplementation { CPython, PyPy, GraalPy, /// Fallback when the value is missing or unrecognised. /// We treat it like CPython but keep the information for diagnostics. + #[default] Unknown, } @@ -169,7 +187,7 @@ pub(crate) struct VirtualEnvironment { /// so it's possible that we might not be able to find this information /// in an acceptable format under any of the keys we expect. /// This field will be `None` if so. - version: Option, + version: Option, implementation: PythonImplementation, /// If this virtual environment was created using uv, @@ -186,10 +204,6 @@ impl VirtualEnvironment { path: SysPrefixPath, system: &dyn System, ) -> SitePackagesDiscoveryResult { - fn pyvenv_cfg_line_number(index: usize) -> NonZeroUsize { - index.checked_add(1).and_then(NonZeroUsize::new).unwrap() - } - let pyvenv_cfg_path = path.join("pyvenv.cfg"); tracing::debug!("Attempting to parse virtual environment metadata at '{pyvenv_cfg_path}'"); @@ -197,62 +211,24 @@ impl VirtualEnvironment { .read_to_string(&pyvenv_cfg_path) .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(path.origin, io_err))?; - let mut include_system_site_packages = false; - let mut base_executable_home_path = None; - let mut version_info_string = None; - let mut implementation = PythonImplementation::Unknown; - let mut created_with_uv = false; - let mut parent_environment = None; - - // A `pyvenv.cfg` file *looks* like a `.ini` file, but actually isn't valid `.ini` syntax! - // The Python standard-library's `site` module parses these files by splitting each line on - // '=' characters, so that's what we should do as well. - // - // See also: https://snarky.ca/how-virtual-environments-work/ - for (index, line) in pyvenv_cfg.lines().enumerate() { - if let Some((key, value)) = line.split_once('=') { - let key = key.trim(); - if key.is_empty() { - return Err(SitePackagesDiscoveryError::PyvenvCfgParseError( - pyvenv_cfg_path, - PyvenvCfgParseErrorKind::MalformedKeyValuePair { - line_number: pyvenv_cfg_line_number(index), - }, - )); - } - - let value = value.trim(); - if value.is_empty() { - return Err(SitePackagesDiscoveryError::PyvenvCfgParseError( - pyvenv_cfg_path, - PyvenvCfgParseErrorKind::MalformedKeyValuePair { - line_number: pyvenv_cfg_line_number(index), - }, - )); - } - - match key { - "include-system-site-packages" => { - include_system_site_packages = value.eq_ignore_ascii_case("true"); - } - "home" => base_executable_home_path = Some(value), - // `virtualenv` and `uv` call this key `version_info`, - // but the stdlib venv module calls it `version` - "version" | "version_info" => version_info_string = Some(value), - "implementation" => { - implementation = match value.to_ascii_lowercase().as_str() { - "cpython" => PythonImplementation::CPython, - "graalvm" => PythonImplementation::GraalPy, - "pypy" => PythonImplementation::PyPy, - _ => PythonImplementation::Unknown, - }; - } - "uv" => created_with_uv = true, - "extends-environment" => parent_environment = Some(value), - _ => continue, - } - } - } + let parsed_pyvenv_cfg = + PyvenvCfgParser::new(&pyvenv_cfg) + .parse() + .map_err(|pyvenv_parse_error| { + SitePackagesDiscoveryError::PyvenvCfgParseError( + pyvenv_cfg_path.clone(), + pyvenv_parse_error, + ) + })?; + + let RawPyvenvCfg { + include_system_site_packages, + base_executable_home_path, + version, + implementation, + created_with_uv, + parent_environment, + } = parsed_pyvenv_cfg; // The `home` key is read by the standard library's `site.py` module, // so if it's missing from the `pyvenv.cfg` file @@ -264,6 +240,7 @@ impl VirtualEnvironment { PyvenvCfgParseErrorKind::NoHomeKey, )); }; + let base_executable_home_path = PythonHomePath::new(base_executable_home_path, system) .map_err(|io_err| { SitePackagesDiscoveryError::PyvenvCfgParseError( @@ -298,10 +275,15 @@ impl VirtualEnvironment { // created the `pyvenv.cfg` file. Lenient parsing is appropriate here: // the file isn't really *invalid* if it doesn't have this key, // or if the value doesn't parse according to our expectations. - let version = version_info_string.and_then(|version_string| { + let version = version.and_then(|(version_string, range)| { let mut version_info_parts = version_string.split('.'); let (major, minor) = (version_info_parts.next()?, version_info_parts.next()?); - PythonVersion::try_from((major, minor)).ok() + let version = PythonVersion::try_from((major, minor)).ok()?; + let source = PythonVersionSource::PyvenvCfgFile(PythonVersionFileSource::new( + Arc::new(pyvenv_cfg_path), + Some(range), + )); + Some(PythonVersionWithSource { version, source }) }); let metadata = Self { @@ -333,8 +315,10 @@ impl VirtualEnvironment { parent_environment, } = self; + let version = version.as_ref().map(|v| v.version); + let mut site_packages_directories = SitePackagesPaths::single( - site_packages_directory_from_sys_prefix(root_path, *version, *implementation, system)?, + site_packages_directory_from_sys_prefix(root_path, version, *implementation, system)?, ); if let Some(parent_env_site_packages) = parent_environment.as_deref() { @@ -362,7 +346,7 @@ impl VirtualEnvironment { if let Some(sys_prefix_path) = system_sys_prefix { match site_packages_directory_from_sys_prefix( &sys_prefix_path, - *version, + version, *implementation, system, ) { @@ -390,6 +374,119 @@ System site-packages will not be used for module resolution.", } } +/// A parser for `pyvenv.cfg` files: metadata files for virtual environments. +/// +/// Note that a `pyvenv.cfg` file *looks* like a `.ini` file, but actually isn't valid `.ini` syntax! +/// +/// See also: +#[derive(Debug)] +struct PyvenvCfgParser<'s> { + source: &'s str, + cursor: Cursor<'s>, + line_number: NonZeroUsize, + data: RawPyvenvCfg<'s>, +} + +impl<'s> PyvenvCfgParser<'s> { + fn new(source: &'s str) -> Self { + Self { + source, + cursor: Cursor::new(source), + line_number: NonZeroUsize::new(1).unwrap(), + data: RawPyvenvCfg::default(), + } + } + + /// Parse the `pyvenv.cfg` file and return the parsed data. + fn parse(mut self) -> Result, PyvenvCfgParseErrorKind> { + while !self.cursor.is_eof() { + self.parse_line()?; + self.line_number = self.line_number.checked_add(1).unwrap(); + } + Ok(self.data) + } + + /// Parse a single line of the `pyvenv.cfg` file and advance the cursor + /// to the beginning of the next line. + fn parse_line(&mut self) -> Result<(), PyvenvCfgParseErrorKind> { + let PyvenvCfgParser { + source, + cursor, + line_number, + data, + } = self; + + let line_number = *line_number; + + cursor.eat_while(|c| c.is_whitespace() && c != '\n'); + + let key_start = cursor.offset(); + cursor.eat_while(|c| !matches!(c, '\n' | '=')); + let key_end = cursor.offset(); + + if !cursor.eat_char('=') { + // Skip over any lines that do not contain '=' characters, same as the CPython stdlib + // + cursor.eat_char('\n'); + return Ok(()); + } + + let key = source[TextRange::new(key_start, key_end)].trim(); + + cursor.eat_while(|c| c.is_whitespace() && c != '\n'); + let value_start = cursor.offset(); + cursor.eat_while(|c| c != '\n'); + let value = source[TextRange::new(value_start, cursor.offset())].trim(); + cursor.eat_char('\n'); + + if value.is_empty() { + return Err(PyvenvCfgParseErrorKind::MalformedKeyValuePair { line_number }); + } + + match key { + "include-system-site-packages" => { + data.include_system_site_packages = value.eq_ignore_ascii_case("true"); + } + "home" => data.base_executable_home_path = Some(value), + // `virtualenv` and `uv` call this key `version_info`, + // but the stdlib venv module calls it `version` + "version" | "version_info" => { + let version_range = TextRange::at(value_start, value.text_len()); + data.version = Some((value, version_range)); + } + "implementation" => { + data.implementation = match value.to_ascii_lowercase().as_str() { + "cpython" => PythonImplementation::CPython, + "graalvm" => PythonImplementation::GraalPy, + "pypy" => PythonImplementation::PyPy, + _ => PythonImplementation::Unknown, + }; + } + "uv" => data.created_with_uv = true, + "extends-environment" => data.parent_environment = Some(value), + "" => { + return Err(PyvenvCfgParseErrorKind::MalformedKeyValuePair { line_number }); + } + _ => {} + } + + Ok(()) + } +} + +/// A `key:value` mapping derived from parsing a `pyvenv.cfg` file. +/// +/// This data contained within is still mostly raw and unvalidated. +#[derive(Debug, Default)] +struct RawPyvenvCfg<'s> { + include_system_site_packages: bool, + base_executable_home_path: Option<&'s str>, + version: Option<(&'s str, TextRange)>, + implementation: PythonImplementation, + created_with_uv: bool, + parent_environment: Option<&'s str>, +} + /// A Python environment that is _not_ a virtual environment. /// /// This environment may or may not be one that is managed by the operating system itself, e.g., @@ -969,7 +1066,7 @@ mod tests { if self_venv.pyvenv_cfg_version_field.is_some() { assert_eq!( - venv.version, + venv.version.as_ref().map(|v| v.version), Some(PythonVersion { major: 3, minor: self.minor_version @@ -1424,4 +1521,29 @@ mod tests { if path == pyvenv_cfg_path )); } + + #[test] + fn pyvenv_cfg_with_carriage_return_line_endings_parses() { + let pyvenv_cfg = "home = /somewhere/python\r\nversion_info = 3.13\r\nimplementation = PyPy"; + let parsed = PyvenvCfgParser::new(pyvenv_cfg).parse().unwrap(); + assert_eq!(parsed.base_executable_home_path, Some("/somewhere/python")); + let version = parsed.version.unwrap(); + assert_eq!(version.0, "3.13"); + assert_eq!(&pyvenv_cfg[version.1], version.0); + assert_eq!(parsed.implementation, PythonImplementation::PyPy); + } + + #[test] + fn pyvenv_cfg_with_strange_whitespace_parses() { + let pyvenv_cfg = " home= /a path with whitespace/python\t \t \nversion_info = 3.13 \n\n\n\nimplementation =PyPy"; + let parsed = PyvenvCfgParser::new(pyvenv_cfg).parse().unwrap(); + assert_eq!( + parsed.base_executable_home_path, + Some("/a path with whitespace/python") + ); + let version = parsed.version.unwrap(); + assert_eq!(version.0, "3.13"); + assert_eq!(&pyvenv_cfg[version.1], version.0); + assert_eq!(parsed.implementation, PythonImplementation::PyPy); + } } diff --git a/crates/ty_python_semantic/src/util/diagnostics.rs b/crates/ty_python_semantic/src/util/diagnostics.rs index c9949f0bddb044..c2419c2feb4f5b 100644 --- a/crates/ty_python_semantic/src/util/diagnostics.rs +++ b/crates/ty_python_semantic/src/util/diagnostics.rs @@ -1,8 +1,5 @@ use crate::{Db, Program, PythonVersionWithSource}; -use ruff_db::{ - diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}, - files::system_path_to_file, -}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; /// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred. /// @@ -22,23 +19,47 @@ pub fn add_inferred_python_version_hint_to_diagnostic( "Python {version} was assumed when {action} because it was specified on the command line", )); } - crate::PythonVersionSource::File(path, range) => { - if let Ok(file) = system_path_to_file(db.upcast(), &**path) { + crate::PythonVersionSource::ConfigFile(source) => { + if let Some(span) = source.span(db) { let mut sub_diagnostic = SubDiagnostic::new( Severity::Info, format_args!("Python {version} was assumed when {action}"), ); - sub_diagnostic.annotate( - Annotation::primary(Span::from(file).with_optional_range(*range)).message( - format_args!("Python {version} assumed due to this configuration setting"), + sub_diagnostic.annotate(Annotation::primary(span).message(format_args!( + "Python {version} assumed due to this configuration setting" + ))); + diagnostic.sub(sub_diagnostic); + } else { + diagnostic.info(format_args!( + "Python {version} was assumed when {action} because of your configuration file(s)", + )); + } + } + crate::PythonVersionSource::PyvenvCfgFile(source) => { + if let Some(span) = source.span(db) { + let mut sub_diagnostic = SubDiagnostic::new( + Severity::Info, + format_args!( + "Python {version} was assumed when {action} because of your virtual environment" ), ); + sub_diagnostic.annotate( + Annotation::primary(span) + .message("Python version inferred from virtual environment metadata file"), + ); + // TODO: it would also be nice to tell them how we resolved their virtual environment... diagnostic.sub(sub_diagnostic); } else { diagnostic.info(format_args!( - "Python {version} was assumed when {action} because of your configuration file(s)", + "Python {version} was assumed when {action} because \ + your virtual environment's pyvenv.cfg file indicated \ + it was the Python version being used", )); } + diagnostic.info( + "No Python version was specified on the command line \ + or in a configuration file", + ); } crate::PythonVersionSource::Default => { diagnostic.info(format_args!( diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index 8df0b1caf2bf8a..38ff0494d0856d 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -501,7 +501,7 @@ mod tests { let mut db = Db::setup(); let settings = ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(Vec::new()), }; diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 36533cf6dea592..234c894725ab61 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -258,10 +258,10 @@ fn run_test( let configuration = test.configuration(); let settings = ProgramSettings { - python_version: PythonVersionWithSource { + python_version: Some(PythonVersionWithSource { version: python_version, source: PythonVersionSource::Cli, - }, + }), python_platform: configuration .python_platform() .unwrap_or(PythonPlatform::Identifier("linux".to_string())), diff --git a/crates/ty_test/src/matcher.rs b/crates/ty_test/src/matcher.rs index ff90010f786357..92e150d8603b9a 100644 --- a/crates/ty_test/src/matcher.rs +++ b/crates/ty_test/src/matcher.rs @@ -385,7 +385,7 @@ mod tests { let mut db = crate::db::Db::setup(); let settings = ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(Vec::new()), }; diff --git a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs index dbc814644c78a1..5635d75c3028fc 100644 --- a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs +++ b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs @@ -118,7 +118,7 @@ fn setup_db() -> TestDb { Program::from_settings( &db, ProgramSettings { - python_version: PythonVersionWithSource::default(), + python_version: Some(PythonVersionWithSource::default()), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![src_root]), }, From b390b3cb8e165550b349ba4cb24f474ad29a9d43 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 30 May 2025 22:45:28 +0100 Subject: [PATCH 294/487] [ty] Update docs for Python version inference (#18397) --- crates/ty/docs/cli.md | 3 +-- crates/ty/src/args.rs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index c98543117e217e..ae4382bd5d8fee 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -72,8 +72,7 @@ over all configuration files.

This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

--python-version, --target-version version

Python version to assume when resolving types.

The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.

-

By default, the Python version is inferred as the lower bound of the project's requires-python field from the pyproject.toml, if available. Otherwise, the latest stable version supported by ty is used, which is currently 3.13.

-

ty will not infer the Python version from the Python environment at this time.

+

By default, the Python version is inferred as the lower bound of the project's requires-python field from the pyproject.toml, if available. Otherwise, if a virtual environment has been configured or detected and a Python version can be inferred from the virtual environment's metadata, that version will be used. If neither of these applies, ty will fall back to the latest stable Python version supported by ty (currently 3.13).

Possible values:

  • 3.7
  • diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index d7f49f79f8e2b2..d2cfadcff90757 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -82,10 +82,10 @@ pub(crate) struct CheckCommand { /// type definitions of first- and third-party modules that are conditional on the Python version. /// /// By default, the Python version is inferred as the lower bound of the project's - /// `requires-python` field from the `pyproject.toml`, if available. Otherwise, the latest - /// stable version supported by ty is used, which is currently 3.13. - /// - /// ty will not infer the Python version from the Python environment at this time. + /// `requires-python` field from the `pyproject.toml`, if available. Otherwise, if a virtual + /// environment has been configured or detected and a Python version can be inferred from the + /// virtual environment's metadata, that version will be used. If neither of these applies, ty + /// will fall back to the latest stable Python version supported by ty (currently 3.13). #[arg(long, value_name = "VERSION", alias = "target-version")] pub(crate) python_version: Option, From aa1fad61e0f9fe0c7faac876f2ef55cd3817fc6c Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 30 May 2025 18:19:20 -0400 Subject: [PATCH 295/487] Support relative `--ty-path` in ty-benchmark (#18385) ## Summary This currently doesn't work because the benchmark changes the working directory. Also updates the process name to make it easier to compare two local ty binaries. --- scripts/ty_benchmark/src/benchmark/cases.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/ty_benchmark/src/benchmark/cases.py b/scripts/ty_benchmark/src/benchmark/cases.py index 786b4b373ff506..9dd0012792b805 100644 --- a/scripts/ty_benchmark/src/benchmark/cases.py +++ b/scripts/ty_benchmark/src/benchmark/cases.py @@ -59,9 +59,9 @@ class Ty(Tool): def __init__(self, *, path: Path | None = None): self.name = str(path) or "ty" - self.path = path or ( - (Path(__file__) / "../../../../../target/release/ty").resolve() - ) + self.path = ( + path or (Path(__file__) / "../../../../../target/release/ty") + ).resolve() assert self.path.is_file(), ( f"ty not found at '{self.path}'. Run `cargo build --release --bin ty`." @@ -73,7 +73,7 @@ def cold_command(self, project: Project, venv: Venv) -> Command: command.extend(["--python", str(venv.path)]) return Command( - name="ty", + name=self.name, command=command, ) From 54f597658ce986b140598fa7db65b99335f90cdf Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sun, 1 Jun 2025 11:07:55 +0200 Subject: [PATCH 296/487] [ty] Fix multithreading related hangs and panics (#18238) --- Cargo.lock | 6 ++--- Cargo.toml | 2 +- .../resources/primer/bad.txt | 24 +++++++++---------- crates/ty_python_semantic/src/types.rs | 2 +- fuzz/Cargo.toml | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 447c014c21a7fb..bc1f58df06dcf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3194,7 +3194,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" +source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" dependencies = [ "boxcar", "compact_str", @@ -3218,12 +3218,12 @@ dependencies = [ [[package]] name = "salsa-macro-rules" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" +source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" [[package]] name = "salsa-macros" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=40d7844a7a7449a136e0946920a678b55a82f30b#40d7844a7a7449a136e0946920a678b55a82f30b" +source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 1b01cf219cd9d7..46597dae62fbe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "40d7844a7a7449a136e0946920a678b55a82f30b" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index 43b465e959d247..da07da7fb26057 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -1,27 +1,27 @@ -Tanjun # hangs -antidote # hangs / slow +Tanjun # too many iterations +antidote # hangs / slow (single threaded) artigraph # cycle panics (value_type_) arviz # too many iterations on versions of arviz newer than https://github.com/arviz-devs/arviz/commit/3205b82bb4d6097c31f7334d7ac51a6de37002d0 core # cycle panics (value_type_) -cpython # access to field whilst being initialized, too many cycle iterations -discord.py # some kind of hang, only when multi-threaded? -freqtrade # hangs +cpython # too many cycle iterations +discord.py +freqtrade hydpy # too many iterations ibis # too many iterations jax # too many iterations packaging # too many iterations -pandas # slow -pandas-stubs # hangs/slow, or else https://github.com/salsa-rs/salsa/issues/831 +pandas # slow (9s) +pandas-stubs pandera # stack overflow pip # vendors packaging, see above -prefect # slow +prefect pylint # cycle panics (self-recursive type alias) pyodide # too many cycle iterations pywin32 # bad use-def map (binding with definitely-visible unbound) -schemathesis # https://github.com/salsa-rs/salsa/issues/831 -scikit-learn # success, but mypy-primer hangs processing the output +schemathesis +scikit-learn setuptools # vendors packaging, see above -spack # success, but mypy-primer hangs processing the output +spack # slow, success, but mypy-primer hangs processing the output spark # too many iterations -steam.py # hangs +steam.py # hangs (single threaded) xarray # too many iterations diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 7704d133c250a6..0992d479d0d015 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -9200,7 +9200,7 @@ impl<'db> BoundSuperType<'db> { // Make sure that the `Type` enum does not grow unexpectedly. #[cfg(not(debug_assertions))] #[cfg(target_pointer_width = "64")] -static_assertions::assert_eq_size!(Type, [u8; 24]); +static_assertions::assert_eq_size!(Type, [u8; 16]); #[cfg(test)] pub(crate) mod tests { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 23a9e50b8faf75..b1671c7bb8a197 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "40d7844a7a7449a136e0946920a678b55a82f30b" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" } similar = { version = "2.5.0" } tracing = { version = "0.1.40" } From 7a63ac145aa95f29a35c4aa2db159a2a6408cbba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 15:21:18 +0100 Subject: [PATCH 297/487] Sync vendored typeshed stubs (#18407) Co-authored-by: typeshedbot <> Co-authored-by: Alex Waygood --- crates/ty_ide/src/goto.rs | 138 +++++++++--------- .../vendor/typeshed/source_commit.txt | 2 +- .../vendor/typeshed/stdlib/_asyncio.pyi | 5 + .../vendor/typeshed/stdlib/_ctypes.pyi | 2 + .../vendor/typeshed/stdlib/_curses.pyi | 3 + .../vendor/typeshed/stdlib/_heapq.pyi | 12 +- .../vendor/typeshed/stdlib/_imp.pyi | 2 + .../typeshed/stdlib/_posixsubprocess.pyi | 79 ++++++---- .../vendor/typeshed/stdlib/_thread.pyi | 5 + .../vendor/typeshed/stdlib/_tkinter.pyi | 2 +- .../typeshed/stdlib/_typeshed/__init__.pyi | 3 - .../vendor/typeshed/stdlib/argparse.pyi | 8 +- .../vendor/typeshed/stdlib/ast.pyi | 36 +++-- .../typeshed/stdlib/asyncio/__init__.pyi | 16 +- .../vendor/typeshed/stdlib/asyncio/events.pyi | 38 ++++- .../typeshed/stdlib/asyncio/futures.pyi | 6 +- .../typeshed/stdlib/asyncio/unix_events.pyi | 31 ++-- .../stdlib/asyncio/windows_events.pyi | 38 +++-- .../vendor/typeshed/stdlib/builtins.pyi | 83 ++++++++--- .../stdlib/concurrent/futures/interpreter.pyi | 4 +- .../typeshed/stdlib/ctypes/__init__.pyi | 20 ++- .../vendor/typeshed/stdlib/ctypes/util.pyi | 3 + .../typeshed/stdlib/ctypes/wintypes.pyi | 9 ++ .../vendor/typeshed/stdlib/dataclasses.pyi | 55 ++++++- .../vendor/typeshed/stdlib/distutils/cmd.pyi | 4 +- .../stdlib/distutils/command/config.pyi | 4 +- .../stdlib/distutils/command/register.pyi | 3 +- .../vendor/typeshed/stdlib/distutils/dist.pyi | 4 +- .../vendor/typeshed/stdlib/enum.pyi | 6 +- .../vendor/typeshed/stdlib/errno.pyi | 3 + .../vendor/typeshed/stdlib/faulthandler.pyi | 4 + .../vendor/typeshed/stdlib/fractions.pyi | 3 + .../vendor/typeshed/stdlib/importlib/abc.pyi | 110 +++++++------- .../typeshed/stdlib/importlib/machinery.pyi | 23 +++ .../stdlib/importlib/resources/__init__.pyi | 10 +- .../stdlib/importlib/resources/_common.pyi | 2 +- .../stdlib/importlib/resources/abc.pyi | 73 +++++++-- .../vendor/typeshed/stdlib/importlib/util.pyi | 24 ++- .../typeshed/stdlib/logging/__init__.pyi | 3 + .../stdlib/multiprocessing/forkserver.pyi | 28 +++- .../stdlib/multiprocessing/managers.pyi | 63 +++++++- .../stdlib/multiprocessing/popen_fork.pyi | 3 + .../stdlib/multiprocessing/reduction.pyi | 3 +- .../typeshed/stdlib/multiprocessing/util.pyi | 20 ++- .../vendor/typeshed/stdlib/numbers.pyi | 3 +- .../vendor/typeshed/stdlib/pyexpat/errors.pyi | 2 + .../vendor/typeshed/stdlib/select.pyi | 2 + .../vendor/typeshed/stdlib/shutil.pyi | 11 +- .../vendor/typeshed/stdlib/socketserver.pyi | 5 +- .../vendor/typeshed/stdlib/sre_constants.pyi | 2 + .../typeshed/stdlib/string/__init__.pyi | 7 +- .../typeshed/stdlib/string/templatelib.pyi | 3 + .../vendor/typeshed/stdlib/sys/__init__.pyi | 6 +- .../typeshed/stdlib/tkinter/__init__.pyi | 52 +++---- .../typeshed/stdlib/tkinter/commondialog.pyi | 2 +- .../vendor/typeshed/stdlib/tkinter/dialog.pyi | 3 +- .../typeshed/stdlib/tkinter/filedialog.pyi | 10 +- .../vendor/typeshed/stdlib/tkinter/ttk.pyi | 26 ++-- .../vendor/typeshed/stdlib/turtle.pyi | 39 ++++- .../vendor/typeshed/stdlib/types.pyi | 11 +- .../vendor/typeshed/stdlib/typing.pyi | 10 +- .../typeshed/stdlib/typing_extensions.pyi | 29 +++- .../vendor/typeshed/stdlib/winsound.pyi | 10 ++ .../typeshed/stdlib/xml/sax/__init__.pyi | 19 ++- .../typeshed/stdlib/zipfile/__init__.pyi | 25 +++- .../vendor/typeshed/stdlib/zipimport.pyi | 14 +- 66 files changed, 922 insertions(+), 362 deletions(-) diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index eaac8473c5afbf..b7fb38c314a7e7 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -421,16 +421,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r#" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:13 @@ -440,7 +440,7 @@ mod tests { 4 | a | ^ | - "###); + "#); } #[test] fn goto_type_of_expression_with_literal_node() { @@ -450,16 +450,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r#" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:2:22 @@ -467,7 +467,7 @@ mod tests { 2 | a: str = "test" | ^^^^^^ | - "###); + "#); } #[test] @@ -566,16 +566,16 @@ mod tests { "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r#" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:18 @@ -585,7 +585,7 @@ mod tests { 4 | test(a= "123") | ^ | - "###); + "#); } #[test] @@ -601,16 +601,16 @@ mod tests { // TODO: This should jump to `str` and not `int` because // the keyword is typed as a string. It's only the passed argument that // is an int. Navigating to `str` would match pyright's behavior. - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:238:7 + --> stdlib/builtins.pyi:244:7 | - 236 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed - 237 | - 238 | class int: + 242 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed + 243 | + 244 | class int: | ^^^ - 239 | @overload - 240 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ... + 245 | @overload + 246 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ... | info: Source --> main.py:4:18 @@ -620,7 +620,7 @@ mod tests { 4 | test(a= 123) | ^ | - "###); + "); } #[test] @@ -635,16 +635,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r#" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:1096:7 + --> stdlib/builtins.pyi:1136:7 | - 1094 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - 1095 | - 1096 | class dict(MutableMapping[_KT, _VT]): + 1134 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + 1135 | + 1136 | class dict(MutableMapping[_KT, _VT]): | ^^^^ - 1097 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics - 1098 | # Also multiprocessing.managers.SyncManager.dict() + 1137 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics + 1138 | # Also multiprocessing.managers.SyncManager.dict() | info: Source --> main.py:6:5 @@ -654,7 +654,7 @@ f(**kwargs) 6 | f(**kwargs) | ^^^^^^ | - "###); + "#); } #[test] @@ -666,16 +666,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:3:17 @@ -684,7 +684,7 @@ f(**kwargs) 3 | a | ^ | - "###); + "); } #[test] @@ -759,16 +759,16 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r" info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:4:27 @@ -778,7 +778,7 @@ f(**kwargs) 4 | print(a) | ^ | - "###); + "); } #[test] @@ -790,15 +790,15 @@ f(**kwargs) "#, ); - assert_snapshot!(test.goto_type_definition(), @r###" + assert_snapshot!(test.goto_type_definition(), @r" info[goto-type-definition]: Type definition - --> stdlib/types.pyi:680:11 + --> stdlib/types.pyi:689:11 | - 678 | if sys.version_info >= (3, 10): - 679 | @final - 680 | class NoneType: + 687 | if sys.version_info >= (3, 10): + 688 | @final + 689 | class NoneType: | ^^^^^^^^ - 681 | def __bool__(self) -> Literal[False]: ... + 690 | def __bool__(self) -> Literal[False]: ... | info: Source --> main.py:3:17 @@ -809,14 +809,14 @@ f(**kwargs) | info[goto-type-definition]: Type definition - --> stdlib/builtins.pyi:445:7 + --> stdlib/builtins.pyi:461:7 | - 443 | def __getitem__(self, key: int, /) -> str | int | None: ... - 444 | - 445 | class str(Sequence[str]): + 459 | def __getitem__(self, key: int, /) -> str | int | None: ... + 460 | + 461 | class str(Sequence[str]): | ^^^ - 446 | @overload - 447 | def __new__(cls, object: object = ...) -> Self: ... + 462 | @overload + 463 | def __new__(cls, object: object = ...) -> Self: ... | info: Source --> main.py:3:17 @@ -825,7 +825,7 @@ f(**kwargs) 3 | a | ^ | - "###); + "); } impl CursorTest { diff --git a/crates/ty_vendored/vendor/typeshed/source_commit.txt b/crates/ty_vendored/vendor/typeshed/source_commit.txt index fdd24bb31d9100..94bba17b2c184c 100644 --- a/crates/ty_vendored/vendor/typeshed/source_commit.txt +++ b/crates/ty_vendored/vendor/typeshed/source_commit.txt @@ -1 +1 @@ -1063db7c15135c172f1f6a81d3aff6d1cb00a980 +5a3c495d2f6fa9b68cd99f39feba4426e4d17ea9 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_asyncio.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_asyncio.pyi index be486fddb12dae..ed56f33af93aff 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_asyncio.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_asyncio.pyi @@ -103,3 +103,8 @@ def _leave_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... if sys.version_info >= (3, 12): def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... + +if sys.version_info >= (3, 14): + def future_discard_from_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... + def future_add_to_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... + def all_tasks(loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi index 944685646c36dc..35c77619d47497 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_ctypes.pyi @@ -75,6 +75,8 @@ class _CData: _objects: Mapping[Any, int] | None def __buffer__(self, flags: int, /) -> memoryview: ... def __ctypes_from_outparam__(self, /) -> Self: ... + if sys.version_info >= (3, 14): + __pointer_type__: type # this is a union of all the subclasses of _CData, which is useful because of # the methods that are present on each of those subclasses which are not present diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_curses.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_curses.pyi index d7820c72c090da..f21a9ca6027085 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_curses.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_curses.pyi @@ -304,6 +304,9 @@ def has_colors() -> bool: ... if sys.version_info >= (3, 10): def has_extended_color_support() -> bool: ... +if sys.version_info >= (3, 14): + def assume_default_colors(fg: int, bg: int, /) -> None: ... + def has_ic() -> bool: ... def has_il() -> bool: ... def has_key(key: int, /) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_heapq.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_heapq.pyi index 9f731bf91eefd1..3363fbcd7e740c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_heapq.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_heapq.pyi @@ -1,11 +1,19 @@ +import sys from typing import Any, Final, TypeVar -_T = TypeVar("_T") +_T = TypeVar("_T") # list items must be comparable __about__: Final[str] -def heapify(heap: list[Any], /) -> None: ... +def heapify(heap: list[Any], /) -> None: ... # list items must be comparable def heappop(heap: list[_T], /) -> _T: ... def heappush(heap: list[_T], item: _T, /) -> None: ... def heappushpop(heap: list[_T], item: _T, /) -> _T: ... def heapreplace(heap: list[_T], item: _T, /) -> _T: ... + +if sys.version_info >= (3, 14): + def heapify_max(heap: list[Any], /) -> None: ... # list items must be comparable + def heappop_max(heap: list[_T], /) -> _T: ... + def heappush_max(heap: list[_T], item: _T, /) -> None: ... + def heappushpop_max(heap: list[_T], item: _T, /) -> _T: ... + def heapreplace_max(heap: list[_T], item: _T, /) -> _T: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_imp.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_imp.pyi index de3549a91da599..c12c26d08ba2a4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_imp.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_imp.pyi @@ -5,6 +5,8 @@ from importlib.machinery import ModuleSpec from typing import Any check_hash_based_pycs: str +if sys.version_info >= (3, 14): + pyc_magic_number_token: int def source_hash(key: int, source: ReadableBuffer) -> bytes: ... def create_builtin(spec: ModuleSpec, /) -> types.ModuleType: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_posixsubprocess.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_posixsubprocess.pyi index df05dcd80be80a..dd74e316e8990e 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_posixsubprocess.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_posixsubprocess.pyi @@ -4,29 +4,56 @@ from collections.abc import Callable, Sequence from typing import SupportsIndex if sys.platform != "win32": - def fork_exec( - args: Sequence[StrOrBytesPath] | None, - executable_list: Sequence[bytes], - close_fds: bool, - pass_fds: tuple[int, ...], - cwd: str, - env: Sequence[bytes] | None, - p2cread: int, - p2cwrite: int, - c2pread: int, - c2pwrite: int, - errread: int, - errwrite: int, - errpipe_read: int, - errpipe_write: int, - restore_signals: int, - call_setsid: int, - pgid_to_set: int, - gid: SupportsIndex | None, - extra_groups: list[int] | None, - uid: SupportsIndex | None, - child_umask: int, - preexec_fn: Callable[[], None], - allow_vfork: bool, - /, - ) -> int: ... + if sys.version_info >= (3, 14): + def fork_exec( + args: Sequence[StrOrBytesPath] | None, + executable_list: Sequence[bytes], + close_fds: bool, + pass_fds: tuple[int, ...], + cwd: str, + env: Sequence[bytes] | None, + p2cread: int, + p2cwrite: int, + c2pread: int, + c2pwrite: int, + errread: int, + errwrite: int, + errpipe_read: int, + errpipe_write: int, + restore_signals: int, + call_setsid: int, + pgid_to_set: int, + gid: SupportsIndex | None, + extra_groups: list[int] | None, + uid: SupportsIndex | None, + child_umask: int, + preexec_fn: Callable[[], None], + /, + ) -> int: ... + else: + def fork_exec( + args: Sequence[StrOrBytesPath] | None, + executable_list: Sequence[bytes], + close_fds: bool, + pass_fds: tuple[int, ...], + cwd: str, + env: Sequence[bytes] | None, + p2cread: int, + p2cwrite: int, + c2pread: int, + c2pwrite: int, + errread: int, + errwrite: int, + errpipe_read: int, + errpipe_write: int, + restore_signals: bool, + call_setsid: bool, + pgid_to_set: int, + gid: SupportsIndex | None, + extra_groups: list[int] | None, + uid: SupportsIndex | None, + child_umask: int, + preexec_fn: Callable[[], None], + allow_vfork: bool, + /, + ) -> int: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_thread.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_thread.pyi index 378ac24237572a..9cfbe55b4fe31f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_thread.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_thread.pyi @@ -18,6 +18,8 @@ class RLock: def release(self) -> None: ... __enter__ = acquire def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ... + if sys.version_info >= (3, 14): + def locked(self) -> bool: ... if sys.version_info >= (3, 13): @final @@ -105,6 +107,9 @@ _excepthook: Callable[[_ExceptHookArgs], Any] if sys.version_info >= (3, 12): def daemon_threads_allowed() -> bool: ... +if sys.version_info >= (3, 14): + def set_name(name: str) -> None: ... + class _local: def __getattribute__(self, name: str, /) -> Any: ... def __setattr__(self, name: str, value: Any, /) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_tkinter.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_tkinter.pyi index 4206a2114f954e..08eb00ca442bfa 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_tkinter.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_tkinter.pyi @@ -77,7 +77,7 @@ class TkappType: def globalgetvar(self, *args, **kwargs): ... def globalsetvar(self, *args, **kwargs): ... def globalunsetvar(self, *args, **kwargs): ... - def interpaddr(self): ... + def interpaddr(self) -> int: ... def loadtk(self) -> None: ... def mainloop(self, threshold: int = 0, /): ... def quit(self): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi index c37d55a7d9ec32..f322244016dd00 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/__init__.pyi @@ -298,9 +298,6 @@ class SupportsGetItemBuffer(SliceableBuffer, IndexableBuffer, Protocol): class SizedBuffer(Sized, Buffer, Protocol): ... -# for compatibility with third-party stubs that may use this -_BufferWithLen: TypeAlias = SizedBuffer # not stable # noqa: Y047 - ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] OptExcInfo: TypeAlias = ExcInfo | tuple[None, None, None] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi index 95ad6c7da8ebff..312093c0aa556e 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/argparse.pyi @@ -281,13 +281,7 @@ class HelpFormatter: if sys.version_info >= (3, 14): def __init__( - self, - prog: str, - indent_increment: int = 2, - max_help_position: int = 24, - width: int | None = None, - prefix_chars: str = "-", - color: bool = False, + self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = False ) -> None: ... else: def __init__( diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi index f26ec4d1a08be6..af9d20d086b330 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi @@ -1095,20 +1095,28 @@ if sys.version_info >= (3, 14): **kwargs: Unpack[_Attributes], ) -> Self: ... +if sys.version_info >= (3, 10): + from types import EllipsisType + + _ConstantValue: typing_extensions.TypeAlias = str | bytes | bool | int | float | complex | None | EllipsisType +else: + # Rely on builtins.ellipsis + _ConstantValue: typing_extensions.TypeAlias = str | bytes | bool | int | float | complex | None | ellipsis # noqa: F821 + class Constant(expr): if sys.version_info >= (3, 10): __match_args__ = ("value", "kind") - value: Any # None, str, bytes, bool, int, float, complex, Ellipsis + value: _ConstantValue kind: str | None if sys.version_info < (3, 14): # Aliases for value, for backwards compatibility - s: Any - n: int | float | complex + s: _ConstantValue + n: _ConstantValue - def __init__(self, value: Any, kind: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... + def __init__(self, value: _ConstantValue, kind: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): - def __replace__(self, *, value: Any = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + def __replace__(self, *, value: _ConstantValue = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Attribute(expr): if sys.version_info >= (3, 10): @@ -1429,15 +1437,19 @@ class keyword(AST): def __replace__(self, *, arg: str | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class alias(AST): - lineno: int - col_offset: int - end_lineno: int | None - end_col_offset: int | None - if sys.version_info >= (3, 10): - __match_args__ = ("name", "asname") name: str asname: str | None - def __init__(self, name: str, asname: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... + if sys.version_info >= (3, 10): + lineno: int + col_offset: int + end_lineno: int | None + end_col_offset: int | None + if sys.version_info >= (3, 10): + __match_args__ = ("name", "asname") + if sys.version_info >= (3, 10): + def __init__(self, name: str, asname: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... + else: + def __init__(self, name: str, asname: str | None = None) -> None: ... if sys.version_info >= (3, 14): def __replace__(self, *, name: str = ..., asname: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi index f9118608060e5d..68e44a88face6b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/__init__.pyi @@ -41,12 +41,14 @@ if sys.platform == "win32": "Server", # from base_events "iscoroutinefunction", # from coroutines "iscoroutine", # from coroutines - "AbstractEventLoopPolicy", # from events + "_AbstractEventLoopPolicy", # from events "AbstractEventLoop", # from events "AbstractServer", # from events "Handle", # from events "TimerHandle", # from events + "_get_event_loop_policy", # from events "get_event_loop_policy", # from events + "_set_event_loop_policy", # from events "set_event_loop_policy", # from events "get_event_loop", # from events "set_event_loop", # from events @@ -132,9 +134,9 @@ if sys.platform == "win32": "SelectorEventLoop", # from windows_events "ProactorEventLoop", # from windows_events "IocpProactor", # from windows_events - "DefaultEventLoopPolicy", # from windows_events - "WindowsSelectorEventLoopPolicy", # from windows_events - "WindowsProactorEventLoopPolicy", # from windows_events + "_DefaultEventLoopPolicy", # from windows_events + "_WindowsSelectorEventLoopPolicy", # from windows_events + "_WindowsProactorEventLoopPolicy", # from windows_events "EventLoop", # from windows_events ) elif sys.version_info >= (3, 13): @@ -515,12 +517,14 @@ else: "Server", # from base_events "iscoroutinefunction", # from coroutines "iscoroutine", # from coroutines - "AbstractEventLoopPolicy", # from events + "_AbstractEventLoopPolicy", # from events "AbstractEventLoop", # from events "AbstractServer", # from events "Handle", # from events "TimerHandle", # from events + "_get_event_loop_policy", # from events "get_event_loop_policy", # from events + "_set_event_loop_policy", # from events "set_event_loop_policy", # from events "get_event_loop", # from events "set_event_loop", # from events @@ -606,7 +610,7 @@ else: "DatagramTransport", # from transports "SubprocessTransport", # from transports "SelectorEventLoop", # from unix_events - "DefaultEventLoopPolicy", # from unix_events + "_DefaultEventLoopPolicy", # from unix_events "EventLoop", # from unix_events ) elif sys.version_info >= (3, 13): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi index af43d2f5937dc6..688ef3ed087948 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/events.pyi @@ -28,12 +28,14 @@ if sys.version_info < (3, 14): # Keep asyncio.__all__ updated with any changes to __all__ here if sys.version_info >= (3, 14): __all__ = ( - "AbstractEventLoopPolicy", + "_AbstractEventLoopPolicy", "AbstractEventLoop", "AbstractServer", "Handle", "TimerHandle", + "_get_event_loop_policy", "get_event_loop_policy", + "_set_event_loop_policy", "set_event_loop_policy", "get_event_loop", "set_event_loop", @@ -600,7 +602,7 @@ class AbstractEventLoop: @abstractmethod async def shutdown_default_executor(self) -> None: ... -class AbstractEventLoopPolicy: +class _AbstractEventLoopPolicy: @abstractmethod def get_event_loop(self) -> AbstractEventLoop: ... @abstractmethod @@ -622,13 +624,33 @@ class AbstractEventLoopPolicy: @abstractmethod def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... -class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy, metaclass=ABCMeta): - def get_event_loop(self) -> AbstractEventLoop: ... - def set_event_loop(self, loop: AbstractEventLoop | None) -> None: ... - def new_event_loop(self) -> AbstractEventLoop: ... +if sys.version_info < (3, 14): + AbstractEventLoopPolicy = _AbstractEventLoopPolicy + +if sys.version_info >= (3, 14): + class _BaseDefaultEventLoopPolicy(_AbstractEventLoopPolicy, metaclass=ABCMeta): + def get_event_loop(self) -> AbstractEventLoop: ... + def set_event_loop(self, loop: AbstractEventLoop | None) -> None: ... + def new_event_loop(self) -> AbstractEventLoop: ... + +else: + class BaseDefaultEventLoopPolicy(_AbstractEventLoopPolicy, metaclass=ABCMeta): + def get_event_loop(self) -> AbstractEventLoop: ... + def set_event_loop(self, loop: AbstractEventLoop | None) -> None: ... + def new_event_loop(self) -> AbstractEventLoop: ... + +if sys.version_info >= (3, 14): + def _get_event_loop_policy() -> _AbstractEventLoopPolicy: ... + def _set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... + @deprecated("Deprecated as of Python 3.14; will be removed in Python 3.16") + def get_event_loop_policy() -> _AbstractEventLoopPolicy: ... + @deprecated("Deprecated as of Python 3.14; will be removed in Python 3.16") + def set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... + +else: + def get_event_loop_policy() -> _AbstractEventLoopPolicy: ... + def set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... -def get_event_loop_policy() -> AbstractEventLoopPolicy: ... -def set_event_loop_policy(policy: AbstractEventLoopPolicy | None) -> None: ... def set_event_loop(loop: AbstractEventLoop | None) -> None: ... def new_event_loop() -> AbstractEventLoop: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi index a63de66f02e67a..644d2d0e94cabf 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/futures.pyi @@ -8,6 +8,8 @@ from .events import AbstractEventLoop # Keep asyncio.__all__ updated with any changes to __all__ here if sys.version_info >= (3, 14): + from _asyncio import future_add_to_awaited_by, future_discard_from_awaited_by + __all__ = ("Future", "wrap_future", "isfuture", "future_discard_from_awaited_by", "future_add_to_awaited_by") else: __all__ = ("Future", "wrap_future", "isfuture") @@ -19,7 +21,3 @@ _T = TypeVar("_T") # That's why the import order is reversed. def isfuture(obj: object) -> TypeIs[Future[Any]]: ... def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]: ... - -if sys.version_info >= (3, 14): - def future_discard_from_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... - def future_add_to_awaited_by(future: Future[Any], waiter: Future[Any], /) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/unix_events.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/unix_events.pyi index 79f99fbe37f028..49f200dcdcae7b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/unix_events.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/unix_events.pyi @@ -7,8 +7,8 @@ from socket import socket from typing import Literal from typing_extensions import Self, TypeVarTuple, Unpack, deprecated +from . import events from .base_events import Server, _ProtocolFactory, _SSLContext -from .events import AbstractEventLoop, BaseDefaultEventLoopPolicy from .selector_events import BaseSelectorEventLoop _Ts = TypeVarTuple("_Ts") @@ -16,7 +16,7 @@ _Ts = TypeVarTuple("_Ts") # Keep asyncio.__all__ updated with any changes to __all__ here if sys.platform != "win32": if sys.version_info >= (3, 14): - __all__ = ("SelectorEventLoop", "DefaultEventLoopPolicy", "EventLoop") + __all__ = ("SelectorEventLoop", "_DefaultEventLoopPolicy", "EventLoop") elif sys.version_info >= (3, 13): # Adds EventLoop __all__ = ( @@ -57,7 +57,7 @@ if sys.version_info < (3, 14): @abstractmethod def remove_child_handler(self, pid: int) -> bool: ... @abstractmethod - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... @abstractmethod def close(self) -> None: ... @abstractmethod @@ -78,7 +78,7 @@ if sys.version_info < (3, 14): @abstractmethod def remove_child_handler(self, pid: int) -> bool: ... @abstractmethod - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... @abstractmethod def close(self) -> None: ... @abstractmethod @@ -98,7 +98,7 @@ if sys.platform != "win32": class BaseChildWatcher(AbstractChildWatcher, metaclass=ABCMeta): def close(self) -> None: ... def is_active(self) -> bool: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") class SafeChildWatcher(BaseChildWatcher): @@ -128,7 +128,7 @@ if sys.platform != "win32": class BaseChildWatcher(AbstractChildWatcher, metaclass=ABCMeta): def close(self) -> None: ... def is_active(self) -> bool: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... class SafeChildWatcher(BaseChildWatcher): def __enter__(self) -> Self: ... @@ -166,8 +166,10 @@ if sys.platform != "win32": cleanup_socket: bool = True, ) -> Server: ... - class _UnixDefaultEventLoopPolicy(BaseDefaultEventLoopPolicy): - if sys.version_info < (3, 14): + if sys.version_info >= (3, 14): + class _UnixDefaultEventLoopPolicy(events._BaseDefaultEventLoopPolicy): ... + else: + class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy): if sys.version_info >= (3, 12): @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") def get_child_watcher(self) -> AbstractChildWatcher: ... @@ -179,7 +181,10 @@ if sys.platform != "win32": SelectorEventLoop = _UnixSelectorEventLoop - DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy + if sys.version_info >= (3, 14): + _DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy + else: + DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy if sys.version_info >= (3, 13): EventLoop = SelectorEventLoop @@ -198,7 +203,7 @@ if sys.platform != "win32": self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... else: class MultiLoopChildWatcher(AbstractChildWatcher): @@ -212,7 +217,7 @@ if sys.platform != "win32": self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... if sys.version_info < (3, 14): class ThreadedChildWatcher(AbstractChildWatcher): @@ -227,7 +232,7 @@ if sys.platform != "win32": self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... class PidfdChildWatcher(AbstractChildWatcher): def __enter__(self) -> Self: ... @@ -236,7 +241,7 @@ if sys.platform != "win32": ) -> None: ... def is_active(self) -> bool: ... def close(self) -> None: ... - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + def attach_loop(self, loop: events.AbstractEventLoop | None) -> None: ... def add_child_handler( self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] ) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/windows_events.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/windows_events.pyi index 2ffc2eccb228ad..b454aca1f26284 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/windows_events.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/windows_events.pyi @@ -8,7 +8,17 @@ from . import events, futures, proactor_events, selector_events, streams, window # Keep asyncio.__all__ updated with any changes to __all__ here if sys.platform == "win32": - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + __all__ = ( + "SelectorEventLoop", + "ProactorEventLoop", + "IocpProactor", + "_DefaultEventLoopPolicy", + "_WindowsSelectorEventLoopPolicy", + "_WindowsProactorEventLoopPolicy", + "EventLoop", + ) + elif sys.version_info >= (3, 13): # 3.13 added `EventLoop`. __all__ = ( "SelectorEventLoop", @@ -85,17 +95,27 @@ if sys.platform == "win32": SelectorEventLoop = _WindowsSelectorEventLoop - class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): - _loop_factory: ClassVar[type[SelectorEventLoop]] - if sys.version_info < (3, 14): + if sys.version_info >= (3, 14): + class _WindowsSelectorEventLoopPolicy(events._BaseDefaultEventLoopPolicy): + _loop_factory: ClassVar[type[SelectorEventLoop]] + + class _WindowsProactorEventLoopPolicy(events._BaseDefaultEventLoopPolicy): + _loop_factory: ClassVar[type[ProactorEventLoop]] + + else: + class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): + _loop_factory: ClassVar[type[SelectorEventLoop]] def get_child_watcher(self) -> NoReturn: ... def set_child_watcher(self, watcher: Any) -> NoReturn: ... - class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): - _loop_factory: ClassVar[type[ProactorEventLoop]] - def get_child_watcher(self) -> NoReturn: ... - def set_child_watcher(self, watcher: Any) -> NoReturn: ... + class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): + _loop_factory: ClassVar[type[ProactorEventLoop]] + def get_child_watcher(self) -> NoReturn: ... + def set_child_watcher(self, watcher: Any) -> NoReturn: ... - DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy + if sys.version_info >= (3, 14): + _DefaultEventLoopPolicy = _WindowsProactorEventLoopPolicy + else: + DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy if sys.version_info >= (3, 13): EventLoop = ProactorEventLoop diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi index ad6994cf605b3f..8de58f1a3d439d 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi @@ -6,7 +6,6 @@ import types from _collections_abc import dict_items, dict_keys, dict_values from _typeshed import ( AnnotationForm, - AnyStr_co, ConvertibleToFloat, ConvertibleToInt, FileDescriptorOrPath, @@ -33,6 +32,7 @@ from _typeshed import ( ) from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper +from os import PathLike from types import CellType, CodeType, GenericAlias, TracebackType # mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} @@ -154,6 +154,9 @@ class staticmethod(Generic[_P, _R_co]): @property def __wrapped__(self) -> Callable[_P, _R_co]: ... def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... + if sys.version_info >= (3, 14): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + __annotate__: AnnotateFunc | None class classmethod(Generic[_T, _P, _R_co]): @property @@ -170,6 +173,9 @@ class classmethod(Generic[_T, _P, _R_co]): __qualname__: str @property def __wrapped__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... + if sys.version_info >= (3, 14): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + __annotate__: AnnotateFunc | None class type: # object.__base__ is None. Otherwise, it would be a type. @@ -325,7 +331,11 @@ class int: def __trunc__(self) -> int: ... def __ceil__(self) -> int: ... def __floor__(self) -> int: ... - def __round__(self, ndigits: SupportsIndex = ..., /) -> int: ... + if sys.version_info >= (3, 14): + def __round__(self, ndigits: SupportsIndex | None = None, /) -> int: ... + else: + def __round__(self, ndigits: SupportsIndex = ..., /) -> int: ... + def __getnewargs__(self) -> tuple[int]: ... def __eq__(self, value: object, /) -> bool: ... def __ne__(self, value: object, /) -> bool: ... @@ -400,6 +410,9 @@ class float: def __abs__(self) -> float: ... def __hash__(self) -> int: ... def __bool__(self) -> bool: ... + if sys.version_info >= (3, 14): + @classmethod + def from_number(cls, number: float | SupportsIndex | SupportsFloat, /) -> Self: ... class complex: # Python doesn't currently accept SupportsComplex for the second argument @@ -435,6 +448,9 @@ class complex: def __bool__(self) -> bool: ... if sys.version_info >= (3, 11): def __complex__(self) -> complex: ... + if sys.version_info >= (3, 14): + @classmethod + def from_number(cls, number: complex | SupportsComplex | SupportsFloat | SupportsIndex, /) -> Self: ... class _FormatMapMapping(Protocol): def __getitem__(self, key: str, /) -> Any: ... @@ -832,6 +848,8 @@ class bytearray(MutableSequence[int]): def __alloc__(self) -> int: ... def __buffer__(self, flags: int, /) -> memoryview: ... def __release_buffer__(self, buffer: memoryview, /) -> None: ... + if sys.version_info >= (3, 14): + def resize(self, size: int, /) -> None: ... _IntegerFormats: TypeAlias = Literal[ "b", "B", "@b", "@B", "h", "H", "@h", "@H", "i", "I", "@i", "@I", "l", "L", "@l", "@L", "q", "Q", "@q", "@Q", "P", "@P" @@ -909,6 +927,8 @@ class memoryview(Sequence[_I]): # See https://github.com/python/cpython/issues/125420 index: ClassVar[None] # type: ignore[assignment] count: ClassVar[None] # type: ignore[assignment] + if sys.version_info >= (3, 14): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @final class bool(int): @@ -940,7 +960,7 @@ class bool(int): @overload def __rxor__(self, value: int, /) -> int: ... def __getnewargs__(self) -> tuple[int]: ... - @deprecated("Will throw an error in Python 3.14. Use `not` for logical negation of bools instead.") + @deprecated("Will throw an error in Python 3.16. Use `not` for logical negation of bools instead.") def __invert__(self) -> int: ... @final @@ -1028,7 +1048,7 @@ class function: __annotations__: dict[str, AnnotationForm] if sys.version_info >= (3, 14): __annotate__: AnnotateFunc | None - __kwdefaults__: dict[str, Any] + __kwdefaults__: dict[str, Any] | None if sys.version_info >= (3, 10): @property def __builtins__(self) -> dict[str, Any]: ... @@ -1036,6 +1056,26 @@ class function: __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] __module__: str + if sys.version_info >= (3, 13): + def __new__( + cls, + code: CodeType, + globals: dict[str, Any], + name: str | None = None, + argdefs: tuple[object, ...] | None = None, + closure: tuple[CellType, ...] | None = None, + kwdefaults: dict[str, object] | None = None, + ) -> Self: ... + else: + def __new__( + cls, + code: CodeType, + globals: dict[str, Any], + name: str | None = None, + argdefs: tuple[object, ...] | None = None, + closure: tuple[CellType, ...] | None = None, + ) -> Self: ... + # mypy uses `builtins.function.__get__` to represent methods, properties, and getset_descriptors so we type the return as Any. def __get__(self, instance: object, owner: type | None = None, /) -> Any: ... @@ -1313,11 +1353,6 @@ def breakpoint(*args: Any, **kws: Any) -> None: ... def callable(obj: object, /) -> TypeIs[Callable[..., object]]: ... def chr(i: int | SupportsIndex, /) -> str: ... -# We define this here instead of using os.PathLike to avoid import cycle issues. -# See https://github.com/python/typeshed/pull/991#issuecomment-288160993 -class _PathLike(Protocol[AnyStr_co]): - def __fspath__(self) -> AnyStr_co: ... - if sys.version_info >= (3, 10): def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co: ... @@ -1338,7 +1373,7 @@ if sys.version_info >= (3, 10): @overload def compile( source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], + filename: str | ReadableBuffer | PathLike[Any], mode: str, flags: Literal[0], dont_inherit: bool = False, @@ -1349,7 +1384,7 @@ def compile( @overload def compile( source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], + filename: str | ReadableBuffer | PathLike[Any], mode: str, *, dont_inherit: bool = False, @@ -1359,7 +1394,7 @@ def compile( @overload def compile( source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], + filename: str | ReadableBuffer | PathLike[Any], mode: str, flags: Literal[1024], dont_inherit: bool = False, @@ -1370,7 +1405,7 @@ def compile( @overload def compile( source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], + filename: str | ReadableBuffer | PathLike[Any], mode: str, flags: int, dont_inherit: bool = False, @@ -2160,27 +2195,27 @@ if sys.version_info >= (3, 11): def exceptions(self) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: ... @overload def subgroup( - self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / + self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> ExceptionGroup[_ExceptionT] | None: ... @overload def subgroup( - self, condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / + self, matcher_value: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / ) -> BaseExceptionGroup[_BaseExceptionT] | None: ... @overload def subgroup( - self, condition: Callable[[_BaseExceptionT_co | Self], bool], / + self, matcher_value: Callable[[_BaseExceptionT_co | Self], bool], / ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ... @overload def split( - self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / + self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> tuple[ExceptionGroup[_ExceptionT] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... @overload def split( - self, condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / + self, matcher_value: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / ) -> tuple[BaseExceptionGroup[_BaseExceptionT] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... @overload def split( - self, condition: Callable[[_BaseExceptionT_co | Self], bool], / + self, matcher_value: Callable[[_BaseExceptionT_co | Self], bool], / ) -> tuple[BaseExceptionGroup[_BaseExceptionT_co] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... # In reality it is `NonEmptySequence`: @overload @@ -2197,17 +2232,19 @@ if sys.version_info >= (3, 11): # We accept a narrower type, but that's OK. @overload # type: ignore[override] def subgroup( - self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / + self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> ExceptionGroup[_ExceptionT] | None: ... @overload - def subgroup(self, condition: Callable[[_ExceptionT_co | Self], bool], /) -> ExceptionGroup[_ExceptionT_co] | None: ... + def subgroup( + self, matcher_value: Callable[[_ExceptionT_co | Self], bool], / + ) -> ExceptionGroup[_ExceptionT_co] | None: ... @overload # type: ignore[override] def split( - self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / + self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> tuple[ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None]: ... @overload def split( - self, condition: Callable[[_ExceptionT_co | Self], bool], / + self, matcher_value: Callable[[_ExceptionT_co | Self], bool], / ) -> tuple[ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None]: ... if sys.version_info >= (3, 13): diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi index c1a29e6b055213..9c1078983d8cf6 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/concurrent/futures/interpreter.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Mapping from concurrent.futures import ThreadPoolExecutor -from typing import Final, Literal, Protocol, overload, type_check_only +from typing import Literal, Protocol, overload, type_check_only from typing_extensions import ParamSpec, Self, TypeAlias, TypeVar, TypeVarTuple, Unpack _Task: TypeAlias = tuple[bytes, Literal["function", "script"]] @@ -37,8 +37,6 @@ if sys.version_info >= (3, 14): class ExecutionFailed(InterpreterError): def __init__(self, excinfo: _ExcInfo) -> None: ... # type: ignore[override] - UNBOUND: Final = 2 - class WorkerContext(ThreadWorkerContext): # Parent class doesn't have `shared` argument, @overload # type: ignore[override] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi index 68b75b86def15c..0b14bd856784c0 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/__init__.pyi @@ -31,6 +31,9 @@ from typing_extensions import Self, TypeAlias, deprecated if sys.platform == "win32": from _ctypes import FormatError as FormatError, get_last_error as get_last_error, set_last_error as set_last_error + if sys.version_info >= (3, 14): + from _ctypes import COMError as COMError + if sys.version_info >= (3, 11): from ctypes._endian import BigEndianUnion as BigEndianUnion, LittleEndianUnion as LittleEndianUnion @@ -197,8 +200,13 @@ if sys.platform == "win32": def wstring_at(ptr: _CVoidConstPLike, size: int = -1) -> str: ... +if sys.version_info >= (3, 14): + def memoryview_at(ptr: _CVoidConstPLike, size: int, readonly: bool = False) -> memoryview: ... + class py_object(_CanCastTo, _SimpleCData[_T]): _type_: ClassVar[Literal["O"]] + if sys.version_info >= (3, 14): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class c_bool(_SimpleCData[bool]): _type_: ClassVar[Literal["?"]] @@ -270,16 +278,16 @@ class c_double(_SimpleCData[float]): class c_longdouble(_SimpleCData[float]): # can be an alias for c_double _type_: ClassVar[Literal["d", "g"]] -if sys.version_info >= (3, 14): - class c_float_complex(_SimpleCData[complex]): - _type_: ClassVar[Literal["E"]] - +if sys.version_info >= (3, 14) and sys.platform != "win32": class c_double_complex(_SimpleCData[complex]): - _type_: ClassVar[Literal["C"]] + _type_: ClassVar[Literal["D"]] - class c_longdouble_complex(_SimpleCData[complex]): + class c_float_complex(_SimpleCData[complex]): _type_: ClassVar[Literal["F"]] + class c_longdouble_complex(_SimpleCData[complex]): + _type_: ClassVar[Literal["G"]] + class c_char(_SimpleCData[bytes]): _type_: ClassVar[Literal["c"]] def __init__(self, value: int | bytes | bytearray = ...) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/util.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/util.pyi index 316f7a2b3e2f56..4f18c1d8db3452 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/util.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/util.pyi @@ -5,4 +5,7 @@ def find_library(name: str) -> str | None: ... if sys.platform == "win32": def find_msvcrt() -> str | None: ... +if sys.version_info >= (3, 14): + def dllist() -> list[str]: ... + def test() -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/wintypes.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/wintypes.pyi index 63f117787aa0b3..e9ed0df24dd137 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/wintypes.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ctypes/wintypes.pyi @@ -83,6 +83,15 @@ HACCEL = HANDLE HBITMAP = HANDLE HBRUSH = HANDLE HCOLORSPACE = HANDLE +if sys.version_info >= (3, 14): + HCONV = HANDLE + HCONVLIST = HANDLE + HCURSOR = HANDLE + HDDEDATA = HANDLE + HDROP = HANDLE + HFILE = INT + HRESULT = LONG + HSZ = HANDLE HDC = HANDLE HDESK = HANDLE HDWP = HANDLE diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi index bba76c1af1b4b0..c76b0b0e61e277 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi @@ -71,14 +71,28 @@ def asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, An def astuple(obj: DataclassInstance) -> tuple[Any, ...]: ... @overload def astuple(obj: DataclassInstance, *, tuple_factory: Callable[[list[Any]], _T]) -> _T: ... -@overload -def dataclass(cls: None, /) -> Callable[[type[_T]], type[_T]]: ... -@overload -def dataclass(cls: type[_T], /) -> type[_T]: ... if sys.version_info >= (3, 11): @overload def dataclass( + cls: type[_T], + /, + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + match_args: bool = True, + kw_only: bool = False, + slots: bool = False, + weakref_slot: bool = False, + ) -> type[_T]: ... + @overload + def dataclass( + cls: None = None, + /, *, init: bool = True, repr: bool = True, @@ -95,6 +109,23 @@ if sys.version_info >= (3, 11): elif sys.version_info >= (3, 10): @overload def dataclass( + cls: type[_T], + /, + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + match_args: bool = True, + kw_only: bool = False, + slots: bool = False, + ) -> type[_T]: ... + @overload + def dataclass( + cls: None = None, + /, *, init: bool = True, repr: bool = True, @@ -110,6 +141,20 @@ elif sys.version_info >= (3, 10): else: @overload def dataclass( + cls: type[_T], + /, + *, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + ) -> type[_T]: ... + @overload + def dataclass( + cls: None = None, + /, *, init: bool = True, repr: bool = True, @@ -308,7 +353,7 @@ def is_dataclass(obj: object) -> TypeIs[DataclassInstance | type[DataclassInstan class FrozenInstanceError(AttributeError): ... -class InitVar(Generic[_T], metaclass=type): +class InitVar(Generic[_T]): type: Type[_T] def __init__(self, type: Type[_T]) -> None: ... @overload diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/cmd.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/cmd.pyi index a4e77ddf138820..7f97bc3a2c9e05 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/cmd.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/cmd.pyi @@ -1,4 +1,4 @@ -from _typeshed import BytesPath, Incomplete, StrOrBytesPath, StrPath, Unused +from _typeshed import BytesPath, StrOrBytesPath, StrPath, Unused from abc import abstractmethod from collections.abc import Callable, Iterable from distutils.command.bdist import bdist @@ -226,4 +226,4 @@ class Command: level: Unused = 1, ) -> None: ... def ensure_finalized(self) -> None: ... - def dump_options(self, header: Incomplete | None = None, indent: str = "") -> None: ... + def dump_options(self, header=None, indent: str = "") -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/config.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/config.pyi index 562ff3a5271f84..381e8e466bf16a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/config.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/config.pyi @@ -1,4 +1,4 @@ -from _typeshed import Incomplete, StrOrBytesPath +from _typeshed import StrOrBytesPath from collections.abc import Sequence from re import Pattern from typing import ClassVar, Final, Literal @@ -81,4 +81,4 @@ class config(Command): self, header: str, include_dirs: Sequence[str] | None = None, library_dirs: Sequence[str] | None = None, lang: str = "c" ) -> bool: ... -def dump_file(filename: StrOrBytesPath, head: Incomplete | None = None) -> None: ... +def dump_file(filename: StrOrBytesPath, head=None) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/register.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/register.pyi index cf98e178a9ba1c..c3bd62aaa7aa07 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/register.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/command/register.pyi @@ -1,4 +1,3 @@ -from _typeshed import Incomplete from collections.abc import Callable from typing import Any, ClassVar @@ -18,4 +17,4 @@ class register(PyPIRCCommand): def verify_metadata(self) -> None: ... def send_metadata(self) -> None: ... def build_post_data(self, action): ... - def post_to_server(self, data, auth: Incomplete | None = None): ... + def post_to_server(self, data, auth=None): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/dist.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/dist.pyi index 09f2b456d26353..412b94131b54ef 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/distutils/dist.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/distutils/dist.pyi @@ -112,9 +112,7 @@ class Distribution: command_obj: Incomplete have_run: Incomplete want_user_cfg: bool - def dump_option_dicts( - self, header: Incomplete | None = None, commands: Incomplete | None = None, indent: str = "" - ) -> None: ... + def dump_option_dicts(self, header=None, commands=None, indent: str = "") -> None: ... def find_config_files(self): ... commands: Incomplete def parse_command_line(self): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/enum.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/enum.pyi index 26f19886711328..327b135459a00a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/enum.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/enum.pyi @@ -53,6 +53,7 @@ _EnumerationT = TypeVar("_EnumerationT", bound=type[Enum]) # >>> Enum('Foo', names={'RED': 1, 'YELLOW': 2}) # _EnumNames: TypeAlias = str | Iterable[str] | Iterable[Iterable[str | Any]] | Mapping[str, Any] +_Signature: TypeAlias = Any # TODO: Unable to import Signature from inspect module if sys.version_info >= (3, 11): class nonmember(Generic[_EnumMemberT]): @@ -166,6 +167,9 @@ class EnumMeta(type): if sys.version_info >= (3, 12): @overload def __call__(cls: type[_EnumMemberT], value: Any, *values: Any) -> _EnumMemberT: ... + if sys.version_info >= (3, 14): + @property + def __signature__(cls) -> _Signature: ... _member_names_: list[str] # undocumented _member_map_: dict[str, Enum] # undocumented @@ -212,7 +216,7 @@ class Enum(metaclass=EnumMeta): if sys.version_info >= (3, 11): def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 12) and sys.version_info < (3, 14): @classmethod def __signature__(cls) -> str: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/errno.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/errno.pyi index 84d2b44a6a61b2..3ba8b66d286501 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/errno.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/errno.pyi @@ -170,6 +170,9 @@ if sys.platform != "win32" and sys.platform != "darwin": ENOMEDIUM: int ERFKILL: int + if sys.version_info >= (3, 14): + EHWPOISON: int + if sys.platform == "win32": # All of these are undocumented WSABASEERR: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/faulthandler.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/faulthandler.pyi index 320a8b6fad1503..8f93222c9936e2 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/faulthandler.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/faulthandler.pyi @@ -4,6 +4,10 @@ from _typeshed import FileDescriptorLike def cancel_dump_traceback_later() -> None: ... def disable() -> None: ... def dump_traceback(file: FileDescriptorLike = ..., all_threads: bool = ...) -> None: ... + +if sys.version_info >= (3, 14): + def dump_c_stack(file: FileDescriptorLike = ...) -> None: ... + def dump_traceback_later(timeout: float, repeat: bool = ..., file: FileDescriptorLike = ..., exit: bool = ...) -> None: ... def enable(file: FileDescriptorLike = ..., all_threads: bool = ...) -> None: ... def is_enabled() -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi index 4d5c2160e60a15..83592eb583366b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi @@ -145,3 +145,6 @@ class Fraction(Rational): @property def imag(self) -> Literal[0]: ... def conjugate(self) -> Fraction: ... + if sys.version_info >= (3, 14): + @classmethod + def from_number(cls, number: float | Rational | _ConvertibleToIntegerRatio) -> Self: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/abc.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/abc.pyi index 3016a3a43b36ad..cf0fd0807b7b8a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/abc.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/abc.pyi @@ -113,63 +113,71 @@ class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader def get_filename(self, name: str | None = None) -> str: ... def load_module(self, name: str | None = None) -> types.ModuleType: ... -class ResourceReader(metaclass=ABCMeta): - @abstractmethod - def open_resource(self, resource: str) -> IO[bytes]: ... - @abstractmethod - def resource_path(self, resource: str) -> str: ... - if sys.version_info >= (3, 10): +if sys.version_info < (3, 11): + class ResourceReader(metaclass=ABCMeta): @abstractmethod - def is_resource(self, path: str) -> bool: ... - else: + def open_resource(self, resource: str) -> IO[bytes]: ... @abstractmethod - def is_resource(self, name: str) -> bool: ... + def resource_path(self, resource: str) -> str: ... + if sys.version_info >= (3, 10): + @abstractmethod + def is_resource(self, path: str) -> bool: ... + else: + @abstractmethod + def is_resource(self, name: str) -> bool: ... - @abstractmethod - def contents(self) -> Iterator[str]: ... + @abstractmethod + def contents(self) -> Iterator[str]: ... -@runtime_checkable -class Traversable(Protocol): - @abstractmethod - def is_dir(self) -> bool: ... - @abstractmethod - def is_file(self) -> bool: ... - @abstractmethod - def iterdir(self) -> Iterator[Traversable]: ... - if sys.version_info >= (3, 11): + @runtime_checkable + class Traversable(Protocol): @abstractmethod - def joinpath(self, *descendants: str) -> Traversable: ... - else: + def is_dir(self) -> bool: ... @abstractmethod - def joinpath(self, child: str, /) -> Traversable: ... - - # The documentation and runtime protocol allows *args, **kwargs arguments, - # but this would mean that all implementers would have to support them, - # which is not the case. - @overload - @abstractmethod - def open(self, mode: Literal["r"] = "r", *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... - @overload - @abstractmethod - def open(self, mode: Literal["rb"]) -> IO[bytes]: ... - @property - @abstractmethod - def name(self) -> str: ... - if sys.version_info >= (3, 10): - def __truediv__(self, child: str, /) -> Traversable: ... - else: + def is_file(self) -> bool: ... + @abstractmethod + def iterdir(self) -> Iterator[Traversable]: ... + if sys.version_info >= (3, 11): + @abstractmethod + def joinpath(self, *descendants: str) -> Traversable: ... + else: + @abstractmethod + def joinpath(self, child: str, /) -> Traversable: ... + + # The documentation and runtime protocol allows *args, **kwargs arguments, + # but this would mean that all implementers would have to support them, + # which is not the case. + @overload + @abstractmethod + def open(self, mode: Literal["r"] = "r", *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... + @overload @abstractmethod - def __truediv__(self, child: str, /) -> Traversable: ... + def open(self, mode: Literal["rb"]) -> IO[bytes]: ... + @property + @abstractmethod + def name(self) -> str: ... + if sys.version_info >= (3, 10): + def __truediv__(self, child: str, /) -> Traversable: ... + else: + @abstractmethod + def __truediv__(self, child: str, /) -> Traversable: ... - @abstractmethod - def read_bytes(self) -> bytes: ... - @abstractmethod - def read_text(self, encoding: str | None = None) -> str: ... + @abstractmethod + def read_bytes(self) -> bytes: ... + @abstractmethod + def read_text(self, encoding: str | None = None) -> str: ... -class TraversableResources(ResourceReader): - @abstractmethod - def files(self) -> Traversable: ... - def open_resource(self, resource: str) -> BufferedReader: ... - def resource_path(self, resource: Any) -> str: ... - def is_resource(self, path: str) -> bool: ... - def contents(self) -> Iterator[str]: ... + class TraversableResources(ResourceReader): + @abstractmethod + def files(self) -> Traversable: ... + def open_resource(self, resource: str) -> BufferedReader: ... + def resource_path(self, resource: Any) -> str: ... + def is_resource(self, path: str) -> bool: ... + def contents(self) -> Iterator[str]: ... + +elif sys.version_info < (3, 14): + from importlib.resources.abc import ( + ResourceReader as ResourceReader, + Traversable as Traversable, + TraversableResources as TraversableResources, + ) diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/machinery.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/machinery.pyi index bb1a6f93d0e096..767046b70a3d19 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/machinery.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/machinery.pyi @@ -16,5 +16,28 @@ from importlib._bootstrap_external import ( if sys.version_info >= (3, 11): from importlib._bootstrap_external import NamespaceLoader as NamespaceLoader +if sys.version_info >= (3, 14): + from importlib._bootstrap_external import AppleFrameworkLoader as AppleFrameworkLoader def all_suffixes() -> list[str]: ... + +if sys.version_info >= (3, 14): + __all__ = [ + "AppleFrameworkLoader", + "BYTECODE_SUFFIXES", + "BuiltinImporter", + "DEBUG_BYTECODE_SUFFIXES", + "EXTENSION_SUFFIXES", + "ExtensionFileLoader", + "FileFinder", + "FrozenImporter", + "ModuleSpec", + "NamespaceLoader", + "OPTIMIZED_BYTECODE_SUFFIXES", + "PathFinder", + "SOURCE_SUFFIXES", + "SourceFileLoader", + "SourcelessFileLoader", + "WindowsRegistryFinder", + "all_suffixes", + ] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/__init__.pyi index 2cf6366b6cb3ba..e672a619bd17a8 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/__init__.pyi @@ -2,12 +2,16 @@ import os import sys from collections.abc import Iterator from contextlib import AbstractContextManager -from importlib.abc import Traversable from pathlib import Path from types import ModuleType from typing import Any, BinaryIO, Literal, TextIO from typing_extensions import TypeAlias +if sys.version_info >= (3, 11): + from importlib.resources.abc import Traversable +else: + from importlib.abc import Traversable + if sys.version_info >= (3, 11): from importlib.resources._common import Package as Package else: @@ -72,5 +76,7 @@ if sys.version_info >= (3, 11): else: def files(package: Package) -> Traversable: ... -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 11): + from importlib.resources.abc import ResourceReader as ResourceReader +elif sys.version_info >= (3, 10): from importlib.abc import ResourceReader as ResourceReader diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/_common.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/_common.pyi index d6a9436544dce6..3dd961bb657b1b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/_common.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/_common.pyi @@ -5,7 +5,7 @@ if sys.version_info >= (3, 11): import types from collections.abc import Callable from contextlib import AbstractContextManager - from importlib.abc import ResourceReader, Traversable + from importlib.resources.abc import ResourceReader, Traversable from pathlib import Path from typing import Literal, overload from typing_extensions import TypeAlias, deprecated diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/abc.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/abc.pyi index ad80605f7c71d3..fe0fe64dba0dfe 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/abc.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/resources/abc.pyi @@ -1,14 +1,69 @@ import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Iterator +from io import BufferedReader +from typing import IO, Any, Literal, Protocol, overload, runtime_checkable if sys.version_info >= (3, 11): - # These are all actually defined in this file on 3.11+, - # and re-exported from importlib.abc, - # but it's much less code duplication for typeshed if we pretend that they're still defined - # in importlib.abc on 3.11+, and re-exported from this file - from importlib.abc import ( - ResourceReader as ResourceReader, - Traversable as Traversable, - TraversableResources as TraversableResources, - ) + class ResourceReader(metaclass=ABCMeta): + @abstractmethod + def open_resource(self, resource: str) -> IO[bytes]: ... + @abstractmethod + def resource_path(self, resource: str) -> str: ... + if sys.version_info >= (3, 10): + @abstractmethod + def is_resource(self, path: str) -> bool: ... + else: + @abstractmethod + def is_resource(self, name: str) -> bool: ... + + @abstractmethod + def contents(self) -> Iterator[str]: ... + + @runtime_checkable + class Traversable(Protocol): + @abstractmethod + def is_dir(self) -> bool: ... + @abstractmethod + def is_file(self) -> bool: ... + @abstractmethod + def iterdir(self) -> Iterator[Traversable]: ... + if sys.version_info >= (3, 11): + @abstractmethod + def joinpath(self, *descendants: str) -> Traversable: ... + else: + @abstractmethod + def joinpath(self, child: str, /) -> Traversable: ... + + # The documentation and runtime protocol allows *args, **kwargs arguments, + # but this would mean that all implementers would have to support them, + # which is not the case. + @overload + @abstractmethod + def open(self, mode: Literal["r"] = "r", *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... + @overload + @abstractmethod + def open(self, mode: Literal["rb"]) -> IO[bytes]: ... + @property + @abstractmethod + def name(self) -> str: ... + if sys.version_info >= (3, 10): + def __truediv__(self, child: str, /) -> Traversable: ... + else: + @abstractmethod + def __truediv__(self, child: str, /) -> Traversable: ... + + @abstractmethod + def read_bytes(self) -> bytes: ... + @abstractmethod + def read_text(self, encoding: str | None = None) -> str: ... + + class TraversableResources(ResourceReader): + @abstractmethod + def files(self) -> Traversable: ... + def open_resource(self, resource: str) -> BufferedReader: ... + def resource_path(self, resource: Any) -> str: ... + def is_resource(self, path: str) -> bool: ... + def contents(self) -> Iterator[str]: ... __all__ = ["ResourceReader", "Traversable", "TraversableResources"] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/util.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/util.pyi index cc1c98ae4d0e4d..370a08623842ef 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/importlib/util.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/importlib/util.pyi @@ -1,4 +1,3 @@ -import importlib.abc import importlib.machinery import sys import types @@ -12,6 +11,7 @@ from importlib._bootstrap_external import ( source_from_cache as source_from_cache, spec_from_file_location as spec_from_file_location, ) +from importlib.abc import Loader from typing_extensions import ParamSpec _P = ParamSpec("_P") @@ -24,10 +24,26 @@ if sys.version_info < (3, 12): def resolve_name(name: str, package: str | None) -> str: ... def find_spec(name: str, package: str | None = None) -> importlib.machinery.ModuleSpec | None: ... -class LazyLoader(importlib.abc.Loader): - def __init__(self, loader: importlib.abc.Loader) -> None: ... +class LazyLoader(Loader): + def __init__(self, loader: Loader) -> None: ... @classmethod - def factory(cls, loader: importlib.abc.Loader) -> Callable[..., LazyLoader]: ... + def factory(cls, loader: Loader) -> Callable[..., LazyLoader]: ... def exec_module(self, module: types.ModuleType) -> None: ... def source_hash(source_bytes: ReadableBuffer) -> bytes: ... + +if sys.version_info >= (3, 14): + __all__ = [ + "LazyLoader", + "Loader", + "MAGIC_NUMBER", + "cache_from_source", + "decode_source", + "find_spec", + "module_from_spec", + "resolve_name", + "source_from_cache", + "source_hash", + "spec_from_file_location", + "spec_from_loader", + ] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/logging/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/logging/__init__.pyi index e555f74a81af75..24529bd48d6a7d 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/logging/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/logging/__init__.pyi @@ -373,6 +373,9 @@ class LoggerAdapter(Generic[_L]): else: extra: Mapping[str, object] + if sys.version_info >= (3, 13): + merge_extra: bool + def process(self, msg: Any, kwargs: MutableMapping[str, Any]) -> tuple[Any, MutableMapping[str, Any]]: ... def debug( self, diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi index 31b98285635543..c4af295d231615 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi @@ -1,3 +1,4 @@ +import sys from _typeshed import FileDescriptorLike, Unused from collections.abc import Sequence from struct import Struct @@ -14,13 +15,26 @@ class ForkServer: def connect_to_new_process(self, fds: Sequence[int]) -> tuple[int, int]: ... def ensure_running(self) -> None: ... -def main( - listener_fd: int | None, - alive_r: FileDescriptorLike, - preload: Sequence[str], - main_path: str | None = None, - sys_path: Unused = None, -) -> None: ... +if sys.version_info >= (3, 14): + def main( + listener_fd: int | None, + alive_r: FileDescriptorLike, + preload: Sequence[str], + main_path: str | None = None, + sys_path: list[str] | None = None, + *, + authkey_r: int | None = None, + ) -> None: ... + +else: + def main( + listener_fd: int | None, + alive_r: FileDescriptorLike, + preload: Sequence[str], + main_path: str | None = None, + sys_path: Unused = None, + ) -> None: ... + def read_signed(fd: int) -> Any: ... def write_signed(fd: int, n: int) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/managers.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/managers.pyi index 50e4f1c1fe6622..b0ccac41b9253e 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/managers.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/managers.pyi @@ -2,7 +2,17 @@ import queue import sys import threading from _typeshed import SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT -from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence +from collections.abc import ( + Callable, + Iterable, + Iterator, + Mapping, + MutableMapping, + MutableSequence, + MutableSet, + Sequence, + Set as AbstractSet, +) from types import GenericAlias, TracebackType from typing import Any, AnyStr, ClassVar, Generic, SupportsIndex, TypeVar, overload from typing_extensions import Self, TypeAlias @@ -18,6 +28,7 @@ __all__ = ["BaseManager", "SyncManager", "BaseProxy", "Token", "SharedMemoryMana _T = TypeVar("_T") _KT = TypeVar("_KT") _VT = TypeVar("_VT") +_S = TypeVar("_S") class Namespace: def __init__(self, **kwds: Any) -> None: ... @@ -111,6 +122,51 @@ else: def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override] def values(self) -> list[_VT]: ... # type: ignore[override] +if sys.version_info >= (3, 14): + class _BaseSetProxy(BaseProxy, MutableSet[_T]): + __builtins__: ClassVar[dict[str, Any]] + # Copied from builtins.set + def add(self, element: _T, /) -> None: ... + def copy(self) -> set[_T]: ... + def clear(self) -> None: ... + def difference(self, *s: Iterable[Any]) -> set[_T]: ... + def difference_update(self, *s: Iterable[Any]) -> None: ... + def discard(self, element: _T, /) -> None: ... + def intersection(self, *s: Iterable[Any]) -> set[_T]: ... + def intersection_update(self, *s: Iterable[Any]) -> None: ... + def isdisjoint(self, s: Iterable[Any], /) -> bool: ... + def issubset(self, s: Iterable[Any], /) -> bool: ... + def issuperset(self, s: Iterable[Any], /) -> bool: ... + def pop(self) -> _T: ... + def remove(self, element: _T, /) -> None: ... + def symmetric_difference(self, s: Iterable[_T], /) -> set[_T]: ... + def symmetric_difference_update(self, s: Iterable[_T], /) -> None: ... + def union(self, *s: Iterable[_S]) -> set[_T | _S]: ... + def update(self, *s: Iterable[_T]) -> None: ... + def __len__(self) -> int: ... + def __contains__(self, o: object, /) -> bool: ... + def __iter__(self) -> Iterator[_T]: ... + def __and__(self, value: AbstractSet[object], /) -> set[_T]: ... + def __iand__(self, value: AbstractSet[object], /) -> Self: ... + def __or__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... + def __ior__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] + def __sub__(self, value: AbstractSet[_T | None], /) -> set[_T]: ... + def __isub__(self, value: AbstractSet[object], /) -> Self: ... + def __xor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... + def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] + def __le__(self, value: AbstractSet[object], /) -> bool: ... + def __lt__(self, value: AbstractSet[object], /) -> bool: ... + def __ge__(self, value: AbstractSet[object], /) -> bool: ... + def __gt__(self, value: AbstractSet[object], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __rand__(self, value: AbstractSet[object], /) -> set[_T]: ... + def __ror__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... # type: ignore[misc] + def __rsub__(self, value: AbstractSet[_T], /) -> set[_T]: ... + def __rxor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... # type: ignore[misc] + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + + class SetProxy(_BaseSetProxy[_T]): ... + class BaseListProxy(BaseProxy, MutableSequence[_T]): __builtins__: ClassVar[dict[str, Any]] def __len__(self) -> int: ... @@ -273,6 +329,11 @@ class SyncManager(BaseManager): def list(self, sequence: Sequence[_T], /) -> ListProxy[_T]: ... @overload def list(self) -> ListProxy[Any]: ... + if sys.version_info >= (3, 14): + @overload + def set(self, iterable: Iterable[_T], /) -> SetProxy[_T]: ... + @overload + def set(self) -> SetProxy[Any]: ... class RemoteError(Exception): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/popen_fork.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/popen_fork.pyi index 4fcbfd99a8d0d1..5e53b055cc7971 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/popen_fork.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/popen_fork.pyi @@ -18,6 +18,9 @@ if sys.platform != "win32": def duplicate_for_child(self, fd: int) -> int: ... def poll(self, flag: int = 1) -> int | None: ... def wait(self, timeout: float | None = None) -> int | None: ... + if sys.version_info >= (3, 14): + def interrupt(self) -> None: ... + def terminate(self) -> None: ... def kill(self) -> None: ... def close(self) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/reduction.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/reduction.pyi index 942e92ce530ec7..490ae195c20e2c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/reduction.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/reduction.pyi @@ -43,7 +43,8 @@ if sys.platform == "win32": def detach(self) -> int: ... else: - ACKNOWLEDGE: Final[bool] + if sys.version_info < (3, 14): + ACKNOWLEDGE: Final[bool] def recvfds(sock: socket, size: int) -> list[int]: ... def send_handle(conn: HasFileno, handle: int, destination_pid: Unused) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/util.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/util.pyi index d5b6384afd5edb..ecb4a7ddec7d28 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/util.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/multiprocessing/util.pyi @@ -1,3 +1,4 @@ +import sys import threading from _typeshed import ConvertibleToInt, Incomplete, Unused from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence @@ -22,14 +23,19 @@ __all__ = [ "SUBWARNING", ] +if sys.version_info >= (3, 14): + __all__ += ["warn"] + _T = TypeVar("_T") _R_co = TypeVar("_R_co", default=Any, covariant=True) -NOTSET: Final[int] -SUBDEBUG: Final[int] -DEBUG: Final[int] -INFO: Final[int] -SUBWARNING: Final[int] +NOTSET: Final = 0 +SUBDEBUG: Final = 5 +DEBUG: Final = 10 +INFO: Final = 20 +SUBWARNING: Final = 25 +if sys.version_info >= (3, 14): + WARNING: Final = 30 LOGGER_NAME: Final[str] DEFAULT_LOGGING_FORMAT: Final[str] @@ -37,6 +43,10 @@ DEFAULT_LOGGING_FORMAT: Final[str] def sub_debug(msg: object, *args: object) -> None: ... def debug(msg: object, *args: object) -> None: ... def info(msg: object, *args: object) -> None: ... + +if sys.version_info >= (3, 14): + def warn(msg: object, *args: object) -> None: ... + def sub_warning(msg: object, *args: object) -> None: ... def get_logger() -> Logger: ... def log_to_stderr(level: _LoggingLevel | None = None) -> Logger: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/numbers.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/numbers.pyi index f2bca4e58bc586..02d469ce0ee54a 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/numbers.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/numbers.pyi @@ -7,7 +7,6 @@ # (since type checkers don't see `complex` as a subtype of `numbers.Complex`, # nor `float` as a subtype of `numbers.Real`, etc.) -from _typeshed import Incomplete from abc import ABCMeta, abstractmethod from typing import ClassVar, Literal, Protocol, overload @@ -166,7 +165,7 @@ class Integral(Rational, _IntegralLike): def __int__(self) -> int: ... def __index__(self) -> int: ... @abstractmethod - def __pow__(self, exponent, modulus: Incomplete | None = None) -> _IntegralLike: ... + def __pow__(self, exponent, modulus=None) -> _IntegralLike: ... @abstractmethod def __lshift__(self, other) -> _IntegralLike: ... @abstractmethod diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/pyexpat/errors.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/pyexpat/errors.pyi index cae4da089161fe..493ae034560444 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/pyexpat/errors.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/pyexpat/errors.pyi @@ -49,3 +49,5 @@ if sys.version_info >= (3, 11): XML_ERROR_INVALID_ARGUMENT: Final[LiteralString] XML_ERROR_NO_BUFFER: Final[LiteralString] XML_ERROR_AMPLIFICATION_LIMIT_BREACH: Final[LiteralString] +if sys.version_info >= (3, 14): + XML_ERROR_NOT_STARTED: Final[LiteralString] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/select.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/select.pyi index 42941b9e41fab7..02354739027330 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/select.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/select.pyi @@ -148,6 +148,8 @@ if sys.platform == "linux": EPOLLWRBAND: int EPOLLWRNORM: int EPOLL_CLOEXEC: int + if sys.version_info >= (3, 14): + EPOLLWAKEUP: int if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win32": # Solaris only diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/shutil.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/shutil.pyi index ea2c29d4625f0a..c66d8fa128beca 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/shutil.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/shutil.pyi @@ -18,7 +18,6 @@ __all__ = [ "rmtree", "Error", "SpecialFileError", - "ExecError", "make_archive", "get_archive_formats", "register_archive_format", @@ -34,6 +33,8 @@ __all__ = [ "SameFileError", "disk_usage", ] +if sys.version_info < (3, 14): + __all__ += ["ExecError"] _StrOrBytesPathT = TypeVar("_StrOrBytesPathT", bound=StrOrBytesPath) _StrPathT = TypeVar("_StrPathT", bound=StrPath) @@ -42,7 +43,13 @@ _BytesPathT = TypeVar("_BytesPathT", bound=BytesPath) class Error(OSError): ... class SameFileError(Error): ... class SpecialFileError(OSError): ... -class ExecError(OSError): ... + +if sys.version_info >= (3, 14): + ExecError = RuntimeError # Deprecated in Python 3.14; removal scheduled for Python 3.16 + +else: + class ExecError(OSError): ... + class ReadError(OSError): ... class RegistryError(Exception): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/socketserver.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/socketserver.pyi index 061932f0fac7e4..f321d14a792b2c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/socketserver.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/socketserver.pyi @@ -35,6 +35,7 @@ if sys.platform != "win32": _RequestType: TypeAlias = _socket | tuple[bytes, _socket] _AfUnixAddress: TypeAlias = str | ReadableBuffer # address acceptable for an AF_UNIX socket _AfInetAddress: TypeAlias = tuple[str | bytes | bytearray, int] # address acceptable for an AF_INET socket +_AfInet6Address: TypeAlias = tuple[str | bytes | bytearray, int, int, int] # address acceptable for an AF_INET6 socket # This can possibly be generic at some point: class BaseServer: @@ -71,10 +72,10 @@ class TCPServer(BaseServer): socket_type: int if sys.version_info >= (3, 11): allow_reuse_port: bool - server_address: _AfInetAddress + server_address: _AfInetAddress | _AfInet6Address def __init__( self, - server_address: _AfInetAddress, + server_address: _AfInetAddress | _AfInet6Address, RequestHandlerClass: Callable[[Any, _RetAddress, Self], BaseRequestHandler], bind_and_activate: bool = True, ) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/sre_constants.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/sre_constants.pyi index c41a52b26d5ab9..a3921aa0fc3b83 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/sre_constants.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/sre_constants.pyi @@ -23,6 +23,8 @@ AT_LOCALE: dict[_NamedIntConstant, _NamedIntConstant] AT_UNICODE: dict[_NamedIntConstant, _NamedIntConstant] CH_LOCALE: dict[_NamedIntConstant, _NamedIntConstant] CH_UNICODE: dict[_NamedIntConstant, _NamedIntConstant] +if sys.version_info >= (3, 14): + CH_NEGATE: dict[_NamedIntConstant, _NamedIntConstant] # flags if sys.version_info < (3, 13): SRE_FLAG_TEMPLATE: Final = 1 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi index da752327d3f78c..29fe27f39b8090 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/string/__init__.pyi @@ -32,12 +32,15 @@ whitespace: LiteralString def capwords(s: StrOrLiteralStr, sep: StrOrLiteralStr | None = None) -> StrOrLiteralStr: ... -class Template(metaclass=type): +class Template: template: str delimiter: ClassVar[str] idpattern: ClassVar[str] braceidpattern: ClassVar[str | None] - flags: ClassVar[RegexFlag] + if sys.version_info >= (3, 14): + flags: ClassVar[RegexFlag | None] + else: + flags: ClassVar[RegexFlag] pattern: ClassVar[Pattern[str]] def __init__(self, template: str) -> None: ... def substitute(self, mapping: Mapping[str, object] = {}, /, **kwds: object) -> str: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi index 01b95377a49c59..324447f5f34ce4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/string/templatelib.pyi @@ -1,4 +1,5 @@ from collections.abc import Iterator +from types import GenericAlias from typing import Any, Literal, final __all__ = ["Interpolation", "Template"] @@ -11,6 +12,7 @@ class Template: # TODO: consider making `Template` generic on `TypeVarTuple` def __new__(cls, *args: str | Interpolation) -> Template: ... def __iter__(self) -> Iterator[str | Interpolation]: ... def __add__(self, other: Template | str) -> Template: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @property def values(self) -> tuple[Any, ...]: ... # Tuple of interpolation values, which can have any type @@ -26,3 +28,4 @@ class Interpolation: def __new__( cls, value: Any, expression: str, conversion: Literal["a", "r", "s"] | None = None, format_spec: str = "" ) -> Interpolation: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/sys/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/sys/__init__.pyi index 2d894674c4af67..ce06551f975a03 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/sys/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/sys/__init__.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import MaybeNone, OptExcInfo, ProfileFunction, TraceFunction, structseq +from _typeshed import MaybeNone, OptExcInfo, ProfileFunction, StrOrBytesPath, TraceFunction, structseq from _typeshed.importlib import MetaPathFinderProtocol, PathEntryFinderProtocol from builtins import object as _object from collections.abc import AsyncGenerator, Callable, Sequence @@ -470,3 +470,7 @@ if sys.version_info >= (3, 12): from . import _monitoring monitoring = _monitoring + +if sys.version_info >= (3, 14): + def is_remote_debug_enabled() -> bool: ... + def remote_exec(pid: int, script: StrOrBytesPath) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi index e23ab07f123d49..2a4657f86ce13c 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi @@ -312,7 +312,7 @@ class Event(Generic[_W_co]): def NoDefaultRoot() -> None: ... class Variable: - def __init__(self, master: Misc | None = None, value: Incomplete | None = None, name: str | None = None) -> None: ... + def __init__(self, master: Misc | None = None, value=None, name: str | None = None) -> None: ... def set(self, value) -> None: ... initialize = set def get(self): ... @@ -379,7 +379,7 @@ class Misc: children: dict[str, Widget] def destroy(self) -> None: ... def deletecommand(self, name: str) -> None: ... - def tk_strictMotif(self, boolean: Incomplete | None = None): ... + def tk_strictMotif(self, boolean=None): ... def tk_bisque(self) -> None: ... def tk_setPalette(self, *args, **kw) -> None: ... def wait_variable(self, name: str | Variable = "PY_VAR") -> None: ... @@ -442,15 +442,15 @@ class Misc: ) -> None: ... def option_clear(self) -> None: ... def option_get(self, name, className): ... - def option_readfile(self, fileName, priority: Incomplete | None = None) -> None: ... + def option_readfile(self, fileName, priority=None) -> None: ... def selection_clear(self, **kw) -> None: ... def selection_get(self, **kw): ... def selection_handle(self, command, **kw) -> None: ... def selection_own(self, **kw) -> None: ... def selection_own_get(self, **kw): ... def send(self, interp, cmd, *args): ... - def lower(self, belowThis: Incomplete | None = None) -> None: ... - def tkraise(self, aboveThis: Incomplete | None = None) -> None: ... + def lower(self, belowThis=None) -> None: ... + def tkraise(self, aboveThis=None) -> None: ... lift = tkraise if sys.version_info >= (3, 11): def info_patchlevel(self) -> _VersionInfoType: ... @@ -888,29 +888,23 @@ class Wm: @overload def wm_geometry(self, newGeometry: str) -> None: ... geometry = wm_geometry - def wm_grid( - self, - baseWidth: Incomplete | None = None, - baseHeight: Incomplete | None = None, - widthInc: Incomplete | None = None, - heightInc: Incomplete | None = None, - ): ... + def wm_grid(self, baseWidth=None, baseHeight=None, widthInc=None, heightInc=None): ... grid = wm_grid - def wm_group(self, pathName: Incomplete | None = None): ... + def wm_group(self, pathName=None): ... group = wm_group - def wm_iconbitmap(self, bitmap: Incomplete | None = None, default: Incomplete | None = None): ... + def wm_iconbitmap(self, bitmap=None, default=None): ... iconbitmap = wm_iconbitmap def wm_iconify(self) -> None: ... iconify = wm_iconify - def wm_iconmask(self, bitmap: Incomplete | None = None): ... + def wm_iconmask(self, bitmap=None): ... iconmask = wm_iconmask - def wm_iconname(self, newName: Incomplete | None = None) -> str: ... + def wm_iconname(self, newName=None) -> str: ... iconname = wm_iconname def wm_iconphoto(self, default: bool, image1: _PhotoImageLike | str, /, *args: _PhotoImageLike | str) -> None: ... iconphoto = wm_iconphoto def wm_iconposition(self, x: int | None = None, y: int | None = None) -> tuple[int, int] | None: ... iconposition = wm_iconposition - def wm_iconwindow(self, pathName: Incomplete | None = None): ... + def wm_iconwindow(self, pathName=None): ... iconwindow = wm_iconwindow def wm_manage(self, widget) -> None: ... manage = wm_manage @@ -1027,7 +1021,7 @@ class Tk(Misc, Wm): def globalgetvar(self, *args, **kwargs): ... def globalsetvar(self, *args, **kwargs): ... def globalunsetvar(self, *args, **kwargs): ... - def interpaddr(self): ... + def interpaddr(self) -> int: ... def loadtk(self) -> None: ... def record(self, script, /): ... if sys.version_info < (3, 11): @@ -1453,8 +1447,8 @@ class Canvas(Widget, XView, YView): @overload def tag_bind(self, tagOrId: str | int, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... def tag_unbind(self, tagOrId: str | int, sequence: str, funcid: str | None = None) -> None: ... - def canvasx(self, screenx, gridspacing: Incomplete | None = None): ... - def canvasy(self, screeny, gridspacing: Incomplete | None = None): ... + def canvasx(self, screenx, gridspacing=None): ... + def canvasy(self, screeny, gridspacing=None): ... @overload def coords(self, tagOrId: str | int, /) -> list[float]: ... @overload @@ -2462,7 +2456,7 @@ class Listbox(Widget, XView, YView): select_set = selection_set def size(self) -> int: ... # type: ignore[override] def itemcget(self, index: str | int, option): ... - def itemconfigure(self, index: str | int, cnf: Incomplete | None = None, **kw): ... + def itemconfigure(self, index: str | int, cnf=None, **kw): ... itemconfig = itemconfigure class Menu(Widget): @@ -3142,7 +3136,7 @@ class Scrollbar(Widget): @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... config = configure - def activate(self, index: Incomplete | None = None): ... + def activate(self, index=None): ... def delta(self, deltax: int, deltay: int) -> float: ... def fraction(self, x: int, y: int) -> float: ... def identify(self, x: int, y: int) -> Literal["arrow1", "arrow2", "slider", "trough1", "trough2", ""]: ... @@ -3625,7 +3619,7 @@ class Text(Widget, XView, YView): def yview_pickplace(self, *what): ... # deprecated class _setit: - def __init__(self, var, value, callback: Incomplete | None = None) -> None: ... + def __init__(self, var, value, callback=None) -> None: ... def __call__(self, *args) -> None: ... # manual page: tk_optionMenu @@ -3663,9 +3657,7 @@ class _PhotoImageLike(_Image): ... class Image(_Image): name: Incomplete tk: _tkinter.TkappType - def __init__( - self, imgtype, name: Incomplete | None = None, cnf={}, master: Misc | _tkinter.TkappType | None = None, **kw - ) -> None: ... + def __init__(self, imgtype, name=None, cnf={}, master: Misc | _tkinter.TkappType | None = None, **kw) -> None: ... def __del__(self) -> None: ... def __setitem__(self, key, value) -> None: ... def __getitem__(self, key): ... @@ -3791,7 +3783,7 @@ class BitmapImage(Image, _BitmapImageLike): # This should be kept in sync with PIL.ImageTK.BitmapImage.__init__() def __init__( self, - name: Incomplete | None = None, + name=None, cnf: dict[str, Any] = {}, master: Misc | _tkinter.TkappType | None = None, *, @@ -3925,7 +3917,7 @@ class Spinbox(Widget, XView): def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... config = configure def bbox(self, index) -> tuple[int, int, int, int] | None: ... # type: ignore[override] - def delete(self, first, last: Incomplete | None = None) -> Literal[""]: ... + def delete(self, first, last=None) -> Literal[""]: ... def get(self) -> str: ... def icursor(self, index): ... def identify(self, x: int, y: int) -> Literal["", "buttondown", "buttonup", "entry"]: ... @@ -3939,7 +3931,7 @@ class Spinbox(Widget, XView): def selection(self, *args) -> tuple[int, ...]: ... def selection_adjust(self, index): ... def selection_clear(self): ... # type: ignore[override] - def selection_element(self, element: Incomplete | None = None): ... + def selection_element(self, element=None): ... def selection_from(self, index: int) -> None: ... def selection_present(self) -> None: ... def selection_range(self, start: int, end: int) -> None: ... @@ -4082,7 +4074,7 @@ class PanedWindow(Widget): def sash_mark(self, index): ... def sash_place(self, index, x, y): ... def panecget(self, child, option): ... - def paneconfigure(self, tagOrId, cnf: Incomplete | None = None, **kw): ... + def paneconfigure(self, tagOrId, cnf=None, **kw): ... paneconfig: Incomplete def panes(self): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/commondialog.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/commondialog.pyi index 201ca13ddd9c55..d5fc2f05ceec7f 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/commondialog.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/commondialog.pyi @@ -8,5 +8,5 @@ class Dialog: command: ClassVar[str | None] master: Incomplete | None options: Mapping[str, Incomplete] - def __init__(self, master: Incomplete | None = None, **options) -> None: ... + def __init__(self, master=None, **options) -> None: ... def show(self, **options): ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/dialog.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/dialog.pyi index 3dc059940964cf..971b64f0912539 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/dialog.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/dialog.pyi @@ -1,4 +1,3 @@ -from _typeshed import Incomplete from collections.abc import Mapping from tkinter import Widget from typing import Any, Final @@ -10,5 +9,5 @@ DIALOG_ICON: Final = "questhead" class Dialog(Widget): widgetName: str num: int - def __init__(self, master: Incomplete | None = None, cnf: Mapping[str, Any] = {}, **kw) -> None: ... + def __init__(self, master=None, cnf: Mapping[str, Any] = {}, **kw) -> None: ... def destroy(self) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/filedialog.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/filedialog.pyi index cafcf61e8635d2..af033dae97c319 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/filedialog.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/filedialog.pyi @@ -38,21 +38,21 @@ class FileDialog: filter_button: Button cancel_button: Button def __init__( - self, master, title: Incomplete | None = None + self, master, title=None ) -> None: ... # title is usually a str or None, but e.g. int doesn't raise en exception either how: Incomplete | None - def go(self, dir_or_file=".", pattern: str = "*", default: str = "", key: Incomplete | None = None): ... - def quit(self, how: Incomplete | None = None) -> None: ... + def go(self, dir_or_file=".", pattern: str = "*", default: str = "", key=None): ... + def quit(self, how=None) -> None: ... def dirs_double_event(self, event) -> None: ... def dirs_select_event(self, event) -> None: ... def files_double_event(self, event) -> None: ... def files_select_event(self, event) -> None: ... def ok_event(self, event) -> None: ... def ok_command(self) -> None: ... - def filter_command(self, event: Incomplete | None = None) -> None: ... + def filter_command(self, event=None) -> None: ... def get_filter(self): ... def get_selection(self): ... - def cancel_command(self, event: Incomplete | None = None) -> None: ... + def cancel_command(self, event=None) -> None: ... def set_filter(self, dir, pat) -> None: ... def set_selection(self, file) -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi index ab3c010938bef0..50b9cd8f9bcde1 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/ttk.pyi @@ -35,7 +35,7 @@ __all__ = [ ] def tclobjs_to_py(adict: dict[Any, Any]) -> dict[Any, Any]: ... -def setup_master(master: Incomplete | None = None): ... +def setup_master(master=None): ... _Padding: TypeAlias = ( tkinter._ScreenUnits @@ -52,14 +52,14 @@ class Style: master: Incomplete tk: _tkinter.TkappType def __init__(self, master: tkinter.Misc | None = None) -> None: ... - def configure(self, style, query_opt: Incomplete | None = None, **kw): ... - def map(self, style, query_opt: Incomplete | None = None, **kw): ... - def lookup(self, style, option, state: Incomplete | None = None, default: Incomplete | None = None): ... - def layout(self, style, layoutspec: Incomplete | None = None): ... + def configure(self, style, query_opt=None, **kw): ... + def map(self, style, query_opt=None, **kw): ... + def lookup(self, style, option, state=None, default=None): ... + def layout(self, style, layoutspec=None): ... def element_create(self, elementname, etype, *args, **kw) -> None: ... def element_names(self): ... def element_options(self, elementname): ... - def theme_create(self, themename, parent: Incomplete | None = None, settings: Incomplete | None = None) -> None: ... + def theme_create(self, themename, parent=None, settings=None) -> None: ... def theme_settings(self, themename, settings) -> None: ... def theme_names(self) -> tuple[str, ...]: ... @overload @@ -68,10 +68,10 @@ class Style: def theme_use(self, themename: None = None) -> str: ... class Widget(tkinter.Widget): - def __init__(self, master: tkinter.Misc | None, widgetname, kw: Incomplete | None = None) -> None: ... + def __init__(self, master: tkinter.Misc | None, widgetname, kw=None) -> None: ... def identify(self, x: int, y: int) -> str: ... - def instate(self, statespec, callback: Incomplete | None = None, *args, **kw): ... - def state(self, statespec: Incomplete | None = None): ... + def instate(self, statespec, callback=None, *args, **kw): ... + def state(self, statespec=None): ... class Button(Widget): def __init__( @@ -567,8 +567,8 @@ class Notebook(Widget): def identify(self, x: int, y: int) -> str: ... def index(self, tab_id): ... def insert(self, pos, child, **kw) -> None: ... - def select(self, tab_id: Incomplete | None = None): ... - def tab(self, tab_id, option: Incomplete | None = None, **kw): ... + def select(self, tab_id=None): ... + def tab(self, tab_id, option=None, **kw): ... def tabs(self): ... def enable_traversal(self) -> None: ... @@ -617,8 +617,8 @@ class Panedwindow(Widget, tkinter.PanedWindow): def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... forget: Incomplete def insert(self, pos, child, **kw) -> None: ... - def pane(self, pane, option: Incomplete | None = None, **kw): ... - def sashpos(self, index, newpos: Incomplete | None = None): ... + def pane(self, pane, option=None, **kw): ... + def sashpos(self, index, newpos=None): ... PanedWindow = Panedwindow diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/turtle.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/turtle.pyi index a2ab728de943fc..9c62c64e718aaa 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/turtle.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/turtle.pyi @@ -1,5 +1,7 @@ import sys -from collections.abc import Callable, Sequence +from _typeshed import StrPath +from collections.abc import Callable, Generator, Sequence +from contextlib import contextmanager from tkinter import Canvas, Frame, Misc, PhotoImage, Scrollbar from typing import Any, ClassVar, Literal, TypedDict, overload from typing_extensions import Self, TypeAlias @@ -128,6 +130,9 @@ __all__ = [ "Terminator", ] +if sys.version_info >= (3, 14): + __all__ += ["fill", "no_animation", "poly", "save"] + if sys.version_info >= (3, 12): __all__ += ["teleport"] @@ -231,6 +236,10 @@ class TurtleScreen(TurtleScreenBase): def delay(self, delay: None = None) -> int: ... @overload def delay(self, delay: int) -> None: ... + if sys.version_info >= (3, 14): + @contextmanager + def no_animation(self) -> Generator[None]: ... + def update(self) -> None: ... def window_width(self) -> int: ... def window_height(self) -> int: ... @@ -249,6 +258,8 @@ class TurtleScreen(TurtleScreenBase): # Looks like if self.cv is not a ScrolledCanvas, this could return a tuple as well @overload def screensize(self, canvwidth: int, canvheight: int, bg: _Color | None = None) -> None: ... + if sys.version_info >= (3, 14): + def save(self, filename: StrPath, *, overwrite: bool = False) -> None: ... onscreenclick = onclick resetscreen = reset clearscreen = clear @@ -428,12 +439,20 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def clearstamp(self, stampid: int | tuple[int, ...]) -> None: ... def clearstamps(self, n: int | None = None) -> None: ... def filling(self) -> bool: ... + if sys.version_info >= (3, 14): + @contextmanager + def fill(self) -> Generator[None]: ... + def begin_fill(self) -> None: ... def end_fill(self) -> None: ... def dot(self, size: int | None = None, *color: _Color) -> None: ... def write( self, arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal") ) -> None: ... + if sys.version_info >= (3, 14): + @contextmanager + def poly(self) -> Generator[None]: ... + def begin_poly(self) -> None: ... def end_poly(self) -> None: ... def get_poly(self) -> _PolygonCoords | None: ... @@ -516,6 +535,11 @@ def tracer(n: int, delay: int | None = None) -> None: ... def delay(delay: None = None) -> int: ... @overload def delay(delay: int) -> None: ... + +if sys.version_info >= (3, 14): + @contextmanager + def no_animation() -> Generator[None]: ... + def update() -> None: ... def window_width() -> int: ... def window_height() -> int: ... @@ -534,6 +558,9 @@ def screensize(canvwidth: None = None, canvheight: None = None, bg: None = None) @overload def screensize(canvwidth: int, canvheight: int, bg: _Color | None = None) -> None: ... +if sys.version_info >= (3, 14): + def save(filename: StrPath, *, overwrite: bool = False) -> None: ... + onscreenclick = onclick resetscreen = reset clearscreen = clear @@ -705,10 +732,20 @@ def stamp() -> Any: ... def clearstamp(stampid: int | tuple[int, ...]) -> None: ... def clearstamps(n: int | None = None) -> None: ... def filling() -> bool: ... + +if sys.version_info >= (3, 14): + @contextmanager + def fill() -> Generator[None]: ... + def begin_fill() -> None: ... def end_fill() -> None: ... def dot(size: int | None = None, *color: _Color) -> None: ... def write(arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal")) -> None: ... + +if sys.version_info >= (3, 14): + @contextmanager + def poly() -> Generator[None]: ... + def begin_poly() -> None: ... def end_poly() -> None: ... def get_poly() -> _PolygonCoords | None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi index 1163d71d2c95af..d9f8e875683345 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/types.pyi @@ -151,7 +151,7 @@ class CodeType: def co_firstlineno(self) -> int: ... if sys.version_info >= (3, 10): @property - @deprecated("Will be removed in Python 3.14. Use the co_lines() method instead.") + @deprecated("Will be removed in Python 3.15. Use the co_lines() method instead.") def co_lnotab(self) -> bytes: ... else: @property @@ -171,6 +171,8 @@ class CodeType: @property def co_qualname(self) -> str: ... def co_positions(self) -> Iterable[tuple[int | None, int | None, int | None, int | None]]: ... + if sys.version_info >= (3, 14): + def co_branches(self) -> Iterator[tuple[int, int, int]]: ... if sys.version_info >= (3, 11): def __new__( @@ -480,6 +482,10 @@ class MethodType: def __qualname__(self) -> str: ... # inherited from the added function def __new__(cls, func: Callable[..., Any], instance: object, /) -> Self: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + + if sys.version_info >= (3, 13): + def __get__(self, instance: object, owner: type | None = None, /) -> Self: ... + def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... @@ -580,6 +586,9 @@ class FrameType: f_trace_lines: bool f_trace_opcodes: bool def clear(self) -> None: ... + if sys.version_info >= (3, 14): + @property + def f_generator(self) -> GeneratorType[Any, Any, Any] | CoroutineType[Any, Any, Any] | None: ... @final class GetSetDescriptorType: diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi index 5aa85543ed2cad..79ab9eee924f2b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/typing.pyi @@ -797,11 +797,15 @@ class MutableMapping(Mapping[_KT, _VT]): # -- weakref.WeakValueDictionary.__ior__ # -- weakref.WeakKeyDictionary.__ior__ @overload - def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... + def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ... @overload - def update(self, m: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... + def update(self: Mapping[str, _VT], m: SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT) -> None: ... @overload - def update(self, **kwargs: _VT) -> None: ... + def update(self, m: Iterable[tuple[_KT, _VT]], /) -> None: ... + @overload + def update(self: Mapping[str, _VT], m: Iterable[tuple[str, _VT]], /, **kwargs: _VT) -> None: ... + @overload + def update(self: Mapping[str, _VT], **kwargs: _VT) -> None: ... Text = str diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi index 37f8e8ba6a4b48..07cd57ebc18f39 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi @@ -110,6 +110,8 @@ __all__ = [ "SupportsIndex", "SupportsInt", "SupportsRound", + "Reader", + "Writer", # One-off things. "Annotated", "assert_never", @@ -136,6 +138,7 @@ __all__ = [ "overload", "override", "Protocol", + "Sentinel", "reveal_type", "runtime", "runtime_checkable", @@ -199,6 +202,7 @@ _T = _TypeVar("_T") _F = _TypeVar("_F", bound=Callable[..., Any]) _TC = _TypeVar("_TC", bound=type[object]) _T_co = _TypeVar("_T_co", covariant=True) # Any type covariant containers. +_T_contra = _TypeVar("_T_contra", contravariant=True) class _Final: ... # This should be imported from typing but that breaks pytype @@ -446,6 +450,19 @@ else: @abc.abstractmethod def __round__(self, ndigits: int, /) -> _T_co: ... +if sys.version_info >= (3, 14): + from io import Reader as Reader, Writer as Writer +else: + @runtime_checkable + class Reader(Protocol[_T_co]): + @abc.abstractmethod + def read(self, size: int = ..., /) -> _T_co: ... + + @runtime_checkable + class Writer(Protocol[_T_contra]): + @abc.abstractmethod + def write(self, data: _T_contra, /) -> int: ... + if sys.version_info >= (3, 13): from types import CapsuleType as CapsuleType from typing import ( @@ -670,6 +687,16 @@ else: globals: Mapping[str, Any] | None = None, # value types depend on the key locals: Mapping[str, Any] | None = None, # value types depend on the key type_params: Iterable[TypeVar | ParamSpec | TypeVarTuple] | None = None, - format: Format = Format.VALUE, # noqa: Y011 + format: Format | None = None, _recursive_guard: Container[str] = ..., ) -> AnnotationForm: ... + +# PEP 661 +class Sentinel: + def __init__(self, name: str, repr: str | None = None) -> None: ... + if sys.version_info >= (3, 14): + def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions + def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions + else: + def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions + def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/winsound.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/winsound.pyi index a20e81f94f98f1..39dfa7b8b9c42d 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/winsound.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/winsound.pyi @@ -13,12 +13,22 @@ if sys.platform == "win32": SND_NODEFAULT: Final = 2 SND_NOSTOP: Final = 16 SND_NOWAIT: Final = 8192 + if sys.version_info >= (3, 14): + SND_SENTRY: Final = 524288 + SND_SYNC: Final = 0 + SND_SYSTEM: Final = 2097152 MB_ICONASTERISK: Final = 64 MB_ICONEXCLAMATION: Final = 48 MB_ICONHAND: Final = 16 MB_ICONQUESTION: Final = 32 MB_OK: Final = 0 + if sys.version_info >= (3, 14): + MB_ICONERROR: Final = 16 + MB_ICONINFORMATION: Final = 64 + MB_ICONSTOP: Final = 16 + MB_ICONWARNING: Final = 48 + def Beep(frequency: int, duration: int) -> None: ... # Can actually accept anything ORed with 4, and if not it's definitely str, but that's inexpressible @overload diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/xml/sax/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/xml/sax/__init__.pyi index a2eecc5a786414..ebe92d28c74d9b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/xml/sax/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/xml/sax/__init__.pyi @@ -1,3 +1,4 @@ +import sys from _typeshed import ReadableBuffer, StrPath, SupportsRead, _T_co from collections.abc import Iterable from typing import Protocol @@ -10,7 +11,7 @@ from xml.sax._exceptions import ( SAXReaderNotAvailable as SAXReaderNotAvailable, ) from xml.sax.handler import ContentHandler as ContentHandler, ErrorHandler as ErrorHandler -from xml.sax.xmlreader import XMLReader +from xml.sax.xmlreader import InputSource as InputSource, XMLReader class _SupportsReadClose(SupportsRead[_T_co], Protocol[_T_co]): def close(self) -> None: ... @@ -23,3 +24,19 @@ def make_parser(parser_list: Iterable[str] = ()) -> XMLReader: ... def parse(source: _Source, handler: ContentHandler, errorHandler: ErrorHandler = ...) -> None: ... def parseString(string: ReadableBuffer | str, handler: ContentHandler, errorHandler: ErrorHandler | None = ...) -> None: ... def _create_parser(parser_name: str) -> XMLReader: ... + +if sys.version_info >= (3, 14): + __all__ = [ + "ContentHandler", + "ErrorHandler", + "InputSource", + "SAXException", + "SAXNotRecognizedException", + "SAXNotSupportedException", + "SAXParseException", + "SAXReaderNotAvailable", + "default_parser_list", + "make_parser", + "parse", + "parseString", + ] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/zipfile/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/zipfile/__init__.pyi index ede732c0f86aec..27c1ef0246c7a8 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/zipfile/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/zipfile/__init__.pyi @@ -24,6 +24,9 @@ __all__ = [ "LargeZipFile", ] +if sys.version_info >= (3, 14): + __all__ += ["ZIP_ZSTANDARD"] + # TODO: use TypeAlias for these two when mypy bugs are fixed # https://github.com/python/mypy/issues/16581 _DateTuple = tuple[int, int, int, int, int, int] # noqa: Y026 @@ -251,6 +254,9 @@ class ZipFile: ) -> None: ... if sys.version_info >= (3, 11): def mkdir(self, zinfo_or_directory_name: str | ZipInfo, mode: int = 0o777) -> None: ... + if sys.version_info >= (3, 14): + @property + def data_offset(self) -> int | None: ... def __del__(self) -> None: ... @@ -361,10 +367,21 @@ else: def is_zipfile(filename: StrOrBytesPath | _SupportsReadSeekTell) -> bool: ... -ZIP_STORED: Final[int] -ZIP_DEFLATED: Final[int] ZIP64_LIMIT: Final[int] ZIP_FILECOUNT_LIMIT: Final[int] ZIP_MAX_COMMENT: Final[int] -ZIP_BZIP2: Final[int] -ZIP_LZMA: Final[int] + +ZIP_STORED: Final = 0 +ZIP_DEFLATED: Final = 8 +ZIP_BZIP2: Final = 12 +ZIP_LZMA: Final = 14 +if sys.version_info >= (3, 14): + ZIP_ZSTANDARD: Final = 93 + +DEFAULT_VERSION: Final[int] +ZIP64_VERSION: Final[int] +BZIP2_VERSION: Final[int] +LZMA_VERSION: Final[int] +if sys.version_info >= (3, 14): + ZSTANDARD_VERSION: Final[int] +MAX_EXTRACT_VERSION: Final[int] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/zipimport.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/zipimport.pyi index 3e94c681b7a2b1..4aab318e7c71d7 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/zipimport.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/zipimport.pyi @@ -1,10 +1,14 @@ import sys from _typeshed import StrOrBytesPath -from importlib.abc import ResourceReader from importlib.machinery import ModuleSpec from types import CodeType, ModuleType from typing_extensions import deprecated +if sys.version_info >= (3, 10): + from importlib.readers import ZipReader +else: + from importlib.abc import ResourceReader + if sys.version_info >= (3, 10): from _frozen_importlib_external import _LoaderBasics else: @@ -29,7 +33,13 @@ class zipimporter(_LoaderBasics): def get_code(self, fullname: str) -> CodeType: ... def get_data(self, pathname: str) -> bytes: ... def get_filename(self, fullname: str) -> str: ... - def get_resource_reader(self, fullname: str) -> ResourceReader | None: ... # undocumented + if sys.version_info >= (3, 14): + def get_resource_reader(self, fullname: str) -> ZipReader: ... # undocumented + elif sys.version_info >= (3, 10): + def get_resource_reader(self, fullname: str) -> ZipReader | None: ... # undocumented + else: + def get_resource_reader(self, fullname: str) -> ResourceReader | None: ... # undocumented + def get_source(self, fullname: str) -> str | None: ... def is_package(self, fullname: str) -> bool: ... @deprecated("Deprecated since 3.10; use exec_module() instead") From 220ab88779cb039ad184e3ed79cae98d1a9912b1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sun, 1 Jun 2025 17:25:46 +0200 Subject: [PATCH 298/487] [ty] Promote projects to good that now no longer hang (#18370) --- crates/ty_python_semantic/resources/primer/bad.txt | 6 ------ crates/ty_python_semantic/resources/primer/good.txt | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index da07da7fb26057..41010335f3a01e 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -4,22 +4,16 @@ artigraph # cycle panics (value_type_) arviz # too many iterations on versions of arviz newer than https://github.com/arviz-devs/arviz/commit/3205b82bb4d6097c31f7334d7ac51a6de37002d0 core # cycle panics (value_type_) cpython # too many cycle iterations -discord.py -freqtrade hydpy # too many iterations ibis # too many iterations jax # too many iterations packaging # too many iterations pandas # slow (9s) -pandas-stubs pandera # stack overflow pip # vendors packaging, see above -prefect pylint # cycle panics (self-recursive type alias) pyodide # too many cycle iterations pywin32 # bad use-def map (binding with definitely-visible unbound) -schemathesis -scikit-learn setuptools # vendors packaging, see above spack # slow, success, but mypy-primer hangs processing the output spark # too many iterations diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index 896083bda1ce16..ea322176ea05bb 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -31,12 +31,14 @@ cwltool dacite dd-trace-py dedupe +discord.py django-stubs downforeveryone dragonchain dulwich flake8 flake8-pyi +freqtrade git-revise graphql-core httpx-caching @@ -67,12 +69,14 @@ openlibrary operator optuna paasta +pandas-stubs paroxython parso pegen poetry porcupine ppb-vector +prefect psycopg pwndbg pybind11 @@ -92,6 +96,8 @@ rclip rich rotki schema_salad +schemathesis +scikit-learn scipy scrapy sockeye From 97b824db3ed34a8e029a3914fc9daf6bbcd8c5b2 Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Sun, 1 Jun 2025 16:39:56 +0100 Subject: [PATCH 299/487] [ty] Ensure `Literal` types are considered assignable to anything their `Instance` supertypes are assignable to (#18351) --- .../resources/mdtest/type_properties/is_assignable_to.md | 7 +++++++ crates/ty_python_semantic/src/types.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 111e5ef9dc1385..a1e32e9ba74559 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -97,12 +97,19 @@ turn a subtype of `str`: ```py from ty_extensions import static_assert, is_assignable_to from typing_extensions import Literal, LiteralString +from typing import Sequence, Any static_assert(is_assignable_to(Literal["foo"], Literal["foo"])) static_assert(is_assignable_to(Literal["foo"], LiteralString)) static_assert(is_assignable_to(Literal["foo"], str)) +static_assert(is_assignable_to(Literal["foo"], Sequence)) +static_assert(is_assignable_to(Literal["foo"], Sequence[str])) +static_assert(is_assignable_to(Literal["foo"], Sequence[Any])) static_assert(is_assignable_to(LiteralString, str)) +static_assert(is_assignable_to(LiteralString, Sequence)) +static_assert(is_assignable_to(LiteralString, Sequence[str])) +static_assert(is_assignable_to(LiteralString, Sequence[Any])) static_assert(not is_assignable_to(Literal["foo"], Literal["bar"])) static_assert(not is_assignable_to(str, Literal["foo"])) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 0992d479d0d015..71de8708f481fd 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1656,6 +1656,13 @@ impl<'db> Type<'db> { } } + _ if self + .literal_fallback_instance(db) + .is_some_and(|instance| instance.is_assignable_to(db, target)) => + { + true + } + (Type::ClassLiteral(class_literal), Type::Callable(_)) => { ClassType::NonGeneric(class_literal) .into_callable(db) From c4015edf481bf3b79f430dd1aa3f574fd6d0f21b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:37:56 +0200 Subject: [PATCH 300/487] Update dependency ruff to v0.11.12 (#18417) --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index a738338a459d3c..8df25c46506e10 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.11 +ruff==0.11.12 mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 diff --git a/docs/requirements.txt b/docs/requirements.txt index e5b667341f0a7c..6373101cd12cc0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.11 +ruff==0.11.12 mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 From 9e952cf0e041c9af34b81bbbd96e2d6d8dedcf2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:38:10 +0200 Subject: [PATCH 301/487] Update pre-commit dependencies (#18418) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index beaee6fed4d01b..df1fb295f1630a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,7 +80,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.11.12 hooks: - id: ruff-format - id: ruff @@ -98,7 +98,7 @@ repos: # zizmor detects security vulnerabilities in GitHub Actions workflows. # Additional configuration for the tool is found in `.github/zizmor.yml` - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.8.0 + rev: v1.9.0 hooks: - id: zizmor From 643c845a47d10bcb362640f8d907f96f581d8b1a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:38:36 +0200 Subject: [PATCH 302/487] Update dependency mdformat-mkdocs to v4.3.0 (#18421) --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index 8df25c46506e10..4848ecca167fd8 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -4,5 +4,5 @@ mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 mdformat==0.7.22 -mdformat-mkdocs==4.1.2 +mdformat-mkdocs==4.3.0 mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 diff --git a/docs/requirements.txt b/docs/requirements.txt index 6373101cd12cc0..c288da37d816bb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,5 +4,5 @@ mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 mdformat==0.7.22 -mdformat-mkdocs==4.1.2 +mdformat-mkdocs==4.3.0 mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7 From 4856377478e6229d7b319581326a601e741eb2de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:38:57 +0200 Subject: [PATCH 303/487] Update cargo-bins/cargo-binstall action to v1.12.6 (#18416) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dd16b14a4ce9ec..b647ce4321234c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -437,7 +437,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo-binstall" - uses: cargo-bins/cargo-binstall@5cbf019d8cb9b9d5b086218c41458ea35d817691 # v1.12.5 + uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6 with: tool: cargo-fuzz@0.11.2 - name: "Install cargo-fuzz" @@ -690,7 +690,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: cargo-bins/cargo-binstall@5cbf019d8cb9b9d5b086218c41458ea35d817691 # v1.12.5 + - uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6 - run: cargo binstall --no-confirm cargo-shear - run: cargo shear From 1c8d9d707eea40ba10c1225904342416f70fedaf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:39:27 +0200 Subject: [PATCH 304/487] Update Rust crate clap to v4.5.39 (#18419) --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc1f58df06dcf9..34c7291c97f9d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -498,7 +498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -507,7 +507,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -921,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1460,7 +1460,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.1", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1524,7 +1524,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3176,7 +3176,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3546,7 +3546,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4541,7 +4541,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] From 844c8626c33a3cb5a73fdf0a4dd0c05c505f6831 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:40:18 +0200 Subject: [PATCH 305/487] Update Rust crate libcst to v1.8.0 (#18424) --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34c7291c97f9d8..bc8cba3ef87928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1613,9 +1613,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libcst" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9e315e3f679e61b9095ffd5e509de78b8a4ea3bba9d772f6fb243209f808d4" +checksum = "3ac076e37f8fe6bcddbb6c3282897e6e9498b254907ccbfc806dc8f9f1491f02" dependencies = [ "annotate-snippets", "libcst_derive", @@ -1623,14 +1623,14 @@ dependencies = [ "paste", "peg", "regex", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "libcst_derive" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa96ed35d0dccc67cf7ba49350cb86de3dcb1d072a7ab28f99117f19d874953" +checksum = "9cf4a12c744a301b216c4f0cb73542709ab15e6dadbb06966ac05864109d05da" dependencies = [ "quote", "syn", From 1e6d76c8783dc90fc1dabd6349cfa4ffa2bbea77 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 2 Jun 2025 08:57:51 +0200 Subject: [PATCH 306/487] [ty] Fix server hang after shutdown request (#18414) --- crates/ty_server/src/lib.rs | 10 +- crates/ty_server/src/server.rs | 117 +++++++++++------- crates/ty_server/src/server/api.rs | 27 ++-- crates/ty_server/src/server/api/requests.rs | 2 + .../src/server/api/requests/shutdown.rs | 17 +++ crates/ty_server/src/server/api/traits.rs | 1 - crates/ty_server/src/server/connection.rs | 81 +----------- crates/ty_server/src/server/main_loop.rs | 59 ++++++--- crates/ty_server/src/server/schedule/task.rs | 6 +- crates/ty_server/src/session.rs | 12 ++ 10 files changed, 175 insertions(+), 157 deletions(-) create mode 100644 crates/ty_server/src/server/api/requests/shutdown.rs diff --git a/crates/ty_server/src/lib.rs b/crates/ty_server/src/lib.rs index 0cf68dfdf988cd..5589c60464c380 100644 --- a/crates/ty_server/src/lib.rs +++ b/crates/ty_server/src/lib.rs @@ -37,10 +37,18 @@ pub fn run_server() -> anyhow::Result<()> { let io_result = io_threads.join(); - match (server_result, io_result) { + let result = match (server_result, io_result) { (Ok(()), Ok(())) => Ok(()), (Err(server), Err(io)) => Err(server).context(format!("IO thread error: {io}")), (Err(server), _) => Err(server), (_, Err(io)) => Err(io).context("IO thread error"), + }; + + if let Err(err) = result.as_ref() { + tracing::warn!("Server shut down with an error: {err}"); + } else { + tracing::info!("Server shut down"); } + + result } diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 3ceb5402021eaf..84093d6ffa65b0 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -1,5 +1,9 @@ //! Scheduling, I/O, and API endpoints. +use self::schedule::spawn_main_loop; +use crate::PositionEncoding; +use crate::session::{AllSettings, ClientSettings, Experimental, Session}; +use lsp_server::Connection; use lsp_types::{ ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability, InlayHintOptions, InlayHintServerCapabilities, MessageType, ServerCapabilities, @@ -8,11 +12,7 @@ use lsp_types::{ }; use std::num::NonZeroUsize; use std::panic::PanicHookInfo; - -use self::connection::Connection; -use self::schedule::spawn_main_loop; -use crate::PositionEncoding; -use crate::session::{AllSettings, ClientSettings, Experimental, Session}; +use std::sync::Arc; mod api; mod connection; @@ -66,7 +66,7 @@ impl Server { // The number 32 was chosen arbitrarily. The main goal was to have enough capacity to queue // some responses before blocking. let (main_loop_sender, main_loop_receiver) = crossbeam::channel::bounded(32); - let client = Client::new(main_loop_sender.clone(), connection.sender()); + let client = Client::new(main_loop_sender.clone(), connection.sender.clone()); crate::logging::init_logging( global_settings.tracing.log_level.unwrap_or_default(), @@ -130,47 +130,12 @@ impl Server { } pub(crate) fn run(mut self) -> crate::Result<()> { - type PanicHook = Box) + 'static + Sync + Send>; - struct RestorePanicHook { - hook: Option, - } - - impl Drop for RestorePanicHook { - fn drop(&mut self) { - if let Some(hook) = self.hook.take() { - std::panic::set_hook(hook); - } - } - } - - // unregister any previously registered panic hook - // The hook will be restored when this function exits. - let _ = RestorePanicHook { - hook: Some(std::panic::take_hook()), - }; - - let client = Client::new(self.main_loop_sender.clone(), self.connection.sender()); - - // When we panic, try to notify the client. - std::panic::set_hook(Box::new(move |panic_info| { - use std::io::Write; - - let backtrace = std::backtrace::Backtrace::force_capture(); - tracing::error!("{panic_info}\n{backtrace}"); - - // we also need to print to stderr directly for when using `$logTrace` because - // the message won't be sent to the client. - // But don't use `eprintln` because `eprintln` itself may panic if the pipe is broken. - let mut stderr = std::io::stderr().lock(); - writeln!(stderr, "{panic_info}\n{backtrace}").ok(); + let client = Client::new( + self.main_loop_sender.clone(), + self.connection.sender.clone(), + ); - client - .show_message( - "The ty language server exited with a panic. See the logs for more details.", - MessageType::ERROR, - ) - .ok(); - })); + let _panic_hook = ServerPanicHookHandler::new(client); spawn_main_loop(move || self.main_loop())?.join() } @@ -221,3 +186,63 @@ impl Server { } } } + +type PanicHook = Box) + 'static + Sync + Send>; + +struct ServerPanicHookHandler { + hook: Option, + // Hold on to the strong reference for as long as the panic hook is set. + _client: Arc, +} + +impl ServerPanicHookHandler { + fn new(client: Client) -> Self { + let hook = std::panic::take_hook(); + let client = Arc::new(client); + + // Use a weak reference to the client because it must be dropped when exiting or the + // io-threads join hangs forever (because client has a reference to the connection sender). + let hook_client = Arc::downgrade(&client); + + // When we panic, try to notify the client. + std::panic::set_hook(Box::new(move |panic_info| { + use std::io::Write; + + let backtrace = std::backtrace::Backtrace::force_capture(); + tracing::error!("{panic_info}\n{backtrace}"); + + // we also need to print to stderr directly for when using `$logTrace` because + // the message won't be sent to the client. + // But don't use `eprintln` because `eprintln` itself may panic if the pipe is broken. + let mut stderr = std::io::stderr().lock(); + writeln!(stderr, "{panic_info}\n{backtrace}").ok(); + + if let Some(client) = hook_client.upgrade() { + client + .show_message( + "The ty language server exited with a panic. See the logs for more details.", + MessageType::ERROR, + ) + .ok(); + } + })); + + Self { + hook: Some(hook), + _client: client, + } + } +} + +impl Drop for ServerPanicHookHandler { + fn drop(&mut self) { + if std::thread::panicking() { + // Calling `std::panic::set_hook` while panicking results in a panic. + return; + } + + if let Some(hook) = self.hook.take() { + std::panic::set_hook(hook); + } + } +} diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index d6a934c9489fbf..db0c1bb1a729fd 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -49,6 +49,7 @@ pub(super) fn request(req: server::Request) -> Task { >( req, BackgroundSchedule::LatencySensitive ), + lsp_types::request::Shutdown::METHOD => sync_request_task::(req), method => { tracing::warn!("Received request {method} which does not have a handler"); @@ -62,7 +63,7 @@ pub(super) fn request(req: server::Request) -> Task { .unwrap_or_else(|err| { tracing::error!("Encountered error when routing request with ID {id}: {err}"); - Task::local(move |_session, client| { + Task::sync(move |_session, client| { client.show_error_message( "ty failed to handle a request from the editor. Check the logs for more details.", ); @@ -82,25 +83,25 @@ pub(super) fn request(req: server::Request) -> Task { pub(super) fn notification(notif: server::Notification) -> Task { match notif.method.as_str() { notifications::DidCloseTextDocumentHandler::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notifications::DidOpenTextDocumentHandler::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notifications::DidChangeTextDocumentHandler::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notifications::DidOpenNotebookHandler::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notifications::DidCloseNotebookHandler::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notifications::DidChangeWatchedFiles::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } lsp_types::notification::Cancel::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } lsp_types::notification::SetTrace::METHOD => { tracing::trace!("Ignoring `setTrace` notification"); @@ -114,7 +115,7 @@ pub(super) fn notification(notif: server::Notification) -> Task { } .unwrap_or_else(|err| { tracing::error!("Encountered error when routing notification: {err}"); - Task::local(|_session, client| { + Task::sync(|_session, client| { client.show_error_message( "ty failed to handle a notification from the editor. Check the logs for more details." ); @@ -122,12 +123,12 @@ pub(super) fn notification(notif: server::Notification) -> Task { }) } -fn _local_request_task(req: server::Request) -> Result +fn sync_request_task(req: server::Request) -> Result where <::RequestType as Request>::Params: UnwindSafe, { let (id, params) = cast_request::(req)?; - Ok(Task::local(move |session, client: &Client| { + Ok(Task::sync(move |session, client: &Client| { let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); let result = R::run(session, client, params); respond::(&id, result, client); @@ -245,11 +246,11 @@ where } } -fn local_notification_task( +fn sync_notification_task( notif: server::Notification, ) -> Result { let (id, params) = cast_notification::(notif)?; - Ok(Task::local(move |session, client| { + Ok(Task::sync(move |session, client| { let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); if let Err(err) = N::run(session, client, params) { tracing::error!("An error occurred while running {id}: {err}"); diff --git a/crates/ty_server/src/server/api/requests.rs b/crates/ty_server/src/server/api/requests.rs index bfdec09623f7f8..12e7243893f777 100644 --- a/crates/ty_server/src/server/api/requests.rs +++ b/crates/ty_server/src/server/api/requests.rs @@ -3,9 +3,11 @@ mod diagnostic; mod goto_type_definition; mod hover; mod inlay_hints; +mod shutdown; pub(super) use completion::CompletionRequestHandler; pub(super) use diagnostic::DocumentDiagnosticRequestHandler; pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler; pub(super) use hover::HoverRequestHandler; pub(super) use inlay_hints::InlayHintRequestHandler; +pub(super) use shutdown::ShutdownHandler; diff --git a/crates/ty_server/src/server/api/requests/shutdown.rs b/crates/ty_server/src/server/api/requests/shutdown.rs new file mode 100644 index 00000000000000..73b956168c7ecd --- /dev/null +++ b/crates/ty_server/src/server/api/requests/shutdown.rs @@ -0,0 +1,17 @@ +use crate::Session; +use crate::server::api::traits::{RequestHandler, SyncRequestHandler}; +use crate::session::client::Client; + +pub(crate) struct ShutdownHandler; + +impl RequestHandler for ShutdownHandler { + type RequestType = lsp_types::request::Shutdown; +} + +impl SyncRequestHandler for ShutdownHandler { + fn run(session: &mut Session, _client: &Client, _params: ()) -> crate::server::Result<()> { + tracing::debug!("Received shutdown request, waiting for shutdown notification"); + session.set_shutdown_requested(true); + Ok(()) + } +} diff --git a/crates/ty_server/src/server/api/traits.rs b/crates/ty_server/src/server/api/traits.rs index 7132251bf07af0..460da654dbffd8 100644 --- a/crates/ty_server/src/server/api/traits.rs +++ b/crates/ty_server/src/server/api/traits.rs @@ -17,7 +17,6 @@ pub(super) trait RequestHandler { /// This will block the main message receiver loop, meaning that no /// incoming requests or notifications will be handled while `run` is /// executing. Try to avoid doing any I/O or long-running computations. -#[expect(dead_code)] pub(super) trait SyncRequestHandler: RequestHandler { fn run( session: &mut Session, diff --git a/crates/ty_server/src/server/connection.rs b/crates/ty_server/src/server/connection.rs index ded1fcee7b257b..f347cf8eb47a61 100644 --- a/crates/ty_server/src/server/connection.rs +++ b/crates/ty_server/src/server/connection.rs @@ -1,20 +1,12 @@ use lsp_server as lsp; -use lsp_types::{notification::Notification, request::Request}; pub(crate) type ConnectionSender = crossbeam::channel::Sender; -type ConnectionReceiver = crossbeam::channel::Receiver; /// A builder for `Connection` that handles LSP initialization. pub(crate) struct ConnectionInitializer { connection: lsp::Connection, } -/// Handles inbound and outbound messages with the client. -pub(crate) struct Connection { - sender: ConnectionSender, - receiver: ConnectionReceiver, -} - impl ConnectionInitializer { /// Create a new LSP server connection over stdin/stdout. pub(crate) fn stdio() -> (Self, lsp::IoThreads) { @@ -40,7 +32,7 @@ impl ConnectionInitializer { server_capabilities: &lsp_types::ServerCapabilities, name: &str, version: &str, - ) -> crate::Result { + ) -> crate::Result { self.connection.initialize_finish( id, serde_json::json!({ @@ -51,76 +43,7 @@ impl ConnectionInitializer { } }), )?; - let Self { - connection: lsp::Connection { sender, receiver }, - } = self; - Ok(Connection { sender, receiver }) - } -} - -impl Connection { - /// Make a new `ClientSender` for sending messages to the client. - pub(super) fn sender(&self) -> ConnectionSender { - self.sender.clone() - } - pub(super) fn send(&self, msg: lsp::Message) -> crate::Result<()> { - self.sender.send(msg)?; - Ok(()) - } - - /// An iterator over incoming messages from the client. - pub(super) fn incoming(&self) -> &crossbeam::channel::Receiver { - &self.receiver - } - - /// Check and respond to any incoming shutdown requests; returns`true` if the server should be shutdown. - pub(super) fn handle_shutdown(&self, message: &lsp::Message) -> crate::Result { - match message { - lsp::Message::Request(lsp::Request { id, method, .. }) - if method == lsp_types::request::Shutdown::METHOD => - { - self.sender - .send(lsp::Response::new_ok(id.clone(), ()).into())?; - tracing::info!("Shutdown request received. Waiting for an exit notification..."); - - loop { - match &self - .receiver - .recv_timeout(std::time::Duration::from_secs(30))? - { - lsp::Message::Notification(lsp::Notification { method, .. }) - if method == lsp_types::notification::Exit::METHOD => - { - tracing::info!("Exit notification received. Server shutting down..."); - return Ok(true); - } - lsp::Message::Request(lsp::Request { id, method, .. }) => { - tracing::warn!( - "Server received unexpected request {method} ({id}) while waiting for exit notification", - ); - self.sender.send(lsp::Message::Response(lsp::Response::new_err( - id.clone(), - lsp::ErrorCode::InvalidRequest as i32, - "Server received unexpected request while waiting for exit notification".to_string(), - )))?; - } - message => { - tracing::warn!( - "Server received unexpected message while waiting for exit notification: {message:?}" - ); - } - } - } - } - lsp::Message::Notification(lsp::Notification { method, .. }) - if method == lsp_types::notification::Exit::METHOD => - { - anyhow::bail!( - "Server received an exit notification before a shutdown request was sent. Exiting..." - ); - } - _ => Ok(false), - } + Ok(self.connection) } } diff --git a/crates/ty_server/src/server/main_loop.rs b/crates/ty_server/src/server/main_loop.rs index 9f0aefaf5b41d0..456e9d88b21354 100644 --- a/crates/ty_server/src/server/main_loop.rs +++ b/crates/ty_server/src/server/main_loop.rs @@ -2,8 +2,10 @@ use crate::Session; use crate::server::schedule::Scheduler; use crate::server::{Server, api}; use crate::session::client::Client; +use anyhow::anyhow; use crossbeam::select; use lsp_server::Message; +use lsp_types::notification::Notification; use lsp_types::{DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher}; pub(crate) type MainLoopSender = crossbeam::channel::Sender; @@ -13,7 +15,7 @@ impl Server { pub(super) fn main_loop(&mut self) -> crate::Result<()> { self.initialize(&Client::new( self.main_loop_sender.clone(), - self.connection.sender(), + self.connection.sender.clone(), )); let mut scheduler = Scheduler::new(self.worker_threads); @@ -25,9 +27,11 @@ impl Server { match next_event { Event::Message(msg) => { - if self.connection.handle_shutdown(&msg)? { - break; - } + let client = Client::new( + self.main_loop_sender.clone(), + self.connection.sender.clone(), + ); + let task = match msg { Message::Request(req) => { self.session @@ -35,9 +39,37 @@ impl Server { .incoming_mut() .register(req.id.clone(), req.method.clone()); + if self.session.is_shutdown_requested() { + tracing::warn!( + "Received request after server shutdown was requested, discarding" + ); + client.respond_err( + req.id, + lsp_server::ResponseError { + code: lsp_server::ErrorCode::InvalidRequest as i32, + message: "Shutdown already requested".to_owned(), + data: None, + }, + )?; + continue; + } + api::request(req) } - Message::Notification(notification) => api::notification(notification), + Message::Notification(notification) => { + if notification.method == lsp_types::notification::Exit::METHOD { + if !self.session.is_shutdown_requested() { + return Err(anyhow!( + "Received exit notification before a shutdown request" + )); + } + + tracing::debug!("Received exit notification, exiting"); + return Ok(()); + } + + api::notification(notification) + } // Handle the response from the client to a server request Message::Response(response) => { @@ -59,8 +91,6 @@ impl Server { } }; - let client = - Client::new(self.main_loop_sender.clone(), self.connection.sender()); scheduler.dispatch(task, &mut self.session, client); } Event::Action(action) => match action { @@ -75,7 +105,7 @@ impl Server { let duration = start_time.elapsed(); tracing::trace!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration)); - self.connection.send(Message::Response(response))?; + self.connection.sender.send(Message::Response(response))?; } else { tracing::trace!( "Ignoring response for canceled request id={}", @@ -112,12 +142,13 @@ impl Server { /// /// Returns `Ok(None)` if the client connection is closed. fn next_event(&self) -> Result, crossbeam::channel::RecvError> { - let next = select!( - recv(self.connection.incoming()) -> msg => msg.map(Event::Message), - recv(self.main_loop_receiver) -> event => return Ok(event.ok()), - ); - - next.map(Some) + select!( + recv(self.connection.receiver) -> msg => { + // Ignore disconnect errors, they're handled by the main loop (it will exit). + Ok(msg.ok().map(Event::Message)) + }, + recv(self.main_loop_receiver) -> event => event.map(Some), + ) } fn initialize(&mut self, client: &Client) { diff --git a/crates/ty_server/src/server/schedule/task.rs b/crates/ty_server/src/server/schedule/task.rs index e781ffcec700a0..024a76fe13121b 100644 --- a/crates/ty_server/src/server/schedule/task.rs +++ b/crates/ty_server/src/server/schedule/task.rs @@ -68,7 +68,7 @@ impl Task { }) } /// Creates a new local task. - pub(crate) fn local(func: F) -> Self + pub(crate) fn sync(func: F) -> Self where F: FnOnce(&mut Session, &Client) + 'static, { @@ -82,7 +82,7 @@ impl Task { where R: Serialize + Send + 'static, { - Self::local(move |_, client| { + Self::sync(move |_, client| { if let Err(err) = client.respond(&id, result) { tracing::error!("Unable to send immediate response: {err}"); } @@ -91,6 +91,6 @@ impl Task { /// Creates a local task that does nothing. pub(crate) fn nothing() -> Self { - Self::local(move |_, _| {}) + Self::sync(move |_, _| {}) } } diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index f995b2f19f69f0..cced715ad56f1e 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -50,6 +50,9 @@ pub struct Session { /// Tracks the pending requests between client and server. request_queue: RequestQueue, + + /// Has the client requested the server to shutdown. + shutdown_requested: bool, } impl Session { @@ -86,6 +89,7 @@ impl Session { client_capabilities, )), request_queue: RequestQueue::new(), + shutdown_requested: false, }) } @@ -97,6 +101,14 @@ impl Session { &mut self.request_queue } + pub(crate) fn is_shutdown_requested(&self) -> bool { + self.shutdown_requested + } + + pub(crate) fn set_shutdown_requested(&mut self, requested: bool) { + self.shutdown_requested = requested; + } + // TODO(dhruvmanila): Ideally, we should have a single method for `workspace_db_for_path_mut` // and `default_workspace_db_mut` but the borrow checker doesn't allow that. // https://github.com/astral-sh/ruff/pull/13041#discussion_r1726725437 From b9f3b0e0a6d4a30902bfc091256ed618f80e46bd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:03:09 +0200 Subject: [PATCH 307/487] Update docker/build-push-action action to v6.18.0 (#18422) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3fa86aed6b6df2..0eb80dfafb2c62 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -79,7 +79,7 @@ jobs: # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ - name: Build and push by digest id: build - uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . platforms: ${{ matrix.platform }} @@ -231,7 +231,7 @@ jobs: ${{ env.TAG_PATTERNS }} - name: Build and push - uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . platforms: linux/amd64,linux/arm64 From 384e80ec80fe1cd47621d00c94499a8da979b56e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:03:32 +0200 Subject: [PATCH 308/487] Update taiki-e/install-action action to v2.52.4 (#18420) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b647ce4321234c..8f715c88389bd6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -239,11 +239,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-insta - name: ty mdtests (GitHub annotations) @@ -297,11 +297,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-insta - name: "Run tests" @@ -324,7 +324,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-nextest - name: "Run tests" @@ -407,11 +407,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-insta - name: "Run tests" @@ -910,7 +910,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 + uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: tool: cargo-codspeed From e2d96df50192803abe63cb0865b447c2552c2eff Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 2 Jun 2025 11:52:26 +0100 Subject: [PATCH 309/487] [ty] Improve diagnostics if the user attempts to import a stdlib module that does not exist on their configured Python version (#18403) --- .../resources/mdtest/import/basic.md | 29 +++++ ...mport\342\200\246_(2fcfcf567587a056).snap" | 65 +++++++++++ ...mport\342\200\246_(c14954eefd15211f).snap" | 47 ++++++++ .../src/module_resolver/resolver.rs | 2 +- .../src/module_resolver/typeshed.rs | 33 +++++- crates/ty_python_semantic/src/types.rs | 4 +- .../src/types/diagnostic.rs | 50 ++++++++- crates/ty_python_semantic/src/types/infer.rs | 104 +++++++++++++----- 8 files changed, 298 insertions(+), 36 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(2fcfcf567587a056).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(c14954eefd15211f).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/import/basic.md b/crates/ty_python_semantic/resources/mdtest/import/basic.md index 60edbc392b7df4..8e7538190ef06a 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/import/basic.md @@ -176,3 +176,32 @@ emitted for the `import from` statement: # error: [unresolved-import] from does_not_exist import foo, bar, baz ``` + +## Attempting to import a stdlib module that's not yet been added + + + +```toml +[environment] +python-version = "3.10" +``` + +```py +import tomllib # error: [unresolved-import] +from string.templatelib import Template # error: [unresolved-import] +from importlib.resources import abc # error: [unresolved-import] +``` + +## Attempting to import a stdlib module that was previously removed + + + +```toml +[environment] +python-version = "3.13" +``` + +```py +import aifc # error: [unresolved-import] +from distutils import sysconfig # error: [unresolved-import] +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(2fcfcf567587a056).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(2fcfcf567587a056).snap" new file mode 100644 index 00000000000000..dd2756ab920659 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(2fcfcf567587a056).snap" @@ -0,0 +1,65 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: basic.md - Structures - Attempting to import a stdlib module that's not yet been added +mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | import tomllib # error: [unresolved-import] +2 | from string.templatelib import Template # error: [unresolved-import] +3 | from importlib.resources import abc # error: [unresolved-import] +``` + +# Diagnostics + +``` +error[unresolved-import]: Cannot resolve imported module `tomllib` + --> src/mdtest_snippet.py:1:8 + | +1 | import tomllib # error: [unresolved-import] + | ^^^^^^^ +2 | from string.templatelib import Template # error: [unresolved-import] +3 | from importlib.resources import abc # error: [unresolved-import] + | +info: The stdlib module `tomllib` is only available on Python 3.11+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Cannot resolve imported module `string.templatelib` + --> src/mdtest_snippet.py:2:6 + | +1 | import tomllib # error: [unresolved-import] +2 | from string.templatelib import Template # error: [unresolved-import] + | ^^^^^^^^^^^^^^^^^^ +3 | from importlib.resources import abc # error: [unresolved-import] + | +info: The stdlib module `string.templatelib` is only available on Python 3.14+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Module `importlib.resources` has no member `abc` + --> src/mdtest_snippet.py:3:33 + | +1 | import tomllib # error: [unresolved-import] +2 | from string.templatelib import Template # error: [unresolved-import] +3 | from importlib.resources import abc # error: [unresolved-import] + | ^^^ + | +info: The stdlib module `importlib.resources` only has a `abc` submodule on Python 3.11+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(c14954eefd15211f).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(c14954eefd15211f).snap" new file mode 100644 index 00000000000000..964ecc9e64abdf --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import\342\200\246_(c14954eefd15211f).snap" @@ -0,0 +1,47 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: basic.md - Structures - Attempting to import a stdlib module that was previously removed +mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | import aifc # error: [unresolved-import] +2 | from distutils import sysconfig # error: [unresolved-import] +``` + +# Diagnostics + +``` +error[unresolved-import]: Cannot resolve imported module `aifc` + --> src/mdtest_snippet.py:1:8 + | +1 | import aifc # error: [unresolved-import] + | ^^^^ +2 | from distutils import sysconfig # error: [unresolved-import] + | +info: The stdlib module `aifc` is only available on Python <=3.12 +info: Python 3.13 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Cannot resolve imported module `distutils` + --> src/mdtest_snippet.py:2:6 + | +1 | import aifc # error: [unresolved-import] +2 | from distutils import sysconfig # error: [unresolved-import] + | ^^^^^^^^^ + | +info: The stdlib module `distutils` is only available on Python <=3.11 +info: Python 3.13 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 6447c203cfd04c..74cc931f893aa5 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -369,7 +369,7 @@ impl SearchPaths { }) } - pub(super) fn typeshed_versions(&self) -> &TypeshedVersions { + pub(crate) fn typeshed_versions(&self) -> &TypeshedVersions { &self.typeshed_versions } diff --git a/crates/ty_python_semantic/src/module_resolver/typeshed.rs b/crates/ty_python_semantic/src/module_resolver/typeshed.rs index b1dcff7d06ad18..850882106f4066 100644 --- a/crates/ty_python_semantic/src/module_resolver/typeshed.rs +++ b/crates/ty_python_semantic/src/module_resolver/typeshed.rs @@ -58,7 +58,7 @@ impl std::error::Error for TypeshedVersionsParseError { } #[derive(Debug, PartialEq, Eq, Clone)] -pub(super) enum TypeshedVersionsParseErrorKind { +pub(crate) enum TypeshedVersionsParseErrorKind { TooManyLines(NonZeroUsize), UnexpectedNumberOfColons, InvalidModuleName(String), @@ -105,7 +105,7 @@ pub(crate) struct TypeshedVersions(FxHashMap); impl TypeshedVersions { #[must_use] - fn exact(&self, module_name: &ModuleName) -> Option<&PyVersionRange> { + pub(crate) fn exact(&self, module_name: &ModuleName) -> Option<&PyVersionRange> { self.0.get(module_name) } @@ -257,19 +257,44 @@ impl fmt::Display for TypeshedVersions { } #[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum PyVersionRange { +pub(crate) enum PyVersionRange { AvailableFrom(RangeFrom), AvailableWithin(RangeInclusive), } impl PyVersionRange { #[must_use] - fn contains(&self, version: PythonVersion) -> bool { + pub(crate) fn contains(&self, version: PythonVersion) -> bool { match self { Self::AvailableFrom(inner) => inner.contains(&version), Self::AvailableWithin(inner) => inner.contains(&version), } } + + /// Display the version range in a way that is suitable for rendering in user-facing diagnostics. + pub(crate) fn diagnostic_display(&self) -> impl std::fmt::Display { + struct DiagnosticDisplay<'a>(&'a PyVersionRange); + + impl fmt::Display for DiagnosticDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + PyVersionRange::AvailableFrom(range_from) => write!(f, "{}+", range_from.start), + PyVersionRange::AvailableWithin(range_inclusive) => { + // Don't trust the start Python version if it's 3.0 or lower. + // Typeshed doesn't attempt to give accurate start versions if a module was added + // in the Python 2 era. + if range_inclusive.start() <= &(PythonVersion { major: 3, minor: 0 }) { + write!(f, "<={}", range_inclusive.end()) + } else { + write!(f, "{}-{}", range_inclusive.start(), range_inclusive.end()) + } + } + } + } + } + + DiagnosticDisplay(self) + } } impl FromStr for PyVersionRange { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 71de8708f481fd..f58bd2412b7ddf 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -843,7 +843,7 @@ impl<'db> Type<'db> { matches!(self, Type::PropertyInstance(..)) } - pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: Module) -> Self { + pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: &Module) -> Self { Self::ModuleLiteral(ModuleLiteralType::new(db, importing_file, submodule)) } @@ -8201,7 +8201,7 @@ impl<'db> ModuleLiteralType<'db> { full_submodule_name.extend(&submodule_name); if imported_submodules.contains(&full_submodule_name) { if let Some(submodule) = resolve_module(db, &full_submodule_name) { - return Symbol::bound(Type::module_literal(db, importing_file, submodule)); + return Symbol::bound(Type::module_literal(db, importing_file, &submodule)); } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index e93101375599e0..1a0b13ef21d7a2 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -5,7 +5,6 @@ use super::{ CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass, add_inferred_python_version_hint_to_diagnostic, }; -use crate::declare_lint; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; use crate::types::LintDiagnosticGuard; @@ -15,6 +14,7 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; +use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -2139,3 +2139,51 @@ fn report_invalid_base<'ctx, 'db>( )); Some(diagnostic) } + +/// This function receives an unresolved `from foo import bar` import, +/// where `foo` can be resolved to a module but that module does not +/// have a `bar` member or submdoule. +/// +/// If the `foo` module originates from the standard library and `foo.bar` +/// *does* exist as a submodule in the standard library on *other* Python +/// versions, we add a hint to the diagnostic that the user may have +/// misconfigured their Python version. +pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions( + db: &dyn Db, + mut diagnostic: LintDiagnosticGuard, + full_submodule_name: &ModuleName, + parent_module: &Module, +) { + let Some(search_path) = parent_module.search_path() else { + return; + }; + + if !search_path.is_standard_library() { + return; + } + + let program = Program::get(db); + let typeshed_versions = program.search_paths(db).typeshed_versions(); + + let Some(version_range) = typeshed_versions.exact(full_submodule_name) else { + return; + }; + + let python_version = program.python_version(db); + if version_range.contains(python_version) { + return; + } + + diagnostic.info(format_args!( + "The stdlib module `{module_name}` only has a `{name}` \ + submodule on Python {version_range}", + module_name = parent_module.name(), + name = full_submodule_name + .components() + .next_back() + .expect("A `ModuleName` always has at least one component"), + version_range = version_range.diagnostic_display(), + )); + + add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules"); +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index acbf27b8815ab8..1f046974cc1592 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -102,7 +102,8 @@ use crate::{Db, FxOrderSet, Program}; use super::context::{InNoTypeCheck, InferContext}; use super::diagnostic::{ INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, REDUNDANT_CAST, STATIC_ASSERT_ERROR, - SUBCLASS_OF_FINAL_CLASS, TYPE_ASSERTION_FAILURE, report_attempted_protocol_instantiation, + SUBCLASS_OF_FINAL_CLASS, TYPE_ASSERTION_FAILURE, + hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation, report_bad_argument_to_get_protocol_members, report_duplicate_bases, report_index_out_of_bounds, report_invalid_exception_caught, report_invalid_exception_cause, report_invalid_exception_raised, report_invalid_or_unsupported_base, @@ -3899,8 +3900,32 @@ impl<'db> TypeInferenceBuilder<'db> { module.unwrap_or_default() )); if level == 0 { + if let Some(module_name) = module.and_then(ModuleName::new) { + let program = Program::get(self.db()); + let typeshed_versions = program.search_paths(self.db()).typeshed_versions(); + + if let Some(version_range) = typeshed_versions.exact(&module_name) { + // We know it is a stdlib module on *some* Python versions... + let python_version = program.python_version(self.db()); + if !version_range.contains(python_version) { + // ...But not on *this* Python version. + diagnostic.info(format_args!( + "The stdlib module `{module_name}` is only available on Python {version_range}", + version_range = version_range.diagnostic_display(), + )); + add_inferred_python_version_hint_to_diagnostic( + self.db(), + &mut diagnostic, + "resolving modules", + ); + return; + } + } + } + diagnostic.info( - "make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment" + "make sure your Python environment is properly configured: \ + https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment", ); } } @@ -4098,11 +4123,13 @@ impl<'db> TypeInferenceBuilder<'db> { return; }; - let Some(module_ty) = self.module_type_from_name(&module_name) else { + let Some(module) = resolve_module(self.db(), &module_name) else { self.add_unknown_declaration_with_binding(alias.into(), definition); return; }; + let module_ty = Type::module_literal(self.db(), self.file(), &module); + // The indirection of having `star_import_info` as a separate variable // is required in order to make the borrow checker happy. let star_import_info = definition @@ -4151,6 +4178,15 @@ impl<'db> TypeInferenceBuilder<'db> { } } + // Evaluate whether `X.Y` would constitute a valid submodule name, + // given a `from X import Y` statement. If it is valid, this will be `Some()`; + // else, it will be `None`. + let full_submodule_name = ModuleName::new(name).map(|final_part| { + let mut ret = module_name.clone(); + ret.extend(&final_part); + ret + }); + // If the module doesn't bind the symbol, check if it's a submodule. This won't get // handled by the `Type::member` call because it relies on the semantic index's // `imported_modules` set. The semantic index does not include information about @@ -4166,35 +4202,47 @@ impl<'db> TypeInferenceBuilder<'db> { // // Regardless, for now, we sidestep all of that by repeating the submodule-or-attribute // check here when inferring types for a `from...import` statement. - if let Some(submodule_name) = ModuleName::new(name) { - let mut full_submodule_name = module_name.clone(); - full_submodule_name.extend(&submodule_name); - if let Some(submodule_ty) = self.module_type_from_name(&full_submodule_name) { - self.add_declaration_with_binding( - alias.into(), - definition, - &DeclaredAndInferredType::AreTheSame(submodule_ty), - ); - return; - } + if let Some(submodule_type) = full_submodule_name + .as_ref() + .and_then(|submodule_name| self.module_type_from_name(submodule_name)) + { + self.add_declaration_with_binding( + alias.into(), + definition, + &DeclaredAndInferredType::AreTheSame(submodule_type), + ); + return; } - if &alias.name != "*" { - let is_import_reachable = self.is_reachable(import_from); + self.add_unknown_declaration_with_binding(alias.into(), definition); - if is_import_reachable { - if let Some(builder) = self - .context - .report_lint(&UNRESOLVED_IMPORT, AnyNodeRef::Alias(alias)) - { - builder.into_diagnostic(format_args!( - "Module `{module_name}` has no member `{name}`" - )); - } - } + if &alias.name == "*" { + return; } - self.add_unknown_declaration_with_binding(alias.into(), definition); + if !self.is_reachable(import_from) { + return; + } + + let Some(builder) = self + .context + .report_lint(&UNRESOLVED_IMPORT, AnyNodeRef::Alias(alias)) + else { + return; + }; + + let diagnostic = builder.into_diagnostic(format_args!( + "Module `{module_name}` has no member `{name}`" + )); + + if let Some(full_submodule_name) = full_submodule_name { + hint_if_stdlib_submodule_exists_on_other_versions( + self.db(), + diagnostic, + &full_submodule_name, + &module, + ); + } } fn infer_return_statement(&mut self, ret: &ast::StmtReturn) { @@ -4218,7 +4266,7 @@ impl<'db> TypeInferenceBuilder<'db> { fn module_type_from_name(&self, module_name: &ModuleName) -> Option> { resolve_module(self.db(), module_name) - .map(|module| Type::module_literal(self.db(), self.file(), module)) + .map(|module| Type::module_literal(self.db(), self.file(), &module)) } fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> { From 47698883ae06f8d647fbe234aeedcf2333240218 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 2 Jun 2025 14:10:00 +0100 Subject: [PATCH 310/487] [ty] Fix false positives for legacy `ParamSpec`s inside `Callable` type expressions (#18426) --- .../resources/mdtest/annotations/callable.md | 5 +- crates/ty_python_semantic/src/types/infer.rs | 60 ++++++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index a819d2c63038b2..49d05eb6535541 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -264,10 +264,9 @@ from typing_extensions import ParamSpec P2 = ParamSpec("P2") -# TODO: Not an error; remove once `ParamSpec` is supported -# error: [invalid-type-form] +# TODO: argument list should not be `...` (requires `ParamSpec` support) def _(c: Callable[P2, int]): - reveal_type(c) # revealed: (...) -> Unknown + reveal_type(c) # revealed: (...) -> int ``` ## Using `typing.Unpack` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 1f046974cc1592..699047d6718bea 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -119,7 +119,8 @@ use super::string_annotation::{ }; use super::subclass_of::SubclassOfInner; use super::{ - BoundSuperError, BoundSuperType, ClassBase, add_inferred_python_version_hint_to_diagnostic, + BoundSuperError, BoundSuperType, ClassBase, NominalInstanceType, + add_inferred_python_version_hint_to_diagnostic, }; /// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope. @@ -9131,9 +9132,9 @@ impl<'db> TypeInferenceBuilder<'db> { &mut self, parameters: &ast::Expr, ) -> Option> { - Some(match parameters { + match parameters { ast::Expr::EllipsisLiteral(ast::ExprEllipsisLiteral { .. }) => { - Parameters::gradual_form() + return Some(Parameters::gradual_form()); } ast::Expr::List(ast::ExprList { elts: params, .. }) => { let mut parameter_types = Vec::with_capacity(params.len()); @@ -9152,43 +9153,48 @@ impl<'db> TypeInferenceBuilder<'db> { parameter_types.push(param_type); } - if return_todo { + return Some(if return_todo { // TODO: `Unpack` Parameters::todo() } else { Parameters::new(parameter_types.iter().map(|param_type| { Parameter::positional_only(None).with_annotated_type(*param_type) })) - } + }); } ast::Expr::Subscript(_) => { // TODO: Support `Concatenate[...]` - Parameters::todo() - } - ast::Expr::Name(name) if name.is_invalid() => { - // This is a special case to avoid raising the error suggesting what the first - // argument should be. This only happens when there's already a syntax error like - // `Callable[]`. - return None; - } - ast::Expr::Name(name) - if self.infer_name_load(name) - == Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => - { return Some(Parameters::todo()); } - _ => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameters) { - let diag = builder.into_diagnostic(format_args!( - "The first argument to `Callable` \ - must be either a list of types, \ - ParamSpec, Concatenate, or `...`", - )); - diagnostic::add_type_expression_reference_link(diag); + ast::Expr::Name(name) => { + if name.is_invalid() { + // This is a special case to avoid raising the error suggesting what the first + // argument should be. This only happens when there's already a syntax error like + // `Callable[]`. + return None; + } + match self.infer_name_load(name) { + Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => { + return Some(Parameters::todo()); + } + Type::NominalInstance(NominalInstanceType { class, .. }) + if class.is_known(self.db(), KnownClass::ParamSpec) => + { + return Some(Parameters::todo()); + } + _ => {} } - return None; } - }) + _ => {} + } + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameters) { + let diag = builder.into_diagnostic(format_args!( + "The first argument to `Callable` must be either a list of types, \ + ParamSpec, Concatenate, or `...`", + )); + diagnostic::add_type_expression_reference_link(diag); + } + None } } From f379eb6e624e03583bd23ca3a9b3bc53cf1448a3 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:46:26 +0200 Subject: [PATCH 311/487] [ty] Treat lambda functions as instances of types.FunctionType (#18431) --- .../resources/mdtest/expression/lambda.md | 19 +++++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/expression/lambda.md b/crates/ty_python_semantic/resources/mdtest/expression/lambda.md index dbafc217acfde6..b48efaad70bf4b 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/lambda.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/lambda.md @@ -115,3 +115,22 @@ a5: Callable[[], None] = lambda x: None # error: [invalid-assignment] a6: Callable[[int], None] = lambda: None ``` + +## Function-like behavior of lambdas + +All `lambda` functions are instances of `types.FunctionType` and should have access to the same set +of attributes. + +```py +x = lambda y: y + +reveal_type(x.__code__) # revealed: CodeType +reveal_type(x.__name__) # revealed: str +reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None +reveal_type(x.__annotations__) # revealed: dict[str, @Todo(Support for `typing.TypeAlias`)] +reveal_type(x.__dict__) # revealed: dict[str, Any] +reveal_type(x.__doc__) # revealed: str | None +reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None +reveal_type(x.__module__) # revealed: str +reveal_type(x.__qualname__) # revealed: str +``` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 699047d6718bea..2120b4a9448fbf 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -4956,7 +4956,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: Useful inference of a lambda's return type will require a different approach, // which does the inference of the body expression based on arguments at each call site, // rather than eagerly computing a return type without knowing the argument types. - CallableType::single(self.db(), Signature::new(parameters, Some(Type::unknown()))) + CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown()))) } /// Returns the type of the first parameter if the given scope is function-like (i.e. function or lambda). From e677863787589e343d7de15b41ce260c290a9485 Mon Sep 17 00:00:00 2001 From: Denys Kyslytsyn <71435127+twentyone212121@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:34:50 +0900 Subject: [PATCH 312/487] [`fastapi`] Avoid false positive for class dependencies (`FAST003`) (#18271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes #17226. This PR updates the `FAST003` rule to correctly handle [FastAPI class dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/). Specifically, if a path parameter is declared in either: - a `pydantic.BaseModel` used as a dependency, or - the `__init__` method of a class used as a dependency, then `FAST003` will no longer incorrectly report it as unused. FastAPI allows a shortcut when using annotated class dependencies - `Depends` can be called without arguments, e.g.: ```python class MyParams(BaseModel): my_id: int @router.get("/{my_id}") def get_id(params: Annotated[MyParams, Depends()]): ... ``` This PR ensures that such usage is properly supported by the linter. Note: Support for dataclasses is not included in this PR. Let me know if you’d like it to be added. ## Test Plan Added relevant test cases to the `FAST003.py` fixture. --- .../test/fixtures/fastapi/FAST003.py | 35 +++++++ .../rules/fastapi_unused_path_parameter.rs | 94 +++++++++++++++---- ...-api-unused-path-parameter_FAST003.py.snap | 61 ++++++++++++ 3 files changed, 173 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/fastapi/FAST003.py b/crates/ruff_linter/resources/test/fixtures/fastapi/FAST003.py index ef808e733600de..cd8653026cc451 100644 --- a/crates/ruff_linter/resources/test/fixtures/fastapi/FAST003.py +++ b/crates/ruff_linter/resources/test/fixtures/fastapi/FAST003.py @@ -178,3 +178,38 @@ async def unknown_1(other: str = Depends(unknown_unresolved)): ... async def unknown_2(other: str = Depends(unknown_not_function)): ... @app.get("/things/{thing_id}") async def unknown_3(other: str = Depends(unknown_imported)): ... + + +# Class dependencies +from pydantic import BaseModel +from dataclasses import dataclass + +class PydanticParams(BaseModel): + my_id: int + + +class InitParams: + def __init__(self, my_id: int): + self.my_id = my_id + + +# Errors +@app.get("/{id}") +async def get_id_pydantic_full( + params: Annotated[PydanticParams, Depends(PydanticParams)], +): ... +@app.get("/{id}") +async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... +@app.get("/{id}") +async def get_id_init_not_annotated(params = Depends(InitParams)): ... + + +# No errors +@app.get("/{my_id}") +async def get_id_pydantic_full( + params: Annotated[PydanticParams, Depends(PydanticParams)], +): ... +@app.get("/{my_id}") +async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... +@app.get("/{my_id}") +async def get_id_init_not_annotated(params = Depends(InitParams)): ... diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs index c0f1cfb5179e31..e0d215b45c5da5 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_unused_path_parameter.rs @@ -226,6 +226,9 @@ enum Dependency<'a> { /// A function defined in the same file, whose parameter names are as given. Function(Vec<&'a str>), + /// A class defined in the same file, whose constructor parameter names are as given. + Class(Vec<&'a str>), + /// There are multiple `Depends()` calls. /// /// Multiple `Depends` annotations aren't supported by fastapi and the exact behavior is @@ -239,6 +242,7 @@ impl<'a> Dependency<'a> { Self::Unknown => None, Self::Multiple => None, Self::Function(parameter_names) => Some(parameter_names.as_slice()), + Self::Class(parameter_names) => Some(parameter_names.as_slice()), } } } @@ -279,7 +283,14 @@ impl<'a> Dependency<'a> { let mut dependencies = tuple.elts.iter().skip(1).filter_map(|metadata_element| { let arguments = depends_arguments(metadata_element, semantic)?; - Self::from_depends_call(arguments, semantic) + // Arguments to `Depends` can be empty if the dependency is a class + // that FastAPI will call to create an instance of the class itself. + // https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcut + if arguments.is_empty() { + Self::from_dependency_name(tuple.elts.first()?.as_name_expr()?, semantic) + } else { + Self::from_depends_call(arguments, semantic) + } }); let dependency = dependencies.next()?; @@ -302,25 +313,68 @@ impl<'a> Dependency<'a> { return None; }; - let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { - return Some(Self::Unknown); - }; + Self::from_dependency_name(name, semantic) + } - let BindingKind::FunctionDefinition(scope_id) = binding.kind else { + fn from_dependency_name(name: &'a ast::ExprName, semantic: &SemanticModel<'a>) -> Option { + let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { return Some(Self::Unknown); }; - let scope = &semantic.scopes[scope_id]; + match binding.kind { + BindingKind::FunctionDefinition(scope_id) => { + let scope = &semantic.scopes[scope_id]; - let ScopeKind::Function(function_def) = scope.kind else { - return Some(Self::Unknown); - }; + let ScopeKind::Function(function_def) = scope.kind else { + return Some(Self::Unknown); + }; - let parameter_names = non_posonly_non_variadic_parameters(function_def) - .map(|param| param.name().as_str()) - .collect(); + let parameter_names = non_posonly_non_variadic_parameters(function_def) + .map(|param| param.name().as_str()) + .collect(); - Some(Self::Function(parameter_names)) + Some(Self::Function(parameter_names)) + } + BindingKind::ClassDefinition(scope_id) => { + let scope = &semantic.scopes[scope_id]; + + let ScopeKind::Class(class_def) = scope.kind else { + return Some(Self::Unknown); + }; + + let parameter_names = if class_def + .bases() + .iter() + .any(|expr| is_pydantic_base_model(expr, semantic)) + { + class_def + .body + .iter() + .filter_map(|stmt| { + stmt.as_ann_assign_stmt() + .and_then(|ann_assign| ann_assign.target.as_name_expr()) + .map(|name| name.id.as_str()) + }) + .collect() + } else if let Some(init_def) = class_def + .body + .iter() + .filter_map(|stmt| stmt.as_function_def_stmt()) + .find(|func_def| func_def.name.as_str() == "__init__") + { + // Skip `self` parameter + non_posonly_non_variadic_parameters(init_def) + .skip(1) + .map(|param| param.name().as_str()) + .collect() + } else { + return None; + }; + + Some(Self::Class(parameter_names)) + } + _ => Some(Self::Unknown), + } } } @@ -340,11 +394,17 @@ fn depends_arguments<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a } fn is_fastapi_depends(expr: &Expr, semantic: &SemanticModel) -> bool { - let Some(qualified_name) = semantic.resolve_qualified_name(expr) else { - return false; - }; + semantic + .resolve_qualified_name(expr) + .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["fastapi", "Depends"])) +} - matches!(qualified_name.segments(), ["fastapi", "Depends"]) +fn is_pydantic_base_model(expr: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(expr) + .is_some_and(|qualified_name| { + matches!(qualified_name.segments(), ["pydantic", "BaseModel"]) + }) } /// Extract the expected in-route name for a given parameter, if it has an alias. diff --git a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-unused-path-parameter_FAST003.py.snap b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-unused-path-parameter_FAST003.py.snap index 9cb210585fd387..6869770ac36ddf 100644 --- a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-unused-path-parameter_FAST003.py.snap +++ b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-unused-path-parameter_FAST003.py.snap @@ -380,3 +380,64 @@ FAST003.py:160:19: FAST003 [*] Parameter `thing_id` appears in route path, but n 162 162 | 163 163 | 164 164 | ### No errors + +FAST003.py:197:12: FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_full` signature + | +196 | # Errors +197 | @app.get("/{id}") + | ^^^^ FAST003 +198 | async def get_id_pydantic_full( +199 | params: Annotated[PydanticParams, Depends(PydanticParams)], + | + = help: Add `id` to function signature + +ℹ Unsafe fix +196 196 | # Errors +197 197 | @app.get("/{id}") +198 198 | async def get_id_pydantic_full( +199 |- params: Annotated[PydanticParams, Depends(PydanticParams)], + 199 |+ params: Annotated[PydanticParams, Depends(PydanticParams)], id, +200 200 | ): ... +201 201 | @app.get("/{id}") +202 202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... + +FAST003.py:201:12: FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_short` signature + | +199 | params: Annotated[PydanticParams, Depends(PydanticParams)], +200 | ): ... +201 | @app.get("/{id}") + | ^^^^ FAST003 +202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... +203 | @app.get("/{id}") + | + = help: Add `id` to function signature + +ℹ Unsafe fix +199 199 | params: Annotated[PydanticParams, Depends(PydanticParams)], +200 200 | ): ... +201 201 | @app.get("/{id}") +202 |-async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... + 202 |+async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()], id): ... +203 203 | @app.get("/{id}") +204 204 | async def get_id_init_not_annotated(params = Depends(InitParams)): ... +205 205 | + +FAST003.py:203:12: FAST003 [*] Parameter `id` appears in route path, but not in `get_id_init_not_annotated` signature + | +201 | @app.get("/{id}") +202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... +203 | @app.get("/{id}") + | ^^^^ FAST003 +204 | async def get_id_init_not_annotated(params = Depends(InitParams)): ... + | + = help: Add `id` to function signature + +ℹ Unsafe fix +201 201 | @app.get("/{id}") +202 202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ... +203 203 | @app.get("/{id}") +204 |-async def get_id_init_not_annotated(params = Depends(InitParams)): ... + 204 |+async def get_id_init_not_annotated(id, params = Depends(InitParams)): ... +205 205 | +206 206 | +207 207 | # No errors From 14c42a8ddf377c8c79e43970a87b80af01258b5a Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Tue, 3 Jun 2025 00:51:09 +0000 Subject: [PATCH 313/487] [`refurb`] Mark `FURB180` fix unsafe when class has bases (#18149) ## Summary Mark `FURB180`'s fix as unsafe if the class already has base classes. This is because the base classes might validate the other base classes (like `typing.Protocol` does) or otherwise alter runtime behavior if more base classes are added. ## Test Plan The existing snapshot test covers this case already. ## References Partially addresses https://github.com/astral-sh/ruff/issues/13307 (left out way to permit certain exceptions) --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Co-authored-by: Brent Westbrook --- .../src/rules/refurb/rules/metaclass_abcmeta.rs | 17 +++++++++++++++-- ...ules__refurb__tests__FURB180_FURB180.py.snap | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs index e079bd93d8f0c5..96016b03514ecf 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs @@ -1,5 +1,6 @@ use itertools::Itertools; +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::{Ranged, TextRange}; @@ -31,6 +32,11 @@ use crate::{AlwaysFixableViolation, Edit, Fix}; /// pass /// ``` /// +/// ## Fix safety +/// The rule's fix is unsafe if the class has base classes. This is because the base classes might +/// be validating the class's other base classes (e.g., `typing.Protocol` does this) or otherwise +/// alter runtime behavior if more base classes are added. +/// /// ## References /// - [Python documentation: `abc.ABC`](https://docs.python.org/3/library/abc.html#abc.ABC) /// - [Python documentation: `abc.ABCMeta`](https://docs.python.org/3/library/abc.html#abc.ABCMeta) @@ -69,6 +75,11 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { return; } + let applicability = if class_def.bases().is_empty() { + Applicability::Safe + } else { + Applicability::Unsafe + }; let mut diagnostic = checker.report_diagnostic(MetaClassABCMeta, keyword.range); diagnostic.try_set_fix(|| { @@ -80,7 +91,7 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { Ok(if position > 0 { // When the `abc.ABCMeta` is not the first keyword, put `abc.ABC` before the first // keyword. - Fix::safe_edits( + Fix::applicable_edits( // Delete from the previous argument, to the end of the `metaclass` argument. Edit::range_deletion(TextRange::new( class_def.keywords()[position - 1].end(), @@ -91,11 +102,13 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { Edit::insertion(format!("{binding}, "), class_def.keywords()[0].start()), import_edit, ], + applicability, ) } else { - Fix::safe_edits( + Fix::applicable_edits( Edit::range_replacement(binding, keyword.range), [import_edit], + applicability, ) }) }); diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB180_FURB180.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB180_FURB180.py.snap index 45df8c8e53cea8..d351d92f937326 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB180_FURB180.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB180_FURB180.py.snap @@ -50,7 +50,7 @@ FURB180.py:26:18: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract | = help: Replace with `abc.ABC` -ℹ Safe fix +ℹ Unsafe fix 23 23 | pass 24 24 | 25 25 | @@ -68,7 +68,7 @@ FURB180.py:31:34: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract | = help: Replace with `abc.ABC` -ℹ Safe fix +ℹ Unsafe fix 28 28 | def foo(self): pass 29 29 | 30 30 | From 2289187b74e38399e82eede9295fe0a95ac52116 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 3 Jun 2025 07:25:07 +0530 Subject: [PATCH 314/487] Infer `list[T]` for starred target in unpacking (#18401) ## Summary Closes: astral-sh/ty#191 ## Test Plan Update existing tests. --- .../resources/mdtest/attributes.md | 2 +- .../resources/mdtest/unpacking.md | 40 +++++++------------ .../ty_python_semantic/src/types/unpacker.rs | 32 +++++++++++---- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index b1a1573ce48082..18f32d9acbf84e 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -302,7 +302,7 @@ class C: c_instance = C() reveal_type(c_instance.a) # revealed: Unknown | Literal[1] -reveal_type(c_instance.b) # revealed: Unknown +reveal_type(c_instance.b) # revealed: Unknown | list[Literal[2, 3]] ``` #### Attributes defined in for-loop (unpacking) diff --git a/crates/ty_python_semantic/resources/mdtest/unpacking.md b/crates/ty_python_semantic/resources/mdtest/unpacking.md index dac296b838849e..93d5c314f607a5 100644 --- a/crates/ty_python_semantic/resources/mdtest/unpacking.md +++ b/crates/ty_python_semantic/resources/mdtest/unpacking.md @@ -109,8 +109,7 @@ reveal_type(d) # revealed: Literal[5] # error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more" [a, *b, c, d] = (1, 2) reveal_type(a) # revealed: Unknown -# TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: Unknown +reveal_type(b) # revealed: list[Unknown] reveal_type(c) # revealed: Unknown reveal_type(d) # revealed: Unknown ``` @@ -120,8 +119,7 @@ reveal_type(d) # revealed: Unknown ```py [a, *b, c] = (1, 2) reveal_type(a) # revealed: Literal[1] -# TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[Unknown] reveal_type(c) # revealed: Literal[2] ``` @@ -130,8 +128,7 @@ reveal_type(c) # revealed: Literal[2] ```py [a, *b, c] = (1, 2, 3) reveal_type(a) # revealed: Literal[1] -# TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[Literal[2]] reveal_type(c) # revealed: Literal[3] ``` @@ -140,8 +137,7 @@ reveal_type(c) # revealed: Literal[3] ```py [a, *b, c, d] = (1, 2, 3, 4, 5, 6) reveal_type(a) # revealed: Literal[1] -# TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[Literal[2, 3, 4]] reveal_type(c) # revealed: Literal[5] reveal_type(d) # revealed: Literal[6] ``` @@ -152,8 +148,7 @@ reveal_type(d) # revealed: Literal[6] [a, b, *c] = (1, 2, 3, 4) reveal_type(a) # revealed: Literal[1] reveal_type(b) # revealed: Literal[2] -# TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(c) # revealed: @Todo(starred unpacking) +reveal_type(c) # revealed: list[Literal[3, 4]] ``` ### Starred expression (6) @@ -164,7 +159,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking) reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown reveal_type(c) # revealed: Unknown -reveal_type(d) # revealed: Unknown +reveal_type(d) # revealed: list[Unknown] reveal_type(e) # revealed: Unknown reveal_type(f) # revealed: Unknown ``` @@ -247,8 +242,7 @@ reveal_type(b) # revealed: Unknown # error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more" (a, *b, c, d) = "ab" reveal_type(a) # revealed: Unknown -# TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: Unknown +reveal_type(b) # revealed: list[Unknown] reveal_type(c) # revealed: Unknown reveal_type(d) # revealed: Unknown ``` @@ -258,7 +252,7 @@ reveal_type(d) # revealed: Unknown (a, b, *c, d) = "a" reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown -reveal_type(c) # revealed: Unknown +reveal_type(c) # revealed: list[Unknown] reveal_type(d) # revealed: Unknown ``` @@ -267,8 +261,7 @@ reveal_type(d) # revealed: Unknown ```py (a, *b, c) = "ab" reveal_type(a) # revealed: LiteralString -# TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[Unknown] reveal_type(c) # revealed: LiteralString ``` @@ -277,8 +270,7 @@ reveal_type(c) # revealed: LiteralString ```py (a, *b, c) = "abc" reveal_type(a) # revealed: LiteralString -# TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[LiteralString] reveal_type(c) # revealed: LiteralString ``` @@ -287,8 +279,7 @@ reveal_type(c) # revealed: LiteralString ```py (a, *b, c, d) = "abcdef" reveal_type(a) # revealed: LiteralString -# TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo(starred unpacking) +reveal_type(b) # revealed: list[LiteralString] reveal_type(c) # revealed: LiteralString reveal_type(d) # revealed: LiteralString ``` @@ -299,8 +290,7 @@ reveal_type(d) # revealed: LiteralString (a, b, *c) = "abcd" reveal_type(a) # revealed: LiteralString reveal_type(b) # revealed: LiteralString -# TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(c) # revealed: @Todo(starred unpacking) +reveal_type(c) # revealed: list[LiteralString] ``` ### Unicode @@ -411,8 +401,7 @@ def _(arg: tuple[int, tuple[str, bytes]] | tuple[tuple[int, bytes], Literal["ab" def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]): a, *b, c = arg reveal_type(a) # revealed: int - # TODO: Should be `list[bytes | int | str]` - reveal_type(b) # revealed: @Todo(starred unpacking) + reveal_type(b) # revealed: list[bytes] | list[int | str] reveal_type(c) # revealed: int | bytes ``` @@ -676,8 +665,7 @@ class ContextManager: with ContextManager() as (a, *b): reveal_type(a) # revealed: int - # TODO: Should be list[int] once support for assigning to starred expression is added - reveal_type(b) # revealed: @Todo(starred unpacking) + reveal_type(b) # revealed: list[int] ``` ### Unbound context manager expression diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index cdde1a2e34e9fc..d790a872dd28d4 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -8,12 +8,12 @@ use ruff_python_ast::{self as ast, AnyNodeRef}; use crate::Db; use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId}; use crate::semantic_index::symbol::ScopeId; -use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types, todo_type}; +use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types}; use crate::unpack::{UnpackKind, UnpackValue}; use super::context::InferContext; use super::diagnostic::INVALID_ASSIGNMENT; -use super::{TupleType, UnionType}; +use super::{KnownClass, TupleType, UnionType}; /// Unpacks the value expression type to their respective targets. pub(crate) struct Unpacker<'db> { @@ -253,12 +253,17 @@ impl<'db> Unpacker<'db> { let starred_end_index = tuple_ty.len(self.db()) - remaining; // SAFETY: Safe because of the length check above. - let _starred_element_types = + let starred_element_types = &tuple_ty.elements(self.db())[starred_index..starred_end_index]; - // TODO: Combine the types into a list type. If the - // starred_element_types is empty, then it should be `List[Any]`. - // combine_types(starred_element_types); - element_types.push(todo_type!("starred unpacking")); + + element_types.push(KnownClass::List.to_specialized_instance( + self.db(), + [if starred_element_types.is_empty() { + Type::unknown() + } else { + UnionType::from_elements(self.db(), starred_element_types) + }], + )); // Insert the types remaining that aren't consumed by the starred expression. element_types.extend_from_slice( @@ -278,7 +283,18 @@ impl<'db> Unpacker<'db> { ); } - Cow::Owned(vec![Type::unknown(); targets.len()]) + Cow::Owned( + targets + .iter() + .map(|target| { + if target.is_starred_expr() { + KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()]) + } else { + Type::unknown() + } + }) + .collect(), + ) } } From 57202c1c770f948c0c6fb0aafef433fcdbb0c109 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 08:06:56 +0200 Subject: [PATCH 315/487] Update NPM Development dependencies (#18423) --- playground/api/package-lock.json | 75 ++++---- playground/package-lock.json | 301 ++++++++++++++++++------------- 2 files changed, 208 insertions(+), 168 deletions(-) diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index 7db60f8b73f243..e96b47173b7f97 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -49,9 +49,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250508.0.tgz", - "integrity": "sha512-9x09MrA9Y5RQs3zqWvWns8xHgM2pVNXWpeJ+3hQYu4PrwPFZXtTD6b/iMmOnlYKzINlREq1RGeEybMFyWEUlUg==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250525.0.tgz", + "integrity": "sha512-L5l+7sSJJT2+riR5rS3Q3PKNNySPjWfRIeaNGMVRi1dPO6QPi4lwuxfRUFNoeUdilZJUVPfSZvTtj9RedsKznQ==", "cpu": [ "x64" ], @@ -66,9 +66,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250508.0.tgz", - "integrity": "sha512-0Ili+nE2LLRzYue/yPc1pepSyNNg6LxR3/ng/rlQzVQUxPXIXldHFkJ/ynsYwQnAcf6OxasSi/kbTm6yvDoSAQ==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250525.0.tgz", + "integrity": "sha512-Y3IbIdrF/vJWh/WBvshwcSyUh175VAiLRW7963S1dXChrZ1N5wuKGQm9xY69cIGVtitpMJWWW3jLq7J/Xxwm0Q==", "cpu": [ "arm64" ], @@ -83,9 +83,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250508.0.tgz", - "integrity": "sha512-5saVrZ3uVwYxvBa7BaonXjeqB6X0YF3ak05qvBaWcmZ3FNmnarMm2W8842cnbhnckDVBpB/iDo51Sy6Y7y1jcw==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250525.0.tgz", + "integrity": "sha512-KSyQPAby+c6cpENoO0ayCQlY6QIh28l/+QID7VC1SLXfiNHy+hPNsH1vVBTST6CilHVAQSsy9tCZ9O9XECB8yg==", "cpu": [ "x64" ], @@ -100,9 +100,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250508.0.tgz", - "integrity": "sha512-muQe1pkxRi3eaq1Q417xvfGd2SlktbLTzNhT5Yftsx8OecWrYuB8i4ttR6Nr5ER06bfEj0FqQjqJJhcp6wLLUQ==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250525.0.tgz", + "integrity": "sha512-Nt0FUxS2kQhJUea4hMCNPaetkrAFDhPnNX/ntwcqVlGgnGt75iaAhupWJbU0GB+gIWlKeuClUUnDZqKbicoKyg==", "cpu": [ "arm64" ], @@ -117,9 +117,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250508.0.tgz", - "integrity": "sha512-EJj8iTWFMqjgvZUxxNvzK7frA1JMFi3y/9eDIdZPL/OaQh3cmk5Lai5DCXsKYUxfooMBZWYTp53zOLrvuJI8VQ==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250525.0.tgz", + "integrity": "sha512-mwTj+9f3uIa4NEXR1cOa82PjLa6dbrb3J+KCVJFYIaq7e63VxEzOchCXS4tublT2pmOhmFqkgBMXrxozxNkR2Q==", "cpu": [ "x64" ], @@ -134,9 +134,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20250525.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250525.0.tgz", - "integrity": "sha512-3loeNVJkcDLb9giarUIHmDgvh+/4RtH+R/rHn4BCmME1qKdu73n/hvECYhH8BabCZplF8zQy1wok1MKwXEWC/A==", + "version": "4.20250603.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250603.0.tgz", + "integrity": "sha512-62g5wVdXiRRu9sfC9fmwgZfE9W0rWy2txlgRKn1Xx1NWHshSdh4RWMX6gO4EBKPqgwlHKaMuSPZ1qM0hHsq7bA==", "dev": true, "license": "MIT OR Apache-2.0" }, @@ -1383,9 +1383,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250508.3", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250508.3.tgz", - "integrity": "sha512-43oTmZ0CCmUcieetI5YDDyX0IiwhOcVIWzdBBCqWXK3F1XgQwg4d3fTqRyJnCmHIoaYx9CE1kTEKZC1UahPQhA==", + "version": "4.20250525.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250525.0.tgz", + "integrity": "sha512-F5XRDn9WqxUaHphUT8qwy5WXC/3UwbBRJTdjjP5uwHX82vypxIlHNyHziZnplPLhQa1kbSdIY7wfuP1XJyyYZw==", "dev": true, "license": "MIT", "dependencies": { @@ -1397,7 +1397,7 @@ "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", - "workerd": "1.20250508.0", + "workerd": "1.20250525.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" @@ -1740,9 +1740,9 @@ } }, "node_modules/workerd": { - "version": "1.20250508.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250508.0.tgz", - "integrity": "sha512-ffLxe7dXSuGoA6jb3Qx2SClIV1aLHfJQ6RhGhzYHjQgv7dL6fdUOSIIGgzmu2mRKs+WFSujp6c8WgKquco6w3w==", + "version": "1.20250525.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250525.0.tgz", + "integrity": "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1753,17 +1753,17 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250508.0", - "@cloudflare/workerd-darwin-arm64": "1.20250508.0", - "@cloudflare/workerd-linux-64": "1.20250508.0", - "@cloudflare/workerd-linux-arm64": "1.20250508.0", - "@cloudflare/workerd-windows-64": "1.20250508.0" + "@cloudflare/workerd-darwin-64": "1.20250525.0", + "@cloudflare/workerd-darwin-arm64": "1.20250525.0", + "@cloudflare/workerd-linux-64": "1.20250525.0", + "@cloudflare/workerd-linux-arm64": "1.20250525.0", + "@cloudflare/workerd-windows-64": "1.20250525.0" } }, "node_modules/wrangler": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.16.1.tgz", - "integrity": "sha512-YiLdWXcaQva2K/bqokpsZbySPmoT8TJFyJPsQPZumnkgimM9+/g/yoXArByA+pf+xU8jhw7ybQ8X1yBGXv731g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.18.0.tgz", + "integrity": "sha512-/ng0KI9io97SNsBU1rheADBLLTE5Djybgsi4gXuvH1RBKJGpyj1xWvZ2fuWu8vAonit3EiZkwtERTm6kESHP3A==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { @@ -1771,10 +1771,10 @@ "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250508.3", + "miniflare": "4.20250525.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", - "workerd": "1.20250508.0" + "workerd": "1.20250525.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -1784,11 +1784,10 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250508.0" + "@cloudflare/workers-types": "^4.20250525.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { diff --git a/playground/package-lock.json b/playground/package-lock.json index 4307b2ee1a6edb..91d06193b1b372 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -574,9 +574,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", "dev": true, "license": "MIT", "engines": { @@ -1320,9 +1320,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.7.tgz", - "integrity": "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz", + "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1332,13 +1332,13 @@ "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.7" + "tailwindcss": "4.1.8" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.7.tgz", - "integrity": "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz", + "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1350,24 +1350,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-x64": "4.1.7", - "@tailwindcss/oxide-freebsd-x64": "4.1.7", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-x64-musl": "4.1.7", - "@tailwindcss/oxide-wasm32-wasi": "4.1.7", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" + "@tailwindcss/oxide-android-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-x64": "4.1.8", + "@tailwindcss/oxide-freebsd-x64": "4.1.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-x64-musl": "4.1.8", + "@tailwindcss/oxide-wasm32-wasi": "4.1.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.7.tgz", - "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz", + "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", "cpu": [ "arm64" ], @@ -1382,9 +1382,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.7.tgz", - "integrity": "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz", + "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", "cpu": [ "arm64" ], @@ -1399,9 +1399,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.7.tgz", - "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz", + "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", "cpu": [ "x64" ], @@ -1416,9 +1416,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.7.tgz", - "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz", + "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", "cpu": [ "x64" ], @@ -1433,9 +1433,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.7.tgz", - "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz", + "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", "cpu": [ "arm" ], @@ -1450,9 +1450,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.7.tgz", - "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz", + "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", "cpu": [ "arm64" ], @@ -1467,9 +1467,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.7.tgz", - "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz", + "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", "cpu": [ "arm64" ], @@ -1484,9 +1484,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz", - "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz", + "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", "cpu": [ "x64" ], @@ -1501,9 +1501,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz", - "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz", + "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", "cpu": [ "x64" ], @@ -1518,9 +1518,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.7.tgz", - "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz", + "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1539,7 +1539,7 @@ "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", + "@napi-rs/wasm-runtime": "^0.2.10", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, @@ -1579,14 +1579,14 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.9", + "version": "0.2.10", "dev": true, "inBundle": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.0", - "@emnapi/runtime": "^1.4.0", + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, @@ -1608,9 +1608,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz", - "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz", + "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", "cpu": [ "arm64" ], @@ -1625,9 +1625,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz", - "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz", + "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", "cpu": [ "x64" ], @@ -1719,15 +1719,15 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.7.tgz", - "integrity": "sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.8.tgz", + "integrity": "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.7", - "@tailwindcss/oxide": "4.1.7", - "tailwindcss": "4.1.7" + "@tailwindcss/node": "4.1.8", + "@tailwindcss/oxide": "4.1.8", + "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" @@ -1755,9 +1755,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", - "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", + "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1775,17 +1775,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", - "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", + "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/type-utils": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/type-utils": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1799,7 +1799,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -1815,16 +1815,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", - "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", + "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "engines": { @@ -1839,33 +1839,72 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", + "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.33.1", + "@typescript-eslint/types": "^8.33.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", - "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", + "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1" + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", + "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", - "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", + "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1882,9 +1921,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", + "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", "dev": true, "license": "MIT", "engines": { @@ -1896,14 +1935,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", - "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", + "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/project-service": "8.33.1", + "@typescript-eslint/tsconfig-utils": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1962,16 +2003,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", - "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", + "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1" + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1986,13 +2027,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", - "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", + "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2004,9 +2045,9 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.0.tgz", - "integrity": "sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.1.tgz", + "integrity": "sha512-FmQvN3yZGyD9XW6IyxE86Kaa/DnxSsrDQX1xCR1qojNpBLaUop+nLYFvhCkJsq8zOupNjCRA9jyhPGOJsSkutA==", "dev": true, "license": "MIT", "dependencies": { @@ -2923,9 +2964,9 @@ } }, "node_modules/eslint": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", - "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2935,7 +2976,7 @@ "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.27.0", + "@eslint/js": "9.28.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5833,9 +5874,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", - "integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz", + "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==", "dev": true, "license": "MIT" }, @@ -6065,15 +6106,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", - "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", + "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.32.1", - "@typescript-eslint/parser": "8.32.1", - "@typescript-eslint/utils": "8.32.1" + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/utils": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From d1cb8e2142c6f761580ac9f6e72311cddcf3ec76 Mon Sep 17 00:00:00 2001 From: otakutyrant <64188229+otakutyrant@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:40:22 +0800 Subject: [PATCH 316/487] Update editor setup docs about Neovim and Vim (#18324) ## Summary I struggled to make ruff_organize_imports work and then I found out I missed the key note about conform.nvim before because it was put in the Vim section wrongly! So I refined them both. --------- Co-authored-by: Dhruv Manilawala --- docs/editors/setup.md | 105 +++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/docs/editors/setup.md b/docs/editors/setup.md index 507e9d450c4618..f50f3abbd395dc 100644 --- a/docs/editors/setup.md +++ b/docs/editors/setup.md @@ -131,6 +131,63 @@ To view the trace logs between Neovim and Ruff, set the log level for Neovim's L vim.lsp.set_log_level('debug') ``` +
    +With the conform.nvim plugin for Neovim. + +```lua +require("conform").setup({ + formatters_by_ft = { + python = { + -- To fix auto-fixable lint errors. + "ruff_fix", + -- To run the Ruff formatter. + "ruff_format", + -- To organize the imports. + "ruff_organize_imports", + }, + }, +}) +``` + +
    + +
    +With the nvim-lint plugin for Neovim. + +```lua +require("lint").linters_by_ft = { + python = { "ruff" }, +} +``` + +
    + +
    +With the ALE plugin for Neovim or Vim. + +Neovim (using Lua): + +```lua +-- Linters +vim.g.ale_linters = { python = { "ruff" } } +-- Fixers +vim.g.ale_fixers = { python = { "ruff", "ruff_format" } } +``` + +Vim (using Vimscript): + +```vim +" Linters +let g:ale_linters = { "python": ["ruff"] } +" Fixers +let g:ale_fixers = { "python": ["ruff", "ruff_format"] } +``` + +For the fixers, ruff will run ruff check --fix (to fix all auto-fixable +problems) whereas ruff_format will run ruff format. + +
    + ## Vim The [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) plugin can be used to configure the Ruff Language Server in Vim. @@ -169,24 +226,8 @@ endfunction Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension for [coc.nvim](https://github.com/neoclide/coc.nvim). -
    -With the ALE plugin for Vim or Neovim. - -```vim -" Linters -let g:ale_linters = { "python": ["ruff"] } -" Fixers -let g:ale_fixers = { "python": ["ruff", "ruff_format"] } -``` - -For the fixers, `ruff` will run `ruff check --fix` (to fix all auto-fixable problems) whereas -`ruff_format` will run `ruff format`. - -
    -
    Ruff can also be integrated via efm language server in just a few lines. -
    Following is an example config for efm to use Ruff for linting and formatting Python files: @@ -203,38 +244,6 @@ tools:
    -
    -With the conform.nvim plugin for Neovim. -
    - -```lua -require("conform").setup({ - formatters_by_ft = { - python = { - -- To fix auto-fixable lint errors. - "ruff_fix", - -- To run the Ruff formatter. - "ruff_format", - -- To organize the imports. - "ruff_organize_imports", - }, - }, -}) -``` - -
    - -
    -With the nvim-lint plugin for Neovim. - -```lua -require("lint").linters_by_ft = { - python = { "ruff" }, -} -``` - -
    - ## Helix Open the [language configuration file](https://docs.helix-editor.com/languages.html#languagestoml-files) for From 67d94d9ec8f3d369af2e2acd8942978ea5c869d1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 3 Jun 2025 10:11:39 +0200 Subject: [PATCH 317/487] Use ty's completions in playground (#18425) --- crates/ty_wasm/src/lib.rs | 30 +++++++++++++++ playground/package-lock.json | 2 +- playground/ty/src/Editor/Editor.tsx | 57 ++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 839442852644c3..20dde86bc66b09 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -293,6 +293,27 @@ impl Workspace { })) } + #[wasm_bindgen] + pub fn completions( + &self, + file_id: &FileHandle, + position: Position, + ) -> Result, Error> { + let source = source_text(&self.db, file_id.file); + let index = line_index(&self.db, file_id.file); + + let offset = position.to_text_size(&source, &index, self.position_encoding)?; + + let completions = ty_ide::completion(&self.db, file_id.file, offset); + + Ok(completions + .into_iter() + .map(|completion| Completion { + label: completion.label, + }) + .collect()) + } + #[wasm_bindgen(js_name = "inlayHints")] pub fn inlay_hints(&self, file_id: &FileHandle, range: Range) -> Result, Error> { let index = line_index(&self.db, file_id.file); @@ -586,6 +607,7 @@ pub struct LocationLink { } #[wasm_bindgen] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Hover { #[wasm_bindgen(getter_with_clone)] pub markdown: String, @@ -594,6 +616,14 @@ pub struct Hover { } #[wasm_bindgen] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Completion { + #[wasm_bindgen(getter_with_clone)] + pub label: String, +} + +#[wasm_bindgen] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHint { #[wasm_bindgen(getter_with_clone)] pub markdown: String, diff --git a/playground/package-lock.json b/playground/package-lock.json index 91d06193b1b372..e6958158764aaf 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -6484,7 +6484,7 @@ } }, "ruff/ruff_wasm": { - "version": "0.11.8", + "version": "0.11.10", "license": "MIT" }, "shared": { diff --git a/playground/ty/src/Editor/Editor.tsx b/playground/ty/src/Editor/Editor.tsx index 7936ce5ac89aa6..fca03cd08b4adb 100644 --- a/playground/ty/src/Editor/Editor.tsx +++ b/playground/ty/src/Editor/Editor.tsx @@ -18,16 +18,16 @@ import { import { useCallback, useEffect, useRef } from "react"; import { Theme } from "shared"; import { + Position as TyPosition, Range as TyRange, Severity, type Workspace, - Position as TyPosition, } from "ty_wasm"; - -import IStandaloneCodeEditor = editor.IStandaloneCodeEditor; import { FileId, ReadonlyFiles } from "../Playground"; import { isPythonFile } from "./Files"; import { Diagnostic } from "./Diagnostics"; +import IStandaloneCodeEditor = editor.IStandaloneCodeEditor; +import CompletionItemKind = languages.CompletionItemKind; type Props = { visible: boolean; @@ -146,13 +146,15 @@ class PlaygroundServer editor.ICodeEditorOpener, languages.HoverProvider, languages.InlayHintsProvider, - languages.DocumentFormattingEditProvider + languages.DocumentFormattingEditProvider, + languages.CompletionItemProvider { private typeDefinitionProviderDisposable: IDisposable; private editorOpenerDisposable: IDisposable; private hoverDisposable: IDisposable; private inlayHintsDisposable: IDisposable; private formatDisposable: IDisposable; + private completionDisposable: IDisposable; constructor( private monaco: Monaco, @@ -168,11 +170,53 @@ class PlaygroundServer "python", this, ); + this.completionDisposable = monaco.languages.registerCompletionItemProvider( + "python", + this, + ); this.editorOpenerDisposable = monaco.editor.registerEditorOpener(this); this.formatDisposable = monaco.languages.registerDocumentFormattingEditProvider("python", this); } + triggerCharacters: undefined; + + provideCompletionItems( + model: editor.ITextModel, + position: Position, + ): languages.ProviderResult { + const selectedFile = this.props.files.selected; + + if (selectedFile == null) { + return; + } + + const selectedHandle = this.props.files.handles[selectedFile]; + + if (selectedHandle == null) { + return; + } + + const completions = this.props.workspace.completions( + selectedHandle, + new TyPosition(position.lineNumber, position.column), + ); + + return { + suggestions: completions.map((completion) => ({ + label: completion.label, + kind: CompletionItemKind.Variable, + insertText: completion.label, + // TODO(micha): It's unclear why this field is required for monaco but not VS Code. + // and omitting it works just fine? The LSP doesn't expose this information right now + // which is why we go with undefined for now. + range: undefined as any, + })), + }; + } + + resolveCompletionItem: undefined; + provideInlayHints( _model: editor.ITextModel, range: Range, @@ -194,7 +238,7 @@ class PlaygroundServer const inlayHints = workspace.inlayHints( selectedHandle, - MonacoRangeToTyRange(range), + monacoRangeToTyRange(range), ); if (inlayHints.length === 0) { @@ -447,6 +491,7 @@ class PlaygroundServer this.typeDefinitionProviderDisposable.dispose(); this.inlayHintsDisposable.dispose(); this.formatDisposable.dispose(); + this.editorOpenerDisposable.dispose(); } } @@ -459,7 +504,7 @@ function tyRangeToMonacoRange(range: TyRange): IRange { }; } -function MonacoRangeToTyRange(range: IRange): TyRange { +function monacoRangeToTyRange(range: IRange): TyRange { return new TyRange( new TyPosition(range.startLineNumber, range.startColumn), new TyPosition(range.endLineNumber, range.endColumn), From f23d2c9b9e600cef08d6ed797cba4d335fe7772b Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:09:51 +0200 Subject: [PATCH 318/487] [ty] Support using legacy typing aliases for generic classes in type annotations (#18404) Co-authored-by: Alex Waygood --- .../annotations/stdlib_typing_aliases.md | 78 ++++++++--- .../resources/mdtest/generics/builtins.md | 7 +- crates/ty_python_semantic/src/types/class.rs | 6 +- crates/ty_python_semantic/src/types/infer.rs | 130 +++++++++++++----- 4 files changed, 158 insertions(+), 63 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md index 0801b2d0dbcd01..990fbe33fdbfd7 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md @@ -31,41 +31,81 @@ def f( ordered_dict_parametrized: typing.OrderedDict[int, str], ): reveal_type(list_bare) # revealed: list[Unknown] - # TODO: revealed: list[int] - reveal_type(list_parametrized) # revealed: list[Unknown] + reveal_type(list_parametrized) # revealed: list[int] reveal_type(dict_bare) # revealed: dict[Unknown, Unknown] - # TODO: revealed: dict[int, str] - reveal_type(dict_parametrized) # revealed: dict[Unknown, Unknown] + reveal_type(dict_parametrized) # revealed: dict[int, str] reveal_type(set_bare) # revealed: set[Unknown] - # TODO: revealed: set[int] - reveal_type(set_parametrized) # revealed: set[Unknown] + reveal_type(set_parametrized) # revealed: set[int] - # TODO: revealed: frozenset[Unknown] reveal_type(frozen_set_bare) # revealed: frozenset[Unknown] - # TODO: revealed: frozenset[str] - reveal_type(frozen_set_parametrized) # revealed: frozenset[Unknown] + reveal_type(frozen_set_parametrized) # revealed: frozenset[str] reveal_type(chain_map_bare) # revealed: ChainMap[Unknown, Unknown] - # TODO: revealed: ChainMap[str, int] - reveal_type(chain_map_parametrized) # revealed: ChainMap[Unknown, Unknown] + reveal_type(chain_map_parametrized) # revealed: ChainMap[str, int] reveal_type(counter_bare) # revealed: Counter[Unknown] - # TODO: revealed: Counter[int] - reveal_type(counter_parametrized) # revealed: Counter[Unknown] + reveal_type(counter_parametrized) # revealed: Counter[int] reveal_type(default_dict_bare) # revealed: defaultdict[Unknown, Unknown] - # TODO: revealed: defaultdict[str, int] - reveal_type(default_dict_parametrized) # revealed: defaultdict[Unknown, Unknown] + reveal_type(default_dict_parametrized) # revealed: defaultdict[str, int] reveal_type(deque_bare) # revealed: deque[Unknown] - # TODO: revealed: deque[str] - reveal_type(deque_parametrized) # revealed: deque[Unknown] + reveal_type(deque_parametrized) # revealed: deque[str] reveal_type(ordered_dict_bare) # revealed: OrderedDict[Unknown, Unknown] - # TODO: revealed: OrderedDict[int, str] - reveal_type(ordered_dict_parametrized) # revealed: OrderedDict[Unknown, Unknown] + reveal_type(ordered_dict_parametrized) # revealed: OrderedDict[int, str] +``` + +## Incorrect number of type arguments + +In case the incorrect number of type arguments is passed, a diagnostic is given. + +```py +import typing + +def f( + # error: [invalid-type-form] "Legacy alias `typing.List` expected exactly 1 argument, got 2" + incorrect_list: typing.List[int, int], + # error: [invalid-type-form] "Legacy alias `typing.Dict` expected exactly 2 arguments, got 3" + incorrect_dict: typing.Dict[int, int, int], + # error: [invalid-type-form] "Legacy alias `typing.Dict` expected exactly 2 arguments, got 1" + incorrect_dict2: typing.Dict[int], # type argument is not a tuple here + # error: [invalid-type-form] + incorrect_set: typing.Set[int, int], + # error: [invalid-type-form] + incorrect_frozen_set: typing.FrozenSet[int, int], + # error: [invalid-type-form] + incorrect_chain_map: typing.ChainMap[int, int, int], + # error: [invalid-type-form] + incorrect_chain_map2: typing.ChainMap[int], + # error: [invalid-type-form] + incorrect_counter: typing.Counter[int, int], + # error: [invalid-type-form] + incorrect_default_dict: typing.DefaultDict[int, int, int], + # error: [invalid-type-form] + incorrect_default_dict2: typing.DefaultDict[int], + # error: [invalid-type-form] + incorrect_deque: typing.Deque[int, int], + # error: [invalid-type-form] + incorrect_ordered_dict: typing.OrderedDict[int, int, int], + # error: [invalid-type-form] + incorrect_ordered_dict2: typing.OrderedDict[int], +): + reveal_type(incorrect_list) # revealed: list[Unknown] + reveal_type(incorrect_dict) # revealed: dict[Unknown, Unknown] + reveal_type(incorrect_dict2) # revealed: dict[Unknown, Unknown] + reveal_type(incorrect_set) # revealed: set[Unknown] + reveal_type(incorrect_frozen_set) # revealed: frozenset[Unknown] + reveal_type(incorrect_chain_map) # revealed: ChainMap[Unknown, Unknown] + reveal_type(incorrect_chain_map2) # revealed: ChainMap[Unknown, Unknown] + reveal_type(incorrect_counter) # revealed: Counter[Unknown] + reveal_type(incorrect_default_dict) # revealed: defaultdict[Unknown, Unknown] + reveal_type(incorrect_default_dict2) # revealed: defaultdict[Unknown, Unknown] + reveal_type(incorrect_deque) # revealed: deque[Unknown] + reveal_type(incorrect_ordered_dict) # revealed: OrderedDict[Unknown, Unknown] + reveal_type(incorrect_ordered_dict2) # revealed: OrderedDict[Unknown, Unknown] ``` ## Inheritance diff --git a/crates/ty_python_semantic/resources/mdtest/generics/builtins.md b/crates/ty_python_semantic/resources/mdtest/generics/builtins.md index 70b9c91902069c..e98de384ab480b 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/builtins.md @@ -24,12 +24,13 @@ class dict[K, V, Extra]: ... def reveal_type(obj, /): ... ``` -If we don't, then we won't be able to infer the types of variadic keyword arguments correctly. +If we don't, then we may get "surprising" results when inferring the types of variadic keyword +arguments. ```py def f(**kwargs): - reveal_type(kwargs) # revealed: Unknown + reveal_type(kwargs) # revealed: dict[Unknown, Unknown, Unknown] def g(**kwargs: int): - reveal_type(kwargs) # revealed: Unknown + reveal_type(kwargs) # revealed: dict[Unknown, Unknown, Unknown] ``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 50b9e8b6820e09..94158e653cd489 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2426,7 +2426,7 @@ impl<'db> KnownClass { return Type::unknown(); }; let Some(generic_context) = class_literal.generic_context(db) else { - return Type::unknown(); + return Type::instance(db, ClassType::NonGeneric(class_literal)); }; let types = specialization.into_iter().collect::>(); @@ -2437,11 +2437,11 @@ impl<'db> KnownClass { if MESSAGES.lock().unwrap().insert(self) { tracing::info!( "Wrong number of types when specializing {}. \ - Falling back to `Unknown` for the symbol instead.", + Falling back to default specialization for the symbol instead.", self.display(db) ); } - return Type::unknown(); + return Type::instance(db, class_literal.default_specialization(db)); } let specialization = generic_context.specialize(db, types); diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 2120b4a9448fbf..44b5d066167e7d 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -7975,7 +7975,7 @@ impl<'db> TypeInferenceBuilder<'db> { match value_ty { Type::SpecialForm(SpecialFormType::Annotated) => { - // This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but + // This branch is similar to the corresponding branch in `infer_parameterized_special_form_type_expression`, but // `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly // differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`). if let ast::Expr::Tuple(ast::ExprTuple { @@ -8701,6 +8701,43 @@ impl<'db> TypeInferenceBuilder<'db> { } } + fn infer_parameterized_legacy_typing_alias( + &mut self, + subscript_node: &ast::ExprSubscript, + expected_arg_count: usize, + alias: SpecialFormType, + class: KnownClass, + ) -> Type<'db> { + let arguments = &*subscript_node.slice; + let (args, args_number) = if let ast::Expr::Tuple(t) = arguments { + (Either::Left(t), t.len()) + } else { + (Either::Right([arguments]), 1) + }; + if args_number != expected_arg_count { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) { + let noun = if expected_arg_count == 1 { + "argument" + } else { + "arguments" + }; + builder.into_diagnostic(format_args!( + "Legacy alias `{alias}` expected exactly {expected_arg_count} {noun}, \ + got {args_number}", + )); + } + } + let ty = class.to_specialized_instance( + self.db(), + args.into_iter() + .map(|node| self.infer_type_expression(node)), + ); + if arguments.is_tuple_expr() { + self.store_expression_type(arguments, ty); + } + ty + } + fn infer_parameterized_special_form_type_expression( &mut self, subscript: &ast::ExprSubscript, @@ -8916,43 +8953,60 @@ impl<'db> TypeInferenceBuilder<'db> { } }, - // TODO: Generics - SpecialFormType::ChainMap => { - self.infer_type_expression(arguments_slice); - KnownClass::ChainMap.to_instance(db) - } - SpecialFormType::OrderedDict => { - self.infer_type_expression(arguments_slice); - KnownClass::OrderedDict.to_instance(db) - } - SpecialFormType::Dict => { - self.infer_type_expression(arguments_slice); - KnownClass::Dict.to_instance(db) - } - SpecialFormType::List => { - self.infer_type_expression(arguments_slice); - KnownClass::List.to_instance(db) - } - SpecialFormType::DefaultDict => { - self.infer_type_expression(arguments_slice); - KnownClass::DefaultDict.to_instance(db) - } - SpecialFormType::Counter => { - self.infer_type_expression(arguments_slice); - KnownClass::Counter.to_instance(db) - } - SpecialFormType::Set => { - self.infer_type_expression(arguments_slice); - KnownClass::Set.to_instance(db) - } - SpecialFormType::FrozenSet => { - self.infer_type_expression(arguments_slice); - KnownClass::FrozenSet.to_instance(db) - } - SpecialFormType::Deque => { - self.infer_type_expression(arguments_slice); - KnownClass::Deque.to_instance(db) - } + SpecialFormType::ChainMap => self.infer_parameterized_legacy_typing_alias( + subscript, + 2, + SpecialFormType::ChainMap, + KnownClass::ChainMap, + ), + SpecialFormType::OrderedDict => self.infer_parameterized_legacy_typing_alias( + subscript, + 2, + SpecialFormType::OrderedDict, + KnownClass::OrderedDict, + ), + SpecialFormType::Dict => self.infer_parameterized_legacy_typing_alias( + subscript, + 2, + SpecialFormType::Dict, + KnownClass::Dict, + ), + SpecialFormType::List => self.infer_parameterized_legacy_typing_alias( + subscript, + 1, + SpecialFormType::List, + KnownClass::List, + ), + SpecialFormType::DefaultDict => self.infer_parameterized_legacy_typing_alias( + subscript, + 2, + SpecialFormType::DefaultDict, + KnownClass::DefaultDict, + ), + SpecialFormType::Counter => self.infer_parameterized_legacy_typing_alias( + subscript, + 1, + SpecialFormType::Counter, + KnownClass::Counter, + ), + SpecialFormType::Set => self.infer_parameterized_legacy_typing_alias( + subscript, + 1, + SpecialFormType::Set, + KnownClass::Set, + ), + SpecialFormType::FrozenSet => self.infer_parameterized_legacy_typing_alias( + subscript, + 1, + SpecialFormType::FrozenSet, + KnownClass::FrozenSet, + ), + SpecialFormType::Deque => self.infer_parameterized_legacy_typing_alias( + subscript, + 1, + SpecialFormType::Deque, + KnownClass::Deque, + ), SpecialFormType::ReadOnly => { self.infer_type_expression(arguments_slice); From 628bb2cd1d3a75d94aa7753658b5ccbd982ef4ad Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 3 Jun 2025 16:09:33 +0300 Subject: [PATCH 319/487] [`pyupgrade`] Make fix unsafe if it deletes comments (`UP004`) (#18393) ## Summary https://github.com/astral-sh/ruff/issues/18387#issuecomment-2923039331 ## Test Plan update snapshots --- .../rules/useless_object_inheritance.rs | 16 +++++++++++--- ...er__rules__pyupgrade__tests__UP004.py.snap | 22 +++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs index 4502cbfeb40c2e..59345bf7f0c899 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -1,3 +1,4 @@ +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -61,14 +62,23 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm }, base.range(), ); + diagnostic.try_set_fix(|| { - remove_argument( + let edit = remove_argument( base, arguments, Parentheses::Remove, checker.locator().contents(), - ) - .map(Fix::safe_edit) + )?; + + let range = edit.range(); + let applicability = if checker.comment_ranges().intersects(range) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + Ok(Fix::applicable_edit(edit, applicability)) }); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP004.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP004.py.snap index 7c4eabfa7bd7db..07d46783e26ad2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP004.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP004.py.snap @@ -51,7 +51,7 @@ UP004.py:16:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 12 12 | ... 13 13 | 14 14 | @@ -75,7 +75,7 @@ UP004.py:24:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 19 19 | ... 20 20 | 21 21 | @@ -99,7 +99,7 @@ UP004.py:31:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 26 26 | ... 27 27 | 28 28 | @@ -122,7 +122,7 @@ UP004.py:37:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 33 33 | ... 34 34 | 35 35 | @@ -146,7 +146,7 @@ UP004.py:45:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 40 40 | ... 41 41 | 42 42 | @@ -171,7 +171,7 @@ UP004.py:53:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 48 48 | ... 49 49 | 50 50 | @@ -196,7 +196,7 @@ UP004.py:61:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 56 56 | ... 57 57 | 58 58 | @@ -221,7 +221,7 @@ UP004.py:69:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 64 64 | ... 65 65 | 66 66 | @@ -320,7 +320,7 @@ UP004.py:98:5: UP004 [*] Class `B` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 95 95 | 96 96 | 97 97 | class B( @@ -381,7 +381,7 @@ UP004.py:125:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 121 121 | ... 122 122 | 123 123 | @@ -403,7 +403,7 @@ UP004.py:131:5: UP004 [*] Class `A` inherits from `object` | = help: Remove `object` inheritance -ℹ Safe fix +ℹ Unsafe fix 127 127 | ... 128 128 | 129 129 | From 03f1f8e2181408c3dd310d864dcfb9c7d2b7bc03 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 3 Jun 2025 16:10:15 +0300 Subject: [PATCH 320/487] [`pyupgrade`] Make fix unsafe if it deletes comments (`UP050`) (#18390) ## Summary /closes #18387 ## Test Plan update snapshots --- .../rules/useless_class_metaclass_type.rs | 15 ++++++++++++--- ...linter__rules__pyupgrade__tests__UP050.py.snap | 12 ++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs index 9e364e55b3226c..ea56ff01206194 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs @@ -1,6 +1,7 @@ use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; use crate::{Fix, FixAvailability, Violation}; +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::StmtClassDef; use ruff_text_size::Ranged; @@ -63,13 +64,21 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl ); diagnostic.try_set_fix(|| { - remove_argument( + let edit = remove_argument( keyword, arguments, Parentheses::Remove, checker.locator().contents(), - ) - .map(Fix::safe_edit) + )?; + + let range = edit.range(); + let applicability = if checker.comment_ranges().intersects(range) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + Ok(Fix::applicable_edit(edit, applicability)) }); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap index 73074b663ee318..1d4e1af6df8558 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP050.py.snap @@ -51,7 +51,7 @@ UP050.py:16:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 12 12 | ... 13 13 | 14 14 | @@ -75,7 +75,7 @@ UP050.py:24:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 19 19 | ... 20 20 | 21 21 | @@ -98,7 +98,7 @@ UP050.py:30:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 26 26 | ... 27 27 | 28 28 | @@ -122,7 +122,7 @@ UP050.py:38:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 33 33 | ... 34 34 | 35 35 | @@ -185,7 +185,7 @@ UP050.py:58:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 54 54 | 55 55 | class B( 56 56 | A, @@ -205,7 +205,7 @@ UP050.py:69:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant | = help: Remove `metaclass=type` -ℹ Safe fix +ℹ Unsafe fix 65 65 | ... 66 66 | 67 67 | From 0986edf42775337ef8eeaa84be4f33db6aba1480 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 3 Jun 2025 15:22:00 +0200 Subject: [PATCH 321/487] [ty] Meta-type of type variables should be type[..] (#18439) ## Summary Came across this while debugging some ecosystem changes in https://github.com/astral-sh/ruff/pull/18347. I think the meta-type of a typevar-annotated variable should be equal to `type`, not ``. ## Test Plan New Markdown tests. --- .../mdtest/generics/pep695/variables.md | 19 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index ecee87ec86fc19..8d64ed34a78739 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -766,4 +766,23 @@ def constrained[T: (Callable[[], int], Callable[[], str])](f: T): reveal_type(f()) # revealed: int | str ``` +## Meta-type + +The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the +meta-types of the constraints: + +```py +def normal[T](x: T): + reveal_type(type(x)) # revealed: type + +def bound_object[T: object](x: T): + reveal_type(type(x)) # revealed: type + +def bound_int[T: int](x: T): + reveal_type(type(x)) # revealed: type[int] + +def constrained[T: (int, str)](x: T): + reveal_type(type(x)) # revealed: type[int] | type[str] +``` + [pep 695]: https://peps.python.org/pep-0695/ diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f58bd2412b7ddf..dc5450245fea24 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5243,7 +5243,7 @@ impl<'db> Type<'db> { Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) { - None => KnownClass::Object.to_class_literal(db), + None => KnownClass::Type.to_instance(db), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db), Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { // TODO: If we add a proper `OneOf` connector, we should use that here instead From 8d98c601d8fb0c7fafe503c6fa22427357eb5080 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 3 Jun 2025 19:17:47 +0530 Subject: [PATCH 322/487] [ty] Infer `list[T]` when unpacking non-tuple type (#18438) ## Summary Follow-up from #18401, I was looking at whether that would fix the issue at https://github.com/astral-sh/ty/issues/247#issuecomment-2917656676 and it didn't, which made me realize that the PR only inferred `list[T]` when the value type was tuple but it could be other types as well. This PR fixes the actual issue by inferring `list[T]` for the non-tuple type case. ## Test Plan Add test cases for starred expression involved with non-tuple type. I also added a few test cases for list type and list literal. I also verified that the example in the linked issue comment works: ```py def _(line: str): a, b, *c = line.split(maxsplit=2) c.pop() ``` --- .../resources/mdtest/unpacking.md | 69 ++++++++++++++++++- .../ty_python_semantic/src/types/unpacker.rs | 11 ++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/unpacking.md b/crates/ty_python_semantic/resources/mdtest/unpacking.md index 93d5c314f607a5..ac94dd99feb538 100644 --- a/crates/ty_python_semantic/resources/mdtest/unpacking.md +++ b/crates/ty_python_semantic/resources/mdtest/unpacking.md @@ -1,8 +1,8 @@ # Unpacking -If there are not enough or too many values ​​when unpacking, an error will occur and the types of -all variables (if nested tuple unpacking fails, only the variables within the failed tuples) is -inferred to be `Unknown`. +If there are not enough or too many values when unpacking, an error will occur and the types of all +variables (if nested tuple unpacking fails, only the variables within the failed tuples) is inferred +to be `Unknown`. ## Tuple @@ -207,6 +207,57 @@ reveal_type(c) # revealed: int reveal_type(d) # revealed: Literal[2] ``` +## List + +### Literal unpacking + +```py +a, b = [1, 2] +# TODO: should be `int` for both `a` and `b` +reveal_type(a) # revealed: Unknown +reveal_type(b) # revealed: Unknown +``` + +### Simple unpacking + +```py +def _(value: list[int]): + a, b = value + reveal_type(a) # revealed: int + reveal_type(b) # revealed: int +``` + +### Nested unpacking + +```py +def _(value: list[list[int]]): + a, (b, c) = value + reveal_type(a) # revealed: list[int] + reveal_type(b) # revealed: int + reveal_type(c) # revealed: int +``` + +### Invalid nested unpacking + +```py +def _(value: list[int]): + # error: [not-iterable] "Object of type `int` is not iterable" + a, (b, c) = value + reveal_type(a) # revealed: int + reveal_type(b) # revealed: Unknown + reveal_type(c) # revealed: Unknown +``` + +### Starred expression + +```py +def _(value: list[int]): + a, *b, c = value + reveal_type(a) # revealed: int + reveal_type(b) # revealed: list[int] + reveal_type(c) # revealed: int +``` + ## String ### Simple unpacking @@ -293,6 +344,18 @@ reveal_type(b) # revealed: LiteralString reveal_type(c) # revealed: list[LiteralString] ``` +### Starred expression (6) + +```py +from typing_extensions import LiteralString + +def _(s: LiteralString): + a, b, *c = s + reveal_type(a) # revealed: LiteralString + reveal_type(b) # revealed: LiteralString + reveal_type(c) # revealed: list[LiteralString] +``` + ### Unicode ```py diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index d790a872dd28d4..07c10ce6835c11 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -192,8 +192,15 @@ impl<'db> Unpacker<'db> { err.fallback_element_type(self.db()) }) }; - for target_type in &mut target_types { - target_type.push(ty); + // Both `elts` and `target_types` are guaranteed to have the same length. + for (element, target_type) in elts.iter().zip(&mut target_types) { + if element.is_starred_expr() { + target_type.push( + KnownClass::List.to_specialized_instance(self.db(), [ty]), + ); + } else { + target_type.push(ty); + } } } } From 2c3b3d32301974d964a4c92a88d493de05125f5d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Jun 2025 10:59:31 -0400 Subject: [PATCH 323/487] [ty] Create separate `FunctionLiteral` and `FunctionType` types (#18360) This updates our representation of functions to more closely match our representation of classes. The new `OverloadLiteral` and `FunctionLiteral` classes represent a function definition in the AST. If a function is generic, this is unspecialized. `FunctionType` has been updated to represent a function type, which is specialized if the function is generic. (These names are chosen to match `ClassLiteral` and `ClassType` on the class side.) This PR does not add a separate `Type` variant for `FunctionLiteral`. Maybe we should? Possibly as a follow-on PR? Part of https://github.com/astral-sh/ty/issues/462 --------- Co-authored-by: Micha Reiser --- crates/ty/docs/rules.md | 108 +- crates/ty_python_semantic/src/types.rs | 810 +------------- .../ty_python_semantic/src/types/call/bind.rs | 150 +-- crates/ty_python_semantic/src/types/class.rs | 10 +- .../ty_python_semantic/src/types/context.rs | 3 +- .../src/types/diagnostic.rs | 3 +- .../ty_python_semantic/src/types/display.rs | 95 +- .../ty_python_semantic/src/types/function.rs | 996 ++++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 67 +- crates/ty_python_semantic/src/types/narrow.rs | 3 +- .../src/types/protocol_class.rs | 5 +- .../src/types/signatures.rs | 51 +- 12 files changed, 1280 insertions(+), 1021 deletions(-) create mode 100644 crates/ty_python_semantic/src/types/function.rs diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 30fe9952314334..731d08c6452ca7 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873) ## `invalid-type-variable-constraints` @@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897) ## `missing-argument` @@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) ## `no-matching-overload` @@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945) ## `non-subscriptable` @@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968) ## `not-iterable` @@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986) ## `parameter-already-assigned` @@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037) ## `raw-string-type-annotation` @@ -1231,7 +1231,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373) ## `subclass-of-final-class` @@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128) ## `too-many-positional-arguments` @@ -1285,7 +1285,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173) ## `type-assertion-failure` @@ -1312,7 +1312,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1356,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194) ## `unknown-argument` @@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) ## `unresolved-attribute` @@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) ## `unresolved-import` @@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) ## `unresolved-reference` @@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) ## `unsupported-bool-conversion` @@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006) ## `unsupported-operator` @@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332) ## `zero-stepsize-in-slice` @@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354) ## `invalid-ignore-comment` @@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058) ## `possibly-unbound-implicit-call` @@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111) ## `possibly-unbound-import` @@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) ## `redundant-cast` @@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425) ## `undefined-reveal` @@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233) ## `unknown-rule` @@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489) ## `division-by-zero` @@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240) ## `possibly-unresolved-reference` @@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dc5450245fea24..aec03a73a6f2c3 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2,7 +2,6 @@ use infer::nearest_enclosing_class; use itertools::Either; use std::slice::Iter; -use std::str::FromStr; use bitflags::bitflags; use call::{CallDunderError, CallError, CallErrorKind}; @@ -14,7 +13,7 @@ use diagnostic::{ use ruff_db::diagnostic::{ Annotation, Severity, Span, SubDiagnostic, create_semantic_syntax_diagnostic, }; -use ruff_db::files::{File, FileRange}; +use ruff_db::files::File; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; @@ -28,23 +27,23 @@ pub(crate) use self::infer::{ infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types, infer_scope_types, }; -pub(crate) use self::narrow::ClassInfoConstraintFunction; pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; -use crate::module_resolver::{KnownModule, file_to_module, resolve_module}; -use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId}; +use crate::module_resolver::{KnownModule, resolve_module}; +use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; -use crate::symbol::{ - Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings, -}; +use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers, imported_symbol}; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; +use crate::types::function::{ + DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, +}; use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; @@ -64,6 +63,7 @@ mod class_base; mod context; mod diagnostic; mod display; +mod function; mod generics; mod ide_support; mod infer; @@ -432,26 +432,6 @@ impl From for DataclassParams { } } -bitflags! { - /// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the - /// arguments that were passed in. For the precise meaning of the fields, see [1]. - /// - /// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] - pub struct DataclassTransformerParams: u8 { - const EQ_DEFAULT = 0b0000_0001; - const ORDER_DEFAULT = 0b0000_0010; - const KW_ONLY_DEFAULT = 0b0000_0100; - const FROZEN_DEFAULT = 0b0000_1000; - } -} - -impl Default for DataclassTransformerParams { - fn default() -> Self { - Self::EQ_DEFAULT - } -} - /// Representation of a type: a set of possible values at runtime. /// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] @@ -7040,710 +7020,6 @@ impl From for Truthiness { } } -bitflags! { - #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)] - pub struct FunctionDecorators: u8 { - /// `@classmethod` - const CLASSMETHOD = 1 << 0; - /// `@typing.no_type_check` - const NO_TYPE_CHECK = 1 << 1; - /// `@typing.overload` - const OVERLOAD = 1 << 2; - /// `@abc.abstractmethod` - const ABSTRACT_METHOD = 1 << 3; - /// `@typing.final` - const FINAL = 1 << 4; - /// `@typing.override` - const OVERRIDE = 1 << 6; - } -} - -/// A function signature, which optionally includes an implementation signature if the function is -/// overloaded. -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct FunctionSignature<'db> { - pub(crate) overloads: CallableSignature<'db>, - pub(crate) implementation: Option>, -} - -impl<'db> FunctionSignature<'db> { - /// Returns the "bottom" signature (subtype of all fully-static signatures.) - pub(crate) fn bottom(db: &'db dyn Db) -> Self { - FunctionSignature { - overloads: CallableSignature::single(Signature::bottom(db)), - implementation: None, - } - } -} - -/// An overloaded function. -/// -/// This is created by the [`to_overloaded`] method on [`FunctionType`]. -/// -/// [`to_overloaded`]: FunctionType::to_overloaded -#[derive(Debug, PartialEq, Eq, salsa::Update)] -struct OverloadedFunction<'db> { - /// The overloads of this function. - overloads: Vec>, - /// The implementation of this overloaded function, if any. - implementation: Option>, -} - -impl<'db> OverloadedFunction<'db> { - /// Returns an iterator over all overloads and the implementation, in that order. - fn all(&self) -> impl Iterator> + '_ { - self.overloads.iter().copied().chain(self.implementation) - } -} - -/// # Ordering -/// Ordering is based on the function type's salsa-assigned id and not on its values. -/// The id may change between runs, or when the function type was garbage collected and recreated. -#[salsa::interned(debug)] -#[derive(PartialOrd, Ord)] -pub struct FunctionType<'db> { - /// Name of the function at definition. - #[returns(ref)] - pub name: ast::name::Name, - - /// Is this a function that we special-case somehow? If so, which one? - known: Option, - - /// The scope that's created by the function, in which the function body is evaluated. - body_scope: ScopeId<'db>, - - /// A set of special decorators that were applied to this function - decorators: FunctionDecorators, - - /// The arguments to `dataclass_transformer`, if this function was annotated - /// with `@dataclass_transformer(...)`. - dataclass_transformer_params: Option, - - /// The inherited generic context, if this function is a class method being used to infer the - /// specialization of its generic class. If the method is itself generic, this is in addition - /// to its own generic context. - inherited_generic_context: Option>, - - /// Type mappings that should be applied to the function's parameter and return types. - type_mappings: Box<[TypeMapping<'db, 'db>]>, -} - -#[salsa::tracked] -impl<'db> FunctionType<'db> { - /// Returns the [`File`] in which this function is defined. - pub(crate) fn file(self, db: &'db dyn Db) -> File { - // NOTE: Do not use `self.definition(db).file(db)` here, as that could create a - // cross-module dependency on the full AST. - self.body_scope(db).file(db) - } - - pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { - self.decorators(db).contains(decorator) - } - - /// Convert the `FunctionType` into a [`Type::Callable`]. - pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::new( - db, - self.signature(db).overloads.clone(), - false, - )) - } - - /// Convert the `FunctionType` into a [`Type::BoundMethod`]. - pub(crate) fn into_bound_method_type( - self, - db: &'db dyn Db, - self_instance: Type<'db>, - ) -> Type<'db> { - Type::BoundMethod(BoundMethodType::new(db, self, self_instance)) - } - - /// Returns the AST node for this function. - pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { - debug_assert_eq!( - file, - self.file(db), - "FunctionType::node() must be called with the same file as the one where \ - the function is defined." - ); - - self.body_scope(db).node(db).expect_function() - } - - /// Returns the [`FileRange`] of the function's name. - pub fn focus_range(self, db: &dyn Db) -> FileRange { - FileRange::new( - self.file(db), - self.body_scope(db).node(db).expect_function().name.range, - ) - } - - pub fn full_range(self, db: &dyn Db) -> FileRange { - FileRange::new( - self.file(db), - self.body_scope(db).node(db).expect_function().range, - ) - } - - /// Returns the [`Definition`] of this function. - /// - /// ## Warning - /// - /// This uses the semantic index to find the definition of the function. This means that if the - /// calling query is not in the same file as this function is defined in, then this will create - /// a cross-module dependency directly on the full AST which will lead to cache - /// over-invalidation. - pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { - let body_scope = self.body_scope(db); - let index = semantic_index(db, body_scope.file(db)); - index.expect_single_definition(body_scope.node(db).expect_function()) - } - - /// Typed externally-visible signature for this function. - /// - /// This is the signature as seen by external callers, possibly modified by decorators and/or - /// overloaded. - /// - /// ## Why is this a salsa query? - /// - /// This is a salsa query to short-circuit the invalidation - /// when the function's AST node changes. - /// - /// Were this not a salsa query, then the calling query - /// would depend on the function's AST and rerun for every change in that file. - #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] - pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> { - let inherited_generic_context = self.inherited_generic_context(db); - let type_mappings = self.type_mappings(db); - if let Some(overloaded) = self.to_overloaded(db) { - FunctionSignature { - overloads: CallableSignature::from_overloads( - overloaded.overloads.iter().copied().map(|overload| { - type_mappings.iter().fold( - overload.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ) - }), - ), - implementation: overloaded.implementation.map(|implementation| { - type_mappings.iter().fold( - implementation.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ) - }), - } - } else { - FunctionSignature { - overloads: CallableSignature::single(type_mappings.iter().fold( - self.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - )), - implementation: None, - } - } - } - - /// Typed internally-visible signature for this function. - /// - /// This represents the annotations on the function itself, unmodified by decorators and - /// overloads. - /// - /// These are the parameter and return types that should be used for type checking the body of - /// the function. - /// - /// Don't call this when checking any other file; only when type-checking the function body - /// scope. - fn internal_signature( - self, - db: &'db dyn Db, - inherited_generic_context: Option>, - ) -> Signature<'db> { - let scope = self.body_scope(db); - let function_stmt_node = scope.node(db).expect_function(); - let definition = self.definition(db); - 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, type_params) - }); - Signature::from_function( - db, - generic_context, - inherited_generic_context, - definition, - function_stmt_node, - ) - } - - pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool { - self.known(db) == Some(known_function) - } - - fn with_dataclass_transformer_params( - self, - db: &'db dyn Db, - params: DataclassTransformerParams, - ) -> Self { - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - Some(params), - self.inherited_generic_context(db), - self.type_mappings(db), - ) - } - - fn with_inherited_generic_context( - self, - db: &'db dyn Db, - inherited_generic_context: GenericContext<'db>, - ) -> Self { - // A function cannot inherit more than one generic context from its containing class. - debug_assert!(self.inherited_generic_context(db).is_none()); - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - Some(inherited_generic_context), - self.type_mappings(db), - ) - } - - fn with_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { - let type_mappings: Box<[_]> = self - .type_mappings(db) - .iter() - .cloned() - .chain(std::iter::once(type_mapping.to_owned())) - .collect(); - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - self.inherited_generic_context(db), - type_mappings, - ) - } - - fn find_legacy_typevars( - self, - db: &'db dyn Db, - typevars: &mut FxOrderSet>, - ) { - let signatures = self.signature(db); - for signature in &signatures.overloads { - signature.find_legacy_typevars(db, typevars); - } - } - - /// Returns `self` as [`OverloadedFunction`] if it is overloaded, [`None`] otherwise. - /// - /// ## Note - /// - /// The way this method works only allows us to "see" the overloads that are defined before - /// this function definition. This is because the semantic model records a use for each - /// function on the name node which is used to get the previous function definition with the - /// same name. This means that [`OverloadedFunction`] would only include the functions that - /// comes before this function definition. Consider the following example: - /// - /// ```py - /// from typing import overload - /// - /// @overload - /// def foo() -> None: ... - /// @overload - /// def foo(x: int) -> int: ... - /// def foo(x: int | None) -> int | None: - /// return x - /// ``` - /// - /// Here, when the `to_overloaded` method is invoked on the - /// 1. first `foo` definition, it would only contain a single overload which is itself and no - /// implementation - /// 2. second `foo` definition, it would contain both overloads and still no implementation - /// 3. third `foo` definition, it would contain both overloads and the implementation which is - /// itself - #[salsa::tracked(returns(as_ref))] - fn to_overloaded(self, db: &'db dyn Db) -> Option> { - let mut current = self; - let mut overloads = vec![]; - - loop { - // The semantic model records a use for each function on the name node. This is used - // here to get the previous function definition with the same name. - let scope = current.definition(db).scope(db); - let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db)); - let use_id = current - .body_scope(db) - .node(db) - .expect_function() - .name - .scoped_use_id(db, scope); - - let Symbol::Type(Type::FunctionLiteral(previous), Boundness::Bound) = - symbol_from_bindings(db, use_def.bindings_at_use(use_id)) - else { - break; - }; - - if previous.has_known_decorator(db, FunctionDecorators::OVERLOAD) { - overloads.push(previous); - } else { - break; - } - - current = previous; - } - - // Overloads are inserted in reverse order, from bottom to top. - overloads.reverse(); - - let implementation = if self.has_known_decorator(db, FunctionDecorators::OVERLOAD) { - overloads.push(self); - None - } else { - Some(self) - }; - - if overloads.is_empty() { - None - } else { - Some(OverloadedFunction { - overloads, - implementation, - }) - } - } - - fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - // A function literal is the subtype of itself, and not of any other function literal. - // However, our representation of a function literal includes any specialization that - // should be applied to the signature. Different specializations of the same function - // literal are only subtypes of each other if they result in subtype signatures. - self.normalized(db) == other.normalized(db) - || (self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_subtype_of(db, other.into_callable_type(db))) - } - - fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - // A function literal is assignable to itself, and not to any other function literal. - // However, our representation of a function literal includes any specialization that - // should be applied to the signature. Different specializations of the same function - // literal are only assignable to each other if they result in assignable signatures. - self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_assignable_to(db, other.into_callable_type(db)) - } - - fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - self.normalized(db) == other.normalized(db) - || (self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_equivalent_to(db, other.into_callable_type(db))) - } - - fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_gradual_equivalent_to(db, other.into_callable_type(db)) - } - - fn normalized(self, db: &'db dyn Db) -> Self { - let context = self - .inherited_generic_context(db) - .map(|ctx| ctx.normalized(db)); - - let mappings: Box<_> = self - .type_mappings(db) - .iter() - .map(|mapping| mapping.normalized(db)) - .collect(); - - Self::new( - db, - self.name(db), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - context, - mappings, - ) - } - - /// Returns a tuple of two spans. The first is - /// the span for the identifier of the function - /// definition for `self`. The second is - /// the span for the parameter in the function - /// definition for `self`. - /// - /// If there are no meaningful spans, then this - /// returns `None`. For example, when this type - /// isn't callable. - /// - /// When `parameter_index` is `None`, then the - /// second span returned covers the entire parameter - /// list. - /// - /// # Performance - /// - /// Note that this may introduce cross-module - /// dependencies. This can have an impact on - /// the effectiveness of incremental caching - /// and should therefore be used judiciously. - /// - /// An example of a good use case is to improve - /// a diagnostic. - fn parameter_span( - self, - db: &'db dyn Db, - parameter_index: Option, - ) -> Option<(Span, Span)> { - let function_scope = self.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let range = parameter_index - .and_then(|parameter_index| { - func_def - .parameters - .iter() - .nth(parameter_index) - .map(|param| param.range()) - }) - .unwrap_or(func_def.parameters.range); - let name_span = span.clone().with_range(func_def.name.range); - let parameter_span = span.with_range(range); - Some((name_span, parameter_span)) - } - - /// Returns a collection of useful spans for a - /// function signature. These are useful for - /// creating annotations on diagnostics. - /// - /// # Performance - /// - /// Note that this may introduce cross-module - /// dependencies. This can have an impact on - /// the effectiveness of incremental caching - /// and should therefore be used judiciously. - /// - /// An example of a good use case is to improve - /// a diagnostic. - fn spans(self, db: &'db dyn Db) -> Option { - let function_scope = self.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - 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); - if let Some(return_type_range) = return_type_range { - signature = signature.cover(return_type_range); - } - Some(FunctionSpans { - signature: span.clone().with_range(signature), - name: span.clone().with_range(func_def.name.range), - parameters: span.clone().with_range(func_def.parameters.range), - return_type: return_type_range.map(|range| span.clone().with_range(range)), - }) - } -} - -/// A collection of useful spans for annotating functions. -/// -/// This can be retrieved via `FunctionType::spans` or -/// `Type::function_spans`. -struct FunctionSpans { - /// The span of the entire function "signature." This includes - /// the name, parameter list and return type (if present). - signature: Span, - /// The span of the function name. i.e., `foo` in `def foo(): ...`. - name: Span, - /// The span of the parameter list, including the opening and - /// closing parentheses. - #[expect(dead_code)] - parameters: Span, - /// The span of the annotated return type, if present. - return_type: Option, -} - -fn signature_cycle_recover<'db>( - _db: &'db dyn Db, - _value: &FunctionSignature<'db>, - _count: u32, - _function: FunctionType<'db>, -) -> salsa::CycleRecoveryAction> { - salsa::CycleRecoveryAction::Iterate -} - -fn signature_cycle_initial<'db>( - db: &'db dyn Db, - _function: FunctionType<'db>, -) -> FunctionSignature<'db> { - FunctionSignature::bottom(db) -} - -/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might -/// have special behavior. -#[derive( - Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr, -)] -#[strum(serialize_all = "snake_case")] -#[cfg_attr(test, derive(strum_macros::EnumIter))] -pub enum KnownFunction { - /// `builtins.isinstance` - #[strum(serialize = "isinstance")] - IsInstance, - /// `builtins.issubclass` - #[strum(serialize = "issubclass")] - IsSubclass, - /// `builtins.hasattr` - #[strum(serialize = "hasattr")] - HasAttr, - /// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type` - RevealType, - /// `builtins.len` - Len, - /// `builtins.repr` - Repr, - /// `typing(_extensions).final` - Final, - - /// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check) - NoTypeCheck, - - /// `typing(_extensions).assert_type` - AssertType, - /// `typing(_extensions).assert_never` - AssertNever, - /// `typing(_extensions).cast` - Cast, - /// `typing(_extensions).overload` - Overload, - /// `typing(_extensions).override` - Override, - /// `typing(_extensions).is_protocol` - IsProtocol, - /// `typing(_extensions).get_protocol_members` - GetProtocolMembers, - /// `typing(_extensions).runtime_checkable` - RuntimeCheckable, - /// `typing(_extensions).dataclass_transform` - DataclassTransform, - - /// `abc.abstractmethod` - #[strum(serialize = "abstractmethod")] - AbstractMethod, - - /// `dataclasses.dataclass` - Dataclass, - - /// `inspect.getattr_static` - GetattrStatic, - - /// `ty_extensions.static_assert` - StaticAssert, - /// `ty_extensions.is_equivalent_to` - IsEquivalentTo, - /// `ty_extensions.is_subtype_of` - IsSubtypeOf, - /// `ty_extensions.is_assignable_to` - IsAssignableTo, - /// `ty_extensions.is_disjoint_from` - IsDisjointFrom, - /// `ty_extensions.is_gradual_equivalent_to` - IsGradualEquivalentTo, - /// `ty_extensions.is_fully_static` - IsFullyStatic, - /// `ty_extensions.is_singleton` - IsSingleton, - /// `ty_extensions.is_single_valued` - IsSingleValued, - /// `ty_extensions.generic_context` - GenericContext, - /// `ty_extensions.dunder_all_names` - DunderAllNames, - /// `ty_extensions.all_members` - AllMembers, -} - -impl KnownFunction { - pub fn into_classinfo_constraint_function(self) -> Option { - match self { - Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance), - Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass), - _ => None, - } - } - - fn try_from_definition_and_name<'db>( - db: &'db dyn Db, - definition: Definition<'db>, - name: &str, - ) -> Option { - let candidate = Self::from_str(name).ok()?; - candidate - .check_module(file_to_module(db, definition.file(db))?.known()?) - .then_some(candidate) - } - - /// Return `true` if `self` is defined in `module` at runtime. - const fn check_module(self, module: KnownModule) -> bool { - match self { - Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => { - module.is_builtins() - } - Self::AssertType - | Self::AssertNever - | Self::Cast - | Self::Overload - | Self::Override - | Self::RevealType - | Self::Final - | Self::IsProtocol - | Self::GetProtocolMembers - | Self::RuntimeCheckable - | Self::DataclassTransform - | Self::NoTypeCheck => { - matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) - } - Self::AbstractMethod => { - matches!(module, KnownModule::Abc) - } - Self::Dataclass => { - matches!(module, KnownModule::Dataclasses) - } - Self::GetattrStatic => module.is_inspect(), - Self::IsAssignableTo - | Self::IsDisjointFrom - | Self::IsEquivalentTo - | Self::IsGradualEquivalentTo - | Self::IsFullyStatic - | Self::IsSingleValued - | Self::IsSingleton - | Self::IsSubtypeOf - | Self::GenericContext - | Self::DunderAllNames - | Self::StaticAssert - | Self::AllMembers => module.is_ty_extensions(), - } - } -} - /// This type represents bound method objects that are created when a method is accessed /// on an instance of a class. For example, the expression `Path("a.txt").touch` creates /// a bound method object that represents the `Path.touch` method which is bound to the @@ -9213,15 +8489,12 @@ static_assertions::assert_eq_size!(Type, [u8; 16]); pub(crate) mod tests { use super::*; use crate::db::tests::{TestDbBuilder, setup_db}; - use crate::symbol::{ - global_symbol, known_module_symbol, typing_extensions_symbol, typing_symbol, - }; + use crate::symbol::{global_symbol, typing_extensions_symbol, typing_symbol}; use ruff_db::files::system_path_to_file; use ruff_db::parsed::parsed_module; use ruff_db::system::DbWithWritableSystem as _; use ruff_db::testing::assert_function_query_was_not_run; use ruff_python_ast::PythonVersion; - use strum::IntoEnumIterator; use test_case::test_case; /// Explicitly test for Python version <3.13 and >=3.13, to ensure that @@ -9364,69 +8637,4 @@ pub(crate) mod tests { .is_todo() ); } - - #[test] - fn known_function_roundtrip_from_str() { - let db = setup_db(); - - for function in KnownFunction::iter() { - let function_name: &'static str = function.into(); - - let module = match function { - KnownFunction::Len - | KnownFunction::Repr - | KnownFunction::IsInstance - | KnownFunction::HasAttr - | KnownFunction::IsSubclass => KnownModule::Builtins, - - KnownFunction::AbstractMethod => KnownModule::Abc, - - KnownFunction::Dataclass => KnownModule::Dataclasses, - - KnownFunction::GetattrStatic => KnownModule::Inspect, - - KnownFunction::Cast - | KnownFunction::Final - | KnownFunction::Overload - | KnownFunction::Override - | KnownFunction::RevealType - | KnownFunction::AssertType - | KnownFunction::AssertNever - | KnownFunction::IsProtocol - | KnownFunction::GetProtocolMembers - | KnownFunction::RuntimeCheckable - | KnownFunction::DataclassTransform - | KnownFunction::NoTypeCheck => KnownModule::TypingExtensions, - - KnownFunction::IsSingleton - | KnownFunction::IsSubtypeOf - | KnownFunction::GenericContext - | KnownFunction::DunderAllNames - | KnownFunction::StaticAssert - | KnownFunction::IsFullyStatic - | KnownFunction::IsDisjointFrom - | KnownFunction::IsSingleValued - | KnownFunction::IsAssignableTo - | KnownFunction::IsEquivalentTo - | KnownFunction::IsGradualEquivalentTo - | KnownFunction::AllMembers => KnownModule::TyExtensions, - }; - - let function_definition = known_module_symbol(&db, module, function_name) - .symbol - .expect_type() - .expect_function_literal() - .definition(&db); - - assert_eq!( - KnownFunction::try_from_definition_and_name( - &db, - function_definition, - function_name - ), - Some(function), - "The strum `EnumString` implementation appears to be incorrect for `{function_name}`" - ); - } - } } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 51ab005c1b8d57..b49343c12d8fbe 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -18,13 +18,13 @@ use crate::types::diagnostic::{ NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, }; +use crate::types::function::{DataclassTransformerParams, FunctionDecorators, KnownFunction}; use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError}; use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ - BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, - KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support, - todo_type, + BoundMethodType, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind, + PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, + WrapperDescriptorKind, ide_support, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -871,47 +871,47 @@ impl<'db> Bindings<'db> { } _ => { - let mut handle_dataclass_transformer_params = - |function_type: &FunctionType| { - if let Some(params) = - function_type.dataclass_transformer_params(db) - { - // This is a call to a custom function that was decorated with `@dataclass_transformer`. - // If this function was called with a keyword argument like `order=False`, we extract - // the argument type and overwrite the corresponding flag in `dataclass_params` after - // constructing them from the `dataclass_transformer`-parameter defaults. - - let mut dataclass_params = DataclassParams::from(params); - - if let Some(Some(Type::BooleanLiteral(order))) = overload - .signature - .parameters() - .keyword_by_name("order") - .map(|(idx, _)| idx) - .and_then(|idx| overload.parameter_types().get(idx)) - { - dataclass_params.set(DataclassParams::ORDER, *order); - } - - overload.set_return_type(Type::DataclassDecorator( - dataclass_params, - )); - } - }; - // Ideally, either the implementation, or exactly one of the overloads // of the function can have the dataclass_transform decorator applied. // However, we do not yet enforce this, and in the case of multiple // applications of the decorator, we will only consider the last one // for the return value, since the prior ones will be over-written. - if let Some(overloaded) = function_type.to_overloaded(db) { - overloaded - .overloads - .iter() - .for_each(&mut handle_dataclass_transformer_params); - } + let return_type = function_type + .iter_overloads_and_implementation(db) + .filter_map(|function_overload| { + function_overload.dataclass_transformer_params(db).map( + |params| { + // This is a call to a custom function that was decorated with `@dataclass_transformer`. + // If this function was called with a keyword argument like `order=False`, we extract + // the argument type and overwrite the corresponding flag in `dataclass_params` after + // constructing them from the `dataclass_transformer`-parameter defaults. + + let mut dataclass_params = + DataclassParams::from(params); + + if let Some(Some(Type::BooleanLiteral(order))) = + overload + .signature + .parameters() + .keyword_by_name("order") + .map(|(idx, _)| idx) + .and_then(|idx| { + overload.parameter_types().get(idx) + }) + { + dataclass_params + .set(DataclassParams::ORDER, *order); + } - handle_dataclass_transformer_params(&function_type); + Type::DataclassDecorator(dataclass_params) + }, + ) + }) + .last(); + + if let Some(return_type) = return_type { + overload.set_return_type(return_type); + } } }, @@ -1261,47 +1261,49 @@ impl<'db> CallableBinding<'db> { _ => None, }; if let Some((kind, function)) = function_type_and_kind { - if let Some(overloaded_function) = function.to_overloaded(context.db()) { - if let Some(spans) = overloaded_function - .overloads - .first() - .and_then(|overload| overload.spans(context.db())) - { - let mut sub = - SubDiagnostic::new(Severity::Info, "First overload defined here"); - sub.annotate(Annotation::primary(spans.signature)); - diag.sub(sub); - } + let (overloads, implementation) = + function.overloads_and_implementation(context.db()); + + if let Some(spans) = overloads + .first() + .and_then(|overload| overload.spans(context.db())) + { + let mut sub = + SubDiagnostic::new(Severity::Info, "First overload defined here"); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); + } + diag.info(format_args!( + "Possible overloads for {kind} `{}`:", + function.name(context.db()) + )); + + for overload in overloads.iter().take(MAXIMUM_OVERLOADS) { diag.info(format_args!( - "Possible overloads for {kind} `{}`:", - function.name(context.db()) + " {}", + overload.signature(context.db(), None).display(context.db()) )); + } + if overloads.len() > MAXIMUM_OVERLOADS { + diag.info(format_args!( + "... omitted {remaining} overloads", + remaining = overloads.len() - MAXIMUM_OVERLOADS + )); + } - let overloads = &function.signature(context.db()).overloads.overloads; - for overload in overloads.iter().take(MAXIMUM_OVERLOADS) { - diag.info(format_args!(" {}", overload.display(context.db()))); - } - if overloads.len() > MAXIMUM_OVERLOADS { - diag.info(format_args!( - "... omitted {remaining} overloads", - remaining = overloads.len() - MAXIMUM_OVERLOADS - )); - } - - if let Some(spans) = overloaded_function - .implementation - .and_then(|function| function.spans(context.db())) - { - let mut sub = SubDiagnostic::new( - Severity::Info, - "Overload implementation defined here", - ); - sub.annotate(Annotation::primary(spans.signature)); - diag.sub(sub); - } + if let Some(spans) = + implementation.and_then(|function| function.spans(context.db())) + { + let mut sub = SubDiagnostic::new( + Severity::Info, + "Overload implementation defined here", + ); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); } } + if let Some(union_diag) = union_diag { union_diag.add_union_context(context.db(), &mut diag); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 94158e653cd489..bcf6c6e1b7438f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2,17 +2,17 @@ use std::hash::BuildHasherDefault; use std::sync::{LazyLock, Mutex}; use super::{ - IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, - SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, - infer_expression_type, infer_unpack_types, + IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType, + SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type, + infer_unpack_types, }; use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; +use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ - CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping, - TypeVarInstance, + CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index d8004122cc7a15..f0596b0318a5b5 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -11,13 +11,14 @@ use ruff_text_size::{Ranged, TextRange}; use super::{Type, TypeCheckDiagnostics, binding_type}; use crate::lint::LintSource; +use crate::semantic_index::semantic_index; use crate::semantic_index::symbol::ScopeId; +use crate::types::function::FunctionDecorators; use crate::{ Db, lint::{LintId, LintMetadata}, suppression::suppressions, }; -use crate::{semantic_index::semantic_index, types::FunctionDecorators}; /// Context for inferring the types of a single file. /// diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 1a0b13ef21d7a2..39277b142b2529 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -8,12 +8,13 @@ use super::{ use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; use crate::types::LintDiagnosticGuard; +use crate::types::function::KnownFunction; use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION, IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; -use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; +use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 6c4dd480d03e77..645a102e84b227 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -7,6 +7,7 @@ use ruff_python_ast::str::{Quote, TripleQuotes}; use ruff_python_literal::escape::AsciiEscape; use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; +use crate::types::function::{FunctionType, OverloadLiteral}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ @@ -112,34 +113,7 @@ impl Display for DisplayRepresentation<'_> { }, Type::SpecialForm(special_form) => special_form.fmt(f), Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f), - Type::FunctionLiteral(function) => { - let signature = function.signature(self.db); - - // TODO: when generic function types are supported, we should add - // the generic type parameters to the signature, i.e. - // show `def foo[T](x: T) -> T`. - - match signature.overloads.as_slice() { - [signature] => { - write!( - f, - // "def {name}{specialization}{signature}", - "def {name}{signature}", - name = function.name(self.db), - signature = signature.display(self.db) - ) - } - signatures => { - // TODO: How to display overloads? - f.write_str("Overload[")?; - let mut join = f.join(", "); - for signature in signatures { - join.entry(&signature.display(self.db)); - } - f.write_str("]") - } - } - } + Type::FunctionLiteral(function) => function.display(self.db).fmt(f), Type::Callable(callable) => callable.display(self.db).fmt(f), Type::BoundMethod(bound_method) => { let function = bound_method.function(self.db); @@ -241,6 +215,71 @@ impl Display for DisplayRepresentation<'_> { } } +impl<'db> OverloadLiteral<'db> { + // Not currently used, but useful for debugging. + #[expect(dead_code)] + pub(crate) fn display(self, db: &'db dyn Db) -> DisplayOverloadLiteral<'db> { + DisplayOverloadLiteral { literal: self, db } + } +} + +pub(crate) struct DisplayOverloadLiteral<'db> { + literal: OverloadLiteral<'db>, + db: &'db dyn Db, +} + +impl Display for DisplayOverloadLiteral<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let signature = self.literal.signature(self.db, None); + write!( + f, + "def {name}{signature}", + name = self.literal.name(self.db), + signature = signature.display(self.db) + ) + } +} + +impl<'db> FunctionType<'db> { + pub(crate) fn display(self, db: &'db dyn Db) -> DisplayFunctionType<'db> { + DisplayFunctionType { ty: self, db } + } +} + +pub(crate) struct DisplayFunctionType<'db> { + ty: FunctionType<'db>, + db: &'db dyn Db, +} + +impl Display for DisplayFunctionType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let signature = self.ty.signature(self.db); + + // TODO: We should consider adding the type parameters to the signature of a generic + // function, i.e. `def foo[T](x: T) -> T`. + + match signature.overloads.as_slice() { + [signature] => { + write!( + f, + "def {name}{signature}", + name = self.ty.name(self.db), + signature = signature.display(self.db) + ) + } + signatures => { + // TODO: How to display overloads? + f.write_str("Overload[")?; + let mut join = f.join(", "); + for signature in signatures { + join.entry(&signature.display(self.db)); + } + f.write_str("]") + } + } + } +} + impl<'db> GenericAlias<'db> { pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { DisplayGenericAlias { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs new file mode 100644 index 00000000000000..2911fbb1d630a3 --- /dev/null +++ b/crates/ty_python_semantic/src/types/function.rs @@ -0,0 +1,996 @@ +//! Contains representations of function literals. There are several complicating factors: +//! +//! - Functions can be generic, and can have specializations applied to them. These are not the +//! same thing! For instance, a method of a generic class might not itself be generic, but it can +//! still have the class's specialization applied to it. +//! +//! - Functions can be overloaded, and each overload can be independently generic or not, with +//! different sets of typevars for different generic overloads. In some cases we need to consider +//! each overload separately; in others we need to consider all of the overloads (and any +//! implementation) as a single collective entity. +//! +//! - Certain “known” functions need special treatment — for instance, inferring a special return +//! type, or raising custom diagnostics. +//! +//! - TODO: Some functions don't correspond to a function definition in the AST, and are instead +//! synthesized as we mimic the behavior of the Python interpreter. Even though they are +//! synthesized, and are “implemented” as Rust code, they are still functions from the POV of the +//! rest of the type system. +//! +//! Given these constraints, we have the following representation: a function is a list of one or +//! more overloads, with zero or more specializations (more specifically, “type mappings”) applied +//! to it. [`FunctionType`] is the outermost type, which is what [`Type::FunctionLiteral`] wraps. +//! It contains the list of type mappings to apply. It wraps a [`FunctionLiteral`], which collects +//! together all of the overloads (and implementation) of an overloaded function. An +//! [`OverloadLiteral`] represents an individual function definition in the AST — that is, each +//! overload (and implementation) of an overloaded function, or the single definition of a +//! non-overloaded function. +//! +//! Technically, each `FunctionLiteral` wraps a particular overload and all _previous_ overloads. +//! So it's only true that it wraps _all_ overloads if you are looking at the last definition. For +//! instance, in +//! +//! ```py +//! @overload +//! def f(x: int) -> None: ... +//! # <-- 1 +//! +//! @overload +//! def f(x: str) -> None: ... +//! # <-- 2 +//! +//! def f(x): pass +//! # <-- 3 +//! ``` +//! +//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which +//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that +//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so +//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads +//! (and the implementation). + +use std::str::FromStr; + +use bitflags::bitflags; +use ruff_db::diagnostic::Span; +use ruff_db::files::{File, FileRange}; +use ruff_python_ast as ast; +use ruff_text_size::Ranged; + +use crate::module_resolver::{KnownModule, file_to_module}; +use crate::semantic_index::ast_ids::HasScopedUseId; +use crate::semantic_index::definition::Definition; +use crate::semantic_index::semantic_index; +use crate::semantic_index::symbol::ScopeId; +use crate::symbol::{Boundness, Symbol, symbol_from_bindings}; +use crate::types::generics::GenericContext; +use crate::types::narrow::ClassInfoConstraintFunction; +use crate::types::signatures::{CallableSignature, Signature}; +use crate::types::{BoundMethodType, CallableType, Type, TypeMapping, TypeVarInstance}; +use crate::{Db, FxOrderSet}; + +/// A collection of useful spans for annotating functions. +/// +/// This can be retrieved via `FunctionType::spans` or +/// `Type::function_spans`. +pub(crate) struct FunctionSpans { + /// The span of the entire function "signature." This includes + /// the name, parameter list and return type (if present). + pub(crate) signature: Span, + /// The span of the function name. i.e., `foo` in `def foo(): ...`. + pub(crate) name: Span, + /// The span of the parameter list, including the opening and + /// closing parentheses. + #[expect(dead_code)] + pub(crate) parameters: Span, + /// The span of the annotated return type, if present. + pub(crate) return_type: Option, +} + +bitflags! { + #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)] + pub struct FunctionDecorators: u8 { + /// `@classmethod` + const CLASSMETHOD = 1 << 0; + /// `@typing.no_type_check` + const NO_TYPE_CHECK = 1 << 1; + /// `@typing.overload` + const OVERLOAD = 1 << 2; + /// `@abc.abstractmethod` + const ABSTRACT_METHOD = 1 << 3; + /// `@typing.final` + const FINAL = 1 << 4; + /// `@typing.override` + const OVERRIDE = 1 << 6; + } +} + +bitflags! { + /// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the + /// arguments that were passed in. For the precise meaning of the fields, see [1]. + /// + /// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] + pub struct DataclassTransformerParams: u8 { + const EQ_DEFAULT = 1 << 0; + const ORDER_DEFAULT = 1 << 1; + const KW_ONLY_DEFAULT = 1 << 2; + const FROZEN_DEFAULT = 1 << 3; + } +} + +impl Default for DataclassTransformerParams { + fn default() -> Self { + Self::EQ_DEFAULT + } +} + +/// Representation of a function definition in the AST: either a non-generic function, or a generic +/// function that has not been specialized. +/// +/// If a function has multiple overloads, each overload is represented by a separate function +/// definition in the AST, and is therefore a separate `OverloadLiteral` instance. +/// +/// # Ordering +/// Ordering is based on the function's id assigned by salsa and not on the function literal's +/// values. The id may change between runs, or when the function literal was garbage collected and +/// recreated. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct OverloadLiteral<'db> { + /// Name of the function at definition. + #[returns(ref)] + pub name: ast::name::Name, + + /// Is this a function that we special-case somehow? If so, which one? + pub(crate) known: Option, + + /// The scope that's created by the function, in which the function body is evaluated. + pub(crate) body_scope: ScopeId<'db>, + + /// A set of special decorators that were applied to this function + pub(crate) decorators: FunctionDecorators, + + /// The arguments to `dataclass_transformer`, if this function was annotated + /// with `@dataclass_transformer(...)`. + pub(crate) dataclass_transformer_params: Option, +} + +#[salsa::tracked] +impl<'db> OverloadLiteral<'db> { + fn with_dataclass_transformer_params( + self, + db: &'db dyn Db, + params: DataclassTransformerParams, + ) -> Self { + Self::new( + db, + self.name(db).clone(), + self.known(db), + self.body_scope(db), + self.decorators(db), + Some(params), + ) + } + + fn file(self, db: &'db dyn Db) -> File { + // NOTE: Do not use `self.definition(db).file(db)` here, as that could create a + // cross-module dependency on the full AST. + self.body_scope(db).file(db) + } + + pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.decorators(db).contains(decorator) + } + + pub(crate) fn is_overload(self, db: &dyn Db) -> bool { + self.has_known_decorator(db, FunctionDecorators::OVERLOAD) + } + + fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { + debug_assert_eq!( + file, + self.file(db), + "OverloadLiteral::node() must be called with the same file as the one where \ + the function is defined." + ); + + self.body_scope(db).node(db).expect_function() + } + + /// Returns the [`FileRange`] of the function's name. + pub(crate) fn focus_range(self, db: &dyn Db) -> FileRange { + FileRange::new( + self.file(db), + self.body_scope(db).node(db).expect_function().name.range, + ) + } + + /// Returns the [`Definition`] of this function. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + fn definition(self, db: &'db dyn Db) -> Definition<'db> { + let body_scope = self.body_scope(db); + let index = semantic_index(db, body_scope.file(db)); + 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 + /// previous overload. + fn previous_overload(self, db: &'db dyn Db) -> Option> { + // The semantic model records a use for each function on the name node. This is used + // here to get the previous function definition with the same name. + let scope = self.definition(db).scope(db); + let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db)); + let use_id = self + .body_scope(db) + .node(db) + .expect_function() + .name + .scoped_use_id(db, scope); + + let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) = + symbol_from_bindings(db, use_def.bindings_at_use(use_id)) + else { + return None; + }; + + let previous_literal = previous_type.literal(db); + let previous_overload = previous_literal.last_definition(db); + if !previous_overload.is_overload(db) { + return None; + } + + Some(previous_literal) + } + + /// Typed internally-visible signature for this function. + /// + /// This represents the annotations on the function itself, unmodified by decorators and + /// overloads. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + pub(crate) fn signature( + self, + db: &'db dyn Db, + inherited_generic_context: Option>, + ) -> Signature<'db> { + let scope = self.body_scope(db); + let function_stmt_node = scope.node(db).expect_function(); + let definition = self.definition(db); + 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, type_params) + }); + Signature::from_function( + db, + generic_context, + inherited_generic_context, + definition, + function_stmt_node, + ) + } + + fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + let range = parameter_index + .and_then(|parameter_index| { + func_def + .parameters + .iter() + .nth(parameter_index) + .map(|param| param.range()) + }) + .unwrap_or(func_def.parameters.range); + let name_span = span.clone().with_range(func_def.name.range); + let parameter_span = span.with_range(range); + Some((name_span, parameter_span)) + } + + pub(crate) fn spans(self, db: &'db dyn Db) -> Option { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + 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); + if let Some(return_type_range) = return_type_range { + signature = signature.cover(return_type_range); + } + Some(FunctionSpans { + signature: span.clone().with_range(signature), + name: span.clone().with_range(func_def.name.range), + parameters: span.clone().with_range(func_def.parameters.range), + return_type: return_type_range.map(|range| span.clone().with_range(range)), + }) + } +} + +/// Representation of a function definition in the AST, along with any previous overloads of the +/// function. Each overload can be separately generic or not, and each generic overload uses +/// distinct typevars. +/// +/// # Ordering +/// Ordering is based on the function's id assigned by salsa and not on the function literal's +/// values. The id may change between runs, or when the function literal was garbage collected and +/// recreated. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct FunctionLiteral<'db> { + pub(crate) last_definition: OverloadLiteral<'db>, + + /// The inherited generic context, if this function is a constructor method (`__new__` or + /// `__init__`) being used to infer the specialization of its generic class. If any of the + /// method's overloads are themselves generic, this is in addition to those per-overload + /// generic contexts (which are created lazily in [`OverloadLiteral::signature`]). + /// + /// If the function is not a constructor method, this field will always be `None`. + /// + /// If the function is a constructor method, we will end up creating two `FunctionLiteral` + /// instances for it. The first is created in [`TypeInferenceBuilder`][infer] when we encounter + /// the function definition during type inference. At this point, we don't yet know if the + /// function is a constructor method, so we create a `FunctionLiteral` with `None` for this + /// field. + /// + /// If at some point we encounter a call expression, which invokes the containing class's + /// constructor, as will create a _new_ `FunctionLiteral` instance for the function, with this + /// field [updated][] to contain the containing class's generic context. + /// + /// [infer]: crate::types::infer::TypeInferenceBuilder::infer_function_definition + /// [updated]: crate::types::class::ClassLiteral::own_class_member + inherited_generic_context: Option>, +} + +#[salsa::tracked] +impl<'db> FunctionLiteral<'db> { + fn with_inherited_generic_context( + self, + db: &'db dyn Db, + inherited_generic_context: GenericContext<'db>, + ) -> Self { + // A function cannot inherit more than one generic context from its containing class. + debug_assert!(self.inherited_generic_context(db).is_none()); + Self::new( + db, + self.last_definition(db), + Some(inherited_generic_context), + ) + } + + fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + // All of the overloads of a function literal should have the same name. + self.last_definition(db).name(db) + } + + fn known(self, db: &'db dyn Db) -> Option { + // Whether a function is known is based on its name (and its containing module's name), so + // all overloads should be known (or not) equivalently. + self.last_definition(db).known(db) + } + + fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.iter_overloads_and_implementation(db) + .any(|overload| overload.decorators(db).contains(decorator)) + } + + fn definition(self, db: &'db dyn Db) -> Definition<'db> { + self.last_definition(db).definition(db) + } + + fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + self.last_definition(db).parameter_span(db, parameter_index) + } + + fn spans(self, db: &'db dyn Db) -> Option { + self.last_definition(db).spans(db) + } + + #[salsa::tracked(returns(ref))] + fn overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> (Box<[OverloadLiteral<'db>]>, Option>) { + let self_overload = self.last_definition(db); + let mut current = self_overload; + let mut overloads = vec![]; + + while let Some(previous) = current.previous_overload(db) { + let overload = previous.last_definition(db); + overloads.push(overload); + current = overload; + } + + // Overloads are inserted in reverse order, from bottom to top. + overloads.reverse(); + + let implementation = if self_overload.is_overload(db) { + overloads.push(self_overload); + None + } else { + Some(self_overload) + }; + + (overloads.into_boxed_slice(), implementation) + } + + fn iter_overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> impl Iterator> + 'db { + let (implementation, overloads) = self.overloads_and_implementation(db); + overloads.iter().chain(implementation).copied() + } + + /// Typed externally-visible signature for this function. + /// + /// This is the signature as seen by external callers, possibly modified by decorators and/or + /// overloaded. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + fn signature<'a>( + self, + db: &'db dyn Db, + type_mappings: &'a [TypeMapping<'a, 'db>], + ) -> CallableSignature<'db> + where + 'db: 'a, + { + // We only include an implementation (i.e. a definition not decorated with `@overload`) if + // it's the only definition. + let inherited_generic_context = self.inherited_generic_context(db); + let (overloads, implementation) = self.overloads_and_implementation(db); + if let Some(implementation) = implementation { + if overloads.is_empty() { + return CallableSignature::single(type_mappings.iter().fold( + implementation.signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + )); + } + } + + CallableSignature::from_overloads(overloads.iter().map(|overload| { + type_mappings.iter().fold( + overload.signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + ) + })) + } + + fn normalized(self, db: &'db dyn Db) -> Self { + let context = self + .inherited_generic_context(db) + .map(|ctx| ctx.normalized(db)); + Self::new(db, self.last_definition(db), context) + } +} + +/// Represents a function type, which might be a non-generic function, or a specialization of a +/// generic function. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct FunctionType<'db> { + pub(crate) literal: FunctionLiteral<'db>, + + /// Type mappings that should be applied to the function's parameter and return types. This + /// might include specializations of enclosing generic contexts (e.g. for non-generic methods + /// of a specialized generic class). + #[returns(deref)] + type_mappings: Box<[TypeMapping<'db, 'db>]>, +} + +#[salsa::tracked] +impl<'db> FunctionType<'db> { + pub(crate) fn with_inherited_generic_context( + self, + db: &'db dyn Db, + inherited_generic_context: GenericContext<'db>, + ) -> Self { + let literal = self + .literal(db) + .with_inherited_generic_context(db, inherited_generic_context); + Self::new(db, literal, self.type_mappings(db)) + } + + pub(crate) fn with_type_mapping<'a>( + self, + db: &'db dyn Db, + type_mapping: &TypeMapping<'a, 'db>, + ) -> Self { + let type_mappings: Box<[_]> = self + .type_mappings(db) + .iter() + .cloned() + .chain(std::iter::once(type_mapping.to_owned())) + .collect(); + Self::new(db, self.literal(db), type_mappings) + } + + pub(crate) fn with_dataclass_transformer_params( + self, + db: &'db dyn Db, + params: DataclassTransformerParams, + ) -> Self { + // A decorator only applies to the specific overload that it is attached to, not to all + // previous overloads. + let literal = self.literal(db); + let last_definition = literal + .last_definition(db) + .with_dataclass_transformer_params(db, params); + let literal = + FunctionLiteral::new(db, last_definition, literal.inherited_generic_context(db)); + Self::new(db, literal, self.type_mappings(db)) + } + + /// Returns the [`File`] in which this function is defined. + pub(crate) fn file(self, db: &'db dyn Db) -> File { + self.literal(db).last_definition(db).file(db) + } + + /// Returns the AST node for this function. + pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { + self.literal(db).last_definition(db).node(db, file) + } + + pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + self.literal(db).name(db) + } + + pub(crate) fn known(self, db: &'db dyn Db) -> Option { + self.literal(db).known(db) + } + + pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool { + self.known(db) == Some(known_function) + } + + /// Returns if any of the overloads of this function have a particular decorator. + /// + /// Some decorators are expected to appear on every overload; others are expected to appear + /// only the implementation or first overload. This method does not check either of those + /// conditions. + pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.literal(db).has_known_decorator(db, decorator) + } + + /// Returns the [`Definition`] of the implementation or first overload of this function. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { + self.literal(db).definition(db) + } + + /// Returns a tuple of two spans. The first is + /// the span for the identifier of the function + /// definition for `self`. The second is + /// the span for the parameter in the function + /// definition for `self`. + /// + /// If there are no meaningful spans, then this + /// returns `None`. For example, when this type + /// isn't callable. + /// + /// When `parameter_index` is `None`, then the + /// second span returned covers the entire parameter + /// list. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + pub(crate) fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + self.literal(db).parameter_span(db, parameter_index) + } + + /// Returns a collection of useful spans for a + /// function signature. These are useful for + /// creating annotations on diagnostics. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + pub(crate) fn spans(self, db: &'db dyn Db) -> Option { + self.literal(db).spans(db) + } + + /// Returns all of the overload signatures and the implementation definition, if any, of this + /// function. The overload signatures will be in source order. + pub(crate) fn overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> &'db (Box<[OverloadLiteral<'db>]>, Option>) { + self.literal(db).overloads_and_implementation(db) + } + + /// Returns an iterator of all of the definitions of this function, including both overload + /// signatures and any implementation, all in source order. + pub(crate) fn iter_overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> impl Iterator> + 'db { + self.literal(db).iter_overloads_and_implementation(db) + } + + /// Typed externally-visible signature for this function. + /// + /// This is the signature as seen by external callers, possibly modified by decorators and/or + /// overloaded. + /// + /// ## Why is this a salsa query? + /// + /// This is a salsa query to short-circuit the invalidation + /// when the function's AST node changes. + /// + /// Were this not a salsa query, then the calling query + /// would depend on the function's AST and rerun for every change in that file. + #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] + pub(crate) fn signature(self, db: &'db dyn Db) -> CallableSignature<'db> { + self.literal(db).signature(db, self.type_mappings(db)) + } + + /// Convert the `FunctionType` into a [`Type::Callable`]. + pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { + Type::Callable(CallableType::new(db, self.signature(db), false)) + } + + /// Convert the `FunctionType` into a [`Type::BoundMethod`]. + pub(crate) fn into_bound_method_type( + self, + db: &'db dyn Db, + self_instance: Type<'db>, + ) -> Type<'db> { + Type::BoundMethod(BoundMethodType::new(db, self, self_instance)) + } + + pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + // A function type is the subtype of itself, and not of any other function type. However, + // our representation of a function type includes any specialization that should be applied + // to the signature. Different specializations of the same function type are only subtypes + // of each other if they result in subtype signatures. + if self.normalized(db) == other.normalized(db) { + return true; + } + if self.literal(db) != other.literal(db) { + return false; + } + let self_signature = self.signature(db); + let other_signature = other.signature(db); + if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) { + return false; + } + self_signature.is_subtype_of(db, other_signature) + } + + pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + // A function type is assignable to itself, and not to any other function type. However, + // our representation of a function type includes any specialization that should be applied + // to the signature. Different specializations of the same function type are only + // assignable to each other if they result in assignable signatures. + self.literal(db) == other.literal(db) + && self.signature(db).is_assignable_to(db, other.signature(db)) + } + + pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + if self.normalized(db) == other.normalized(db) { + return true; + } + if self.literal(db) != other.literal(db) { + return false; + } + let self_signature = self.signature(db); + let other_signature = other.signature(db); + if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) { + return false; + } + self_signature.is_equivalent_to(db, other_signature) + } + + pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.literal(db) == other.literal(db) + && self + .signature(db) + .is_gradual_equivalent_to(db, other.signature(db)) + } + + pub(crate) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + let signatures = self.signature(db); + for signature in &signatures.overloads { + signature.find_legacy_typevars(db, typevars); + } + } + + pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + let mappings: Box<_> = self + .type_mappings(db) + .iter() + .map(|mapping| mapping.normalized(db)) + .collect(); + Self::new(db, self.literal(db).normalized(db), mappings) + } +} + +fn signature_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &CallableSignature<'db>, + _count: u32, + _function: FunctionType<'db>, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn signature_cycle_initial<'db>( + db: &'db dyn Db, + _function: FunctionType<'db>, +) -> CallableSignature<'db> { + CallableSignature::single(Signature::bottom(db)) +} + +/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might +/// have special behavior. +#[derive( + Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr, +)] +#[strum(serialize_all = "snake_case")] +#[cfg_attr(test, derive(strum_macros::EnumIter))] +pub enum KnownFunction { + /// `builtins.isinstance` + #[strum(serialize = "isinstance")] + IsInstance, + /// `builtins.issubclass` + #[strum(serialize = "issubclass")] + IsSubclass, + /// `builtins.hasattr` + #[strum(serialize = "hasattr")] + HasAttr, + /// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type` + RevealType, + /// `builtins.len` + Len, + /// `builtins.repr` + Repr, + /// `typing(_extensions).final` + Final, + + /// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check) + NoTypeCheck, + + /// `typing(_extensions).assert_type` + AssertType, + /// `typing(_extensions).assert_never` + AssertNever, + /// `typing(_extensions).cast` + Cast, + /// `typing(_extensions).overload` + Overload, + /// `typing(_extensions).override` + Override, + /// `typing(_extensions).is_protocol` + IsProtocol, + /// `typing(_extensions).get_protocol_members` + GetProtocolMembers, + /// `typing(_extensions).runtime_checkable` + RuntimeCheckable, + /// `typing(_extensions).dataclass_transform` + DataclassTransform, + + /// `abc.abstractmethod` + #[strum(serialize = "abstractmethod")] + AbstractMethod, + + /// `dataclasses.dataclass` + Dataclass, + + /// `inspect.getattr_static` + GetattrStatic, + + /// `ty_extensions.static_assert` + StaticAssert, + /// `ty_extensions.is_equivalent_to` + IsEquivalentTo, + /// `ty_extensions.is_subtype_of` + IsSubtypeOf, + /// `ty_extensions.is_assignable_to` + IsAssignableTo, + /// `ty_extensions.is_disjoint_from` + IsDisjointFrom, + /// `ty_extensions.is_gradual_equivalent_to` + IsGradualEquivalentTo, + /// `ty_extensions.is_fully_static` + IsFullyStatic, + /// `ty_extensions.is_singleton` + IsSingleton, + /// `ty_extensions.is_single_valued` + IsSingleValued, + /// `ty_extensions.generic_context` + GenericContext, + /// `ty_extensions.dunder_all_names` + DunderAllNames, + /// `ty_extensions.all_members` + AllMembers, +} + +impl KnownFunction { + pub fn into_classinfo_constraint_function(self) -> Option { + match self { + Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance), + Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass), + _ => None, + } + } + + pub(crate) fn try_from_definition_and_name<'db>( + db: &'db dyn Db, + definition: Definition<'db>, + name: &str, + ) -> Option { + let candidate = Self::from_str(name).ok()?; + candidate + .check_module(file_to_module(db, definition.file(db))?.known()?) + .then_some(candidate) + } + + /// Return `true` if `self` is defined in `module` at runtime. + const fn check_module(self, module: KnownModule) -> bool { + match self { + Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => { + module.is_builtins() + } + Self::AssertType + | Self::AssertNever + | Self::Cast + | Self::Overload + | Self::Override + | Self::RevealType + | Self::Final + | Self::IsProtocol + | Self::GetProtocolMembers + | Self::RuntimeCheckable + | Self::DataclassTransform + | Self::NoTypeCheck => { + matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) + } + Self::AbstractMethod => { + matches!(module, KnownModule::Abc) + } + Self::Dataclass => { + matches!(module, KnownModule::Dataclasses) + } + Self::GetattrStatic => module.is_inspect(), + Self::IsAssignableTo + | Self::IsDisjointFrom + | Self::IsEquivalentTo + | Self::IsGradualEquivalentTo + | Self::IsFullyStatic + | Self::IsSingleValued + | Self::IsSingleton + | Self::IsSubtypeOf + | Self::GenericContext + | Self::DunderAllNames + | Self::StaticAssert + | Self::AllMembers => module.is_ty_extensions(), + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use strum::IntoEnumIterator; + + use super::*; + use crate::db::tests::setup_db; + use crate::symbol::known_module_symbol; + + #[test] + fn known_function_roundtrip_from_str() { + let db = setup_db(); + + for function in KnownFunction::iter() { + let function_name: &'static str = function.into(); + + let module = match function { + KnownFunction::Len + | KnownFunction::Repr + | KnownFunction::IsInstance + | KnownFunction::HasAttr + | KnownFunction::IsSubclass => KnownModule::Builtins, + + KnownFunction::AbstractMethod => KnownModule::Abc, + + KnownFunction::Dataclass => KnownModule::Dataclasses, + + KnownFunction::GetattrStatic => KnownModule::Inspect, + + KnownFunction::Cast + | KnownFunction::Final + | KnownFunction::Overload + | KnownFunction::Override + | KnownFunction::RevealType + | KnownFunction::AssertType + | KnownFunction::AssertNever + | KnownFunction::IsProtocol + | KnownFunction::GetProtocolMembers + | KnownFunction::RuntimeCheckable + | KnownFunction::DataclassTransform + | KnownFunction::NoTypeCheck => KnownModule::TypingExtensions, + + KnownFunction::IsSingleton + | KnownFunction::IsSubtypeOf + | KnownFunction::GenericContext + | KnownFunction::DunderAllNames + | KnownFunction::StaticAssert + | KnownFunction::IsFullyStatic + | KnownFunction::IsDisjointFrom + | KnownFunction::IsSingleValued + | KnownFunction::IsAssignableTo + | KnownFunction::IsEquivalentTo + | KnownFunction::IsGradualEquivalentTo + | KnownFunction::AllMembers => KnownModule::TyExtensions, + }; + + let function_definition = known_module_symbol(&db, module, function_name) + .symbol + .expect_type() + .expect_function_literal() + .definition(&db); + + assert_eq!( + KnownFunction::try_from_definition_and_name( + &db, + function_definition, + function_name + ), + Some(function), + "The strum `EnumString` implementation appears to be incorrect for `{function_name}`" + ); + } + } +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 44b5d066167e7d..af77abaabc37fe 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -81,19 +81,21 @@ use crate::types::diagnostic::{ report_invalid_attribute_assignment, report_invalid_generator_function_return_type, report_invalid_return_type, report_possibly_unbound_attribute, }; +use crate::types::function::{ + FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, +}; use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; use crate::types::signatures::{CallableSignature, Signature}; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, - DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, - IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, - MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, - StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, - TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, - TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, - todo_type, + DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, + KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, + ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol, + SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, + TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, + TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -1131,12 +1133,13 @@ impl<'db> TypeInferenceBuilder<'db> { } for function in self.called_functions.union(&public_functions) { - let Some(overloaded) = function.to_overloaded(self.db()) else { + let (overloads, implementation) = function.overloads_and_implementation(self.db()); + if overloads.is_empty() { continue; - }; + } // Check that the overloaded function has at least two overloads - if let [single_overload] = overloaded.overloads.as_slice() { + if let [single_overload] = overloads.as_ref() { let function_node = function.node(self.db(), self.file()); if let Some(builder) = self .context @@ -1157,7 +1160,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Check that the overloaded function has an implementation. Overload definitions // within stub files, protocols, and on abstract methods within abstract base classes // are exempt from this check. - if overloaded.implementation.is_none() && !self.in_stub() { + if implementation.is_none() && !self.in_stub() { let mut implementation_required = true; if let NodeWithScopeKind::Class(class_node_ref) = scope { @@ -1169,7 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db> { if class.is_protocol(self.db()) || (class.is_abstract(self.db()) - && overloaded.overloads.iter().all(|overload| { + && overloads.iter().all(|overload| { overload.has_known_decorator( self.db(), FunctionDecorators::ABSTRACT_METHOD, @@ -1199,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> { let mut decorator_present = false; let mut decorator_missing = vec![]; - for function in overloaded.all() { + for function in overloads.iter().chain(implementation.as_ref()) { if function.has_known_decorator(self.db(), decorator) { decorator_present = true; } else { @@ -1240,8 +1243,8 @@ impl<'db> TypeInferenceBuilder<'db> { (FunctionDecorators::FINAL, "final"), (FunctionDecorators::OVERRIDE, "override"), ] { - if let Some(implementation) = overloaded.implementation.as_ref() { - for overload in &overloaded.overloads { + if let Some(implementation) = implementation { + for overload in overloads.as_ref() { if !overload.has_known_decorator(self.db(), decorator) { continue; } @@ -1263,7 +1266,7 @@ impl<'db> TypeInferenceBuilder<'db> { ); } } else { - let mut overloads = overloaded.overloads.iter(); + let mut overloads = overloads.iter(); let Some(first_overload) = overloads.next() else { continue; }; @@ -2027,7 +2030,7 @@ impl<'db> TypeInferenceBuilder<'db> { } } - let function_kind = + let known_function = KnownFunction::try_from_definition_and_name(self.db(), definition, name); let body_scope = self @@ -2035,17 +2038,23 @@ impl<'db> TypeInferenceBuilder<'db> { .node_scope(NodeWithScopeRef::Function(function)) .to_scope_id(self.db(), self.file()); - let inherited_generic_context = None; - let type_mappings = Box::from([]); - - let mut inferred_ty = Type::FunctionLiteral(FunctionType::new( + let overload_literal = OverloadLiteral::new( self.db(), &name.id, - function_kind, + known_function, body_scope, function_decorators, dataclass_transformer_params, - inherited_generic_context, + ); + + let inherited_generic_context = None; + let function_literal = + FunctionLiteral::new(self.db(), overload_literal, inherited_generic_context); + + let type_mappings = Box::from([]); + let mut inferred_ty = Type::FunctionLiteral(FunctionType::new( + self.db(), + function_literal, type_mappings, )); @@ -2314,14 +2323,10 @@ impl<'db> TypeInferenceBuilder<'db> { // overload, or an overload and the implementation both. Nevertheless, this is not // allowed. We do not try to treat the offenders intelligently -- just use the // params of the last seen usage of `@dataclass_transform` - if let Some(overloaded) = f.to_overloaded(self.db()) { - overloaded.overloads.iter().for_each(|overload| { - if let Some(params) = overload.dataclass_transformer_params(self.db()) { - dataclass_params = Some(params.into()); - } - }); - } - if let Some(params) = f.dataclass_transformer_params(self.db()) { + let params = f + .iter_overloads_and_implementation(self.db()) + .find_map(|overload| overload.dataclass_transformer_params(self.db())); + if let Some(params) = params { dataclass_params = Some(params.into()); continue; } diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 303885c32049a4..f8bf6d610bc966 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -6,6 +6,7 @@ use crate::semantic_index::predicate::{ }; use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable}; use crate::semantic_index::symbol_table; +use crate::types::function::KnownFunction; use crate::types::infer::infer_same_file_expression_type; use crate::types::{ IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder, @@ -20,7 +21,7 @@ use ruff_python_ast::{BoolOp, ExprBoolOp}; use rustc_hash::FxHashMap; use std::collections::hash_map::Entry; -use super::{KnownFunction, UnionType}; +use super::UnionType; /// Return the type constraint that `test` (if true) would place on `symbol`, if any. /// diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 005ea95765a207..7efdc8453126f1 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -7,9 +7,8 @@ use ruff_python_ast::name::Name; use crate::{ semantic_index::{symbol_table, use_def_map}, symbol::{symbol_from_bindings, symbol_from_declarations}, - types::{ - ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance, - }, + types::function::KnownFunction, + types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance}, {Db, FxOrderSet}, }; diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 24448b661db1e7..201ab0eddf8367 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -53,10 +53,6 @@ impl<'db> CallableSignature<'db> { self.overloads.iter() } - pub(crate) fn as_slice(&self) -> &[Signature<'db>] { - self.overloads.as_slice() - } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { Self::from_overloads( self.overloads @@ -1538,7 +1534,8 @@ mod tests { use super::*; use crate::db::tests::{TestDb, setup_db}; use crate::symbol::global_symbol; - use crate::types::{FunctionSignature, FunctionType, KnownClass}; + use crate::types::KnownClass; + use crate::types::function::FunctionType; use ruff_db::system::DbWithWritableSystem as _; #[track_caller] @@ -1559,9 +1556,11 @@ mod tests { fn empty() { let mut db = setup_db(); db.write_dedented("/src/a.py", "def f(): ...").unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); assert!(sig.return_ty.is_none()); assert_params(&sig, &[]); @@ -1582,9 +1581,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes"); assert_params( @@ -1633,9 +1634,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1669,9 +1672,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.pyi"); + let func = get_function_f(&db, "/src/a.pyi") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1705,9 +1710,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1751,9 +1758,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.pyi"); + let func = get_function_f(&db, "/src/a.pyi") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1789,15 +1798,13 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let expected_sig = func.internal_signature(&db, None); + let overload = func.literal(&db).last_definition(&db); + let expected_sig = overload.signature(&db, None); // With no decorators, internal and external signature are the same assert_eq!( func.signature(&db), - &FunctionSignature { - overloads: CallableSignature::single(expected_sig), - implementation: None - }, + &CallableSignature::single(expected_sig) ); } } From 71d8a5da2aef888e96e575f2a83c0b992e8ff36c Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Bodas <55339528+abhijeetbodas2001@users.noreply.github.com> Date: Tue, 3 Jun 2025 22:20:29 +0530 Subject: [PATCH 324/487] [ty] dataclasses: Allow using dataclasses.dataclass as a function. (#18440) ## Summary Part of https://github.com/astral-sh/ty/issues/111 Using `dataclass` as a function, instead of as a decorator did not work as expected prior to this. Fix that by modifying the dataclass overload's return type. ## Test Plan New mdtests, fixing the existing TODO. --- .../resources/mdtest/dataclasses.md | 15 ++++++++++++++- .../ty_python_semantic/src/types/call/bind.rs | 19 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses.md index a74c125b5dbe65..4616e59e224548 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses.md @@ -797,7 +797,20 @@ C(1) < C(2) # ok ### Using `dataclass` as a function -To do +```py +from dataclasses import dataclass + +class B: + x: int + +# error: [missing-argument] +dataclass(B)() + +# error: [invalid-argument-type] +dataclass(B)("a") + +reveal_type(dataclass(B)(3).x) # revealed: int +``` ## Internals diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index b49343c12d8fbe..2eab02c9c78527 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -22,8 +22,8 @@ use crate::types::function::{DataclassTransformerParams, FunctionDecorators, Kno use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError}; use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ - BoundMethodType, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind, - PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, + BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType, + MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; @@ -839,6 +839,21 @@ impl<'db> Bindings<'db> { overload.set_return_type(Type::DataclassDecorator(params)); } + + // `dataclass` being used as a non-decorator + if let [Some(Type::ClassLiteral(class_literal))] = + overload.parameter_types() + { + let params = DataclassParams::default(); + overload.set_return_type(Type::from(ClassLiteral::new( + db, + class_literal.name(db), + class_literal.body_scope(db), + class_literal.known(db), + Some(params), + class_literal.dataclass_transformer_params(db), + ))); + } } Some(KnownFunction::DataclassTransform) => { From e8ea40012afda872ef9bdf496a8b888ba80b533b Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Tue, 3 Jun 2025 17:59:43 +0100 Subject: [PATCH 325/487] [ty] Add generic inference for dataclasses (#18443) ## Summary An issue seen here https://github.com/astral-sh/ty/issues/500 The `__init__` method of dataclasses had no inherited generic context, so we could not infer the type of an instance from a constructor call with generics ## Test Plan Add tests to classes.md` in generics folder --- .../resources/mdtest/generics/legacy/classes.md | 15 +++++++++++++++ .../resources/mdtest/generics/pep695/classes.md | 12 ++++++++++++ .../resources/mdtest/named_tuple.md | 4 +--- crates/ty_python_semantic/src/types/class.rs | 3 ++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index ad1fe65958a2a5..5e8858bf7b716d 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -379,6 +379,21 @@ C[None](b"bytes") # error: [no-matching-overload] C[None](12) ``` +### Synthesized methods with dataclasses + +```py +from dataclasses import dataclass +from typing import Generic, TypeVar + +T = TypeVar("T") + +@dataclass +class A(Generic[T]): + x: T + +reveal_type(A(x=1)) # revealed: A[int] +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 1d3fee229bace0..9726dbae542250 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -354,6 +354,18 @@ C[None](b"bytes") # error: [no-matching-overload] C[None](12) ``` +### Synthesized methods with dataclasses + +```py +from dataclasses import dataclass + +@dataclass +class A[T]: + x: T + +reveal_type(A(x=1)) # revealed: A[int] +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 8c5a79d62f9095..dfc81a73f0e77f 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -134,9 +134,7 @@ class Property[T](NamedTuple): name: str value: T -# TODO: this should be supported (no error, revealed type of `Property[float]`) -# error: [invalid-argument-type] -reveal_type(Property("height", 3.4)) # revealed: Property[Unknown] +reveal_type(Property("height", 3.4)) # revealed: Property[float] ``` ## Attributes on `NamedTuple` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index bcf6c6e1b7438f..ab99ae27a6854b 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1372,7 +1372,8 @@ impl<'db> ClassLiteral<'db> { parameters.push(parameter); } - let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); + let mut signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); + signature.inherited_generic_context = self.generic_context(db); Some(CallableType::function_like(db, signature)) }; From 0079cc6817d070ff42bfc568c6652e260485983b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 3 Jun 2025 19:49:14 +0100 Subject: [PATCH 326/487] [ty] Minor cleanup for `site-packages` discovery logic (#18446) --- .../ty_python_semantic/src/site_packages.rs | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 9f28d4380a1a6a..85484304929d09 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -95,15 +95,13 @@ impl PythonEnvironment { origin: SysPrefixPathOrigin, system: &dyn System, ) -> SitePackagesDiscoveryResult { - let path = SysPrefixPath::new(path, origin, system)?; + let path = SysPrefixPath::new(path.as_ref(), origin, system)?; // Attempt to inspect as a virtual environment first - // TODO(zanieb): Consider avoiding the clone here by checking for `pyvenv.cfg` ahead-of-time - match VirtualEnvironment::new(path.clone(), system) { + match VirtualEnvironment::new(path, system) { Ok(venv) => Ok(Self::Virtual(venv)), // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment - // - Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(_, _)) + Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, _)) if !origin.must_be_virtual_env() => { Ok(Self::System(SystemEnvironment::new(path))) @@ -207,9 +205,10 @@ impl VirtualEnvironment { let pyvenv_cfg_path = path.join("pyvenv.cfg"); tracing::debug!("Attempting to parse virtual environment metadata at '{pyvenv_cfg_path}'"); - let pyvenv_cfg = system - .read_to_string(&pyvenv_cfg_path) - .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(path.origin, io_err))?; + let pyvenv_cfg = match system.read_to_string(&pyvenv_cfg_path) { + Ok(pyvenv_cfg) => pyvenv_cfg, + Err(err) => return Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, err)), + }; let parsed_pyvenv_cfg = PyvenvCfgParser::new(&pyvenv_cfg) @@ -530,20 +529,40 @@ impl SystemEnvironment { } } +/// Enumeration of ways in which `site-packages` discovery can fail. #[derive(Debug, thiserror::Error)] pub(crate) enum SitePackagesDiscoveryError { + /// `site-packages` discovery failed because the provided path couldn't be canonicalized. #[error("Invalid {1}: `{0}` could not be canonicalized")] - EnvDirCanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), + CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), + + /// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that + /// the provided path should point to `sys.prefix` directly, but the path wasn't a directory. #[error("Invalid {1}: `{0}` does not point to a directory on disk")] - EnvDirNotDirectory(SystemPathBuf, SysPrefixPathOrigin), - #[error("{0} points to a broken venv with no pyvenv.cfg file")] - NoPyvenvCfgFile(SysPrefixPathOrigin, #[source] io::Error), + SysPrefixNotADirectory(SystemPathBuf, SysPrefixPathOrigin), + + /// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that + /// the provided path should point to the `sys.prefix` of a virtual environment, + /// but there was no file at `/pyvenv.cfg`. + #[error("{} points to a broken venv with no pyvenv.cfg file", .0.origin)] + NoPyvenvCfgFile(SysPrefixPath, #[source] io::Error), + + /// `site-packages` discovery failed because the `pyvenv.cfg` file could not be parsed. #[error("Failed to parse the pyvenv.cfg file at {0} because {1}")] PyvenvCfgParseError(SystemPathBuf, PyvenvCfgParseErrorKind), + + /// `site-packages` discovery failed because we're on a Unix system, + /// we weren't able to figure out from the `pyvenv.cfg` file exactly where `site-packages` + /// would be relative to the `sys.prefix` path, and we tried to fallback to iterating + /// through the `/lib` directory looking for a `site-packages` directory, + /// but we came across some I/O error while trying to do so. #[error( - "Failed to search the `lib` directory of the Python installation at {1} for `site-packages`" + "Failed to iterate over the contents of the `lib` directory of the Python installation at {1}" )] CouldNotReadLibDirectory(#[source] io::Error, SysPrefixPath), + + /// We looked everywhere we could think of for the `site-packages` directory, + /// but none could be found despite our best endeavours. #[error("Could not find the `site-packages` directory for the Python installation at {0}")] NoSitePackagesDirFound(SysPrefixPath), } @@ -709,14 +728,6 @@ pub(crate) struct SysPrefixPath { impl SysPrefixPath { fn new( - unvalidated_path: impl AsRef, - origin: SysPrefixPathOrigin, - system: &dyn System, - ) -> SitePackagesDiscoveryResult { - Self::new_impl(unvalidated_path.as_ref(), origin, system) - } - - fn new_impl( unvalidated_path: &SystemPath, origin: SysPrefixPathOrigin, system: &dyn System, @@ -727,7 +738,7 @@ impl SysPrefixPath { let canonicalized = system .canonicalize_path(unvalidated_path) .map_err(|io_err| { - SitePackagesDiscoveryError::EnvDirCanonicalizationError( + SitePackagesDiscoveryError::CanonicalizationError( unvalidated_path.to_path_buf(), origin, io_err, @@ -740,7 +751,7 @@ impl SysPrefixPath { origin, }) .ok_or_else(|| { - SitePackagesDiscoveryError::EnvDirNotDirectory( + SitePackagesDiscoveryError::SysPrefixNotADirectory( unvalidated_path.to_path_buf(), origin, ) @@ -1367,7 +1378,7 @@ mod tests { let system = TestSystem::default(); assert!(matches!( PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), - Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..)) + Err(SitePackagesDiscoveryError::CanonicalizationError(..)) )); } @@ -1380,7 +1391,7 @@ mod tests { .unwrap(); assert!(matches!( PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), - Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..)) + Err(SitePackagesDiscoveryError::SysPrefixNotADirectory(..)) )); } From 7ea773daf21c794e33f2d0f082260ecf1a001648 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 4 Jun 2025 07:42:00 +0530 Subject: [PATCH 327/487] [ty] Argument type expansion for overload call evaluation (#18382) ## Summary Part of astral-sh/ty#104, closes: astral-sh/ty#468 This PR implements the argument type expansion which is step 3 of the overload call evaluation algorithm. Specifically, this step needs to be taken if type checking resolves to no matching overload and there are argument types that can be expanded. ## Test Plan Add new test cases. ## Ecosystem analysis This PR removes 174 `no-matching-overload` false positives -- I looked at a lot of them and they all are false positives. One thing that I'm not able to understand is that in https://github.com/sphinx-doc/sphinx/blob/2b7e3adf27c158305acca9b5e4d0d93d3e4c6f09/sphinx/ext/autodoc/preserve_defaults.py#L179 the inferred type of `value` is `str | None` by ty and Pyright, which is correct, but it's only ty that raises `invalid-argument-type` error while Pyright doesn't. The constructor method of `DefaultValue` has declared type of `str` which is invalid. There are few cases of false positives resulting due to the fact that ty doesn't implement narrowing on attribute expressions. --- .../resources/mdtest/call/overloads.md | 401 ++++++++++++++++++ .../src/types/call/arguments.rs | 210 +++++++++ .../ty_python_semantic/src/types/call/bind.rs | 369 ++++++++++++++-- 3 files changed, 955 insertions(+), 25 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/call/overloads.md diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md new file mode 100644 index 00000000000000..2258035524679e --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -0,0 +1,401 @@ +# Overloads + +When ty evaluates the call of an overloaded function, it attempts to "match" the supplied arguments +with one or more overloads. This document describes the algorithm that it uses for overload +matching, which is the same as the one mentioned in the +[spec](https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation). + +## Arity check + +The first step is to perform arity check. The non-overloaded cases are described in the +[function](./function.md) document. + +`overloaded.pyi`: + +```pyi +from typing import overload + +@overload +def f() -> None: ... +@overload +def f(x: int) -> int: ... +``` + +```py +from overloaded import f + +# These match a single overload +reveal_type(f()) # revealed: None +reveal_type(f(1)) # revealed: int + +# error: [no-matching-overload] "No overload of function `f` matches arguments" +reveal_type(f("a", "b")) # revealed: Unknown +``` + +## Type checking + +The second step is to perform type checking. This is done for all the overloads that passed the +arity check. + +### Single match + +`overloaded.pyi`: + +```pyi +from typing import overload + +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +@overload +def f(x: bytes) -> bytes: ... +``` + +Here, all of the calls below pass the arity check for all overloads, so we proceed to type checking +which filters out all but the matching overload: + +```py +from overloaded import f + +reveal_type(f(1)) # revealed: int +reveal_type(f("a")) # revealed: str +reveal_type(f(b"b")) # revealed: bytes +``` + +### Single match error + +`overloaded.pyi`: + +```pyi +from typing import overload + +@overload +def f() -> None: ... +@overload +def f(x: int) -> int: ... +``` + +If the arity check only matches a single overload, it should be evaluated as a regular +(non-overloaded) function call. This means that any diagnostics resulted during type checking that +call should be reported directly and not as a `no-matching-overload` error. + +```py +from overloaded import f + +reveal_type(f()) # revealed: None + +# TODO: This should be `invalid-argument-type` instead +# error: [no-matching-overload] +reveal_type(f("a")) # revealed: Unknown +``` + +### Multiple matches + +`overloaded.pyi`: + +```pyi +from typing import overload + +class A: ... +class B(A): ... + +@overload +def f(x: A) -> A: ... +@overload +def f(x: B, y: int = 0) -> B: ... +``` + +```py +from overloaded import A, B, f + +# These calls pass the arity check, and type checking matches both overloads: +reveal_type(f(A())) # revealed: A +reveal_type(f(B())) # revealed: A + +# But, in this case, the arity check filters out the first overload, so we only have one match: +reveal_type(f(B(), 1)) # revealed: B +``` + +## Argument type expansion + +This step is performed only if the previous steps resulted in **no matches**. + +In this case, the algorithm would perform +[argument type expansion](https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion) +and loops over from the type checking step, evaluating the argument lists. + +### Expanding the only argument + +`overloaded.pyi`: + +```pyi +from typing import overload + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: A) -> A: ... +@overload +def f(x: B) -> B: ... +@overload +def f(x: C) -> C: ... +``` + +```py +from overloaded import A, B, C, f + +def _(ab: A | B, ac: A | C, bc: B | C): + reveal_type(f(ab)) # revealed: A | B + reveal_type(f(bc)) # revealed: B | C + reveal_type(f(ac)) # revealed: A | C +``` + +### Expanding first argument + +If the set of argument lists created by expanding the first argument evaluates successfully, the +algorithm shouldn't expand the second argument. + +`overloaded.pyi`: + +```pyi +from typing import Literal, overload + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def f(x: A, y: C) -> A: ... +@overload +def f(x: A, y: D) -> B: ... +@overload +def f(x: B, y: C) -> C: ... +@overload +def f(x: B, y: D) -> D: ... +``` + +```py +from overloaded import A, B, C, D, f + +def _(a_b: A | B): + reveal_type(f(a_b, C())) # revealed: A | C + reveal_type(f(a_b, D())) # revealed: B | D + +# But, if it doesn't, it should expand the second argument and try again: +def _(a_b: A | B, c_d: C | D): + reveal_type(f(a_b, c_d)) # revealed: A | B | C | D +``` + +### Expanding second argument + +If the first argument cannot be expanded, the algorithm should move on to the second argument, +keeping the first argument as is. + +`overloaded.pyi`: + +```pyi +from typing import overload + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def f(x: A, y: B) -> B: ... +@overload +def f(x: A, y: C) -> C: ... +@overload +def f(x: B, y: D) -> D: ... +``` + +```py +from overloaded import A, B, C, D, f + +def _(a: A, bc: B | C, cd: C | D): + # This also tests that partial matching works correctly as the argument type expansion results + # in matching the first and second overloads, but not the third one. + reveal_type(f(a, bc)) # revealed: B | C + + # error: [no-matching-overload] "No overload of function `f` matches arguments" + reveal_type(f(a, cd)) # revealed: Unknown +``` + +### Generics (legacy) + +`overloaded.pyi`: + +```pyi +from typing import TypeVar, overload + +_T = TypeVar("_T") + +class A: ... +class B: ... + +@overload +def f(x: A) -> A: ... +@overload +def f(x: _T) -> _T: ... +``` + +```py +from overloaded import A, f + +def _(x: int, y: A | int): + reveal_type(f(x)) # revealed: int + reveal_type(f(y)) # revealed: A | int +``` + +### Generics (PEP 695) + +```toml +[environment] +python-version = "3.12" +``` + +`overloaded.pyi`: + +```pyi +from typing import overload + +class A: ... +class B: ... + +@overload +def f(x: B) -> B: ... +@overload +def f[T](x: T) -> T: ... +``` + +```py +from overloaded import B, f + +def _(x: int, y: B | int): + reveal_type(f(x)) # revealed: int + reveal_type(f(y)) # revealed: B | int +``` + +### Expanding `bool` + +`overloaded.pyi`: + +```pyi +from typing import Literal, overload + +class T: ... +class F: ... + +@overload +def f(x: Literal[True]) -> T: ... +@overload +def f(x: Literal[False]) -> F: ... +``` + +```py +from overloaded import f + +def _(flag: bool): + reveal_type(f(True)) # revealed: T + reveal_type(f(False)) # revealed: F + reveal_type(f(flag)) # revealed: T | F +``` + +### Expanding `tuple` + +`overloaded.pyi`: + +```pyi +from typing import Literal, overload + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def f(x: tuple[A, int], y: tuple[int, Literal[True]]) -> A: ... +@overload +def f(x: tuple[A, int], y: tuple[int, Literal[False]]) -> B: ... +@overload +def f(x: tuple[B, int], y: tuple[int, Literal[True]]) -> C: ... +@overload +def f(x: tuple[B, int], y: tuple[int, Literal[False]]) -> D: ... +``` + +```py +from overloaded import A, B, f + +def _(x: tuple[A | B, int], y: tuple[int, bool]): + reveal_type(f(x, y)) # revealed: A | B | C | D +``` + +### Expanding `type` + +There's no special handling for expanding `type[A | B]` type because ty stores this type in it's +distributed form, which is `type[A] | type[B]`. + +`overloaded.pyi`: + +```pyi +from typing import overload + +class A: ... +class B: ... + +@overload +def f(x: type[A]) -> A: ... +@overload +def f(x: type[B]) -> B: ... +``` + +```py +from overloaded import A, B, f + +def _(x: type[A | B]): + reveal_type(x) # revealed: type[A] | type[B] + reveal_type(f(x)) # revealed: A | B +``` + +### Expanding enums + +`overloaded.pyi`: + +```pyi +from enum import Enum +from typing import Literal, overload + +class SomeEnum(Enum): + A = 1 + B = 2 + C = 3 + + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: Literal[SomeEnum.A]) -> A: ... +@overload +def f(x: Literal[SomeEnum.B]) -> B: ... +@overload +def f(x: Literal[SomeEnum.C]) -> C: ... +``` + +```py +from overloaded import SomeEnum, A, B, C, f + +def _(x: SomeEnum): + reveal_type(f(SomeEnum.A)) # revealed: A + # TODO: This should be `B` once enums are supported and are expanded + reveal_type(f(SomeEnum.B)) # revealed: A + # TODO: This should be `C` once enums are supported and are expanded + reveal_type(f(SomeEnum.C)) # revealed: A + # TODO: This should be `A | B | C` once enums are supported and are expanded + reveal_type(f(x)) # revealed: A +``` diff --git a/crates/ty_python_semantic/src/types/call/arguments.rs b/crates/ty_python_semantic/src/types/call/arguments.rs index 392c4ee5142683..3ed581e4151804 100644 --- a/crates/ty_python_semantic/src/types/call/arguments.rs +++ b/crates/ty_python_semantic/src/types/call/arguments.rs @@ -1,6 +1,11 @@ use std::borrow::Cow; use std::ops::{Deref, DerefMut}; +use itertools::{Either, Itertools}; + +use crate::Db; +use crate::types::{KnownClass, TupleType}; + use super::Type; /// Arguments for a single call, in source order. @@ -86,6 +91,10 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> { Self { arguments, types } } + pub(crate) fn types(&self) -> &[Type<'db>] { + &self.types + } + /// Prepend an optional extra synthetic argument (for a `self` or `cls` parameter) to the front /// of this argument list. (If `bound_self` is none, we return the argument list /// unmodified.) @@ -108,6 +117,72 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> { pub(crate) fn iter(&self) -> impl Iterator, Type<'db>)> + '_ { self.arguments.iter().zip(self.types.iter().copied()) } + + /// Returns an iterator on performing [argument type expansion]. + /// + /// Each element of the iterator represents a set of argument lists, where each argument list + /// contains the same arguments, but with one or more of the argument types expanded. + /// + /// [argument type expansion]: https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion + pub(crate) fn expand(&self, db: &'db dyn Db) -> impl Iterator>>> + '_ { + /// Represents the state of the expansion process. + /// + /// This is useful to avoid cloning the initial types vector if none of the types can be + /// expanded. + enum State<'a, 'db> { + Initial(&'a Vec>), + Expanded(Vec>>), + } + + impl<'db> State<'_, 'db> { + fn len(&self) -> usize { + match self { + State::Initial(_) => 1, + State::Expanded(expanded) => expanded.len(), + } + } + + fn iter(&self) -> impl Iterator>> + '_ { + match self { + State::Initial(types) => std::slice::from_ref(*types).iter(), + State::Expanded(expanded) => expanded.iter(), + } + } + } + + let mut index = 0; + + std::iter::successors(Some(State::Initial(&self.types)), move |previous| { + // Find the next type that can be expanded. + let expanded_types = loop { + let arg_type = self.types.get(index)?; + if let Some(expanded_types) = expand_type(db, *arg_type) { + break expanded_types; + } + index += 1; + }; + + let mut expanded_arg_types = Vec::with_capacity(expanded_types.len() * previous.len()); + + for pre_expanded_types in previous.iter() { + for subtype in &expanded_types { + let mut new_expanded_types = pre_expanded_types.clone(); + new_expanded_types[index] = *subtype; + expanded_arg_types.push(new_expanded_types); + } + } + + // Increment the index to move to the next argument type for the next iteration. + index += 1; + + Some(State::Expanded(expanded_arg_types)) + }) + .skip(1) // Skip the initial state, which has no expanded types. + .map(|state| match state { + State::Initial(_) => unreachable!("initial state should be skipped"), + State::Expanded(expanded) => expanded, + }) + } } impl<'a> Deref for CallArgumentTypes<'a, '_> { @@ -122,3 +197,138 @@ impl<'a> DerefMut for CallArgumentTypes<'a, '_> { &mut self.arguments } } + +/// Expands a type into its possible subtypes, if applicable. +/// +/// Returns [`None`] if the type cannot be expanded. +fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option>> { + // TODO: Expand enums to their variants + match ty { + Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Bool) => { + Some(vec![ + Type::BooleanLiteral(true), + Type::BooleanLiteral(false), + ]) + } + Type::Tuple(tuple) => { + // Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]` + // should not be expanded here. + let expanded = tuple + .iter(db) + .map(|element| { + if let Some(expanded) = expand_type(db, element) { + Either::Left(expanded.into_iter()) + } else { + Either::Right(std::iter::once(element)) + } + }) + .multi_cartesian_product() + .map(|types| TupleType::from_elements(db, types)) + .collect::>(); + if expanded.len() == 1 { + // There are no elements in the tuple type that can be expanded. + None + } else { + Some(expanded) + } + } + Type::Union(union) => Some(union.iter(db).copied().collect()), + // We don't handle `type[A | B]` here because it's already stored in the expanded form + // i.e., `type[A] | type[B]` which is handled by the `Type::Union` case. + _ => None, + } +} + +#[cfg(test)] +mod tests { + use crate::db::tests::setup_db; + use crate::types::{KnownClass, TupleType, Type, UnionType}; + + use super::expand_type; + + #[test] + fn expand_union_type() { + let db = setup_db(); + let types = [ + KnownClass::Int.to_instance(&db), + KnownClass::Str.to_instance(&db), + KnownClass::Bytes.to_instance(&db), + ]; + let union_type = UnionType::from_elements(&db, types); + let expanded = expand_type(&db, union_type).unwrap(); + assert_eq!(expanded.len(), types.len()); + assert_eq!(expanded, types); + } + + #[test] + fn expand_bool_type() { + let db = setup_db(); + let bool_instance = KnownClass::Bool.to_instance(&db); + let expanded = expand_type(&db, bool_instance).unwrap(); + let expected_types = [Type::BooleanLiteral(true), Type::BooleanLiteral(false)]; + assert_eq!(expanded.len(), expected_types.len()); + assert_eq!(expanded, expected_types); + } + + #[test] + fn expand_tuple_type() { + let db = setup_db(); + + let int_ty = KnownClass::Int.to_instance(&db); + let str_ty = KnownClass::Str.to_instance(&db); + let bytes_ty = KnownClass::Bytes.to_instance(&db); + let bool_ty = KnownClass::Bool.to_instance(&db); + let true_ty = Type::BooleanLiteral(true); + let false_ty = Type::BooleanLiteral(false); + + // Empty tuple + let empty_tuple = TupleType::empty(&db); + let expanded = expand_type(&db, empty_tuple); + assert!(expanded.is_none()); + + // None of the elements can be expanded. + let tuple_type1 = TupleType::from_elements(&db, [int_ty, str_ty]); + let expanded = expand_type(&db, tuple_type1); + assert!(expanded.is_none()); + + // All elements can be expanded. + let tuple_type2 = TupleType::from_elements( + &db, + [ + bool_ty, + UnionType::from_elements(&db, [int_ty, str_ty, bytes_ty]), + ], + ); + let expected_types = [ + TupleType::from_elements(&db, [true_ty, int_ty]), + TupleType::from_elements(&db, [true_ty, str_ty]), + TupleType::from_elements(&db, [true_ty, bytes_ty]), + TupleType::from_elements(&db, [false_ty, int_ty]), + TupleType::from_elements(&db, [false_ty, str_ty]), + TupleType::from_elements(&db, [false_ty, bytes_ty]), + ]; + let expanded = expand_type(&db, tuple_type2).unwrap(); + assert_eq!(expanded.len(), expected_types.len()); + assert_eq!(expanded, expected_types); + + // Mixed set of elements where some can be expanded while others cannot be. + let tuple_type3 = TupleType::from_elements( + &db, + [ + bool_ty, + int_ty, + UnionType::from_elements(&db, [str_ty, bytes_ty]), + str_ty, + ], + ); + let expected_types = [ + TupleType::from_elements(&db, [true_ty, int_ty, str_ty, str_ty]), + TupleType::from_elements(&db, [true_ty, int_ty, bytes_ty, str_ty]), + TupleType::from_elements(&db, [false_ty, int_ty, str_ty, str_ty]), + TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]), + ]; + let expanded = expand_type(&db, tuple_type3).unwrap(); + assert_eq!(expanded.len(), expected_types.len()); + assert_eq!(expanded, expected_types); + } +} diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 2eab02c9c78527..3d349abf17f090 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1012,6 +1012,7 @@ impl<'db> From> for Bindings<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, + return_type: None, overloads: smallvec![from], }; Bindings { @@ -1030,14 +1031,9 @@ impl<'db> From> for Bindings<'db> { /// If the callable has multiple overloads, the first one that matches is used as the overall /// binding match. /// -/// TODO: Implement the call site evaluation algorithm in the [proposed updated typing -/// spec][overloads], which is much more subtle than “first match wins”. -/// /// If the arguments cannot be matched to formal parameters, we store information about the /// specific errors that occurred when trying to match them up. If the callable has multiple /// overloads, we store this error information for each overload. -/// -/// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug)] pub(crate) struct CallableBinding<'db> { /// The type that is (hopefully) callable. @@ -1055,6 +1051,14 @@ pub(crate) struct CallableBinding<'db> { /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. pub(crate) bound_type: Option>, + /// The return type of this callable. + /// + /// This is only `Some` if it's an overloaded callable, "argument type expansion" was + /// performed, and one of the expansion evaluated successfully for all of the argument lists. + /// This type is then the union of all the return types of the matched overloads for the + /// expanded argument lists. + return_type: Option>, + /// The bindings of each overload of this callable. Will be empty if the type is not callable. /// /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a @@ -1076,6 +1080,7 @@ impl<'db> CallableBinding<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, + return_type: None, overloads, } } @@ -1086,6 +1091,7 @@ impl<'db> CallableBinding<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, + return_type: None, overloads: smallvec![], } } @@ -1114,12 +1120,6 @@ impl<'db> CallableBinding<'db> { // before checking. let arguments = arguments.with_self(self.bound_type); - // TODO: This checks every overload. In the proposed more detailed call checking spec [1], - // arguments are checked for arity first, and are only checked for type assignability against - // the matching overloads. Make sure to implement that as part of separating call binding into - // two phases. - // - // [1] https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation for overload in &mut self.overloads { overload.match_parameters(arguments.as_ref(), argument_forms, conflicting_forms); } @@ -1129,9 +1129,154 @@ impl<'db> CallableBinding<'db> { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. let argument_types = argument_types.with_self(self.bound_type); - for overload in &mut self.overloads { - overload.check_types(db, argument_types.as_ref()); + + // Step 1: Check the result of the arity check which is done by `match_parameters` + let matching_overload_indexes = match self.matching_overload_index() { + MatchingOverloadIndex::None => { + // If no candidate overloads remain from the arity check, we can stop here. We + // still perform type checking for non-overloaded function to provide better user + // experience. + if let [overload] = self.overloads.as_mut_slice() { + overload.check_types(db, argument_types.as_ref(), argument_types.types()); + } + return; + } + MatchingOverloadIndex::Single(index) => { + // If only one candidate overload remains, it is the winning match. + // TODO: Evaluate it as a regular (non-overloaded) call. This means that any + // diagnostics reported in this check should be reported directly instead of + // reporting it as `no-matching-overload`. + self.overloads[index].check_types( + db, + argument_types.as_ref(), + argument_types.types(), + ); + return; + } + MatchingOverloadIndex::Multiple(indexes) => { + // If two or more candidate overloads remain, proceed to step 2. + indexes + } + }; + + let snapshotter = MatchingOverloadsSnapshotter::new(matching_overload_indexes); + + // State of the bindings _before_ evaluating (type checking) the matching overloads using + // the non-expanded argument types. + let pre_evaluation_snapshot = snapshotter.take(self); + + // Step 2: Evaluate each remaining overload as a regular (non-overloaded) call to determine + // whether it is compatible with the supplied argument list. + for (_, overload) in self.matching_overloads_mut() { + overload.check_types(db, argument_types.as_ref(), argument_types.types()); + } + + match self.matching_overload_index() { + MatchingOverloadIndex::None => { + // If all overloads result in errors, proceed to step 3. + } + MatchingOverloadIndex::Single(_) => { + // If only one overload evaluates without error, it is the winning match. + return; + } + MatchingOverloadIndex::Multiple(_) => { + // If two or more candidate overloads remain, proceed to step 4. + // TODO: Step 4 and Step 5 goes here... + // We're returning here because this shouldn't lead to argument type expansion. + return; + } } + + // Step 3: Perform "argument type expansion". Reference: + // https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion + let mut expansions = argument_types.expand(db).peekable(); + + if expansions.peek().is_none() { + // Return early if there are no argument types to expand. + return; + } + + // State of the bindings _after_ evaluating (type checking) the matching overloads using + // the non-expanded argument types. + let post_evaluation_snapshot = snapshotter.take(self); + + // Restore the bindings state to the one prior to the type checking step in preparation + // for evaluating the expanded argument lists. + snapshotter.restore(self, pre_evaluation_snapshot); + + for expanded_argument_lists in expansions { + // This is the merged state of the bindings after evaluating all of the expanded + // argument lists. This will be the final state to restore the bindings to if all of + // the expanded argument lists evaluated successfully. + let mut merged_evaluation_state: Option> = None; + + let mut return_types = Vec::new(); + + for expanded_argument_types in &expanded_argument_lists { + let pre_evaluation_snapshot = snapshotter.take(self); + + for (_, overload) in self.matching_overloads_mut() { + overload.check_types(db, argument_types.as_ref(), expanded_argument_types); + } + + let return_type = match self.matching_overload_index() { + MatchingOverloadIndex::None => None, + MatchingOverloadIndex::Single(index) => { + Some(self.overloads[index].return_type()) + } + MatchingOverloadIndex::Multiple(index) => { + // TODO: Step 4 and Step 5 goes here... but for now we just use the return + // type of the first matched overload. + Some(self.overloads[index[0]].return_type()) + } + }; + + // This split between initializing and updating the merged evaluation state is + // required because otherwise it's difficult to differentiate between the + // following: + // 1. An initial unmatched overload becomes a matched overload when evaluating the + // first argument list + // 2. An unmatched overload after evaluating the first argument list becomes a + // matched overload when evaluating the second argument list + if let Some(merged_evaluation_state) = merged_evaluation_state.as_mut() { + merged_evaluation_state.update(self); + } else { + merged_evaluation_state = Some(snapshotter.take(self)); + } + + // Restore the bindings state before evaluating the next argument list. + snapshotter.restore(self, pre_evaluation_snapshot); + + if let Some(return_type) = return_type { + return_types.push(return_type); + } else { + // No need to check the remaining argument lists if the current argument list + // doesn't evaluate successfully. Move on to expanding the next argument type. + break; + } + } + + if return_types.len() == expanded_argument_lists.len() { + // If the number of return types is equal to the number of expanded argument lists, + // they all evaluated successfully. So, we need to combine their return types by + // union to determine the final return type. + self.return_type = Some(UnionType::from_elements(db, return_types)); + + // Restore the bindings state to the one that merges the bindings state evaluating + // each of the expanded argument list. + if let Some(merged_evaluation_state) = merged_evaluation_state { + snapshotter.restore(self, merged_evaluation_state); + } + + return; + } + } + + // If the type expansion didn't yield any successful return type, we need to restore the + // bindings state back to the one after the type checking step using the non-expanded + // argument types. This is necessary because we restore the state to the pre-evaluation + // snapshot when processing the expanded argument lists. + snapshotter.restore(self, post_evaluation_snapshot); } fn as_result(&self) -> Result<(), CallErrorKind> { @@ -1160,6 +1305,25 @@ impl<'db> CallableBinding<'db> { self.matching_overloads().next().is_none() } + /// Returns the index of the matching overload in the form of [`MatchingOverloadIndex`]. + fn matching_overload_index(&self) -> MatchingOverloadIndex { + let mut matching_overloads = self.matching_overloads(); + match matching_overloads.next() { + None => MatchingOverloadIndex::None, + Some((first, _)) => { + if let Some((second, _)) = matching_overloads.next() { + let mut indexes = vec![first, second]; + for (index, _) in matching_overloads { + indexes.push(index); + } + MatchingOverloadIndex::Multiple(indexes) + } else { + MatchingOverloadIndex::Single(first) + } + } + } + } + /// Returns an iterator over all the overloads that matched for this call binding. pub(crate) fn matching_overloads(&self) -> impl Iterator)> { self.overloads @@ -1178,16 +1342,20 @@ impl<'db> CallableBinding<'db> { .filter(|(_, overload)| overload.as_result().is_ok()) } - /// Returns the return type of this call. For a valid call, this is the return type of the - /// first overload that the arguments matched against. For an invalid call to a non-overloaded - /// function, this is the return type of the function. For an invalid call to an overloaded - /// function, we return `Type::unknown`, since we cannot make any useful conclusions about - /// which overload was intended to be called. + /// Returns the return type of this call. + /// + /// For a valid call, this is the return type of either a successful argument type expansion of + /// an overloaded function, or the return type of the first overload that the arguments matched + /// against. + /// + /// For an invalid call to a non-overloaded function, this is the return type of the function. + /// + /// For an invalid call to an overloaded function, we return `Type::unknown`, since we cannot + /// make any useful conclusions about which overload was intended to be called. pub(crate) fn return_type(&self) -> Type<'db> { - // TODO: Implement the overload call evaluation algorithm as mentioned in the spec [1] to - // get the matching overload and use that to get the return type. - // - // [1]: https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation + if let Some(return_type) = self.return_type { + return return_type; + } if let Some((_, first_overload)) = self.matching_overloads().next() { return first_overload.return_type(); } @@ -1336,6 +1504,18 @@ impl<'a, 'db> IntoIterator for &'a CallableBinding<'db> { } } +#[derive(Debug)] +enum MatchingOverloadIndex { + /// No matching overloads found. + None, + + /// Exactly one matching overload found at the given index. + Single(usize), + + /// Multiple matching overloads found at the given indexes. + Multiple(Vec), +} + /// Binding information for one of the overloads of a callable. #[derive(Debug)] pub(crate) struct Binding<'db> { @@ -1510,7 +1690,12 @@ impl<'db> Binding<'db> { self.parameter_tys = vec![None; parameters.len()].into_boxed_slice(); } - fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) { + fn check_types( + &mut self, + db: &'db dyn Db, + arguments: &CallArguments<'_>, + argument_types: &[Type<'db>], + ) { let mut num_synthetic_args = 0; let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { if argument_index >= num_synthetic_args { @@ -1524,13 +1709,20 @@ impl<'db> Binding<'db> { } }; + let enumerate_argument_types = || { + arguments + .iter() + .zip(argument_types.iter().copied()) + .enumerate() + }; + // If this overload is generic, first see if we can infer a specialization of the function // from the arguments that were passed in. let signature = &self.signature; let parameters = signature.parameters(); if signature.generic_context.is_some() || signature.inherited_generic_context.is_some() { let mut builder = SpecializationBuilder::new(db); - for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() { + for (argument_index, (argument, argument_type)) in enumerate_argument_types() { if matches!(argument, Argument::Synthetic) { num_synthetic_args += 1; } @@ -1562,7 +1754,7 @@ impl<'db> Binding<'db> { } num_synthetic_args = 0; - for (argument_index, (argument, mut argument_type)) in argument_types.iter().enumerate() { + for (argument_index, (argument, mut argument_type)) in enumerate_argument_types() { if matches!(argument, Argument::Synthetic) { num_synthetic_args += 1; } @@ -1665,6 +1857,133 @@ impl<'db> Binding<'db> { } Ok(()) } + + fn snapshot(&self) -> BindingSnapshot<'db> { + BindingSnapshot { + return_ty: self.return_ty, + specialization: self.specialization, + inherited_specialization: self.inherited_specialization, + argument_parameters: self.argument_parameters.clone(), + parameter_tys: self.parameter_tys.clone(), + errors: self.errors.clone(), + } + } + + fn restore(&mut self, snapshot: BindingSnapshot<'db>) { + let BindingSnapshot { + return_ty, + specialization, + inherited_specialization, + argument_parameters, + parameter_tys, + errors, + } = snapshot; + + self.return_ty = return_ty; + self.specialization = specialization; + self.inherited_specialization = inherited_specialization; + self.argument_parameters = argument_parameters; + self.parameter_tys = parameter_tys; + self.errors = errors; + } +} + +#[derive(Clone, Debug)] +struct BindingSnapshot<'db> { + return_ty: Type<'db>, + specialization: Option>, + inherited_specialization: Option>, + argument_parameters: Box<[Option]>, + parameter_tys: Box<[Option>]>, + errors: Vec>, +} + +/// Represents the snapshot of the matched overload bindings. +/// +/// The reason that this only contains the matched overloads are: +/// 1. Avoid creating snapshots for the overloads that have been filtered by the arity check +/// 2. Avoid duplicating errors when merging the snapshots on a successful evaluation of all the +/// expanded argument lists +#[derive(Clone, Debug)] +struct MatchingOverloadsSnapshot<'db>(Vec<(usize, BindingSnapshot<'db>)>); + +impl<'db> MatchingOverloadsSnapshot<'db> { + /// Update the state of the matched overload bindings in this snapshot with the current + /// state in the given `binding`. + fn update(&mut self, binding: &CallableBinding<'db>) { + // Here, the `snapshot` is the state of this binding for the previous argument list and + // `binding` would contain the state after evaluating the current argument list. + for (snapshot, binding) in self + .0 + .iter_mut() + .map(|(index, snapshot)| (snapshot, &binding.overloads[*index])) + { + if binding.errors.is_empty() { + // If the binding has no errors, this means that the current argument list was + // evaluated successfully and this is the matching overload. + // + // Clear the errors from the snapshot of this overload to signal this change ... + snapshot.errors.clear(); + + // ... and update the snapshot with the current state of the binding. + snapshot.return_ty = binding.return_ty; + snapshot.specialization = binding.specialization; + snapshot.inherited_specialization = binding.inherited_specialization; + snapshot + .argument_parameters + .clone_from(&binding.argument_parameters); + snapshot.parameter_tys.clone_from(&binding.parameter_tys); + } + + // If the errors in the snapshot was empty, then this binding is the matching overload + // for a previously evaluated argument list. This means that we don't need to change + // any information for an already matched overload binding. + // + // If it does have errors, we could extend it with the errors from evaluating the + // current argument list. Arguably, this isn't required, since the errors in the + // snapshot should already signal that this is an unmatched overload which is why we + // don't do it. Similarly, due to this being an unmatched overload, there's no point in + // updating the binding state. + } + } +} + +/// A helper to take snapshots of the matched overload bindings for the current state of the +/// bindings. +struct MatchingOverloadsSnapshotter(Vec); + +impl MatchingOverloadsSnapshotter { + /// Creates a new snapshotter for the given indexes of the matched overloads. + fn new(indexes: Vec) -> Self { + debug_assert!(indexes.len() > 1); + MatchingOverloadsSnapshotter(indexes) + } + + /// Takes a snapshot of the current state of the matched overload bindings. + /// + /// # Panics + /// + /// Panics if the indexes of the matched overloads are not valid for the given binding. + fn take<'db>(&self, binding: &CallableBinding<'db>) -> MatchingOverloadsSnapshot<'db> { + MatchingOverloadsSnapshot( + self.0 + .iter() + .map(|index| (*index, binding.overloads[*index].snapshot())) + .collect(), + ) + } + + /// Restores the state of the matched overload bindings from the given snapshot. + fn restore<'db>( + &self, + binding: &mut CallableBinding<'db>, + snapshot: MatchingOverloadsSnapshot<'db>, + ) { + debug_assert_eq!(self.0.len(), snapshot.0.len()); + for (index, snapshot) in snapshot.0 { + binding.overloads[index].restore(snapshot); + } + } } /// Describes a callable for the purposes of diagnostics. From 453e5f593468ad9a09d5fd26d7d4f8fc054b5f46 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 4 Jun 2025 08:10:26 +0530 Subject: [PATCH 328/487] [ty] Add tests for empty list/tuple unpacking (#18451) ## Summary This PR is to address this comment: https://github.com/astral-sh/ruff/pull/18438#issuecomment-2935344415 ## Test Plan Run mdtest --- .../ty_python_semantic/resources/mdtest/unpacking.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/unpacking.md b/crates/ty_python_semantic/resources/mdtest/unpacking.md index ac94dd99feb538..d4bdc2ce51ba5f 100644 --- a/crates/ty_python_semantic/resources/mdtest/unpacking.md +++ b/crates/ty_python_semantic/resources/mdtest/unpacking.md @@ -851,3 +851,14 @@ def _(arg: tuple[tuple[int, str], Iterable]): # revealed: tuple[int | bytes, str | bytes] [reveal_type((a, b)) for a, b in arg] ``` + +## Empty + +Unpacking an empty tuple or list shouldn't raise any diagnostics. + +```py +[] = [] +() = () +[] = () +() = [] +``` From 9e8a7e93530edaf54e7eeb972e90943bec500a01 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 3 Jun 2025 22:40:16 -0700 Subject: [PATCH 329/487] update to salsa that doesn't panic silently on cycles (#18450) --- Cargo.lock | 7 +++---- Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc8cba3ef87928..9f952927cc9a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3194,7 +3194,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" +source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" dependencies = [ "boxcar", "compact_str", @@ -3218,14 +3218,13 @@ dependencies = [ [[package]] name = "salsa-macro-rules" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" +source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" [[package]] name = "salsa-macros" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630" +source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" dependencies = [ - "heck", "proc-macro2", "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 46597dae62fbe4..989b595f6affc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" } +salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b1671c7bb8a197..0549d4f9b78c24 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" } +salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" } similar = { version = "2.5.0" } tracing = { version = "0.1.40" } From 293d4ac388715c0b5cbef1d0c80f4a0ae0246078 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 4 Jun 2025 09:44:44 +0200 Subject: [PATCH 330/487] [ty] Add meta-type tests for legavy TypeVars (#18453) ## Summary Follow up to the comment by @dcreager [here](https://github.com/astral-sh/ruff/pull/18439#discussion_r2123802784). --- .../mdtest/generics/legacy/variables.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index b58507f43c743f..cf3cff8177bcf8 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -196,4 +196,33 @@ def constrained(f: T): reveal_type(f()) # revealed: int | str ``` +## Meta-type + +The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the +meta-types of the constraints: + +```py +from typing import TypeVar + +T_normal = TypeVar("T_normal") + +def normal(x: T_normal): + reveal_type(type(x)) # revealed: type + +T_bound_object = TypeVar("T_bound_object", bound=object) + +def bound_object(x: T_bound_object): + reveal_type(type(x)) # revealed: type + +T_bound_int = TypeVar("T_bound_int", bound=int) + +def bound_int(x: T_bound_int): + reveal_type(type(x)) # revealed: type[int] + +T_constrained = TypeVar("T_constrained", int, str) + +def constrained(x: T_constrained): + reveal_type(type(x)) # revealed: type[int] | type[str] +``` + [generics]: https://typing.python.org/en/latest/spec/generics.html From 9f8c3de46260ea4523ff7c24f8c3434f5b80eff8 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 4 Jun 2025 09:55:45 +0200 Subject: [PATCH 331/487] [ty] Improve docs for Class{Literal,Type}::instance_member (#18454) ## Summary Mostly just refer to `Type::instance_member` which has much more details. --- crates/ty_python_semantic/src/types/class.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ab99ae27a6854b..205e587fad5942 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -472,12 +472,9 @@ impl<'db> ClassType<'db> { .map_type(|ty| ty.apply_optional_specialization(db, specialization)) } - /// Returns the `name` attribute of an instance of this class. + /// Look up an instance attribute (available in `__dict__`) of the given name. /// - /// The attribute could be defined in the class body, but it could also be an implicitly - /// defined attribute that is only present in a method (typically `__init__`). - /// - /// The attribute might also be defined in a superclass of this class. + /// See [`Type::instance_member`] for more details. pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { let (class_literal, specialization) = self.class_literal(db); class_literal @@ -1537,12 +1534,9 @@ impl<'db> ClassLiteral<'db> { attributes } - /// Returns the `name` attribute of an instance of this class. - /// - /// The attribute could be defined in the class body, but it could also be an implicitly - /// defined attribute that is only present in a method (typically `__init__`). + /// Look up an instance attribute (available in `__dict__`) of the given name. /// - /// The attribute might also be defined in a superclass of this class. + /// See [`Type::instance_member`] for more details. pub(super) fn instance_member( self, db: &'db dyn Db, From 11db567b0b493b107446124d4121c3a5257fd6c3 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 4 Jun 2025 10:39:16 +0200 Subject: [PATCH 332/487] [ty] ty_ide: Hotfix for `expression_scope_id` panics (#18455) ## Summary Implement a hotfix for the playground/LSP crashes related to missing `expression_scope_id`s. relates to: https://github.com/astral-sh/ty/issues/572 ## Test Plan * Regression tests from https://github.com/astral-sh/ruff/pull/18441 * Ran the playground locally to check if panics occur / completions still work. --------- Co-authored-by: Andrew Gallant --- _typos.toml | 4 + crates/ty_ide/src/completion.rs | 156 ++++++++++++++++++ .../ty_python_semantic/src/semantic_index.rs | 8 + .../ty_python_semantic/src/semantic_model.rs | 15 +- 4 files changed, 179 insertions(+), 4 deletions(-) diff --git a/_typos.toml b/_typos.toml index 402e1755337bb0..24406f10bba161 100644 --- a/_typos.toml +++ b/_typos.toml @@ -4,6 +4,10 @@ extend-exclude = [ "crates/ty_vendored/vendor/**/*", "**/resources/**/*", "**/snapshots/**/*", + # Completion tests tend to have a lot of incomplete + # words naturally. It's annoying to have to make all + # of them actually words. So just ignore typos here. + "crates/ty_ide/src/completion.rs", ] [default.extend-words] diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index ab8b6e7cf0c84b..cfbd998d31495e 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -861,6 +861,162 @@ print(f\"{some "); } + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_function_identifier1() { + let test = cursor_test( + "\ +def m +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_function_identifier2() { + let test = cursor_test( + "\ +def m(): pass +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn fscope_id_missing_function_identifier3() { + let test = cursor_test( + "\ +def m(): pass + +", + ); + + assert_snapshot!(test.completions(), @r" + m + "); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_class_identifier1() { + let test = cursor_test( + "\ +class M +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_type_alias1() { + let test = cursor_test( + "\ +Fo = float +", + ); + + assert_snapshot!(test.completions(), @r" + Fo + float + "); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_import1() { + let test = cursor_test( + "\ +import fo +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_import2() { + let test = cursor_test( + "\ +import foo as ba +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_from_import1() { + let test = cursor_test( + "\ +from fo import wat +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_from_import2() { + let test = cursor_test( + "\ +from foo import wa +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_from_import3() { + let test = cursor_test( + "\ +from foo import wat as ba +", + ); + + assert_snapshot!(test.completions(), @""); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_try_except1() { + let test = cursor_test( + "\ +try: + pass +except Type: + pass +", + ); + + assert_snapshot!(test.completions(), @r" + Type + "); + } + + // Ref: https://github.com/astral-sh/ty/issues/572 + #[test] + fn scope_id_missing_global1() { + let test = cursor_test( + "\ +def _(): + global fo +", + ); + + assert_snapshot!(test.completions(), @""); + } + impl CursorTest { fn completions(&self) -> String { let completions = completion(&self.db, self.file, self.cursor_offset); diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index 6d57a8515c8790..c3a2f19418ebab 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -259,6 +259,14 @@ impl<'db> SemanticIndex<'db> { self.scopes_by_expression[&expression.into()] } + /// Returns the ID of the `expression`'s enclosing scope. + pub(crate) fn try_expression_scope_id( + &self, + expression: impl Into, + ) -> Option { + self.scopes_by_expression.get(&expression.into()).copied() + } + /// Returns the [`Scope`] of the `expression`'s enclosing scope. #[allow(unused)] #[track_caller] diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index e3e7b15cf86133..c112fc1ba3b7e8 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -47,15 +47,22 @@ impl<'db> SemanticModel<'db> { /// scope of this model's `File` are returned. pub fn completions(&self, node: ast::AnyNodeRef<'_>) -> Vec { let index = semantic_index(self.db, self.file); - let file_scope = match node { - ast::AnyNodeRef::Identifier(identifier) => index.expression_scope_id(identifier), + + // TODO: We currently use `try_expression_scope_id` here as a hotfix for [1]. + // Revert this to use `expression_scope_id` once a proper fix is in place. + // + // [1] https://github.com/astral-sh/ty/issues/572 + let Some(file_scope) = (match node { + ast::AnyNodeRef::Identifier(identifier) => index.try_expression_scope_id(identifier), node => match node.as_expr_ref() { // If we couldn't identify a specific // expression that we're in, then just // fall back to the global scope. - None => FileScopeId::global(), - Some(expr) => index.expression_scope_id(expr), + None => Some(FileScopeId::global()), + Some(expr) => index.try_expression_scope_id(expr), }, + }) else { + return vec![]; }; let mut symbols = vec![]; for (file_scope, _) in index.ancestor_scopes(file_scope) { From f1883d71a4d47a56f8dd8c1874db40f12bc3b105 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 4 Jun 2025 16:11:05 +0200 Subject: [PATCH 333/487] [ty] IDE: only provide declarations and bindings as completions (#18456) ## Summary Previously, all symbols where provided as possible completions. In an example like the following, both `foo` and `f` were suggested as completions, because `f` itself is a symbol. ```py foo = 1 f ``` Similarly, in the following example, `hidden_symbol` was suggested, even though it is not statically visible: ```py if 1 + 2 != 3: hidden_symbol = 1 hidden_ ``` With the change suggested here, we only use statically visible declarations and bindings as a source for completions. ## Test Plan - Updated snapshot tests - New test for statically hidden definitions - Added test for star import --- crates/ty_ide/src/completion.rs | 151 ++++++++++-------- .../ty_python_semantic/src/semantic_model.rs | 8 +- crates/ty_python_semantic/src/types.rs | 2 +- .../src/types/ide_support.rs | 51 +++--- 4 files changed, 126 insertions(+), 86 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index cfbd998d31495e..ebc0c9197ebbbd 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -127,10 +127,7 @@ f ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -143,10 +140,7 @@ g ", ); - assert_snapshot!(test.completions(), @r" - foo - g - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -175,10 +169,7 @@ f ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -208,7 +199,6 @@ def foo(): ); assert_snapshot!(test.completions(), @r" - f foo foofoo "); @@ -259,7 +249,6 @@ def foo(): ); assert_snapshot!(test.completions(), @r" - f foo foofoo "); @@ -276,7 +265,6 @@ def foo(): ); assert_snapshot!(test.completions(), @r" - f foo foofoo "); @@ -295,7 +283,6 @@ def frob(): ... ); assert_snapshot!(test.completions(), @r" - f foo foofoo frob @@ -315,7 +302,6 @@ def frob(): ... ); assert_snapshot!(test.completions(), @r" - f foo frob "); @@ -334,7 +320,6 @@ def frob(): ... ); assert_snapshot!(test.completions(), @r" - f foo foofoo foofoofoo @@ -451,15 +436,10 @@ def frob(): ... ", ); - // It's not totally clear why `for` shows up in the - // symbol tables of the detected scopes here. My guess - // is that there's perhaps some sub-optimal behavior - // here because the list comprehension as written is not - // valid. - assert_snapshot!(test.completions(), @r" - bar - for - "); + // TODO: it would be good if `bar` was included here, but + // the list comprehension is not yet valid and so we do not + // detect this as a definition of `bar`. + assert_snapshot!(test.completions(), @""); } #[test] @@ -470,10 +450,7 @@ def frob(): ... ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -484,10 +461,7 @@ def frob(): ... ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -498,10 +472,7 @@ def frob(): ... ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -512,10 +483,7 @@ def frob(): ... ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -526,10 +494,7 @@ def frob(): ... ", ); - assert_snapshot!(test.completions(), @r" - f - foo - "); + assert_snapshot!(test.completions(), @"foo"); } #[test] @@ -602,7 +567,6 @@ class Foo: assert_snapshot!(test.completions(), @r" Foo - b bar frob quux @@ -621,7 +585,6 @@ class Foo: assert_snapshot!(test.completions(), @r" Foo - b bar quux "); @@ -750,7 +713,6 @@ bar(o assert_snapshot!(test.completions(), @r" bar foo - o "); } @@ -788,7 +750,6 @@ class C: assert_snapshot!(test.completions(), @r" C bar - f foo self "); @@ -825,7 +786,6 @@ class C: assert_snapshot!(test.completions(), @r" C bar - f foo self "); @@ -854,11 +814,55 @@ print(f\"{some ", ); - assert_snapshot!(test.completions(), @r" - print - some - some_symbol - "); + assert_snapshot!(test.completions(), @"some_symbol"); + } + + #[test] + fn statically_invisible_symbols() { + let test = cursor_test( + "\ +if 1 + 2 != 3: + hidden_symbol = 1 + +hidden_ +", + ); + + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn completions_inside_unreachable_sections() { + let test = cursor_test( + "\ +import sys + +if sys.platform == \"not-my-current-platform\": + only_available_in_this_branch = 1 + + on +", + ); + + // TODO: ideally, `only_available_in_this_branch` should be available here, but we + // currently make no effort to provide a good IDE experience within sections that + // are unreachable + assert_snapshot!(test.completions(), @"sys"); + } + + #[test] + fn star_import() { + let test = cursor_test( + "\ +from typing import * + +Re +", + ); + + test.assert_completions_include("Reversible"); + // `ReadableBuffer` is a symbol in `typing`, but it is not re-exported + test.assert_completions_do_not_include("ReadableBuffer"); } // Ref: https://github.com/astral-sh/ty/issues/572 @@ -921,10 +925,7 @@ Fo = float ", ); - assert_snapshot!(test.completions(), @r" - Fo - float - "); + assert_snapshot!(test.completions(), @"Fo"); } // Ref: https://github.com/astral-sh/ty/issues/572 @@ -999,9 +1000,7 @@ except Type: ", ); - assert_snapshot!(test.completions(), @r" - Type - "); + assert_snapshot!(test.completions(), @""); } // Ref: https://github.com/astral-sh/ty/issues/572 @@ -1029,5 +1028,29 @@ def _(): .collect::>() .join("\n") } + + #[track_caller] + fn assert_completions_include(&self, expected: &str) { + let completions = completion(&self.db, self.file, self.cursor_offset); + + assert!( + completions + .iter() + .any(|completion| completion.label == expected), + "Expected completions to include `{expected}`" + ); + } + + #[track_caller] + fn assert_completions_do_not_include(&self, unexpected: &str) { + let completions = completion(&self.db, self.file, self.cursor_offset); + + assert!( + completions + .iter() + .all(|completion| completion.label != unexpected), + "Expected completions to not include `{unexpected}`", + ); + } } } diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index c112fc1ba3b7e8..bc4204386e0168 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -10,6 +10,7 @@ use crate::module_resolver::{Module, resolve_module}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::semantic_index; use crate::semantic_index::symbol::FileScopeId; +use crate::types::ide_support::all_declarations_and_bindings; use crate::types::{Type, binding_type, infer_scope_types}; pub struct SemanticModel<'db> { @@ -66,9 +67,10 @@ impl<'db> SemanticModel<'db> { }; let mut symbols = vec![]; for (file_scope, _) in index.ancestor_scopes(file_scope) { - for symbol in index.symbol_table(file_scope).symbols() { - symbols.push(symbol.name().clone()); - } + symbols.extend(all_declarations_and_bindings( + self.db, + file_scope.to_scope_id(self.db, self.file), + )); } symbols } diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index aec03a73a6f2c3..b2d1c6b91f1ac8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -65,7 +65,7 @@ mod diagnostic; mod display; mod function; mod generics; -mod ide_support; +pub(crate) mod ide_support; mod infer; mod instance; mod mro; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 2ba4606d6a8dff..c8f150aa437e4e 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -8,6 +8,37 @@ use crate::types::{ClassBase, ClassLiteral, KnownClass, Type}; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; +pub(crate) fn all_declarations_and_bindings<'db>( + db: &'db dyn Db, + scope_id: ScopeId<'db>, +) -> impl Iterator + 'db { + let use_def_map = use_def_map(db, scope_id); + let symbol_table = symbol_table(db, scope_id); + + use_def_map + .all_public_declarations() + .filter_map(move |(symbol_id, declarations)| { + if symbol_from_declarations(db, declarations) + .is_ok_and(|result| !result.symbol.is_unbound()) + { + Some(symbol_table.symbol(symbol_id).name().clone()) + } else { + None + } + }) + .chain( + use_def_map + .all_public_bindings() + .filter_map(move |(symbol_id, bindings)| { + if symbol_from_bindings(db, bindings).is_unbound() { + None + } else { + Some(symbol_table.symbol(symbol_id).name().clone()) + } + }), + ) +} + struct AllMembers { members: FxHashSet, } @@ -118,24 +149,8 @@ impl AllMembers { } fn extend_with_declarations_and_bindings(&mut self, db: &dyn Db, scope_id: ScopeId) { - let use_def_map = use_def_map(db, scope_id); - let symbol_table = symbol_table(db, scope_id); - - for (symbol_id, declarations) in use_def_map.all_public_declarations() { - if symbol_from_declarations(db, declarations) - .is_ok_and(|result| !result.symbol.is_unbound()) - { - self.members - .insert(symbol_table.symbol(symbol_id).name().clone()); - } - } - - for (symbol_id, bindings) in use_def_map.all_public_bindings() { - if !symbol_from_bindings(db, bindings).is_unbound() { - self.members - .insert(symbol_table.symbol(symbol_id).name().clone()); - } - } + self.members + .extend(all_declarations_and_bindings(db, scope_id)); } fn extend_with_class_members<'db>( From e658778cedc25051ec7f6aa6bc243bbc446fe416 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:13:50 +0200 Subject: [PATCH 334/487] [ty] Add subdiagnostic suggestion to `unresolved-reference` diagnostic when variable exists on `self` (#18444) ## Summary Closes https://github.com/astral-sh/ty/issues/502. In the following example: ```py class Foo: x: int def method(self): y = x ``` The user may intended to use `y = self.x` in `method`. This is now added as a subdiagnostic in the following form : `info: An attribute with the same name as 'x' is defined, consider using 'self.x'` ## Test Plan Added mdtest with snapshot diagnostics. --- .../resources/mdtest/attributes.md | 36 ++++++++ ...to_at\342\200\246_(5457445ffed43a87).snap" | 82 +++++++++++++++++++ .../src/types/diagnostic.rs | 12 ++- crates/ty_python_semantic/src/types/infer.rs | 18 +++- 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 18f32d9acbf84e..0b74ab50aa3c8d 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -928,6 +928,42 @@ def _(flag1: bool, flag2: bool): reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"] ``` +## Invalid access to attribute + + + +If a non-declared variable is used and an attribute with the same name is defined and accessible, +then we emit a subdiagnostic suggesting the use of `self.`. +(`An attribute with the same name as 'x' is defined, consider using 'self.x'` in these cases) + +```py +class Foo: + x: int + + def method(self): + # error: [unresolved-reference] "Name `x` used when not defined" + y = x +``` + +```py +class Foo: + x: int = 1 + + def method(self): + # error: [unresolved-reference] "Name `x` used when not defined" + y = x +``` + +```py +class Foo: + def __init__(self): + self.x = 1 + + def method(self): + # error: [unresolved-reference] "Name `x` used when not defined" + y = x +``` + ## Unions of attributes If the (meta)class is a union type or if the attribute on the (meta) class has a union type, we diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" new file mode 100644 index 00000000000000..41b32f8ac85996 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" @@ -0,0 +1,82 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: attributes.md - Attributes - Invalid access to attribute +mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | class Foo: + 2 | x: int + 3 | + 4 | def method(self): + 5 | # error: [unresolved-reference] "Name `x` used when not defined" + 6 | y = x + 7 | class Foo: + 8 | x: int = 1 + 9 | +10 | def method(self): +11 | # error: [unresolved-reference] "Name `x` used when not defined" +12 | y = x +13 | class Foo: +14 | def __init__(self): +15 | self.x = 1 +16 | +17 | def method(self): +18 | # error: [unresolved-reference] "Name `x` used when not defined" +19 | y = x +``` + +# Diagnostics + +``` +error[unresolved-reference]: Name `x` used when not defined + --> src/mdtest_snippet.py:6:13 + | +4 | def method(self): +5 | # error: [unresolved-reference] "Name `x` used when not defined" +6 | y = x + | ^ +7 | class Foo: +8 | x: int = 1 + | +info: An attribute `x` is available, consider using `self.x` +info: rule `unresolved-reference` is enabled by default + +``` + +``` +error[unresolved-reference]: Name `x` used when not defined + --> src/mdtest_snippet.py:12:13 + | +10 | def method(self): +11 | # error: [unresolved-reference] "Name `x` used when not defined" +12 | y = x + | ^ +13 | class Foo: +14 | def __init__(self): + | +info: An attribute `x` is available, consider using `self.x` +info: rule `unresolved-reference` is enabled by default + +``` + +``` +error[unresolved-reference]: Name `x` used when not defined + --> src/mdtest_snippet.py:19:13 + | +17 | def method(self): +18 | # error: [unresolved-reference] "Name `x` used when not defined" +19 | y = x + | ^ + | +info: An attribute `x` is available, consider using `self.x` +info: rule `unresolved-reference` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 39277b142b2529..addffb6e23820f 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1778,7 +1778,11 @@ pub(super) fn report_possibly_unbound_attribute( )); } -pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) { +pub(super) fn report_unresolved_reference( + context: &InferContext, + expr_name_node: &ast::ExprName, + attribute_exists: bool, +) { let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else { return; }; @@ -1795,6 +1799,12 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node "resolving types", ); } + + if attribute_exists { + diagnostic.info(format_args!( + "An attribute `{id}` is available, consider using `self.{id}`" + )); + } } pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index af77abaabc37fe..7537573b60ad83 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1718,6 +1718,9 @@ impl<'db> TypeInferenceBuilder<'db> { fn class_context_of_current_method(&self) -> Option> { let current_scope_id = self.scope().file_scope_id(self.db()); let current_scope = self.index.scope(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); @@ -5899,7 +5902,20 @@ impl<'db> TypeInferenceBuilder<'db> { .unwrap_with_diagnostic(|lookup_error| match lookup_error { LookupError::Unbound(qualifiers) => { if self.is_reachable(name_node) { - report_unresolved_reference(&self.context, name_node); + let attribute_exists = + if let Some(class) = self.class_context_of_current_method() { + let symbol = Type::instance(db, class.default_specialization(db)) + .member(db, symbol_name) + .symbol; + match symbol { + Symbol::Type(..) => true, + Symbol::Unbound => false, + } + } else { + false + }; + + report_unresolved_reference(&self.context, name_node, attribute_exists); } TypeAndQualifiers::new(Type::unknown(), qualifiers) } From 3a8191529c71084493ad16b054552151feb7a4b0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 4 Jun 2025 20:34:09 +0100 Subject: [PATCH 335/487] [ty] Exclude members starting with `_abc_` from a protocol interface (#18467) ## Summary As well as excluding a hardcoded set of special attributes, CPython at runtime also excludes any attributes or declarations starting with `_abc_` from the set of members that make up a protocol interface. I missed this in my initial implementation. This is a bit of a CPython implementation detail, but I do think it's important that we try to model the runtime as best we can here. The closer we are to the runtime behaviour, the closer we come to sound behaviour when narrowing types from `isinstance()` checks against runtime-checkable protocols (for example) ## Test Plan Extended an existing mdtest --- crates/ty_python_semantic/resources/mdtest/protocols.md | 1 + crates/ty_python_semantic/src/types/protocol_class.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 763694dacba82c..c51916ece76db4 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -389,6 +389,7 @@ not be considered protocol members by type checkers either: class Lumberjack(Protocol): __slots__ = () __match_args__ = () + _abc_foo: str # any attribute starting with `_abc_` is excluded as a protocol attribute x: int def __new__(cls, x: int) -> "Lumberjack": diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 7efdc8453126f1..dbf7837a0c60df 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -273,7 +273,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { /// The list of excluded members is subject to change between Python versions, /// especially for dunders, but it probably doesn't matter *too* much if this /// list goes out of date. It's up to date as of Python commit 87b1ea016b1454b1e83b9113fa9435849b7743aa -/// () +/// () fn excluded_from_proto_members(member: &str) -> bool { matches!( member, @@ -303,7 +303,7 @@ fn excluded_from_proto_members(member: &str) -> bool { | "__annotate__" | "__annotate_func__" | "__annotations_cache__" - ) + ) || member.starts_with("_abc_") } /// Inner Salsa query for [`ProtocolClassLiteral::interface`]. From 5a8cdab7712eedebb4e0dade71ba80f7f5b9c841 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 4 Jun 2025 20:39:14 +0100 Subject: [PATCH 336/487] [ty] Only consider a type `T` a subtype of a protocol `P` if all of `P`'s members are fully bound on `T` (#18466) ## Summary Fixes https://github.com/astral-sh/ty/issues/578 ## Test Plan mdtests --- .../resources/mdtest/narrow/hasattr.md | 23 ++++++++++++++++++- .../ty_python_semantic/src/types/instance.rs | 14 +++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md index b15891216086fd..394eb975887190 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md @@ -26,7 +26,7 @@ def f(x: Foo): else: reveal_type(x) # revealed: Foo -def y(x: Bar): +def g(x: Bar): if hasattr(x, "spam"): reveal_type(x) # revealed: Never reveal_type(x.spam) # revealed: Never @@ -35,4 +35,25 @@ def y(x: Bar): # error: [unresolved-attribute] reveal_type(x.spam) # revealed: Unknown + +def returns_bool() -> bool: + return False + +class Baz: + if returns_bool(): + x: int = 42 + +def h(obj: Baz): + reveal_type(obj) # revealed: Baz + # error: [possibly-unbound-attribute] + reveal_type(obj.x) # revealed: int + + if hasattr(obj, "x"): + reveal_type(obj) # revealed: Baz & + reveal_type(obj.x) # revealed: int + else: + reveal_type(obj) # revealed: Baz & ~ + + # TODO: should emit `[unresolved-attribute]` and reveal `Unknown` + reveal_type(obj.x) # revealed: @Todo(map_with_boundness: intersections with negative contributions) ``` diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index e63a420cd1c3dd..e727655990ad8f 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; -use crate::symbol::{Symbol, SymbolAndQualifiers}; +use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers}; use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance}; use crate::{Db, FxOrderSet}; @@ -45,12 +45,12 @@ impl<'db> Type<'db> { protocol: ProtocolInstanceType<'db>, ) -> bool { // TODO: this should consider the types of the protocol members - // as well as whether each member *exists* on `self`. - protocol - .inner - .interface(db) - .members(db) - .all(|member| !self.member(db, member.name()).symbol.is_unbound()) + protocol.inner.interface(db).members(db).all(|member| { + matches!( + self.member(db, member.name()).symbol, + Symbol::Type(_, Boundness::Bound) + ) + }) } } From ce8b744f17ef2bfe0a507a197df947458703d42e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 4 Jun 2025 20:41:00 +0100 Subject: [PATCH 337/487] [ty] Only calculate information for unresolved-reference subdiagnostic if we know we'll emit the diagnostic (#18465) ## Summary This optimizes some of the logic added in https://github.com/astral-sh/ruff/pull/18444. In general, we only calculate information for subdiagnostics if we know we'll actually emit the diagnostic. The check to see whether we'll emit the diagnostic is work we'll definitely have to do whereas the the work to gather information for a subdiagnostic isn't work we necessarily have to do if the diagnostic isn't going to be emitted at all. This PR makes us lazier about gathering the information we need for the subdiagnostic, and moves all the subdiagnostic logic into one function rather than having some `unresolved-reference` subdiagnostic logic in `infer.rs` and some in `diagnostic.rs`. ## Test Plan `cargo test -p ty_python_semantic` --- crates/ty/docs/rules.md | 108 +++++++++--------- ...to_at\342\200\246_(5457445ffed43a87).snap" | 6 +- .../src/types/diagnostic.rs | 30 ----- crates/ty_python_semantic/src/types/infer.rs | 67 ++++++++--- 4 files changed, 105 insertions(+), 106 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 731d08c6452ca7..30fe9952314334 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) ## `invalid-type-variable-constraints` @@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896) ## `missing-argument` @@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925) ## `no-matching-overload` @@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944) ## `non-subscriptable` @@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967) ## `not-iterable` @@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985) ## `parameter-already-assigned` @@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) ## `raw-string-type-annotation` @@ -1231,7 +1231,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) ## `subclass-of-final-class` @@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127) ## `too-many-positional-arguments` @@ -1285,7 +1285,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) ## `type-assertion-failure` @@ -1312,7 +1312,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1356,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193) ## `unknown-argument` @@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250) ## `unresolved-attribute` @@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) ## `unresolved-import` @@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293) ## `unresolved-reference` @@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) ## `unsupported-bool-conversion` @@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005) ## `unsupported-operator` @@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331) ## `zero-stepsize-in-slice` @@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353) ## `invalid-ignore-comment` @@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057) ## `possibly-unbound-implicit-call` @@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110) ## `possibly-unbound-import` @@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079) ## `redundant-cast` @@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424) ## `undefined-reveal` @@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232) ## `unknown-rule` @@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488) ## `division-by-zero` @@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239) ## `possibly-unresolved-reference` @@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105) ## `unused-ignore-comment` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" index 41b32f8ac85996..d4eb778d48beea 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Invalid_access_to_at\342\200\246_(5457445ffed43a87).snap" @@ -46,7 +46,7 @@ error[unresolved-reference]: Name `x` used when not defined 7 | class Foo: 8 | x: int = 1 | -info: An attribute `x` is available, consider using `self.x` +info: An attribute `x` is available: consider using `self.x` info: rule `unresolved-reference` is enabled by default ``` @@ -62,7 +62,7 @@ error[unresolved-reference]: Name `x` used when not defined 13 | class Foo: 14 | def __init__(self): | -info: An attribute `x` is available, consider using `self.x` +info: An attribute `x` is available: consider using `self.x` info: rule `unresolved-reference` is enabled by default ``` @@ -76,7 +76,7 @@ error[unresolved-reference]: Name `x` used when not defined 19 | y = x | ^ | -info: An attribute `x` is available, consider using `self.x` +info: An attribute `x` is available: consider using `self.x` info: rule `unresolved-reference` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index addffb6e23820f..8f62976c1e5ed0 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -19,7 +19,6 @@ use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; -use ruff_python_stdlib::builtins::version_builtin_was_added; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::Formatter; @@ -1778,35 +1777,6 @@ pub(super) fn report_possibly_unbound_attribute( )); } -pub(super) fn report_unresolved_reference( - context: &InferContext, - expr_name_node: &ast::ExprName, - attribute_exists: bool, -) { - let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else { - return; - }; - - let ast::ExprName { id, .. } = expr_name_node; - let mut diagnostic = builder.into_diagnostic(format_args!("Name `{id}` used when not defined")); - if let Some(version_added_to_builtins) = version_builtin_was_added(id) { - diagnostic.info(format_args!( - "`{id}` was added as a builtin in Python 3.{version_added_to_builtins}" - )); - add_inferred_python_version_hint_to_diagnostic( - context.db(), - &mut diagnostic, - "resolving types", - ); - } - - if attribute_exists { - diagnostic.info(format_args!( - "An attribute `{id}` is available, consider using `self.{id}`" - )); - } -} - pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) { let Some(builder) = context.report_lint(&INVALID_EXCEPTION_CAUGHT, node) else { return; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 7537573b60ad83..5e485b1346fd34 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -39,6 +39,7 @@ use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_python_ast::visitor::{Visitor, walk_expr}; use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion}; +use ruff_python_stdlib::builtins::version_builtin_was_added; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::{FxHashMap, FxHashSet}; use salsa; @@ -75,8 +76,8 @@ use crate::types::diagnostic::{ INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, - UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR, - report_implicit_return_type, report_invalid_arguments_to_annotated, + UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, + UNSUPPORTED_OPERATOR, report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, report_invalid_attribute_assignment, report_invalid_generator_function_return_type, report_invalid_return_type, report_possibly_unbound_attribute, @@ -112,7 +113,6 @@ use super::diagnostic::{ report_invalid_type_checking_constant, report_non_subscriptable, report_possibly_unresolved_reference, report_runtime_check_against_non_runtime_checkable_protocol, report_slice_step_size_zero, - report_unresolved_reference, }; use super::generics::LegacyGenericBase; use super::slots::check_class_slots; @@ -5901,22 +5901,7 @@ impl<'db> TypeInferenceBuilder<'db> { symbol .unwrap_with_diagnostic(|lookup_error| match lookup_error { LookupError::Unbound(qualifiers) => { - if self.is_reachable(name_node) { - let attribute_exists = - if let Some(class) = self.class_context_of_current_method() { - let symbol = Type::instance(db, class.default_specialization(db)) - .member(db, symbol_name) - .symbol; - match symbol { - Symbol::Type(..) => true, - Symbol::Unbound => false, - } - } else { - false - }; - - report_unresolved_reference(&self.context, name_node, attribute_exists); - } + self.report_unresolved_reference(name_node); TypeAndQualifiers::new(Type::unknown(), qualifiers) } LookupError::PossiblyUnbound(type_when_bound) => { @@ -5929,6 +5914,50 @@ impl<'db> TypeInferenceBuilder<'db> { .inner_type() } + pub(super) fn report_unresolved_reference(&self, expr_name_node: &ast::ExprName) { + if !self.is_reachable(expr_name_node) { + return; + } + + let Some(builder) = self + .context + .report_lint(&UNRESOLVED_REFERENCE, expr_name_node) + else { + return; + }; + + let ast::ExprName { id, .. } = expr_name_node; + let mut diagnostic = + builder.into_diagnostic(format_args!("Name `{id}` used when not defined")); + + if let Some(version_added_to_builtins) = version_builtin_was_added(id) { + diagnostic.info(format_args!( + "`{id}` was added as a builtin in Python 3.{version_added_to_builtins}" + )); + add_inferred_python_version_hint_to_diagnostic( + self.db(), + &mut diagnostic, + "resolving types", + ); + } + + let attribute_exists = self + .class_context_of_current_method() + .and_then(|class| { + Type::instance(self.db(), class.default_specialization(self.db())) + .member(self.db(), id) + .symbol + .ignore_possibly_unbound() + }) + .is_some(); + + if attribute_exists { + diagnostic.info(format_args!( + "An attribute `{id}` is available: consider using `self.{id}`" + )); + } + } + fn infer_name_expression(&mut self, name: &ast::ExprName) -> Type<'db> { match name.ctx { ExprContext::Load => self.infer_name_load(name), From 0858896bc434bb7666b7230bde52d2113f328ac9 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama <45118249+mtshiba@users.noreply.github.com> Date: Thu, 5 Jun 2025 09:24:27 +0900 Subject: [PATCH 338/487] [ty] type narrowing by attribute/subscript assignments (#18041) ## Summary This PR partially solves https://github.com/astral-sh/ty/issues/164 (derived from #17643). Currently, the definitions we manage are limited to those for simple name (symbol) targets, but we expand this to track definitions for attribute and subscript targets as well. This was originally planned as part of the work in #17643, but the changes are significant, so I made it a separate PR. After merging this PR, I will reflect this changes in #17643. There is still some incomplete work remaining, but the basic features have been implemented, so I am publishing it as a draft PR. Here is the TODO list (there may be more to come): * [x] Complete rewrite and refactoring of documentation (removing `Symbol` and replacing it with `Place`) * [x] More thorough testing * [x] Consolidation of duplicated code (maybe we can consolidate the handling related to name, attribute, and subscript) This PR replaces the current `Symbol` API with the `Place` API, which is a concept that includes attributes and subscripts (the term is borrowed from Rust). ## Test Plan `mdtest/narrow/assignment.md` is added. --------- Co-authored-by: David Peter Co-authored-by: Carl Meyer --- ...xprs_not_found_in_evaluate_expr_compare.py | 11 + .../resources/mdtest/attributes.md | 83 +- .../resources/mdtest/descriptor_protocol.md | 4 +- .../resources/mdtest/narrow/assignment.md | 318 ++++++ .../mdtest/narrow/conditionals/nested.md | 177 ++++ crates/ty_python_semantic/src/dunder_all.rs | 2 +- crates/ty_python_semantic/src/lib.rs | 2 +- .../src/{symbol.rs => place.rs} | 505 +++++----- .../ty_python_semantic/src/semantic_index.rs | 228 ++--- .../src/semantic_index/ast_ids.rs | 24 +- .../src/semantic_index/builder.rs | 557 ++++------- .../src/semantic_index/definition.rs | 151 ++- .../src/semantic_index/expression.rs | 2 +- .../semantic_index/narrowing_constraints.rs | 6 +- .../src/semantic_index/place.rs | 942 ++++++++++++++++++ .../src/semantic_index/predicate.rs | 6 +- .../src/semantic_index/symbol.rs | 589 ----------- .../src/semantic_index/use_def.rs | 460 ++++----- .../{symbol_state.rs => place_state.rs} | 153 +-- .../semantic_index/visibility_constraints.rs | 18 +- .../ty_python_semantic/src/semantic_model.rs | 2 +- crates/ty_python_semantic/src/types.rs | 421 ++++---- .../ty_python_semantic/src/types/call/bind.rs | 8 +- crates/ty_python_semantic/src/types/class.rs | 186 ++-- .../ty_python_semantic/src/types/context.rs | 2 +- .../ty_python_semantic/src/types/display.rs | 4 +- .../ty_python_semantic/src/types/function.rs | 12 +- .../src/types/ide_support.rs | 48 +- crates/ty_python_semantic/src/types/infer.rs | 793 +++++++++------ .../ty_python_semantic/src/types/instance.rs | 12 +- crates/ty_python_semantic/src/types/narrow.rs | 22 +- .../types/property_tests/type_generation.rs | 20 +- .../src/types/protocol_class.rs | 37 +- .../src/types/signatures.rs | 7 +- crates/ty_python_semantic/src/types/slots.rs | 4 +- .../src/types/subclass_of.rs | 4 +- .../ty_python_semantic/src/types/unpacker.rs | 4 +- crates/ty_python_semantic/src/unpack.rs | 2 +- 38 files changed, 3427 insertions(+), 2399 deletions(-) create mode 100644 crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py create mode 100644 crates/ty_python_semantic/resources/mdtest/narrow/assignment.md rename crates/ty_python_semantic/src/{symbol.rs => place.rs} (72%) create mode 100644 crates/ty_python_semantic/src/semantic_index/place.rs delete mode 100644 crates/ty_python_semantic/src/semantic_index/symbol.rs rename crates/ty_python_semantic/src/semantic_index/use_def/{symbol_state.rs => place_state.rs} (84%) diff --git a/crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py b/crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py new file mode 100644 index 00000000000000..7dc644bec95f22 --- /dev/null +++ b/crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py @@ -0,0 +1,11 @@ +# This is a regression test for `infer_expression_types`. +# ref: https://github.com/astral-sh/ruff/pull/18041#discussion_r2094573989 + +class C: + def f(self, other: "C"): + if self.a > other.b or self.b: + return False + if self: + return True + +C().a diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 0b74ab50aa3c8d..71288e51093439 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -37,7 +37,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown # See https://github.com/astral-sh/ruff/issues/15960 for a related discussion. reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None -reveal_type(c_instance.declared_only) # revealed: bytes +# TODO: Should be `bytes` with no error, like mypy and pyright? +# error: [unresolved-attribute] +reveal_type(c_instance.declared_only) # revealed: Unknown reveal_type(c_instance.declared_and_bound) # revealed: bool @@ -64,12 +66,10 @@ C.inferred_from_value = "overwritten on class" # This assignment is fine: c_instance.declared_and_bound = False -# TODO: After this assignment to the attribute within this scope, we may eventually want to narrow -# the `bool` type (see above) for this instance variable to `Literal[False]` here. This is unsound -# in general (we don't know what else happened to `c_instance` between the assignment and the use -# here), but mypy and pyright support this. In conclusion, this could be `bool` but should probably -# be `Literal[False]`. -reveal_type(c_instance.declared_and_bound) # revealed: bool +# Strictly speaking, inferring this as `Literal[False]` rather than `bool` is unsound in general +# (we don't know what else happened to `c_instance` between the assignment and the use here), +# but mypy and pyright support this. +reveal_type(c_instance.declared_and_bound) # revealed: Literal[False] ``` #### Variable declared in class body and possibly bound in `__init__` @@ -149,14 +149,16 @@ class C: c_instance = C(True) reveal_type(c_instance.only_declared_in_body) # revealed: str | None -reveal_type(c_instance.only_declared_in_init) # revealed: str | None +# TODO: should be `str | None` without error +# error: [unresolved-attribute] +reveal_type(c_instance.only_declared_in_init) # revealed: Unknown reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None # TODO: This should be `str | None`. Fixing this requires an overhaul of the `Symbol` API, # which is planned in https://github.com/astral-sh/ruff/issues/14297 -reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | str | None +reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | Literal["a"] reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None | Literal["a"] ``` @@ -187,7 +189,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None -reveal_type(c_instance.declared_only) # revealed: bytes +# TODO: should be `bytes` with no error, like mypy and pyright? +# error: [unresolved-attribute] +reveal_type(c_instance.declared_only) # revealed: Unknown reveal_type(c_instance.declared_and_bound) # revealed: bool @@ -260,8 +264,8 @@ class C: self.w += None # TODO: Mypy and pyright do not support this, but it would be great if we could -# infer `Unknown | str` or at least `Unknown | Weird | str` here. -reveal_type(C().w) # revealed: Unknown | Weird +# infer `Unknown | str` here (`Weird` is not a possible type for the `w` attribute). +reveal_type(C().w) # revealed: Unknown ``` #### Attributes defined in tuple unpackings @@ -410,14 +414,41 @@ class C: [... for self.a in IntIterable()] [... for (self.b, self.c) in TupleIterable()] [... for self.d in IntIterable() for self.e in IntIterable()] + [[... for self.f in IntIterable()] for _ in IntIterable()] + [[... for self.g in IntIterable()] for self in [D()]] + +class D: + g: int c_instance = C() -reveal_type(c_instance.a) # revealed: Unknown | int -reveal_type(c_instance.b) # revealed: Unknown | int -reveal_type(c_instance.c) # revealed: Unknown | str -reveal_type(c_instance.d) # revealed: Unknown | int -reveal_type(c_instance.e) # revealed: Unknown | int +# TODO: no error, reveal Unknown | int +# error: [unresolved-attribute] +reveal_type(c_instance.a) # revealed: Unknown + +# TODO: no error, reveal Unknown | int +# error: [unresolved-attribute] +reveal_type(c_instance.b) # revealed: Unknown + +# TODO: no error, reveal Unknown | str +# error: [unresolved-attribute] +reveal_type(c_instance.c) # revealed: Unknown + +# TODO: no error, reveal Unknown | int +# error: [unresolved-attribute] +reveal_type(c_instance.d) # revealed: Unknown + +# TODO: no error, reveal Unknown | int +# error: [unresolved-attribute] +reveal_type(c_instance.e) # revealed: Unknown + +# TODO: no error, reveal Unknown | int +# error: [unresolved-attribute] +reveal_type(c_instance.f) # revealed: Unknown + +# This one is correctly not resolved as an attribute: +# error: [unresolved-attribute] +reveal_type(c_instance.g) # revealed: Unknown ``` #### Conditionally declared / bound attributes @@ -721,10 +752,7 @@ reveal_type(C.pure_class_variable) # revealed: Unknown # error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object ``" C.pure_class_variable = "overwritten on class" -# TODO: should be `Unknown | Literal["value set in class method"]` or -# Literal["overwritten on class"]`, once/if we support local narrowing. -# error: [unresolved-attribute] -reveal_type(C.pure_class_variable) # revealed: Unknown +reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"] c_instance = C() reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"] @@ -762,19 +790,12 @@ reveal_type(c_instance.variable_with_class_default2) # revealed: Unknown | Lite c_instance.variable_with_class_default1 = "value set on instance" reveal_type(C.variable_with_class_default1) # revealed: str - -# TODO: Could be Literal["value set on instance"], or still `str` if we choose not to -# narrow the type. -reveal_type(c_instance.variable_with_class_default1) # revealed: str +reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"] C.variable_with_class_default1 = "overwritten on class" -# TODO: Could be `Literal["overwritten on class"]`, or still `str` if we choose not to -# narrow the type. -reveal_type(C.variable_with_class_default1) # revealed: str - -# TODO: should still be `Literal["value set on instance"]`, or `str`. -reveal_type(c_instance.variable_with_class_default1) # revealed: str +reveal_type(C.variable_with_class_default1) # revealed: Literal["overwritten on class"] +reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"] ``` #### Descriptor attributes as class variables diff --git a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md index cc9661b54f6a8a..86b16e42026739 100644 --- a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md @@ -699,9 +699,7 @@ class C: descriptor = Descriptor() C.descriptor = "something else" - -# This could also be `Literal["something else"]` if we support narrowing of attribute types based on assignments -reveal_type(C.descriptor) # revealed: Unknown | int +reveal_type(C.descriptor) # revealed: Literal["something else"] ``` ### Possibly unbound descriptor attributes diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md new file mode 100644 index 00000000000000..73d676a2a371b6 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md @@ -0,0 +1,318 @@ +# Narrowing by assignment + +## Attribute + +### Basic + +```py +class A: + x: int | None = None + y = None + + def __init__(self): + self.z = None + +a = A() +a.x = 0 +a.y = 0 +a.z = 0 + +reveal_type(a.x) # revealed: Literal[0] +reveal_type(a.y) # revealed: Literal[0] +reveal_type(a.z) # revealed: Literal[0] + +# Make sure that we infer the narrowed type for eager +# scopes (class, comprehension) and the non-narrowed +# public type for lazy scopes (function) +class _: + reveal_type(a.x) # revealed: Literal[0] + reveal_type(a.y) # revealed: Literal[0] + reveal_type(a.z) # revealed: Literal[0] + +[reveal_type(a.x) for _ in range(1)] # revealed: Literal[0] +[reveal_type(a.y) for _ in range(1)] # revealed: Literal[0] +[reveal_type(a.z) for _ in range(1)] # revealed: Literal[0] + +def _(): + reveal_type(a.x) # revealed: Unknown | int | None + reveal_type(a.y) # revealed: Unknown | None + reveal_type(a.z) # revealed: Unknown | None + +if False: + a = A() +reveal_type(a.x) # revealed: Literal[0] +reveal_type(a.y) # revealed: Literal[0] +reveal_type(a.z) # revealed: Literal[0] + +if True: + a = A() +reveal_type(a.x) # revealed: int | None +reveal_type(a.y) # revealed: Unknown | None +reveal_type(a.z) # revealed: Unknown | None + +a.x = 0 +a.y = 0 +a.z = 0 +reveal_type(a.x) # revealed: Literal[0] +reveal_type(a.y) # revealed: Literal[0] +reveal_type(a.z) # revealed: Literal[0] + +class _: + a = A() + reveal_type(a.x) # revealed: int | None + reveal_type(a.y) # revealed: Unknown | None + reveal_type(a.z) # revealed: Unknown | None + +def cond() -> bool: + return True + +class _: + if False: + a = A() + reveal_type(a.x) # revealed: Literal[0] + reveal_type(a.y) # revealed: Literal[0] + reveal_type(a.z) # revealed: Literal[0] + + if cond(): + a = A() + reveal_type(a.x) # revealed: int | None + reveal_type(a.y) # revealed: Unknown | None + reveal_type(a.z) # revealed: Unknown | None + +class _: + a = A() + + class Inner: + reveal_type(a.x) # revealed: int | None + reveal_type(a.y) # revealed: Unknown | None + reveal_type(a.z) # revealed: Unknown | None + +# error: [unresolved-reference] +does.nt.exist = 0 +# error: [unresolved-reference] +reveal_type(does.nt.exist) # revealed: Unknown +``` + +### Narrowing chain + +```py +class D: ... + +class C: + d: D | None = None + +class B: + c1: C | None = None + c2: C | None = None + +class A: + b: B | None = None + +a = A() +a.b = B() +a.b.c1 = C() +a.b.c2 = C() +a.b.c1.d = D() +a.b.c2.d = D() +reveal_type(a.b) # revealed: B +reveal_type(a.b.c1) # revealed: C +reveal_type(a.b.c1.d) # revealed: D + +a.b.c1 = C() +reveal_type(a.b) # revealed: B +reveal_type(a.b.c1) # revealed: C +reveal_type(a.b.c1.d) # revealed: D | None +reveal_type(a.b.c2.d) # revealed: D + +a.b.c1.d = D() +a.b = B() +reveal_type(a.b) # revealed: B +reveal_type(a.b.c1) # revealed: C | None +reveal_type(a.b.c2) # revealed: C | None +# error: [possibly-unbound-attribute] +reveal_type(a.b.c1.d) # revealed: D | None +# error: [possibly-unbound-attribute] +reveal_type(a.b.c2.d) # revealed: D | None +``` + +### Do not narrow the type of a `property` by assignment + +```py +class C: + def __init__(self): + self._x: int = 0 + + @property + def x(self) -> int: + return self._x + + @x.setter + def x(self, value: int) -> None: + self._x = abs(value) + +c = C() +c.x = -1 +# Don't infer `c.x` to be `Literal[-1]` +reveal_type(c.x) # revealed: int +``` + +### Do not narrow the type of a descriptor by assignment + +```py +class Descriptor: + def __get__(self, instance: object, owner: type) -> int: + return 1 + + def __set__(self, instance: object, value: int) -> None: + pass + +class C: + desc: Descriptor = Descriptor() + +c = C() +c.desc = -1 +# Don't infer `c.desc` to be `Literal[-1]` +reveal_type(c.desc) # revealed: int +``` + +## Subscript + +### Specialization for builtin types + +Type narrowing based on assignment to a subscript expression is generally unsound, because arbitrary +`__getitem__`/`__setitem__` methods on a class do not necessarily guarantee that the passed-in value +for `__setitem__` is stored and can be retrieved unmodified via `__getitem__`. Therefore, we +currently only perform assignment-based narrowing on a few built-in classes (`list`, `dict`, +`bytesarray`, `TypedDict` and `collections` types) where we are confident that this kind of +narrowing can be performed soundly. This is the same approach as pyright. + +```py +from typing import TypedDict +from collections import ChainMap, defaultdict + +l: list[int | None] = [None] +l[0] = 0 +d: dict[int, int] = {1: 1} +d[0] = 0 +b: bytearray = bytearray(b"abc") +b[0] = 0 +dd: defaultdict[int, int] = defaultdict(int) +dd[0] = 0 +cm: ChainMap[int, int] = ChainMap({1: 1}, {0: 0}) +cm[0] = 0 +# TODO: should be ChainMap[int, int] +reveal_type(cm) # revealed: ChainMap[Unknown, Unknown] + +reveal_type(l[0]) # revealed: Literal[0] +reveal_type(d[0]) # revealed: Literal[0] +reveal_type(b[0]) # revealed: Literal[0] +reveal_type(dd[0]) # revealed: Literal[0] +# TODO: should be Literal[0] +reveal_type(cm[0]) # revealed: Unknown + +class C: + reveal_type(l[0]) # revealed: Literal[0] + reveal_type(d[0]) # revealed: Literal[0] + reveal_type(b[0]) # revealed: Literal[0] + reveal_type(dd[0]) # revealed: Literal[0] + # TODO: should be Literal[0] + reveal_type(cm[0]) # revealed: Unknown + +[reveal_type(l[0]) for _ in range(1)] # revealed: Literal[0] +[reveal_type(d[0]) for _ in range(1)] # revealed: Literal[0] +[reveal_type(b[0]) for _ in range(1)] # revealed: Literal[0] +[reveal_type(dd[0]) for _ in range(1)] # revealed: Literal[0] +# TODO: should be Literal[0] +[reveal_type(cm[0]) for _ in range(1)] # revealed: Unknown + +def _(): + reveal_type(l[0]) # revealed: int | None + reveal_type(d[0]) # revealed: int + reveal_type(b[0]) # revealed: int + reveal_type(dd[0]) # revealed: int + reveal_type(cm[0]) # revealed: int + +class D(TypedDict): + x: int + label: str + +td = D(x=1, label="a") +td["x"] = 0 +# TODO: should be Literal[0] +reveal_type(td["x"]) # revealed: @Todo(TypedDict) + +# error: [unresolved-reference] +does["not"]["exist"] = 0 +# error: [unresolved-reference] +reveal_type(does["not"]["exist"]) # revealed: Unknown + +non_subscriptable = 1 +# error: [non-subscriptable] +non_subscriptable[0] = 0 +# error: [non-subscriptable] +reveal_type(non_subscriptable[0]) # revealed: Unknown +``` + +### No narrowing for custom classes with arbitrary `__getitem__` / `__setitem__` + +```py +class C: + def __init__(self): + self.l: list[str] = [] + + def __getitem__(self, index: int) -> str: + return self.l[index] + + def __setitem__(self, index: int, value: str | int) -> None: + if len(self.l) == index: + self.l.append(str(value)) + else: + self.l[index] = str(value) + +c = C() +c[0] = 0 +reveal_type(c[0]) # revealed: str +``` + +## Complex target + +```py +class A: + x: list[int | None] = [] + +class B: + a: A | None = None + +b = B() +b.a = A() +b.a.x[0] = 0 + +reveal_type(b.a.x[0]) # revealed: Literal[0] + +class C: + reveal_type(b.a.x[0]) # revealed: Literal[0] + +def _(): + # error: [possibly-unbound-attribute] + reveal_type(b.a.x[0]) # revealed: Unknown | int | None + # error: [possibly-unbound-attribute] + reveal_type(b.a.x) # revealed: Unknown | list[int | None] + reveal_type(b.a) # revealed: Unknown | A | None +``` + +## Invalid assignments are not used for narrowing + +```py +class C: + x: int | None + l: list[int] + +def f(c: C, s: str): + c.x = s # error: [invalid-assignment] + reveal_type(c.x) # revealed: int | None + s = c.x # error: [invalid-assignment] + + # TODO: This assignment is invalid and should result in an error. + c.l[0] = s + reveal_type(c.l[0]) # revealed: int +``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md index 6b001ea09411a0..b3b077f1bcb718 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md @@ -53,11 +53,114 @@ constraints may no longer be valid due to a "time lag". However, it may be possi that some of them are valid by performing a more detailed analysis (e.g. checking that the narrowing target has not changed in all places where the function is called). +### Narrowing by attribute/subscript assignments + +```py +class A: + x: str | None = None + + def update_x(self, value: str | None): + self.x = value + +a = A() +a.x = "a" + +class B: + reveal_type(a.x) # revealed: Literal["a"] + +def f(): + reveal_type(a.x) # revealed: Unknown | str | None + +[reveal_type(a.x) for _ in range(1)] # revealed: Literal["a"] + +a = A() + +class C: + reveal_type(a.x) # revealed: str | None + +def g(): + reveal_type(a.x) # revealed: Unknown | str | None + +[reveal_type(a.x) for _ in range(1)] # revealed: str | None + +a = A() +a.x = "a" +a.update_x("b") + +class D: + # TODO: should be `str | None` + reveal_type(a.x) # revealed: Literal["a"] + +def h(): + reveal_type(a.x) # revealed: Unknown | str | None + +# TODO: should be `str | None` +[reveal_type(a.x) for _ in range(1)] # revealed: Literal["a"] +``` + +### Narrowing by attribute/subscript assignments in nested scopes + +```py +class D: ... + +class C: + d: D | None = None + +class B: + c1: C | None = None + c2: C | None = None + +class A: + b: B | None = None + +a = A() +a.b = B() + +class _: + a.b.c1 = C() + + class _: + a.b.c1.d = D() + a = 1 + + class _3: + reveal_type(a) # revealed: A + reveal_type(a.b.c1.d) # revealed: D + + class _: + a = 1 + # error: [unresolved-attribute] + a.b.c1.d = D() + + class _3: + reveal_type(a) # revealed: A + # TODO: should be `D | None` + reveal_type(a.b.c1.d) # revealed: D + +a.b.c1 = C() +a.b.c1.d = D() + +class _: + a.b = B() + + class _: + # error: [possibly-unbound-attribute] + reveal_type(a.b.c1.d) # revealed: D | None + reveal_type(a.b.c1) # revealed: C | None +``` + ### Narrowing constraints introduced in eager nested scopes ```py g: str | None = "a" +class A: + x: str | None = None + +a = A() + +l: list[str | None] = [None] + def f(x: str | None): def _(): if x is not None: @@ -69,6 +172,14 @@ def f(x: str | None): if g is not None: reveal_type(g) # revealed: str + if a.x is not None: + # TODO(#17643): should be `Unknown | str` + reveal_type(a.x) # revealed: Unknown | str | None + + if l[0] is not None: + # TODO(#17643): should be `str` + reveal_type(l[0]) # revealed: str | None + class C: if x is not None: reveal_type(x) # revealed: str @@ -79,6 +190,14 @@ def f(x: str | None): if g is not None: reveal_type(g) # revealed: str + if a.x is not None: + # TODO(#17643): should be `Unknown | str` + reveal_type(a.x) # revealed: Unknown | str | None + + if l[0] is not None: + # TODO(#17643): should be `str` + reveal_type(l[0]) # revealed: str | None + # TODO: should be str # This could be fixed if we supported narrowing with if clauses in comprehensions. [reveal_type(x) for _ in range(1) if x is not None] # revealed: str | None @@ -89,6 +208,13 @@ def f(x: str | None): ```py g: str | None = "a" +class A: + x: str | None = None + +a = A() + +l: list[str | None] = [None] + def f(x: str | None): if x is not None: def _(): @@ -109,6 +235,28 @@ def f(x: str | None): reveal_type(g) # revealed: str [reveal_type(g) for _ in range(1)] # revealed: str + + if a.x is not None: + def _(): + reveal_type(a.x) # revealed: Unknown | str | None + + class D: + # TODO(#17643): should be `Unknown | str` + reveal_type(a.x) # revealed: Unknown | str | None + + # TODO(#17643): should be `Unknown | str` + [reveal_type(a.x) for _ in range(1)] # revealed: Unknown | str | None + + if l[0] is not None: + def _(): + reveal_type(l[0]) # revealed: str | None + + class D: + # TODO(#17643): should be `str` + reveal_type(l[0]) # revealed: str | None + + # TODO(#17643): should be `str` + [reveal_type(l[0]) for _ in range(1)] # revealed: str | None ``` ### Narrowing constraints introduced in multiple scopes @@ -118,6 +266,13 @@ from typing import Literal g: str | Literal[1] | None = "a" +class A: + x: str | Literal[1] | None = None + +a = A() + +l: list[str | Literal[1] | None] = [None] + def f(x: str | Literal[1] | None): class C: if x is not None: @@ -140,6 +295,28 @@ def f(x: str | Literal[1] | None): class D: if g != 1: reveal_type(g) # revealed: str + + if a.x is not None: + def _(): + if a.x != 1: + # TODO(#17643): should be `Unknown | str | None` + reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None + + class D: + if a.x != 1: + # TODO(#17643): should be `Unknown | str` + reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None + + if l[0] is not None: + def _(): + if l[0] != 1: + # TODO(#17643): should be `str | None` + reveal_type(l[0]) # revealed: str | Literal[1] | None + + class D: + if l[0] != 1: + # TODO(#17643): should be `str` + reveal_type(l[0]) # revealed: str | Literal[1] | None ``` ### Narrowing constraints with bindings in class scope, and nested scopes diff --git a/crates/ty_python_semantic/src/dunder_all.rs b/crates/ty_python_semantic/src/dunder_all.rs index 4265de2ac6d8ff..6976e35e22443d 100644 --- a/crates/ty_python_semantic/src/dunder_all.rs +++ b/crates/ty_python_semantic/src/dunder_all.rs @@ -7,7 +7,7 @@ use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_ast::{self as ast}; use crate::semantic_index::ast_ids::HasScopedExpressionId; -use crate::semantic_index::symbol::ScopeId; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::{SemanticIndex, global_scope, semantic_index}; use crate::types::{Truthiness, Type, infer_expression_types}; use crate::{Db, ModuleName, resolve_module}; diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index 1a204734c0f55f..0123d28c171e96 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -24,13 +24,13 @@ pub(crate) mod list; mod module_name; mod module_resolver; mod node_key; +pub(crate) mod place; mod program; mod python_platform; pub mod semantic_index; mod semantic_model; pub(crate) mod site_packages; mod suppression; -pub(crate) mod symbol; pub mod types; mod unpack; mod util; diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/place.rs similarity index 72% rename from crates/ty_python_semantic/src/symbol.rs rename to crates/ty_python_semantic/src/place.rs index d2b2518e61cc05..0f25a3ff30abd3 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -2,10 +2,10 @@ use ruff_db::files::File; use crate::dunder_all::dunder_all_names; use crate::module_resolver::file_to_module; -use crate::semantic_index::definition::Definition; -use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId}; +use crate::semantic_index::definition::{Definition, DefinitionState}; +use crate::semantic_index::place::{PlaceExpr, ScopeId, ScopedPlaceId}; use crate::semantic_index::{ - BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, symbol_table, + BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, place_table, }; use crate::semantic_index::{DeclarationWithConstraint, global_scope, use_def_map}; use crate::types::{ @@ -33,8 +33,8 @@ impl Boundness { } } -/// The result of a symbol lookup, which can either be a (possibly unbound) type -/// or a completely unbound symbol. +/// The result of a place lookup, which can either be a (possibly unbound) type +/// or a completely unbound place. /// /// Consider this example: /// ```py @@ -44,47 +44,47 @@ impl Boundness { /// possibly_unbound = 2 /// ``` /// -/// If we look up symbols in this scope, we would get the following results: +/// If we look up places in this scope, we would get the following results: /// ```rs -/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound), -/// possibly_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound), -/// non_existent: Symbol::Unbound, +/// bound: Place::Type(Type::IntLiteral(1), Boundness::Bound), +/// possibly_unbound: Place::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound), +/// non_existent: Place::Unbound, /// ``` #[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] -pub(crate) enum Symbol<'db> { +pub(crate) enum Place<'db> { Type(Type<'db>, Boundness), Unbound, } -impl<'db> Symbol<'db> { - /// Constructor that creates a `Symbol` with boundness [`Boundness::Bound`]. +impl<'db> Place<'db> { + /// Constructor that creates a `Place` with boundness [`Boundness::Bound`]. pub(crate) fn bound(ty: impl Into>) -> Self { - Symbol::Type(ty.into(), Boundness::Bound) + Place::Type(ty.into(), Boundness::Bound) } pub(crate) fn possibly_unbound(ty: impl Into>) -> Self { - Symbol::Type(ty.into(), Boundness::PossiblyUnbound) + Place::Type(ty.into(), Boundness::PossiblyUnbound) } - /// Constructor that creates a [`Symbol`] with a [`crate::types::TodoType`] type + /// Constructor that creates a [`Place`] with a [`crate::types::TodoType`] type /// and boundness [`Boundness::Bound`]. #[allow(unused_variables)] // Only unused in release builds pub(crate) fn todo(message: &'static str) -> Self { - Symbol::Type(todo_type!(message), Boundness::Bound) + Place::Type(todo_type!(message), Boundness::Bound) } pub(crate) fn is_unbound(&self) -> bool { - matches!(self, Symbol::Unbound) + matches!(self, Place::Unbound) } - /// Returns the type of the symbol, ignoring possible unboundness. + /// Returns the type of the place, ignoring possible unboundness. /// - /// If the symbol is *definitely* unbound, this function will return `None`. Otherwise, - /// if there is at least one control-flow path where the symbol is bound, return the type. + /// If the place is *definitely* unbound, this function will return `None`. Otherwise, + /// if there is at least one control-flow path where the place is bound, return the type. pub(crate) fn ignore_possibly_unbound(&self) -> Option> { match self { - Symbol::Type(ty, _) => Some(*ty), - Symbol::Unbound => None, + Place::Type(ty, _) => Some(*ty), + Place::Unbound => None, } } @@ -92,71 +92,71 @@ impl<'db> Symbol<'db> { #[track_caller] pub(crate) fn expect_type(self) -> Type<'db> { self.ignore_possibly_unbound() - .expect("Expected a (possibly unbound) type, not an unbound symbol") + .expect("Expected a (possibly unbound) type, not an unbound place") } #[must_use] - pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Symbol<'db> { + pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Place<'db> { match self { - Symbol::Type(ty, boundness) => Symbol::Type(f(ty), boundness), - Symbol::Unbound => Symbol::Unbound, + Place::Type(ty, boundness) => Place::Type(f(ty), boundness), + Place::Unbound => Place::Unbound, } } #[must_use] - pub(crate) fn with_qualifiers(self, qualifiers: TypeQualifiers) -> SymbolAndQualifiers<'db> { - SymbolAndQualifiers { - symbol: self, + pub(crate) fn with_qualifiers(self, qualifiers: TypeQualifiers) -> PlaceAndQualifiers<'db> { + PlaceAndQualifiers { + place: self, qualifiers, } } - /// Try to call `__get__(None, owner)` on the type of this symbol (not on the meta type). - /// If it succeeds, return the `__get__` return type. Otherwise, returns the original symbol. + /// Try to call `__get__(None, owner)` on the type of this place (not on the meta type). + /// If it succeeds, return the `__get__` return type. Otherwise, returns the original place. /// This is used to resolve (potential) descriptor attributes. - pub(crate) fn try_call_dunder_get(self, db: &'db dyn Db, owner: Type<'db>) -> Symbol<'db> { + pub(crate) fn try_call_dunder_get(self, db: &'db dyn Db, owner: Type<'db>) -> Place<'db> { match self { - Symbol::Type(Type::Union(union), boundness) => union.map_with_boundness(db, |elem| { - Symbol::Type(*elem, boundness).try_call_dunder_get(db, owner) + Place::Type(Type::Union(union), boundness) => union.map_with_boundness(db, |elem| { + Place::Type(*elem, boundness).try_call_dunder_get(db, owner) }), - Symbol::Type(Type::Intersection(intersection), boundness) => intersection + Place::Type(Type::Intersection(intersection), boundness) => intersection .map_with_boundness(db, |elem| { - Symbol::Type(*elem, boundness).try_call_dunder_get(db, owner) + Place::Type(*elem, boundness).try_call_dunder_get(db, owner) }), - Symbol::Type(self_ty, boundness) => { + Place::Type(self_ty, boundness) => { if let Some((dunder_get_return_ty, _)) = self_ty.try_call_dunder_get(db, Type::none(db), owner) { - Symbol::Type(dunder_get_return_ty, boundness) + Place::Type(dunder_get_return_ty, boundness) } else { self } } - Symbol::Unbound => Symbol::Unbound, + Place::Unbound => Place::Unbound, } } } -impl<'db> From> for SymbolAndQualifiers<'db> { +impl<'db> From> for PlaceAndQualifiers<'db> { fn from(value: LookupResult<'db>) -> Self { match value { Ok(type_and_qualifiers) => { - Symbol::Type(type_and_qualifiers.inner_type(), Boundness::Bound) + Place::Type(type_and_qualifiers.inner_type(), Boundness::Bound) .with_qualifiers(type_and_qualifiers.qualifiers()) } - Err(LookupError::Unbound(qualifiers)) => Symbol::Unbound.with_qualifiers(qualifiers), + Err(LookupError::Unbound(qualifiers)) => Place::Unbound.with_qualifiers(qualifiers), Err(LookupError::PossiblyUnbound(type_and_qualifiers)) => { - Symbol::Type(type_and_qualifiers.inner_type(), Boundness::PossiblyUnbound) + Place::Type(type_and_qualifiers.inner_type(), Boundness::PossiblyUnbound) .with_qualifiers(type_and_qualifiers.qualifiers()) } } } } -/// Possible ways in which a symbol lookup can (possibly or definitely) fail. +/// Possible ways in which a place lookup can (possibly or definitely) fail. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum LookupError<'db> { Unbound(TypeQualifiers), @@ -168,7 +168,7 @@ impl<'db> LookupError<'db> { pub(crate) fn or_fall_back_to( self, db: &'db dyn Db, - fallback: SymbolAndQualifiers<'db>, + fallback: PlaceAndQualifiers<'db>, ) -> LookupResult<'db> { let fallback = fallback.into_lookup_result(); match (&self, &fallback) { @@ -188,34 +188,45 @@ impl<'db> LookupError<'db> { } } -/// A [`Result`] type in which the `Ok` variant represents a definitely bound symbol -/// and the `Err` variant represents a symbol that is either definitely or possibly unbound. +/// A [`Result`] type in which the `Ok` variant represents a definitely bound place +/// and the `Err` variant represents a place that is either definitely or possibly unbound. /// -/// Note that this type is exactly isomorphic to [`Symbol`]. -/// In the future, we could possibly consider removing `Symbol` and using this type everywhere instead. +/// Note that this type is exactly isomorphic to [`Place`]. +/// In the future, we could possibly consider removing `Place` and using this type everywhere instead. pub(crate) type LookupResult<'db> = Result, LookupError<'db>>; /// Infer the public type of a symbol (its type as seen from outside its scope) in the given /// `scope`. +#[allow(unused)] pub(crate) fn symbol<'db>( db: &'db dyn Db, scope: ScopeId<'db>, name: &str, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { symbol_impl(db, scope, name, RequiresExplicitReExport::No) } +/// Infer the public type of a place (its type as seen from outside its scope) in the given +/// `scope`. +pub(crate) fn place<'db>( + db: &'db dyn Db, + scope: ScopeId<'db>, + expr: &PlaceExpr, +) -> PlaceAndQualifiers<'db> { + place_impl(db, scope, expr, RequiresExplicitReExport::No) +} + /// Infer the public type of a class symbol (its type as seen from outside its scope) in the given /// `scope`. pub(crate) fn class_symbol<'db>( db: &'db dyn Db, scope: ScopeId<'db>, name: &str, -) -> SymbolAndQualifiers<'db> { - symbol_table(db, scope) - .symbol_id_by_name(name) +) -> PlaceAndQualifiers<'db> { + place_table(db, scope) + .place_id_by_name(name) .map(|symbol| { - let symbol_and_quals = symbol_by_id(db, scope, symbol, RequiresExplicitReExport::No); + let symbol_and_quals = place_by_id(db, scope, symbol, RequiresExplicitReExport::No); if symbol_and_quals.is_class_var() { // For declared class vars we do not need to check if they have bindings, @@ -223,27 +234,26 @@ pub(crate) fn class_symbol<'db>( return symbol_and_quals; } - if let SymbolAndQualifiers { - symbol: Symbol::Type(ty, _), + if let PlaceAndQualifiers { + place: Place::Type(ty, _), qualifiers, } = symbol_and_quals { // Otherwise, we need to check if the symbol has bindings let use_def = use_def_map(db, scope); let bindings = use_def.public_bindings(symbol); - let inferred = - symbol_from_bindings_impl(db, bindings, RequiresExplicitReExport::No); + let inferred = place_from_bindings_impl(db, bindings, RequiresExplicitReExport::No); // TODO: we should not need to calculate inferred type second time. This is a temporary // solution until the notion of Boundness and Declaredness is split. See #16036, #16264 match inferred { - Symbol::Unbound => Symbol::Unbound.with_qualifiers(qualifiers), - Symbol::Type(_, boundness) => { - Symbol::Type(ty, boundness).with_qualifiers(qualifiers) + Place::Unbound => Place::Unbound.with_qualifiers(qualifiers), + Place::Type(_, boundness) => { + Place::Type(ty, boundness).with_qualifiers(qualifiers) } } } else { - Symbol::Unbound.into() + Place::Unbound.into() } }) .unwrap_or_default() @@ -253,7 +263,7 @@ pub(crate) fn class_symbol<'db>( /// /// Note that all global scopes also include various "implicit globals" such as `__name__`, /// `__doc__` and `__file__`. This function **does not** consider those symbols; it will return -/// `Symbol::Unbound` for them. Use the (currently test-only) `global_symbol` query to also include +/// `Place::Unbound` for them. Use the (currently test-only) `global_symbol` query to also include /// those additional symbols. /// /// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports). @@ -261,7 +271,7 @@ pub(crate) fn explicit_global_symbol<'db>( db: &'db dyn Db, file: File, name: &str, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { symbol_impl( db, global_scope(db, file), @@ -277,11 +287,12 @@ pub(crate) fn explicit_global_symbol<'db>( /// rather than being looked up as symbols explicitly defined/declared in the global scope. /// /// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports). +#[allow(unused)] pub(crate) fn global_symbol<'db>( db: &'db dyn Db, file: File, name: &str, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { explicit_global_symbol(db, file, name) .or_fall_back_to(db, || module_type_implicit_global_symbol(db, name)) } @@ -295,7 +306,7 @@ pub(crate) fn imported_symbol<'db>( file: File, name: &str, requires_explicit_reexport: Option, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { let requires_explicit_reexport = requires_explicit_reexport.unwrap_or_else(|| { if file.is_stub(db.upcast()) { RequiresExplicitReExport::Yes @@ -323,9 +334,9 @@ pub(crate) fn imported_symbol<'db>( db, || { if name == "__getattr__" { - Symbol::Unbound.into() + Place::Unbound.into() } else if name == "__builtins__" { - Symbol::bound(Type::any()).into() + Place::bound(Type::any()).into() } else { KnownClass::ModuleType.to_instance(db).member(db, name) } @@ -335,12 +346,12 @@ pub(crate) fn imported_symbol<'db>( /// Lookup the type of `symbol` in the builtins namespace. /// -/// Returns `Symbol::Unbound` if the `builtins` module isn't available for some reason. +/// Returns `Place::Unbound` if the `builtins` module isn't available for some reason. /// /// Note that this function is only intended for use in the context of the builtins *namespace* /// and should not be used when a symbol is being explicitly imported from the `builtins` module /// (e.g. `from builtins import int`). -pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> SymbolAndQualifiers<'db> { +pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQualifiers<'db> { resolve_module(db, &KnownModule::Builtins.name()) .and_then(|module| { let file = module.file()?; @@ -364,12 +375,12 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> SymbolAndQu /// Lookup the type of `symbol` in a given known module. /// -/// Returns `Symbol::Unbound` if the given known module cannot be resolved for some reason. +/// Returns `Place::Unbound` if the given known module cannot be resolved for some reason. pub(crate) fn known_module_symbol<'db>( db: &'db dyn Db, known_module: KnownModule, symbol: &str, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { resolve_module(db, &known_module.name()) .and_then(|module| { let file = module.file()?; @@ -380,21 +391,21 @@ pub(crate) fn known_module_symbol<'db>( /// Lookup the type of `symbol` in the `typing` module namespace. /// -/// Returns `Symbol::Unbound` if the `typing` module isn't available for some reason. +/// Returns `Place::Unbound` if the `typing` module isn't available for some reason. #[inline] #[cfg(test)] -pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> SymbolAndQualifiers<'db> { +pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQualifiers<'db> { known_module_symbol(db, KnownModule::Typing, symbol) } /// Lookup the type of `symbol` in the `typing_extensions` module namespace. /// -/// Returns `Symbol::Unbound` if the `typing_extensions` module isn't available for some reason. +/// Returns `Place::Unbound` if the `typing_extensions` module isn't available for some reason. #[inline] pub(crate) fn typing_extensions_symbol<'db>( db: &'db dyn Db, symbol: &str, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { known_module_symbol(db, KnownModule::TypingExtensions, symbol) } @@ -414,14 +425,14 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option( +pub(super) fn place_from_bindings<'db>( db: &'db dyn Db, bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>, -) -> Symbol<'db> { - symbol_from_bindings_impl(db, bindings_with_constraints, RequiresExplicitReExport::No) +) -> Place<'db> { + place_from_bindings_impl(db, bindings_with_constraints, RequiresExplicitReExport::No) } /// Build a declared type from a [`DeclarationsIterator`]. @@ -430,18 +441,18 @@ pub(super) fn symbol_from_bindings<'db>( /// `Ok(..)`. If there are conflicting declarations, returns an `Err(..)` variant with /// a union of the declared types as well as a list of all conflicting types. /// -/// This function also returns declaredness information (see [`Symbol`]) and a set of +/// This function also returns declaredness information (see [`Place`]) and a set of /// [`TypeQualifiers`] that have been specified on the declaration(s). -pub(crate) fn symbol_from_declarations<'db>( +pub(crate) fn place_from_declarations<'db>( db: &'db dyn Db, declarations: DeclarationsIterator<'_, 'db>, -) -> SymbolFromDeclarationsResult<'db> { - symbol_from_declarations_impl(db, declarations, RequiresExplicitReExport::No) +) -> PlaceFromDeclarationsResult<'db> { + place_from_declarations_impl(db, declarations, RequiresExplicitReExport::No) } -/// The result of looking up a declared type from declarations; see [`symbol_from_declarations`]. -pub(crate) type SymbolFromDeclarationsResult<'db> = - Result, (TypeAndQualifiers<'db>, Box<[Type<'db>]>)>; +/// The result of looking up a declared type from declarations; see [`place_from_declarations`]. +pub(crate) type PlaceFromDeclarationsResult<'db> = + Result, (TypeAndQualifiers<'db>, Box<[Type<'db>]>)>; /// A type with declaredness information, and a set of type qualifiers. /// @@ -458,33 +469,33 @@ pub(crate) type SymbolFromDeclarationsResult<'db> = /// /// [`CLASS_VAR`]: crate::types::TypeQualifiers::CLASS_VAR #[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] -pub(crate) struct SymbolAndQualifiers<'db> { - pub(crate) symbol: Symbol<'db>, +pub(crate) struct PlaceAndQualifiers<'db> { + pub(crate) place: Place<'db>, pub(crate) qualifiers: TypeQualifiers, } -impl Default for SymbolAndQualifiers<'_> { +impl Default for PlaceAndQualifiers<'_> { fn default() -> Self { - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, qualifiers: TypeQualifiers::empty(), } } } -impl<'db> SymbolAndQualifiers<'db> { - /// Constructor that creates a [`SymbolAndQualifiers`] instance with a [`TodoType`] type +impl<'db> PlaceAndQualifiers<'db> { + /// Constructor that creates a [`PlaceAndQualifiers`] instance with a [`TodoType`] type /// and no qualifiers. /// /// [`TodoType`]: crate::types::TodoType pub(crate) fn todo(message: &'static str) -> Self { Self { - symbol: Symbol::todo(message), + place: Place::todo(message), qualifiers: TypeQualifiers::empty(), } } - /// Returns `true` if the symbol has a `ClassVar` type qualifier. + /// Returns `true` if the place has a `ClassVar` type qualifier. pub(crate) fn is_class_var(&self) -> bool { self.qualifiers.contains(TypeQualifiers::CLASS_VAR) } @@ -493,41 +504,41 @@ impl<'db> SymbolAndQualifiers<'db> { pub(crate) fn map_type( self, f: impl FnOnce(Type<'db>) -> Type<'db>, - ) -> SymbolAndQualifiers<'db> { - SymbolAndQualifiers { - symbol: self.symbol.map_type(f), + ) -> PlaceAndQualifiers<'db> { + PlaceAndQualifiers { + place: self.place.map_type(f), qualifiers: self.qualifiers, } } - /// Transform symbol and qualifiers into a [`LookupResult`], - /// a [`Result`] type in which the `Ok` variant represents a definitely bound symbol - /// and the `Err` variant represents a symbol that is either definitely or possibly unbound. + /// Transform place and qualifiers into a [`LookupResult`], + /// a [`Result`] type in which the `Ok` variant represents a definitely bound place + /// and the `Err` variant represents a place that is either definitely or possibly unbound. pub(crate) fn into_lookup_result(self) -> LookupResult<'db> { match self { - SymbolAndQualifiers { - symbol: Symbol::Type(ty, Boundness::Bound), + PlaceAndQualifiers { + place: Place::Type(ty, Boundness::Bound), qualifiers, } => Ok(TypeAndQualifiers::new(ty, qualifiers)), - SymbolAndQualifiers { - symbol: Symbol::Type(ty, Boundness::PossiblyUnbound), + PlaceAndQualifiers { + place: Place::Type(ty, Boundness::PossiblyUnbound), qualifiers, } => Err(LookupError::PossiblyUnbound(TypeAndQualifiers::new( ty, qualifiers, ))), - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, qualifiers, } => Err(LookupError::Unbound(qualifiers)), } } - /// Safely unwrap the symbol and the qualifiers into a [`TypeQualifiers`]. + /// Safely unwrap the place and the qualifiers into a [`TypeQualifiers`]. /// - /// If the symbol is definitely unbound or possibly unbound, it will be transformed into a + /// If the place is definitely unbound or possibly unbound, it will be transformed into a /// [`LookupError`] and `diagnostic_fn` will be applied to the error value before returning /// the result of `diagnostic_fn` (which will be a [`TypeQualifiers`]). This allows the caller - /// to ensure that a diagnostic is emitted if the symbol is possibly or definitely unbound. + /// to ensure that a diagnostic is emitted if the place is possibly or definitely unbound. pub(crate) fn unwrap_with_diagnostic( self, diagnostic_fn: impl FnOnce(LookupError<'db>) -> TypeAndQualifiers<'db>, @@ -535,21 +546,21 @@ impl<'db> SymbolAndQualifiers<'db> { self.into_lookup_result().unwrap_or_else(diagnostic_fn) } - /// Fallback (partially or fully) to another symbol if `self` is partially or fully unbound. + /// Fallback (partially or fully) to another place if `self` is partially or fully unbound. /// /// 1. If `self` is definitely bound, return `self` without evaluating `fallback_fn()`. /// 2. Else, evaluate `fallback_fn()`: /// 1. If `self` is definitely unbound, return the result of `fallback_fn()`. /// 2. Else, if `fallback` is definitely unbound, return `self`. /// 3. Else, if `self` is possibly unbound and `fallback` is definitely bound, - /// return `Symbol(, Boundness::Bound)` + /// return `Place(, Boundness::Bound)` /// 4. Else, if `self` is possibly unbound and `fallback` is possibly unbound, - /// return `Symbol(, Boundness::PossiblyUnbound)` + /// return `Place(, Boundness::PossiblyUnbound)` #[must_use] pub(crate) fn or_fall_back_to( self, db: &'db dyn Db, - fallback_fn: impl FnOnce() -> SymbolAndQualifiers<'db>, + fallback_fn: impl FnOnce() -> PlaceAndQualifiers<'db>, ) -> Self { self.into_lookup_result() .or_else(|lookup_error| lookup_error.or_fall_back_to(db, fallback_fn())) @@ -557,87 +568,87 @@ impl<'db> SymbolAndQualifiers<'db> { } } -impl<'db> From> for SymbolAndQualifiers<'db> { - fn from(symbol: Symbol<'db>) -> Self { - symbol.with_qualifiers(TypeQualifiers::empty()) +impl<'db> From> for PlaceAndQualifiers<'db> { + fn from(place: Place<'db>) -> Self { + place.with_qualifiers(TypeQualifiers::empty()) } } -fn symbol_cycle_recover<'db>( +fn place_cycle_recover<'db>( _db: &'db dyn Db, - _value: &SymbolAndQualifiers<'db>, + _value: &PlaceAndQualifiers<'db>, _count: u32, _scope: ScopeId<'db>, - _symbol_id: ScopedSymbolId, + _place_id: ScopedPlaceId, _requires_explicit_reexport: RequiresExplicitReExport, -) -> salsa::CycleRecoveryAction> { +) -> salsa::CycleRecoveryAction> { salsa::CycleRecoveryAction::Iterate } -fn symbol_cycle_initial<'db>( +fn place_cycle_initial<'db>( _db: &'db dyn Db, _scope: ScopeId<'db>, - _symbol_id: ScopedSymbolId, + _place_id: ScopedPlaceId, _requires_explicit_reexport: RequiresExplicitReExport, -) -> SymbolAndQualifiers<'db> { - Symbol::bound(Type::Never).into() +) -> PlaceAndQualifiers<'db> { + Place::bound(Type::Never).into() } -#[salsa::tracked(cycle_fn=symbol_cycle_recover, cycle_initial=symbol_cycle_initial)] -fn symbol_by_id<'db>( +#[salsa::tracked(cycle_fn=place_cycle_recover, cycle_initial=place_cycle_initial)] +fn place_by_id<'db>( db: &'db dyn Db, scope: ScopeId<'db>, - symbol_id: ScopedSymbolId, + place_id: ScopedPlaceId, requires_explicit_reexport: RequiresExplicitReExport, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { let use_def = use_def_map(db, scope); - // If the symbol is declared, the public type is based on declarations; otherwise, it's based + // If the place is declared, the public type is based on declarations; otherwise, it's based // on inference from bindings. - let declarations = use_def.public_declarations(symbol_id); - let declared = symbol_from_declarations_impl(db, declarations, requires_explicit_reexport); + let declarations = use_def.public_declarations(place_id); + let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport); match declared { - // Symbol is declared, trust the declared type + // Place is declared, trust the declared type Ok( - symbol_and_quals @ SymbolAndQualifiers { - symbol: Symbol::Type(_, Boundness::Bound), + place_and_quals @ PlaceAndQualifiers { + place: Place::Type(_, Boundness::Bound), qualifiers: _, }, - ) => symbol_and_quals, - // Symbol is possibly declared - Ok(SymbolAndQualifiers { - symbol: Symbol::Type(declared_ty, Boundness::PossiblyUnbound), + ) => place_and_quals, + // Place is possibly declared + Ok(PlaceAndQualifiers { + place: Place::Type(declared_ty, Boundness::PossiblyUnbound), qualifiers, }) => { - let bindings = use_def.public_bindings(symbol_id); - let inferred = symbol_from_bindings_impl(db, bindings, requires_explicit_reexport); + let bindings = use_def.public_bindings(place_id); + let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport); - let symbol = match inferred { - // Symbol is possibly undeclared and definitely unbound - Symbol::Unbound => { + let place = match inferred { + // Place is possibly undeclared and definitely unbound + Place::Unbound => { // TODO: We probably don't want to report `Bound` here. This requires a bit of // design work though as we might want a different behavior for stubs and for // normal modules. - Symbol::Type(declared_ty, Boundness::Bound) + Place::Type(declared_ty, Boundness::Bound) } - // Symbol is possibly undeclared and (possibly) bound - Symbol::Type(inferred_ty, boundness) => Symbol::Type( + // Place is possibly undeclared and (possibly) bound + Place::Type(inferred_ty, boundness) => Place::Type( UnionType::from_elements(db, [inferred_ty, declared_ty]), boundness, ), }; - SymbolAndQualifiers { symbol, qualifiers } + PlaceAndQualifiers { place, qualifiers } } - // Symbol is undeclared, return the union of `Unknown` with the inferred type - Ok(SymbolAndQualifiers { - symbol: Symbol::Unbound, + // Place is undeclared, return the union of `Unknown` with the inferred type + Ok(PlaceAndQualifiers { + place: Place::Unbound, qualifiers: _, }) => { - let bindings = use_def.public_bindings(symbol_id); - let inferred = symbol_from_bindings_impl(db, bindings, requires_explicit_reexport); + let bindings = use_def.public_bindings(place_id); + let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport); // `__slots__` is a symbol with special behavior in Python's runtime. It can be // modified externally, but those changes do not take effect. We therefore issue @@ -648,13 +659,12 @@ fn symbol_by_id<'db>( // `TYPE_CHECKING` is a special variable that should only be assigned `False` // at runtime, but is always considered `True` in type checking. // See mdtest/known_constants.md#user-defined-type_checking for details. - let is_considered_non_modifiable = matches!( - symbol_table(db, scope).symbol(symbol_id).name().as_str(), - "__slots__" | "TYPE_CHECKING" - ); + let is_considered_non_modifiable = place_table(db, scope) + .place_expr(place_id) + .is_name_and(|name| matches!(name, "__slots__" | "TYPE_CHECKING")); if scope.file(db).is_stub(db.upcast()) { - // We generally trust module-level undeclared symbols in stubs and do not union + // We generally trust module-level undeclared places in stubs and do not union // with `Unknown`. If we don't do this, simple aliases like `IOError = OSError` in // stubs would result in `IOError` being a union of `OSError` and `Unknown`, which // leads to all sorts of downstream problems. Similarly, type variables are often @@ -666,17 +676,17 @@ fn symbol_by_id<'db>( .into() } } - // Symbol has conflicting declared types + // Place has conflicting declared types Err((declared, _)) => { // Intentionally ignore conflicting declared types; that's not our problem, // it's the problem of the module we are importing from. - Symbol::bound(declared.inner_type()).with_qualifiers(declared.qualifiers()) + Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers()) } } // TODO (ticket: https://github.com/astral-sh/ruff/issues/14297) Our handling of boundness // currently only depends on bindings, and ignores declarations. This is inconsistent, since - // we only look at bindings if the symbol may be undeclared. Consider the following example: + // we only look at bindings if the place may be undeclared. Consider the following example: // ```py // x: int // @@ -685,7 +695,7 @@ fn symbol_by_id<'db>( // else // y = 3 // ``` - // If we import from this module, we will currently report `x` as a definitely-bound symbol + // If we import from this module, we will currently report `x` as a definitely-bound place // (even though it has no bindings at all!) but report `y` as possibly-unbound (even though // every path has either a binding or a declaration for it.) } @@ -696,7 +706,7 @@ fn symbol_impl<'db>( scope: ScopeId<'db>, name: &str, requires_explicit_reexport: RequiresExplicitReExport, -) -> SymbolAndQualifiers<'db> { +) -> PlaceAndQualifiers<'db> { let _span = tracing::trace_span!("symbol", ?name).entered(); if name == "platform" @@ -705,7 +715,7 @@ fn symbol_impl<'db>( { match Program::get(db).python_platform(db) { crate::PythonPlatform::Identifier(platform) => { - return Symbol::bound(Type::string_literal(db, platform.as_str())).into(); + return Place::bound(Type::string_literal(db, platform.as_str())).into(); } crate::PythonPlatform::All => { // Fall through to the looked up type @@ -713,22 +723,37 @@ fn symbol_impl<'db>( } } - symbol_table(db, scope) - .symbol_id_by_name(name) - .map(|symbol| symbol_by_id(db, scope, symbol, requires_explicit_reexport)) + place_table(db, scope) + .place_id_by_name(name) + .map(|symbol| place_by_id(db, scope, symbol, requires_explicit_reexport)) + .unwrap_or_default() +} + +/// Implementation of [`place`]. +fn place_impl<'db>( + db: &'db dyn Db, + scope: ScopeId<'db>, + expr: &PlaceExpr, + requires_explicit_reexport: RequiresExplicitReExport, +) -> PlaceAndQualifiers<'db> { + let _span = tracing::trace_span!("place", ?expr).entered(); + + place_table(db, scope) + .place_id_by_expr(expr) + .map(|place| place_by_id(db, scope, place, requires_explicit_reexport)) .unwrap_or_default() } -/// Implementation of [`symbol_from_bindings`]. +/// Implementation of [`place_from_bindings`]. /// /// ## Implementation Note /// This function gets called cross-module. It, therefore, shouldn't /// access any AST nodes from the file containing the declarations. -fn symbol_from_bindings_impl<'db>( +fn place_from_bindings_impl<'db>( db: &'db dyn Db, bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>, requires_explicit_reexport: RequiresExplicitReExport, -) -> Symbol<'db> { +) -> Place<'db> { let predicates = bindings_with_constraints.predicates; let visibility_constraints = bindings_with_constraints.visibility_constraints; let mut bindings_with_constraints = bindings_with_constraints.peekable(); @@ -742,9 +767,10 @@ fn symbol_from_bindings_impl<'db>( binding, visibility_constraint, narrowing_constraint: _, - }) if binding.is_none_or(is_non_exported) => Some(*visibility_constraint), + }) if binding.is_undefined_or(is_non_exported) => Some(*visibility_constraint), _ => None, }; + let mut deleted_visibility = Truthiness::AlwaysFalse; // Evaluate this lazily because we don't always need it (for example, if there are no visible // bindings at all, we don't need it), and it can cause us to evaluate visibility constraint @@ -763,7 +789,19 @@ fn symbol_from_bindings_impl<'db>( narrowing_constraint, visibility_constraint, }| { - let binding = binding?; + let binding = + match binding { + DefinitionState::Defined(binding) => binding, + DefinitionState::Undefined => { + return None; + } + DefinitionState::Deleted => { + deleted_visibility = deleted_visibility.or( + visibility_constraints.evaluate(db, predicates, visibility_constraint) + ); + return None; + } + }; if is_non_exported(binding) { return None; @@ -774,7 +812,7 @@ fn symbol_from_bindings_impl<'db>( if static_visibility.is_always_false() { // We found a binding that we have statically determined to not be visible from - // the use of the symbol that we are investigating. There are three interesting + // the use of the place that we are investigating. There are three interesting // cases to consider: // // ```py @@ -824,7 +862,7 @@ fn symbol_from_bindings_impl<'db>( } let binding_ty = binding_type(db, binding); - Some(narrowing_constraint.narrow(db, binding_ty, binding.symbol(db))) + Some(narrowing_constraint.narrow(db, binding_ty, binding.place(db))) }, ); @@ -839,29 +877,31 @@ fn symbol_from_bindings_impl<'db>( Truthiness::Ambiguous => Boundness::PossiblyUnbound, }; - if let Some(second) = types.next() { - Symbol::Type( - UnionType::from_elements(db, [first, second].into_iter().chain(types)), - boundness, - ) + let ty = if let Some(second) = types.next() { + UnionType::from_elements(db, [first, second].into_iter().chain(types)) } else { - Symbol::Type(first, boundness) + first + }; + match deleted_visibility { + Truthiness::AlwaysFalse => Place::Type(ty, boundness), + Truthiness::AlwaysTrue => Place::Unbound, + Truthiness::Ambiguous => Place::Type(ty, Boundness::PossiblyUnbound), } } else { - Symbol::Unbound + Place::Unbound } } -/// Implementation of [`symbol_from_declarations`]. +/// Implementation of [`place_from_declarations`]. /// /// ## Implementation Note /// This function gets called cross-module. It, therefore, shouldn't /// access any AST nodes from the file containing the declarations. -fn symbol_from_declarations_impl<'db>( +fn place_from_declarations_impl<'db>( db: &'db dyn Db, declarations: DeclarationsIterator<'_, 'db>, requires_explicit_reexport: RequiresExplicitReExport, -) -> SymbolFromDeclarationsResult<'db> { +) -> PlaceFromDeclarationsResult<'db> { let predicates = declarations.predicates; let visibility_constraints = declarations.visibility_constraints; let mut declarations = declarations.peekable(); @@ -874,7 +914,7 @@ fn symbol_from_declarations_impl<'db>( Some(DeclarationWithConstraint { declaration, visibility_constraint, - }) if declaration.is_none_or(is_non_exported) => { + }) if declaration.is_undefined_or(is_non_exported) => { visibility_constraints.evaluate(db, predicates, *visibility_constraint) } _ => Truthiness::AlwaysFalse, @@ -885,7 +925,9 @@ fn symbol_from_declarations_impl<'db>( declaration, visibility_constraint, }| { - let declaration = declaration?; + let DefinitionState::Defined(declaration) = declaration else { + return None; + }; if is_non_exported(declaration) { return None; @@ -932,8 +974,10 @@ fn symbol_from_declarations_impl<'db>( Truthiness::Ambiguous => Boundness::PossiblyUnbound, }; - Ok(Symbol::Type(declared.inner_type(), boundness) - .with_qualifiers(declared.qualifiers())) + Ok( + Place::Type(declared.inner_type(), boundness) + .with_qualifiers(declared.qualifiers()), + ) } else { Err(( declared, @@ -943,7 +987,7 @@ fn symbol_from_declarations_impl<'db>( )) } } else { - Ok(Symbol::Unbound.into()) + Ok(Place::Unbound.into()) } } @@ -963,8 +1007,8 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool { let Some(all_names) = dunder_all_names(db, definition.file(db)) else { return false; }; - let table = symbol_table(db, definition.scope(db)); - let symbol_name = table.symbol(definition.symbol(db)).name(); + let table = place_table(db, definition.scope(db)); + let symbol_name = table.place_expr(definition.place(db)).expect_name(); all_names.contains(symbol_name) } @@ -972,38 +1016,39 @@ mod implicit_globals { use ruff_python_ast as ast; use crate::db::Db; - use crate::semantic_index::{self, symbol_table, use_def_map}; - use crate::symbol::SymbolAndQualifiers; + use crate::place::PlaceAndQualifiers; + use crate::semantic_index::place::PlaceExpr; + use crate::semantic_index::{self, place_table, use_def_map}; use crate::types::{KnownClass, Type}; - use super::{Symbol, SymbolFromDeclarationsResult, symbol_from_declarations}; + use super::{Place, PlaceFromDeclarationsResult, place_from_declarations}; pub(crate) fn module_type_implicit_global_declaration<'db>( db: &'db dyn Db, - name: &str, - ) -> SymbolFromDeclarationsResult<'db> { + expr: &PlaceExpr, + ) -> PlaceFromDeclarationsResult<'db> { if !module_type_symbols(db) .iter() - .any(|module_type_member| &**module_type_member == name) + .any(|module_type_member| Some(module_type_member) == expr.as_name()) { - return Ok(Symbol::Unbound.into()); + return Ok(Place::Unbound.into()); } let Type::ClassLiteral(module_type_class) = KnownClass::ModuleType.to_class_literal(db) else { - return Ok(Symbol::Unbound.into()); + return Ok(Place::Unbound.into()); }; let module_type_scope = module_type_class.body_scope(db); - let symbol_table = symbol_table(db, module_type_scope); - let Some(symbol_id) = symbol_table.symbol_id_by_name(name) else { - return Ok(Symbol::Unbound.into()); + let place_table = place_table(db, module_type_scope); + let Some(place_id) = place_table.place_id_by_expr(expr) else { + return Ok(Place::Unbound.into()); }; - symbol_from_declarations( + place_from_declarations( db, - use_def_map(db, module_type_scope).public_declarations(symbol_id), + use_def_map(db, module_type_scope).public_declarations(place_id), ) } - /// Looks up the type of an "implicit global symbol". Returns [`Symbol::Unbound`] if + /// Looks up the type of an "implicit global symbol". Returns [`Place::Unbound`] if /// `name` is not present as an implicit symbol in module-global namespaces. /// /// Implicit global symbols are symbols such as `__doc__`, `__name__`, and `__file__` @@ -1014,20 +1059,20 @@ mod implicit_globals { /// up in the global scope **from within the same file**. If the symbol is being looked up /// from outside the file (e.g. via imports), use [`super::imported_symbol`] (or fallback logic /// like the logic used in that function) instead. The reason is that this function returns - /// [`Symbol::Unbound`] for `__init__` and `__dict__` (which cannot be found in globals if + /// [`Place::Unbound`] for `__init__` and `__dict__` (which cannot be found in globals if /// the lookup is being done from the same file) -- but these symbols *are* available in the /// global scope if they're being imported **from a different file**. pub(crate) fn module_type_implicit_global_symbol<'db>( db: &'db dyn Db, name: &str, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { // We special-case `__file__` here because we know that for an internal implicit global // lookup in a Python module, it is always a string, even though typeshed says `str | // None`. if name == "__file__" { - Symbol::bound(KnownClass::Str.to_instance(db)).into() + Place::bound(KnownClass::Str.to_instance(db)).into() } else if name == "__builtins__" { - Symbol::bound(Type::any()).into() + Place::bound(Type::any()).into() } // In general we wouldn't check to see whether a symbol exists on a class before doing the // `.member()` call on the instance type -- we'd just do the `.member`() call on the instance @@ -1040,7 +1085,7 @@ mod implicit_globals { { KnownClass::ModuleType.to_instance(db).member(db, name) } else { - Symbol::Unbound.into() + Place::Unbound.into() } } @@ -1073,12 +1118,12 @@ mod implicit_globals { }; let module_type_scope = module_type.body_scope(db); - let module_type_symbol_table = symbol_table(db, module_type_scope); + let module_type_symbol_table = place_table(db, module_type_scope); module_type_symbol_table - .symbols() - .filter(|symbol| symbol.is_declared()) - .map(semantic_index::symbol::Symbol::name) + .places() + .filter(|symbol| symbol.is_declared() && symbol.is_name()) + .map(semantic_index::place::PlaceExpr::expect_name) .filter(|symbol_name| { !matches!(&***symbol_name, "__dict__" | "__getattr__" | "__init__") }) @@ -1123,9 +1168,9 @@ impl RequiresExplicitReExport { /// of symbols that have no declared type. fn widen_type_for_undeclared_public_symbol<'db>( db: &'db dyn Db, - inferred: Symbol<'db>, + inferred: Place<'db>, is_considered_non_modifiable: bool, -) -> Symbol<'db> { +) -> Place<'db> { // We special-case known-instance types here since symbols like `typing.Any` are typically // not declared in the stubs (e.g. `Any = object()`), but we still want to treat them as // such. @@ -1153,15 +1198,15 @@ mod tests { let ty1 = Type::IntLiteral(1); let ty2 = Type::IntLiteral(2); - let unbound = || Symbol::Unbound.with_qualifiers(TypeQualifiers::empty()); + let unbound = || Place::Unbound.with_qualifiers(TypeQualifiers::empty()); let possibly_unbound_ty1 = - || Symbol::Type(ty1, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); + || Place::Type(ty1, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); let possibly_unbound_ty2 = - || Symbol::Type(ty2, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); + || Place::Type(ty2, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); - let bound_ty1 = || Symbol::Type(ty1, Bound).with_qualifiers(TypeQualifiers::empty()); - let bound_ty2 = || Symbol::Type(ty2, Bound).with_qualifiers(TypeQualifiers::empty()); + let bound_ty1 = || Place::Type(ty1, Bound).with_qualifiers(TypeQualifiers::empty()); + let bound_ty2 = || Place::Type(ty2, Bound).with_qualifiers(TypeQualifiers::empty()); // Start from an unbound symbol assert_eq!(unbound().or_fall_back_to(&db, unbound), unbound()); @@ -1178,11 +1223,11 @@ mod tests { ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, possibly_unbound_ty2), - Symbol::Type(UnionType::from_elements(&db, [ty1, ty2]), PossiblyUnbound).into() + Place::Type(UnionType::from_elements(&db, [ty1, ty2]), PossiblyUnbound).into() ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, bound_ty2), - Symbol::Type(UnionType::from_elements(&db, [ty1, ty2]), Bound).into() + Place::Type(UnionType::from_elements(&db, [ty1, ty2]), Bound).into() ); // Start from a definitely bound symbol @@ -1195,10 +1240,10 @@ mod tests { } #[track_caller] - fn assert_bound_string_symbol<'db>(db: &'db dyn Db, symbol: Symbol<'db>) { + fn assert_bound_string_symbol<'db>(db: &'db dyn Db, symbol: Place<'db>) { assert!(matches!( symbol, - Symbol::Type(Type::NominalInstance(_), Boundness::Bound) + Place::Type(Type::NominalInstance(_), Boundness::Bound) )); assert_eq!(symbol.expect_type(), KnownClass::Str.to_instance(db)); } @@ -1206,19 +1251,19 @@ mod tests { #[test] fn implicit_builtin_globals() { let db = setup_db(); - assert_bound_string_symbol(&db, builtins_symbol(&db, "__name__").symbol); + assert_bound_string_symbol(&db, builtins_symbol(&db, "__name__").place); } #[test] fn implicit_typing_globals() { let db = setup_db(); - assert_bound_string_symbol(&db, typing_symbol(&db, "__name__").symbol); + assert_bound_string_symbol(&db, typing_symbol(&db, "__name__").place); } #[test] fn implicit_typing_extensions_globals() { let db = setup_db(); - assert_bound_string_symbol(&db, typing_extensions_symbol(&db, "__name__").symbol); + assert_bound_string_symbol(&db, typing_extensions_symbol(&db, "__name__").place); } #[test] @@ -1226,7 +1271,7 @@ mod tests { let db = setup_db(); assert_bound_string_symbol( &db, - known_module_symbol(&db, KnownModule::Sys, "__name__").symbol, + known_module_symbol(&db, KnownModule::Sys, "__name__").place, ); } } diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index c3a2f19418ebab..c117b7f737435c 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -19,9 +19,9 @@ use crate::semantic_index::builder::SemanticIndexBuilder; use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions}; use crate::semantic_index::expression::Expression; use crate::semantic_index::narrowing_constraints::ScopedNarrowingConstraint; -use crate::semantic_index::symbol::{ - FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopedSymbolId, - SymbolTable, +use crate::semantic_index::place::{ + FileScopeId, NodeWithScopeKey, NodeWithScopeRef, PlaceExpr, PlaceTable, Scope, ScopeId, + ScopeKind, ScopedPlaceId, }; use crate::semantic_index::use_def::{EagerSnapshotKey, ScopedEagerSnapshotId, UseDefMap}; @@ -30,9 +30,9 @@ mod builder; pub mod definition; pub mod expression; pub(crate) mod narrowing_constraints; +pub mod place; pub(crate) mod predicate; mod re_exports; -pub mod symbol; mod use_def; mod visibility_constraints; @@ -41,7 +41,7 @@ pub(crate) use self::use_def::{ DeclarationsIterator, }; -type SymbolMap = hashbrown::HashMap; +type PlaceSet = hashbrown::HashMap; /// Returns the semantic index for `file`. /// @@ -55,18 +55,18 @@ pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> { SemanticIndexBuilder::new(db, file, parsed).build() } -/// Returns the symbol table for a specific `scope`. +/// Returns the place table for a specific `scope`. /// -/// Using [`symbol_table`] over [`semantic_index`] has the advantage that -/// Salsa can avoid invalidating dependent queries if this scope's symbol table +/// Using [`place_table`] over [`semantic_index`] has the advantage that +/// Salsa can avoid invalidating dependent queries if this scope's place table /// is unchanged. #[salsa::tracked(returns(deref))] -pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc { +pub(crate) fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc { let file = scope.file(db); - let _span = tracing::trace_span!("symbol_table", scope=?scope.as_id(), ?file).entered(); + let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?file).entered(); let index = semantic_index(db, file); - index.symbol_table(scope.file_scope_id(db)) + index.place_table(scope.file_scope_id(db)) } /// Returns the set of modules that are imported anywhere in `file`. @@ -113,13 +113,10 @@ pub(crate) fn attribute_assignments<'db, 's>( let index = semantic_index(db, file); attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| { - let attribute_table = index.instance_attribute_table(function_scope_id); - let symbol = attribute_table.symbol_id_by_name(name)?; + let place_table = index.place_table(function_scope_id); + let place = place_table.place_id_by_instance_attribute_name(name)?; let use_def = &index.use_def_maps[function_scope_id]; - Some(( - use_def.instance_attribute_bindings(symbol), - function_scope_id, - )) + Some((use_def.public_bindings(place), function_scope_id)) }) } @@ -167,14 +164,11 @@ pub(crate) enum EagerSnapshotResult<'map, 'db> { NoLongerInEagerContext, } -/// The symbol tables and use-def maps for all scopes in a file. +/// The place tables and use-def maps for all scopes in a file. #[derive(Debug, Update)] pub(crate) struct SemanticIndex<'db> { - /// List of all symbol tables in this file, indexed by scope. - symbol_tables: IndexVec>, - - /// List of all instance attribute tables in this file, indexed by scope. - instance_attribute_tables: IndexVec, + /// List of all place tables in this file, indexed by scope. + place_tables: IndexVec>, /// List of all scopes in this file. scopes: IndexVec, @@ -195,7 +189,7 @@ pub(crate) struct SemanticIndex<'db> { scope_ids_by_scope: IndexVec>, /// Map from the file-local [`FileScopeId`] to the set of explicit-global symbols it contains. - globals_by_scope: FxHashMap>, + globals_by_scope: FxHashMap>, /// Use-def map for each scope in this file. use_def_maps: IndexVec>>, @@ -223,17 +217,13 @@ pub(crate) struct SemanticIndex<'db> { } impl<'db> SemanticIndex<'db> { - /// Returns the symbol table for a specific scope. + /// Returns the place table for a specific scope. /// - /// Use the Salsa cached [`symbol_table()`] query if you only need the - /// symbol table for a single scope. + /// Use the Salsa cached [`place_table()`] query if you only need the + /// place table for a single scope. #[track_caller] - pub(super) fn symbol_table(&self, scope_id: FileScopeId) -> Arc { - self.symbol_tables[scope_id].clone() - } - - pub(super) fn instance_attribute_table(&self, scope_id: FileScopeId) -> &SymbolTable { - &self.instance_attribute_tables[scope_id] + pub(super) fn place_table(&self, scope_id: FileScopeId) -> Arc { + self.place_tables[scope_id].clone() } /// Returns the use-def map for a specific scope. @@ -286,7 +276,7 @@ impl<'db> SemanticIndex<'db> { pub(crate) fn symbol_is_global_in_scope( &self, - symbol: ScopedSymbolId, + symbol: ScopedPlaceId, scope: FileScopeId, ) -> bool { self.globals_by_scope @@ -444,7 +434,7 @@ impl<'db> SemanticIndex<'db> { pub(crate) fn eager_snapshot( &self, enclosing_scope: FileScopeId, - symbol: &str, + expr: &PlaceExpr, nested_scope: FileScopeId, ) -> EagerSnapshotResult<'_, 'db> { for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) { @@ -455,12 +445,12 @@ impl<'db> SemanticIndex<'db> { return EagerSnapshotResult::NoLongerInEagerContext; } } - let Some(symbol_id) = self.symbol_tables[enclosing_scope].symbol_id_by_name(symbol) else { + let Some(place_id) = self.place_tables[enclosing_scope].place_id_by_expr(expr) else { return EagerSnapshotResult::NotFound; }; let key = EagerSnapshotKey { enclosing_scope, - enclosing_symbol: symbol_id, + enclosing_place: place_id, nested_scope, }; let Some(id) = self.eager_snapshots.get(&key) else { @@ -480,9 +470,9 @@ pub struct AncestorsIter<'a> { } impl<'a> AncestorsIter<'a> { - fn new(module_symbol_table: &'a SemanticIndex, start: FileScopeId) -> Self { + fn new(module_table: &'a SemanticIndex, start: FileScopeId) -> Self { Self { - scopes: &module_symbol_table.scopes, + scopes: &module_table.scopes, next_id: Some(start), } } @@ -508,9 +498,9 @@ pub struct DescendantsIter<'a> { } impl<'a> DescendantsIter<'a> { - fn new(symbol_table: &'a SemanticIndex, scope_id: FileScopeId) -> Self { - let scope = &symbol_table.scopes[scope_id]; - let scopes = &symbol_table.scopes[scope.descendants()]; + fn new(index: &'a SemanticIndex, scope_id: FileScopeId) -> Self { + let scope = &index.scopes[scope_id]; + let scopes = &index.scopes[scope.descendants()]; Self { next_id: scope_id + 1, @@ -545,8 +535,8 @@ pub struct ChildrenIter<'a> { } impl<'a> ChildrenIter<'a> { - pub(crate) fn new(module_symbol_table: &'a SemanticIndex, parent: FileScopeId) -> Self { - let descendants = DescendantsIter::new(module_symbol_table, parent); + pub(crate) fn new(module_index: &'a SemanticIndex, parent: FileScopeId) -> Self { + let descendants = DescendantsIter::new(module_index, parent); Self { parent, @@ -577,21 +567,19 @@ mod tests { use crate::db::tests::{TestDb, TestDbBuilder}; use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId}; use crate::semantic_index::definition::{Definition, DefinitionKind}; - use crate::semantic_index::symbol::{ - FileScopeId, Scope, ScopeKind, ScopedSymbolId, SymbolTable, - }; + use crate::semantic_index::place::{FileScopeId, PlaceTable, Scope, ScopeKind, ScopedPlaceId}; use crate::semantic_index::use_def::UseDefMap; - use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map}; + use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; impl UseDefMap<'_> { - fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option> { + fn first_public_binding(&self, symbol: ScopedPlaceId) -> Option> { self.public_bindings(symbol) - .find_map(|constrained_binding| constrained_binding.binding) + .find_map(|constrained_binding| constrained_binding.binding.definition()) } fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option> { self.bindings_at_use(use_id) - .find_map(|constrained_binding| constrained_binding.binding) + .find_map(|constrained_binding| constrained_binding.binding.definition()) } } @@ -613,17 +601,17 @@ mod tests { TestCase { db, file } } - fn names(table: &SymbolTable) -> Vec { + fn names(table: &PlaceTable) -> Vec { table - .symbols() - .map(|symbol| symbol.name().to_string()) + .places() + .filter_map(|expr| Some(expr.as_name()?.to_string())) .collect() } #[test] fn empty() { let TestCase { db, file } = test_case(""); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); let global_names = names(global_table); @@ -633,7 +621,7 @@ mod tests { #[test] fn simple() { let TestCase { db, file } = test_case("x"); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["x"]); } @@ -641,7 +629,7 @@ mod tests { #[test] fn annotation_only() { let TestCase { db, file } = test_case("x: int"); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["int", "x"]); // TODO record definition @@ -651,10 +639,10 @@ mod tests { fn import() { let TestCase { db, file } = test_case("import foo"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(names(global_table), vec!["foo"]); - let foo = global_table.symbol_id_by_name("foo").unwrap(); + let foo = global_table.place_id_by_name("foo").unwrap(); let use_def = use_def_map(&db, scope); let binding = use_def.first_public_binding(foo).unwrap(); @@ -664,7 +652,7 @@ mod tests { #[test] fn import_sub() { let TestCase { db, file } = test_case("import foo.bar"); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["foo"]); } @@ -672,7 +660,7 @@ mod tests { #[test] fn import_as() { let TestCase { db, file } = test_case("import foo.bar as baz"); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["baz"]); } @@ -681,12 +669,12 @@ mod tests { fn import_from() { let TestCase { db, file } = test_case("from bar import foo"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(names(global_table), vec!["foo"]); assert!( global_table - .symbol_by_name("foo") + .place_by_name("foo") .is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }), "symbols that are defined get the defined flag" ); @@ -695,7 +683,7 @@ mod tests { let binding = use_def .first_public_binding( global_table - .symbol_id_by_name("foo") + .place_id_by_name("foo") .expect("symbol to exist"), ) .unwrap(); @@ -706,18 +694,18 @@ mod tests { fn assign() { let TestCase { db, file } = test_case("x = foo"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(names(global_table), vec!["foo", "x"]); assert!( global_table - .symbol_by_name("foo") + .place_by_name("foo") .is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }), "a symbol used but not bound in a scope should have only the used flag" ); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.symbol_id_by_name("x").expect("symbol exists")) + .first_public_binding(global_table.place_id_by_name("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -726,13 +714,13 @@ mod tests { fn augmented_assignment() { let TestCase { db, file } = test_case("x += 1"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(names(global_table), vec!["x"]); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.symbol_id_by_name("x").unwrap()) + .first_public_binding(global_table.place_id_by_name("x").unwrap()) .unwrap(); assert!(matches!( @@ -750,7 +738,7 @@ class C: y = 2 ", ); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["C", "y"]); @@ -765,12 +753,12 @@ y = 2 assert_eq!(class_scope.kind(), ScopeKind::Class); assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C"); - let class_table = index.symbol_table(class_scope_id); + let class_table = index.place_table(class_scope_id); assert_eq!(names(&class_table), vec!["x"]); let use_def = index.use_def_map(class_scope_id); let binding = use_def - .first_public_binding(class_table.symbol_id_by_name("x").expect("symbol exists")) + .first_public_binding(class_table.place_id_by_name("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -785,7 +773,7 @@ y = 2 ", ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["func", "y"]); @@ -798,16 +786,12 @@ y = 2 assert_eq!(function_scope.kind(), ScopeKind::Function); assert_eq!(function_scope_id.to_scope_id(&db, file).name(&db), "func"); - let function_table = index.symbol_table(function_scope_id); + let function_table = index.place_table(function_scope_id); assert_eq!(names(&function_table), vec!["x"]); let use_def = index.use_def_map(function_scope_id); let binding = use_def - .first_public_binding( - function_table - .symbol_id_by_name("x") - .expect("symbol exists"), - ) + .first_public_binding(function_table.place_id_by_name("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -822,7 +806,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): ); let index = semantic_index(&db, file); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert_eq!(names(global_table), vec!["str", "int", "f"]); @@ -833,7 +817,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): panic!("Expected a function scope") }; - let function_table = index.symbol_table(function_scope_id); + let function_table = index.place_table(function_scope_id); assert_eq!( names(&function_table), vec!["a", "b", "c", "d", "args", "kwargs"], @@ -844,7 +828,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let binding = use_def .first_public_binding( function_table - .symbol_id_by_name(name) + .place_id_by_name(name) .expect("symbol exists"), ) .unwrap(); @@ -853,7 +837,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let args_binding = use_def .first_public_binding( function_table - .symbol_id_by_name("args") + .place_id_by_name("args") .expect("symbol exists"), ) .unwrap(); @@ -864,7 +848,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let kwargs_binding = use_def .first_public_binding( function_table - .symbol_id_by_name("kwargs") + .place_id_by_name("kwargs") .expect("symbol exists"), ) .unwrap(); @@ -879,7 +863,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let TestCase { db, file } = test_case("lambda a, b, c=1, *args, d=2, **kwargs: None"); let index = semantic_index(&db, file); - let global_table = symbol_table(&db, global_scope(&db, file)); + let global_table = place_table(&db, global_scope(&db, file)); assert!(names(global_table).is_empty()); @@ -890,7 +874,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): panic!("Expected a lambda scope") }; - let lambda_table = index.symbol_table(lambda_scope_id); + let lambda_table = index.place_table(lambda_scope_id); assert_eq!( names(&lambda_table), vec!["a", "b", "c", "d", "args", "kwargs"], @@ -899,14 +883,14 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let use_def = index.use_def_map(lambda_scope_id); for name in ["a", "b", "c", "d"] { let binding = use_def - .first_public_binding(lambda_table.symbol_id_by_name(name).expect("symbol exists")) + .first_public_binding(lambda_table.place_id_by_name(name).expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_))); } let args_binding = use_def .first_public_binding( lambda_table - .symbol_id_by_name("args") + .place_id_by_name("args") .expect("symbol exists"), ) .unwrap(); @@ -917,7 +901,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let kwargs_binding = use_def .first_public_binding( lambda_table - .symbol_id_by_name("kwargs") + .place_id_by_name("kwargs") .expect("symbol exists"), ) .unwrap(); @@ -938,7 +922,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["iter1"]); @@ -955,7 +939,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): "" ); - let comprehension_symbol_table = index.symbol_table(comprehension_scope_id); + let comprehension_symbol_table = index.place_table(comprehension_scope_id); assert_eq!(names(&comprehension_symbol_table), vec!["x", "y"]); @@ -964,7 +948,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let binding = use_def .first_public_binding( comprehension_symbol_table - .symbol_id_by_name(name) + .place_id_by_name(name) .expect("symbol exists"), ) .unwrap(); @@ -1031,7 +1015,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["iter1"]); @@ -1048,7 +1032,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): "" ); - let comprehension_symbol_table = index.symbol_table(comprehension_scope_id); + let comprehension_symbol_table = index.place_table(comprehension_scope_id); assert_eq!(names(&comprehension_symbol_table), vec!["y", "iter2"]); @@ -1067,7 +1051,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): "" ); - let inner_comprehension_symbol_table = index.symbol_table(inner_comprehension_scope_id); + let inner_comprehension_symbol_table = index.place_table(inner_comprehension_scope_id); assert_eq!(names(&inner_comprehension_symbol_table), vec!["x"]); } @@ -1082,14 +1066,14 @@ with item1 as x, item2 as y: ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["item1", "x", "item2", "y"]); let use_def = index.use_def_map(FileScopeId::global()); for name in ["x", "y"] { let binding = use_def - .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); } @@ -1105,14 +1089,14 @@ with context() as (x, y): ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["context", "x", "y"]); let use_def = index.use_def_map(FileScopeId::global()); for name in ["x", "y"] { let binding = use_def - .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); } @@ -1129,7 +1113,7 @@ def func(): ", ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["func"]); let [ @@ -1148,8 +1132,8 @@ def func(): assert_eq!(func_scope_2.kind(), ScopeKind::Function); assert_eq!(func_scope2_id.to_scope_id(&db, file).name(&db), "func"); - let func1_table = index.symbol_table(func_scope1_id); - let func2_table = index.symbol_table(func_scope2_id); + let func1_table = index.place_table(func_scope1_id); + let func2_table = index.place_table(func_scope2_id); assert_eq!(names(&func1_table), vec!["x"]); assert_eq!(names(&func2_table), vec!["y"]); @@ -1157,7 +1141,7 @@ def func(): let binding = use_def .first_public_binding( global_table - .symbol_id_by_name("func") + .place_id_by_name("func") .expect("symbol exists"), ) .unwrap(); @@ -1174,7 +1158,7 @@ def func[T](): ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["func"]); @@ -1187,7 +1171,7 @@ def func[T](): assert_eq!(ann_scope.kind(), ScopeKind::Annotation); assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "func"); - let ann_table = index.symbol_table(ann_scope_id); + let ann_table = index.place_table(ann_scope_id); assert_eq!(names(&ann_table), vec!["T"]); let [(func_scope_id, func_scope)] = @@ -1197,7 +1181,7 @@ def func[T](): }; assert_eq!(func_scope.kind(), ScopeKind::Function); assert_eq!(func_scope_id.to_scope_id(&db, file).name(&db), "func"); - let func_table = index.symbol_table(func_scope_id); + let func_table = index.place_table(func_scope_id); assert_eq!(names(&func_table), vec!["x"]); } @@ -1211,7 +1195,7 @@ class C[T]: ); let index = semantic_index(&db, file); - let global_table = index.symbol_table(FileScopeId::global()); + let global_table = index.place_table(FileScopeId::global()); assert_eq!(names(&global_table), vec!["C"]); @@ -1224,11 +1208,11 @@ class C[T]: assert_eq!(ann_scope.kind(), ScopeKind::Annotation); assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "C"); - let ann_table = index.symbol_table(ann_scope_id); + let ann_table = index.place_table(ann_scope_id); assert_eq!(names(&ann_table), vec!["T"]); assert!( ann_table - .symbol_by_name("T") + .place_by_name("T") .is_some_and(|s| s.is_bound() && !s.is_used()), "type parameters are defined by the scope that introduces them" ); @@ -1241,7 +1225,7 @@ class C[T]: assert_eq!(class_scope.kind(), ScopeKind::Class); assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C"); - assert_eq!(names(&index.symbol_table(class_scope_id)), vec!["x"]); + assert_eq!(names(&index.place_table(class_scope_id)), vec!["x"]); } #[test] @@ -1369,9 +1353,9 @@ match subject: ); let global_scope_id = global_scope(&db, file); - let global_table = symbol_table(&db, global_scope_id); + let global_table = place_table(&db, global_scope_id); - assert!(global_table.symbol_by_name("Foo").unwrap().is_used()); + assert!(global_table.place_by_name("Foo").unwrap().is_used()); assert_eq!( names(global_table), vec![ @@ -1395,7 +1379,7 @@ match subject: ("l", 1), ] { let binding = use_def - .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { assert_eq!(pattern.index(), expected_index); @@ -1418,14 +1402,14 @@ match 1: ); let global_scope_id = global_scope(&db, file); - let global_table = symbol_table(&db, global_scope_id); + let global_table = place_table(&db, global_scope_id); assert_eq!(names(global_table), vec!["first", "second"]); let use_def = use_def_map(&db, global_scope_id); for (name, expected_index) in [("first", 0), ("second", 0)] { let binding = use_def - .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { assert_eq!(pattern.index(), expected_index); @@ -1439,13 +1423,13 @@ match 1: fn for_loops_single_assignment() { let TestCase { db, file } = test_case("for x in a: pass"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(&names(global_table), &["a", "x"]); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.symbol_id_by_name("x").unwrap()) + .first_public_binding(global_table.place_id_by_name("x").unwrap()) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); @@ -1455,16 +1439,16 @@ match 1: fn for_loops_simple_unpacking() { let TestCase { db, file } = test_case("for (x, y) in a: pass"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(&names(global_table), &["a", "x", "y"]); let use_def = use_def_map(&db, scope); let x_binding = use_def - .first_public_binding(global_table.symbol_id_by_name("x").unwrap()) + .first_public_binding(global_table.place_id_by_name("x").unwrap()) .unwrap(); let y_binding = use_def - .first_public_binding(global_table.symbol_id_by_name("y").unwrap()) + .first_public_binding(global_table.place_id_by_name("y").unwrap()) .unwrap(); assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_))); @@ -1475,13 +1459,13 @@ match 1: fn for_loops_complex_unpacking() { let TestCase { db, file } = test_case("for [((a,) b), (c, d)] in e: pass"); let scope = global_scope(&db, file); - let global_table = symbol_table(&db, scope); + let global_table = place_table(&db, scope); assert_eq!(&names(global_table), &["e", "a", "b", "c", "d"]); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.symbol_id_by_name("a").unwrap()) + .first_public_binding(global_table.place_id_by_name("a").unwrap()) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); diff --git a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs index 0a18330569047e..191be73c23244d 100644 --- a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs +++ b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs @@ -6,14 +6,14 @@ use ruff_python_ast::ExprRef; use crate::Db; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::semantic_index; -use crate::semantic_index::symbol::ScopeId; /// AST ids for a single scope. /// /// The motivation for building the AST ids per scope isn't about reducing invalidation because /// the struct changes whenever the parsed AST changes. Instead, it's mainly that we can -/// build the AST ids struct when building the symbol table and also keep the property that +/// build the AST ids struct when building the place table and also keep the property that /// IDs of outer scopes are unaffected by changes in inner scopes. /// /// For example, we don't want that adding new statements to `foo` changes the statement id of `x = foo()` in: @@ -28,7 +28,7 @@ use crate::semantic_index::symbol::ScopeId; pub(crate) struct AstIds { /// Maps expressions to their expression id. expressions_map: FxHashMap, - /// Maps expressions which "use" a symbol (that is, [`ast::ExprName`]) to a use id. + /// Maps expressions which "use" a place (that is, [`ast::ExprName`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`]) to a use id. uses_map: FxHashMap, } @@ -49,7 +49,7 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds { semantic_index(db, scope.file(db)).ast_ids(scope.file_scope_id(db)) } -/// Uniquely identifies a use of a name in a [`crate::semantic_index::symbol::FileScopeId`]. +/// Uniquely identifies a use of a name in a [`crate::semantic_index::place::FileScopeId`]. #[newtype_index] pub struct ScopedUseId; @@ -72,6 +72,20 @@ impl HasScopedUseId for ast::ExprName { } } +impl HasScopedUseId for ast::ExprAttribute { + fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId { + let expression_ref = ExprRef::from(self); + expression_ref.scoped_use_id(db, scope) + } +} + +impl HasScopedUseId for ast::ExprSubscript { + fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId { + let expression_ref = ExprRef::from(self); + expression_ref.scoped_use_id(db, scope) + } +} + impl HasScopedUseId for ast::ExprRef<'_> { fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId { let ast_ids = ast_ids(db, scope); @@ -79,7 +93,7 @@ impl HasScopedUseId for ast::ExprRef<'_> { } } -/// Uniquely identifies an [`ast::Expr`] in a [`crate::semantic_index::symbol::FileScopeId`]. +/// Uniquely identifies an [`ast::Expr`] in a [`crate::semantic_index::place::FileScopeId`]. #[newtype_index] #[derive(salsa::Update)] pub struct ScopedExpressionId; diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index a5b0819cac3451..db4cf3da50f08f 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -24,24 +24,22 @@ use crate::semantic_index::SemanticIndex; use crate::semantic_index::ast_ids::AstIdsBuilder; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; use crate::semantic_index::definition::{ - AnnotatedAssignmentDefinitionKind, AnnotatedAssignmentDefinitionNodeRef, - AssignmentDefinitionKind, AssignmentDefinitionNodeRef, ComprehensionDefinitionKind, - ComprehensionDefinitionNodeRef, Definition, DefinitionCategory, DefinitionKind, - DefinitionNodeKey, DefinitionNodeRef, Definitions, ExceptHandlerDefinitionNodeRef, - ForStmtDefinitionKind, ForStmtDefinitionNodeRef, ImportDefinitionNodeRef, - ImportFromDefinitionNodeRef, MatchPatternDefinitionNodeRef, StarImportDefinitionNodeRef, - TargetKind, WithItemDefinitionKind, WithItemDefinitionNodeRef, + AnnotatedAssignmentDefinitionNodeRef, AssignmentDefinitionNodeRef, + ComprehensionDefinitionNodeRef, Definition, DefinitionCategory, DefinitionNodeKey, + DefinitionNodeRef, Definitions, ExceptHandlerDefinitionNodeRef, ForStmtDefinitionNodeRef, + ImportDefinitionNodeRef, ImportFromDefinitionNodeRef, MatchPatternDefinitionNodeRef, + StarImportDefinitionNodeRef, WithItemDefinitionNodeRef, }; use crate::semantic_index::expression::{Expression, ExpressionKind}; +use crate::semantic_index::place::{ + FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, + PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId, +}; use crate::semantic_index::predicate::{ PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, ScopedPredicateId, StarImportPlaceholderPredicate, }; use crate::semantic_index::re_exports::exported_names; -use crate::semantic_index::symbol::{ - FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, Scope, ScopeId, ScopeKind, - ScopedSymbolId, SymbolTableBuilder, -}; use crate::semantic_index::use_def::{ EagerSnapshotKey, FlowSnapshot, ScopedEagerSnapshotId, UseDefMapBuilder, }; @@ -100,13 +98,12 @@ pub(super) struct SemanticIndexBuilder<'db> { // Semantic Index fields scopes: IndexVec, scope_ids_by_scope: IndexVec>, - symbol_tables: IndexVec, - instance_attribute_tables: IndexVec, + place_tables: IndexVec, ast_ids: IndexVec, use_def_maps: IndexVec>, scopes_by_node: FxHashMap, scopes_by_expression: FxHashMap, - globals_by_scope: FxHashMap>, + globals_by_scope: FxHashMap>, definitions_by_node: FxHashMap>, expressions_by_node: FxHashMap>, imported_modules: FxHashSet, @@ -135,8 +132,7 @@ impl<'db> SemanticIndexBuilder<'db> { has_future_annotations: false, scopes: IndexVec::new(), - symbol_tables: IndexVec::new(), - instance_attribute_tables: IndexVec::new(), + place_tables: IndexVec::new(), ast_ids: IndexVec::new(), scope_ids_by_scope: IndexVec::new(), use_def_maps: IndexVec::new(), @@ -259,9 +255,7 @@ impl<'db> SemanticIndexBuilder<'db> { self.try_node_context_stack_manager.enter_nested_scope(); let file_scope_id = self.scopes.push(scope); - self.symbol_tables.push(SymbolTableBuilder::default()); - self.instance_attribute_tables - .push(SymbolTableBuilder::default()); + self.place_tables.push(PlaceTableBuilder::default()); self.use_def_maps .push(UseDefMapBuilder::new(is_class_scope)); let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default()); @@ -301,36 +295,35 @@ impl<'db> SemanticIndexBuilder<'db> { // If the scope that we just popped off is an eager scope, we need to "lock" our view of // which bindings reach each of the uses in the scope. Loop through each enclosing scope, - // looking for any that bind each symbol. + // looking for any that bind each place. for enclosing_scope_info in self.scope_stack.iter().rev() { let enclosing_scope_id = enclosing_scope_info.file_scope_id; let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind(); - let enclosing_symbol_table = &self.symbol_tables[enclosing_scope_id]; + let enclosing_place_table = &self.place_tables[enclosing_scope_id]; - for nested_symbol in self.symbol_tables[popped_scope_id].symbols() { - // Skip this symbol if this enclosing scope doesn't contain any bindings for it. - // Note that even if this symbol is bound in the popped scope, + for nested_place in self.place_tables[popped_scope_id].places() { + // Skip this place if this enclosing scope doesn't contain any bindings for it. + // Note that even if this place is bound in the popped scope, // it may refer to the enclosing scope bindings // so we also need to snapshot the bindings of the enclosing scope. - let Some(enclosing_symbol_id) = - enclosing_symbol_table.symbol_id_by_name(nested_symbol.name()) + let Some(enclosing_place_id) = enclosing_place_table.place_id_by_expr(nested_place) else { continue; }; - let enclosing_symbol = enclosing_symbol_table.symbol(enclosing_symbol_id); + let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id); - // Snapshot the state of this symbol that are visible at this point in this + // Snapshot the state of this place that are visible at this point in this // enclosing scope. let key = EagerSnapshotKey { enclosing_scope: enclosing_scope_id, - enclosing_symbol: enclosing_symbol_id, + enclosing_place: enclosing_place_id, nested_scope: popped_scope_id, }; let eager_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_eager_state( - enclosing_symbol_id, + enclosing_place_id, enclosing_scope_kind, - enclosing_symbol.is_bound(), + enclosing_place, ); self.eager_snapshots.insert(key, eager_snapshot); } @@ -338,7 +331,7 @@ impl<'db> SemanticIndexBuilder<'db> { // Lazy scopes are "sticky": once we see a lazy scope we stop doing lookups // eagerly, even if we would encounter another eager enclosing scope later on. // Also, narrowing constraints outside a lazy scope are not applicable. - // TODO: If the symbol has never been rewritten, they are applicable. + // TODO: If the place has never been rewritten, they are applicable. if !enclosing_scope_kind.is_eager() { break; } @@ -347,14 +340,9 @@ impl<'db> SemanticIndexBuilder<'db> { popped_scope_id } - fn current_symbol_table(&mut self) -> &mut SymbolTableBuilder { - let scope_id = self.current_scope(); - &mut self.symbol_tables[scope_id] - } - - fn current_attribute_table(&mut self) -> &mut SymbolTableBuilder { + fn current_place_table(&mut self) -> &mut PlaceTableBuilder { let scope_id = self.current_scope(); - &mut self.instance_attribute_tables[scope_id] + &mut self.place_tables[scope_id] } fn current_use_def_map_mut(&mut self) -> &mut UseDefMapBuilder<'db> { @@ -389,34 +377,36 @@ impl<'db> SemanticIndexBuilder<'db> { self.current_use_def_map_mut().merge(state); } - /// Add a symbol to the symbol table and the use-def map. - /// Return the [`ScopedSymbolId`] that uniquely identifies the symbol in both. - fn add_symbol(&mut self, name: Name) -> ScopedSymbolId { - let (symbol_id, added) = self.current_symbol_table().add_symbol(name); + /// Add a symbol to the place table and the use-def map. + /// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both. + fn add_symbol(&mut self, name: Name) -> ScopedPlaceId { + let (place_id, added) = self.current_place_table().add_symbol(name); if added { - self.current_use_def_map_mut().add_symbol(symbol_id); + self.current_use_def_map_mut().add_place(place_id); } - symbol_id + place_id } - fn add_attribute(&mut self, name: Name) -> ScopedSymbolId { - let (symbol_id, added) = self.current_attribute_table().add_symbol(name); + /// Add a place to the place table and the use-def map. + /// Return the [`ScopedPlaceId`] that uniquely identifies the place in both. + fn add_place(&mut self, place_expr: PlaceExpr) -> ScopedPlaceId { + let (place_id, added) = self.current_place_table().add_place(place_expr); if added { - self.current_use_def_map_mut().add_attribute(symbol_id); + self.current_use_def_map_mut().add_place(place_id); } - symbol_id + place_id } - fn mark_symbol_bound(&mut self, id: ScopedSymbolId) { - self.current_symbol_table().mark_symbol_bound(id); + fn mark_place_bound(&mut self, id: ScopedPlaceId) { + self.current_place_table().mark_place_bound(id); } - fn mark_symbol_declared(&mut self, id: ScopedSymbolId) { - self.current_symbol_table().mark_symbol_declared(id); + fn mark_place_declared(&mut self, id: ScopedPlaceId) { + self.current_place_table().mark_place_declared(id); } - fn mark_symbol_used(&mut self, id: ScopedSymbolId) { - self.current_symbol_table().mark_symbol_used(id); + fn mark_place_used(&mut self, id: ScopedPlaceId) { + self.current_place_table().mark_place_used(id); } fn add_entry_for_definition_key(&mut self, key: DefinitionNodeKey) -> &mut Definitions<'db> { @@ -432,11 +422,10 @@ impl<'db> SemanticIndexBuilder<'db> { /// for all nodes *except* [`ast::Alias`] nodes representing `*` imports. fn add_definition( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, definition_node: impl Into> + std::fmt::Debug + Copy, ) -> Definition<'db> { - let (definition, num_definitions) = - self.push_additional_definition(symbol, definition_node); + let (definition, num_definitions) = self.push_additional_definition(place, definition_node); debug_assert_eq!( num_definitions, 1, "Attempted to create multiple `Definition`s associated with AST node {definition_node:?}" @@ -444,6 +433,22 @@ impl<'db> SemanticIndexBuilder<'db> { definition } + fn delete_associated_bindings(&mut self, place: ScopedPlaceId) { + let scope = self.current_scope(); + // Don't delete associated bindings if the scope is a class scope & place is a name (it's never visible to nested scopes) + if self.scopes[scope].kind() == ScopeKind::Class + && self.place_tables[scope].place_expr(place).is_name() + { + return; + } + for associated_place in self.place_tables[scope].associated_place_ids(place) { + let is_place_name = self.place_tables[scope] + .place_expr(associated_place) + .is_name(); + self.use_def_maps[scope].delete_binding(associated_place, is_place_name); + } + } + /// Push a new [`Definition`] onto the list of definitions /// associated with the `definition_node` AST node. /// @@ -457,7 +462,7 @@ impl<'db> SemanticIndexBuilder<'db> { /// prefer to use `self.add_definition()`, which ensures that this invariant is maintained. fn push_additional_definition( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, definition_node: impl Into>, ) -> (Definition<'db>, usize) { let definition_node: DefinitionNodeRef<'_> = definition_node.into(); @@ -471,7 +476,7 @@ impl<'db> SemanticIndexBuilder<'db> { self.db, self.file, self.current_scope(), - symbol, + place, kind, is_reexported, countme::Count::default(), @@ -484,19 +489,24 @@ impl<'db> SemanticIndexBuilder<'db> { }; if category.is_binding() { - self.mark_symbol_bound(symbol); + self.mark_place_bound(place); } if category.is_declaration() { - self.mark_symbol_declared(symbol); + self.mark_place_declared(place); } + let is_place_name = self.current_place_table().place_expr(place).is_name(); let use_def = self.current_use_def_map_mut(); match category { DefinitionCategory::DeclarationAndBinding => { - use_def.record_declaration_and_binding(symbol, definition); + use_def.record_declaration_and_binding(place, definition, is_place_name); + self.delete_associated_bindings(place); + } + DefinitionCategory::Declaration => use_def.record_declaration(place, definition), + DefinitionCategory::Binding => { + use_def.record_binding(place, definition, is_place_name); + self.delete_associated_bindings(place); } - DefinitionCategory::Declaration => use_def.record_declaration(symbol, definition), - DefinitionCategory::Binding => use_def.record_binding(symbol, definition), } let mut try_node_stack_manager = std::mem::take(&mut self.try_node_context_stack_manager); @@ -506,25 +516,6 @@ impl<'db> SemanticIndexBuilder<'db> { (definition, num_definitions) } - fn add_attribute_definition( - &mut self, - symbol: ScopedSymbolId, - definition_kind: DefinitionKind<'db>, - ) -> Definition { - let definition = Definition::new( - self.db, - self.file, - self.current_scope(), - symbol, - definition_kind, - false, - countme::Count::default(), - ); - self.current_use_def_map_mut() - .record_attribute_binding(symbol, definition); - definition - } - fn record_expression_narrowing_constraint( &mut self, precide_node: &ast::Expr, @@ -684,28 +675,6 @@ impl<'db> SemanticIndexBuilder<'db> { self.current_assignments.last_mut() } - /// Records the fact that we saw an attribute assignment of the form - /// `object.attr: ( = …)` or `object.attr = `. - fn register_attribute_assignment( - &mut self, - object: &ast::Expr, - attr: &'db ast::Identifier, - definition_kind: DefinitionKind<'db>, - ) { - if self.is_method_of_class().is_some() { - // We only care about attribute assignments to the first parameter of a method, - // i.e. typically `self` or `cls`. - let accessed_object_refers_to_first_parameter = - object.as_name_expr().map(|name| name.id.as_str()) - == self.current_first_parameter_name; - - if accessed_object_refers_to_first_parameter { - let symbol = self.add_attribute(attr.id().clone()); - self.add_attribute_definition(symbol, definition_kind); - } - } - } - fn predicate_kind(&mut self, pattern: &ast::Pattern) -> PatternPredicateKind<'db> { match pattern { ast::Pattern::MatchValue(pattern) => { @@ -850,8 +819,8 @@ impl<'db> SemanticIndexBuilder<'db> { // TODO create Definition for PEP 695 typevars // note that the "bound" on the typevar is a totally different thing than whether // or not a name is "bound" by a typevar declaration; the latter is always true. - self.mark_symbol_bound(symbol); - self.mark_symbol_declared(symbol); + self.mark_place_bound(symbol); + self.mark_place_declared(symbol); if let Some(bounds) = bound { self.visit_expr(bounds); } @@ -1022,7 +991,7 @@ impl<'db> SemanticIndexBuilder<'db> { )); Some(unpackable.as_current_assignment(unpack)) } - ast::Expr::Name(_) | ast::Expr::Attribute(_) => { + ast::Expr::Name(_) | ast::Expr::Attribute(_) | ast::Expr::Subscript(_) => { Some(unpackable.as_current_assignment(None)) } _ => None, @@ -1050,18 +1019,12 @@ impl<'db> SemanticIndexBuilder<'db> { assert_eq!(&self.current_assignments, &[]); - let mut symbol_tables: IndexVec<_, _> = self - .symbol_tables + let mut place_tables: IndexVec<_, _> = self + .place_tables .into_iter() .map(|builder| Arc::new(builder.finish())) .collect(); - let mut instance_attribute_tables: IndexVec<_, _> = self - .instance_attribute_tables - .into_iter() - .map(SymbolTableBuilder::finish) - .collect(); - let mut use_def_maps: IndexVec<_, _> = self .use_def_maps .into_iter() @@ -1075,8 +1038,7 @@ impl<'db> SemanticIndexBuilder<'db> { .collect(); self.scopes.shrink_to_fit(); - symbol_tables.shrink_to_fit(); - instance_attribute_tables.shrink_to_fit(); + place_tables.shrink_to_fit(); use_def_maps.shrink_to_fit(); ast_ids.shrink_to_fit(); self.scopes_by_expression.shrink_to_fit(); @@ -1089,8 +1051,7 @@ impl<'db> SemanticIndexBuilder<'db> { self.globals_by_scope.shrink_to_fit(); SemanticIndex { - symbol_tables, - instance_attribute_tables, + place_tables, scopes: self.scopes, definitions_by_node: self.definitions_by_node, expressions_by_node: self.expressions_by_node, @@ -1213,7 +1174,7 @@ where // used to collect all the overloaded definitions of a function. This needs to be // done on the `Identifier` node as opposed to `ExprName` because that's what the // AST uses. - self.mark_symbol_used(symbol); + self.mark_place_used(symbol); let use_id = self.current_ast_ids().record_use(name); self.current_use_def_map_mut() .record_use(symbol, use_id, NodeKey::from_node(name)); @@ -1356,7 +1317,10 @@ where // For more details, see the doc-comment on `StarImportPlaceholderPredicate`. for export in exported_names(self.db, referenced_module) { let symbol_id = self.add_symbol(export.clone()); - let node_ref = StarImportDefinitionNodeRef { node, symbol_id }; + let node_ref = StarImportDefinitionNodeRef { + node, + place_id: symbol_id, + }; let star_import = StarImportPlaceholderPredicate::new( self.db, self.file, @@ -1365,7 +1329,7 @@ where ); let pre_definition = - self.current_use_def_map().single_symbol_snapshot(symbol_id); + self.current_use_def_map().single_place_snapshot(symbol_id); self.push_additional_definition(symbol_id, node_ref); self.current_use_def_map_mut() .record_and_negate_star_import_visibility_constraint( @@ -1920,8 +1884,8 @@ where ast::Stmt::Global(ast::StmtGlobal { range: _, names }) => { for name in names { let symbol_id = self.add_symbol(name.id.clone()); - let symbol_table = self.current_symbol_table(); - let symbol = symbol_table.symbol(symbol_id); + let symbol_table = self.current_place_table(); + let symbol = symbol_table.place_expr(symbol_id); if symbol.is_bound() || symbol.is_declared() || symbol.is_used() { self.report_semantic_error(SemanticSyntaxError { kind: SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { @@ -1942,9 +1906,9 @@ where } ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { for target in targets { - if let ast::Expr::Name(ast::ExprName { id, .. }) = target { - let symbol_id = self.add_symbol(id.clone()); - self.current_symbol_table().mark_symbol_used(symbol_id); + if let Ok(target) = PlaceExpr::try_from(target) { + let place_id = self.add_place(target); + self.current_place_table().mark_place_used(place_id); } } walk_stmt(self, stmt); @@ -1971,109 +1935,133 @@ where let node_key = NodeKey::from_node(expr); match expr { - ast::Expr::Name(ast::ExprName { id, ctx, .. }) => { - let (is_use, is_definition) = match (ctx, self.current_assignment()) { - (ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => { - // For augmented assignment, the target expression is also used. - (true, true) + ast::Expr::Name(ast::ExprName { ctx, .. }) + | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) + | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { + if let Ok(mut place_expr) = PlaceExpr::try_from(expr) { + if self.is_method_of_class().is_some() { + // We specifically mark attribute assignments to the first parameter of a method, + // i.e. typically `self` or `cls`. + let accessed_object_refers_to_first_parameter = self + .current_first_parameter_name + .is_some_and(|fst| place_expr.root_name().as_str() == fst); + + if accessed_object_refers_to_first_parameter && place_expr.is_member() { + place_expr.mark_instance_attribute(); + } } - (ast::ExprContext::Load, _) => (true, false), - (ast::ExprContext::Store, _) => (false, true), - (ast::ExprContext::Del, _) => (false, true), - (ast::ExprContext::Invalid, _) => (false, false), - }; - let symbol = self.add_symbol(id.clone()); - if is_use { - self.mark_symbol_used(symbol); - let use_id = self.current_ast_ids().record_use(expr); - self.current_use_def_map_mut() - .record_use(symbol, use_id, node_key); - } - - if is_definition { - match self.current_assignment() { - Some(CurrentAssignment::Assign { node, unpack }) => { - self.add_definition( - symbol, - AssignmentDefinitionNodeRef { - unpack, - value: &node.value, - target: expr, - }, - ); - } - Some(CurrentAssignment::AnnAssign(ann_assign)) => { - self.add_definition( - symbol, - AnnotatedAssignmentDefinitionNodeRef { - node: ann_assign, - annotation: &ann_assign.annotation, - value: ann_assign.value.as_deref(), - target: expr, - }, - ); - } - Some(CurrentAssignment::AugAssign(aug_assign)) => { - self.add_definition(symbol, aug_assign); + let (is_use, is_definition) = match (ctx, self.current_assignment()) { + (ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => { + // For augmented assignment, the target expression is also used. + (true, true) } - Some(CurrentAssignment::For { node, unpack }) => { - self.add_definition( - symbol, - ForStmtDefinitionNodeRef { - unpack, - iterable: &node.iter, - target: expr, - is_async: node.is_async, - }, - ); - } - Some(CurrentAssignment::Named(named)) => { - // TODO(dhruvmanila): If the current scope is a comprehension, then the - // named expression is implicitly nonlocal. This is yet to be - // implemented. - self.add_definition(symbol, named); - } - Some(CurrentAssignment::Comprehension { - unpack, - node, - first, - }) => { - self.add_definition( - symbol, - ComprehensionDefinitionNodeRef { - unpack, - iterable: &node.iter, - target: expr, - first, - is_async: node.is_async, - }, - ); - } - Some(CurrentAssignment::WithItem { - item, - is_async, - unpack, - }) => { - self.add_definition( - symbol, - WithItemDefinitionNodeRef { - unpack, - context_expr: &item.context_expr, - target: expr, - is_async, - }, - ); + (ast::ExprContext::Load, _) => (true, false), + (ast::ExprContext::Store, _) => (false, true), + (ast::ExprContext::Del, _) => (false, true), + (ast::ExprContext::Invalid, _) => (false, false), + }; + let place_id = self.add_place(place_expr); + + if is_use { + self.mark_place_used(place_id); + let use_id = self.current_ast_ids().record_use(expr); + self.current_use_def_map_mut() + .record_use(place_id, use_id, node_key); + } + + if is_definition { + match self.current_assignment() { + Some(CurrentAssignment::Assign { node, unpack }) => { + self.add_definition( + place_id, + AssignmentDefinitionNodeRef { + unpack, + value: &node.value, + target: expr, + }, + ); + } + Some(CurrentAssignment::AnnAssign(ann_assign)) => { + self.add_standalone_type_expression(&ann_assign.annotation); + self.add_definition( + place_id, + AnnotatedAssignmentDefinitionNodeRef { + node: ann_assign, + annotation: &ann_assign.annotation, + value: ann_assign.value.as_deref(), + target: expr, + }, + ); + } + Some(CurrentAssignment::AugAssign(aug_assign)) => { + self.add_definition(place_id, aug_assign); + } + Some(CurrentAssignment::For { node, unpack }) => { + self.add_definition( + place_id, + ForStmtDefinitionNodeRef { + unpack, + iterable: &node.iter, + target: expr, + is_async: node.is_async, + }, + ); + } + Some(CurrentAssignment::Named(named)) => { + // TODO(dhruvmanila): If the current scope is a comprehension, then the + // named expression is implicitly nonlocal. This is yet to be + // implemented. + self.add_definition(place_id, named); + } + Some(CurrentAssignment::Comprehension { + unpack, + node, + first, + }) => { + self.add_definition( + place_id, + ComprehensionDefinitionNodeRef { + unpack, + iterable: &node.iter, + target: expr, + first, + is_async: node.is_async, + }, + ); + } + Some(CurrentAssignment::WithItem { + item, + is_async, + unpack, + }) => { + self.add_definition( + place_id, + WithItemDefinitionNodeRef { + unpack, + context_expr: &item.context_expr, + target: expr, + is_async, + }, + ); + } + None => {} } - None => {} + } + + if let Some(unpack_position) = self + .current_assignment_mut() + .and_then(CurrentAssignment::unpack_position_mut) + { + *unpack_position = UnpackPosition::Other; } } - if let Some(unpack_position) = self - .current_assignment_mut() - .and_then(CurrentAssignment::unpack_position_mut) - { - *unpack_position = UnpackPosition::Other; + // Track reachability of attribute expressions to silence `unresolved-attribute` + // diagnostics in unreachable code. + if expr.is_attribute_expr() { + self.current_use_def_map_mut() + .record_node_reachability(node_key); } walk_expr(self, expr); @@ -2239,125 +2227,6 @@ where self.simplify_visibility_constraints(pre_op); } - ast::Expr::Attribute(ast::ExprAttribute { - value: object, - attr, - ctx, - range: _, - }) => { - if ctx.is_store() { - match self.current_assignment() { - Some(CurrentAssignment::Assign { node, unpack, .. }) => { - // SAFETY: `value` and `expr` belong to the `self.module` tree - #[expect(unsafe_code)] - let assignment = AssignmentDefinitionKind::new( - TargetKind::from(unpack), - unsafe { AstNodeRef::new(self.module.clone(), &node.value) }, - unsafe { AstNodeRef::new(self.module.clone(), expr) }, - ); - self.register_attribute_assignment( - object, - attr, - DefinitionKind::Assignment(assignment), - ); - } - Some(CurrentAssignment::AnnAssign(ann_assign)) => { - self.add_standalone_type_expression(&ann_assign.annotation); - // SAFETY: `annotation`, `value` and `expr` belong to the `self.module` tree - #[expect(unsafe_code)] - let assignment = AnnotatedAssignmentDefinitionKind::new( - unsafe { - AstNodeRef::new(self.module.clone(), &ann_assign.annotation) - }, - ann_assign.value.as_deref().map(|value| unsafe { - AstNodeRef::new(self.module.clone(), value) - }), - unsafe { AstNodeRef::new(self.module.clone(), expr) }, - ); - self.register_attribute_assignment( - object, - attr, - DefinitionKind::AnnotatedAssignment(assignment), - ); - } - Some(CurrentAssignment::For { node, unpack, .. }) => { - // // SAFETY: `iter` and `expr` belong to the `self.module` tree - #[expect(unsafe_code)] - let assignment = ForStmtDefinitionKind::new( - TargetKind::from(unpack), - unsafe { AstNodeRef::new(self.module.clone(), &node.iter) }, - unsafe { AstNodeRef::new(self.module.clone(), expr) }, - node.is_async, - ); - self.register_attribute_assignment( - object, - attr, - DefinitionKind::For(assignment), - ); - } - Some(CurrentAssignment::WithItem { - item, - unpack, - is_async, - .. - }) => { - // SAFETY: `context_expr` and `expr` belong to the `self.module` tree - #[expect(unsafe_code)] - let assignment = WithItemDefinitionKind::new( - TargetKind::from(unpack), - unsafe { AstNodeRef::new(self.module.clone(), &item.context_expr) }, - unsafe { AstNodeRef::new(self.module.clone(), expr) }, - is_async, - ); - self.register_attribute_assignment( - object, - attr, - DefinitionKind::WithItem(assignment), - ); - } - Some(CurrentAssignment::Comprehension { - unpack, - node, - first, - }) => { - // SAFETY: `iter` and `expr` belong to the `self.module` tree - #[expect(unsafe_code)] - let assignment = ComprehensionDefinitionKind { - target_kind: TargetKind::from(unpack), - iterable: unsafe { - AstNodeRef::new(self.module.clone(), &node.iter) - }, - target: unsafe { AstNodeRef::new(self.module.clone(), expr) }, - first, - is_async: node.is_async, - }; - // Temporarily move to the scope of the method to which the instance attribute is defined. - // SAFETY: `self.scope_stack` is not empty because the targets in comprehensions should always introduce a new scope. - let scope = self.scope_stack.pop().expect("The popped scope must be a comprehension, which must have a parent scope"); - self.register_attribute_assignment( - object, - attr, - DefinitionKind::Comprehension(assignment), - ); - self.scope_stack.push(scope); - } - Some(CurrentAssignment::AugAssign(_)) => { - // TODO: - } - Some(CurrentAssignment::Named(_)) => { - // A named expression whose target is an attribute is syntactically prohibited - } - None => {} - } - } - - // Track reachability of attribute expressions to silence `unresolved-attribute` - // diagnostics in unreachable code. - self.current_use_def_map_mut() - .record_node_reachability(node_key); - - walk_expr(self, expr); - } ast::Expr::StringLiteral(_) => { // Track reachability of string literals, as they could be a stringified annotation // with child expressions whose reachability we are interested in. diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index c8720d7cf77d03..3adbeb68c63059 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -8,16 +8,16 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::node_key::NodeKey; -use crate::semantic_index::symbol::{FileScopeId, ScopeId, ScopedSymbolId}; +use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId}; use crate::unpack::{Unpack, UnpackPosition}; -/// A definition of a symbol. +/// A definition of a place. /// /// ## ID stability /// The `Definition`'s ID is stable when the only field that change is its `kind` (AST node). /// -/// The `Definition` changes when the `file`, `scope`, or `symbol` change. This can be -/// because a new scope gets inserted before the `Definition` or a new symbol is inserted +/// The `Definition` changes when the `file`, `scope`, or `place` change. This can be +/// because a new scope gets inserted before the `Definition` or a new place is inserted /// before this `Definition`. However, the ID can be considered stable and it is okay to use /// `Definition` in cross-module` salsa queries or as a field on other salsa tracked structs. #[salsa::tracked(debug)] @@ -28,8 +28,8 @@ pub struct Definition<'db> { /// The scope in which the definition occurs. pub(crate) file_scope: FileScopeId, - /// The symbol defined. - pub(crate) symbol: ScopedSymbolId, + /// The place ID of the definition. + pub(crate) place: ScopedPlaceId, /// WARNING: Only access this field when doing type inference for the same /// file as where `Definition` is defined to avoid cross-file query dependencies. @@ -89,6 +89,39 @@ impl<'a, 'db> IntoIterator for &'a Definitions<'db> { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update)] +pub(crate) enum DefinitionState<'db> { + Defined(Definition<'db>), + /// Represents the implicit "unbound"/"undeclared" definition of every place. + Undefined, + /// Represents a definition that has been deleted. + /// This used when an attribute/subscript definition (such as `x.y = ...`, `x[0] = ...`) becomes obsolete due to a reassignment of the root place. + Deleted, +} + +impl<'db> DefinitionState<'db> { + pub(crate) fn is_defined_and(self, f: impl Fn(Definition<'db>) -> bool) -> bool { + matches!(self, DefinitionState::Defined(def) if f(def)) + } + + pub(crate) fn is_undefined_or(self, f: impl Fn(Definition<'db>) -> bool) -> bool { + matches!(self, DefinitionState::Undefined) + || matches!(self, DefinitionState::Defined(def) if f(def)) + } + + pub(crate) fn is_undefined(self) -> bool { + matches!(self, DefinitionState::Undefined) + } + + #[allow(unused)] + pub(crate) fn definition(self) -> Option> { + match self { + DefinitionState::Defined(def) => Some(def), + DefinitionState::Deleted | DefinitionState::Undefined => None, + } + } +} + #[derive(Copy, Clone, Debug)] pub(crate) enum DefinitionNodeRef<'a> { Import(ImportDefinitionNodeRef<'a>), @@ -232,7 +265,7 @@ pub(crate) struct ImportDefinitionNodeRef<'a> { #[derive(Copy, Clone, Debug)] pub(crate) struct StarImportDefinitionNodeRef<'a> { pub(crate) node: &'a ast::StmtImportFrom, - pub(crate) symbol_id: ScopedSymbolId, + pub(crate) place_id: ScopedPlaceId, } #[derive(Copy, Clone, Debug)] @@ -323,10 +356,10 @@ impl<'db> DefinitionNodeRef<'db> { is_reexported, }), DefinitionNodeRef::ImportStar(star_import) => { - let StarImportDefinitionNodeRef { node, symbol_id } = star_import; + let StarImportDefinitionNodeRef { node, place_id } = star_import; DefinitionKind::StarImport(StarImportDefinitionKind { node: unsafe { AstNodeRef::new(parsed, node) }, - symbol_id, + place_id, }) } DefinitionNodeRef::Function(function) => { @@ -456,7 +489,7 @@ impl<'db> DefinitionNodeRef<'db> { // INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list. - Self::ImportStar(StarImportDefinitionNodeRef { node, symbol_id: _ }) => node + Self::ImportStar(StarImportDefinitionNodeRef { node, place_id: _ }) => node .names .iter() .find(|alias| &alias.name == "*") @@ -517,7 +550,7 @@ pub(crate) enum DefinitionCategory { } impl DefinitionCategory { - /// True if this definition establishes a "declared type" for the symbol. + /// True if this definition establishes a "declared type" for the place. /// /// If so, any assignments reached by this definition are in error if they assign a value of a /// type not assignable to the declared type. @@ -530,7 +563,7 @@ impl DefinitionCategory { ) } - /// True if this definition assigns a value to the symbol. + /// True if this definition assigns a value to the place. /// /// False only for annotated assignments without a RHS. pub(crate) fn is_binding(self) -> bool { @@ -591,8 +624,8 @@ impl DefinitionKind<'_> { /// Returns the [`TextRange`] of the definition target. /// - /// A definition target would mainly be the node representing the symbol being defined i.e., - /// [`ast::ExprName`] or [`ast::Identifier`] but could also be other nodes. + /// A definition target would mainly be the node representing the place being defined i.e., + /// [`ast::ExprName`], [`ast::Identifier`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`] but could also be other nodes. pub(crate) fn target_range(&self) -> TextRange { match self { DefinitionKind::Import(import) => import.alias().range(), @@ -700,14 +733,15 @@ impl DefinitionKind<'_> { #[derive(Copy, Clone, Debug, PartialEq, Hash)] pub(crate) enum TargetKind<'db> { Sequence(UnpackPosition, Unpack<'db>), - NameOrAttribute, + /// Name, attribute, or subscript. + Single, } impl<'db> From)>> for TargetKind<'db> { fn from(value: Option<(UnpackPosition, Unpack<'db>)>) -> Self { match value { Some((unpack_position, unpack)) => TargetKind::Sequence(unpack_position, unpack), - None => TargetKind::NameOrAttribute, + None => TargetKind::Single, } } } @@ -715,7 +749,7 @@ impl<'db> From)>> for TargetKind<'db> { #[derive(Clone, Debug)] pub struct StarImportDefinitionKind { node: AstNodeRef, - symbol_id: ScopedSymbolId, + place_id: ScopedPlaceId, } impl StarImportDefinitionKind { @@ -737,8 +771,8 @@ impl StarImportDefinitionKind { ) } - pub(crate) fn symbol_id(&self) -> ScopedSymbolId { - self.symbol_id + pub(crate) fn place_id(&self) -> ScopedPlaceId { + self.place_id } } @@ -759,13 +793,18 @@ impl MatchPatternDefinitionKind { } } +/// Note that the elements of a comprehension can be in different scopes. +/// If the definition target of a comprehension is a name, it is in the comprehension's scope. +/// But if the target is an attribute or subscript, its definition is not in the comprehension's scope; +/// it is in the scope in which the root variable is bound. +/// TODO: currently we don't model this correctly and simply assume that it is in a scope outside the comprehension. #[derive(Clone, Debug)] pub struct ComprehensionDefinitionKind<'db> { - pub(super) target_kind: TargetKind<'db>, - pub(super) iterable: AstNodeRef, - pub(super) target: AstNodeRef, - pub(super) first: bool, - pub(super) is_async: bool, + target_kind: TargetKind<'db>, + iterable: AstNodeRef, + target: AstNodeRef, + first: bool, + is_async: bool, } impl<'db> ComprehensionDefinitionKind<'db> { @@ -840,18 +879,6 @@ pub struct AssignmentDefinitionKind<'db> { } impl<'db> AssignmentDefinitionKind<'db> { - pub(crate) fn new( - target_kind: TargetKind<'db>, - value: AstNodeRef, - target: AstNodeRef, - ) -> Self { - Self { - target_kind, - value, - target, - } - } - pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } @@ -873,18 +900,6 @@ pub struct AnnotatedAssignmentDefinitionKind { } impl AnnotatedAssignmentDefinitionKind { - pub(crate) fn new( - annotation: AstNodeRef, - value: Option>, - target: AstNodeRef, - ) -> Self { - Self { - annotation, - value, - target, - } - } - pub(crate) fn value(&self) -> Option<&ast::Expr> { self.value.as_deref() } @@ -907,20 +922,6 @@ pub struct WithItemDefinitionKind<'db> { } impl<'db> WithItemDefinitionKind<'db> { - pub(crate) fn new( - target_kind: TargetKind<'db>, - context_expr: AstNodeRef, - target: AstNodeRef, - is_async: bool, - ) -> Self { - Self { - target_kind, - context_expr, - target, - is_async, - } - } - pub(crate) fn context_expr(&self) -> &ast::Expr { self.context_expr.node() } @@ -947,20 +948,6 @@ pub struct ForStmtDefinitionKind<'db> { } impl<'db> ForStmtDefinitionKind<'db> { - pub(crate) fn new( - target_kind: TargetKind<'db>, - iterable: AstNodeRef, - target: AstNodeRef, - is_async: bool, - ) -> Self { - Self { - target_kind, - iterable, - target, - is_async, - } - } - pub(crate) fn iterable(&self) -> &ast::Expr { self.iterable.node() } @@ -1031,6 +1018,18 @@ impl From<&ast::ExprName> for DefinitionNodeKey { } } +impl From<&ast::ExprAttribute> for DefinitionNodeKey { + fn from(node: &ast::ExprAttribute) -> Self { + Self(NodeKey::from_node(node)) + } +} + +impl From<&ast::ExprSubscript> for DefinitionNodeKey { + fn from(node: &ast::ExprSubscript) -> Self { + Self(NodeKey::from_node(node)) + } +} + impl From<&ast::ExprNamed> for DefinitionNodeKey { fn from(node: &ast::ExprNamed) -> Self { Self(NodeKey::from_node(node)) diff --git a/crates/ty_python_semantic/src/semantic_index/expression.rs b/crates/ty_python_semantic/src/semantic_index/expression.rs index f3afd45f497e62..1c5178b244a878 100644 --- a/crates/ty_python_semantic/src/semantic_index/expression.rs +++ b/crates/ty_python_semantic/src/semantic_index/expression.rs @@ -1,6 +1,6 @@ use crate::ast_node_ref::AstNodeRef; use crate::db::Db; -use crate::semantic_index::symbol::{FileScopeId, ScopeId}; +use crate::semantic_index::place::{FileScopeId, ScopeId}; use ruff_db::files::File; use ruff_python_ast as ast; use salsa; diff --git a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs index 83bfb0d25dbf95..fa6280ead67cee 100644 --- a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs @@ -1,7 +1,7 @@ //! # Narrowing constraints //! //! When building a semantic index for a file, we associate each binding with a _narrowing -//! constraint_, which constrains the type of the binding's symbol. Note that a binding can be +//! constraint_, which constrains the type of the binding's place. Note that a binding can be //! associated with a different narrowing constraint at different points in a file. See the //! [`use_def`][crate::semantic_index::use_def] module for more details. //! @@ -34,7 +34,7 @@ use crate::semantic_index::predicate::ScopedPredicateId; /// A narrowing constraint associated with a live binding. /// -/// A constraint is a list of [`Predicate`]s that each constrain the type of the binding's symbol. +/// A constraint is a list of [`Predicate`]s that each constrain the type of the binding's place. /// /// [`Predicate`]: crate::semantic_index::predicate::Predicate pub(crate) type ScopedNarrowingConstraint = List; @@ -46,7 +46,7 @@ pub(crate) enum ConstraintKey { } /// One of the [`Predicate`]s in a narrowing constraint, which constraints the type of the -/// binding's symbol. +/// binding's place. /// /// Note that those [`Predicate`]s are stored in [their own per-scope /// arena][crate::semantic_index::predicate::Predicates], so internally we use a diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs new file mode 100644 index 00000000000000..4862c61b2ad169 --- /dev/null +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -0,0 +1,942 @@ +use std::convert::Infallible; +use std::hash::{Hash, Hasher}; +use std::ops::Range; + +use bitflags::bitflags; +use hashbrown::hash_map::RawEntryMut; +use ruff_db::files::File; +use ruff_db::parsed::ParsedModule; +use ruff_index::{IndexVec, newtype_index}; +use ruff_python_ast as ast; +use ruff_python_ast::name::Name; +use rustc_hash::FxHasher; +use smallvec::{SmallVec, smallvec}; + +use crate::Db; +use crate::ast_node_ref::AstNodeRef; +use crate::node_key::NodeKey; +use crate::semantic_index::visibility_constraints::ScopedVisibilityConstraintId; +use crate::semantic_index::{PlaceSet, SemanticIndex, semantic_index}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, salsa::Update)] +pub(crate) enum PlaceExprSubSegment { + /// A member access, e.g. `.y` in `x.y` + Member(ast::name::Name), + /// An integer-based index access, e.g. `[1]` in `x[1]` + IntSubscript(ast::Int), + /// A string-based index access, e.g. `["foo"]` in `x["foo"]` + StringSubscript(String), +} + +impl PlaceExprSubSegment { + pub(crate) fn as_member(&self) -> Option<&ast::name::Name> { + match self { + PlaceExprSubSegment::Member(name) => Some(name), + _ => None, + } + } +} + +/// An expression that can be the target of a `Definition`. +/// If you want to perform a comparison based on the equality of segments (without including +/// flags), use [`PlaceSegments`]. +#[derive(Eq, PartialEq, Debug)] +pub struct PlaceExpr { + root_name: Name, + sub_segments: SmallVec<[PlaceExprSubSegment; 1]>, + flags: PlaceFlags, +} + +impl std::fmt::Display for PlaceExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.root_name)?; + for segment in &self.sub_segments { + match segment { + PlaceExprSubSegment::Member(name) => write!(f, ".{name}")?, + PlaceExprSubSegment::IntSubscript(int) => write!(f, "[{int}]")?, + PlaceExprSubSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?, + } + } + Ok(()) + } +} + +impl TryFrom<&ast::name::Name> for PlaceExpr { + type Error = Infallible; + + fn try_from(name: &ast::name::Name) -> Result { + Ok(PlaceExpr::name(name.clone())) + } +} + +impl TryFrom for PlaceExpr { + type Error = Infallible; + + fn try_from(name: ast::name::Name) -> Result { + Ok(PlaceExpr::name(name)) + } +} + +impl TryFrom<&ast::ExprAttribute> for PlaceExpr { + type Error = (); + + fn try_from(attr: &ast::ExprAttribute) -> Result { + let mut place = PlaceExpr::try_from(&*attr.value)?; + place + .sub_segments + .push(PlaceExprSubSegment::Member(attr.attr.id.clone())); + Ok(place) + } +} + +impl TryFrom for PlaceExpr { + type Error = (); + + fn try_from(attr: ast::ExprAttribute) -> Result { + let mut place = PlaceExpr::try_from(&*attr.value)?; + place + .sub_segments + .push(PlaceExprSubSegment::Member(attr.attr.id)); + Ok(place) + } +} + +impl TryFrom<&ast::ExprSubscript> for PlaceExpr { + type Error = (); + + fn try_from(subscript: &ast::ExprSubscript) -> Result { + let mut place = PlaceExpr::try_from(&*subscript.value)?; + match &*subscript.slice { + ast::Expr::NumberLiteral(ast::ExprNumberLiteral { + value: ast::Number::Int(index), + .. + }) => { + place + .sub_segments + .push(PlaceExprSubSegment::IntSubscript(index.clone())); + } + ast::Expr::StringLiteral(string) => { + place + .sub_segments + .push(PlaceExprSubSegment::StringSubscript( + string.value.to_string(), + )); + } + _ => { + return Err(()); + } + } + Ok(place) + } +} + +impl TryFrom for PlaceExpr { + type Error = (); + + fn try_from(subscript: ast::ExprSubscript) -> Result { + PlaceExpr::try_from(&subscript) + } +} + +impl TryFrom<&ast::Expr> for PlaceExpr { + type Error = (); + + fn try_from(expr: &ast::Expr) -> Result { + match expr { + ast::Expr::Name(name) => Ok(PlaceExpr::name(name.id.clone())), + ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr), + ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript), + _ => Err(()), + } + } +} + +impl PlaceExpr { + pub(super) fn name(name: Name) -> Self { + Self { + root_name: name, + sub_segments: smallvec![], + flags: PlaceFlags::empty(), + } + } + + fn insert_flags(&mut self, flags: PlaceFlags) { + self.flags.insert(flags); + } + + pub(super) fn mark_instance_attribute(&mut self) { + self.flags.insert(PlaceFlags::IS_INSTANCE_ATTRIBUTE); + } + + pub(crate) fn root_name(&self) -> &Name { + &self.root_name + } + + pub(crate) fn sub_segments(&self) -> &[PlaceExprSubSegment] { + &self.sub_segments + } + + pub(crate) fn as_name(&self) -> Option<&Name> { + if self.is_name() { + Some(&self.root_name) + } else { + None + } + } + + /// Assumes that the place expression is a name. + #[track_caller] + pub(crate) fn expect_name(&self) -> &Name { + debug_assert_eq!(self.sub_segments.len(), 0); + &self.root_name + } + + /// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)? + pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool { + self.is_instance_attribute() + && self.sub_segments.len() == 1 + && self.sub_segments[0].as_member().unwrap().as_str() == name + } + + /// Is the place an instance attribute? + pub fn is_instance_attribute(&self) -> bool { + self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE) + } + + /// Is the place used in its containing scope? + pub fn is_used(&self) -> bool { + self.flags.contains(PlaceFlags::IS_USED) + } + + /// Is the place defined in its containing scope? + pub fn is_bound(&self) -> bool { + self.flags.contains(PlaceFlags::IS_BOUND) + } + + /// Is the place declared in its containing scope? + pub fn is_declared(&self) -> bool { + self.flags.contains(PlaceFlags::IS_DECLARED) + } + + /// Is the place just a name? + pub fn is_name(&self) -> bool { + self.sub_segments.is_empty() + } + + pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool { + self.is_name() && f(&self.root_name) + } + + /// Does the place expression have the form `.member`? + pub fn is_member(&self) -> bool { + self.sub_segments + .last() + .is_some_and(|last| last.as_member().is_some()) + } + + pub(crate) fn segments(&self) -> PlaceSegments { + PlaceSegments { + root_name: Some(&self.root_name), + sub_segments: &self.sub_segments, + } + } + + // TODO: Ideally this would iterate PlaceSegments instead of RootExprs, both to reduce + // allocation and to avoid having both flagged and non-flagged versions of PlaceExprs. + fn root_exprs(&self) -> RootExprs<'_> { + RootExprs { + expr: self, + len: self.sub_segments.len(), + } + } +} + +struct RootExprs<'e> { + expr: &'e PlaceExpr, + len: usize, +} + +impl Iterator for RootExprs<'_> { + type Item = PlaceExpr; + + fn next(&mut self) -> Option { + if self.len == 0 { + return None; + } + self.len -= 1; + Some(PlaceExpr { + root_name: self.expr.root_name.clone(), + sub_segments: self.expr.sub_segments[..self.len].iter().cloned().collect(), + flags: PlaceFlags::empty(), + }) + } +} + +bitflags! { + /// Flags that can be queried to obtain information about a place in a given scope. + /// + /// See the doc-comment at the top of [`super::use_def`] for explanations of what it + /// means for a place to be *bound* as opposed to *declared*. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct PlaceFlags: u8 { + const IS_USED = 1 << 0; + const IS_BOUND = 1 << 1; + const IS_DECLARED = 1 << 2; + /// TODO: This flag is not yet set by anything + const MARKED_GLOBAL = 1 << 3; + /// TODO: This flag is not yet set by anything + const MARKED_NONLOCAL = 1 << 4; + const IS_INSTANCE_ATTRIBUTE = 1 << 5; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PlaceSegment<'a> { + /// A first segment of a place expression (root name), e.g. `x` in `x.y.z[0]`. + Name(&'a ast::name::Name), + Member(&'a ast::name::Name), + IntSubscript(&'a ast::Int), + StringSubscript(&'a str), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PlaceSegments<'a> { + root_name: Option<&'a ast::name::Name>, + sub_segments: &'a [PlaceExprSubSegment], +} + +impl<'a> Iterator for PlaceSegments<'a> { + type Item = PlaceSegment<'a>; + + fn next(&mut self) -> Option { + if let Some(name) = self.root_name.take() { + return Some(PlaceSegment::Name(name)); + } + if self.sub_segments.is_empty() { + return None; + } + let segment = &self.sub_segments[0]; + self.sub_segments = &self.sub_segments[1..]; + Some(match segment { + PlaceExprSubSegment::Member(name) => PlaceSegment::Member(name), + PlaceExprSubSegment::IntSubscript(int) => PlaceSegment::IntSubscript(int), + PlaceExprSubSegment::StringSubscript(string) => PlaceSegment::StringSubscript(string), + }) + } +} + +/// ID that uniquely identifies a place in a file. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct FilePlaceId { + scope: FileScopeId, + scoped_place_id: ScopedPlaceId, +} + +impl FilePlaceId { + pub fn scope(self) -> FileScopeId { + self.scope + } + + pub(crate) fn scoped_place_id(self) -> ScopedPlaceId { + self.scoped_place_id + } +} + +impl From for ScopedPlaceId { + fn from(val: FilePlaceId) -> Self { + val.scoped_place_id() + } +} + +/// ID that uniquely identifies a place inside a [`Scope`]. +#[newtype_index] +#[derive(salsa::Update)] +pub struct ScopedPlaceId; + +/// A cross-module identifier of a scope that can be used as a salsa query parameter. +#[salsa::tracked(debug)] +pub struct ScopeId<'db> { + pub file: File, + + pub file_scope_id: FileScopeId, + + count: countme::Count>, +} + +impl<'db> ScopeId<'db> { + pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool { + self.node(db).scope_kind().is_function_like() + } + + pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool { + self.node(db).scope_kind().is_type_parameter() + } + + pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind { + self.scope(db).node() + } + + pub(crate) fn scope(self, db: &dyn Db) -> &Scope { + semantic_index(db, self.file(db)).scope(self.file_scope_id(db)) + } + + #[cfg(test)] + pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { + match self.node(db) { + NodeWithScopeKind::Module => "", + NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => { + class.name.as_str() + } + NodeWithScopeKind::Function(function) + | NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(), + NodeWithScopeKind::TypeAlias(type_alias) + | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias + .name + .as_name_expr() + .map(|name| name.id.as_str()) + .unwrap_or(""), + NodeWithScopeKind::Lambda(_) => "", + NodeWithScopeKind::ListComprehension(_) => "", + NodeWithScopeKind::SetComprehension(_) => "", + NodeWithScopeKind::DictComprehension(_) => "", + NodeWithScopeKind::GeneratorExpression(_) => "", + } + } +} + +/// ID that uniquely identifies a scope inside of a module. +#[newtype_index] +#[derive(salsa::Update)] +pub struct FileScopeId; + +impl FileScopeId { + /// Returns the scope id of the module-global scope. + pub fn global() -> Self { + FileScopeId::from_u32(0) + } + + pub fn is_global(self) -> bool { + self == FileScopeId::global() + } + + pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> { + let index = semantic_index(db, file); + index.scope_ids_by_scope[self] + } + + pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool { + index.generator_functions.contains(&self) + } +} + +#[derive(Debug, salsa::Update)] +pub struct Scope { + parent: Option, + node: NodeWithScopeKind, + descendants: Range, + reachability: ScopedVisibilityConstraintId, +} + +impl Scope { + pub(super) fn new( + parent: Option, + node: NodeWithScopeKind, + descendants: Range, + reachability: ScopedVisibilityConstraintId, + ) -> Self { + Scope { + parent, + node, + descendants, + reachability, + } + } + + pub fn parent(&self) -> Option { + self.parent + } + + pub fn node(&self) -> &NodeWithScopeKind { + &self.node + } + + pub fn kind(&self) -> ScopeKind { + self.node().scope_kind() + } + + pub fn descendants(&self) -> Range { + self.descendants.clone() + } + + pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) { + self.descendants = self.descendants.start..children_end; + } + + pub(crate) fn is_eager(&self) -> bool { + self.kind().is_eager() + } + + pub(crate) fn reachability(&self) -> ScopedVisibilityConstraintId { + self.reachability + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ScopeKind { + Module, + Annotation, + Class, + Function, + Lambda, + Comprehension, + TypeAlias, +} + +impl ScopeKind { + pub(crate) fn is_eager(self) -> bool { + match self { + ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => true, + ScopeKind::Annotation + | ScopeKind::Function + | ScopeKind::Lambda + | ScopeKind::TypeAlias => false, + } + } + + pub(crate) fn is_function_like(self) -> bool { + // Type parameter scopes behave like function scopes in terms of name resolution; CPython + // place table also uses the term "function-like" for these scopes. + matches!( + self, + ScopeKind::Annotation + | ScopeKind::Function + | ScopeKind::Lambda + | ScopeKind::TypeAlias + | ScopeKind::Comprehension + ) + } + + pub(crate) fn is_class(self) -> bool { + matches!(self, ScopeKind::Class) + } + + pub(crate) fn is_type_parameter(self) -> bool { + matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) + } +} + +/// [`PlaceExpr`] table for a specific [`Scope`]. +#[derive(Default, salsa::Update)] +pub struct PlaceTable { + /// The place expressions in this scope. + places: IndexVec, + + /// The set of places. + place_set: PlaceSet, +} + +impl PlaceTable { + fn shrink_to_fit(&mut self) { + self.places.shrink_to_fit(); + } + + pub(crate) fn place_expr(&self, place_id: impl Into) -> &PlaceExpr { + &self.places[place_id.into()] + } + + /// Iterate over the "root" expressions of the place (e.g. `x.y.z`, `x.y`, `x` for `x.y.z[0]`). + pub(crate) fn root_place_exprs( + &self, + place_expr: &PlaceExpr, + ) -> impl Iterator { + place_expr + .root_exprs() + .filter_map(|place_expr| self.place_by_expr(&place_expr)) + } + + #[expect(unused)] + pub(crate) fn place_ids(&self) -> impl Iterator { + self.places.indices() + } + + pub fn places(&self) -> impl Iterator { + self.places.iter() + } + + pub fn symbols(&self) -> impl Iterator { + self.places().filter(|place_expr| place_expr.is_name()) + } + + pub fn instance_attributes(&self) -> impl Iterator { + self.places() + .filter(|place_expr| place_expr.is_instance_attribute()) + } + + /// Returns the place named `name`. + #[allow(unused)] // used in tests + pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExpr> { + let id = self.place_id_by_name(name)?; + Some(self.place_expr(id)) + } + + /// Returns the flagged place by the unflagged place expression. + /// + /// TODO: Ideally this would take a [`PlaceSegments`] instead of [`PlaceExpr`], to avoid the + /// awkward distinction between "flagged" (canonical) and unflagged [`PlaceExpr`]; in that + /// world, we would only create [`PlaceExpr`] in semantic indexing; in type inference we'd + /// create [`PlaceSegments`] if we need to look up a [`PlaceExpr`]. The [`PlaceTable`] would + /// need to gain the ability to hash and look up by a [`PlaceSegments`]. + pub(crate) fn place_by_expr(&self, place_expr: &PlaceExpr) -> Option<&PlaceExpr> { + let id = self.place_id_by_expr(place_expr)?; + Some(self.place_expr(id)) + } + + /// Returns the [`ScopedPlaceId`] of the place named `name`. + pub(crate) fn place_id_by_name(&self, name: &str) -> Option { + let (id, ()) = self + .place_set + .raw_entry() + .from_hash(Self::hash_name(name), |id| { + self.place_expr(*id).as_name().map(Name::as_str) == Some(name) + })?; + + Some(*id) + } + + /// Returns the [`ScopedPlaceId`] of the place expression. + pub(crate) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option { + let (id, ()) = self + .place_set + .raw_entry() + .from_hash(Self::hash_place_expr(place_expr), |id| { + self.place_expr(*id).segments() == place_expr.segments() + })?; + + Some(*id) + } + + pub(crate) fn place_id_by_instance_attribute_name(&self, name: &str) -> Option { + self.places + .indices() + .find(|id| self.places[*id].is_instance_attribute_named(name)) + } + + fn hash_name(name: &str) -> u64 { + let mut hasher = FxHasher::default(); + name.hash(&mut hasher); + hasher.finish() + } + + fn hash_place_expr(place_expr: &PlaceExpr) -> u64 { + let mut hasher = FxHasher::default(); + place_expr.root_name().as_str().hash(&mut hasher); + for segment in &place_expr.sub_segments { + match segment { + PlaceExprSubSegment::Member(name) => name.hash(&mut hasher), + PlaceExprSubSegment::IntSubscript(int) => int.hash(&mut hasher), + PlaceExprSubSegment::StringSubscript(string) => string.hash(&mut hasher), + } + } + hasher.finish() + } +} + +impl PartialEq for PlaceTable { + fn eq(&self, other: &Self) -> bool { + // We don't need to compare the place_set because the place is already captured in `PlaceExpr`. + self.places == other.places + } +} + +impl Eq for PlaceTable {} + +impl std::fmt::Debug for PlaceTable { + /// Exclude the `place_set` field from the debug output. + /// It's very noisy and not useful for debugging. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PlaceTable") + .field(&self.places) + .finish_non_exhaustive() + } +} + +#[derive(Debug, Default)] +pub(super) struct PlaceTableBuilder { + table: PlaceTable, + + associated_place_ids: IndexVec>, +} + +impl PlaceTableBuilder { + pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedPlaceId, bool) { + let hash = PlaceTable::hash_name(&name); + let entry = self + .table + .place_set + .raw_entry_mut() + .from_hash(hash, |id| self.table.places[*id].as_name() == Some(&name)); + + match entry { + RawEntryMut::Occupied(entry) => (*entry.key(), false), + RawEntryMut::Vacant(entry) => { + let symbol = PlaceExpr::name(name); + + let id = self.table.places.push(symbol); + entry.insert_with_hasher(hash, id, (), |id| { + PlaceTable::hash_place_expr(&self.table.places[*id]) + }); + let new_id = self.associated_place_ids.push(vec![]); + debug_assert_eq!(new_id, id); + (id, true) + } + } + } + + pub(super) fn add_place(&mut self, place_expr: PlaceExpr) -> (ScopedPlaceId, bool) { + let hash = PlaceTable::hash_place_expr(&place_expr); + let entry = self.table.place_set.raw_entry_mut().from_hash(hash, |id| { + self.table.places[*id].segments() == place_expr.segments() + }); + + match entry { + RawEntryMut::Occupied(entry) => (*entry.key(), false), + RawEntryMut::Vacant(entry) => { + let id = self.table.places.push(place_expr); + entry.insert_with_hasher(hash, id, (), |id| { + PlaceTable::hash_place_expr(&self.table.places[*id]) + }); + let new_id = self.associated_place_ids.push(vec![]); + debug_assert_eq!(new_id, id); + for root in self.table.places[id].root_exprs() { + if let Some(root_id) = self.table.place_id_by_expr(&root) { + self.associated_place_ids[root_id].push(id); + } + } + (id, true) + } + } + } + + pub(super) fn mark_place_bound(&mut self, id: ScopedPlaceId) { + self.table.places[id].insert_flags(PlaceFlags::IS_BOUND); + } + + pub(super) fn mark_place_declared(&mut self, id: ScopedPlaceId) { + self.table.places[id].insert_flags(PlaceFlags::IS_DECLARED); + } + + pub(super) fn mark_place_used(&mut self, id: ScopedPlaceId) { + self.table.places[id].insert_flags(PlaceFlags::IS_USED); + } + + pub(super) fn places(&self) -> impl Iterator { + self.table.places() + } + + pub(super) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option { + self.table.place_id_by_expr(place_expr) + } + + pub(super) fn place_expr(&self, place_id: impl Into) -> &PlaceExpr { + self.table.place_expr(place_id) + } + + /// Returns the place IDs associated with the place (e.g. `x.y`, `x.y.z`, `x.y.z[0]` for `x`). + pub(super) fn associated_place_ids( + &self, + place: ScopedPlaceId, + ) -> impl Iterator { + self.associated_place_ids[place].iter().copied() + } + + pub(super) fn finish(mut self) -> PlaceTable { + self.table.shrink_to_fit(); + self.table + } +} + +/// Reference to a node that introduces a new scope. +#[derive(Copy, Clone, Debug)] +pub(crate) enum NodeWithScopeRef<'a> { + Module, + Class(&'a ast::StmtClassDef), + Function(&'a ast::StmtFunctionDef), + Lambda(&'a ast::ExprLambda), + FunctionTypeParameters(&'a ast::StmtFunctionDef), + ClassTypeParameters(&'a ast::StmtClassDef), + TypeAlias(&'a ast::StmtTypeAlias), + TypeAliasTypeParameters(&'a ast::StmtTypeAlias), + ListComprehension(&'a ast::ExprListComp), + SetComprehension(&'a ast::ExprSetComp), + DictComprehension(&'a ast::ExprDictComp), + GeneratorExpression(&'a ast::ExprGenerator), +} + +impl NodeWithScopeRef<'_> { + /// Converts the unowned reference to an owned [`NodeWithScopeKind`]. + /// + /// # Safety + /// The node wrapped by `self` must be a child of `module`. + #[expect(unsafe_code)] + pub(super) unsafe fn to_kind(self, module: ParsedModule) -> NodeWithScopeKind { + unsafe { + match self { + NodeWithScopeRef::Module => NodeWithScopeKind::Module, + NodeWithScopeRef::Class(class) => { + NodeWithScopeKind::Class(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::Function(function) => { + NodeWithScopeKind::Function(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::Lambda(lambda) => { + NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) + } + NodeWithScopeRef::FunctionTypeParameters(function) => { + NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::ClassTypeParameters(class) => { + NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::ListComprehension(comprehension) => { + NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::SetComprehension(comprehension) => { + NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::DictComprehension(comprehension) => { + NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::GeneratorExpression(generator) => { + NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) + } + } + } + } + + pub(crate) fn node_key(self) -> NodeWithScopeKey { + match self { + NodeWithScopeRef::Module => NodeWithScopeKey::Module, + NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)), + NodeWithScopeRef::Function(function) => { + NodeWithScopeKey::Function(NodeKey::from_node(function)) + } + NodeWithScopeRef::Lambda(lambda) => { + NodeWithScopeKey::Lambda(NodeKey::from_node(lambda)) + } + NodeWithScopeRef::FunctionTypeParameters(function) => { + NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function)) + } + NodeWithScopeRef::ClassTypeParameters(class) => { + NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class)) + } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias)) + } + NodeWithScopeRef::ListComprehension(comprehension) => { + NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::SetComprehension(comprehension) => { + NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::DictComprehension(comprehension) => { + NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::GeneratorExpression(generator) => { + NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator)) + } + } + } +} + +/// Node that introduces a new scope. +#[derive(Clone, Debug, salsa::Update)] +pub enum NodeWithScopeKind { + Module, + Class(AstNodeRef), + ClassTypeParameters(AstNodeRef), + Function(AstNodeRef), + FunctionTypeParameters(AstNodeRef), + TypeAliasTypeParameters(AstNodeRef), + TypeAlias(AstNodeRef), + Lambda(AstNodeRef), + ListComprehension(AstNodeRef), + SetComprehension(AstNodeRef), + DictComprehension(AstNodeRef), + GeneratorExpression(AstNodeRef), +} + +impl NodeWithScopeKind { + pub(crate) const fn scope_kind(&self) -> ScopeKind { + match self { + Self::Module => ScopeKind::Module, + Self::Class(_) => ScopeKind::Class, + Self::Function(_) => ScopeKind::Function, + Self::Lambda(_) => ScopeKind::Lambda, + Self::FunctionTypeParameters(_) + | Self::ClassTypeParameters(_) + | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, + Self::TypeAlias(_) => ScopeKind::TypeAlias, + Self::ListComprehension(_) + | Self::SetComprehension(_) + | Self::DictComprehension(_) + | Self::GeneratorExpression(_) => ScopeKind::Comprehension, + } + } + + pub fn expect_class(&self) -> &ast::StmtClassDef { + match self { + Self::Class(class) => class.node(), + _ => panic!("expected class"), + } + } + + pub(crate) const fn as_class(&self) -> Option<&ast::StmtClassDef> { + match self { + Self::Class(class) => Some(class.node()), + _ => None, + } + } + + pub fn expect_function(&self) -> &ast::StmtFunctionDef { + self.as_function().expect("expected function") + } + + pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias { + match self { + Self::TypeAlias(type_alias) => type_alias.node(), + _ => panic!("expected type alias"), + } + } + + pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> { + match self { + Self::Function(function) => Some(function.node()), + _ => None, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub(crate) enum NodeWithScopeKey { + Module, + Class(NodeKey), + ClassTypeParameters(NodeKey), + Function(NodeKey), + FunctionTypeParameters(NodeKey), + TypeAlias(NodeKey), + TypeAliasTypeParameters(NodeKey), + Lambda(NodeKey), + ListComprehension(NodeKey), + SetComprehension(NodeKey), + DictComprehension(NodeKey), + GeneratorExpression(NodeKey), +} diff --git a/crates/ty_python_semantic/src/semantic_index/predicate.rs b/crates/ty_python_semantic/src/semantic_index/predicate.rs index 86e3ba903a60e9..06b71396e70405 100644 --- a/crates/ty_python_semantic/src/semantic_index/predicate.rs +++ b/crates/ty_python_semantic/src/semantic_index/predicate.rs @@ -14,7 +14,7 @@ use ruff_python_ast::Singleton; use crate::db::Db; use crate::semantic_index::expression::Expression; use crate::semantic_index::global_scope; -use crate::semantic_index::symbol::{FileScopeId, ScopeId, ScopedSymbolId}; +use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId}; // A scoped identifier for each `Predicate` in a scope. #[newtype_index] @@ -144,13 +144,13 @@ pub(crate) struct StarImportPlaceholderPredicate<'db> { /// Each symbol imported by a `*` import has a separate predicate associated with it: /// this field identifies which symbol that is. /// - /// Note that a [`ScopedSymbolId`] is only meaningful if you also know the scope + /// Note that a [`ScopedPlaceId`] is only meaningful if you also know the scope /// it is relative to. For this specific struct, however, there's no need to store a /// separate field to hold the ID of the scope. `StarImportPredicate`s are only created /// for valid `*`-import definitions, and valid `*`-import definitions can only ever /// exist in the global scope; thus, we know that the `symbol_id` here will be relative /// to the global scope of the importing file. - pub(crate) symbol_id: ScopedSymbolId, + pub(crate) symbol_id: ScopedPlaceId, pub(crate) referenced_file: File, } diff --git a/crates/ty_python_semantic/src/semantic_index/symbol.rs b/crates/ty_python_semantic/src/semantic_index/symbol.rs deleted file mode 100644 index 4d25978bed26a6..00000000000000 --- a/crates/ty_python_semantic/src/semantic_index/symbol.rs +++ /dev/null @@ -1,589 +0,0 @@ -use std::hash::{Hash, Hasher}; -use std::ops::Range; - -use bitflags::bitflags; -use hashbrown::hash_map::RawEntryMut; -use ruff_db::files::File; -use ruff_db::parsed::ParsedModule; -use ruff_index::{IndexVec, newtype_index}; -use ruff_python_ast as ast; -use ruff_python_ast::name::Name; -use rustc_hash::FxHasher; - -use crate::Db; -use crate::ast_node_ref::AstNodeRef; -use crate::node_key::NodeKey; -use crate::semantic_index::visibility_constraints::ScopedVisibilityConstraintId; -use crate::semantic_index::{SemanticIndex, SymbolMap, semantic_index}; - -#[derive(Eq, PartialEq, Debug)] -pub struct Symbol { - name: Name, - flags: SymbolFlags, -} - -impl Symbol { - fn new(name: Name) -> Self { - Self { - name, - flags: SymbolFlags::empty(), - } - } - - fn insert_flags(&mut self, flags: SymbolFlags) { - self.flags.insert(flags); - } - - /// The symbol's name. - pub fn name(&self) -> &Name { - &self.name - } - - /// Is the symbol used in its containing scope? - pub fn is_used(&self) -> bool { - self.flags.contains(SymbolFlags::IS_USED) - } - - /// Is the symbol defined in its containing scope? - pub fn is_bound(&self) -> bool { - self.flags.contains(SymbolFlags::IS_BOUND) - } - - /// Is the symbol declared in its containing scope? - pub fn is_declared(&self) -> bool { - self.flags.contains(SymbolFlags::IS_DECLARED) - } -} - -bitflags! { - /// Flags that can be queried to obtain information about a symbol in a given scope. - /// - /// See the doc-comment at the top of [`super::use_def`] for explanations of what it - /// means for a symbol to be *bound* as opposed to *declared*. - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - struct SymbolFlags: u8 { - const IS_USED = 1 << 0; - const IS_BOUND = 1 << 1; - const IS_DECLARED = 1 << 2; - /// TODO: This flag is not yet set by anything - const MARKED_GLOBAL = 1 << 3; - /// TODO: This flag is not yet set by anything - const MARKED_NONLOCAL = 1 << 4; - } -} - -/// ID that uniquely identifies a symbol in a file. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct FileSymbolId { - scope: FileScopeId, - scoped_symbol_id: ScopedSymbolId, -} - -impl FileSymbolId { - pub fn scope(self) -> FileScopeId { - self.scope - } - - pub(crate) fn scoped_symbol_id(self) -> ScopedSymbolId { - self.scoped_symbol_id - } -} - -impl From for ScopedSymbolId { - fn from(val: FileSymbolId) -> Self { - val.scoped_symbol_id() - } -} - -/// Symbol ID that uniquely identifies a symbol inside a [`Scope`]. -#[newtype_index] -#[derive(salsa::Update)] -pub struct ScopedSymbolId; - -/// A cross-module identifier of a scope that can be used as a salsa query parameter. -#[salsa::tracked(debug)] -pub struct ScopeId<'db> { - pub file: File, - - pub file_scope_id: FileScopeId, - - count: countme::Count>, -} - -impl<'db> ScopeId<'db> { - pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool { - self.node(db).scope_kind().is_function_like() - } - - pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool { - self.node(db).scope_kind().is_type_parameter() - } - - pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind { - self.scope(db).node() - } - - pub(crate) fn scope(self, db: &dyn Db) -> &Scope { - semantic_index(db, self.file(db)).scope(self.file_scope_id(db)) - } - - #[cfg(test)] - pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { - match self.node(db) { - NodeWithScopeKind::Module => "", - NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => { - class.name.as_str() - } - NodeWithScopeKind::Function(function) - | NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(), - NodeWithScopeKind::TypeAlias(type_alias) - | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias - .name - .as_name_expr() - .map(|name| name.id.as_str()) - .unwrap_or(""), - NodeWithScopeKind::Lambda(_) => "", - NodeWithScopeKind::ListComprehension(_) => "", - NodeWithScopeKind::SetComprehension(_) => "", - NodeWithScopeKind::DictComprehension(_) => "", - NodeWithScopeKind::GeneratorExpression(_) => "", - } - } -} - -/// ID that uniquely identifies a scope inside of a module. -#[newtype_index] -#[derive(salsa::Update)] -pub struct FileScopeId; - -impl FileScopeId { - /// Returns the scope id of the module-global scope. - pub fn global() -> Self { - FileScopeId::from_u32(0) - } - - pub fn is_global(self) -> bool { - self == FileScopeId::global() - } - - pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> { - let index = semantic_index(db, file); - index.scope_ids_by_scope[self] - } - - pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool { - index.generator_functions.contains(&self) - } -} - -#[derive(Debug, salsa::Update)] -pub struct Scope { - parent: Option, - node: NodeWithScopeKind, - descendants: Range, - reachability: ScopedVisibilityConstraintId, -} - -impl Scope { - pub(super) fn new( - parent: Option, - node: NodeWithScopeKind, - descendants: Range, - reachability: ScopedVisibilityConstraintId, - ) -> Self { - Scope { - parent, - node, - descendants, - reachability, - } - } - - pub fn parent(&self) -> Option { - self.parent - } - - pub fn node(&self) -> &NodeWithScopeKind { - &self.node - } - - pub fn kind(&self) -> ScopeKind { - self.node().scope_kind() - } - - pub fn descendants(&self) -> Range { - self.descendants.clone() - } - - pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) { - self.descendants = self.descendants.start..children_end; - } - - pub(crate) fn is_eager(&self) -> bool { - self.kind().is_eager() - } - - pub(crate) fn reachability(&self) -> ScopedVisibilityConstraintId { - self.reachability - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ScopeKind { - Module, - Annotation, - Class, - Function, - Lambda, - Comprehension, - TypeAlias, -} - -impl ScopeKind { - pub(crate) fn is_eager(self) -> bool { - match self { - ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => true, - ScopeKind::Annotation - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::TypeAlias => false, - } - } - - pub(crate) fn is_function_like(self) -> bool { - // Type parameter scopes behave like function scopes in terms of name resolution; CPython - // symbol table also uses the term "function-like" for these scopes. - matches!( - self, - ScopeKind::Annotation - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::TypeAlias - | ScopeKind::Comprehension - ) - } - - pub(crate) fn is_class(self) -> bool { - matches!(self, ScopeKind::Class) - } - - pub(crate) fn is_type_parameter(self) -> bool { - matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) - } -} - -/// Symbol table for a specific [`Scope`]. -#[derive(Default, salsa::Update)] -pub struct SymbolTable { - /// The symbols in this scope. - symbols: IndexVec, - - /// The symbols indexed by name. - symbols_by_name: SymbolMap, -} - -impl SymbolTable { - fn shrink_to_fit(&mut self) { - self.symbols.shrink_to_fit(); - } - - pub(crate) fn symbol(&self, symbol_id: impl Into) -> &Symbol { - &self.symbols[symbol_id.into()] - } - - #[expect(unused)] - pub(crate) fn symbol_ids(&self) -> impl Iterator { - self.symbols.indices() - } - - pub fn symbols(&self) -> impl Iterator { - self.symbols.iter() - } - - /// Returns the symbol named `name`. - pub(crate) fn symbol_by_name(&self, name: &str) -> Option<&Symbol> { - let id = self.symbol_id_by_name(name)?; - Some(self.symbol(id)) - } - - /// Returns the [`ScopedSymbolId`] of the symbol named `name`. - pub(crate) fn symbol_id_by_name(&self, name: &str) -> Option { - let (id, ()) = self - .symbols_by_name - .raw_entry() - .from_hash(Self::hash_name(name), |id| { - self.symbol(*id).name().as_str() == name - })?; - - Some(*id) - } - - fn hash_name(name: &str) -> u64 { - let mut hasher = FxHasher::default(); - name.hash(&mut hasher); - hasher.finish() - } -} - -impl PartialEq for SymbolTable { - fn eq(&self, other: &Self) -> bool { - // We don't need to compare the symbols_by_name because the name is already captured in `Symbol`. - self.symbols == other.symbols - } -} - -impl Eq for SymbolTable {} - -impl std::fmt::Debug for SymbolTable { - /// Exclude the `symbols_by_name` field from the debug output. - /// It's very noisy and not useful for debugging. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SymbolTable") - .field(&self.symbols) - .finish_non_exhaustive() - } -} - -#[derive(Debug, Default)] -pub(super) struct SymbolTableBuilder { - table: SymbolTable, -} - -impl SymbolTableBuilder { - pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedSymbolId, bool) { - let hash = SymbolTable::hash_name(&name); - let entry = self - .table - .symbols_by_name - .raw_entry_mut() - .from_hash(hash, |id| self.table.symbols[*id].name() == &name); - - match entry { - RawEntryMut::Occupied(entry) => (*entry.key(), false), - RawEntryMut::Vacant(entry) => { - let symbol = Symbol::new(name); - - let id = self.table.symbols.push(symbol); - entry.insert_with_hasher(hash, id, (), |id| { - SymbolTable::hash_name(self.table.symbols[*id].name().as_str()) - }); - (id, true) - } - } - } - - pub(super) fn mark_symbol_bound(&mut self, id: ScopedSymbolId) { - self.table.symbols[id].insert_flags(SymbolFlags::IS_BOUND); - } - - pub(super) fn mark_symbol_declared(&mut self, id: ScopedSymbolId) { - self.table.symbols[id].insert_flags(SymbolFlags::IS_DECLARED); - } - - pub(super) fn mark_symbol_used(&mut self, id: ScopedSymbolId) { - self.table.symbols[id].insert_flags(SymbolFlags::IS_USED); - } - - pub(super) fn symbols(&self) -> impl Iterator { - self.table.symbols() - } - - pub(super) fn symbol_id_by_name(&self, name: &str) -> Option { - self.table.symbol_id_by_name(name) - } - - pub(super) fn symbol(&self, symbol_id: impl Into) -> &Symbol { - self.table.symbol(symbol_id) - } - - pub(super) fn finish(mut self) -> SymbolTable { - self.table.shrink_to_fit(); - self.table - } -} - -/// Reference to a node that introduces a new scope. -#[derive(Copy, Clone, Debug)] -pub(crate) enum NodeWithScopeRef<'a> { - Module, - Class(&'a ast::StmtClassDef), - Function(&'a ast::StmtFunctionDef), - Lambda(&'a ast::ExprLambda), - FunctionTypeParameters(&'a ast::StmtFunctionDef), - ClassTypeParameters(&'a ast::StmtClassDef), - TypeAlias(&'a ast::StmtTypeAlias), - TypeAliasTypeParameters(&'a ast::StmtTypeAlias), - ListComprehension(&'a ast::ExprListComp), - SetComprehension(&'a ast::ExprSetComp), - DictComprehension(&'a ast::ExprDictComp), - GeneratorExpression(&'a ast::ExprGenerator), -} - -impl NodeWithScopeRef<'_> { - /// Converts the unowned reference to an owned [`NodeWithScopeKind`]. - /// - /// # Safety - /// The node wrapped by `self` must be a child of `module`. - #[expect(unsafe_code)] - pub(super) unsafe fn to_kind(self, module: ParsedModule) -> NodeWithScopeKind { - unsafe { - match self { - NodeWithScopeRef::Module => NodeWithScopeKind::Module, - NodeWithScopeRef::Class(class) => { - NodeWithScopeKind::Class(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::Function(function) => { - NodeWithScopeKind::Function(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::TypeAlias(type_alias) => { - NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { - NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::Lambda(lambda) => { - NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) - } - NodeWithScopeRef::FunctionTypeParameters(function) => { - NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::ClassTypeParameters(class) => { - NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::ListComprehension(comprehension) => { - NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::SetComprehension(comprehension) => { - NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::DictComprehension(comprehension) => { - NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::GeneratorExpression(generator) => { - NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) - } - } - } - } - - pub(crate) fn node_key(self) -> NodeWithScopeKey { - match self { - NodeWithScopeRef::Module => NodeWithScopeKey::Module, - NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)), - NodeWithScopeRef::Function(function) => { - NodeWithScopeKey::Function(NodeKey::from_node(function)) - } - NodeWithScopeRef::Lambda(lambda) => { - NodeWithScopeKey::Lambda(NodeKey::from_node(lambda)) - } - NodeWithScopeRef::FunctionTypeParameters(function) => { - NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function)) - } - NodeWithScopeRef::ClassTypeParameters(class) => { - NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class)) - } - NodeWithScopeRef::TypeAlias(type_alias) => { - NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias)) - } - NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { - NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias)) - } - NodeWithScopeRef::ListComprehension(comprehension) => { - NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::SetComprehension(comprehension) => { - NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::DictComprehension(comprehension) => { - NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::GeneratorExpression(generator) => { - NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator)) - } - } - } -} - -/// Node that introduces a new scope. -#[derive(Clone, Debug, salsa::Update)] -pub enum NodeWithScopeKind { - Module, - Class(AstNodeRef), - ClassTypeParameters(AstNodeRef), - Function(AstNodeRef), - FunctionTypeParameters(AstNodeRef), - TypeAliasTypeParameters(AstNodeRef), - TypeAlias(AstNodeRef), - Lambda(AstNodeRef), - ListComprehension(AstNodeRef), - SetComprehension(AstNodeRef), - DictComprehension(AstNodeRef), - GeneratorExpression(AstNodeRef), -} - -impl NodeWithScopeKind { - pub(crate) const fn scope_kind(&self) -> ScopeKind { - match self { - Self::Module => ScopeKind::Module, - Self::Class(_) => ScopeKind::Class, - Self::Function(_) => ScopeKind::Function, - Self::Lambda(_) => ScopeKind::Lambda, - Self::FunctionTypeParameters(_) - | Self::ClassTypeParameters(_) - | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, - Self::TypeAlias(_) => ScopeKind::TypeAlias, - Self::ListComprehension(_) - | Self::SetComprehension(_) - | Self::DictComprehension(_) - | Self::GeneratorExpression(_) => ScopeKind::Comprehension, - } - } - - pub fn expect_class(&self) -> &ast::StmtClassDef { - match self { - Self::Class(class) => class.node(), - _ => panic!("expected class"), - } - } - - pub(crate) const fn as_class(&self) -> Option<&ast::StmtClassDef> { - match self { - Self::Class(class) => Some(class.node()), - _ => None, - } - } - - pub fn expect_function(&self) -> &ast::StmtFunctionDef { - self.as_function().expect("expected function") - } - - pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias { - match self { - Self::TypeAlias(type_alias) => type_alias.node(), - _ => panic!("expected type alias"), - } - } - - pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> { - match self { - Self::Function(function) => Some(function.node()), - _ => None, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) enum NodeWithScopeKey { - Module, - Class(NodeKey), - ClassTypeParameters(NodeKey), - Function(NodeKey), - FunctionTypeParameters(NodeKey), - TypeAlias(NodeKey), - TypeAliasTypeParameters(NodeKey), - Lambda(NodeKey), - ListComprehension(NodeKey), - SetComprehension(NodeKey), - DictComprehension(NodeKey), - GeneratorExpression(NodeKey), -} diff --git a/crates/ty_python_semantic/src/semantic_index/use_def.rs b/crates/ty_python_semantic/src/semantic_index/use_def.rs index 7605843db4e20f..39647ac7faa164 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def.rs @@ -1,12 +1,20 @@ //! First, some terminology: //! -//! * A "binding" gives a new value to a variable. This includes many different Python statements +//! * A "place" is semantically a location where a value can be read or written, and syntactically, +//! an expression that can be the target of an assignment, e.g. `x`, `x[0]`, `x.y`. (The term is +//! borrowed from Rust). In Python syntax, an expression like `f().x` is also allowed as the +//! target so it can be called a place, but we do not record declarations / bindings like `f().x: +//! int`, `f().x = ...`. Type checking itself can be done by recording only assignments to names, +//! but in order to perform type narrowing by attribute/subscript assignments, they must also be +//! recorded. +//! +//! * A "binding" gives a new value to a place. This includes many different Python statements //! (assignment statements of course, but also imports, `def` and `class` statements, `as` //! clauses in `with` and `except` statements, match patterns, and others) and even one //! expression kind (named expressions). It notably does not include annotated assignment -//! statements without a right-hand side value; these do not assign any new value to the -//! variable. We consider function parameters to be bindings as well, since (from the perspective -//! of the function's internal scope), a function parameter begins the scope bound to a value. +//! statements without a right-hand side value; these do not assign any new value to the place. +//! We consider function parameters to be bindings as well, since (from the perspective of the +//! function's internal scope), a function parameter begins the scope bound to a value. //! //! * A "declaration" establishes an upper bound type for the values that a variable may be //! permitted to take on. Annotated assignment statements (with or without an RHS value) are @@ -67,12 +75,12 @@ //! Path(path)`, with the explicit `: Path` annotation, is permitted. //! //! The general rule is that whatever declaration(s) can reach a given binding determine the -//! validity of that binding. If there is a path in which the symbol is not declared, that is a +//! validity of that binding. If there is a path in which the place is not declared, that is a //! declaration of `Unknown`. If multiple declarations can reach a binding, we union them, but by //! default we also issue a type error, since this implicit union of declared types may hide an //! error. //! -//! To support type inference, we build a map from each use of a symbol to the bindings live at +//! To support type inference, we build a map from each use of a place to the bindings live at //! that use, and the type narrowing constraints that apply to each binding. //! //! Let's take this code sample: @@ -103,12 +111,12 @@ //! bindings and infer a type of `Literal[3, 4]` -- the union of `Literal[3]` and `Literal[4]` -- //! for the second use of `x`. //! -//! So that's one question our use-def map needs to answer: given a specific use of a symbol, which +//! So that's one question our use-def map needs to answer: given a specific use of a place, which //! binding(s) can reach that use. In [`AstIds`](crate::semantic_index::ast_ids::AstIds) we number -//! all uses (that means a `Name` node with `Load` context) so we have a `ScopedUseId` to -//! efficiently represent each use. +//! all uses (that means a `Name`/`ExprAttribute`/`ExprSubscript` node with `Load` context) +//! so we have a `ScopedUseId` to efficiently represent each use. //! -//! We also need to know, for a given definition of a symbol, what type narrowing constraints apply +//! We also need to know, for a given definition of a place, what type narrowing constraints apply //! to it. For instance, in this code sample: //! //! ```python @@ -122,70 +130,70 @@ //! can rule out the possibility that `x` is `None` here, which should give us the type //! `Literal[1]` for this use. //! -//! For declared types, we need to be able to answer the question "given a binding to a symbol, -//! which declarations of that symbol can reach the binding?" This allows us to emit a diagnostic +//! For declared types, we need to be able to answer the question "given a binding to a place, +//! which declarations of that place can reach the binding?" This allows us to emit a diagnostic //! if the binding is attempting to bind a value of a type that is not assignable to the declared -//! type for that symbol, at that point in control flow. +//! type for that place, at that point in control flow. //! -//! We also need to know, given a declaration of a symbol, what the inferred type of that symbol is +//! We also need to know, given a declaration of a place, what the inferred type of that place is //! at that point. This allows us to emit a diagnostic in a case like `x = "foo"; x: int`. The //! binding `x = "foo"` occurs before the declaration `x: int`, so according to our //! control-flow-sensitive interpretation of declarations, the assignment is not an error. But the //! declaration is an error, since it would violate the "inferred type must be assignable to //! declared type" rule. //! -//! Another case we need to handle is when a symbol is referenced from a different scope (for -//! example, an import or a nonlocal reference). We call this "public" use of a symbol. For public -//! use of a symbol, we prefer the declared type, if there are any declarations of that symbol; if +//! Another case we need to handle is when a place is referenced from a different scope (for +//! example, an import or a nonlocal reference). We call this "public" use of a place. For public +//! use of a place, we prefer the declared type, if there are any declarations of that place; if //! not, we fall back to the inferred type. So we also need to know which declarations and bindings //! can reach the end of the scope. //! -//! Technically, public use of a symbol could occur from any point in control flow of the scope -//! where the symbol is defined (via inline imports and import cycles, in the case of an import, or -//! via a function call partway through the local scope that ends up using a symbol from the scope +//! Technically, public use of a place could occur from any point in control flow of the scope +//! where the place is defined (via inline imports and import cycles, in the case of an import, or +//! via a function call partway through the local scope that ends up using a place from the scope //! via a global or nonlocal reference.) But modeling this fully accurately requires whole-program -//! analysis that isn't tractable for an efficient analysis, since it means a given symbol could +//! analysis that isn't tractable for an efficient analysis, since it means a given place could //! have a different type every place it's referenced throughout the program, depending on the //! shape of arbitrarily-sized call/import graphs. So we follow other Python type checkers in //! making the simplifying assumption that usually the scope will finish execution before its -//! symbols are made visible to other scopes; for instance, most imports will import from a +//! places are made visible to other scopes; for instance, most imports will import from a //! complete module, not a partially-executed module. (We may want to get a little smarter than //! this in the future for some closures, but for now this is where we start.) //! //! The data structure we build to answer these questions is the `UseDefMap`. It has a -//! `bindings_by_use` vector of [`SymbolBindings`] indexed by [`ScopedUseId`], a -//! `declarations_by_binding` vector of [`SymbolDeclarations`] indexed by [`ScopedDefinitionId`], a -//! `bindings_by_declaration` vector of [`SymbolBindings`] indexed by [`ScopedDefinitionId`], and -//! `public_bindings` and `public_definitions` vectors indexed by [`ScopedSymbolId`]. The values in +//! `bindings_by_use` vector of [`Bindings`] indexed by [`ScopedUseId`], a +//! `declarations_by_binding` vector of [`Declarations`] indexed by [`ScopedDefinitionId`], a +//! `bindings_by_declaration` vector of [`Bindings`] indexed by [`ScopedDefinitionId`], and +//! `public_bindings` and `public_definitions` vectors indexed by [`ScopedPlaceId`]. The values in //! each of these vectors are (in principle) a list of live bindings at that use/definition, or at -//! the end of the scope for that symbol, with a list of the dominating constraints for each +//! the end of the scope for that place, with a list of the dominating constraints for each //! binding. //! //! In order to avoid vectors-of-vectors-of-vectors and all the allocations that would entail, we //! don't actually store these "list of visible definitions" as a vector of [`Definition`]. -//! Instead, [`SymbolBindings`] and [`SymbolDeclarations`] are structs which use bit-sets to track +//! Instead, [`Bindings`] and [`Declarations`] are structs which use bit-sets to track //! definitions (and constraints, in the case of bindings) in terms of [`ScopedDefinitionId`] and //! [`ScopedPredicateId`], which are indices into the `all_definitions` and `predicates` //! indexvecs in the [`UseDefMap`]. //! -//! There is another special kind of possible "definition" for a symbol: there might be a path from -//! the scope entry to a given use in which the symbol is never bound. We model this with a special -//! "unbound" definition (a `None` entry at the start of the `all_definitions` vector). If that -//! sentinel definition is present in the live bindings at a given use, it means that there is a -//! possible path through control flow in which that symbol is unbound. Similarly, if that sentinel -//! is present in the live declarations, it means that the symbol is (possibly) undeclared. +//! There is another special kind of possible "definition" for a place: there might be a path from +//! the scope entry to a given use in which the place is never bound. We model this with a special +//! "unbound/undeclared" definition (a [`DefinitionState::Undefined`] entry at the start of the +//! `all_definitions` vector). If that sentinel definition is present in the live bindings at a +//! given use, it means that there is a possible path through control flow in which that place is +//! unbound. Similarly, if that sentinel is present in the live declarations, it means that the +//! place is (possibly) undeclared. //! //! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and //! constraint as they are encountered by the //! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder) AST visit. For -//! each symbol, the builder tracks the `SymbolState` (`SymbolBindings` and `SymbolDeclarations`) -//! for that symbol. When we hit a use or definition of a symbol, we record the necessary parts of -//! the current state for that symbol that we need for that use or definition. When we reach the -//! end of the scope, it records the state for each symbol as the public definitions of that -//! symbol. +//! each place, the builder tracks the `PlaceState` (`Bindings` and `Declarations`) for that place. +//! When we hit a use or definition of a place, we record the necessary parts of the current state +//! for that place that we need for that use or definition. When we reach the end of the scope, it +//! records the state for each place as the public definitions of that place. //! //! Let's walk through the above example. Initially we do not have any record of `x`. When we add -//! the new symbol (before we process the first binding), we create a new undefined `SymbolState` +//! the new place (before we process the first binding), we create a new undefined `PlaceState` //! which has a single live binding (the "unbound" definition) and a single live declaration (the //! "undeclared" definition). When we see `x = 1`, we record that as the sole live binding of `x`. //! The "unbound" binding is no longer visible. Then we see `x = 2`, and we replace `x = 1` as the @@ -193,11 +201,11 @@ //! of `x` are just the `x = 2` definition. //! //! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will -//! happen regardless. Then we take a pre-branch snapshot of the current state for all symbols, +//! happen regardless. Then we take a pre-branch snapshot of the current state for all places, //! which we'll need later. Then we record `flag` as a possible constraint on the current binding //! (`x = 2`), and go ahead and visit the `if` body. When we see `x = 3`, it replaces `x = 2` //! (constrained by `flag`) as the sole live binding of `x`. At the end of the `if` body, we take -//! another snapshot of the current symbol state; we'll call this the post-if-body snapshot. +//! another snapshot of the current place state; we'll call this the post-if-body snapshot. //! //! Now we need to visit the `else` clause. The conditions when entering the `else` clause should //! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test @@ -247,7 +255,7 @@ //! `__bool__` method of `test` returns type `bool`, we can see both bindings. //! //! Note that we also record visibility constraints for the start of the scope. This is important -//! to determine if a symbol is definitely bound, possibly unbound, or definitely unbound. In the +//! to determine if a place is definitely bound, possibly unbound, or definitely unbound. In the //! example above, The `y = ` binding is constrained by `~test`, so `y` would only be //! definitely-bound if `test` is always truthy. //! @@ -259,34 +267,34 @@ use ruff_index::{IndexVec, newtype_index}; use rustc_hash::FxHashMap; -use self::symbol_state::{ - EagerSnapshot, LiveBindingsIterator, LiveDeclaration, LiveDeclarationsIterator, - ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState, +use self::place_state::{ + Bindings, Declarations, EagerSnapshot, LiveBindingsIterator, LiveDeclaration, + LiveDeclarationsIterator, PlaceState, ScopedDefinitionId, }; use crate::node_key::NodeKey; use crate::semantic_index::EagerSnapshotResult; use crate::semantic_index::ast_ids::ScopedUseId; -use crate::semantic_index::definition::Definition; +use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::narrowing_constraints::{ ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator, }; +use crate::semantic_index::place::{FileScopeId, PlaceExpr, ScopeKind, ScopedPlaceId}; use crate::semantic_index::predicate::{ Predicate, Predicates, PredicatesBuilder, ScopedPredicateId, StarImportPlaceholderPredicate, }; -use crate::semantic_index::symbol::{FileScopeId, ScopeKind, ScopedSymbolId}; use crate::semantic_index::visibility_constraints::{ ScopedVisibilityConstraintId, VisibilityConstraints, VisibilityConstraintsBuilder, }; use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint}; -mod symbol_state; +mod place_state; /// Applicable definitions and constraints for every use of a name. #[derive(Debug, PartialEq, Eq, salsa::Update)] pub(crate) struct UseDefMap<'db> { - /// Array of [`Definition`] in this scope. Only the first entry should be `None`; - /// this represents the implicit "unbound"/"undeclared" definition of every symbol. - all_definitions: IndexVec>>, + /// Array of [`Definition`] in this scope. Only the first entry should be [`DefinitionState::Undefined`]; + /// this represents the implicit "unbound"/"undeclared" definition of every place. + all_definitions: IndexVec>, /// Array of predicates in this scope. predicates: Predicates<'db>, @@ -297,34 +305,31 @@ pub(crate) struct UseDefMap<'db> { /// Array of visibility constraints in this scope. visibility_constraints: VisibilityConstraints, - /// [`SymbolBindings`] reaching a [`ScopedUseId`]. - bindings_by_use: IndexVec, + /// [`Bindings`] reaching a [`ScopedUseId`]. + bindings_by_use: IndexVec, /// Tracks whether or not a given AST node is reachable from the start of the scope. node_reachability: FxHashMap, /// If the definition is a binding (only) -- `x = 1` for example -- then we need - /// [`SymbolDeclarations`] to know whether this binding is permitted by the live declarations. + /// [`Declarations`] to know whether this binding is permitted by the live declarations. /// /// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then /// we don't actually need anything here, all we'll need to validate is that our own RHS is a /// valid assignment to our own annotation. - declarations_by_binding: FxHashMap, SymbolDeclarations>, + declarations_by_binding: FxHashMap, Declarations>, /// If the definition is a declaration (only) -- `x: int` for example -- then we need - /// [`SymbolBindings`] to know whether this declaration is consistent with the previously + /// [`Bindings`] to know whether this declaration is consistent with the previously /// inferred type. /// /// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then /// we don't actually need anything here, all we'll need to validate is that our own RHS is a /// valid assignment to our own annotation. - bindings_by_declaration: FxHashMap, SymbolBindings>, - - /// [`SymbolState`] visible at end of scope for each symbol. - public_symbols: IndexVec, + bindings_by_declaration: FxHashMap, Bindings>, - /// [`SymbolState`] for each instance attribute. - instance_attributes: IndexVec, + /// [`PlaceState`] visible at end of scope for each place. + public_places: IndexVec, /// Snapshot of bindings in this scope that can be used to resolve a reference in a nested /// eager scope. @@ -402,16 +407,9 @@ impl<'db> UseDefMap<'db> { pub(crate) fn public_bindings( &self, - symbol: ScopedSymbolId, - ) -> BindingWithConstraintsIterator<'_, 'db> { - self.bindings_iterator(self.public_symbols[symbol].bindings()) - } - - pub(crate) fn instance_attribute_bindings( - &self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, ) -> BindingWithConstraintsIterator<'_, 'db> { - self.bindings_iterator(self.instance_attributes[symbol].bindings()) + self.bindings_iterator(self.public_places[place].bindings()) } pub(crate) fn eager_snapshot( @@ -422,8 +420,8 @@ impl<'db> UseDefMap<'db> { Some(EagerSnapshot::Constraint(constraint)) => { EagerSnapshotResult::FoundConstraint(*constraint) } - Some(EagerSnapshot::Bindings(symbol_bindings)) => { - EagerSnapshotResult::FoundBindings(self.bindings_iterator(symbol_bindings)) + Some(EagerSnapshot::Bindings(bindings)) => { + EagerSnapshotResult::FoundBindings(self.bindings_iterator(bindings)) } None => EagerSnapshotResult::NotFound, } @@ -445,27 +443,27 @@ impl<'db> UseDefMap<'db> { pub(crate) fn public_declarations<'map>( &'map self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, ) -> DeclarationsIterator<'map, 'db> { - let declarations = self.public_symbols[symbol].declarations(); + let declarations = self.public_places[place].declarations(); self.declarations_iterator(declarations) } pub(crate) fn all_public_declarations<'map>( &'map self, - ) -> impl Iterator)> + 'map { - (0..self.public_symbols.len()) - .map(ScopedSymbolId::from_usize) - .map(|symbol_id| (symbol_id, self.public_declarations(symbol_id))) + ) -> impl Iterator)> + 'map { + (0..self.public_places.len()) + .map(ScopedPlaceId::from_usize) + .map(|place_id| (place_id, self.public_declarations(place_id))) } pub(crate) fn all_public_bindings<'map>( &'map self, - ) -> impl Iterator)> + 'map + ) -> impl Iterator)> + 'map { - (0..self.public_symbols.len()) - .map(ScopedSymbolId::from_usize) - .map(|symbol_id| (symbol_id, self.public_bindings(symbol_id))) + (0..self.public_places.len()) + .map(ScopedPlaceId::from_usize) + .map(|place_id| (place_id, self.public_bindings(place_id))) } /// This function is intended to be called only once inside `TypeInferenceBuilder::infer_function_body`. @@ -487,7 +485,7 @@ impl<'db> UseDefMap<'db> { fn bindings_iterator<'map>( &'map self, - bindings: &'map SymbolBindings, + bindings: &'map Bindings, ) -> BindingWithConstraintsIterator<'map, 'db> { BindingWithConstraintsIterator { all_definitions: &self.all_definitions, @@ -500,7 +498,7 @@ impl<'db> UseDefMap<'db> { fn declarations_iterator<'map>( &'map self, - declarations: &'map SymbolDeclarations, + declarations: &'map Declarations, ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, @@ -511,12 +509,12 @@ impl<'db> UseDefMap<'db> { } } -/// Uniquely identifies a snapshot of a symbol state that can be used to resolve a reference in a +/// Uniquely identifies a snapshot of a place state that can be used to resolve a reference in a /// nested eager scope. /// /// An eager scope has its entire body executed immediately at the location where it is defined. /// For any free references in the nested scope, we use the bindings that are visible at the point -/// where the nested scope is defined, instead of using the public type of the symbol. +/// where the nested scope is defined, instead of using the public type of the place. /// /// There is a unique ID for each distinct [`EagerSnapshotKey`] in the file. #[newtype_index] @@ -526,18 +524,18 @@ pub(crate) struct ScopedEagerSnapshotId; pub(crate) struct EagerSnapshotKey { /// The enclosing scope containing the bindings pub(crate) enclosing_scope: FileScopeId, - /// The referenced symbol (in the enclosing scope) - pub(crate) enclosing_symbol: ScopedSymbolId, + /// The referenced place (in the enclosing scope) + pub(crate) enclosing_place: ScopedPlaceId, /// The nested eager scope containing the reference pub(crate) nested_scope: FileScopeId, } -/// A snapshot of symbol states that can be used to resolve a reference in a nested eager scope. +/// A snapshot of place states that can be used to resolve a reference in a nested eager scope. type EagerSnapshots = IndexVec; #[derive(Debug)] pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { - all_definitions: &'map IndexVec>>, + all_definitions: &'map IndexVec>, pub(crate) predicates: &'map Predicates<'db>, pub(crate) narrowing_constraints: &'map NarrowingConstraints, pub(crate) visibility_constraints: &'map VisibilityConstraints, @@ -568,7 +566,7 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { - pub(crate) binding: Option>, + pub(crate) binding: DefinitionState<'db>, pub(crate) narrowing_constraint: ConstraintsIterator<'map, 'db>, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } @@ -595,10 +593,10 @@ impl<'db> ConstraintsIterator<'_, 'db> { self, db: &'db dyn crate::Db, base_ty: Type<'db>, - symbol: ScopedSymbolId, + place: ScopedPlaceId, ) -> Type<'db> { let constraint_tys: Vec<_> = self - .filter_map(|constraint| infer_narrowing_constraint(db, constraint, symbol)) + .filter_map(|constraint| infer_narrowing_constraint(db, constraint, place)) .collect(); if constraint_tys.is_empty() { @@ -618,14 +616,14 @@ impl<'db> ConstraintsIterator<'_, 'db> { #[derive(Clone)] pub(crate) struct DeclarationsIterator<'map, 'db> { - all_definitions: &'map IndexVec>>, + all_definitions: &'map IndexVec>, pub(crate) predicates: &'map Predicates<'db>, pub(crate) visibility_constraints: &'map VisibilityConstraints, inner: LiveDeclarationsIterator<'map>, } pub(crate) struct DeclarationWithConstraint<'db> { - pub(crate) declaration: Option>, + pub(crate) declaration: DefinitionState<'db>, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } @@ -652,8 +650,7 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} /// A snapshot of the definitions and constraints state at a particular point in control flow. #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { - symbol_states: IndexVec, - instance_attribute_states: IndexVec, + place_states: IndexVec, scope_start_visibility: ScopedVisibilityConstraintId, reachability: ScopedVisibilityConstraintId, } @@ -661,7 +658,7 @@ pub(super) struct FlowSnapshot { #[derive(Debug)] pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`Definition`]. - all_definitions: IndexVec>>, + all_definitions: IndexVec>, /// Builder of predicates. pub(super) predicates: PredicatesBuilder<'db>, @@ -673,7 +670,7 @@ pub(super) struct UseDefMapBuilder<'db> { pub(super) visibility_constraints: VisibilityConstraintsBuilder, /// A constraint which describes the visibility of the unbound/undeclared state, i.e. - /// whether or not a use of a symbol at the current point in control flow would see + /// whether or not a use of a place at the current point in control flow would see /// the fake `x = ` binding at the start of the scope. This is important for /// cases like the following, where we need to hide the implicit unbound binding in /// the "else" branch: @@ -688,7 +685,7 @@ pub(super) struct UseDefMapBuilder<'db> { pub(super) scope_start_visibility: ScopedVisibilityConstraintId, /// Live bindings at each so-far-recorded use. - bindings_by_use: IndexVec, + bindings_by_use: IndexVec, /// Tracks whether or not the scope start is visible at the current point in control flow. /// This is subtly different from `scope_start_visibility`, as we apply these constraints @@ -725,18 +722,15 @@ pub(super) struct UseDefMapBuilder<'db> { node_reachability: FxHashMap, /// Live declarations for each so-far-recorded binding. - declarations_by_binding: FxHashMap, SymbolDeclarations>, + declarations_by_binding: FxHashMap, Declarations>, /// Live bindings for each so-far-recorded declaration. - bindings_by_declaration: FxHashMap, SymbolBindings>, + bindings_by_declaration: FxHashMap, Bindings>, - /// Currently live bindings and declarations for each symbol. - symbol_states: IndexVec, + /// Currently live bindings and declarations for each place. + place_states: IndexVec, - /// Currently live bindings for each instance attribute. - instance_attribute_states: IndexVec, - - /// Snapshots of symbol states in this scope that can be used to resolve a reference in a + /// Snapshots of place states in this scope that can be used to resolve a reference in a /// nested eager scope. eager_snapshots: EagerSnapshots, @@ -747,7 +741,7 @@ pub(super) struct UseDefMapBuilder<'db> { impl<'db> UseDefMapBuilder<'db> { pub(super) fn new(is_class_scope: bool) -> Self { Self { - all_definitions: IndexVec::from_iter([None]), + all_definitions: IndexVec::from_iter([DefinitionState::Undefined]), predicates: PredicatesBuilder::default(), narrowing_constraints: NarrowingConstraintsBuilder::default(), visibility_constraints: VisibilityConstraintsBuilder::default(), @@ -757,9 +751,8 @@ impl<'db> UseDefMapBuilder<'db> { node_reachability: FxHashMap::default(), declarations_by_binding: FxHashMap::default(), bindings_by_declaration: FxHashMap::default(), - symbol_states: IndexVec::new(), + place_states: IndexVec::new(), eager_snapshots: EagerSnapshots::default(), - instance_attribute_states: IndexVec::new(), is_class_scope, } } @@ -768,38 +761,29 @@ impl<'db> UseDefMapBuilder<'db> { self.reachability = ScopedVisibilityConstraintId::ALWAYS_FALSE; } - pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { - let new_symbol = self - .symbol_states - .push(SymbolState::undefined(self.scope_start_visibility)); - debug_assert_eq!(symbol, new_symbol); - } - - pub(super) fn add_attribute(&mut self, symbol: ScopedSymbolId) { - let new_symbol = self - .instance_attribute_states - .push(SymbolState::undefined(self.scope_start_visibility)); - debug_assert_eq!(symbol, new_symbol); + pub(super) fn add_place(&mut self, place: ScopedPlaceId) { + let new_place = self + .place_states + .push(PlaceState::undefined(self.scope_start_visibility)); + debug_assert_eq!(place, new_place); } - pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) { - let def_id = self.all_definitions.push(Some(binding)); - let symbol_state = &mut self.symbol_states[symbol]; - self.declarations_by_binding - .insert(binding, symbol_state.declarations().clone()); - symbol_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope); - } - - pub(super) fn record_attribute_binding( + pub(super) fn record_binding( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, binding: Definition<'db>, + is_place_name: bool, ) { - let def_id = self.all_definitions.push(Some(binding)); - let attribute_state = &mut self.instance_attribute_states[symbol]; + let def_id = self.all_definitions.push(DefinitionState::Defined(binding)); + let place_state = &mut self.place_states[place]; self.declarations_by_binding - .insert(binding, attribute_state.declarations().clone()); - attribute_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope); + .insert(binding, place_state.declarations().clone()); + place_state.record_binding( + def_id, + self.scope_start_visibility, + self.is_class_scope, + is_place_name, + ); } pub(super) fn add_predicate(&mut self, predicate: Predicate<'db>) -> ScopedPredicateId { @@ -808,11 +792,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn record_narrowing_constraint(&mut self, predicate: ScopedPredicateId) { let narrowing_constraint = predicate.into(); - for state in &mut self.symbol_states { - state - .record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint); - } - for state in &mut self.instance_attribute_states { + for state in &mut self.place_states { state .record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint); } @@ -822,10 +802,7 @@ impl<'db> UseDefMapBuilder<'db> { &mut self, constraint: ScopedVisibilityConstraintId, ) { - for state in &mut self.symbol_states { - state.record_visibility_constraint(&mut self.visibility_constraints, constraint); - } - for state in &mut self.instance_attribute_states { + for state in &mut self.place_states { state.record_visibility_constraint(&mut self.visibility_constraints, constraint); } self.scope_start_visibility = self @@ -833,13 +810,13 @@ impl<'db> UseDefMapBuilder<'db> { .add_and_constraint(self.scope_start_visibility, constraint); } - /// Snapshot the state of a single symbol at the current point in control flow. + /// Snapshot the state of a single place at the current point in control flow. /// /// This is only used for `*`-import visibility constraints, which are handled differently /// to most other visibility constraints. See the doc-comment for /// [`Self::record_and_negate_star_import_visibility_constraint`] for more details. - pub(super) fn single_symbol_snapshot(&self, symbol: ScopedSymbolId) -> SymbolState { - self.symbol_states[symbol].clone() + pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState { + self.place_states[place].clone() } /// This method exists solely for handling `*`-import visibility constraints. @@ -863,10 +840,10 @@ impl<'db> UseDefMapBuilder<'db> { /// Doing things this way is cheaper in and of itself. However, it also allows us to avoid /// calling [`Self::simplify_visibility_constraints`] after the constraint has been applied to /// the "if-predicate-true" branch and negated for the "if-predicate-false" branch. Simplifying - /// the visibility constraints is only important for symbols that did not have any new + /// the visibility constraints is only important for places that did not have any new /// definitions inside either the "if-predicate-true" branch or the "if-predicate-false" branch. /// - /// - We only snapshot the state for a single symbol prior to the definition, rather than doing + /// - We only snapshot the state for a single place prior to the definition, rather than doing /// expensive calls to [`Self::snapshot`]. Again, this is possible because we know /// that only a single definition occurs inside the "if-predicate-true" predicate branch. /// @@ -880,8 +857,8 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn record_and_negate_star_import_visibility_constraint( &mut self, star_import: StarImportPlaceholderPredicate<'db>, - symbol: ScopedSymbolId, - pre_definition_state: SymbolState, + symbol: ScopedPlaceId, + pre_definition_state: PlaceState, ) { let predicate_id = self.add_predicate(star_import.into()); let visibility_id = self.visibility_constraints.add_atom(predicate_id); @@ -890,22 +867,22 @@ impl<'db> UseDefMapBuilder<'db> { .add_not_constraint(visibility_id); let mut post_definition_state = - std::mem::replace(&mut self.symbol_states[symbol], pre_definition_state); + std::mem::replace(&mut self.place_states[symbol], pre_definition_state); post_definition_state .record_visibility_constraint(&mut self.visibility_constraints, visibility_id); - self.symbol_states[symbol] + self.place_states[symbol] .record_visibility_constraint(&mut self.visibility_constraints, negated_visibility_id); - self.symbol_states[symbol].merge( + self.place_states[symbol].merge( post_definition_state, &mut self.narrowing_constraints, &mut self.visibility_constraints, ); } - /// This method resets the visibility constraints for all symbols to a previous state + /// This method resets the visibility constraints for all places to a previous state /// *if* there have been no new declarations or bindings since then. Consider the /// following example: /// ```py @@ -924,10 +901,7 @@ impl<'db> UseDefMapBuilder<'db> { /// constraint for the `x = 0` binding as well, but at the `RESET` point, we can get rid /// of it, as the `if`-`elif`-`elif` chain doesn't include any new bindings of `x`. pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { - debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); - debug_assert!( - self.instance_attribute_states.len() >= snapshot.instance_attribute_states.len() - ); + debug_assert!(self.place_states.len() >= snapshot.place_states.len()); // If there are any control flow paths that have become unreachable between `snapshot` and // now, then it's not valid to simplify any visibility constraints to `snapshot`. @@ -935,20 +909,13 @@ impl<'db> UseDefMapBuilder<'db> { return; } - // Note that this loop terminates when we reach a symbol not present in the snapshot. - // This means we keep visibility constraints for all new symbols, which is intended, - // since these symbols have been introduced in the corresponding branch, which might + // Note that this loop terminates when we reach a place not present in the snapshot. + // This means we keep visibility constraints for all new places, which is intended, + // since these places have been introduced in the corresponding branch, which might // be subject to visibility constraints. We only simplify/reset visibility constraints - // for symbols that have the same bindings and declarations present compared to the + // for places that have the same bindings and declarations present compared to the // snapshot. - for (current, snapshot) in self.symbol_states.iter_mut().zip(snapshot.symbol_states) { - current.simplify_visibility_constraints(snapshot); - } - for (current, snapshot) in self - .instance_attribute_states - .iter_mut() - .zip(snapshot.instance_attribute_states) - { + for (current, snapshot) in self.place_states.iter_mut().zip(snapshot.place_states) { current.simplify_visibility_constraints(snapshot); } } @@ -965,43 +932,64 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn record_declaration( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, declaration: Definition<'db>, ) { - let def_id = self.all_definitions.push(Some(declaration)); - let symbol_state = &mut self.symbol_states[symbol]; + let def_id = self + .all_definitions + .push(DefinitionState::Defined(declaration)); + let place_state = &mut self.place_states[place]; self.bindings_by_declaration - .insert(declaration, symbol_state.bindings().clone()); - symbol_state.record_declaration(def_id); + .insert(declaration, place_state.bindings().clone()); + place_state.record_declaration(def_id); } pub(super) fn record_declaration_and_binding( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, definition: Definition<'db>, + is_place_name: bool, ) { // We don't need to store anything in self.bindings_by_declaration or // self.declarations_by_binding. - let def_id = self.all_definitions.push(Some(definition)); - let symbol_state = &mut self.symbol_states[symbol]; - symbol_state.record_declaration(def_id); - symbol_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope); + let def_id = self + .all_definitions + .push(DefinitionState::Defined(definition)); + let place_state = &mut self.place_states[place]; + place_state.record_declaration(def_id); + place_state.record_binding( + def_id, + self.scope_start_visibility, + self.is_class_scope, + is_place_name, + ); + } + + pub(super) fn delete_binding(&mut self, place: ScopedPlaceId, is_place_name: bool) { + let def_id = self.all_definitions.push(DefinitionState::Deleted); + let place_state = &mut self.place_states[place]; + place_state.record_binding( + def_id, + self.scope_start_visibility, + self.is_class_scope, + is_place_name, + ); } pub(super) fn record_use( &mut self, - symbol: ScopedSymbolId, + place: ScopedPlaceId, use_id: ScopedUseId, node_key: NodeKey, ) { - // We have a use of a symbol; clone the current bindings for that symbol, and record them + // We have a use of a place; clone the current bindings for that place, and record them // as the live bindings for this use. let new_use = self .bindings_by_use - .push(self.symbol_states[symbol].bindings().clone()); + .push(self.place_states[place].bindings().clone()); debug_assert_eq!(use_id, new_use); - // Track reachability of all uses of symbols to silence `unresolved-reference` + // Track reachability of all uses of places to silence `unresolved-reference` // diagnostics in unreachable code. self.record_node_reachability(node_key); } @@ -1012,66 +1000,59 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn snapshot_eager_state( &mut self, - enclosing_symbol: ScopedSymbolId, + enclosing_place: ScopedPlaceId, scope: ScopeKind, - is_bound: bool, + enclosing_place_expr: &PlaceExpr, ) -> ScopedEagerSnapshotId { - // Names bound in class scopes are never visible to nested scopes, so we never need to - // save eager scope bindings in a class scope. - if scope.is_class() || !is_bound { + // Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible), + // so we never need to save eager scope bindings in a class scope. + if (scope.is_class() && enclosing_place_expr.is_name()) || !enclosing_place_expr.is_bound() + { self.eager_snapshots.push(EagerSnapshot::Constraint( - self.symbol_states[enclosing_symbol] + self.place_states[enclosing_place] .bindings() .unbound_narrowing_constraint(), )) } else { self.eager_snapshots.push(EagerSnapshot::Bindings( - self.symbol_states[enclosing_symbol].bindings().clone(), + self.place_states[enclosing_place].bindings().clone(), )) } } - /// Take a snapshot of the current visible-symbols state. + /// Take a snapshot of the current visible-places state. pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { - symbol_states: self.symbol_states.clone(), - instance_attribute_states: self.instance_attribute_states.clone(), + place_states: self.place_states.clone(), scope_start_visibility: self.scope_start_visibility, reachability: self.reachability, } } - /// Restore the current builder symbols state to the given snapshot. + /// Restore the current builder places state to the given snapshot. pub(super) fn restore(&mut self, snapshot: FlowSnapshot) { - // We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol - // IDs must line up), so the current number of known symbols must always be equal to or - // greater than the number of known symbols in a previously-taken snapshot. - let num_symbols = self.symbol_states.len(); - debug_assert!(num_symbols >= snapshot.symbol_states.len()); - let num_attributes = self.instance_attribute_states.len(); - debug_assert!(num_attributes >= snapshot.instance_attribute_states.len()); + // We never remove places from `place_states` (it's an IndexVec, and the place + // IDs must line up), so the current number of known places must always be equal to or + // greater than the number of known places in a previously-taken snapshot. + let num_places = self.place_states.len(); + debug_assert!(num_places >= snapshot.place_states.len()); // Restore the current visible-definitions state to the given snapshot. - self.symbol_states = snapshot.symbol_states; - self.instance_attribute_states = snapshot.instance_attribute_states; + self.place_states = snapshot.place_states; self.scope_start_visibility = snapshot.scope_start_visibility; self.reachability = snapshot.reachability; - // If the snapshot we are restoring is missing some symbols we've recorded since, we need - // to fill them in so the symbol IDs continue to line up. Since they don't exist in the + // If the snapshot we are restoring is missing some places we've recorded since, we need + // to fill them in so the place IDs continue to line up. Since they don't exist in the // snapshot, the correct state to fill them in with is "undefined". - self.symbol_states.resize( - num_symbols, - SymbolState::undefined(self.scope_start_visibility), - ); - self.instance_attribute_states.resize( - num_attributes, - SymbolState::undefined(self.scope_start_visibility), + self.place_states.resize( + num_places, + PlaceState::undefined(self.scope_start_visibility), ); } /// Merge the given snapshot into the current state, reflecting that we might have taken either - /// path to get here. The new state for each symbol should include definitions from both the + /// path to get here. The new state for each place should include definitions from both the /// prior state and the snapshot. pub(super) fn merge(&mut self, snapshot: FlowSnapshot) { // As an optimization, if we know statically that either of the snapshots is always @@ -1089,33 +1070,13 @@ impl<'db> UseDefMapBuilder<'db> { return; } - // We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol - // IDs must line up), so the current number of known symbols must always be equal to or - // greater than the number of known symbols in a previously-taken snapshot. - debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); - debug_assert!( - self.instance_attribute_states.len() >= snapshot.instance_attribute_states.len() - ); + // We never remove places from `place_states` (it's an IndexVec, and the place + // IDs must line up), so the current number of known places must always be equal to or + // greater than the number of known places in a previously-taken snapshot. + debug_assert!(self.place_states.len() >= snapshot.place_states.len()); - let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); - for current in &mut self.symbol_states { - if let Some(snapshot) = snapshot_definitions_iter.next() { - current.merge( - snapshot, - &mut self.narrowing_constraints, - &mut self.visibility_constraints, - ); - } else { - current.merge( - SymbolState::undefined(snapshot.scope_start_visibility), - &mut self.narrowing_constraints, - &mut self.visibility_constraints, - ); - // Symbol not present in snapshot, so it's unbound/undeclared from that path. - } - } - let mut snapshot_definitions_iter = snapshot.instance_attribute_states.into_iter(); - for current in &mut self.instance_attribute_states { + let mut snapshot_definitions_iter = snapshot.place_states.into_iter(); + for current in &mut self.place_states { if let Some(snapshot) = snapshot_definitions_iter.next() { current.merge( snapshot, @@ -1124,10 +1085,11 @@ impl<'db> UseDefMapBuilder<'db> { ); } else { current.merge( - SymbolState::undefined(snapshot.scope_start_visibility), + PlaceState::undefined(snapshot.scope_start_visibility), &mut self.narrowing_constraints, &mut self.visibility_constraints, ); + // Place not present in snapshot, so it's unbound/undeclared from that path. } } @@ -1142,8 +1104,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn finish(mut self) -> UseDefMap<'db> { self.all_definitions.shrink_to_fit(); - self.symbol_states.shrink_to_fit(); - self.instance_attribute_states.shrink_to_fit(); + self.place_states.shrink_to_fit(); self.bindings_by_use.shrink_to_fit(); self.node_reachability.shrink_to_fit(); self.declarations_by_binding.shrink_to_fit(); @@ -1157,8 +1118,7 @@ impl<'db> UseDefMapBuilder<'db> { visibility_constraints: self.visibility_constraints.build(), bindings_by_use: self.bindings_by_use, node_reachability: self.node_reachability, - public_symbols: self.symbol_states, - instance_attributes: self.instance_attribute_states, + public_places: self.place_states, declarations_by_binding: self.declarations_by_binding, bindings_by_declaration: self.bindings_by_declaration, eager_snapshots: self.eager_snapshots, diff --git a/crates/ty_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs similarity index 84% rename from crates/ty_python_semantic/src/semantic_index/use_def/symbol_state.rs rename to crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs index 0a7df85a83f73c..d73134ead65a70 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs @@ -1,4 +1,4 @@ -//! Track live bindings per symbol, applicable constraints per binding, and live declarations. +//! Track live bindings per place, applicable constraints per binding, and live declarations. //! //! These data structures operate entirely on scope-local newtype-indices for definitions and //! constraints, referring to their location in the `all_definitions` and `all_constraints` @@ -60,9 +60,9 @@ pub(super) struct ScopedDefinitionId; impl ScopedDefinitionId { /// A special ID that is used to describe an implicit start-of-scope state. When - /// we see that this definition is live, we know that the symbol is (possibly) + /// we see that this definition is live, we know that the place is (possibly) /// unbound or undeclared at a given usage site. - /// When creating a use-def-map builder, we always add an empty `None` definition + /// When creating a use-def-map builder, we always add an empty `DefinitionState::Undefined` definition /// at index 0, so this ID is always present. pub(super) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0); @@ -71,19 +71,19 @@ impl ScopedDefinitionId { } } -/// Can keep inline this many live bindings or declarations per symbol at a given time; more will +/// Can keep inline this many live bindings or declarations per place at a given time; more will /// go to heap. -const INLINE_DEFINITIONS_PER_SYMBOL: usize = 4; +const INLINE_DEFINITIONS_PER_PLACE: usize = 4; -/// Live declarations for a single symbol at some point in control flow, with their +/// Live declarations for a single place at some point in control flow, with their /// corresponding visibility constraints. #[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)] -pub(super) struct SymbolDeclarations { - /// A list of live declarations for this symbol, sorted by their `ScopedDefinitionId` - live_declarations: SmallVec<[LiveDeclaration; INLINE_DEFINITIONS_PER_SYMBOL]>, +pub(super) struct Declarations { + /// A list of live declarations for this place, sorted by their `ScopedDefinitionId` + live_declarations: SmallVec<[LiveDeclaration; INLINE_DEFINITIONS_PER_PLACE]>, } -/// One of the live declarations for a single symbol at some point in control flow. +/// One of the live declarations for a single place at some point in control flow. #[derive(Clone, Debug, PartialEq, Eq)] pub(super) struct LiveDeclaration { pub(super) declaration: ScopedDefinitionId, @@ -92,7 +92,7 @@ pub(super) struct LiveDeclaration { pub(super) type LiveDeclarationsIterator<'a> = std::slice::Iter<'a, LiveDeclaration>; -impl SymbolDeclarations { +impl Declarations { fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { let initial_declaration = LiveDeclaration { declaration: ScopedDefinitionId::UNBOUND, @@ -103,7 +103,7 @@ impl SymbolDeclarations { } } - /// Record a newly-encountered declaration for this symbol. + /// Record a newly-encountered declaration for this place. fn record_declaration(&mut self, declaration: ScopedDefinitionId) { // The new declaration replaces all previous live declaration in this path. self.live_declarations.clear(); @@ -125,17 +125,17 @@ impl SymbolDeclarations { } } - /// Return an iterator over live declarations for this symbol. + /// Return an iterator over live declarations for this place. pub(super) fn iter(&self) -> LiveDeclarationsIterator<'_> { self.live_declarations.iter() } - /// Iterate over the IDs of each currently live declaration for this symbol + /// Iterate over the IDs of each currently live declaration for this place fn iter_declarations(&self) -> impl Iterator + '_ { self.iter().map(|lb| lb.declaration) } - fn simplify_visibility_constraints(&mut self, other: SymbolDeclarations) { + fn simplify_visibility_constraints(&mut self, other: Declarations) { // If the set of live declarations hasn't changed, don't simplify. if self.live_declarations.len() != other.live_declarations.len() || !self.iter_declarations().eq(other.iter_declarations()) @@ -181,7 +181,7 @@ impl SymbolDeclarations { } } -/// A snapshot of a symbol state that can be used to resolve a reference in a nested eager scope. +/// A snapshot of a place state that can be used to resolve a reference in a nested eager scope. /// If there are bindings in a (non-class) scope , they are stored in `Bindings`. /// Even if it's a class scope (class variables are not visible to nested scopes) or there are no /// bindings, the current narrowing constraint is necessary for narrowing, so it's stored in @@ -189,34 +189,30 @@ impl SymbolDeclarations { #[derive(Clone, Debug, PartialEq, Eq, salsa::Update)] pub(super) enum EagerSnapshot { Constraint(ScopedNarrowingConstraint), - Bindings(SymbolBindings), + Bindings(Bindings), } -/// Live bindings for a single symbol at some point in control flow. Each live binding comes +/// Live bindings for a single place at some point in control flow. Each live binding comes /// with a set of narrowing constraints and a visibility constraint. #[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)] -pub(super) struct SymbolBindings { +pub(super) struct Bindings { /// The narrowing constraint applicable to the "unbound" binding, if we need access to it even - /// when it's not visible. This happens in class scopes, where local bindings are not visible + /// when it's not visible. This happens in class scopes, where local name bindings are not visible /// to nested scopes, but we still need to know what narrowing constraints were applied to the /// "unbound" binding. unbound_narrowing_constraint: Option, - /// A list of live bindings for this symbol, sorted by their `ScopedDefinitionId` - live_bindings: SmallVec<[LiveBinding; INLINE_DEFINITIONS_PER_SYMBOL]>, + /// A list of live bindings for this place, sorted by their `ScopedDefinitionId` + live_bindings: SmallVec<[LiveBinding; INLINE_DEFINITIONS_PER_PLACE]>, } -impl SymbolBindings { +impl Bindings { pub(super) fn unbound_narrowing_constraint(&self) -> ScopedNarrowingConstraint { - debug_assert!( - self.unbound_narrowing_constraint.is_some() - || self.live_bindings[0].binding.is_unbound() - ); self.unbound_narrowing_constraint .unwrap_or(self.live_bindings[0].narrowing_constraint) } } -/// One of the live bindings for a single symbol at some point in control flow. +/// One of the live bindings for a single place at some point in control flow. #[derive(Clone, Debug, PartialEq, Eq)] pub(super) struct LiveBinding { pub(super) binding: ScopedDefinitionId, @@ -226,7 +222,7 @@ pub(super) struct LiveBinding { pub(super) type LiveBindingsIterator<'a> = std::slice::Iter<'a, LiveBinding>; -impl SymbolBindings { +impl Bindings { fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { let initial_binding = LiveBinding { binding: ScopedDefinitionId::UNBOUND, @@ -239,16 +235,17 @@ impl SymbolBindings { } } - /// Record a newly-encountered binding for this symbol. + /// Record a newly-encountered binding for this place. pub(super) fn record_binding( &mut self, binding: ScopedDefinitionId, visibility_constraint: ScopedVisibilityConstraintId, is_class_scope: bool, + is_place_name: bool, ) { - // If we are in a class scope, and the unbound binding was previously visible, but we will + // If we are in a class scope, and the unbound name binding was previously visible, but we will // now replace it, record the narrowing constraints on it: - if is_class_scope && self.live_bindings[0].binding.is_unbound() { + if is_class_scope && is_place_name && self.live_bindings[0].binding.is_unbound() { self.unbound_narrowing_constraint = Some(self.live_bindings[0].narrowing_constraint); } // The new binding replaces all previous live bindings in this path, and has no @@ -285,17 +282,17 @@ impl SymbolBindings { } } - /// Iterate over currently live bindings for this symbol + /// Iterate over currently live bindings for this place pub(super) fn iter(&self) -> LiveBindingsIterator<'_> { self.live_bindings.iter() } - /// Iterate over the IDs of each currently live binding for this symbol + /// Iterate over the IDs of each currently live binding for this place fn iter_bindings(&self) -> impl Iterator + '_ { self.iter().map(|lb| lb.binding) } - fn simplify_visibility_constraints(&mut self, other: SymbolBindings) { + fn simplify_visibility_constraints(&mut self, other: Bindings) { // If the set of live bindings hasn't changed, don't simplify. if self.live_bindings.len() != other.live_bindings.len() || !self.iter_bindings().eq(other.iter_bindings()) @@ -360,30 +357,35 @@ impl SymbolBindings { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::semantic_index) struct SymbolState { - declarations: SymbolDeclarations, - bindings: SymbolBindings, +pub(in crate::semantic_index) struct PlaceState { + declarations: Declarations, + bindings: Bindings, } -impl SymbolState { - /// Return a new [`SymbolState`] representing an unbound, undeclared symbol. +impl PlaceState { + /// Return a new [`PlaceState`] representing an unbound, undeclared place. pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { Self { - declarations: SymbolDeclarations::undeclared(scope_start_visibility), - bindings: SymbolBindings::unbound(scope_start_visibility), + declarations: Declarations::undeclared(scope_start_visibility), + bindings: Bindings::unbound(scope_start_visibility), } } - /// Record a newly-encountered binding for this symbol. + /// Record a newly-encountered binding for this place. pub(super) fn record_binding( &mut self, binding_id: ScopedDefinitionId, visibility_constraint: ScopedVisibilityConstraintId, is_class_scope: bool, + is_place_name: bool, ) { debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND); - self.bindings - .record_binding(binding_id, visibility_constraint, is_class_scope); + self.bindings.record_binding( + binding_id, + visibility_constraint, + is_class_scope, + is_place_name, + ); } /// Add given constraint to all live bindings. @@ -409,24 +411,24 @@ impl SymbolState { } /// Simplifies this snapshot to have the same visibility constraints as a previous point in the - /// control flow, but only if the set of live bindings or declarations for this symbol hasn't + /// control flow, but only if the set of live bindings or declarations for this place hasn't /// changed. - pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) { + pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: PlaceState) { self.bindings .simplify_visibility_constraints(snapshot_state.bindings); self.declarations .simplify_visibility_constraints(snapshot_state.declarations); } - /// Record a newly-encountered declaration of this symbol. + /// Record a newly-encountered declaration of this place. pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { self.declarations.record_declaration(declaration_id); } - /// Merge another [`SymbolState`] into this one. + /// Merge another [`PlaceState`] into this one. pub(super) fn merge( &mut self, - b: SymbolState, + b: PlaceState, narrowing_constraints: &mut NarrowingConstraintsBuilder, visibility_constraints: &mut VisibilityConstraintsBuilder, ) { @@ -436,11 +438,11 @@ impl SymbolState { .merge(b.declarations, visibility_constraints); } - pub(super) fn bindings(&self) -> &SymbolBindings { + pub(super) fn bindings(&self) -> &Bindings { &self.bindings } - pub(super) fn declarations(&self) -> &SymbolDeclarations { + pub(super) fn declarations(&self) -> &Declarations { &self.declarations } } @@ -454,10 +456,10 @@ mod tests { #[track_caller] fn assert_bindings( narrowing_constraints: &NarrowingConstraintsBuilder, - symbol: &SymbolState, + place: &PlaceState, expected: &[&str], ) { - let actual = symbol + let actual = place .bindings() .iter() .map(|live_binding| { @@ -479,8 +481,8 @@ mod tests { } #[track_caller] - pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) { - let actual = symbol + pub(crate) fn assert_declarations(place: &PlaceState, expected: &[&str]) { + let actual = place .declarations() .iter() .map( @@ -502,7 +504,7 @@ mod tests { #[test] fn unbound() { let narrowing_constraints = NarrowingConstraintsBuilder::default(); - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); assert_bindings(&narrowing_constraints, &sym, &["unbound<>"]); } @@ -510,11 +512,12 @@ mod tests { #[test] fn with() { let narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding( ScopedDefinitionId::from_u32(1), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); assert_bindings(&narrowing_constraints, &sym, &["1<>"]); @@ -523,11 +526,12 @@ mod tests { #[test] fn record_constraint() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding( ScopedDefinitionId::from_u32(1), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(0).into(); sym.record_narrowing_constraint(&mut narrowing_constraints, predicate); @@ -541,20 +545,22 @@ mod tests { let mut visibility_constraints = VisibilityConstraintsBuilder::default(); // merging the same definition with the same constraint keeps the constraint - let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1a.record_binding( ScopedDefinitionId::from_u32(1), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(0).into(); sym1a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1b.record_binding( ScopedDefinitionId::from_u32(1), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(0).into(); sym1b.record_narrowing_constraint(&mut narrowing_constraints, predicate); @@ -568,20 +574,22 @@ mod tests { assert_bindings(&narrowing_constraints, &sym1, &["1<0>"]); // merging the same definition with differing constraints drops all constraints - let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym2a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym2a.record_binding( ScopedDefinitionId::from_u32(2), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(1).into(); sym2a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1b.record_binding( ScopedDefinitionId::from_u32(2), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(2).into(); sym1b.record_narrowing_constraint(&mut narrowing_constraints, predicate); @@ -595,16 +603,17 @@ mod tests { assert_bindings(&narrowing_constraints, &sym2, &["2<>"]); // merging a constrained definition with unbound keeps both - let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym3a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym3a.record_binding( ScopedDefinitionId::from_u32(3), ScopedVisibilityConstraintId::ALWAYS_TRUE, false, + true, ); let predicate = ScopedPredicateId::from_u32(3).into(); sym3a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym2b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym3a.merge( sym2b, @@ -626,14 +635,14 @@ mod tests { #[test] fn no_declaration() { - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); assert_declarations(&sym, &["undeclared"]); } #[test] fn record_declaration() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); assert_declarations(&sym, &["1"]); @@ -641,7 +650,7 @@ mod tests { #[test] fn record_declaration_override() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); sym.record_declaration(ScopedDefinitionId::from_u32(2)); @@ -652,10 +661,10 @@ mod tests { fn record_declaration_merge() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); let mut visibility_constraints = VisibilityConstraintsBuilder::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); - let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym2.record_declaration(ScopedDefinitionId::from_u32(2)); sym.merge( @@ -671,10 +680,10 @@ mod tests { fn record_declaration_merge_partial_undeclared() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); let mut visibility_constraints = VisibilityConstraintsBuilder::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); - let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.merge( sym2, diff --git a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs b/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs index 4e2e76e301fffd..60db138c57da9a 100644 --- a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs @@ -180,12 +180,12 @@ use rustc_hash::FxHashMap; use crate::Db; use crate::dunder_all::dunder_all_names; +use crate::place::{RequiresExplicitReExport, imported_symbol}; use crate::semantic_index::expression::Expression; +use crate::semantic_index::place_table; use crate::semantic_index::predicate::{ PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, Predicates, ScopedPredicateId, }; -use crate::semantic_index::symbol_table; -use crate::symbol::{RequiresExplicitReExport, imported_symbol}; use crate::types::{Truthiness, Type, infer_expression_type}; /// A ternary formula that defines under what conditions a binding is visible. (A ternary formula @@ -654,8 +654,10 @@ impl VisibilityConstraints { } PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner), PredicateNode::StarImportPlaceholder(star_import) => { - let symbol_table = symbol_table(db, star_import.scope(db)); - let symbol_name = symbol_table.symbol(star_import.symbol_id(db)).name(); + let place_table = place_table(db, star_import.scope(db)); + let symbol_name = place_table + .place_expr(star_import.symbol_id(db)) + .expect_name(); let referenced_file = star_import.referenced_file(db); let requires_explicit_reexport = match dunder_all_names(db, referenced_file) { @@ -675,15 +677,15 @@ impl VisibilityConstraints { }; match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport) - .symbol + .place { - crate::symbol::Symbol::Type(_, crate::symbol::Boundness::Bound) => { + crate::place::Place::Type(_, crate::place::Boundness::Bound) => { Truthiness::AlwaysTrue } - crate::symbol::Symbol::Type(_, crate::symbol::Boundness::PossiblyUnbound) => { + crate::place::Place::Type(_, crate::place::Boundness::PossiblyUnbound) => { Truthiness::Ambiguous } - crate::symbol::Symbol::Unbound => Truthiness::AlwaysFalse, + crate::place::Place::Unbound => Truthiness::AlwaysFalse, } } } diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index bc4204386e0168..4a85f138a32df4 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -8,8 +8,8 @@ use crate::Db; use crate::module_name::ModuleName; use crate::module_resolver::{Module, resolve_module}; use crate::semantic_index::ast_ids::HasScopedExpressionId; +use crate::semantic_index::place::FileScopeId; use crate::semantic_index::semantic_index; -use crate::semantic_index::symbol::FileScopeId; use crate::types::ide_support::all_declarations_and_bindings; use crate::types::{Type, binding_type, infer_scope_types}; diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index b2d1c6b91f1ac8..ebccf4a0e7bb4f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -31,12 +31,12 @@ pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, resolve_module}; +use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::definition::Definition; -use crate::semantic_index::symbol::ScopeId; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; -use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers, imported_symbol}; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; @@ -242,12 +242,12 @@ impl Default for MemberLookupPolicy { fn member_lookup_cycle_recover<'db>( _db: &'db dyn Db, - _value: &SymbolAndQualifiers<'db>, + _value: &PlaceAndQualifiers<'db>, _count: u32, _self: Type<'db>, _name: Name, _policy: MemberLookupPolicy, -) -> salsa::CycleRecoveryAction> { +) -> salsa::CycleRecoveryAction> { salsa::CycleRecoveryAction::Iterate } @@ -256,8 +256,28 @@ fn member_lookup_cycle_initial<'db>( _self: Type<'db>, _name: Name, _policy: MemberLookupPolicy, -) -> SymbolAndQualifiers<'db> { - Symbol::bound(Type::Never).into() +) -> PlaceAndQualifiers<'db> { + Place::bound(Type::Never).into() +} + +fn class_lookup_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &PlaceAndQualifiers<'db>, + _count: u32, + _self: Type<'db>, + _name: Name, + _policy: MemberLookupPolicy, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn class_lookup_cycle_initial<'db>( + _db: &'db dyn Db, + _self: Type<'db>, + _name: Name, + _policy: MemberLookupPolicy, +) -> PlaceAndQualifiers<'db> { + Place::bound(Type::Never).into() } /// Meta data for `Type::Todo`, which represents a known limitation in ty. @@ -1262,11 +1282,11 @@ impl<'db> Type<'db> { Name::new_static("__call__"), MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) - .symbol; + .place; // If the type of __call__ is a subtype of a callable type, this instance is. // Don't add other special cases here; our subtyping of a callable type // shouldn't get out of sync with the calls we will actually allow. - if let Symbol::Type(t, Boundness::Bound) = call_symbol { + if let Place::Type(t, Boundness::Bound) = call_symbol { t.is_subtype_of(db, target) } else { false @@ -1625,11 +1645,9 @@ impl<'db> Type<'db> { Name::new_static("__call__"), MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) - .symbol; - // If the type of __call__ is assignable to a callable type, this instance is. - // Don't add other special cases here; our assignability to a callable type + .place; // shouldn't get out of sync with the calls we will actually allow. - if let Symbol::Type(t, Boundness::Bound) = call_symbol { + if let Place::Type(t, Boundness::Bound) = call_symbol { t.is_assignable_to(db, target) } else { false @@ -2178,7 +2196,7 @@ impl<'db> Type<'db> { Name::new_static("__call__"), MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) - .symbol + .place .ignore_possibly_unbound() .is_none_or(|dunder_call| { !dunder_call.is_assignable_to(db, CallableType::unknown(db)) @@ -2504,7 +2522,7 @@ impl<'db> Type<'db> { /// /// [descriptor guide]: https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance /// [`_PyType_Lookup`]: https://github.com/python/cpython/blob/e285232c76606e3be7bf216efb1be1e742423e4b/Objects/typeobject.c#L5223 - fn find_name_in_mro(&self, db: &'db dyn Db, name: &str) -> Option> { + fn find_name_in_mro(&self, db: &'db dyn Db, name: &str) -> Option> { self.find_name_in_mro_with_policy(db, name, MemberLookupPolicy::default()) } @@ -2513,7 +2531,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, name: &str, policy: MemberLookupPolicy, - ) -> Option> { + ) -> Option> { match self { Type::Union(union) => Some(union.map_with_boundness_and_qualifiers(db, |elem| { elem.find_name_in_mro_with_policy(db, name, policy) @@ -2531,28 +2549,28 @@ impl<'db> Type<'db> { })) } - Type::Dynamic(_) | Type::Never => Some(Symbol::bound(self).into()), + Type::Dynamic(_) | Type::Never => Some(Place::bound(self).into()), Type::ClassLiteral(class) => { match (class.known(db), name) { (Some(KnownClass::FunctionType), "__get__") => Some( - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::FunctionTypeDunderGet, )) .into(), ), (Some(KnownClass::FunctionType), "__set__" | "__delete__") => { // Hard code this knowledge, as we look up `__set__` and `__delete__` on `FunctionType` often. - Some(Symbol::Unbound.into()) + Some(Place::Unbound.into()) } (Some(KnownClass::Property), "__get__") => Some( - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::PropertyDunderGet, )) .into(), ), (Some(KnownClass::Property), "__set__") => Some( - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::PropertyDunderSet, )) .into(), @@ -2582,7 +2600,7 @@ impl<'db> Type<'db> { // MRO of the class `object`. Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => { if policy.mro_no_object_fallback() { - Some(Symbol::Unbound.into()) + Some(Place::Unbound.into()) } else { KnownClass::Object .to_class_literal(db) @@ -2620,17 +2638,17 @@ impl<'db> Type<'db> { /// /// Basically corresponds to `self.to_meta_type().find_name_in_mro(name)`, except for the handling /// of union and intersection types. - fn class_member(self, db: &'db dyn Db, name: Name) -> SymbolAndQualifiers<'db> { + fn class_member(self, db: &'db dyn Db, name: Name) -> PlaceAndQualifiers<'db> { self.class_member_with_policy(db, name, MemberLookupPolicy::default()) } - #[salsa::tracked] + #[salsa::tracked(cycle_fn=class_lookup_cycle_recover, cycle_initial=class_lookup_cycle_initial)] fn class_member_with_policy( self, db: &'db dyn Db, name: Name, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { tracing::trace!("class_member: {}.{}", self.display(db), name); match self { Type::Union(union) => union.map_with_boundness_and_qualifiers(db, |elem| { @@ -2664,7 +2682,7 @@ impl<'db> Type<'db> { /// def __init__(self): /// self.b: str = "a" /// ``` - fn instance_member(&self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + fn instance_member(&self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { match self { Type::Union(union) => { union.map_with_boundness_and_qualifiers(db, |elem| elem.instance_member(db, name)) @@ -2673,7 +2691,7 @@ impl<'db> Type<'db> { Type::Intersection(intersection) => intersection .map_with_boundness_and_qualifiers(db, |elem| elem.instance_member(db, name)), - Type::Dynamic(_) | Type::Never => Symbol::bound(self).into(), + Type::Dynamic(_) | Type::Never => Place::bound(self).into(), Type::NominalInstance(instance) => instance.class.instance_member(db, name), @@ -2723,7 +2741,7 @@ impl<'db> Type<'db> { .to_instance(db) .instance_member(db, name), - Type::SpecialForm(_) | Type::KnownInstance(_) => Symbol::Unbound.into(), + Type::SpecialForm(_) | Type::KnownInstance(_) => Place::Unbound.into(), Type::PropertyInstance(_) => KnownClass::Property .to_instance(db) @@ -2741,7 +2759,7 @@ impl<'db> Type<'db> { // required, as `instance_member` is only called for instance-like types through `member`, // but we might want to add this in the future. Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => { - Symbol::Unbound.into() + Place::Unbound.into() } } } @@ -2750,17 +2768,17 @@ impl<'db> Type<'db> { /// method corresponds to `inspect.getattr_static(, name)`. /// /// See also: [`Type::member`] - fn static_member(&self, db: &'db dyn Db, name: &str) -> Symbol<'db> { + fn static_member(&self, db: &'db dyn Db, name: &str) -> Place<'db> { if let Type::ModuleLiteral(module) = self { module.static_member(db, name) - } else if let symbol @ Symbol::Type(_, _) = self.class_member(db, name.into()).symbol { - symbol - } else if let Some(symbol @ Symbol::Type(_, _)) = - self.find_name_in_mro(db, name).map(|inner| inner.symbol) + } else if let place @ Place::Type(_, _) = self.class_member(db, name.into()).place { + place + } else if let Some(place @ Place::Type(_, _)) = + self.find_name_in_mro(db, name).map(|inner| inner.place) { - symbol + place } else { - self.instance_member(db, name).symbol + self.instance_member(db, name).place } } @@ -2806,9 +2824,9 @@ impl<'db> Type<'db> { _ => {} } - let descr_get = self.class_member(db, "__get__".into()).symbol; + let descr_get = self.class_member(db, "__get__".into()).place; - if let Symbol::Type(descr_get, descr_get_boundness) = descr_get { + if let Place::Type(descr_get, descr_get_boundness) = descr_get { let return_ty = descr_get .try_call(db, &CallArgumentTypes::positional([self, instance, owner])) .map(|bindings| { @@ -2820,15 +2838,10 @@ impl<'db> Type<'db> { }) .ok()?; - let descriptor_kind = if self.class_member(db, "__set__".into()).symbol.is_unbound() - && self - .class_member(db, "__delete__".into()) - .symbol - .is_unbound() - { - AttributeKind::NormalOrNonDataDescriptor - } else { + let descriptor_kind = if self.is_data_descriptor(db) { AttributeKind::DataDescriptor + } else { + AttributeKind::NormalOrNonDataDescriptor }; Some((return_ty, descriptor_kind)) @@ -2842,10 +2855,10 @@ impl<'db> Type<'db> { /// and intersections explicitly. fn try_call_dunder_get_on_attribute( db: &'db dyn Db, - attribute: SymbolAndQualifiers<'db>, + attribute: PlaceAndQualifiers<'db>, instance: Type<'db>, owner: Type<'db>, - ) -> (SymbolAndQualifiers<'db>, AttributeKind) { + ) -> (PlaceAndQualifiers<'db>, AttributeKind) { match attribute { // This branch is not strictly needed, but it short-circuits the lookup of various dunder // methods and calls that would otherwise be made. @@ -2855,18 +2868,18 @@ impl<'db> Type<'db> { // data descriptors. // // The same is true for `Never`. - SymbolAndQualifiers { - symbol: Symbol::Type(Type::Dynamic(_) | Type::Never, _), + PlaceAndQualifiers { + place: Place::Type(Type::Dynamic(_) | Type::Never, _), qualifiers: _, } => (attribute, AttributeKind::DataDescriptor), - SymbolAndQualifiers { - symbol: Symbol::Type(Type::Union(union), boundness), + PlaceAndQualifiers { + place: Place::Type(Type::Union(union), boundness), qualifiers, } => ( union .map_with_boundness(db, |elem| { - Symbol::Type( + Place::Type( elem.try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), boundness, @@ -2884,13 +2897,13 @@ impl<'db> Type<'db> { }, ), - SymbolAndQualifiers { - symbol: Symbol::Type(Type::Intersection(intersection), boundness), + PlaceAndQualifiers { + place: Place::Type(Type::Intersection(intersection), boundness), qualifiers, } => ( intersection .map_with_boundness(db, |elem| { - Symbol::Type( + Place::Type( elem.try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), boundness, @@ -2901,14 +2914,14 @@ impl<'db> Type<'db> { AttributeKind::NormalOrNonDataDescriptor, ), - SymbolAndQualifiers { - symbol: Symbol::Type(attribute_ty, boundness), + PlaceAndQualifiers { + place: Place::Type(attribute_ty, boundness), qualifiers: _, } => { if let Some((return_ty, attribute_kind)) = attribute_ty.try_call_dunder_get(db, instance, owner) { - (Symbol::Type(return_ty, boundness).into(), attribute_kind) + (Place::Type(return_ty, boundness).into(), attribute_kind) } else { (attribute, AttributeKind::NormalOrNonDataDescriptor) } @@ -2918,6 +2931,44 @@ impl<'db> Type<'db> { } } + /// Returns whether this type is a data descriptor, i.e. defines `__set__` or `__delete__`. + /// If this type is a union, requires all elements of union to be data descriptors. + pub(crate) fn is_data_descriptor(self, d: &'db dyn Db) -> bool { + self.is_data_descriptor_impl(d, false) + } + + /// Returns whether this type may be a data descriptor. + /// If this type is a union, returns true if _any_ element is a data descriptor. + pub(crate) fn may_be_data_descriptor(self, d: &'db dyn Db) -> bool { + self.is_data_descriptor_impl(d, true) + } + + fn is_data_descriptor_impl(self, db: &'db dyn Db, any_of_union: bool) -> bool { + match self { + Type::Dynamic(_) | Type::Never | Type::PropertyInstance(_) => true, + Type::Union(union) if any_of_union => union + .elements(db) + .iter() + // Types of instance attributes that are not explicitly typed are unioned with `Unknown`, it should be excluded when checking. + .filter(|ty| !ty.is_unknown()) + .any(|ty| ty.is_data_descriptor_impl(db, any_of_union)), + Type::Union(union) => union + .elements(db) + .iter() + .all(|ty| ty.is_data_descriptor_impl(db, any_of_union)), + Type::Intersection(intersection) => intersection + .iter_positive(db) + .any(|ty| ty.is_data_descriptor_impl(db, any_of_union)), + _ => { + !self.class_member(db, "__set__".into()).place.is_unbound() + || !self + .class_member(db, "__delete__".into()) + .place + .is_unbound() + } + } + } + /// Implementation of the descriptor protocol. /// /// This method roughly performs the following steps: @@ -2936,13 +2987,13 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, name: &str, - fallback: SymbolAndQualifiers<'db>, + fallback: PlaceAndQualifiers<'db>, policy: InstanceFallbackShadowsNonDataDescriptor, member_policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { let ( - SymbolAndQualifiers { - symbol: meta_attr, + PlaceAndQualifiers { + place: meta_attr, qualifiers: meta_attr_qualifiers, }, meta_attr_kind, @@ -2953,21 +3004,21 @@ impl<'db> Type<'db> { self.to_meta_type(db), ); - let SymbolAndQualifiers { - symbol: fallback, + let PlaceAndQualifiers { + place: fallback, qualifiers: fallback_qualifiers, } = fallback; match (meta_attr, meta_attr_kind, fallback) { // The fallback type is unbound, so we can just return `meta_attr` unconditionally, // no matter if it's data descriptor, a non-data descriptor, or a normal attribute. - (meta_attr @ Symbol::Type(_, _), _, Symbol::Unbound) => { + (meta_attr @ Place::Type(_, _), _, Place::Unbound) => { meta_attr.with_qualifiers(meta_attr_qualifiers) } // `meta_attr` is the return type of a data descriptor and definitely bound, so we // return it. - (meta_attr @ Symbol::Type(_, Boundness::Bound), AttributeKind::DataDescriptor, _) => { + (meta_attr @ Place::Type(_, Boundness::Bound), AttributeKind::DataDescriptor, _) => { meta_attr.with_qualifiers(meta_attr_qualifiers) } @@ -2975,10 +3026,10 @@ impl<'db> Type<'db> { // meta-type is possibly-unbound. This means that we "fall through" to the next // stage of the descriptor protocol and union with the fallback type. ( - Symbol::Type(meta_attr_ty, Boundness::PossiblyUnbound), + Place::Type(meta_attr_ty, Boundness::PossiblyUnbound), AttributeKind::DataDescriptor, - Symbol::Type(fallback_ty, fallback_boundness), - ) => Symbol::Type( + Place::Type(fallback_ty, fallback_boundness), + ) => Place::Type( UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), fallback_boundness, ) @@ -2993,9 +3044,9 @@ impl<'db> Type<'db> { // would require us to statically infer if an instance attribute is always set, which // is something we currently don't attempt to do. ( - Symbol::Type(_, _), + Place::Type(_, _), AttributeKind::NormalOrNonDataDescriptor, - fallback @ Symbol::Type(_, Boundness::Bound), + fallback @ Place::Type(_, Boundness::Bound), ) if policy == InstanceFallbackShadowsNonDataDescriptor::Yes => { fallback.with_qualifiers(fallback_qualifiers) } @@ -3004,17 +3055,17 @@ impl<'db> Type<'db> { // unbound or the policy argument is `No`. In both cases, the `fallback` type does // not completely shadow the non-data descriptor, so we build a union of the two. ( - Symbol::Type(meta_attr_ty, meta_attr_boundness), + Place::Type(meta_attr_ty, meta_attr_boundness), AttributeKind::NormalOrNonDataDescriptor, - Symbol::Type(fallback_ty, fallback_boundness), - ) => Symbol::Type( + Place::Type(fallback_ty, fallback_boundness), + ) => Place::Type( UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), meta_attr_boundness.max(fallback_boundness), ) .with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)), // If the attribute is not found on the meta-type, we simply return the fallback. - (Symbol::Unbound, _, fallback) => fallback.with_qualifiers(fallback_qualifiers), + (Place::Unbound, _, fallback) => fallback.with_qualifiers(fallback_qualifiers), } } @@ -3026,7 +3077,7 @@ impl<'db> Type<'db> { /// TODO: We should return a `Result` here to handle errors that can appear during attribute /// lookup, like a failed `__get__` call on a descriptor. #[must_use] - pub(crate) fn member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + pub(crate) fn member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { self.member_lookup_with_policy(db, name.into(), MemberLookupPolicy::default()) } @@ -3038,10 +3089,10 @@ impl<'db> Type<'db> { db: &'db dyn Db, name: Name, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { tracing::trace!("member_lookup_with_policy: {}.{}", self.display(db), name); if name == "__class__" { - return Symbol::bound(self.to_meta_type(db)).into(); + return Place::bound(self.to_meta_type(db)).into(); } let name_str = name.as_str(); @@ -3050,36 +3101,36 @@ impl<'db> Type<'db> { Type::Union(union) => union .map_with_boundness(db, |elem| { elem.member_lookup_with_policy(db, name_str.into(), policy) - .symbol + .place }) .into(), Type::Intersection(intersection) => intersection .map_with_boundness(db, |elem| { elem.member_lookup_with_policy(db, name_str.into(), policy) - .symbol + .place }) .into(), - Type::Dynamic(..) | Type::Never => Symbol::bound(self).into(), + Type::Dynamic(..) | Type::Never => Place::bound(self).into(), - Type::FunctionLiteral(function) if name == "__get__" => Symbol::bound( + Type::FunctionLiteral(function) if name == "__get__" => Place::bound( Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)), ) .into(), - Type::FunctionLiteral(function) if name == "__call__" => Symbol::bound( + Type::FunctionLiteral(function) if name == "__call__" => Place::bound( Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)), ) .into(), - Type::PropertyInstance(property) if name == "__get__" => Symbol::bound( + Type::PropertyInstance(property) if name == "__get__" => Place::bound( Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)), ) .into(), - Type::PropertyInstance(property) if name == "__set__" => Symbol::bound( + Type::PropertyInstance(property) if name == "__set__" => Place::bound( Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)), ) .into(), - Type::StringLiteral(literal) if name == "startswith" => Symbol::bound( + Type::StringLiteral(literal) if name == "startswith" => Place::bound( Type::MethodWrapper(MethodWrapperKind::StrStartswith(literal)), ) .into(), @@ -3087,7 +3138,7 @@ impl<'db> Type<'db> { Type::ClassLiteral(class) if name == "__get__" && class.is_known(db, KnownClass::FunctionType) => { - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::FunctionTypeDunderGet, )) .into() @@ -3095,7 +3146,7 @@ impl<'db> Type<'db> { Type::ClassLiteral(class) if name == "__get__" && class.is_known(db, KnownClass::Property) => { - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::PropertyDunderGet, )) .into() @@ -3103,16 +3154,14 @@ impl<'db> Type<'db> { Type::ClassLiteral(class) if name == "__set__" && class.is_known(db, KnownClass::Property) => { - Symbol::bound(Type::WrapperDescriptor( + Place::bound(Type::WrapperDescriptor( WrapperDescriptorKind::PropertyDunderSet, )) .into() } Type::BoundMethod(bound_method) => match name_str { - "__self__" => Symbol::bound(bound_method.self_instance(db)).into(), - "__func__" => { - Symbol::bound(Type::FunctionLiteral(bound_method.function(db))).into() - } + "__self__" => Place::bound(bound_method.self_instance(db)).into(), + "__func__" => Place::bound(Type::FunctionLiteral(bound_method.function(db))).into(), _ => { KnownClass::MethodType .to_instance(db) @@ -3136,7 +3185,7 @@ impl<'db> Type<'db> { .member_lookup_with_policy(db, name, policy), Type::Callable(_) | Type::DataclassTransformer(_) if name_str == "__call__" => { - Symbol::bound(self).into() + Place::bound(self).into() } Type::Callable(callable) if callable.is_function_like(db) => KnownClass::FunctionType @@ -3157,22 +3206,22 @@ impl<'db> Type<'db> { } else { python_version.minor }; - Symbol::bound(Type::IntLiteral(segment.into())).into() + Place::bound(Type::IntLiteral(segment.into())).into() } Type::PropertyInstance(property) if name == "fget" => { - Symbol::bound(property.getter(db).unwrap_or(Type::none(db))).into() + Place::bound(property.getter(db).unwrap_or(Type::none(db))).into() } Type::PropertyInstance(property) if name == "fset" => { - Symbol::bound(property.setter(db).unwrap_or(Type::none(db))).into() + Place::bound(property.setter(db).unwrap_or(Type::none(db))).into() } Type::IntLiteral(_) if matches!(name_str, "real" | "numerator") => { - Symbol::bound(self).into() + Place::bound(self).into() } Type::BooleanLiteral(bool_value) if matches!(name_str, "real" | "numerator") => { - Symbol::bound(Type::IntLiteral(i64::from(bool_value))).into() + Place::bound(Type::IntLiteral(i64::from(bool_value))).into() } Type::ModuleLiteral(module) => module.static_member(db, name_str).into(), @@ -3184,7 +3233,7 @@ impl<'db> Type<'db> { _ if policy.no_instance_fallback() => self.invoke_descriptor_protocol( db, name_str, - Symbol::Unbound.into(), + Place::Unbound.into(), InstanceFallbackShadowsNonDataDescriptor::No, policy, ), @@ -3225,7 +3274,7 @@ impl<'db> Type<'db> { .and_then(|instance| instance.class.known(db)), Some(KnownClass::ModuleType | KnownClass::GenericAlias) ) { - return Symbol::Unbound.into(); + return Place::Unbound.into(); } self.try_call_dunder( @@ -3235,16 +3284,16 @@ impl<'db> Type<'db> { StringLiteralType::new(db, Box::from(name.as_str())), )]), ) - .map(|outcome| Symbol::bound(outcome.return_type(db))) + .map(|outcome| Place::bound(outcome.return_type(db))) // TODO: Handle call errors here. - .unwrap_or(Symbol::Unbound) + .unwrap_or(Place::Unbound) .into() }; let custom_getattribute_result = || { // Avoid cycles when looking up `__getattribute__` if "__getattribute__" == name.as_str() { - return Symbol::Unbound.into(); + return Place::Unbound.into(); } // Typeshed has a `__getattribute__` method defined on `builtins.object` so we @@ -3257,25 +3306,25 @@ impl<'db> Type<'db> { )]), MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) - .map(|outcome| Symbol::bound(outcome.return_type(db))) + .map(|outcome| Place::bound(outcome.return_type(db))) // TODO: Handle call errors here. - .unwrap_or(Symbol::Unbound) + .unwrap_or(Place::Unbound) .into() }; match result { - member @ SymbolAndQualifiers { - symbol: Symbol::Type(_, Boundness::Bound), + member @ PlaceAndQualifiers { + place: Place::Type(_, Boundness::Bound), qualifiers: _, } => member, - member @ SymbolAndQualifiers { - symbol: Symbol::Type(_, Boundness::PossiblyUnbound), + member @ PlaceAndQualifiers { + place: Place::Type(_, Boundness::PossiblyUnbound), qualifiers: _, } => member .or_fall_back_to(db, custom_getattribute_result) .or_fall_back_to(db, custom_getattr_result), - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, qualifiers: _, } => custom_getattribute_result().or_fall_back_to(db, custom_getattr_result), } @@ -3291,7 +3340,7 @@ impl<'db> Type<'db> { } if self.is_subtype_of(db, KnownClass::Enum.to_subclass_of(db)) { - return SymbolAndQualifiers::todo("Attribute access on enum classes"); + return PlaceAndQualifiers::todo("Attribute access on enum classes"); } let class_attr_fallback = Self::try_call_dunder_get_on_attribute( @@ -4366,9 +4415,9 @@ impl<'db> Type<'db> { Name::new_static("__call__"), MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) - .symbol + .place { - Symbol::Type(dunder_callable, boundness) => { + Place::Type(dunder_callable, boundness) => { let mut bindings = dunder_callable.bindings(db); bindings.replace_callable_type(dunder_callable, self); if boundness == Boundness::PossiblyUnbound { @@ -4376,7 +4425,7 @@ impl<'db> Type<'db> { } bindings } - Symbol::Unbound => CallableBinding::not_callable(self).into(), + Place::Unbound => CallableBinding::not_callable(self).into(), } } @@ -4478,9 +4527,9 @@ impl<'db> Type<'db> { name.into(), policy | MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) - .symbol + .place { - Symbol::Type(dunder_callable, boundness) => { + Place::Type(dunder_callable, boundness) => { let bindings = dunder_callable .bindings(db) .match_parameters(argument_types) @@ -4490,7 +4539,7 @@ impl<'db> Type<'db> { } Ok(bindings) } - Symbol::Unbound => Err(CallDunderError::MethodNotAvailable), + Place::Unbound => Err(CallDunderError::MethodNotAvailable), } } @@ -4722,8 +4771,8 @@ impl<'db> Type<'db> { | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, ); let new_call_outcome = new_method.and_then(|new_method| { - match new_method.symbol.try_call_dunder_get(db, self_type) { - Symbol::Type(new_method, boundness) => { + match new_method.place.try_call_dunder_get(db, self_type) { + Place::Type(new_method, boundness) => { let result = new_method.try_call(db, argument_types.with_self(Some(self_type)).as_ref()); if boundness == Boundness::PossiblyUnbound { @@ -4732,7 +4781,7 @@ impl<'db> Type<'db> { Some(result.map_err(DunderNewCallError::CallError)) } } - Symbol::Unbound => None, + Place::Unbound => None, } }); @@ -4751,7 +4800,7 @@ impl<'db> Type<'db> { MemberLookupPolicy::NO_INSTANCE_FALLBACK | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) - .symbol + .place .is_unbound() { Some(init_ty.try_call_dunder(db, "__init__", argument_types)) @@ -5024,9 +5073,9 @@ impl<'db> Type<'db> { ], }); }; - let instance = Type::ClassLiteral(class) - .to_instance(db) - .expect("enclosing_class_symbol must return type that can be instantiated"); + let instance = Type::ClassLiteral(class).to_instance(db).expect( + "nearest_enclosing_class must return type that can be instantiated", + ); Ok(Type::TypeVar(TypeVarInstance::new( db, ast::name::Name::new("Self"), @@ -5685,6 +5734,20 @@ impl<'db> Type<'db> { _ => None, } } + + pub(crate) fn generic_origin(self, db: &'db dyn Db) -> Option> { + match self { + Type::GenericAlias(generic) => Some(generic.origin(db)), + Type::NominalInstance(instance) => { + if let ClassType::Generic(generic) = instance.class { + Some(generic.origin(db)) + } else { + None + } + } + _ => None, + } + } } impl<'db> From<&Type<'db>> for Type<'db> { @@ -5902,7 +5965,7 @@ bitflags! { /// When inferring the type of an annotation expression, we can also encounter type qualifiers /// such as `ClassVar` or `Final`. These do not affect the inferred type itself, but rather -/// control how a particular symbol can be accessed or modified. This struct holds a type and +/// control how a particular place can be accessed or modified. This struct holds a type and /// a set of type qualifiers. /// /// Example: `Annotated[ClassVar[tuple[int]], "metadata"]` would have type `tuple[int]` and the @@ -6073,7 +6136,7 @@ impl<'db> InvalidTypeExpression<'db> { }; let Some(module_member_with_same_name) = ty .member(db, module_name_final_part) - .symbol + .place .ignore_possibly_unbound() else { return; @@ -7001,6 +7064,14 @@ impl Truthiness { } } + pub(crate) fn or(self, other: Self) -> Self { + match (self, other) { + (Truthiness::AlwaysFalse, Truthiness::AlwaysFalse) => Truthiness::AlwaysFalse, + (Truthiness::AlwaysTrue, _) | (_, Truthiness::AlwaysTrue) => Truthiness::AlwaysTrue, + _ => Truthiness::Ambiguous, + } + } + fn into_type(self, db: &dyn Db) -> Type { match self { Self::AlwaysTrue => Type::BooleanLiteral(true), @@ -7450,7 +7521,7 @@ pub struct ModuleLiteralType<'db> { } impl<'db> ModuleLiteralType<'db> { - fn static_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> { + fn static_member(self, db: &'db dyn Db, name: &str) -> Place<'db> { // `__dict__` is a very special member that is never overridden by module globals; // we should always look it up directly as an attribute on `types.ModuleType`, // never in the global scope of the module. @@ -7458,7 +7529,7 @@ impl<'db> ModuleLiteralType<'db> { return KnownClass::ModuleType .to_instance(db) .member(db, "__dict__") - .symbol; + .place; } // If the file that originally imported the module has also imported a submodule @@ -7477,7 +7548,7 @@ impl<'db> ModuleLiteralType<'db> { full_submodule_name.extend(&submodule_name); if imported_submodules.contains(&full_submodule_name) { if let Some(submodule) = resolve_module(db, &full_submodule_name) { - return Symbol::bound(Type::module_literal(db, importing_file, &submodule)); + return Place::bound(Type::module_literal(db, importing_file, &submodule)); } } } @@ -7486,7 +7557,7 @@ impl<'db> ModuleLiteralType<'db> { .file() .map(|file| imported_symbol(db, file, name, None)) .unwrap_or_default() - .symbol + .place } } @@ -7638,8 +7709,8 @@ impl<'db> UnionType<'db> { pub(crate) fn map_with_boundness( self, db: &'db dyn Db, - mut transform_fn: impl FnMut(&Type<'db>) -> Symbol<'db>, - ) -> Symbol<'db> { + mut transform_fn: impl FnMut(&Type<'db>) -> Place<'db>, + ) -> Place<'db> { let mut builder = UnionBuilder::new(db); let mut all_unbound = true; @@ -7647,10 +7718,10 @@ impl<'db> UnionType<'db> { for ty in self.elements(db) { let ty_member = transform_fn(ty); match ty_member { - Symbol::Unbound => { + Place::Unbound => { possibly_unbound = true; } - Symbol::Type(ty_member, member_boundness) => { + Place::Type(ty_member, member_boundness) => { if member_boundness == Boundness::PossiblyUnbound { possibly_unbound = true; } @@ -7662,9 +7733,9 @@ impl<'db> UnionType<'db> { } if all_unbound { - Symbol::Unbound + Place::Unbound } else { - Symbol::Type( + Place::Type( builder.build(), if possibly_unbound { Boundness::PossiblyUnbound @@ -7678,24 +7749,24 @@ impl<'db> UnionType<'db> { pub(crate) fn map_with_boundness_and_qualifiers( self, db: &'db dyn Db, - mut transform_fn: impl FnMut(&Type<'db>) -> SymbolAndQualifiers<'db>, - ) -> SymbolAndQualifiers<'db> { + mut transform_fn: impl FnMut(&Type<'db>) -> PlaceAndQualifiers<'db>, + ) -> PlaceAndQualifiers<'db> { let mut builder = UnionBuilder::new(db); let mut qualifiers = TypeQualifiers::empty(); let mut all_unbound = true; let mut possibly_unbound = false; for ty in self.elements(db) { - let SymbolAndQualifiers { - symbol: ty_member, + let PlaceAndQualifiers { + place: ty_member, qualifiers: new_qualifiers, } = transform_fn(ty); qualifiers |= new_qualifiers; match ty_member { - Symbol::Unbound => { + Place::Unbound => { possibly_unbound = true; } - Symbol::Type(ty_member, member_boundness) => { + Place::Type(ty_member, member_boundness) => { if member_boundness == Boundness::PossiblyUnbound { possibly_unbound = true; } @@ -7705,11 +7776,11 @@ impl<'db> UnionType<'db> { } } } - SymbolAndQualifiers { - symbol: if all_unbound { - Symbol::Unbound + PlaceAndQualifiers { + place: if all_unbound { + Place::Unbound } else { - Symbol::Type( + Place::Type( builder.build(), if possibly_unbound { Boundness::PossiblyUnbound @@ -7950,10 +8021,10 @@ impl<'db> IntersectionType<'db> { pub(crate) fn map_with_boundness( self, db: &'db dyn Db, - mut transform_fn: impl FnMut(&Type<'db>) -> Symbol<'db>, - ) -> Symbol<'db> { + mut transform_fn: impl FnMut(&Type<'db>) -> Place<'db>, + ) -> Place<'db> { if !self.negative(db).is_empty() { - return Symbol::todo("map_with_boundness: intersections with negative contributions"); + return Place::todo("map_with_boundness: intersections with negative contributions"); } let mut builder = IntersectionBuilder::new(db); @@ -7963,8 +8034,8 @@ impl<'db> IntersectionType<'db> { for ty in self.positive(db) { let ty_member = transform_fn(ty); match ty_member { - Symbol::Unbound => {} - Symbol::Type(ty_member, member_boundness) => { + Place::Unbound => {} + Place::Type(ty_member, member_boundness) => { all_unbound = false; if member_boundness == Boundness::Bound { any_definitely_bound = true; @@ -7976,9 +8047,9 @@ impl<'db> IntersectionType<'db> { } if all_unbound { - Symbol::Unbound + Place::Unbound } else { - Symbol::Type( + Place::Type( builder.build(), if any_definitely_bound { Boundness::Bound @@ -7992,10 +8063,10 @@ impl<'db> IntersectionType<'db> { pub(crate) fn map_with_boundness_and_qualifiers( self, db: &'db dyn Db, - mut transform_fn: impl FnMut(&Type<'db>) -> SymbolAndQualifiers<'db>, - ) -> SymbolAndQualifiers<'db> { + mut transform_fn: impl FnMut(&Type<'db>) -> PlaceAndQualifiers<'db>, + ) -> PlaceAndQualifiers<'db> { if !self.negative(db).is_empty() { - return Symbol::todo("map_with_boundness: intersections with negative contributions") + return Place::todo("map_with_boundness: intersections with negative contributions") .into(); } @@ -8005,16 +8076,16 @@ impl<'db> IntersectionType<'db> { let mut any_unbound = false; let mut any_possibly_unbound = false; for ty in self.positive(db) { - let SymbolAndQualifiers { - symbol: member, + let PlaceAndQualifiers { + place: member, qualifiers: new_qualifiers, } = transform_fn(ty); qualifiers |= new_qualifiers; match member { - Symbol::Unbound => { + Place::Unbound => { any_unbound = true; } - Symbol::Type(ty_member, member_boundness) => { + Place::Type(ty_member, member_boundness) => { if member_boundness == Boundness::PossiblyUnbound { any_possibly_unbound = true; } @@ -8024,11 +8095,11 @@ impl<'db> IntersectionType<'db> { } } - SymbolAndQualifiers { - symbol: if any_unbound { - Symbol::Unbound + PlaceAndQualifiers { + place: if any_unbound { + Place::Unbound } else { - Symbol::Type( + Place::Type( builder.build(), if any_possibly_unbound { Boundness::PossiblyUnbound @@ -8400,8 +8471,8 @@ impl<'db> BoundSuperType<'db> { fn try_call_dunder_get_on_attribute( self, db: &'db dyn Db, - attribute: SymbolAndQualifiers<'db>, - ) -> Option> { + attribute: PlaceAndQualifiers<'db>, + ) -> Option> { let owner = self.owner(db); match owner { @@ -8436,7 +8507,7 @@ impl<'db> BoundSuperType<'db> { db: &'db dyn Db, name: &str, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { let owner = self.owner(db); let class = match owner { SuperOwnerKind::Dynamic(_) => { @@ -8461,7 +8532,7 @@ impl<'db> BoundSuperType<'db> { // super(B[int], b_unknown) // ``` match class_literal.generic_context(db) { - Some(_) => Symbol::bound(todo_type!("super in generic class")).into(), + Some(_) => Place::bound(todo_type!("super in generic class")).into(), None => class_literal.class_member_from_mro( db, name, @@ -8489,7 +8560,7 @@ static_assertions::assert_eq_size!(Type, [u8; 16]); pub(crate) mod tests { use super::*; use crate::db::tests::{TestDbBuilder, setup_db}; - use crate::symbol::{global_symbol, typing_extensions_symbol, typing_symbol}; + use crate::place::{global_symbol, typing_extensions_symbol, typing_symbol}; use ruff_db::files::system_path_to_file; use ruff_db::parsed::parsed_module; use ruff_db::system::DbWithWritableSystem as _; @@ -8520,9 +8591,9 @@ pub(crate) mod tests { .build() .unwrap(); - let typing_no_default = typing_symbol(&db, "NoDefault").symbol.expect_type(); + let typing_no_default = typing_symbol(&db, "NoDefault").place.expect_type(); let typing_extensions_no_default = typing_extensions_symbol(&db, "NoDefault") - .symbol + .place .expect_type(); assert_eq!(typing_no_default.display(&db).to_string(), "NoDefault"); @@ -8555,7 +8626,7 @@ pub(crate) mod tests { )?; let bar = system_path_to_file(&db, "src/bar.py")?; - let a = global_symbol(&db, bar, "a").symbol; + let a = global_symbol(&db, bar, "a").place; assert_eq!( a.expect_type(), @@ -8574,7 +8645,7 @@ pub(crate) mod tests { )?; db.clear_salsa_events(); - let a = global_symbol(&db, bar, "a").symbol; + let a = global_symbol(&db, bar, "a").place; assert_eq!( a.expect_type(), diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 3d349abf17f090..14906fd6daa70b 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -12,7 +12,7 @@ use super::{ }; use crate::db::Db; use crate::dunder_all::dunder_all_names; -use crate::symbol::{Boundness, Symbol}; +use crate::place::{Boundness, Place}; use crate::types::diagnostic::{ CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, @@ -770,7 +770,7 @@ impl<'db> Bindings<'db> { // TODO: we could emit a diagnostic here (if default is not set) overload.set_return_type( match instance_ty.static_member(db, attr_name.value(db)) { - Symbol::Type(ty, Boundness::Bound) => { + Place::Type(ty, Boundness::Bound) => { if instance_ty.is_fully_static(db) { ty } else { @@ -782,10 +782,10 @@ impl<'db> Bindings<'db> { union_with_default(ty) } } - Symbol::Type(ty, Boundness::PossiblyUnbound) => { + Place::Type(ty, Boundness::PossiblyUnbound) => { union_with_default(ty) } - Symbol::Unbound => default, + Place::Unbound => default, }, ); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 205e587fad5942..ffc558e969724a 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -7,7 +7,7 @@ use super::{ infer_unpack_types, }; use crate::semantic_index::DeclarationWithConstraint; -use crate::semantic_index::definition::Definition; +use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; @@ -17,17 +17,16 @@ use crate::types::{ use crate::{ Db, FxOrderSet, KnownModule, Program, module_resolver::file_to_module, + place::{ + Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, class_symbol, + known_module_symbol, place_from_bindings, place_from_declarations, + }, semantic_index::{ ast_ids::HasScopedExpressionId, attribute_assignments, definition::{DefinitionKind, TargetKind}, - semantic_index, - symbol::ScopeId, - symbol_table, use_def_map, - }, - symbol::{ - Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers, class_symbol, - known_module_symbol, symbol_from_bindings, symbol_from_declarations, + place::ScopeId, + place_table, semantic_index, use_def_map, }, types::{ CallArgumentTypes, CallError, CallErrorKind, DynamicType, MetaclassCandidate, TupleType, @@ -454,7 +453,7 @@ impl<'db> ClassType<'db> { db: &'db dyn Db, name: &str, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { let (class_literal, specialization) = self.class_literal(db); class_literal.class_member_inner(db, specialization, name, policy) } @@ -462,10 +461,10 @@ impl<'db> ClassType<'db> { /// Returns the inferred type of the class member named `name`. Only bound members /// or those marked as ClassVars are considered. /// - /// Returns [`Symbol::Unbound`] if `name` cannot be found in this class's scope + /// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope /// directly. Use [`ClassType::class_member`] if you require a method that will /// traverse through the MRO until it finds the member. - pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { let (class_literal, specialization) = self.class_literal(db); class_literal .own_class_member(db, specialization, name) @@ -475,7 +474,7 @@ impl<'db> ClassType<'db> { /// Look up an instance attribute (available in `__dict__`) of the given name. /// /// See [`Type::instance_member`] for more details. - pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { let (class_literal, specialization) = self.class_literal(db); class_literal .instance_member(db, specialization, name) @@ -484,7 +483,7 @@ impl<'db> ClassType<'db> { /// A helper function for `instance_member` that looks up the `name` attribute only on /// this class, not on its superclasses. - fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + fn own_instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { let (class_literal, specialization) = self.class_literal(db); class_literal .own_instance_member(db, name) @@ -502,9 +501,9 @@ impl<'db> ClassType<'db> { MemberLookupPolicy::NO_INSTANCE_FALLBACK | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, ) - .symbol; + .place; - if let Symbol::Type(Type::BoundMethod(metaclass_dunder_call_function), _) = + if let Place::Type(Type::BoundMethod(metaclass_dunder_call_function), _) = metaclass_dunder_call_function_symbol { // TODO: this intentionally diverges from step 1 in @@ -520,10 +519,10 @@ impl<'db> ClassType<'db> { "__new__".into(), MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) - .symbol; + .place; let dunder_new_function = - if let Symbol::Type(Type::FunctionLiteral(dunder_new_function), _) = + if let Place::Type(Type::FunctionLiteral(dunder_new_function), _) = dunder_new_function_symbol { // Step 3: If the return type of the `__new__` evaluates to a type that is not a subclass of this class, @@ -562,7 +561,7 @@ impl<'db> ClassType<'db> { MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, ) - .symbol; + .place; let correct_return_type = self_ty.to_instance(db).unwrap_or_else(Type::unknown); @@ -570,7 +569,7 @@ impl<'db> ClassType<'db> { // same parameters as the `__init__` method after it is bound, and with the return type of // the concrete type of `Self`. let synthesized_dunder_init_callable = - if let Symbol::Type(Type::FunctionLiteral(dunder_init_function), _) = + if let Place::Type(Type::FunctionLiteral(dunder_init_function), _) = dunder_init_function_symbol { let synthesized_signature = |signature: Signature<'db>| { @@ -612,9 +611,9 @@ impl<'db> ClassType<'db> { "__new__".into(), MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK, ) - .symbol; + .place; - if let Symbol::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol { + if let Place::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol { new_function.into_bound_method_type(db, self_ty) } else { // Fallback if no `object.__new__` is found. @@ -1136,7 +1135,7 @@ impl<'db> ClassLiteral<'db> { db: &'db dyn Db, name: &str, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { self.class_member_inner(db, None, name, policy) } @@ -1146,10 +1145,10 @@ impl<'db> ClassLiteral<'db> { specialization: Option>, name: &str, policy: MemberLookupPolicy, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { if name == "__mro__" { let tuple_elements = self.iter_mro(db, specialization).map(Type::from); - return Symbol::bound(TupleType::from_elements(db, tuple_elements)).into(); + return Place::bound(TupleType::from_elements(db, tuple_elements)).into(); } self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization)) @@ -1161,7 +1160,7 @@ impl<'db> ClassLiteral<'db> { name: &str, policy: MemberLookupPolicy, mro_iter: impl Iterator>, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { // If we encounter a dynamic type in this class's MRO, we'll save that dynamic type // in this variable. After we've traversed the MRO, we'll either: // (1) Use that dynamic type as the type for this attribute, @@ -1208,18 +1207,18 @@ impl<'db> ClassLiteral<'db> { } match ( - SymbolAndQualifiers::from(lookup_result), + PlaceAndQualifiers::from(lookup_result), dynamic_type_to_intersect_with, ) { (symbol_and_qualifiers, None) => symbol_and_qualifiers, ( - SymbolAndQualifiers { - symbol: Symbol::Type(ty, _), + PlaceAndQualifiers { + place: Place::Type(ty, _), qualifiers, }, Some(dynamic_type), - ) => Symbol::bound( + ) => Place::bound( IntersectionBuilder::new(db) .add_positive(ty) .add_positive(dynamic_type) @@ -1228,19 +1227,19 @@ impl<'db> ClassLiteral<'db> { .with_qualifiers(qualifiers), ( - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, qualifiers, }, Some(dynamic_type), - ) => Symbol::bound(dynamic_type).with_qualifiers(qualifiers), + ) => Place::bound(dynamic_type).with_qualifiers(qualifiers), } } /// Returns the inferred type of the class member named `name`. Only bound members /// or those marked as ClassVars are considered. /// - /// Returns [`Symbol::Unbound`] if `name` cannot be found in this class's scope + /// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope /// directly. Use [`ClassLiteral::class_member`] if you require a method that will /// traverse through the MRO until it finds the member. pub(super) fn own_class_member( @@ -1248,10 +1247,10 @@ impl<'db> ClassLiteral<'db> { db: &'db dyn Db, specialization: Option>, name: &str, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { if name == "__dataclass_fields__" && self.dataclass_params(db).is_some() { // Make this class look like a subclass of the `DataClassInstance` protocol - return Symbol::bound(KnownClass::Dict.to_specialized_instance( + return Place::bound(KnownClass::Dict.to_specialized_instance( db, [ KnownClass::Str.to_instance(db), @@ -1287,10 +1286,10 @@ impl<'db> ClassLiteral<'db> { } }); - if symbol.symbol.is_unbound() { + if symbol.place.is_unbound() { if let Some(synthesized_member) = self.own_synthesized_member(db, specialization, name) { - return Symbol::bound(synthesized_member).into(); + return Place::bound(synthesized_member).into(); } } @@ -1322,7 +1321,7 @@ impl<'db> ClassLiteral<'db> { // itself in this case, so we skip the special descriptor handling. if attr_ty.is_fully_static(db) { let dunder_set = attr_ty.class_member(db, "__set__".into()); - if let Some(dunder_set) = dunder_set.symbol.ignore_possibly_unbound() { + if let Some(dunder_set) = dunder_set.place.ignore_possibly_unbound() { // This type of this attribute is a data descriptor. Instead of overwriting the // descriptor attribute, data-classes will (implicitly) call the `__set__` method // of the descriptor. This means that the synthesized `__init__` parameter for @@ -1428,7 +1427,7 @@ impl<'db> ClassLiteral<'db> { .to_class_literal(db) .into_class_literal()? .own_class_member(db, None, name) - .symbol + .place .ignore_possibly_unbound() } _ => None, @@ -1490,10 +1489,10 @@ impl<'db> ClassLiteral<'db> { let mut attributes = FxOrderMap::default(); let class_body_scope = self.body_scope(db); - let table = symbol_table(db, class_body_scope); + let table = place_table(db, class_body_scope); let use_def = use_def_map(db, class_body_scope); - for (symbol_id, declarations) in use_def.all_public_declarations() { + for (place_id, declarations) in use_def.all_public_declarations() { // Here, we exclude all declarations that are not annotated assignments. We need this because // things like function definitions and nested classes would otherwise be considered dataclass // fields. The check is too broad in the sense that it also excludes (weird) constructs where @@ -1504,7 +1503,7 @@ impl<'db> ClassLiteral<'db> { if !declarations .clone() .all(|DeclarationWithConstraint { declaration, .. }| { - declaration.is_some_and(|declaration| { + declaration.is_defined_and(|declaration| { matches!( declaration.kind(db), DefinitionKind::AnnotatedAssignment(..) @@ -1515,18 +1514,18 @@ impl<'db> ClassLiteral<'db> { continue; } - let symbol = table.symbol(symbol_id); + let place_expr = table.place_expr(place_id); - if let Ok(attr) = symbol_from_declarations(db, declarations) { + if let Ok(attr) = place_from_declarations(db, declarations) { if attr.is_class_var() { continue; } - if let Some(attr_ty) = attr.symbol.ignore_possibly_unbound() { - let bindings = use_def.public_bindings(symbol_id); - let default_ty = symbol_from_bindings(db, bindings).ignore_possibly_unbound(); + if let Some(attr_ty) = attr.place.ignore_possibly_unbound() { + let bindings = use_def.public_bindings(place_id); + let default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound(); - attributes.insert(symbol.name().clone(), (attr_ty, default_ty)); + attributes.insert(place_expr.expect_name().clone(), (attr_ty, default_ty)); } } } @@ -1542,7 +1541,7 @@ impl<'db> ClassLiteral<'db> { db: &'db dyn Db, specialization: Option>, name: &str, - ) -> SymbolAndQualifiers<'db> { + ) -> PlaceAndQualifiers<'db> { let mut union = UnionBuilder::new(db); let mut union_qualifiers = TypeQualifiers::empty(); @@ -1552,13 +1551,13 @@ impl<'db> ClassLiteral<'db> { // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { - return SymbolAndQualifiers::todo( + return PlaceAndQualifiers::todo( "instance attribute on class with dynamic base", ); } ClassBase::Class(class) => { - if let member @ SymbolAndQualifiers { - symbol: Symbol::Type(ty, boundness), + if let member @ PlaceAndQualifiers { + place: Place::Type(ty, boundness), qualifiers, } = class.own_instance_member(db, name) { @@ -1571,7 +1570,7 @@ impl<'db> ClassLiteral<'db> { return member; } - return Symbol::bound(union.add(ty).build()) + return Place::bound(union.add(ty).build()) .with_qualifiers(union_qualifiers); } @@ -1584,13 +1583,12 @@ impl<'db> ClassLiteral<'db> { } if union.is_empty() { - Symbol::Unbound.with_qualifiers(TypeQualifiers::empty()) + Place::Unbound.with_qualifiers(TypeQualifiers::empty()) } else { - // If we have reached this point, we know that we have only seen possibly-unbound symbols. + // If we have reached this point, we know that we have only seen possibly-unbound places. // This means that the final result is still possibly-unbound. - Symbol::Type(union.build(), Boundness::PossiblyUnbound) - .with_qualifiers(union_qualifiers) + Place::Type(union.build(), Boundness::PossiblyUnbound).with_qualifiers(union_qualifiers) } } @@ -1600,7 +1598,7 @@ impl<'db> ClassLiteral<'db> { db: &'db dyn Db, class_body_scope: ScopeId<'db>, name: &str, - ) -> Symbol<'db> { + ) -> Place<'db> { // If we do not see any declarations of an attribute, neither in the class body nor in // any method, we build a union of `Unknown` with the inferred types of all bindings of // that attribute. We include `Unknown` in that union to account for the fact that the @@ -1612,7 +1610,7 @@ impl<'db> ClassLiteral<'db> { let file = class_body_scope.file(db); let index = semantic_index(db, file); let class_map = use_def_map(db, class_body_scope); - let class_table = symbol_table(db, class_body_scope); + let class_table = place_table(db, class_body_scope); for (attribute_assignments, method_scope_id) in attribute_assignments(db, class_body_scope, name) @@ -1623,11 +1621,11 @@ impl<'db> ClassLiteral<'db> { // The attribute assignment inherits the visibility of the method which contains it let is_method_visible = if let Some(method_def) = method_scope.node(db).as_function() { let method = index.expect_single_definition(method_def); - let method_symbol = class_table.symbol_id_by_name(&method_def.name).unwrap(); + let method_place = class_table.place_id_by_name(&method_def.name).unwrap(); class_map - .public_bindings(method_symbol) + .public_bindings(method_place) .find_map(|bind| { - (bind.binding == Some(method)) + (bind.binding.is_defined_and(|def| def == method)) .then(|| class_map.is_binding_visible(db, &bind)) }) .unwrap_or(Truthiness::AlwaysFalse) @@ -1642,7 +1640,7 @@ impl<'db> ClassLiteral<'db> { let unbound_visibility = attribute_assignments .peek() .map(|attribute_assignment| { - if attribute_assignment.binding.is_none() { + if attribute_assignment.binding.is_undefined() { method_map.is_binding_visible(db, attribute_assignment) } else { Truthiness::AlwaysFalse @@ -1651,7 +1649,7 @@ impl<'db> ClassLiteral<'db> { .unwrap_or(Truthiness::AlwaysFalse); for attribute_assignment in attribute_assignments { - let Some(binding) = attribute_assignment.binding else { + let DefinitionState::Defined(binding) = attribute_assignment.binding else { continue; }; match method_map @@ -1696,10 +1694,10 @@ impl<'db> ClassLiteral<'db> { // TODO: check if there are conflicting declarations match is_attribute_bound { Truthiness::AlwaysTrue => { - return Symbol::bound(annotation_ty); + return Place::bound(annotation_ty); } Truthiness::Ambiguous => { - return Symbol::possibly_unbound(annotation_ty); + return Place::possibly_unbound(annotation_ty); } Truthiness::AlwaysFalse => unreachable!( "If the attribute assignments are all invisible, inference of their types should be skipped" @@ -1722,7 +1720,7 @@ impl<'db> ClassLiteral<'db> { union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } - TargetKind::NameOrAttribute => { + TargetKind::Single => { // We found an un-annotated attribute assignment of the form: // // self.name = @@ -1748,7 +1746,7 @@ impl<'db> ClassLiteral<'db> { union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } - TargetKind::NameOrAttribute => { + TargetKind::Single => { // We found an attribute assignment like: // // for self.name in : @@ -1778,7 +1776,7 @@ impl<'db> ClassLiteral<'db> { union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } - TargetKind::NameOrAttribute => { + TargetKind::Single => { // We found an attribute assignment like: // // with as self.name: @@ -1808,7 +1806,7 @@ impl<'db> ClassLiteral<'db> { union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } - TargetKind::NameOrAttribute => { + TargetKind::Single => { // We found an attribute assignment like: // // [... for self.name in ] @@ -1836,42 +1834,42 @@ impl<'db> ClassLiteral<'db> { } match is_attribute_bound { - Truthiness::AlwaysTrue => Symbol::bound(union_of_inferred_types.build()), - Truthiness::Ambiguous => Symbol::possibly_unbound(union_of_inferred_types.build()), - Truthiness::AlwaysFalse => Symbol::Unbound, + Truthiness::AlwaysTrue => Place::bound(union_of_inferred_types.build()), + Truthiness::Ambiguous => Place::possibly_unbound(union_of_inferred_types.build()), + Truthiness::AlwaysFalse => Place::Unbound, } } /// A helper function for `instance_member` that looks up the `name` attribute only on /// this class, not on its superclasses. - fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + fn own_instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { // TODO: There are many things that are not yet implemented here: // - `typing.Final` // - Proper diagnostics let body_scope = self.body_scope(db); - let table = symbol_table(db, body_scope); + let table = place_table(db, body_scope); - if let Some(symbol_id) = table.symbol_id_by_name(name) { + if let Some(place_id) = table.place_id_by_name(name) { let use_def = use_def_map(db, body_scope); - let declarations = use_def.public_declarations(symbol_id); - let declared_and_qualifiers = symbol_from_declarations(db, declarations); + let declarations = use_def.public_declarations(place_id); + let declared_and_qualifiers = place_from_declarations(db, declarations); match declared_and_qualifiers { - Ok(SymbolAndQualifiers { - symbol: mut declared @ Symbol::Type(declared_ty, declaredness), + Ok(PlaceAndQualifiers { + place: mut declared @ Place::Type(declared_ty, declaredness), qualifiers, }) => { // For the purpose of finding instance attributes, ignore `ClassVar` // declarations: if qualifiers.contains(TypeQualifiers::CLASS_VAR) { - declared = Symbol::Unbound; + declared = Place::Unbound; } // The attribute is declared in the class body. - let bindings = use_def.public_bindings(symbol_id); - let inferred = symbol_from_bindings(db, bindings); + let bindings = use_def.public_bindings(place_id); + let inferred = place_from_bindings(db, bindings); let has_binding = !inferred.is_unbound(); if has_binding { @@ -1887,7 +1885,7 @@ impl<'db> ClassLiteral<'db> { // we trust the declared type. declared.with_qualifiers(qualifiers) } else { - Symbol::Type( + Place::Type( UnionType::from_elements(db, [declared_ty, implicit_ty]), declaredness, ) @@ -1900,7 +1898,7 @@ impl<'db> ClassLiteral<'db> { // has a class-level default value, but it would not be // found in a `__dict__` lookup. - Symbol::Unbound.into() + Place::Unbound.into() } } else { // The attribute is declared but not bound in the class body. @@ -1916,7 +1914,7 @@ impl<'db> ClassLiteral<'db> { Self::implicit_instance_attribute(db, body_scope, name) .ignore_possibly_unbound() { - Symbol::Type( + Place::Type( UnionType::from_elements(db, [declared_ty, implicit_ty]), declaredness, ) @@ -1928,8 +1926,8 @@ impl<'db> ClassLiteral<'db> { } } - Ok(SymbolAndQualifiers { - symbol: Symbol::Unbound, + Ok(PlaceAndQualifiers { + place: Place::Unbound, qualifiers: _, }) => { // The attribute is not *declared* in the class body. It could still be declared/bound @@ -1939,7 +1937,7 @@ impl<'db> ClassLiteral<'db> { } Err((declared, _conflicting_declarations)) => { // There are conflicting declarations for this attribute in the class body. - Symbol::bound(declared.inner_type()).with_qualifiers(declared.qualifiers()) + Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers()) } } } else { @@ -2454,16 +2452,16 @@ impl<'db> KnownClass { self, db: &'db dyn Db, ) -> Result, KnownClassLookupError<'db>> { - let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).symbol; + let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place; match symbol { - Symbol::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal), - Symbol::Type(Type::ClassLiteral(class_literal), Boundness::PossiblyUnbound) => { + Place::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal), + Place::Type(Type::ClassLiteral(class_literal), Boundness::PossiblyUnbound) => { Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }) } - Symbol::Type(found_type, _) => { + Place::Type(found_type, _) => { Err(KnownClassLookupError::SymbolNotAClass { found_type }) } - Symbol::Unbound => Err(KnownClassLookupError::ClassNotFound), + Place::Unbound => Err(KnownClassLookupError::ClassNotFound), } } diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index f0596b0318a5b5..498a1b644a664a 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -11,8 +11,8 @@ use ruff_text_size::{Ranged, TextRange}; use super::{Type, TypeCheckDiagnostics, binding_type}; use crate::lint::LintSource; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::semantic_index; -use crate::semantic_index::symbol::ScopeId; use crate::types::function::FunctionDecorators; use crate::{ Db, diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 645a102e84b227..02f38c5701b253 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -794,7 +794,7 @@ mod tests { use crate::Db; use crate::db::tests::setup_db; - use crate::symbol::typing_extensions_symbol; + use crate::place::typing_extensions_symbol; use crate::types::{KnownClass, Parameter, Parameters, Signature, StringLiteralType, Type}; #[test] @@ -833,7 +833,7 @@ mod tests { ); let iterator_synthesized = typing_extensions_symbol(&db, "Iterator") - .symbol + .place .ignore_possibly_unbound() .unwrap() .to_instance(&db) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 2911fbb1d630a3..3b67e9c682fefb 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -58,11 +58,11 @@ use ruff_python_ast as ast; use ruff_text_size::Ranged; use crate::module_resolver::{KnownModule, file_to_module}; +use crate::place::{Boundness, Place, place_from_bindings}; use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::definition::Definition; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::semantic_index; -use crate::semantic_index::symbol::ScopeId; -use crate::symbol::{Boundness, Symbol, symbol_from_bindings}; use crate::types::generics::GenericContext; use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; @@ -234,8 +234,8 @@ impl<'db> OverloadLiteral<'db> { .name .scoped_use_id(db, scope); - let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) = - symbol_from_bindings(db, use_def.bindings_at_use(use_id)) + let Place::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) = + place_from_bindings(db, use_def.bindings_at_use(use_id)) else { return None; }; @@ -927,7 +927,7 @@ pub(crate) mod tests { use super::*; use crate::db::tests::setup_db; - use crate::symbol::known_module_symbol; + use crate::place::known_module_symbol; #[test] fn known_function_roundtrip_from_str() { @@ -977,7 +977,7 @@ pub(crate) mod tests { }; let function_definition = known_module_symbol(&db, module, function_name) - .symbol + .place .expect_type() .expect_function_literal() .definition(&db); diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index c8f150aa437e4e..13a1ec3487fcdb 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -1,9 +1,9 @@ use crate::Db; -use crate::semantic_index::symbol::ScopeId; +use crate::place::{imported_symbol, place_from_bindings, place_from_declarations}; +use crate::semantic_index::place::ScopeId; use crate::semantic_index::{ - attribute_scopes, global_scope, semantic_index, symbol_table, use_def_map, + attribute_scopes, global_scope, place_table, semantic_index, use_def_map, }; -use crate::symbol::{imported_symbol, symbol_from_bindings, symbol_from_declarations}; use crate::types::{ClassBase, ClassLiteral, KnownClass, Type}; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; @@ -13,28 +13,27 @@ pub(crate) fn all_declarations_and_bindings<'db>( scope_id: ScopeId<'db>, ) -> impl Iterator + 'db { let use_def_map = use_def_map(db, scope_id); - let symbol_table = symbol_table(db, scope_id); + let table = place_table(db, scope_id); use_def_map .all_public_declarations() .filter_map(move |(symbol_id, declarations)| { - if symbol_from_declarations(db, declarations) - .is_ok_and(|result| !result.symbol.is_unbound()) - { - Some(symbol_table.symbol(symbol_id).name().clone()) - } else { - None - } + place_from_declarations(db, declarations) + .ok() + .and_then(|result| { + result + .place + .ignore_possibly_unbound() + .and_then(|_| table.place_expr(symbol_id).as_name().cloned()) + }) }) .chain( use_def_map .all_public_bindings() .filter_map(move |(symbol_id, bindings)| { - if symbol_from_bindings(db, bindings).is_unbound() { - None - } else { - Some(symbol_table.symbol(symbol_id).name().clone()) - } + place_from_bindings(db, bindings) + .ignore_possibly_unbound() + .and_then(|_| table.place_expr(symbol_id).as_name().cloned()) }), ) } @@ -132,16 +131,18 @@ impl AllMembers { let module_scope = global_scope(db, file); let use_def_map = use_def_map(db, module_scope); - let symbol_table = symbol_table(db, module_scope); + let place_table = place_table(db, module_scope); for (symbol_id, _) in use_def_map.all_public_declarations() { - let symbol_name = symbol_table.symbol(symbol_id).name(); + let Some(symbol_name) = place_table.place_expr(symbol_id).as_name() else { + continue; + }; if !imported_symbol(db, file, symbol_name, None) - .symbol + .place .is_unbound() { self.members - .insert(symbol_table.symbol(symbol_id).name().clone()); + .insert(place_table.place_expr(symbol_id).expect_name().clone()); } } } @@ -182,9 +183,10 @@ impl AllMembers { let file = class_body_scope.file(db); let index = semantic_index(db, file); for function_scope_id in attribute_scopes(db, class_body_scope) { - let attribute_table = index.instance_attribute_table(function_scope_id); - for symbol in attribute_table.symbols() { - self.members.insert(symbol.name().clone()); + let place_table = index.place_table(function_scope_id); + for instance_attribute in place_table.instance_attributes() { + let name = instance_attribute.sub_segments()[0].as_member().unwrap(); + self.members.insert(name.clone()); } } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 5e485b1346fd34..b0e6a19ddda30a 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5,15 +5,15 @@ //! everything in that file's scopes, or give a linter access to types of arbitrary expressions //! (via the [`HasType`](crate::semantic_model::HasType) trait). //! -//! Definition-level inference allows us to look up the types of symbols in other scopes (e.g. for -//! imports) with the minimum inference necessary, so that if we're looking up one symbol from a +//! Definition-level inference allows us to look up the types of places in other scopes (e.g. for +//! imports) with the minimum inference necessary, so that if we're looking up one place from a //! very large module, we can avoid a bunch of unnecessary work. Definition-level inference also //! allows us to handle import cycles without getting into a cycle of scope-level inference //! queries. //! //! The expression-level inference query is needed in only a few cases. Since some assignments can //! have multiple targets (via `x = y = z` or unpacking `(x, y) = z`, they can be associated with -//! multiple definitions (one per assigned symbol). In order to avoid inferring the type of the +//! multiple definitions (one per assigned place). In order to avoid inferring the type of the //! right-hand side once per definition, we infer it as a standalone query, so its result will be //! cached by Salsa. We also need the expression-level query for inferring types in type guard //! expressions (e.g. the test clause of an `if` statement.) @@ -48,7 +48,15 @@ use salsa::plumbing::AsId; use crate::module_name::{ModuleName, ModuleNameResolutionError}; use crate::module_resolver::resolve_module; use crate::node_key::NodeKey; -use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId, ScopedExpressionId}; +use crate::place::{ + Boundness, LookupError, Place, PlaceAndQualifiers, builtins_module_scope, builtins_symbol, + explicit_global_symbol, global_symbol, module_type_implicit_global_declaration, + module_type_implicit_global_symbol, place, place_from_bindings, place_from_declarations, + typing_extensions_symbol, +}; +use crate::semantic_index::ast_ids::{ + HasScopedExpressionId, HasScopedUseId, ScopedExpressionId, ScopedUseId, +}; use crate::semantic_index::definition::{ AnnotatedAssignmentDefinitionKind, AssignmentDefinitionKind, ComprehensionDefinitionKind, Definition, DefinitionKind, DefinitionNodeKey, ExceptHandlerDefinitionKind, @@ -56,15 +64,10 @@ use crate::semantic_index::definition::{ }; use crate::semantic_index::expression::{Expression, ExpressionKind}; use crate::semantic_index::narrowing_constraints::ConstraintKey; -use crate::semantic_index::symbol::{ - FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind, ScopedSymbolId, +use crate::semantic_index::place::{ + FileScopeId, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, ScopeId, ScopeKind, ScopedPlaceId, }; use crate::semantic_index::{EagerSnapshotResult, SemanticIndex, semantic_index}; -use crate::symbol::{ - Boundness, LookupError, builtins_module_scope, builtins_symbol, explicit_global_symbol, - global_symbol, module_type_implicit_global_declaration, module_type_implicit_global_symbol, - symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, -}; use crate::types::call::{ Argument, Binding, Bindings, CallArgumentTypes, CallArguments, CallError, }; @@ -93,10 +96,10 @@ use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, - ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol, - SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, - TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, - TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, + ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness, + TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, + TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, + UnionType, binding_type, todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -154,7 +157,7 @@ fn scope_cycle_initial<'db>(_db: &'db dyn Db, scope: ScopeId<'db>) -> TypeInfere } /// Infer all types for a [`Definition`] (including sub-expressions). -/// Use when resolving a symbol name use or public type of a symbol. +/// Use when resolving a place use or public type of a place. #[salsa::tracked(returns(ref), cycle_fn=definition_cycle_recover, cycle_initial=definition_cycle_initial)] pub(crate) fn infer_definition_types<'db>( db: &'db dyn Db, @@ -1087,10 +1090,10 @@ impl<'db> TypeInferenceBuilder<'db> { /// For (1), this has the consequence of not checking an overloaded function that is being /// shadowed by another function with the same name in this scope. fn check_overloaded_functions(&mut self, scope: &NodeWithScopeKind) { - // Collect all the unique overloaded function symbols in this scope. This requires a set - // because an overloaded function uses the same symbol for each of the overloads and the + // Collect all the unique overloaded function places in this scope. This requires a set + // because an overloaded function uses the same place for each of the overloads and the // implementation. - let overloaded_function_symbols: FxHashSet<_> = self + let overloaded_function_places: FxHashSet<_> = self .types .declarations .iter() @@ -1102,7 +1105,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let function = ty.inner_type().into_function_literal()?; if function.has_known_decorator(self.db(), FunctionDecorators::OVERLOAD) { - Some(definition.symbol(self.db())) + Some(definition.place(self.db())) } else { None } @@ -1115,9 +1118,9 @@ impl<'db> TypeInferenceBuilder<'db> { let mut public_functions = FxHashSet::default(); - for symbol in overloaded_function_symbols { - if let Symbol::Type(Type::FunctionLiteral(function), Boundness::Bound) = - symbol_from_bindings(self.db(), use_def.public_bindings(symbol)) + for place in overloaded_function_places { + if let Place::Type(Type::FunctionLiteral(function), Boundness::Bound) = + place_from_bindings(self.db(), use_def.public_bindings(place)) { if function.file(self.db()) != self.file() { // If the function is not in this file, we don't need to check it. @@ -1442,20 +1445,21 @@ impl<'db> TypeInferenceBuilder<'db> { .is_binding() ); - let file_scope_id = binding.file_scope(self.db()); - let symbol_table = self.index.symbol_table(file_scope_id); + let db = self.db(); + let file_scope_id = binding.file_scope(db); + let place_table = self.index.place_table(file_scope_id); let use_def = self.index.use_def_map(file_scope_id); let mut bound_ty = ty; - let symbol_id = binding.symbol(self.db()); let global_use_def_map = self.index.use_def_map(FileScopeId::global()); - let symbol_name = symbol_table.symbol(symbol_id).name(); - let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, symbol_id); + let place_id = binding.place(self.db()); + let expr = place_table.place_expr(place_id); + let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, place_id); let declarations = if skip_non_global_scopes { match self .index - .symbol_table(FileScopeId::global()) - .symbol_id_by_name(symbol_name) + .place_table(FileScopeId::global()) + .place_id_by_expr(expr) { Some(id) => global_use_def_map.public_declarations(id), // This case is a syntax error (load before global declaration) but ignore that here @@ -1465,37 +1469,66 @@ impl<'db> TypeInferenceBuilder<'db> { use_def.declarations_at_binding(binding) }; - let declared_ty = symbol_from_declarations(self.db(), declarations) - .and_then(|symbol| { - let symbol = if matches!(symbol.symbol, Symbol::Type(_, Boundness::Bound)) { - symbol + let declared_ty = place_from_declarations(self.db(), declarations) + .and_then(|place| { + Ok(if matches!(place.place, Place::Type(_, Boundness::Bound)) { + place } else if skip_non_global_scopes || self.scope().file_scope_id(self.db()).is_global() { let module_type_declarations = - module_type_implicit_global_declaration(self.db(), symbol_name)?; - symbol.or_fall_back_to(self.db(), || module_type_declarations) + module_type_implicit_global_declaration(self.db(), expr)?; + place.or_fall_back_to(self.db(), || module_type_declarations) } else { - symbol - }; - Ok(symbol) - }) - .map(|SymbolAndQualifiers { symbol, .. }| { - symbol.ignore_possibly_unbound().unwrap_or(Type::unknown()) + place + }) }) + .map( + |PlaceAndQualifiers { + place: resolved_place, + .. + }| { + if resolved_place.is_unbound() && !place_table.place_expr(place_id).is_name() { + if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { + value, attr, .. + }) = node + { + let value_type = self.infer_maybe_standalone_expression(value); + if let Place::Type(ty, Boundness::Bound) = + value_type.member(db, attr).place + { + return ty; + } + } else if let AnyNodeRef::ExprSubscript(ast::ExprSubscript { + value, + slice, + .. + }) = node + { + let value_ty = self.infer_expression(value); + let slice_ty = self.infer_expression(slice); + let result_ty = + self.infer_subscript_expression_types(value, value_ty, slice_ty); + return result_ty; + } + } + resolved_place + .ignore_possibly_unbound() + .unwrap_or(Type::unknown()) + }, + ) .unwrap_or_else(|(ty, conflicting)| { // TODO point out the conflicting declarations in the diagnostic? - let symbol_table = self.index.symbol_table(binding.file_scope(self.db())); - let symbol_name = symbol_table.symbol(binding.symbol(self.db())).name(); + let expr = place_table.place_expr(binding.place(db)); if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) { builder.into_diagnostic(format_args!( - "Conflicting declared types for `{symbol_name}`: {}", - conflicting.display(self.db()) + "Conflicting declared types for `{expr}`: {}", + conflicting.display(db) )); } ty.inner_type() }); - if !bound_ty.is_assignable_to(self.db(), declared_ty) { + if !bound_ty.is_assignable_to(db, declared_ty) { report_invalid_assignment(&self.context, node, declared_ty, bound_ty); // allow declarations to override inference in case of invalid assignment bound_ty = declared_ty; @@ -1506,11 +1539,7 @@ impl<'db> TypeInferenceBuilder<'db> { /// Returns `true` if `symbol_id` should be looked up in the global scope, skipping intervening /// local scopes. - fn skip_non_global_scopes( - &self, - file_scope_id: FileScopeId, - symbol_id: ScopedSymbolId, - ) -> bool { + fn skip_non_global_scopes(&self, file_scope_id: FileScopeId, symbol_id: ScopedPlaceId) -> bool { !file_scope_id.is_global() && self .index @@ -1532,24 +1561,20 @@ impl<'db> TypeInferenceBuilder<'db> { let use_def = self.index.use_def_map(declaration.file_scope(self.db())); let prior_bindings = use_def.bindings_at_declaration(declaration); // unbound_ty is Never because for this check we don't care about unbound - let inferred_ty = symbol_from_bindings(self.db(), prior_bindings) + let inferred_ty = place_from_bindings(self.db(), prior_bindings) .with_qualifiers(TypeQualifiers::empty()) .or_fall_back_to(self.db(), || { // Fallback to bindings declared on `types.ModuleType` if it's a global symbol let scope = self.scope().file_scope_id(self.db()); - if scope.is_global() { - module_type_implicit_global_symbol( - self.db(), - self.index - .symbol_table(scope) - .symbol(declaration.symbol(self.db())) - .name(), - ) + let place_table = self.index.place_table(scope); + let expr = place_table.place_expr(declaration.place(self.db())); + if scope.is_global() && expr.is_name() { + module_type_implicit_global_symbol(self.db(), expr.expect_name()) } else { - Symbol::Unbound.into() + Place::Unbound.into() } }) - .symbol + .place .ignore_possibly_unbound() .unwrap_or(Type::Never); let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_type()) { @@ -1594,12 +1619,12 @@ impl<'db> TypeInferenceBuilder<'db> { } => { let file_scope_id = self.scope().file_scope_id(self.db()); if file_scope_id.is_global() { - let symbol_table = self.index.symbol_table(file_scope_id); - let symbol_name = symbol_table.symbol(definition.symbol(self.db())).name(); + let place_table = self.index.place_table(file_scope_id); + let expr = place_table.place_expr(definition.place(self.db())); if let Some(module_type_implicit_declaration) = - module_type_implicit_global_declaration(self.db(), symbol_name) + module_type_implicit_global_declaration(self.db(), expr) .ok() - .and_then(|sym| sym.symbol.ignore_possibly_unbound()) + .and_then(|place| place.place.ignore_possibly_unbound()) { let declared_type = declared_ty.inner_type(); if !declared_type @@ -1609,11 +1634,11 @@ impl<'db> TypeInferenceBuilder<'db> { self.context.report_lint(&INVALID_DECLARATION, node) { let mut diagnostic = builder.into_diagnostic(format_args!( - "Cannot shadow implicit global attribute `{symbol_name}` with declaration of type `{}`", + "Cannot shadow implicit global attribute `{expr}` with declaration of type `{}`", declared_type.display(self.db()) )); diagnostic.info(format_args!("The global symbol `{}` must always have a type assignable to `{}`", - symbol_name, + expr, module_type_implicit_declaration.display(self.db()) )); } @@ -2550,7 +2575,7 @@ impl<'db> TypeInferenceBuilder<'db> { } unpacked.expression_type(target_ast_id) } - TargetKind::NameOrAttribute => self.infer_context_expression( + TargetKind::Single => self.infer_context_expression( context_expr, context_expr_ty, with_item.is_async(), @@ -3124,7 +3149,7 @@ impl<'db> TypeInferenceBuilder<'db> { }; match object_ty.class_member(db, attribute.into()) { - meta_attr @ SymbolAndQualifiers { .. } if meta_attr.is_class_var() => { + meta_attr @ PlaceAndQualifiers { .. } if meta_attr.is_class_var() => { if emit_diagnostics { if let Some(builder) = self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target) @@ -3138,8 +3163,8 @@ impl<'db> TypeInferenceBuilder<'db> { } false } - SymbolAndQualifiers { - symbol: Symbol::Type(meta_attr_ty, meta_attr_boundness), + PlaceAndQualifiers { + place: Place::Type(meta_attr_ty, meta_attr_boundness), qualifiers: _, } => { if is_read_only() { @@ -3155,8 +3180,8 @@ impl<'db> TypeInferenceBuilder<'db> { } false } else { - let assignable_to_meta_attr = if let Symbol::Type(meta_dunder_set, _) = - meta_attr_ty.class_member(db, "__set__".into()).symbol + let assignable_to_meta_attr = if let Place::Type(meta_dunder_set, _) = + meta_attr_ty.class_member(db, "__set__".into()).place { let successful_call = meta_dunder_set .try_call( @@ -3187,13 +3212,12 @@ impl<'db> TypeInferenceBuilder<'db> { ensure_assignable_to(meta_attr_ty) }; - let assignable_to_instance_attribute = - if meta_attr_boundness == Boundness::PossiblyUnbound { - let (assignable, boundness) = if let Symbol::Type( - instance_attr_ty, - instance_attr_boundness, - ) = - object_ty.instance_member(db, attribute).symbol + let assignable_to_instance_attribute = if meta_attr_boundness + == Boundness::PossiblyUnbound + { + let (assignable, boundness) = + if let Place::Type(instance_attr_ty, instance_attr_boundness) = + object_ty.instance_member(db, attribute).place { ( ensure_assignable_to(instance_attr_ty), @@ -3203,30 +3227,30 @@ impl<'db> TypeInferenceBuilder<'db> { (true, Boundness::PossiblyUnbound) }; - if boundness == Boundness::PossiblyUnbound { - report_possibly_unbound_attribute( - &self.context, - target, - attribute, - object_ty, - ); - } + if boundness == Boundness::PossiblyUnbound { + report_possibly_unbound_attribute( + &self.context, + target, + attribute, + object_ty, + ); + } - assignable - } else { - true - }; + assignable + } else { + true + }; assignable_to_meta_attr && assignable_to_instance_attribute } } - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, .. } => { - if let Symbol::Type(instance_attr_ty, instance_attr_boundness) = - object_ty.instance_member(db, attribute).symbol + if let Place::Type(instance_attr_ty, instance_attr_boundness) = + object_ty.instance_member(db, attribute).place { if instance_attr_boundness == Boundness::PossiblyUnbound { report_possibly_unbound_attribute( @@ -3307,12 +3331,12 @@ impl<'db> TypeInferenceBuilder<'db> { Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => { match object_ty.class_member(db, attribute.into()) { - SymbolAndQualifiers { - symbol: Symbol::Type(meta_attr_ty, meta_attr_boundness), + PlaceAndQualifiers { + place: Place::Type(meta_attr_ty, meta_attr_boundness), qualifiers: _, } => { - let assignable_to_meta_attr = if let Symbol::Type(meta_dunder_set, _) = - meta_attr_ty.class_member(db, "__set__".into()).symbol + let assignable_to_meta_attr = if let Place::Type(meta_dunder_set, _) = + meta_attr_ty.class_member(db, "__set__".into()).place { let successful_call = meta_dunder_set .try_call( @@ -3346,18 +3370,16 @@ impl<'db> TypeInferenceBuilder<'db> { let assignable_to_class_attr = if meta_attr_boundness == Boundness::PossiblyUnbound { - let (assignable, boundness) = if let Symbol::Type( - class_attr_ty, - class_attr_boundness, - ) = object_ty - .find_name_in_mro(db, attribute) - .expect("called on Type::ClassLiteral or Type::SubclassOf") - .symbol - { - (ensure_assignable_to(class_attr_ty), class_attr_boundness) - } else { - (true, Boundness::PossiblyUnbound) - }; + let (assignable, boundness) = + if let Place::Type(class_attr_ty, class_attr_boundness) = object_ty + .find_name_in_mro(db, attribute) + .expect("called on Type::ClassLiteral or Type::SubclassOf") + .place + { + (ensure_assignable_to(class_attr_ty), class_attr_boundness) + } else { + (true, Boundness::PossiblyUnbound) + }; if boundness == Boundness::PossiblyUnbound { report_possibly_unbound_attribute( @@ -3375,14 +3397,14 @@ impl<'db> TypeInferenceBuilder<'db> { assignable_to_meta_attr && assignable_to_class_attr } - SymbolAndQualifiers { - symbol: Symbol::Unbound, + PlaceAndQualifiers { + place: Place::Unbound, .. } => { - if let Symbol::Type(class_attr_ty, class_attr_boundness) = object_ty + if let Place::Type(class_attr_ty, class_attr_boundness) = object_ty .find_name_in_mro(db, attribute) .expect("called on Type::ClassLiteral or Type::SubclassOf") - .symbol + .place { if class_attr_boundness == Boundness::PossiblyUnbound { report_possibly_unbound_attribute( @@ -3399,7 +3421,7 @@ impl<'db> TypeInferenceBuilder<'db> { object_ty.to_instance(self.db()).is_some_and(|instance| { !instance .instance_member(self.db(), attribute) - .symbol + .place .is_unbound() }); @@ -3435,7 +3457,7 @@ impl<'db> TypeInferenceBuilder<'db> { } Type::ModuleLiteral(module) => { - if let Symbol::Type(attr_ty, _) = module.static_member(db, attribute) { + if let Place::Type(attr_ty, _) = module.static_member(db, attribute) { let assignable = value_ty.is_assignable_to(db, attr_ty); if assignable { true @@ -3537,7 +3559,7 @@ impl<'db> TypeInferenceBuilder<'db> { let target_ast_id = target.scoped_expression_id(self.db(), self.scope()); unpacked.expression_type(target_ast_id) } - TargetKind::NameOrAttribute => { + TargetKind::Single => { // `TYPE_CHECKING` is a special variable that should only be assigned `False` // at runtime, but is always considered `True` in type checking. // See mdtest/known_constants.md#user-defined-type_checking for details. @@ -3568,10 +3590,10 @@ impl<'db> TypeInferenceBuilder<'db> { } fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) { - // assignments to non-Names are not Definitions - if matches!(*assignment.target, ast::Expr::Name(_)) { + if assignment.target.is_name_expr() { self.infer_definition(assignment); } else { + // Non-name assignment targets are inferred as ordinary expressions, not definitions. let ast::StmtAnnAssign { range: _, annotation, @@ -3655,10 +3677,10 @@ impl<'db> TypeInferenceBuilder<'db> { } } - // Annotated assignments to non-names are not definitions, so we can only be here - // if the target is a name. In this case, we can simply store types in `target` - // below, instead of calling `infer_expression` (which would return `Never`). - debug_assert!(target.is_name_expr()); + // If the target of an assignment is not one of the place expressions we support, + // then they are not definitions, so we can only be here if the target is in a form supported as a place expression. + // In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`). + debug_assert!(PlaceExpr::try_from(target).is_ok()); if let Some(value) = value { let inferred_ty = self.infer_expression(value); @@ -3701,7 +3723,7 @@ impl<'db> TypeInferenceBuilder<'db> { if assignment.target.is_name_expr() { self.infer_definition(assignment); } else { - // TODO currently we don't consider assignments to non-Names to be Definitions + // Non-name assignment targets are inferred as ordinary expressions, not definitions. self.infer_augment_assignment(assignment); } } @@ -3792,6 +3814,11 @@ impl<'db> TypeInferenceBuilder<'db> { self.store_expression_type(target, previous_value); previous_value } + ast::Expr::Subscript(subscript) => { + let previous_value = self.infer_subscript_load(subscript); + self.store_expression_type(target, previous_value); + previous_value + } _ => self.infer_expression(target), }; let value_type = self.infer_expression(value); @@ -3848,12 +3875,10 @@ impl<'db> TypeInferenceBuilder<'db> { let target_ast_id = target.scoped_expression_id(self.db(), self.scope()); unpacked.expression_type(target_ast_id) } - TargetKind::NameOrAttribute => { - iterable_type.try_iterate(self.db()).unwrap_or_else(|err| { - err.report_diagnostic(&self.context, iterable_type, iterable.into()); - err.fallback_element_type(self.db()) - }) - } + TargetKind::Single => iterable_type.try_iterate(self.db()).unwrap_or_else(|err| { + err.report_diagnostic(&self.context, iterable_type, iterable.into()); + err.fallback_element_type(self.db()) + }), } }; @@ -4147,12 +4172,14 @@ impl<'db> TypeInferenceBuilder<'db> { .map(|star_import| { let symbol_table = self .index - .symbol_table(self.scope().file_scope_id(self.db())); + .place_table(self.scope().file_scope_id(self.db())); (star_import, symbol_table) }); let name = if let Some((star_import, symbol_table)) = star_import_info.as_ref() { - symbol_table.symbol(star_import.symbol_id()).name() + symbol_table + .place_expr(star_import.place_id()) + .expect_name() } else { &alias.name.id }; @@ -4165,7 +4192,7 @@ impl<'db> TypeInferenceBuilder<'db> { // First try loading the requested attribute from the module. if !import_is_self_referential { - if let Symbol::Type(ty, boundness) = module_ty.member(self.db(), name).symbol { + if let Place::Type(ty, boundness) = module_ty.member(self.db(), name).place { if &alias.name != "*" && boundness == Boundness::PossiblyUnbound { // TODO: Consider loading _both_ the attribute and any submodule and unioning them // together if the attribute exists but is possibly-unbound. @@ -4354,15 +4381,22 @@ impl<'db> TypeInferenceBuilder<'db> { #[track_caller] fn infer_expression(&mut self, expression: &ast::Expr) -> Type<'db> { - debug_assert_eq!( - self.index.try_expression(expression), - None, + debug_assert!( + !self.index.is_standalone_expression(expression), "Calling `self.infer_expression` on a standalone-expression is not allowed because it can lead to double-inference. Use `self.infer_standalone_expression` instead." ); self.infer_expression_impl(expression) } + fn infer_maybe_standalone_expression(&mut self, expression: &ast::Expr) -> Type<'db> { + if self.index.is_standalone_expression(expression) { + self.infer_standalone_expression(expression) + } else { + self.infer_expression(expression) + } + } + fn infer_standalone_expression(&mut self, expression: &ast::Expr) -> Type<'db> { let standalone_expression = self.index.expression(expression); let types = infer_expression_types(self.db(), standalone_expression); @@ -4798,7 +4832,7 @@ impl<'db> TypeInferenceBuilder<'db> { // (2) We must *not* call `self.extend()` on the result of the type inference, // because `ScopedExpressionId`s are only meaningful within their own scope, so // we'd add types for random wrong expressions in the current scope - let iterable_type = if comprehension.is_first() { + let iterable_type = if comprehension.is_first() && target.is_name_expr() { let lookup_scope = self .index .parent_scope_id(self.scope().file_scope_id(self.db())) @@ -4806,8 +4840,13 @@ impl<'db> TypeInferenceBuilder<'db> { .to_scope_id(self.db(), self.file()); result.expression_type(iterable.scoped_expression_id(self.db(), lookup_scope)) } else { + let scope = self.types.scope; + self.types.scope = result.scope; self.extend(result); - result.expression_type(iterable.scoped_expression_id(self.db(), self.scope())) + self.types.scope = scope; + result.expression_type( + iterable.scoped_expression_id(self.db(), expression.scope(self.db())), + ) }; let target_type = if comprehension.is_async() { @@ -4820,15 +4859,14 @@ impl<'db> TypeInferenceBuilder<'db> { if unpack_position == UnpackPosition::First { self.context.extend(unpacked.diagnostics()); } - let target_ast_id = target.scoped_expression_id(self.db(), self.scope()); + let target_ast_id = + target.scoped_expression_id(self.db(), unpack.target_scope(self.db())); unpacked.expression_type(target_ast_id) } - TargetKind::NameOrAttribute => { - iterable_type.try_iterate(self.db()).unwrap_or_else(|err| { - err.report_diagnostic(&self.context, iterable_type, iterable.into()); - err.fallback_element_type(self.db()) - }) - } + TargetKind::Single => iterable_type.try_iterate(self.db()).unwrap_or_else(|err| { + err.report_diagnostic(&self.context, iterable_type, iterable.into()); + err.fallback_element_type(self.db()) + }), } }; @@ -5663,59 +5701,135 @@ impl<'db> TypeInferenceBuilder<'db> { todo_type!("generic `typing.Awaitable` type") } - /// Infer the type of a [`ast::ExprName`] expression, assuming a load context. + // Perform narrowing with applicable constraints between the current scope and the enclosing scope. + fn narrow_with_applicable_constraints( + &self, + expr: &PlaceExpr, + mut ty: Type<'db>, + constraint_keys: &[(FileScopeId, ConstraintKey)], + ) -> Type<'db> { + let db = self.db(); + for (enclosing_scope_file_id, constraint_key) in constraint_keys { + let use_def = self.index.use_def_map(*enclosing_scope_file_id); + let constraints = use_def.narrowing_constraints_at_use(*constraint_key); + let place_table = self.index.place_table(*enclosing_scope_file_id); + let place = place_table.place_id_by_expr(expr).unwrap(); + + ty = constraints.narrow(db, ty, place); + } + ty + } + fn infer_name_load(&mut self, name_node: &ast::ExprName) -> Type<'db> { let ast::ExprName { range: _, id: symbol_name, ctx: _, } = name_node; + let Ok(expr) = PlaceExpr::try_from(symbol_name); + let db = self.db(); + + let (resolved, constraint_keys) = + self.infer_place_load(&expr, ast::ExprRef::Name(name_node)); + resolved + // Not found in the module's explicitly declared global symbols? + // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. + // These are looked up as attributes on `types.ModuleType`. + .or_fall_back_to(db, || { + module_type_implicit_global_symbol(db, symbol_name).map_type(|ty| { + self.narrow_with_applicable_constraints(&expr, ty, &constraint_keys) + }) + }) + // Not found in globals? Fallback to builtins + // (without infinite recursion if we're already in builtins.) + .or_fall_back_to(db, || { + if Some(self.scope()) == builtins_module_scope(db) { + Place::Unbound.into() + } else { + builtins_symbol(db, symbol_name) + } + }) + // Still not found? It might be `reveal_type`... + .or_fall_back_to(db, || { + if symbol_name == "reveal_type" { + if let Some(builder) = self.context.report_lint(&UNDEFINED_REVEAL, name_node) { + let mut diag = + builder.into_diagnostic("`reveal_type` used without importing it"); + diag.info( + "This is allowed for debugging convenience but will fail at runtime", + ); + } + typing_extensions_symbol(db, symbol_name) + } else { + Place::Unbound.into() + } + }) + .unwrap_with_diagnostic(|lookup_error| match lookup_error { + LookupError::Unbound(qualifiers) => { + self.report_unresolved_reference(name_node); + TypeAndQualifiers::new(Type::unknown(), qualifiers) + } + LookupError::PossiblyUnbound(type_when_bound) => { + if self.is_reachable(name_node) { + report_possibly_unresolved_reference(&self.context, name_node); + } + type_when_bound + } + }) + .inner_type() + } + fn infer_local_place_load( + &self, + expr: &PlaceExpr, + expr_ref: ast::ExprRef, + ) -> (Place<'db>, Option) { let db = self.db(); let scope = self.scope(); let file_scope_id = scope.file_scope_id(db); - let symbol_table = self.index.symbol_table(file_scope_id); + let place_table = self.index.place_table(file_scope_id); let use_def = self.index.use_def_map(file_scope_id); - let mut constraint_keys = vec![]; - // Perform narrowing with applicable constraints between the current scope and the enclosing scope. - let narrow_with_applicable_constraints = |mut ty, constraint_keys: &[_]| { - for (enclosing_scope_file_id, constraint_key) in constraint_keys { - let use_def = self.index.use_def_map(*enclosing_scope_file_id); - let constraints = use_def.narrowing_constraints_at_use(*constraint_key); - let symbol_table = self.index.symbol_table(*enclosing_scope_file_id); - let symbol = symbol_table.symbol_id_by_name(symbol_name).unwrap(); - - ty = constraints.narrow(db, ty, symbol); - } - ty - }; - // If we're inferring types of deferred expressions, always treat them as public symbols - let (local_scope_symbol, use_id) = if self.is_deferred() { - let symbol = if let Some(symbol_id) = symbol_table.symbol_id_by_name(symbol_name) { - symbol_from_bindings(db, use_def.public_bindings(symbol_id)) + if self.is_deferred() { + let place = if let Some(place_id) = place_table.place_id_by_expr(expr) { + place_from_bindings(db, use_def.public_bindings(place_id)) } else { assert!( self.deferred_state.in_string_annotation(), - "Expected the symbol table to create a symbol for every Name node" + "Expected the place table to create a place for every valid PlaceExpr node" ); - Symbol::Unbound + Place::Unbound }; - (symbol, None) + (place, None) } else { - let use_id = name_node.scoped_use_id(db, scope); - let symbol = symbol_from_bindings(db, use_def.bindings_at_use(use_id)); - (symbol, Some(use_id)) - }; + let use_id = expr_ref.scoped_use_id(db, scope); + let place = place_from_bindings(db, use_def.bindings_at_use(use_id)); + (place, Some(use_id)) + } + } + + /// Infer the type of a place expression, assuming a load context. + fn infer_place_load( + &self, + expr: &PlaceExpr, + expr_ref: ast::ExprRef, + ) -> (PlaceAndQualifiers<'db>, Vec<(FileScopeId, ConstraintKey)>) { + let db = self.db(); + let scope = self.scope(); + let file_scope_id = scope.file_scope_id(db); + let place_table = self.index.place_table(file_scope_id); + + let mut constraint_keys = vec![]; + let (local_scope_place, use_id) = self.infer_local_place_load(expr, expr_ref); - let symbol = SymbolAndQualifiers::from(local_scope_symbol).or_fall_back_to(db, || { - let has_bindings_in_this_scope = match symbol_table.symbol_by_name(symbol_name) { - Some(symbol) => symbol.is_bound(), + let place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || { + let has_bindings_in_this_scope = match place_table.place_by_expr(expr) { + Some(place_expr) => place_expr.is_bound(), None => { assert!( self.deferred_state.in_string_annotation(), - "Expected the symbol table to create a symbol for every Name node" + "Expected the place table to create a place for every Name node" ); false } @@ -5723,25 +5837,46 @@ impl<'db> TypeInferenceBuilder<'db> { let current_file = self.file(); - let skip_non_global_scopes = symbol_table - .symbol_id_by_name(symbol_name) - .is_some_and(|symbol_id| self.skip_non_global_scopes(file_scope_id, symbol_id)); + if let Some(name) = expr.as_name() { + let skip_non_global_scopes = place_table + .place_id_by_name(name) + .is_some_and(|symbol_id| self.skip_non_global_scopes(file_scope_id, symbol_id)); - if skip_non_global_scopes { - return global_symbol(self.db(), self.file(), symbol_name); + if skip_non_global_scopes { + return global_symbol(self.db(), self.file(), name); + } } // If it's a function-like scope and there is one or more binding in this scope (but // none of those bindings are visible from where we are in the control flow), we cannot // fallback to any bindings in enclosing scopes. As such, we can immediately short-circuit - // here and return `Symbol::Unbound`. + // here and return `Place::Unbound`. // // This is because Python is very strict in its categorisation of whether a variable is // a local variable or not in function-like scopes. If a variable has any bindings in a // function-like scope, it is considered a local variable; it never references another // scope. (At runtime, it would use the `LOAD_FAST` opcode.) if has_bindings_in_this_scope && scope.is_function_like(db) { - return Symbol::Unbound.into(); + return Place::Unbound.into(); + } + + for root_expr in place_table.root_place_exprs(expr) { + let mut expr_ref = expr_ref; + for _ in 0..(expr.sub_segments().len() - root_expr.sub_segments().len()) { + match expr_ref { + ast::ExprRef::Attribute(attribute) => { + expr_ref = ast::ExprRef::from(&attribute.value); + } + ast::ExprRef::Subscript(subscript) => { + expr_ref = ast::ExprRef::from(&subscript.value); + } + _ => unreachable!(), + } + } + let (parent_place, _use_id) = self.infer_local_place_load(root_expr, expr_ref); + if let Place::Type(_, _) = parent_place { + return Place::Unbound.into(); + } } if let Some(use_id) = use_id { @@ -5751,7 +5886,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Walk up parent scopes looking for a possible enclosing scope that may have a // definition of this name visible to us (would be `LOAD_DEREF` at runtime.) // Note that we skip the scope containing the use that we are resolving, since we - // already looked for the symbol there up above. + // already looked for the place there up above. for (enclosing_scope_file_id, _) in self.index.ancestor_scopes(file_scope_id).skip(1) { // Class scopes are not visible to nested scopes, and we need to handle global // scope differently (because an unbound name there falls back to builtins), so @@ -5766,18 +5901,17 @@ impl<'db> TypeInferenceBuilder<'db> { .parent() .is_some_and(|parent| parent == enclosing_scope_file_id); - // If the reference is in a nested eager scope, we need to look for the symbol at + // If the reference is in a nested eager scope, we need to look for the place at // the point where the previous enclosing scope was defined, instead of at the end // of the scope. (Note that the semantic index builder takes care of only // registering eager bindings for nested scopes that are actually eager, and for // enclosing scopes that actually contain bindings that we should use when // resolving the reference.) if !self.is_deferred() { - match self.index.eager_snapshot( - enclosing_scope_file_id, - symbol_name, - file_scope_id, - ) { + match self + .index + .eager_snapshot(enclosing_scope_file_id, expr, file_scope_id) + { EagerSnapshotResult::FoundConstraint(constraint) => { constraint_keys.push(( enclosing_scope_file_id, @@ -5785,20 +5919,37 @@ impl<'db> TypeInferenceBuilder<'db> { )); } EagerSnapshotResult::FoundBindings(bindings) => { - if !enclosing_scope_id.is_function_like(db) + if expr.is_name() + && !enclosing_scope_id.is_function_like(db) && !is_immediately_enclosing_scope { continue; } - return symbol_from_bindings(db, bindings) + return place_from_bindings(db, bindings) .map_type(|ty| { - narrow_with_applicable_constraints(ty, &constraint_keys) + self.narrow_with_applicable_constraints( + expr, + ty, + &constraint_keys, + ) }) .into(); } // There are no visible bindings / constraint here. - // Don't fall back to non-eager symbol resolution. + // Don't fall back to non-eager place resolution. EagerSnapshotResult::NotFound => { + let enclosing_place_table = + self.index.place_table(enclosing_scope_file_id); + for enclosing_root_place in enclosing_place_table.root_place_exprs(expr) + { + if enclosing_root_place.is_bound() { + if let Place::Type(_, _) = + place(db, enclosing_scope_id, enclosing_root_place).place + { + return Place::Unbound.into(); + } + } + } continue; } EagerSnapshotResult::NoLongerInEagerContext => {} @@ -5809,36 +5960,35 @@ impl<'db> TypeInferenceBuilder<'db> { continue; } - let enclosing_symbol_table = self.index.symbol_table(enclosing_scope_file_id); - let Some(enclosing_symbol) = enclosing_symbol_table.symbol_by_name(symbol_name) - else { + let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); + let Some(enclosing_place) = enclosing_place_table.place_by_expr(expr) else { continue; }; - if enclosing_symbol.is_bound() { + if enclosing_place.is_bound() { // We can return early here, because the nearest function-like scope that // defines a name must be the only source for the nonlocal reference (at // runtime, it is the scope that creates the cell for our closure.) If the name // isn't bound in that scope, we should get an unbound name, not continue // falling back to other scopes / globals / builtins. - return symbol(db, enclosing_scope_id, symbol_name) - .map_type(|ty| narrow_with_applicable_constraints(ty, &constraint_keys)); + return place(db, enclosing_scope_id, expr).map_type(|ty| { + self.narrow_with_applicable_constraints(expr, ty, &constraint_keys) + }); } } - SymbolAndQualifiers::from(Symbol::Unbound) + PlaceAndQualifiers::from(Place::Unbound) // No nonlocal binding? Check the module's explicit globals. // Avoid infinite recursion if `self.scope` already is the module's global scope. .or_fall_back_to(db, || { if file_scope_id.is_global() { - return Symbol::Unbound.into(); + return Place::Unbound.into(); } if !self.is_deferred() { - match self.index.eager_snapshot( - FileScopeId::global(), - symbol_name, - file_scope_id, - ) { + match self + .index + .eager_snapshot(FileScopeId::global(), expr, file_scope_id) + { EagerSnapshotResult::FoundConstraint(constraint) => { constraint_keys.push(( FileScopeId::global(), @@ -5846,72 +5996,35 @@ impl<'db> TypeInferenceBuilder<'db> { )); } EagerSnapshotResult::FoundBindings(bindings) => { - return symbol_from_bindings(db, bindings) + return place_from_bindings(db, bindings) .map_type(|ty| { - narrow_with_applicable_constraints(ty, &constraint_keys) + self.narrow_with_applicable_constraints( + expr, + ty, + &constraint_keys, + ) }) .into(); } // There are no visible bindings / constraint here. EagerSnapshotResult::NotFound => { - return Symbol::Unbound.into(); + return Place::Unbound.into(); } EagerSnapshotResult::NoLongerInEagerContext => {} } } - explicit_global_symbol(db, self.file(), symbol_name) - .map_type(|ty| narrow_with_applicable_constraints(ty, &constraint_keys)) - }) - // Not found in the module's explicitly declared global symbols? - // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. - // These are looked up as attributes on `types.ModuleType`. - .or_fall_back_to(db, || { - module_type_implicit_global_symbol(db, symbol_name) - .map_type(|ty| narrow_with_applicable_constraints(ty, &constraint_keys)) - }) - // Not found in globals? Fallback to builtins - // (without infinite recursion if we're already in builtins.) - .or_fall_back_to(db, || { - if Some(self.scope()) == builtins_module_scope(db) { - Symbol::Unbound.into() - } else { - builtins_symbol(db, symbol_name) - } - }) - // Still not found? It might be `reveal_type`... - .or_fall_back_to(db, || { - if symbol_name == "reveal_type" { - if let Some(builder) = - self.context.report_lint(&UNDEFINED_REVEAL, name_node) - { - let mut diag = - builder.into_diagnostic("`reveal_type` used without importing it"); - diag.info( - "This is allowed for debugging convenience but will fail at runtime" - ); - } - typing_extensions_symbol(db, symbol_name) - } else { - Symbol::Unbound.into() - } + let Some(name) = expr.as_name() else { + return Place::Unbound.into(); + }; + + explicit_global_symbol(db, self.file(), name).map_type(|ty| { + self.narrow_with_applicable_constraints(expr, ty, &constraint_keys) + }) }) }); - symbol - .unwrap_with_diagnostic(|lookup_error| match lookup_error { - LookupError::Unbound(qualifiers) => { - self.report_unresolved_reference(name_node); - TypeAndQualifiers::new(Type::unknown(), qualifiers) - } - LookupError::PossiblyUnbound(type_when_bound) => { - if self.is_reachable(name_node) { - report_possibly_unresolved_reference(&self.context, name_node); - } - type_when_bound - } - }) - .inner_type() + (place, constraint_keys) } pub(super) fn report_unresolved_reference(&self, expr_name_node: &ast::ExprName) { @@ -5946,7 +6059,7 @@ impl<'db> TypeInferenceBuilder<'db> { .and_then(|class| { Type::instance(self.db(), class.default_specialization(self.db())) .member(self.db(), id) - .symbol + .place .ignore_possibly_unbound() }) .is_some(); @@ -5975,14 +6088,27 @@ impl<'db> TypeInferenceBuilder<'db> { ctx: _, } = attribute; - let value_type = if self.index.is_standalone_expression(&**value) { - self.infer_standalone_expression(value) - } else { - self.infer_expression(value) - }; - + let value_type = self.infer_maybe_standalone_expression(value); let db = self.db(); + // If `attribute` is a valid reference, we attempt type narrowing by assignment. + if let Ok(place_expr) = PlaceExpr::try_from(attribute) { + let member = value_type.class_member(db, attr.id.clone()); + // If the member is a data descriptor, the value most recently assigned + // to the attribute may not necessarily be obtained here. + if member + .place + .ignore_possibly_unbound() + .is_none_or(|ty| !ty.may_be_data_descriptor(db)) + { + let (resolved, _) = + self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute)); + if let Place::Type(ty, Boundness::Bound) = resolved.place { + return ty; + } + } + } + value_type .member(db, &attr.id) .unwrap_with_diagnostic(|lookup_error| match lookup_error { @@ -5992,12 +6118,12 @@ impl<'db> TypeInferenceBuilder<'db> { if report_unresolved_attribute { let bound_on_instance = match value_type { Type::ClassLiteral(class) => { - !class.instance_member(db, None, attr).symbol.is_unbound() + !class.instance_member(db, None, attr).place.is_unbound() } Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => { match subclass_of.subclass_of() { SubclassOfInner::Class(class) => { - !class.instance_member(db, attr).symbol.is_unbound() + !class.instance_member(db, attr).place.is_unbound() } SubclassOfInner::Dynamic(_) => unreachable!( "Attribute lookup on a dynamic `SubclassOf` type should always return a bound symbol" @@ -6504,11 +6630,11 @@ impl<'db> TypeInferenceBuilder<'db> { let right_class = right_ty.to_meta_type(self.db()); if left_ty != right_ty && right_ty.is_subtype_of(self.db(), left_ty) { let reflected_dunder = op.reflected_dunder(); - let rhs_reflected = right_class.member(self.db(), reflected_dunder).symbol; + let rhs_reflected = right_class.member(self.db(), reflected_dunder).place; // TODO: if `rhs_reflected` is possibly unbound, we should union the two possible // Bindings together if !rhs_reflected.is_unbound() - && rhs_reflected != left_class.member(self.db(), reflected_dunder).symbol + && rhs_reflected != left_class.member(self.db(), reflected_dunder).place { return right_ty .try_call_dunder( @@ -7318,9 +7444,9 @@ impl<'db> TypeInferenceBuilder<'db> { ) -> Result, CompareUnsupportedError<'db>> { let db = self.db(); - let contains_dunder = right.class_member(db, "__contains__".into()).symbol; + let contains_dunder = right.class_member(db, "__contains__".into()).place; let compare_result_opt = match contains_dunder { - Symbol::Type(contains_dunder, Boundness::Bound) => { + Place::Type(contains_dunder, Boundness::Bound) => { // If `__contains__` is available, it is used directly for the membership test. contains_dunder .try_call(db, &CallArgumentTypes::positional([right, left])) @@ -7440,12 +7566,80 @@ impl<'db> TypeInferenceBuilder<'db> { } fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> { + let ast::ExprSubscript { + value, + slice, + range: _, + ctx, + } = subscript; + + match ctx { + ExprContext::Load => self.infer_subscript_load(subscript), + ExprContext::Store | ExprContext::Del => { + let value_ty = self.infer_expression(value); + let slice_ty = self.infer_expression(slice); + self.infer_subscript_expression_types(value, value_ty, slice_ty); + Type::Never + } + ExprContext::Invalid => { + let value_ty = self.infer_expression(value); + let slice_ty = self.infer_expression(slice); + self.infer_subscript_expression_types(value, value_ty, slice_ty); + Type::unknown() + } + } + } + + fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> { let ast::ExprSubscript { range: _, value, slice, ctx: _, } = subscript; + let db = self.db(); + let value_ty = self.infer_expression(value); + + // If `value` is a valid reference, we attempt type narrowing by assignment. + if !value_ty.is_unknown() { + if let Ok(expr) = PlaceExpr::try_from(subscript) { + // Type narrowing based on assignment to a subscript expression is generally + // unsound, because arbitrary `__getitem__`/`__setitem__` methods on a class do not + // necessarily guarantee that the passed-in value for `__setitem__` is stored and + // can be retrieved unmodified via `__getitem__`. Therefore, we currently only + // perform assignment-based narrowing on a few built-in classes (`list`, `dict`, + // `bytesarray`, `TypedDict` and `collections` types) where we are confident that + // this kind of narrowing can be performed soundly. This is the same approach as + // pyright. TODO: Other standard library classes may also be considered safe. Also, + // subclasses of these safe classes that do not override `__getitem__/__setitem__` + // may be considered safe. + let safe_mutable_classes = [ + KnownClass::List.to_instance(db), + KnownClass::Dict.to_instance(db), + KnownClass::Bytearray.to_instance(db), + KnownClass::DefaultDict.to_instance(db), + SpecialFormType::ChainMap.instance_fallback(db), + SpecialFormType::Counter.instance_fallback(db), + SpecialFormType::Deque.instance_fallback(db), + SpecialFormType::OrderedDict.instance_fallback(db), + SpecialFormType::TypedDict.instance_fallback(db), + ]; + if safe_mutable_classes.iter().any(|safe_mutable_class| { + value_ty.is_equivalent_to(db, *safe_mutable_class) + || value_ty + .generic_origin(db) + .zip(safe_mutable_class.generic_origin(db)) + .is_some_and(|(l, r)| l == r) + }) { + let (place, _) = + self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript)); + if let Place::Type(ty, Boundness::Bound) = place.place { + self.infer_expression(slice); + return ty; + } + } + } + } // HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the // subscript inference logic and treat this as an explicit specialization. @@ -7453,7 +7647,6 @@ impl<'db> TypeInferenceBuilder<'db> { // this callable as the `__class_getitem__` method on `type`. That probably requires // updating all of the subscript logic below to use custom callables for all of the _other_ // special cases, too. - let value_ty = self.infer_expression(value); if let Type::ClassLiteral(class) = value_ty { if class.is_known(self.db(), KnownClass::Tuple) { self.infer_expression(slice); @@ -7747,11 +7940,11 @@ impl<'db> TypeInferenceBuilder<'db> { // method in these `sys.version_info` branches. if value_ty.is_subtype_of(self.db(), KnownClass::Type.to_instance(self.db())) { let dunder_class_getitem_method = - value_ty.member(self.db(), "__class_getitem__").symbol; + value_ty.member(self.db(), "__class_getitem__").place; match dunder_class_getitem_method { - Symbol::Unbound => {} - Symbol::Type(ty, boundness) => { + Place::Unbound => {} + Place::Type(ty, boundness) => { if boundness == Boundness::PossiblyUnbound { if let Some(builder) = self .context @@ -9209,7 +9402,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: Check that value type is enum otherwise return None value_ty .member(self.db(), &attr.id) - .symbol + .place .ignore_possibly_unbound() .unwrap_or(Type::unknown()) } @@ -9510,10 +9703,10 @@ fn contains_string_literal(expr: &ast::Expr) -> bool { #[cfg(test)] mod tests { use crate::db::tests::{TestDb, setup_db}; + use crate::place::{global_symbol, symbol}; use crate::semantic_index::definition::Definition; - use crate::semantic_index::symbol::FileScopeId; - use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map}; - use crate::symbol::global_symbol; + use crate::semantic_index::place::FileScopeId; + use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; use crate::types::check_types; use ruff_db::diagnostic::Diagnostic; use ruff_db::files::{File, system_path_to_file}; @@ -9528,7 +9721,7 @@ mod tests { file_name: &str, scopes: &[&str], symbol_name: &str, - ) -> Symbol<'db> { + ) -> Place<'db> { let file = system_path_to_file(db, file_name).expect("file to exist"); let index = semantic_index(db, file); let mut file_scope_id = FileScopeId::global(); @@ -9543,7 +9736,7 @@ mod tests { assert_eq!(scope.name(db), *expected_scope_name); } - symbol(db, scope, symbol_name).symbol + symbol(db, scope, symbol_name).place } #[track_caller] @@ -9698,7 +9891,7 @@ mod tests { assert_eq!(var_ty.display(&db).to_string(), "typing.TypeVar"); let expected_name_ty = format!(r#"Literal["{var}"]"#); - let name_ty = var_ty.member(&db, "__name__").symbol.expect_type(); + let name_ty = var_ty.member(&db, "__name__").place.expect_type(); assert_eq!(name_ty.display(&db).to_string(), expected_name_ty); let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = var_ty else { @@ -9788,8 +9981,8 @@ mod tests { fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> { let scope = global_scope(db, file); use_def_map(db, scope) - .public_bindings(symbol_table(db, scope).symbol_id_by_name(name).unwrap()) - .find_map(|b| b.binding) + .public_bindings(place_table(db, scope).place_id_by_name(name).unwrap()) + .find_map(|b| b.binding.definition()) .expect("no binding found") } @@ -9803,7 +9996,7 @@ mod tests { ])?; let a = system_path_to_file(&db, "/src/a.py").unwrap(); - let x_ty = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty.display(&db).to_string(), "int"); @@ -9812,7 +10005,7 @@ mod tests { let a = system_path_to_file(&db, "/src/a.py").unwrap(); - let x_ty_2 = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty_2 = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty_2.display(&db).to_string(), "bool"); @@ -9829,7 +10022,7 @@ mod tests { ])?; let a = system_path_to_file(&db, "/src/a.py").unwrap(); - let x_ty = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty.display(&db).to_string(), "int"); @@ -9839,7 +10032,7 @@ mod tests { db.clear_salsa_events(); - let x_ty_2 = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty_2 = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty_2.display(&db).to_string(), "int"); @@ -9865,7 +10058,7 @@ mod tests { ])?; let a = system_path_to_file(&db, "/src/a.py").unwrap(); - let x_ty = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty.display(&db).to_string(), "int"); @@ -9875,7 +10068,7 @@ mod tests { db.clear_salsa_events(); - let x_ty_2 = global_symbol(&db, a, "x").symbol.expect_type(); + let x_ty_2 = global_symbol(&db, a, "x").place.expect_type(); assert_eq!(x_ty_2.display(&db).to_string(), "int"); @@ -9922,7 +10115,7 @@ mod tests { )?; let file_main = system_path_to_file(&db, "/src/main.py").unwrap(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None"); // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred @@ -9937,7 +10130,7 @@ mod tests { let events = { db.clear_salsa_events(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None"); db.take_salsa_events() }; @@ -9956,7 +10149,7 @@ mod tests { let events = { db.clear_salsa_events(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None"); db.take_salsa_events() }; @@ -10007,7 +10200,7 @@ mod tests { )?; let file_main = system_path_to_file(&db, "/src/main.py").unwrap(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None"); // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred @@ -10024,7 +10217,7 @@ mod tests { let events = { db.clear_salsa_events(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None"); db.take_salsa_events() }; @@ -10045,7 +10238,7 @@ mod tests { let events = { db.clear_salsa_events(); - let attr_ty = global_symbol(&db, file_main, "x").symbol.expect_type(); + let attr_ty = global_symbol(&db, file_main, "x").place.expect_type(); assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None"); db.take_salsa_events() }; diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index e727655990ad8f..c62c6f0b4f6b86 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; -use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers}; +use crate::place::{Boundness, Place, PlaceAndQualifiers}; use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance}; use crate::{Db, FxOrderSet}; @@ -47,8 +47,8 @@ impl<'db> Type<'db> { // TODO: this should consider the types of the protocol members protocol.inner.interface(db).members(db).all(|member| { matches!( - self.member(db, member.name()).symbol, - Symbol::Type(_, Boundness::Bound) + self.member(db, member.name()).place, + Place::Type(_, Boundness::Bound) ) }) } @@ -294,14 +294,14 @@ impl<'db> ProtocolInstanceType<'db> { false } - pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> { + pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { match self.inner { Protocol::FromClass(class) => class.instance_member(db, name), Protocol::Synthesized(synthesized) => synthesized .interface() .member_by_name(db, name) - .map(|member| SymbolAndQualifiers { - symbol: Symbol::bound(member.ty()), + .map(|member| PlaceAndQualifiers { + place: Place::bound(member.ty()), qualifiers: member.qualifiers(), }) .unwrap_or_else(|| KnownClass::Object.to_instance(db).instance_member(db, name)), diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index f8bf6d610bc966..e6cc62c383758f 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -1,11 +1,11 @@ use crate::Db; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::expression::Expression; +use crate::semantic_index::place::{PlaceTable, ScopeId, ScopedPlaceId}; +use crate::semantic_index::place_table; use crate::semantic_index::predicate::{ PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, }; -use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable}; -use crate::semantic_index::symbol_table; use crate::types::function::KnownFunction; use crate::types::infer::infer_same_file_expression_type; use crate::types::{ @@ -42,7 +42,7 @@ use super::UnionType; pub(crate) fn infer_narrowing_constraint<'db>( db: &'db dyn Db, predicate: Predicate<'db>, - symbol: ScopedSymbolId, + place: ScopedPlaceId, ) -> Option> { let constraints = match predicate.node { PredicateNode::Expression(expression) => { @@ -62,7 +62,7 @@ pub(crate) fn infer_narrowing_constraint<'db>( PredicateNode::StarImportPlaceholder(_) => return None, }; if let Some(constraints) = constraints { - constraints.get(&symbol).copied() + constraints.get(&place).copied() } else { None } @@ -190,7 +190,7 @@ impl ClassInfoConstraintFunction { } } -type NarrowingConstraints<'db> = FxHashMap>; +type NarrowingConstraints<'db> = FxHashMap>; fn merge_constraints_and<'db>( into: &mut NarrowingConstraints<'db>, @@ -235,7 +235,7 @@ fn merge_constraints_or<'db>( } fn negate_if<'db>(constraints: &mut NarrowingConstraints<'db>, db: &'db dyn Db, yes: bool) { - for (_symbol, ty) in constraints.iter_mut() { + for (_place, ty) in constraints.iter_mut() { *ty = ty.negate_if(db, yes); } } @@ -347,8 +347,8 @@ impl<'db> NarrowingConstraintsBuilder<'db> { }) } - fn symbols(&self) -> &'db SymbolTable { - symbol_table(self.db, self.scope()) + fn places(&self) -> &'db PlaceTable { + place_table(self.db, self.scope()) } fn scope(&self) -> ScopeId<'db> { @@ -360,9 +360,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> { } #[track_caller] - fn expect_expr_name_symbol(&self, symbol: &str) -> ScopedSymbolId { - self.symbols() - .symbol_id_by_name(symbol) + fn expect_expr_name_symbol(&self, symbol: &str) -> ScopedPlaceId { + self.places() + .place_id_by_name(symbol) .expect("We should always have a symbol for every `Name` node") } diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index c419b1c31b662e..10dfac9c07288e 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -1,5 +1,5 @@ use crate::db::tests::TestDb; -use crate::symbol::{builtins_symbol, known_module_symbol}; +use crate::place::{builtins_symbol, known_module_symbol}; use crate::types::{ BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters, Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType, @@ -130,20 +130,20 @@ impl Ty { Ty::LiteralString => Type::LiteralString, Ty::BytesLiteral(s) => Type::bytes_literal(db, s.as_bytes()), Ty::BuiltinInstance(s) => builtins_symbol(db, s) - .symbol + .place .expect_type() .to_instance(db) .unwrap(), Ty::AbcInstance(s) => known_module_symbol(db, KnownModule::Abc, s) - .symbol + .place .expect_type() .to_instance(db) .unwrap(), Ty::AbcClassLiteral(s) => known_module_symbol(db, KnownModule::Abc, s) - .symbol + .place .expect_type(), Ty::TypingLiteral => Type::SpecialForm(SpecialFormType::Literal), - Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).symbol.expect_type(), + Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).place.expect_type(), Ty::KnownClassInstance(known_class) => known_class.to_instance(db), Ty::Union(tys) => { UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db))) @@ -166,7 +166,7 @@ impl Ty { Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from( db, builtins_symbol(db, s) - .symbol + .place .expect_type() .expect_class_literal() .default_specialization(db), @@ -174,17 +174,17 @@ impl Ty { Ty::SubclassOfAbcClass(s) => SubclassOfType::from( db, known_module_symbol(db, KnownModule::Abc, s) - .symbol + .place .expect_type() .expect_class_literal() .default_specialization(db), ), Ty::AlwaysTruthy => Type::AlwaysTruthy, Ty::AlwaysFalsy => Type::AlwaysFalsy, - Ty::BuiltinsFunction(name) => builtins_symbol(db, name).symbol.expect_type(), + Ty::BuiltinsFunction(name) => builtins_symbol(db, name).place.expect_type(), Ty::BuiltinsBoundMethod { class, method } => { - let builtins_class = builtins_symbol(db, class).symbol.expect_type(); - let function = builtins_class.member(db, method).symbol.expect_type(); + let builtins_class = builtins_symbol(db, class).place.expect_type(); + let function = builtins_class.member(db, method).place.expect_type(); create_bound_method(db, function, builtins_class) } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index dbf7837a0c60df..b17864490e32e7 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -5,10 +5,11 @@ use itertools::{Either, Itertools}; use ruff_python_ast::name::Name; use crate::{ - semantic_index::{symbol_table, use_def_map}, - symbol::{symbol_from_bindings, symbol_from_declarations}, - types::function::KnownFunction, - types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance}, + place::{place_from_bindings, place_from_declarations}, + semantic_index::{place_table, use_def_map}, + types::{ + ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance, + }, {Db, FxOrderSet}, }; @@ -321,19 +322,19 @@ fn cached_protocol_interface<'db>( { let parent_scope = parent_protocol.body_scope(db); let use_def_map = use_def_map(db, parent_scope); - let symbol_table = symbol_table(db, parent_scope); + let place_table = place_table(db, parent_scope); members.extend( use_def_map .all_public_declarations() - .flat_map(|(symbol_id, declarations)| { - symbol_from_declarations(db, declarations).map(|symbol| (symbol_id, symbol)) + .flat_map(|(place_id, declarations)| { + place_from_declarations(db, declarations).map(|place| (place_id, place)) }) - .filter_map(|(symbol_id, symbol)| { - symbol - .symbol + .filter_map(|(place_id, place)| { + place + .place .ignore_possibly_unbound() - .map(|ty| (symbol_id, ty, symbol.qualifiers)) + .map(|ty| (place_id, ty, place.qualifiers)) }) // Bindings in the class body that are not declared in the class body // are not valid protocol members, and we plan to emit diagnostics for them @@ -346,14 +347,18 @@ fn cached_protocol_interface<'db>( .chain( use_def_map .all_public_bindings() - .filter_map(|(symbol_id, bindings)| { - symbol_from_bindings(db, bindings) + .filter_map(|(place_id, bindings)| { + place_from_bindings(db, bindings) .ignore_possibly_unbound() - .map(|ty| (symbol_id, ty, TypeQualifiers::default())) + .map(|ty| (place_id, ty, TypeQualifiers::default())) }), ) - .map(|(symbol_id, member, qualifiers)| { - (symbol_table.symbol(symbol_id).name(), member, qualifiers) + .filter_map(|(place_id, member, qualifiers)| { + Some(( + place_table.place_expr(place_id).as_name()?, + member, + qualifiers, + )) }) .filter(|(name, _, _)| !excluded_from_proto_members(name)) .map(|(name, ty, qualifiers)| { diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 201ab0eddf8367..1f440f782f576e 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1533,16 +1533,15 @@ pub(crate) enum ParameterForm { mod tests { use super::*; use crate::db::tests::{TestDb, setup_db}; - use crate::symbol::global_symbol; - use crate::types::KnownClass; - use crate::types::function::FunctionType; + use crate::place::global_symbol; + use crate::types::{FunctionType, KnownClass}; use ruff_db::system::DbWithWritableSystem as _; #[track_caller] fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> { let module = ruff_db::files::system_path_to_file(db, file).unwrap(); global_symbol(db, module, "f") - .symbol + .place .expect_type() .expect_function_literal() } diff --git a/crates/ty_python_semantic/src/types/slots.rs b/crates/ty_python_semantic/src/types/slots.rs index 760185db98a08c..e5165fb69ba08d 100644 --- a/crates/ty_python_semantic/src/types/slots.rs +++ b/crates/ty_python_semantic/src/types/slots.rs @@ -1,7 +1,7 @@ use ruff_python_ast as ast; use crate::db::Db; -use crate::symbol::{Boundness, Symbol}; +use crate::place::{Boundness, Place}; use crate::types::class_base::ClassBase; use crate::types::diagnostic::report_base_with_incompatible_slots; use crate::types::{ClassLiteral, Type}; @@ -24,7 +24,7 @@ enum SlotsKind { impl SlotsKind { fn from(db: &dyn Db, base: ClassLiteral) -> Self { - let Symbol::Type(slots_ty, bound) = base.own_class_member(db, None, "__slots__").symbol + let Place::Type(slots_ty, bound) = base.own_class_member(db, None, "__slots__").place else { return Self::NotSpecified; }; diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index b177ea92f62701..b2febf439be6c8 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,4 +1,4 @@ -use crate::symbol::SymbolAndQualifiers; +use crate::place::PlaceAndQualifiers; use crate::types::{ ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeVarInstance, }; @@ -99,7 +99,7 @@ impl<'db> SubclassOfType<'db> { db: &'db dyn Db, name: &str, policy: MemberLookupPolicy, - ) -> Option> { + ) -> Option> { Type::from(self.subclass_of).find_name_in_mro_with_policy(db, name, policy) } diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index 07c10ce6835c11..f06ad7c5171894 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -7,7 +7,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef}; use crate::Db; use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId}; -use crate::semantic_index::symbol::ScopeId; +use crate::semantic_index::place::ScopeId; use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types}; use crate::unpack::{UnpackKind, UnpackValue}; @@ -84,7 +84,7 @@ impl<'db> Unpacker<'db> { value_ty: Type<'db>, ) { match target { - ast::Expr::Name(_) | ast::Expr::Attribute(_) => { + ast::Expr::Name(_) | ast::Expr::Attribute(_) | ast::Expr::Subscript(_) => { self.targets.insert( target.scoped_expression_id(self.db(), self.target_scope), value_ty, diff --git a/crates/ty_python_semantic/src/unpack.rs b/crates/ty_python_semantic/src/unpack.rs index 338c9a945a3c1a..0e34dbe7654c38 100644 --- a/crates/ty_python_semantic/src/unpack.rs +++ b/crates/ty_python_semantic/src/unpack.rs @@ -6,7 +6,7 @@ use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId}; use crate::semantic_index::expression::Expression; -use crate::semantic_index::symbol::{FileScopeId, ScopeId}; +use crate::semantic_index::place::{FileScopeId, ScopeId}; /// This ingredient represents a single unpacking. /// From 8485dbb324212dab0e26d2afb5929097af129bbf Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 5 Jun 2025 08:19:15 +0100 Subject: [PATCH 339/487] [ty] Fix `--python` argument for Windows, and improve error messages for bad `--python` arguments (#18457) ## Summary Fixes https://github.com/astral-sh/ty/issues/556. On Windows, system installations have different layouts to virtual environments. In Windows virtual environments, the Python executable is found at `/Scripts/python.exe`. But in Windows system installations, the Python executable is found at `/python.exe`. That means that Windows users were able to point to Python executables inside virtual environments with the `--python` flag, but they weren't able to point to Python executables inside system installations. This PR fixes that issue. It also makes a couple of other changes: - Nearly all `sys.prefix` resolution is moved inside `site_packages.rs`. That was the original design of the `site-packages` resolution logic, but features implemented since the initial implementation have added some resolution and validation to `resolver.rs` inside the module resolver. That means that we've ended up with a somewhat confusing code structure and a situation where several checks are unnecessarily duplicated between the two modules. - I noticed that we had quite bad error messages if you e.g. pointed to a path that didn't exist on disk with `--python` (we just gave a somewhat impenetrable message saying that we "failed to canonicalize" the path). I improved the error messages here and added CLI tests for `--python` and the `environment.python` configuration setting. ## Test Plan - Existing tests pass - Added new CLI tests - I manually checked that virtual-environment discovery still works if no configuration is given - Micha did some manual testing to check that pointing `--python` to a system-installation executable now works on Windows --- crates/ruff/tests/analyze_graph.rs | 2 +- crates/ruff_graph/src/db.rs | 5 +- crates/ty/tests/cli.rs | 150 ++++++++++++++++++ crates/ty_project/src/metadata/options.rs | 26 +-- .../src/module_resolver/resolver.rs | 83 +++------- crates/ty_python_semantic/src/program.rs | 26 +-- .../ty_python_semantic/src/site_packages.rs | 119 +++++++++++--- crates/ty_test/src/lib.rs | 2 +- 8 files changed, 297 insertions(+), 116 deletions(-) diff --git a/crates/ruff/tests/analyze_graph.rs b/crates/ruff/tests/analyze_graph.rs index e59a01c2949cc3..3c5ba4498b3029 100644 --- a/crates/ruff/tests/analyze_graph.rs +++ b/crates/ruff/tests/analyze_graph.rs @@ -566,7 +566,7 @@ fn venv() -> Result<()> { ----- stderr ----- ruff failed Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` could not be canonicalized + Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` does not point to a Python executable or a directory on disk "); }); diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index 2e84254f366a19..89eb974cc8dc56 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -10,7 +10,7 @@ use ruff_python_ast::PythonVersion; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::{ Db, Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource, - PythonVersionWithSource, SearchPathSettings, default_lint_registry, + PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry, }; static EMPTY_VENDORED: std::sync::LazyLock = std::sync::LazyLock::new(|| { @@ -37,7 +37,8 @@ impl ModuleDb { ) -> Result { let mut search_paths = SearchPathSettings::new(src_roots); if let Some(venv_path) = venv_path { - search_paths.python_path = PythonPath::from_cli_flag(venv_path); + search_paths.python_path = + PythonPath::sys_prefix(venv_path, SysPrefixPathOrigin::PythonCliFlag); } let db = Self::default(); diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 201781ff9d9f72..3bf7d972dc566d 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -918,6 +918,156 @@ fn cli_unknown_rules() -> anyhow::Result<()> { Ok(()) } +#[test] +fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { + let path_to_executable = if cfg!(windows) { + "my-venv/Scripts/python.exe" + } else { + "my-venv/bin/python" + }; + + let other_venv_path = "my-venv/foo/some_other_file.txt"; + + let case = TestCase::with_files([ + ("test.py", ""), + ( + if cfg!(windows) { + "my-venv/Lib/site-packages/foo.py" + } else { + "my-venv/lib/python3.13/site-packages/foo.py" + }, + "", + ), + (path_to_executable, ""), + (other_venv_path, ""), + ])?; + + // Passing a path to the installation works + assert_cmd_snapshot!(case.command().arg("--python").arg("my-venv"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // And so does passing a path to the executable inside the installation + assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // But random other paths inside the installation are rejected + assert_cmd_snapshot!(case.command().arg("--python").arg(other_venv_path), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/my-venv/foo/some_other_file.txt` does not point to a Python executable or a directory on disk + "); + + // And so are paths that do not exist on disk + assert_cmd_snapshot!(case.command().arg("--python").arg("not-a-directory-or-executable"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/not-a-directory-or-executable` does not point to a Python executable or a directory on disk + "); + + Ok(()) +} + +#[test] +fn python_cli_argument_system_installation() -> anyhow::Result<()> { + let path_to_executable = if cfg!(windows) { + "Python3.11/python.exe" + } else { + "Python3.11/bin/python" + }; + + let case = TestCase::with_files([ + ("test.py", ""), + ( + if cfg!(windows) { + "Python3.11/Lib/site-packages/foo.py" + } else { + "Python3.11/lib/python3.11/site-packages/foo.py" + }, + "", + ), + (path_to_executable, ""), + ])?; + + // Passing a path to the installation works + assert_cmd_snapshot!(case.command().arg("--python").arg("Python3.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // And so does passing a path to the executable inside the installation + assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn config_file_broken_python_setting() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "not-a-directory-or-executable" + "#, + ), + ("test.py", ""), + ])?; + + // TODO: this error message should say "invalid `python` configuration setting" rather than "invalid `--python` argument" + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/not-a-directory-or-executable` does not point to a Python executable or a directory on disk + "); + + Ok(()) +} + #[test] fn exit_code_only_warnings() -> anyhow::Result<()> { let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index a9a52a4b078097..4571655ce34424 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -12,7 +12,7 @@ use thiserror::Error; use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection}; use ty_python_semantic::{ ProgramSettings, PythonPath, PythonPlatform, PythonVersionFileSource, PythonVersionSource, - PythonVersionWithSource, SearchPathSettings, + PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, }; use super::settings::{Settings, TerminalSettings}; @@ -182,19 +182,27 @@ impl Options { custom_typeshed: typeshed.map(|path| path.absolute(project_root, system)), python_path: python .map(|python_path| { - PythonPath::from_cli_flag(python_path.absolute(project_root, system)) + PythonPath::sys_prefix( + python_path.absolute(project_root, system), + SysPrefixPathOrigin::PythonCliFlag, + ) }) .or_else(|| { - std::env::var("VIRTUAL_ENV") - .ok() - .map(PythonPath::from_virtual_env_var) + std::env::var("VIRTUAL_ENV").ok().map(|virtual_env| { + PythonPath::sys_prefix(virtual_env, SysPrefixPathOrigin::VirtualEnvVar) + }) }) .or_else(|| { - std::env::var("CONDA_PREFIX") - .ok() - .map(PythonPath::from_conda_prefix_var) + std::env::var("CONDA_PREFIX").ok().map(|path| { + PythonPath::sys_prefix(path, SysPrefixPathOrigin::CondaPrefixVar) + }) }) - .unwrap_or_else(|| PythonPath::Discover(project_root.to_path_buf())), + .unwrap_or_else(|| { + PythonPath::sys_prefix( + project_root.to_path_buf(), + SysPrefixPathOrigin::LocalVenv, + ) + }), } } diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 74cc931f893aa5..d5088828f9bcf1 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -139,15 +139,6 @@ pub(crate) fn search_paths(db: &dyn Db) -> SearchPathIterator { Program::get(db).search_paths(db).iter(db) } -/// Searches for a `.venv` directory in `project_root` that contains a `pyvenv.cfg` file. -fn discover_venv_in(system: &dyn System, project_root: &SystemPath) -> Option { - let virtual_env_directory = project_root.join(".venv"); - - system - .is_file(&virtual_env_directory.join("pyvenv.cfg")) - .then_some(virtual_env_directory) -} - #[derive(Debug, PartialEq, Eq)] pub struct SearchPaths { /// Search paths that have been statically determined purely from reading Ruff's configuration settings. @@ -243,68 +234,34 @@ impl SearchPaths { static_paths.push(stdlib_path); let (site_packages_paths, python_version) = match python_path { - PythonPath::SysPrefix(sys_prefix, origin) => { - tracing::debug!( - "Discovering site-packages paths from sys-prefix `{sys_prefix}` ({origin}')" - ); - // TODO: We may want to warn here if the venv's python version is older - // than the one resolved in the program settings because it indicates - // that the `target-version` is incorrectly configured or that the - // venv is out of date. - PythonEnvironment::new(sys_prefix, *origin, system)?.into_settings(system)? - } - - PythonPath::Resolve(target, origin) => { - tracing::debug!("Resolving {origin}: {target}"); - - let root = system - // If given a file, assume it's a Python executable, e.g., `.venv/bin/python3`, - // and search for a virtual environment in the root directory. Ideally, we'd - // invoke the target to determine `sys.prefix` here, but that's more complicated - // and may be deferred to uv. - .is_file(target) - .then(|| target.as_path()) - .take_if(|target| { - // Avoid using the target if it doesn't look like a Python executable, e.g., - // to deny cases like `.venv/bin/foo` - target - .file_name() - .is_some_and(|name| name.starts_with("python")) - }) - .and_then(SystemPath::parent) - .and_then(SystemPath::parent) - // If not a file, use the path as given and allow let `PythonEnvironment::new` - // handle the error. - .unwrap_or(target); - - PythonEnvironment::new(root, *origin, system)?.into_settings(system)? - } - - PythonPath::Discover(root) => { - tracing::debug!("Discovering virtual environment in `{root}`"); - discover_venv_in(db.system(), root) - .and_then(|virtual_env_path| { - tracing::debug!("Found `.venv` folder at `{}`", virtual_env_path); - - PythonEnvironment::new( - virtual_env_path.clone(), - SysPrefixPathOrigin::LocalVenv, - system, - ) - .and_then(|env| env.into_settings(system)) - .inspect_err(|err| { + PythonPath::IntoSysPrefix(path, origin) => { + if *origin == SysPrefixPathOrigin::LocalVenv { + tracing::debug!("Discovering virtual environment in `{path}`"); + let virtual_env_directory = path.join(".venv"); + + PythonEnvironment::new( + &virtual_env_directory, + SysPrefixPathOrigin::LocalVenv, + system, + ) + .and_then(|venv| venv.into_settings(system)) + .inspect_err(|err| { + if system.is_directory(&virtual_env_directory) { tracing::debug!( "Ignoring automatically detected virtual environment at `{}`: {}", - virtual_env_path, + &virtual_env_directory, err ); - }) - .ok() + } }) - .unwrap_or_else(|| { + .unwrap_or_else(|_| { tracing::debug!("No virtual environment found"); (SitePackagesPaths::default(), None) }) + } else { + tracing::debug!("Resolving {origin}: {path}"); + PythonEnvironment::new(path, *origin, system)?.into_settings(system)? + } } PythonPath::KnownSitePackages(paths) => ( diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 6057b19419768b..b968c57bd02c0c 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -262,8 +262,10 @@ impl SearchPathSettings { #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PythonPath { - /// A path that represents the value of [`sys.prefix`] at runtime in Python - /// for a given Python executable. + /// A path that either represents the value of [`sys.prefix`] at runtime in Python + /// for a given Python executable, or which represents a path relative to `sys.prefix` + /// that we will attempt later to resolve into `sys.prefix`. Exactly which this variant + /// represents depends on the [`SysPrefixPathOrigin`] element in the tuple. /// /// For the case of a virtual environment, where a /// Python binary is at `/.venv/bin/python`, `sys.prefix` is the path to @@ -275,13 +277,7 @@ pub enum PythonPath { /// `/opt/homebrew/lib/python3.X/site-packages`. /// /// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix - SysPrefix(SystemPathBuf, SysPrefixPathOrigin), - - /// Resolve a path to an executable (or environment directory) into a usable environment. - Resolve(SystemPathBuf, SysPrefixPathOrigin), - - /// Tries to discover a virtual environment in the given path. - Discover(SystemPathBuf), + IntoSysPrefix(SystemPathBuf, SysPrefixPathOrigin), /// Resolved site packages paths. /// @@ -291,16 +287,8 @@ pub enum PythonPath { } impl PythonPath { - pub fn from_virtual_env_var(path: impl Into) -> Self { - Self::SysPrefix(path.into(), SysPrefixPathOrigin::VirtualEnvVar) - } - - pub fn from_conda_prefix_var(path: impl Into) -> Self { - Self::Resolve(path.into(), SysPrefixPathOrigin::CondaPrefixVar) - } - - pub fn from_cli_flag(path: SystemPathBuf) -> Self { - Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag) + pub fn sys_prefix(path: impl Into, origin: SysPrefixPathOrigin) -> Self { + Self::IntoSysPrefix(path.into(), origin) } } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 85484304929d09..677ab75880dd8e 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -536,10 +536,18 @@ pub(crate) enum SitePackagesDiscoveryError { #[error("Invalid {1}: `{0}` could not be canonicalized")] CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), - /// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that - /// the provided path should point to `sys.prefix` directly, but the path wasn't a directory. - #[error("Invalid {1}: `{0}` does not point to a directory on disk")] - SysPrefixNotADirectory(SystemPathBuf, SysPrefixPathOrigin), + /// `site-packages` discovery failed because the provided path doesn't appear to point to + /// a Python executable or a `sys.prefix` directory. + #[error( + "Invalid {1}: `{0}` does not point to a {thing}", + + thing = if .1.must_point_directly_to_sys_prefix() { + "directory on disk" + } else { + "Python executable or a directory on disk" + } + )] + PathNotExecutableOrDirectory(SystemPathBuf, SysPrefixPathOrigin), /// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that /// the provided path should point to the `sys.prefix` of a virtual environment, @@ -738,24 +746,79 @@ impl SysPrefixPath { let canonicalized = system .canonicalize_path(unvalidated_path) .map_err(|io_err| { - SitePackagesDiscoveryError::CanonicalizationError( - unvalidated_path.to_path_buf(), - origin, - io_err, - ) + let unvalidated_path = unvalidated_path.to_path_buf(); + if io_err.kind() == io::ErrorKind::NotFound { + SitePackagesDiscoveryError::PathNotExecutableOrDirectory( + unvalidated_path, + origin, + ) + } else { + SitePackagesDiscoveryError::CanonicalizationError( + unvalidated_path, + origin, + io_err, + ) + } })?; - system - .is_directory(&canonicalized) - .then_some(Self { - inner: canonicalized, - origin, - }) - .ok_or_else(|| { - SitePackagesDiscoveryError::SysPrefixNotADirectory( + + if origin.must_point_directly_to_sys_prefix() { + return system + .is_directory(&canonicalized) + .then_some(Self { + inner: canonicalized, + origin, + }) + .ok_or_else(|| { + SitePackagesDiscoveryError::PathNotExecutableOrDirectory( + unvalidated_path.to_path_buf(), + origin, + ) + }); + } + + let sys_prefix = if system.is_file(&canonicalized) + && canonicalized + .file_name() + .is_some_and(|name| name.starts_with("python")) + { + // It looks like they passed us a path to a Python executable, e.g. `.venv/bin/python3`. + // Try to figure out the `sys.prefix` value from the Python executable. + let sys_prefix = if cfg!(windows) { + // On Windows, the relative path to the Python executable from `sys.prefix` + // is different depending on whether it's a virtual environment or a system installation. + // System installations have their executable at `/python.exe`, + // whereas virtual environments have their executable at `/Scripts/python.exe`. + canonicalized.parent().and_then(|parent| { + if parent.file_name() == Some("Scripts") { + parent.parent() + } else { + Some(parent) + } + }) + } else { + // On Unix, `sys.prefix` is always the grandparent directory of the Python executable, + // regardless of whether it's a virtual environment or a system installation. + canonicalized.ancestors().nth(2) + }; + sys_prefix.map(SystemPath::to_path_buf).ok_or_else(|| { + SitePackagesDiscoveryError::PathNotExecutableOrDirectory( unvalidated_path.to_path_buf(), origin, ) - }) + })? + } else if system.is_directory(&canonicalized) { + canonicalized + } else { + return Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory( + unvalidated_path.to_path_buf(), + origin, + )); + }; + + Ok(Self { + inner: sys_prefix, + origin, + }) } fn from_executable_home_path(path: &PythonHomePath) -> Option { @@ -812,12 +875,26 @@ pub enum SysPrefixPathOrigin { impl SysPrefixPathOrigin { /// Whether the given `sys.prefix` path must be a virtual environment (rather than a system /// Python environment). - pub(crate) fn must_be_virtual_env(self) -> bool { + pub(crate) const fn must_be_virtual_env(self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, Self::PythonCliFlag | Self::DerivedFromPyvenvCfg | Self::CondaPrefixVar => false, } } + + /// Whether paths with this origin always point directly to the `sys.prefix` directory. + /// + /// Some variants can point either directly to `sys.prefix` or to a Python executable inside + /// the `sys.prefix` directory, e.g. the `--python` CLI flag. + pub(crate) const fn must_point_directly_to_sys_prefix(self) -> bool { + match self { + Self::PythonCliFlag => false, + Self::VirtualEnvVar + | Self::CondaPrefixVar + | Self::DerivedFromPyvenvCfg + | Self::LocalVenv => true, + } + } } impl Display for SysPrefixPathOrigin { @@ -1378,7 +1455,7 @@ mod tests { let system = TestSystem::default(); assert!(matches!( PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), - Err(SitePackagesDiscoveryError::CanonicalizationError(..)) + Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory(..)) )); } @@ -1391,7 +1468,7 @@ mod tests { .unwrap(); assert!(matches!( PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system), - Err(SitePackagesDiscoveryError::SysPrefixNotADirectory(..)) + Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory(..)) )); } diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 234c894725ab61..ba247811063c13 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -272,7 +272,7 @@ fn run_test( python_path: configuration .python() .map(|sys_prefix| { - PythonPath::SysPrefix( + PythonPath::IntoSysPrefix( sys_prefix.to_path_buf(), SysPrefixPathOrigin::PythonCliFlag, ) From 74a4e9af3d52142247be218bd4b524d58b1c56e5 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:50:02 -0400 Subject: [PATCH 340/487] Combine lint and syntax error handling (#18471) ## Summary This is a spin-off from https://github.com/astral-sh/ruff/pull/18447#discussion_r2125844669 to avoid using `Message::noqa_code` to differentiate between lints and syntax errors. I went through all of the calls on `main` and on the branch from #18447, and the instance in `ruff_server` noted in the linked comment was actually the primary place where this was being done. Other calls to `noqa_code` are typically some variation of `message.noqa_code().map_or(String::new, format!(...))`, with the major exception of the gitlab output format: https://github.com/astral-sh/ruff/blob/a120610b5b01a9e7bb91740a23f6c2b5bbcd4b5f/crates/ruff_linter/src/message/gitlab.rs#L93-L105 which obviously assumes that `None` means syntax error. A simple fix here would be to use `message.name()` for `check_name` instead of the noqa code, but I'm not sure how breaking that would be. This could just be: ```rust let description = message.body(); let description = description.strip_prefix("SyntaxError: ").unwrap_or(description).to_string(); let check_name = message.name(); ``` In that case. This sounds reasonable based on the [Code Quality report format](https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format) docs: > | Name | Type | Description| > |-----|-----|----| > |`check_name` | String | A unique name representing the check, or rule, associated with this violation. | ## Test Plan Existing tests --- crates/ruff_server/src/lint.rs | 97 ++++++++++------------------------ crates/ruff_wasm/src/lib.rs | 47 ++++++---------- 2 files changed, 46 insertions(+), 98 deletions(-) diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index 9ad6be52762511..bf20172cbaaa99 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -12,7 +12,6 @@ use crate::{ use ruff_diagnostics::{Applicability, Edit, Fix}; use ruff_linter::{ Locator, - codes::Rule, directives::{Flags, extract_directives}, generate_noqa_edits, linter::check_path, @@ -166,26 +165,17 @@ pub(crate) fn check( messages .into_iter() .zip(noqa_edits) - .filter_map(|(message, noqa_edit)| match message.to_rule() { - Some(rule) => Some(to_lsp_diagnostic( - rule, - &message, - noqa_edit, - &source_kind, - locator.to_index(), - encoding, - )), - None => { - if show_syntax_errors { - Some(syntax_error_to_lsp_diagnostic( - &message, - &source_kind, - locator.to_index(), - encoding, - )) - } else { - None - } + .filter_map(|(message, noqa_edit)| { + if message.is_syntax_error() && !show_syntax_errors { + None + } else { + Some(to_lsp_diagnostic( + &message, + noqa_edit, + &source_kind, + locator.to_index(), + encoding, + )) } }); @@ -241,7 +231,6 @@ pub(crate) fn fixes_for_diagnostics( /// Generates an LSP diagnostic with an associated cell index for the diagnostic to go in. /// If the source kind is a text document, the cell index will always be `0`. fn to_lsp_diagnostic( - rule: Rule, diagnostic: &Message, noqa_edit: Option, source_kind: &SourceKind, @@ -253,11 +242,13 @@ fn to_lsp_diagnostic( let body = diagnostic.body().to_string(); let fix = diagnostic.fix(); let suggestion = diagnostic.suggestion(); + let code = diagnostic.to_noqa_code(); let fix = fix.and_then(|fix| fix.applies(Applicability::Unsafe).then_some(fix)); let data = (fix.is_some() || noqa_edit.is_some()) .then(|| { + let code = code?.to_string(); let edits = fix .into_iter() .flat_map(Fix::edits) @@ -274,14 +265,12 @@ fn to_lsp_diagnostic( title: suggestion.unwrap_or(name).to_string(), noqa_edit, edits, - code: rule.noqa_code().to_string(), + code, }) .ok() }) .flatten(); - let code = rule.noqa_code().to_string(); - let range: lsp_types::Range; let cell: usize; @@ -297,14 +286,25 @@ fn to_lsp_diagnostic( range = diagnostic_range.to_range(source_kind.source_code(), index, encoding); } + let (severity, tags, code) = if let Some(code) = code { + let code = code.to_string(); + ( + Some(severity(&code)), + tags(&code), + Some(lsp_types::NumberOrString::String(code)), + ) + } else { + (None, None, None) + }; + ( cell, lsp_types::Diagnostic { range, - severity: Some(severity(&code)), - tags: tags(&code), - code: Some(lsp_types::NumberOrString::String(code)), - code_description: rule.url().and_then(|url| { + severity, + tags, + code, + code_description: diagnostic.to_url().and_then(|url| { Some(lsp_types::CodeDescription { href: lsp_types::Url::parse(&url).ok()?, }) @@ -317,45 +317,6 @@ fn to_lsp_diagnostic( ) } -fn syntax_error_to_lsp_diagnostic( - syntax_error: &Message, - source_kind: &SourceKind, - index: &LineIndex, - encoding: PositionEncoding, -) -> (usize, lsp_types::Diagnostic) { - let range: lsp_types::Range; - let cell: usize; - - if let Some(notebook_index) = source_kind.as_ipy_notebook().map(Notebook::index) { - NotebookRange { cell, range } = syntax_error.range().to_notebook_range( - source_kind.source_code(), - index, - notebook_index, - encoding, - ); - } else { - cell = usize::default(); - range = syntax_error - .range() - .to_range(source_kind.source_code(), index, encoding); - } - - ( - cell, - lsp_types::Diagnostic { - range, - severity: Some(lsp_types::DiagnosticSeverity::ERROR), - tags: None, - code: None, - code_description: None, - source: Some(DIAGNOSTIC_NAME.into()), - message: syntax_error.body().to_string(), - related_information: None, - data: None, - }, - ) -} - fn diagnostic_edit_range( range: TextRange, source_kind: &SourceKind, diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 55c339c53c41b6..24e683042bc8c0 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -207,36 +207,23 @@ impl Workspace { let messages: Vec = messages .into_iter() - .map(|msg| { - let message = msg.body().to_string(); - let range = msg.range(); - match msg.to_noqa_code() { - Some(code) => ExpandedMessage { - code: Some(code.to_string()), - message, - start_location: source_code.line_column(range.start()).into(), - end_location: source_code.line_column(range.end()).into(), - fix: msg.fix().map(|fix| ExpandedFix { - message: msg.suggestion().map(ToString::to_string), - edits: fix - .edits() - .iter() - .map(|edit| ExpandedEdit { - location: source_code.line_column(edit.start()).into(), - end_location: source_code.line_column(edit.end()).into(), - content: edit.content().map(ToString::to_string), - }) - .collect(), - }), - }, - None => ExpandedMessage { - code: None, - message, - start_location: source_code.line_column(range.start()).into(), - end_location: source_code.line_column(range.end()).into(), - fix: None, - }, - } + .map(|msg| ExpandedMessage { + code: msg.to_noqa_code().map(|code| code.to_string()), + message: msg.body().to_string(), + start_location: source_code.line_column(msg.start()).into(), + end_location: source_code.line_column(msg.end()).into(), + fix: msg.fix().map(|fix| ExpandedFix { + message: msg.suggestion().map(ToString::to_string), + edits: fix + .edits() + .iter() + .map(|edit| ExpandedEdit { + location: source_code.line_column(edit.start()).into(), + end_location: source_code.line_column(edit.end()).into(), + content: edit.content().map(ToString::to_string), + }) + .collect(), + }), }) .collect(); From c0bb83b88279f5ea21a3b9e8910d23e8437c89b5 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 5 Jun 2025 17:57:08 +0300 Subject: [PATCH 341/487] [`perflint`] fix missing parentheses for lambda and ternary conditions (PERF401, PERF403) (#18412) Closes #18405 --- .../test/fixtures/perflint/PERF401.py | 12 +++++ .../test/fixtures/perflint/PERF403.py | 13 +++++ .../rules/manual_dict_comprehension.rs | 9 ++-- .../rules/manual_list_comprehension.rs | 11 +++-- ...__perflint__tests__PERF401_PERF401.py.snap | 22 +++++++++ ...__perflint__tests__PERF403_PERF403.py.snap | 20 ++++++++ ...t__tests__preview__PERF401_PERF401.py.snap | 48 +++++++++++++++++++ ...t__tests__preview__PERF403_PERF403.py.snap | 46 ++++++++++++++++++ 8 files changed, 172 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py b/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py index 263f0ff6c6eb97..880ca4c09a0919 100644 --- a/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py +++ b/crates/ruff_linter/resources/test/fixtures/perflint/PERF401.py @@ -266,3 +266,15 @@ def f(): result = list() # this should be replaced with a comprehension for i in values: result.append(i + 1) # PERF401 + +def f(): + src = [1] + dst = [] + + for i in src: + if True if True else False: + dst.append(i) + + for i in src: + if lambda: 0: + dst.append(i) diff --git a/crates/ruff_linter/resources/test/fixtures/perflint/PERF403.py b/crates/ruff_linter/resources/test/fixtures/perflint/PERF403.py index 89afca39ffd7e6..9264af4f0f7218 100644 --- a/crates/ruff_linter/resources/test/fixtures/perflint/PERF403.py +++ b/crates/ruff_linter/resources/test/fixtures/perflint/PERF403.py @@ -151,3 +151,16 @@ def foo(): result = {} for idx, name in indices, fruit: result[name] = idx # PERF403 + + +def foo(): + src = (("x", 1),) + dst = {} + + for k, v in src: + if True if True else False: + dst[k] = v + + for k, v in src: + if lambda: 0: + dst[k] = v \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs index 298f62af968ce3..be8c6feb0f5c10 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs @@ -346,10 +346,11 @@ fn convert_to_dict_comprehension( // since if the assignment expression appears // internally (e.g. as an operand in a boolean // operation) then it will already be parenthesized. - if test.is_named_expr() { - format!(" if ({})", locator.slice(test.range())) - } else { - format!(" if {}", locator.slice(test.range())) + match test { + Expr::Named(_) | Expr::If(_) | Expr::Lambda(_) => { + format!(" if ({})", locator.slice(test.range())) + } + _ => format!(" if {}", locator.slice(test.range())), } } None => String::new(), diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs index 2566ecb770ce62..da95d98ad350f2 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs @@ -358,7 +358,7 @@ fn convert_to_list_extend( fix_type: ComprehensionType, binding: &Binding, for_stmt: &ast::StmtFor, - if_test: Option<&ast::Expr>, + if_test: Option<&Expr>, to_append: &Expr, checker: &Checker, ) -> Result { @@ -374,10 +374,11 @@ fn convert_to_list_extend( // since if the assignment expression appears // internally (e.g. as an operand in a boolean // operation) then it will already be parenthesized. - if test.is_named_expr() { - format!(" if ({})", locator.slice(test.range())) - } else { - format!(" if {}", locator.slice(test.range())) + match test { + Expr::Named(_) | Expr::If(_) | Expr::Lambda(_) => { + format!(" if ({})", locator.slice(test.range())) + } + _ => format!(" if {}", locator.slice(test.range())), } } None => String::new(), diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap index 92d7d6df85ba40..d451379bcf5ec2 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF401_PERF401.py.snap @@ -219,5 +219,27 @@ PERF401.py:268:9: PERF401 Use a list comprehension to create a transformed list 267 | for i in values: 268 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 +269 | +270 | def f(): | = help: Replace for loop with list comprehension + +PERF401.py:276:13: PERF401 Use a list comprehension to create a transformed list + | +274 | for i in src: +275 | if True if True else False: +276 | dst.append(i) + | ^^^^^^^^^^^^^ PERF401 +277 | +278 | for i in src: + | + = help: Replace for loop with list comprehension + +PERF401.py:280:13: PERF401 Use `list.extend` to create a transformed list + | +278 | for i in src: +279 | if lambda: 0: +280 | dst.append(i) + | ^^^^^^^^^^^^^ PERF401 + | + = help: Replace for loop with list.extend diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF403_PERF403.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF403_PERF403.py.snap index 2615a1772d1b0b..f398eae1da034d 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF403_PERF403.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__PERF403_PERF403.py.snap @@ -128,3 +128,23 @@ PERF403.py:153:9: PERF403 Use a dictionary comprehension instead of a for-loop | ^^^^^^^^^^^^^^^^^^ PERF403 | = help: Replace for loop with dict comprehension + +PERF403.py:162:13: PERF403 Use a dictionary comprehension instead of a for-loop + | +160 | for k, v in src: +161 | if True if True else False: +162 | dst[k] = v + | ^^^^^^^^^^ PERF403 +163 | +164 | for k, v in src: + | + = help: Replace for loop with dict comprehension + +PERF403.py:166:13: PERF403 Use a dictionary comprehension instead of a for-loop + | +164 | for k, v in src: +165 | if lambda: 0: +166 | dst[k] = v + | ^^^^^^^^^^ PERF403 + | + = help: Replace for loop with dict comprehension diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap index 35f6a6bd927d47..d5b614bfe98bb6 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF401_PERF401.py.snap @@ -517,6 +517,8 @@ PERF401.py:268:9: PERF401 [*] Use a list comprehension to create a transformed l 267 | for i in values: 268 | result.append(i + 1) # PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401 +269 | +270 | def f(): | = help: Replace for loop with list comprehension @@ -529,3 +531,49 @@ PERF401.py:268:9: PERF401 [*] Use a list comprehension to create a transformed l 268 |- result.append(i + 1) # PERF401 266 |+ # this should be replaced with a comprehension 267 |+ result = [i + 1 for i in values] # PERF401 +269 268 | +270 269 | def f(): +271 270 | src = [1] + +PERF401.py:276:13: PERF401 [*] Use a list comprehension to create a transformed list + | +274 | for i in src: +275 | if True if True else False: +276 | dst.append(i) + | ^^^^^^^^^^^^^ PERF401 +277 | +278 | for i in src: + | + = help: Replace for loop with list comprehension + +ℹ Unsafe fix +269 269 | +270 270 | def f(): +271 271 | src = [1] +272 |- dst = [] +273 272 | +274 |- for i in src: +275 |- if True if True else False: +276 |- dst.append(i) + 273 |+ dst = [i for i in src if (True if True else False)] +277 274 | +278 275 | for i in src: +279 276 | if lambda: 0: + +PERF401.py:280:13: PERF401 [*] Use `list.extend` to create a transformed list + | +278 | for i in src: +279 | if lambda: 0: +280 | dst.append(i) + | ^^^^^^^^^^^^^ PERF401 + | + = help: Replace for loop with list.extend + +ℹ Unsafe fix +275 275 | if True if True else False: +276 276 | dst.append(i) +277 277 | +278 |- for i in src: +279 |- if lambda: 0: +280 |- dst.append(i) + 278 |+ dst.extend(i for i in src if (lambda: 0)) diff --git a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF403_PERF403.py.snap b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF403_PERF403.py.snap index 9e01e0db30b486..b83358171cbe7f 100644 --- a/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF403_PERF403.py.snap +++ b/crates/ruff_linter/src/rules/perflint/snapshots/ruff_linter__rules__perflint__tests__preview__PERF403_PERF403.py.snap @@ -305,3 +305,49 @@ PERF403.py:153:9: PERF403 [*] Use a dictionary comprehension instead of a for-lo 152 |- for idx, name in indices, fruit: 153 |- result[name] = idx # PERF403 151 |+ result = {name: idx for idx, name in (indices, fruit)} # PERF403 +154 152 | +155 153 | +156 154 | def foo(): + +PERF403.py:162:13: PERF403 [*] Use a dictionary comprehension instead of a for-loop + | +160 | for k, v in src: +161 | if True if True else False: +162 | dst[k] = v + | ^^^^^^^^^^ PERF403 +163 | +164 | for k, v in src: + | + = help: Replace for loop with dict comprehension + +ℹ Unsafe fix +155 155 | +156 156 | def foo(): +157 157 | src = (("x", 1),) +158 |- dst = {} +159 158 | +160 |- for k, v in src: +161 |- if True if True else False: +162 |- dst[k] = v + 159 |+ dst = {k: v for k, v in src if (True if True else False)} +163 160 | +164 161 | for k, v in src: +165 162 | if lambda: 0: + +PERF403.py:166:13: PERF403 [*] Use `dict.update` instead of a for-loop + | +164 | for k, v in src: +165 | if lambda: 0: +166 | dst[k] = v + | ^^^^^^^^^^ PERF403 + | + = help: Replace for loop with `dict.update` + +ℹ Unsafe fix +161 161 | if True if True else False: +162 162 | dst[k] = v +163 163 | +164 |- for k, v in src: +165 |- if lambda: 0: +166 |- dst[k] = v + 164 |+ dst.update({k: v for k, v in src if (lambda: 0)}) From 55100209c781b35e22b38d22bd7a732ac6b1cc5c Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 5 Jun 2025 11:15:19 -0400 Subject: [PATCH 342/487] [ty] IDE: add support for `object.` completions (#18468) This PR adds logic for detecting `Name Dot [Name]` token patterns, finding the corresponding `ExprAttribute`, getting the type of the object and returning the members available on that object. Here's a video demonstrating this working: https://github.com/user-attachments/assets/42ce78e8-5930-4211-a18a-fa2a0434d0eb Ref astral-sh/ty#86 --- crates/ty_ide/src/completion.rs | 447 +++++++++++++++++- .../ty_python_semantic/src/semantic_model.rs | 8 +- crates/ty_python_semantic/src/types.rs | 1 + .../src/types/ide_support.rs | 2 +- crates/ty_server/src/server.rs | 1 + playground/ty/src/Editor/Editor.tsx | 2 +- 6 files changed, 437 insertions(+), 24 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index ebc0c9197ebbbd..6584b7339b7fcc 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1,10 +1,13 @@ +use std::cmp::Ordering; + use ruff_db::files::File; use ruff_db::parsed::{ParsedModule, parsed_module}; -use ruff_python_parser::TokenAt; +use ruff_python_ast as ast; +use ruff_python_parser::{Token, TokenAt, TokenKind}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Db; -use crate::find_node::{CoveringNode, covering_node}; +use crate::find_node::covering_node; #[derive(Debug, Clone)] pub struct Completion { @@ -14,13 +17,16 @@ pub struct Completion { pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec { let parsed = parsed_module(db.upcast(), file); - let Some(target) = find_target(parsed, offset) else { + let Some(target) = CompletionTargetTokens::find(parsed, offset).ast(parsed) else { return vec![]; }; let model = ty_python_semantic::SemanticModel::new(db.upcast(), file); - let mut completions = model.completions(target.node()); - completions.sort(); + let mut completions = match target { + CompletionTargetAst::ObjectDot { expr } => model.attribute_completions(expr), + CompletionTargetAst::Scoped { node } => model.scoped_completions(node), + }; + completions.sort_by(|name1, name2| compare_suggestions(name1, name2)); completions.dedup(); completions .into_iter() @@ -28,30 +34,253 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec .collect() } -fn find_target(parsed: &ParsedModule, offset: TextSize) -> Option { - let offset = match parsed.tokens().at_offset(offset) { - TokenAt::None => { - return Some(covering_node( - parsed.syntax().into(), - TextRange::empty(offset), - )); +/// The kind of tokens identified under the cursor. +#[derive(Debug)] +enum CompletionTargetTokens<'t> { + /// A `object.attribute` token form was found, where + /// `attribute` may be empty. + /// + /// This requires a name token followed by a dot token. + ObjectDot { + /// The token preceding the dot. + object: &'t Token, + /// The token, if non-empty, following the dot. + /// + /// This is currently unused, but we should use this + /// eventually to remove completions that aren't a + /// prefix of what has already been typed. (We are + /// currently relying on the LSP client to do this.) + #[expect(dead_code)] + attribute: Option<&'t Token>, + }, + /// A token was found under the cursor, but it didn't + /// match any of our anticipated token patterns. + Generic { token: &'t Token }, + /// No token was found, but we have the offset of the + /// cursor. + Unknown { offset: TextSize }, +} + +impl<'t> CompletionTargetTokens<'t> { + /// Look for the best matching token pattern at the given offset. + fn find(parsed: &ParsedModule, offset: TextSize) -> CompletionTargetTokens<'_> { + static OBJECT_DOT_EMPTY: [TokenKind; 2] = [TokenKind::Name, TokenKind::Dot]; + static OBJECT_DOT_NON_EMPTY: [TokenKind; 3] = + [TokenKind::Name, TokenKind::Dot, TokenKind::Name]; + + let offset = match parsed.tokens().at_offset(offset) { + TokenAt::None => return CompletionTargetTokens::Unknown { offset }, + TokenAt::Single(tok) => tok.end(), + TokenAt::Between(_, tok) => tok.start(), + }; + let before = parsed.tokens().before(offset); + if let Some([object, _dot]) = token_suffix_by_kinds(before, OBJECT_DOT_EMPTY) { + CompletionTargetTokens::ObjectDot { + object, + attribute: None, + } + } else if let Some([object, _dot, attribute]) = + token_suffix_by_kinds(before, OBJECT_DOT_NON_EMPTY) + { + CompletionTargetTokens::ObjectDot { + object, + attribute: Some(attribute), + } + } else { + let Some(last) = before.last() else { + return CompletionTargetTokens::Unknown { offset }; + }; + CompletionTargetTokens::Generic { token: last } } - TokenAt::Single(tok) => tok.end(), - TokenAt::Between(_, tok) => tok.start(), - }; - let before = parsed.tokens().before(offset); - let last = before.last()?; - let covering_node = covering_node(parsed.syntax().into(), last.range()); - Some(covering_node) + } + + /// Returns a corresponding AST node for these tokens. + /// + /// If no plausible AST node could be found, then `None` is returned. + fn ast(&self, parsed: &'t ParsedModule) -> Option> { + match *self { + CompletionTargetTokens::ObjectDot { object, .. } => { + let covering_node = covering_node(parsed.syntax().into(), object.range()) + .find(|node| node.is_expr_attribute()) + .ok()?; + match covering_node.node() { + ast::AnyNodeRef::ExprAttribute(expr) => { + Some(CompletionTargetAst::ObjectDot { expr }) + } + _ => None, + } + } + CompletionTargetTokens::Generic { token } => { + let covering_node = covering_node(parsed.syntax().into(), token.range()); + Some(CompletionTargetAst::Scoped { + node: covering_node.node(), + }) + } + CompletionTargetTokens::Unknown { offset } => { + let range = TextRange::empty(offset); + let covering_node = covering_node(parsed.syntax().into(), range); + Some(CompletionTargetAst::Scoped { + node: covering_node.node(), + }) + } + } + } +} + +/// The AST node patterns that we support identifying under the cursor. +#[derive(Debug)] +enum CompletionTargetAst<'t> { + /// A `object.attribute` scenario, where we want to + /// list attributes on `object` for completions. + ObjectDot { expr: &'t ast::ExprAttribute }, + /// A scoped scenario, where we want to list all items available in + /// the most narrow scope containing the giving AST node. + Scoped { node: ast::AnyNodeRef<'t> }, +} + +/// Returns a suffix of `tokens` corresponding to the `kinds` given. +/// +/// If a suffix of `tokens` with the given `kinds` could not be found, +/// then `None` is returned. +/// +/// This is useful for matching specific patterns of token sequences +/// in order to identify what kind of completions we should offer. +fn token_suffix_by_kinds( + tokens: &[Token], + kinds: [TokenKind; N], +) -> Option<[&Token; N]> { + if kinds.len() > tokens.len() { + return None; + } + for (token, expected_kind) in tokens.iter().rev().zip(kinds.iter().rev()) { + if &token.kind() != expected_kind { + return None; + } + } + Some(std::array::from_fn(|i| { + &tokens[tokens.len() - (kinds.len() - i)] + })) +} + +/// Order completions lexicographically, with these exceptions: +/// +/// 1) A `_[^_]` prefix sorts last and +/// 2) A `__` prefix sorts last except before (1) +/// +/// This has the effect of putting all dunder attributes after "normal" +/// attributes, and all single-underscore attributes after dunder attributes. +fn compare_suggestions(name1: &str, name2: &str) -> Ordering { + /// A helper type for sorting completions based only on name. + /// + /// This sorts "normal" names first, then dunder names and finally + /// single-underscore names. This matches the order of the variants defined for + /// this enum, which is in turn picked up by the derived trait implementation + /// for `Ord`. + #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] + enum Kind { + Normal, + Dunder, + Sunder, + } + + impl Kind { + fn classify(name: &str) -> Kind { + // Dunder needs a prefix and suffix double underscore. + // When there's only a prefix double underscore, this + // results in explicit name mangling. We let that be + // classified as-if they were single underscore names. + // + // Ref: + if name.starts_with("__") && name.ends_with("__") { + Kind::Dunder + } else if name.starts_with('_') { + Kind::Sunder + } else { + Kind::Normal + } + } + } + + let (kind1, kind2) = (Kind::classify(name1), Kind::classify(name2)); + kind1.cmp(&kind2).then_with(|| name1.cmp(name2)) } #[cfg(test)] mod tests { use insta::assert_snapshot; + use ruff_python_parser::{Mode, ParseOptions, TokenKind, Tokens}; use crate::completion; use crate::tests::{CursorTest, cursor_test}; + use super::token_suffix_by_kinds; + + #[test] + fn token_suffixes_match() { + insta::assert_debug_snapshot!( + token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Newline]), + @r" + Some( + [ + Newline 5..5, + ], + ) + ", + ); + + insta::assert_debug_snapshot!( + token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Name, TokenKind::Newline]), + @r" + Some( + [ + Name 4..5, + Newline 5..5, + ], + ) + ", + ); + + let all = [ + TokenKind::Name, + TokenKind::Dot, + TokenKind::Name, + TokenKind::Newline, + ]; + insta::assert_debug_snapshot!( + token_suffix_by_kinds(&tokenize("foo.x"), all), + @r" + Some( + [ + Name 0..3, + Dot 3..4, + Name 4..5, + Newline 5..5, + ], + ) + ", + ); + } + + #[test] + fn token_suffixes_nomatch() { + insta::assert_debug_snapshot!( + token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Name]), + @"None", + ); + + let too_many = [ + TokenKind::Dot, + TokenKind::Name, + TokenKind::Dot, + TokenKind::Name, + TokenKind::Newline, + ]; + insta::assert_debug_snapshot!( + token_suffix_by_kinds(&tokenize("foo.x"), too_many), + @"None", + ); + } + // At time of writing (2025-05-22), the tests below show some of the // naivete of our completions. That is, we don't even take what has been // typed into account. We just kind return all possible completions @@ -113,8 +342,7 @@ import re re. ", ); - - assert_snapshot!(test.completions(), @"re"); + test.assert_completions_include("findall"); } #[test] @@ -697,6 +925,119 @@ class Foo(", "); } + #[test] + fn class_init1() { + let test = cursor_test( + "\ +class Quux: + def __init__(self): + self.foo = 1 + self.bar = 2 + self.baz = 3 + +quux = Quux() +quux. +", + ); + + assert_snapshot!(test.completions(), @r" + bar + baz + foo + __annotations__ + __class__ + __delattr__ + __dict__ + __dir__ + __doc__ + __eq__ + __format__ + __getattribute__ + __getstate__ + __hash__ + __init__ + __init_subclass__ + __module__ + __ne__ + __new__ + __reduce__ + __reduce_ex__ + __repr__ + __setattr__ + __sizeof__ + __str__ + __subclasshook__ + "); + } + + #[test] + fn class_init2() { + let test = cursor_test( + "\ +class Quux: + def __init__(self): + self.foo = 1 + self.bar = 2 + self.baz = 3 + +quux = Quux() +quux.b +", + ); + + assert_snapshot!(test.completions(), @r" + bar + baz + foo + __annotations__ + __class__ + __delattr__ + __dict__ + __dir__ + __doc__ + __eq__ + __format__ + __getattribute__ + __getstate__ + __hash__ + __init__ + __init_subclass__ + __module__ + __ne__ + __new__ + __reduce__ + __reduce_ex__ + __repr__ + __setattr__ + __sizeof__ + __str__ + __subclasshook__ + "); + } + + #[test] + fn class_init3() { + let test = cursor_test( + "\ +class Quux: + def __init__(self): + self.foo = 1 + self.bar = 2 + self. + self.baz = 3 +", + ); + + // FIXME: This should list completions on `self`, which should + // include, at least, `foo` and `bar`. At time of writing + // (2025-06-04), the type of `self` is inferred as `Unknown` in + // this context. This in turn prevents us from getting a list + // of available attributes. + // + // See: https://github.com/astral-sh/ty/issues/159 + assert_snapshot!(test.completions(), @""); + } + // We don't yet take function parameters into account. #[test] fn call_prefix1() { @@ -865,6 +1206,59 @@ Re test.assert_completions_do_not_include("ReadableBuffer"); } + #[test] + fn nested_attribute_access() { + let test = cursor_test( + "\ +class A: + x: str + +class B: + a: A + +b = B() +b.a. +", + ); + + // FIXME: These should be flipped. + test.assert_completions_include("a"); + test.assert_completions_do_not_include("x"); + } + + #[test] + fn ordering() { + let test = cursor_test( + "\ +class A: + foo: str + _foo: str + __foo__: str + __foo: str + FOO: str + _FOO: str + __FOO__: str + __FOO: str + +A. +", + ); + + assert_snapshot!( + test.completions_if(|name| name.contains("FOO") || name.contains("foo")), + @r" + FOO + foo + __FOO__ + __foo__ + _FOO + __FOO + __foo + _foo + ", + ); + } + // Ref: https://github.com/astral-sh/ty/issues/572 #[test] fn scope_id_missing_function_identifier1() { @@ -1018,6 +1412,10 @@ def _(): impl CursorTest { fn completions(&self) -> String { + self.completions_if(|_| true) + } + + fn completions_if(&self, predicate: impl Fn(&str) -> bool) -> String { let completions = completion(&self.db, self.file, self.cursor_offset); if completions.is_empty() { return "".to_string(); @@ -1025,6 +1423,7 @@ def _(): completions .into_iter() .map(|completion| completion.label) + .filter(|label| predicate(label)) .collect::>() .join("\n") } @@ -1053,4 +1452,10 @@ def _(): ); } } + + fn tokenize(src: &str) -> Tokens { + let parsed = ruff_python_parser::parse(src, ParseOptions::from(Mode::Module)) + .expect("valid Python source for token stream"); + parsed.tokens().clone() + } } diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 4a85f138a32df4..b850d9d6c37988 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -41,12 +41,18 @@ impl<'db> SemanticModel<'db> { resolve_module(self.db, module_name) } + /// Returns completions for symbols available in a `object.` context. + pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec { + let ty = node.value.inferred_type(self); + crate::types::all_members(self.db, ty).into_iter().collect() + } + /// Returns completions for symbols available in the scope containing the /// given expression. /// /// If a scope could not be determined, then completions for the global /// scope of this model's `File` are returned. - pub fn completions(&self, node: ast::AnyNodeRef<'_>) -> Vec { + pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec { let index = semantic_index(self.db, self.file); // TODO: We currently use `try_expression_scope_id` here as a hotfix for [1]. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ebccf4a0e7bb4f..468fd018e93b7e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -45,6 +45,7 @@ use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; +pub use crate::types::ide_support::all_members; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 13a1ec3487fcdb..b79d232c70737b 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -195,6 +195,6 @@ impl AllMembers { /// List all members of a given type: anything that would be valid when accessed /// as an attribute on an object of the given type. -pub(crate) fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet { +pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet { AllMembers::of(db, ty).members } diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 84093d6ffa65b0..4846e0219c5ff5 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -180,6 +180,7 @@ impl Server { completion_provider: experimental .is_some_and(Experimental::is_completions_enabled) .then_some(lsp_types::CompletionOptions { + trigger_characters: Some(vec!['.'.to_string()]), ..Default::default() }), ..Default::default() diff --git a/playground/ty/src/Editor/Editor.tsx b/playground/ty/src/Editor/Editor.tsx index fca03cd08b4adb..a25e3b4ddb7abe 100644 --- a/playground/ty/src/Editor/Editor.tsx +++ b/playground/ty/src/Editor/Editor.tsx @@ -179,7 +179,7 @@ class PlaygroundServer monaco.languages.registerDocumentFormattingEditProvider("python", this); } - triggerCharacters: undefined; + triggerCharacters: string[] = ["."]; provideCompletionItems( model: editor.ITextModel, From 8531f4b3ca23b1bc877ddd3a4204832673851468 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 5 Jun 2025 11:43:18 -0400 Subject: [PATCH 343/487] [ty] Add infrastructure for AST garbage collection (#18445) ## Summary https://github.com/astral-sh/ty/issues/214 will require a couple invasive changes that I would like to get merged even before garbage collection is fully implemented (to avoid rebasing): - `ParsedModule` can no longer be dereferenced directly. Instead you need to load a `ParsedModuleRef` to access the AST, which requires a reference to the salsa database (as it may require re-parsing the AST if it was collected). - `AstNodeRef` can only be dereferenced with the `node` method, which takes a reference to the `ParsedModuleRef`. This allows us to encode the fact that ASTs do not live as long as the database and may be collected as soon a given instance of a `ParsedModuleRef` is dropped. There are a number of places where we currently merge the `'db` and `'ast` lifetimes, so this requires giving some types/functions two separate lifetime parameters. --- crates/ruff_db/src/parsed.rs | 58 ++- crates/ruff_python_formatter/src/lib.rs | 4 +- crates/ty/src/lib.rs | 3 +- crates/ty_ide/src/completion.rs | 10 +- crates/ty_ide/src/goto.rs | 15 +- crates/ty_ide/src/hover.rs | 6 +- crates/ty_ide/src/inlay_hints.rs | 2 +- crates/ty_project/src/lib.rs | 6 +- crates/ty_project/tests/check.rs | 2 +- crates/ty_python_semantic/src/ast_node_ref.rs | 143 +------ crates/ty_python_semantic/src/dunder_all.rs | 2 +- .../ty_python_semantic/src/semantic_index.rs | 100 +++-- .../src/semantic_index/builder.rs | 125 +++--- .../src/semantic_index/definition.rs | 401 ++++++++++-------- .../src/semantic_index/expression.rs | 13 +- .../src/semantic_index/place.rs | 45 +- .../src/semantic_index/re_exports.rs | 2 +- .../ty_python_semantic/src/semantic_model.rs | 6 +- crates/ty_python_semantic/src/suppression.rs | 2 +- crates/ty_python_semantic/src/types.rs | 22 +- .../ty_python_semantic/src/types/call/bind.rs | 18 +- crates/ty_python_semantic/src/types/class.rs | 98 +++-- .../ty_python_semantic/src/types/context.rs | 30 +- .../src/types/definition.rs | 11 +- .../src/types/diagnostic.rs | 14 +- .../ty_python_semantic/src/types/function.rs | 42 +- crates/ty_python_semantic/src/types/infer.rs | 283 +++++++----- crates/ty_python_semantic/src/types/narrow.rs | 43 +- .../ty_python_semantic/src/types/unpacker.rs | 41 +- crates/ty_python_semantic/src/unpack.rs | 28 +- crates/ty_test/src/assertion.rs | 2 +- crates/ty_test/src/lib.rs | 2 +- crates/ty_wasm/src/lib.rs | 4 +- 33 files changed, 890 insertions(+), 693 deletions(-) diff --git a/crates/ruff_db/src/parsed.rs b/crates/ruff_db/src/parsed.rs index d1f6f6a125f79e..a47971392411e7 100644 --- a/crates/ruff_db/src/parsed.rs +++ b/crates/ruff_db/src/parsed.rs @@ -1,5 +1,4 @@ use std::fmt::Formatter; -use std::ops::Deref; use std::sync::Arc; use ruff_python_ast::ModModule; @@ -18,7 +17,7 @@ use crate::source::source_text; /// The query is only cached when the [`source_text()`] hasn't changed. This is because /// comparing two ASTs is a non-trivial operation and every offset change is directly /// reflected in the changed AST offsets. -/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires +/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires /// for determining if a query result is unchanged. #[salsa::tracked(returns(ref), no_eq)] pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { @@ -36,7 +35,10 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { ParsedModule::new(parsed) } -/// Cheap cloneable wrapper around the parsed module. +/// A wrapper around a parsed module. +/// +/// This type manages instances of the module AST. A particular instance of the AST +/// is represented with the [`ParsedModuleRef`] type. #[derive(Clone)] pub struct ParsedModule { inner: Arc>, @@ -49,17 +51,11 @@ impl ParsedModule { } } - /// Consumes `self` and returns the Arc storing the parsed module. - pub fn into_arc(self) -> Arc> { - self.inner - } -} - -impl Deref for ParsedModule { - type Target = Parsed; - - fn deref(&self) -> &Self::Target { - &self.inner + /// Loads a reference to the parsed module. + pub fn load(&self, _db: &dyn Db) -> ParsedModuleRef { + ParsedModuleRef { + module_ref: self.inner.clone(), + } } } @@ -77,6 +73,30 @@ impl PartialEq for ParsedModule { impl Eq for ParsedModule {} +/// Cheap cloneable wrapper around an instance of a module AST. +#[derive(Clone)] +pub struct ParsedModuleRef { + module_ref: Arc>, +} + +impl ParsedModuleRef { + pub fn as_arc(&self) -> &Arc> { + &self.module_ref + } + + pub fn into_arc(self) -> Arc> { + self.module_ref + } +} + +impl std::ops::Deref for ParsedModuleRef { + type Target = Parsed; + + fn deref(&self) -> &Self::Target { + &self.module_ref + } +} + #[cfg(test)] mod tests { use crate::Db; @@ -98,7 +118,7 @@ mod tests { let file = system_path_to_file(&db, path).unwrap(); - let parsed = parsed_module(&db, file); + let parsed = parsed_module(&db, file).load(&db); assert!(parsed.has_valid_syntax()); @@ -114,7 +134,7 @@ mod tests { let file = system_path_to_file(&db, path).unwrap(); - let parsed = parsed_module(&db, file); + let parsed = parsed_module(&db, file).load(&db); assert!(parsed.has_valid_syntax()); @@ -130,7 +150,7 @@ mod tests { let virtual_file = db.files().virtual_file(&db, path); - let parsed = parsed_module(&db, virtual_file.file()); + let parsed = parsed_module(&db, virtual_file.file()).load(&db); assert!(parsed.has_valid_syntax()); @@ -146,7 +166,7 @@ mod tests { let virtual_file = db.files().virtual_file(&db, path); - let parsed = parsed_module(&db, virtual_file.file()); + let parsed = parsed_module(&db, virtual_file.file()).load(&db); assert!(parsed.has_valid_syntax()); @@ -177,7 +197,7 @@ else: let file = vendored_path_to_file(&db, VendoredPath::new("path.pyi")).unwrap(); - let parsed = parsed_module(&db, file); + let parsed = parsed_module(&db, file).load(&db); assert!(parsed.has_valid_syntax()); } diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index c9c1c35bf8d296..18e1ef4c62d3b2 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -165,7 +165,7 @@ where pub fn formatted_file(db: &dyn Db, file: File) -> Result, FormatModuleError> { let options = db.format_options(file); - let parsed = parsed_module(db.upcast(), file); + let parsed = parsed_module(db.upcast(), file).load(db.upcast()); if let Some(first) = parsed.errors().first() { return Err(FormatModuleError::ParseError(first.clone())); @@ -174,7 +174,7 @@ pub fn formatted_file(db: &dyn Db, file: File) -> Result, FormatM let comment_ranges = CommentRanges::from(parsed.tokens()); let source = source_text(db.upcast(), file); - let formatted = format_node(parsed, &comment_ranges, &source, options)?; + let formatted = format_node(&parsed, &comment_ranges, &source, options)?; let printed = formatted.print()?; if printed.as_code() == &*source { diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 91efddde605f7a..aa00d2e3741374 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -18,10 +18,9 @@ use clap::{CommandFactory, Parser}; use colored::Colorize; use crossbeam::channel as crossbeam_channel; use rayon::ThreadPoolBuilder; -use ruff_db::Upcast; use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity}; -use ruff_db::max_parallelism; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; +use ruff_db::{Upcast, max_parallelism}; use salsa::plumbing::ZalsaDatabase; use ty_project::metadata::options::ProjectOptionsOverrides; use ty_project::watch::ProjectWatcher; diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 6584b7339b7fcc..10bc2971d1307b 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use ruff_db::files::File; -use ruff_db::parsed::{ParsedModule, parsed_module}; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast as ast; use ruff_python_parser::{Token, TokenAt, TokenKind}; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -15,9 +15,9 @@ pub struct Completion { } pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec { - let parsed = parsed_module(db.upcast(), file); + let parsed = parsed_module(db.upcast(), file).load(db.upcast()); - let Some(target) = CompletionTargetTokens::find(parsed, offset).ast(parsed) else { + let Some(target) = CompletionTargetTokens::find(&parsed, offset).ast(&parsed) else { return vec![]; }; @@ -63,7 +63,7 @@ enum CompletionTargetTokens<'t> { impl<'t> CompletionTargetTokens<'t> { /// Look for the best matching token pattern at the given offset. - fn find(parsed: &ParsedModule, offset: TextSize) -> CompletionTargetTokens<'_> { + fn find(parsed: &ParsedModuleRef, offset: TextSize) -> CompletionTargetTokens<'_> { static OBJECT_DOT_EMPTY: [TokenKind; 2] = [TokenKind::Name, TokenKind::Dot]; static OBJECT_DOT_NON_EMPTY: [TokenKind; 3] = [TokenKind::Name, TokenKind::Dot, TokenKind::Name]; @@ -97,7 +97,7 @@ impl<'t> CompletionTargetTokens<'t> { /// Returns a corresponding AST node for these tokens. /// /// If no plausible AST node could be found, then `None` is returned. - fn ast(&self, parsed: &'t ParsedModule) -> Option> { + fn ast(&self, parsed: &'t ParsedModuleRef) -> Option> { match *self { CompletionTargetTokens::ObjectDot { object, .. } => { let covering_node = covering_node(parsed.syntax().into(), object.range()) diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index b7fb38c314a7e7..89861b73ff976b 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -1,7 +1,7 @@ use crate::find_node::covering_node; use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue}; use ruff_db::files::{File, FileRange}; -use ruff_db::parsed::{ParsedModule, parsed_module}; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_python_parser::TokenKind; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -13,8 +13,8 @@ pub fn goto_type_definition( file: File, offset: TextSize, ) -> Option> { - let parsed = parsed_module(db.upcast(), file); - let goto_target = find_goto_target(parsed, offset)?; + let module = parsed_module(db.upcast(), file).load(db.upcast()); + let goto_target = find_goto_target(&module, offset)?; let model = SemanticModel::new(db.upcast(), file); let ty = goto_target.inferred_type(&model)?; @@ -128,8 +128,8 @@ pub(crate) enum GotoTarget<'a> { }, } -impl<'db> GotoTarget<'db> { - pub(crate) fn inferred_type(self, model: &SemanticModel<'db>) -> Option> { +impl GotoTarget<'_> { + pub(crate) fn inferred_type<'db>(self, model: &SemanticModel<'db>) -> Option> { let ty = match self { GotoTarget::Expression(expression) => expression.inferred_type(model), GotoTarget::FunctionDef(function) => function.inferred_type(model), @@ -183,7 +183,10 @@ impl Ranged for GotoTarget<'_> { } } -pub(crate) fn find_goto_target(parsed: &ParsedModule, offset: TextSize) -> Option { +pub(crate) fn find_goto_target( + parsed: &ParsedModuleRef, + offset: TextSize, +) -> Option> { let token = parsed .tokens() .at_offset(offset) diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 6509b4bf76a234..34ce394b9f07d5 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -8,9 +8,9 @@ use std::fmt::Formatter; use ty_python_semantic::SemanticModel; use ty_python_semantic::types::Type; -pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option> { - let parsed = parsed_module(db.upcast(), file); - let goto_target = find_goto_target(parsed, offset)?; +pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option>> { + let parsed = parsed_module(db.upcast(), file).load(db.upcast()); + let goto_target = find_goto_target(&parsed, offset)?; if let GotoTarget::Expression(expr) = goto_target { if expr.is_literal_expr() { diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 0ef71ddc05eff1..9c1c8350249a62 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -54,7 +54,7 @@ impl fmt::Display for DisplayInlayHint<'_, '_> { pub fn inlay_hints(db: &dyn Db, file: File, range: TextRange) -> Vec> { let mut visitor = InlayHintVisitor::new(db, file, range); - let ast = parsed_module(db.upcast(), file); + let ast = parsed_module(db.upcast(), file).load(db.upcast()); visitor.visit_body(ast.suite()); diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index ecd9501a47c5d6..b57608d5f5f003 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -453,14 +453,16 @@ fn check_file_impl(db: &dyn Db, file: File) -> Vec { } let parsed = parsed_module(db.upcast(), file); + + let parsed_ref = parsed.load(db.upcast()); diagnostics.extend( - parsed + parsed_ref .errors() .iter() .map(|error| create_parse_diagnostic(file, error)), ); - diagnostics.extend(parsed.unsupported_syntax_errors().iter().map(|error| { + diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| { let mut error = create_unsupported_syntax_diagnostic(file, error); add_inferred_python_version_hint_to_diagnostic(db.upcast(), &mut error, "parsing syntax"); error diff --git a/crates/ty_project/tests/check.rs b/crates/ty_project/tests/check.rs index 36f8b16f1be387..9d4d461ff531b1 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_project/tests/check.rs @@ -172,7 +172,7 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { fn pull_types(db: &ProjectDatabase, file: File) { let mut visitor = PullTypesVisitor::new(db, file); - let ast = parsed_module(db, file); + let ast = parsed_module(db, file).load(db); visitor.visit_body(ast.suite()); } diff --git a/crates/ty_python_semantic/src/ast_node_ref.rs b/crates/ty_python_semantic/src/ast_node_ref.rs index 7d460fed1989c5..fd3e1726dca151 100644 --- a/crates/ty_python_semantic/src/ast_node_ref.rs +++ b/crates/ty_python_semantic/src/ast_node_ref.rs @@ -1,15 +1,14 @@ -use std::hash::Hash; -use std::ops::Deref; +use std::sync::Arc; -use ruff_db::parsed::ParsedModule; +use ruff_db::parsed::ParsedModuleRef; /// Ref-counted owned reference to an AST node. /// -/// The type holds an owned reference to the node's ref-counted [`ParsedModule`]. -/// Holding on to the node's [`ParsedModule`] guarantees that the reference to the +/// The type holds an owned reference to the node's ref-counted [`ParsedModuleRef`]. +/// Holding on to the node's [`ParsedModuleRef`] guarantees that the reference to the /// node must still be valid. /// -/// Holding on to any [`AstNodeRef`] prevents the [`ParsedModule`] from being released. +/// Holding on to any [`AstNodeRef`] prevents the [`ParsedModuleRef`] from being released. /// /// ## Equality /// Two `AstNodeRef` are considered equal if their pointer addresses are equal. @@ -33,11 +32,11 @@ use ruff_db::parsed::ParsedModule; /// run on every AST change. All other queries only run when the expression's identity changes. #[derive(Clone)] pub struct AstNodeRef { - /// Owned reference to the node's [`ParsedModule`]. + /// Owned reference to the node's [`ParsedModuleRef`]. /// /// The node's reference is guaranteed to remain valid as long as it's enclosing - /// [`ParsedModule`] is alive. - parsed: ParsedModule, + /// [`ParsedModuleRef`] is alive. + parsed: ParsedModuleRef, /// Pointer to the referenced node. node: std::ptr::NonNull, @@ -45,15 +44,15 @@ pub struct AstNodeRef { #[expect(unsafe_code)] impl AstNodeRef { - /// Creates a new `AstNodeRef` that references `node`. The `parsed` is the [`ParsedModule`] to + /// Creates a new `AstNodeRef` that references `node`. The `parsed` is the [`ParsedModuleRef`] to /// which the `AstNodeRef` belongs. /// /// ## Safety /// /// Dereferencing the `node` can result in undefined behavior if `parsed` isn't the - /// [`ParsedModule`] to which `node` belongs. It's the caller's responsibility to ensure that + /// [`ParsedModuleRef`] to which `node` belongs. It's the caller's responsibility to ensure that /// the invariant `node belongs to parsed` is upheld. - pub(super) unsafe fn new(parsed: ParsedModule, node: &T) -> Self { + pub(super) unsafe fn new(parsed: ParsedModuleRef, node: &T) -> Self { Self { parsed, node: std::ptr::NonNull::from(node), @@ -61,54 +60,26 @@ impl AstNodeRef { } /// Returns a reference to the wrapped node. - pub const fn node(&self) -> &T { + /// + /// Note that this method will panic if the provided module is from a different file or Salsa revision + /// than the module this node was created with. + pub fn node<'ast>(&self, parsed: &'ast ParsedModuleRef) -> &'ast T { + debug_assert!(Arc::ptr_eq(self.parsed.as_arc(), parsed.as_arc())); + // SAFETY: Holding on to `parsed` ensures that the AST to which `node` belongs is still // alive and not moved. unsafe { self.node.as_ref() } } } -impl Deref for AstNodeRef { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.node() - } -} - impl std::fmt::Debug for AstNodeRef where T: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("AstNodeRef").field(&self.node()).finish() - } -} - -impl PartialEq for AstNodeRef -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - if self.parsed == other.parsed { - // Comparing the pointer addresses is sufficient to determine equality - // if the parsed are the same. - self.node.eq(&other.node) - } else { - // Otherwise perform a deep comparison. - self.node().eq(other.node()) - } - } -} - -impl Eq for AstNodeRef where T: Eq {} - -impl Hash for AstNodeRef -where - T: Hash, -{ - fn hash(&self, state: &mut H) { - self.node().hash(state); + f.debug_tuple("AstNodeRef") + .field(self.node(&self.parsed)) + .finish() } } @@ -117,7 +88,9 @@ unsafe impl salsa::Update for AstNodeRef { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let old_ref = unsafe { &mut (*old_pointer) }; - if old_ref.parsed == new_value.parsed && old_ref.node.eq(&new_value.node) { + if Arc::ptr_eq(old_ref.parsed.as_arc(), new_value.parsed.as_arc()) + && old_ref.node.eq(&new_value.node) + { false } else { *old_ref = new_value; @@ -130,73 +103,3 @@ unsafe impl salsa::Update for AstNodeRef { unsafe impl Send for AstNodeRef where T: Send {} #[expect(unsafe_code)] unsafe impl Sync for AstNodeRef where T: Sync {} - -#[cfg(test)] -mod tests { - use crate::ast_node_ref::AstNodeRef; - use ruff_db::parsed::ParsedModule; - use ruff_python_ast::PySourceType; - use ruff_python_parser::parse_unchecked_source; - - #[test] - #[expect(unsafe_code)] - fn equality() { - let parsed_raw = parse_unchecked_source("1 + 2", PySourceType::Python); - let parsed = ParsedModule::new(parsed_raw.clone()); - - let stmt = &parsed.syntax().body[0]; - - let node1 = unsafe { AstNodeRef::new(parsed.clone(), stmt) }; - let node2 = unsafe { AstNodeRef::new(parsed.clone(), stmt) }; - - assert_eq!(node1, node2); - - // Compare from different trees - let cloned = ParsedModule::new(parsed_raw); - let stmt_cloned = &cloned.syntax().body[0]; - let cloned_node = unsafe { AstNodeRef::new(cloned.clone(), stmt_cloned) }; - - assert_eq!(node1, cloned_node); - - let other_raw = parse_unchecked_source("2 + 2", PySourceType::Python); - let other = ParsedModule::new(other_raw); - - let other_stmt = &other.syntax().body[0]; - let other_node = unsafe { AstNodeRef::new(other.clone(), other_stmt) }; - - assert_ne!(node1, other_node); - } - - #[expect(unsafe_code)] - #[test] - fn inequality() { - let parsed_raw = parse_unchecked_source("1 + 2", PySourceType::Python); - let parsed = ParsedModule::new(parsed_raw); - - let stmt = &parsed.syntax().body[0]; - let node = unsafe { AstNodeRef::new(parsed.clone(), stmt) }; - - let other_raw = parse_unchecked_source("2 + 2", PySourceType::Python); - let other = ParsedModule::new(other_raw); - - let other_stmt = &other.syntax().body[0]; - let other_node = unsafe { AstNodeRef::new(other.clone(), other_stmt) }; - - assert_ne!(node, other_node); - } - - #[test] - #[expect(unsafe_code)] - fn debug() { - let parsed_raw = parse_unchecked_source("1 + 2", PySourceType::Python); - let parsed = ParsedModule::new(parsed_raw); - - let stmt = &parsed.syntax().body[0]; - - let stmt_node = unsafe { AstNodeRef::new(parsed.clone(), stmt) }; - - let debug = format!("{stmt_node:?}"); - - assert_eq!(debug, format!("AstNodeRef({stmt:?})")); - } -} diff --git a/crates/ty_python_semantic/src/dunder_all.rs b/crates/ty_python_semantic/src/dunder_all.rs index 6976e35e22443d..78ceb1250d4161 100644 --- a/crates/ty_python_semantic/src/dunder_all.rs +++ b/crates/ty_python_semantic/src/dunder_all.rs @@ -32,7 +32,7 @@ fn dunder_all_names_cycle_initial(_db: &dyn Db, _file: File) -> Option Option> { let _span = tracing::trace_span!("dunder_all_names", file=?file.path(db)).entered(); - let module = parsed_module(db.upcast(), file); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let index = semantic_index(db, file); let mut collector = DunderAllNamesCollector::new(db, file, index); collector.visit_body(module.suite()); diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index c117b7f737435c..0b71130d1eb08c 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -50,9 +50,9 @@ type PlaceSet = hashbrown::HashMap; pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> { let _span = tracing::trace_span!("semantic_index", ?file).entered(); - let parsed = parsed_module(db.upcast(), file); + let module = parsed_module(db.upcast(), file).load(db.upcast()); - SemanticIndexBuilder::new(db, file, parsed).build() + SemanticIndexBuilder::new(db, file, &module).build() } /// Returns the place table for a specific `scope`. @@ -129,10 +129,11 @@ pub(crate) fn attribute_scopes<'db, 's>( class_body_scope: ScopeId<'db>, ) -> impl Iterator + use<'s, 'db> { let file = class_body_scope.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let index = semantic_index(db, file); let class_scope_id = class_body_scope.file_scope_id(db); - ChildrenIter::new(index, class_scope_id).filter_map(|(child_scope_id, scope)| { + ChildrenIter::new(index, class_scope_id).filter_map(move |(child_scope_id, scope)| { let (function_scope_id, function_scope) = if scope.node().scope_kind() == ScopeKind::Annotation { // This could be a generic method with a type-params scope. @@ -144,7 +145,7 @@ pub(crate) fn attribute_scopes<'db, 's>( (child_scope_id, scope) }; - function_scope.node().as_function()?; + function_scope.node().as_function(&module)?; Some(function_scope_id) }) } @@ -559,7 +560,7 @@ impl FusedIterator for ChildrenIter<'_> {} #[cfg(test)] mod tests { use ruff_db::files::{File, system_path_to_file}; - use ruff_db::parsed::parsed_module; + use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast::{self as ast}; use ruff_text_size::{Ranged, TextRange}; @@ -742,6 +743,7 @@ y = 2 assert_eq!(names(global_table), vec!["C", "y"]); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let [(class_scope_id, class_scope)] = index @@ -751,7 +753,10 @@ y = 2 panic!("expected one child scope") }; assert_eq!(class_scope.kind(), ScopeKind::Class); - assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C"); + assert_eq!( + class_scope_id.to_scope_id(&db, file).name(&db, &module), + "C" + ); let class_table = index.place_table(class_scope_id); assert_eq!(names(&class_table), vec!["x"]); @@ -772,6 +777,7 @@ def func(): y = 2 ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -784,7 +790,10 @@ y = 2 panic!("expected one child scope") }; assert_eq!(function_scope.kind(), ScopeKind::Function); - assert_eq!(function_scope_id.to_scope_id(&db, file).name(&db), "func"); + assert_eq!( + function_scope_id.to_scope_id(&db, file).name(&db, &module), + "func" + ); let function_table = index.place_table(function_scope_id); assert_eq!(names(&function_table), vec!["x"]); @@ -921,6 +930,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -935,7 +945,9 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension); assert_eq!( - comprehension_scope_id.to_scope_id(&db, file).name(&db), + comprehension_scope_id + .to_scope_id(&db, file) + .name(&db, &module), "" ); @@ -979,8 +991,9 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let use_def = index.use_def_map(comprehension_scope_id); - let module = parsed_module(&db, file).syntax(); - let element = module.body[0] + let module = parsed_module(&db, file).load(&db); + let syntax = module.syntax(); + let element = syntax.body[0] .as_expr_stmt() .unwrap() .value @@ -996,7 +1009,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let DefinitionKind::Comprehension(comprehension) = binding.kind(&db) else { panic!("expected generator definition") }; - let target = comprehension.target(); + let target = comprehension.target(&module); let name = target.as_name_expr().unwrap().id().as_str(); assert_eq!(name, "x"); @@ -1014,6 +1027,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -1028,7 +1042,9 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension); assert_eq!( - comprehension_scope_id.to_scope_id(&db, file).name(&db), + comprehension_scope_id + .to_scope_id(&db, file) + .name(&db, &module), "" ); @@ -1047,7 +1063,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): assert_eq!( inner_comprehension_scope_id .to_scope_id(&db, file) - .name(&db), + .name(&db, &module), "" ); @@ -1112,6 +1128,7 @@ def func(): y = 2 ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -1128,9 +1145,15 @@ def func(): assert_eq!(func_scope_1.kind(), ScopeKind::Function); - assert_eq!(func_scope1_id.to_scope_id(&db, file).name(&db), "func"); + assert_eq!( + func_scope1_id.to_scope_id(&db, file).name(&db, &module), + "func" + ); assert_eq!(func_scope_2.kind(), ScopeKind::Function); - assert_eq!(func_scope2_id.to_scope_id(&db, file).name(&db), "func"); + assert_eq!( + func_scope2_id.to_scope_id(&db, file).name(&db, &module), + "func" + ); let func1_table = index.place_table(func_scope1_id); let func2_table = index.place_table(func_scope2_id); @@ -1157,6 +1180,7 @@ def func[T](): ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -1170,7 +1194,10 @@ def func[T](): }; assert_eq!(ann_scope.kind(), ScopeKind::Annotation); - assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "func"); + assert_eq!( + ann_scope_id.to_scope_id(&db, file).name(&db, &module), + "func" + ); let ann_table = index.place_table(ann_scope_id); assert_eq!(names(&ann_table), vec!["T"]); @@ -1180,7 +1207,10 @@ def func[T](): panic!("expected one child scope"); }; assert_eq!(func_scope.kind(), ScopeKind::Function); - assert_eq!(func_scope_id.to_scope_id(&db, file).name(&db), "func"); + assert_eq!( + func_scope_id.to_scope_id(&db, file).name(&db, &module), + "func" + ); let func_table = index.place_table(func_scope_id); assert_eq!(names(&func_table), vec!["x"]); } @@ -1194,6 +1224,7 @@ class C[T]: ", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let global_table = index.place_table(FileScopeId::global()); @@ -1207,7 +1238,7 @@ class C[T]: }; assert_eq!(ann_scope.kind(), ScopeKind::Annotation); - assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "C"); + assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db, &module), "C"); let ann_table = index.place_table(ann_scope_id); assert_eq!(names(&ann_table), vec!["T"]); assert!( @@ -1224,16 +1255,19 @@ class C[T]: }; assert_eq!(class_scope.kind(), ScopeKind::Class); - assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C"); + assert_eq!( + class_scope_id.to_scope_id(&db, file).name(&db, &module), + "C" + ); assert_eq!(names(&index.place_table(class_scope_id)), vec!["x"]); } #[test] fn reachability_trivial() { let TestCase { db, file } = test_case("x = 1; x"); - let parsed = parsed_module(&db, file); + let module = parsed_module(&db, file).load(&db); let scope = global_scope(&db, file); - let ast = parsed.syntax(); + let ast = module.syntax(); let ast::Stmt::Expr(ast::StmtExpr { value: x_use_expr, .. }) = &ast.body[1] @@ -1252,7 +1286,7 @@ class C[T]: let ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(num), .. - }) = assignment.value() + }) = assignment.value(&module) else { panic!("should be a number literal") }; @@ -1264,8 +1298,8 @@ class C[T]: let TestCase { db, file } = test_case("x = 1;\ndef test():\n y = 4"); let index = semantic_index(&db, file); - let parsed = parsed_module(&db, file); - let ast = parsed.syntax(); + let module = parsed_module(&db, file).load(&db); + let ast = module.syntax(); let x_stmt = ast.body[0].as_assign_stmt().unwrap(); let x = &x_stmt.targets[0]; @@ -1282,14 +1316,15 @@ class C[T]: #[test] fn scope_iterators() { - fn scope_names<'a>( - scopes: impl Iterator, - db: &'a dyn Db, + fn scope_names<'a, 'db>( + scopes: impl Iterator, + db: &'db dyn Db, file: File, + module: &'a ParsedModuleRef, ) -> Vec<&'a str> { scopes .into_iter() - .map(|(scope_id, _)| scope_id.to_scope_id(db, file).name(db)) + .map(|(scope_id, _)| scope_id.to_scope_id(db, file).name(db, module)) .collect() } @@ -1306,21 +1341,22 @@ def x(): pass", ); + let module = parsed_module(&db, file).load(&db); let index = semantic_index(&db, file); let descendants = index.descendent_scopes(FileScopeId::global()); assert_eq!( - scope_names(descendants, &db, file), + scope_names(descendants, &db, file, &module), vec!["Test", "foo", "bar", "baz", "x"] ); let children = index.child_scopes(FileScopeId::global()); - assert_eq!(scope_names(children, &db, file), vec!["Test", "x"]); + assert_eq!(scope_names(children, &db, file, &module), vec!["Test", "x"]); let test_class = index.child_scopes(FileScopeId::global()).next().unwrap().0; let test_child_scopes = index.child_scopes(test_class); assert_eq!( - scope_names(test_child_scopes, &db, file), + scope_names(test_child_scopes, &db, file, &module), vec!["foo", "baz"] ); @@ -1332,7 +1368,7 @@ def x(): let ancestors = index.ancestor_scopes(bar_scope); assert_eq!( - scope_names(ancestors, &db, file), + scope_names(ancestors, &db, file, &module), vec!["bar", "foo", "Test", ""] ); } diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index db4cf3da50f08f..bb5f40d3be6dd2 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -5,7 +5,7 @@ use except_handlers::TryNodeContextStackManager; use rustc_hash::{FxHashMap, FxHashSet}; use ruff_db::files::File; -use ruff_db::parsed::ParsedModule; +use ruff_db::parsed::ParsedModuleRef; use ruff_db::source::{SourceText, source_text}; use ruff_index::IndexVec; use ruff_python_ast::name::Name; @@ -69,20 +69,20 @@ struct ScopeInfo { current_loop: Option, } -pub(super) struct SemanticIndexBuilder<'db> { +pub(super) struct SemanticIndexBuilder<'db, 'ast> { // Builder state db: &'db dyn Db, file: File, source_type: PySourceType, - module: &'db ParsedModule, + module: &'ast ParsedModuleRef, scope_stack: Vec, /// The assignments we're currently visiting, with /// the most recent visit at the end of the Vec - current_assignments: Vec>, + current_assignments: Vec>, /// The match case we're currently visiting. - current_match_case: Option>, + current_match_case: Option>, /// The name of the first function parameter of the innermost function that we're currently visiting. - current_first_parameter_name: Option<&'db str>, + current_first_parameter_name: Option<&'ast str>, /// Per-scope contexts regarding nested `try`/`except` statements try_node_context_stack_manager: TryNodeContextStackManager, @@ -116,13 +116,13 @@ pub(super) struct SemanticIndexBuilder<'db> { semantic_syntax_errors: RefCell>, } -impl<'db> SemanticIndexBuilder<'db> { - pub(super) fn new(db: &'db dyn Db, file: File, parsed: &'db ParsedModule) -> Self { +impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { + pub(super) fn new(db: &'db dyn Db, file: File, module_ref: &'ast ParsedModuleRef) -> Self { let mut builder = Self { db, file, source_type: file.source_type(db.upcast()), - module: parsed, + module: module_ref, scope_stack: Vec::new(), current_assignments: vec![], current_match_case: None, @@ -423,7 +423,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn add_definition( &mut self, place: ScopedPlaceId, - definition_node: impl Into> + std::fmt::Debug + Copy, + definition_node: impl Into> + std::fmt::Debug + Copy, ) -> Definition<'db> { let (definition, num_definitions) = self.push_additional_definition(place, definition_node); debug_assert_eq!( @@ -463,16 +463,18 @@ impl<'db> SemanticIndexBuilder<'db> { fn push_additional_definition( &mut self, place: ScopedPlaceId, - definition_node: impl Into>, + definition_node: impl Into>, ) -> (Definition<'db>, usize) { - let definition_node: DefinitionNodeRef<'_> = definition_node.into(); + let definition_node: DefinitionNodeRef<'ast, 'db> = definition_node.into(); + #[expect(unsafe_code)] // SAFETY: `definition_node` is guaranteed to be a child of `self.module` let kind = unsafe { definition_node.into_owned(self.module.clone()) }; - let category = kind.category(self.source_type.is_stub()); + + let category = kind.category(self.source_type.is_stub(), self.module); let is_reexported = kind.is_reexported(); - let definition = Definition::new( + let definition: Definition<'db> = Definition::new( self.db, self.file, self.current_scope(), @@ -658,7 +660,7 @@ impl<'db> SemanticIndexBuilder<'db> { .record_reachability_constraint(negated_constraint); } - fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { + fn push_assignment(&mut self, assignment: CurrentAssignment<'ast, 'db>) { self.current_assignments.push(assignment); } @@ -667,11 +669,11 @@ impl<'db> SemanticIndexBuilder<'db> { debug_assert!(popped_assignment.is_some()); } - fn current_assignment(&self) -> Option> { + fn current_assignment(&self) -> Option> { self.current_assignments.last().copied() } - fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'db>> { + fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'ast, 'db>> { self.current_assignments.last_mut() } @@ -792,7 +794,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn with_type_params( &mut self, with_scope: NodeWithScopeRef, - type_params: Option<&'db ast::TypeParams>, + type_params: Option<&'ast ast::TypeParams>, nested: impl FnOnce(&mut Self) -> FileScopeId, ) -> FileScopeId { if let Some(type_params) = type_params { @@ -858,7 +860,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn with_generators_scope( &mut self, scope: NodeWithScopeRef, - generators: &'db [ast::Comprehension], + generators: &'ast [ast::Comprehension], visit_outer_elt: impl FnOnce(&mut Self), ) { let mut generators_iter = generators.iter(); @@ -908,7 +910,7 @@ impl<'db> SemanticIndexBuilder<'db> { self.pop_scope(); } - fn declare_parameters(&mut self, parameters: &'db ast::Parameters) { + fn declare_parameters(&mut self, parameters: &'ast ast::Parameters) { for parameter in parameters.iter_non_variadic_params() { self.declare_parameter(parameter); } @@ -925,7 +927,7 @@ impl<'db> SemanticIndexBuilder<'db> { } } - fn declare_parameter(&mut self, parameter: &'db ast::ParameterWithDefault) { + fn declare_parameter(&mut self, parameter: &'ast ast::ParameterWithDefault) { let symbol = self.add_symbol(parameter.name().id().clone()); let definition = self.add_definition(symbol, parameter); @@ -946,8 +948,8 @@ impl<'db> SemanticIndexBuilder<'db> { /// for statements, etc. fn add_unpackable_assignment( &mut self, - unpackable: &Unpackable<'db>, - target: &'db ast::Expr, + unpackable: &Unpackable<'ast>, + target: &'ast ast::Expr, value: Expression<'db>, ) { // We only handle assignments to names and unpackings here, other targets like @@ -1010,8 +1012,7 @@ impl<'db> SemanticIndexBuilder<'db> { } pub(super) fn build(mut self) -> SemanticIndex<'db> { - let module = self.module; - self.visit_body(module.suite()); + self.visit_body(self.module.suite()); // Pop the root scope self.pop_scope(); @@ -1081,10 +1082,7 @@ impl<'db> SemanticIndexBuilder<'db> { } } -impl<'db, 'ast> Visitor<'ast> for SemanticIndexBuilder<'db> -where - 'ast: 'db, -{ +impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { fn visit_stmt(&mut self, stmt: &'ast ast::Stmt) { self.with_semantic_checker(|semantic, context| semantic.visit_stmt(stmt, context)); @@ -2299,7 +2297,7 @@ where } } -impl SemanticSyntaxContext for SemanticIndexBuilder<'_> { +impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> { fn future_annotations_or_stub(&self) -> bool { self.has_future_annotations } @@ -2324,7 +2322,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_> { match scope.kind() { ScopeKind::Class | ScopeKind::Lambda => return false, ScopeKind::Function => { - return scope.node().expect_function().is_async; + return scope.node().expect_function(self.module).is_async; } ScopeKind::Comprehension | ScopeKind::Module @@ -2366,9 +2364,9 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_> { for scope_info in self.scope_stack.iter().rev() { let scope = &self.scopes[scope_info.file_scope_id]; let generators = match scope.node() { - NodeWithScopeKind::ListComprehension(node) => &node.generators, - NodeWithScopeKind::SetComprehension(node) => &node.generators, - NodeWithScopeKind::DictComprehension(node) => &node.generators, + NodeWithScopeKind::ListComprehension(node) => &node.node(self.module).generators, + NodeWithScopeKind::SetComprehension(node) => &node.node(self.module).generators, + NodeWithScopeKind::DictComprehension(node) => &node.node(self.module).generators, _ => continue, }; if generators @@ -2409,31 +2407,31 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_> { } #[derive(Copy, Clone, Debug, PartialEq)] -enum CurrentAssignment<'a> { +enum CurrentAssignment<'ast, 'db> { Assign { - node: &'a ast::StmtAssign, - unpack: Option<(UnpackPosition, Unpack<'a>)>, + node: &'ast ast::StmtAssign, + unpack: Option<(UnpackPosition, Unpack<'db>)>, }, - AnnAssign(&'a ast::StmtAnnAssign), - AugAssign(&'a ast::StmtAugAssign), + AnnAssign(&'ast ast::StmtAnnAssign), + AugAssign(&'ast ast::StmtAugAssign), For { - node: &'a ast::StmtFor, - unpack: Option<(UnpackPosition, Unpack<'a>)>, + node: &'ast ast::StmtFor, + unpack: Option<(UnpackPosition, Unpack<'db>)>, }, - Named(&'a ast::ExprNamed), + Named(&'ast ast::ExprNamed), Comprehension { - node: &'a ast::Comprehension, + node: &'ast ast::Comprehension, first: bool, - unpack: Option<(UnpackPosition, Unpack<'a>)>, + unpack: Option<(UnpackPosition, Unpack<'db>)>, }, WithItem { - item: &'a ast::WithItem, + item: &'ast ast::WithItem, is_async: bool, - unpack: Option<(UnpackPosition, Unpack<'a>)>, + unpack: Option<(UnpackPosition, Unpack<'db>)>, }, } -impl CurrentAssignment<'_> { +impl CurrentAssignment<'_, '_> { fn unpack_position_mut(&mut self) -> Option<&mut UnpackPosition> { match self { Self::Assign { unpack, .. } @@ -2445,28 +2443,28 @@ impl CurrentAssignment<'_> { } } -impl<'a> From<&'a ast::StmtAnnAssign> for CurrentAssignment<'a> { - fn from(value: &'a ast::StmtAnnAssign) -> Self { +impl<'ast> From<&'ast ast::StmtAnnAssign> for CurrentAssignment<'ast, '_> { + fn from(value: &'ast ast::StmtAnnAssign) -> Self { Self::AnnAssign(value) } } -impl<'a> From<&'a ast::StmtAugAssign> for CurrentAssignment<'a> { - fn from(value: &'a ast::StmtAugAssign) -> Self { +impl<'ast> From<&'ast ast::StmtAugAssign> for CurrentAssignment<'ast, '_> { + fn from(value: &'ast ast::StmtAugAssign) -> Self { Self::AugAssign(value) } } -impl<'a> From<&'a ast::ExprNamed> for CurrentAssignment<'a> { - fn from(value: &'a ast::ExprNamed) -> Self { +impl<'ast> From<&'ast ast::ExprNamed> for CurrentAssignment<'ast, '_> { + fn from(value: &'ast ast::ExprNamed) -> Self { Self::Named(value) } } #[derive(Debug, PartialEq)] -struct CurrentMatchCase<'a> { +struct CurrentMatchCase<'ast> { /// The pattern that's part of the current match case. - pattern: &'a ast::Pattern, + pattern: &'ast ast::Pattern, /// The index of the sub-pattern that's being currently visited within the pattern. /// @@ -2488,20 +2486,20 @@ impl<'a> CurrentMatchCase<'a> { } } -enum Unpackable<'a> { - Assign(&'a ast::StmtAssign), - For(&'a ast::StmtFor), +enum Unpackable<'ast> { + Assign(&'ast ast::StmtAssign), + For(&'ast ast::StmtFor), WithItem { - item: &'a ast::WithItem, + item: &'ast ast::WithItem, is_async: bool, }, Comprehension { first: bool, - node: &'a ast::Comprehension, + node: &'ast ast::Comprehension, }, } -impl<'a> Unpackable<'a> { +impl<'ast> Unpackable<'ast> { const fn kind(&self) -> UnpackKind { match self { Unpackable::Assign(_) => UnpackKind::Assign, @@ -2510,7 +2508,10 @@ impl<'a> Unpackable<'a> { } } - fn as_current_assignment(&self, unpack: Option>) -> CurrentAssignment<'a> { + fn as_current_assignment<'db>( + &self, + unpack: Option>, + ) -> CurrentAssignment<'ast, 'db> { let unpack = unpack.map(|unpack| (UnpackPosition::First, unpack)); match self { Unpackable::Assign(stmt) => CurrentAssignment::Assign { node: stmt, unpack }, diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 3adbeb68c63059..391bbf87eb5f92 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use ruff_db::files::{File, FileRange}; -use ruff_db::parsed::ParsedModule; +use ruff_db::parsed::ParsedModuleRef; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextRange}; @@ -49,12 +49,12 @@ impl<'db> Definition<'db> { self.file_scope(db).to_scope_id(db, self.file(db)) } - pub fn full_range(self, db: &'db dyn Db) -> FileRange { - FileRange::new(self.file(db), self.kind(db).full_range()) + pub fn full_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> FileRange { + FileRange::new(self.file(db), self.kind(db).full_range(module)) } - pub fn focus_range(self, db: &'db dyn Db) -> FileRange { - FileRange::new(self.file(db), self.kind(db).target_range()) + pub fn focus_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> FileRange { + FileRange::new(self.file(db), self.kind(db).target_range(module)) } } @@ -123,218 +123,218 @@ impl<'db> DefinitionState<'db> { } #[derive(Copy, Clone, Debug)] -pub(crate) enum DefinitionNodeRef<'a> { - Import(ImportDefinitionNodeRef<'a>), - ImportFrom(ImportFromDefinitionNodeRef<'a>), - ImportStar(StarImportDefinitionNodeRef<'a>), - For(ForStmtDefinitionNodeRef<'a>), - Function(&'a ast::StmtFunctionDef), - Class(&'a ast::StmtClassDef), - TypeAlias(&'a ast::StmtTypeAlias), - NamedExpression(&'a ast::ExprNamed), - Assignment(AssignmentDefinitionNodeRef<'a>), - AnnotatedAssignment(AnnotatedAssignmentDefinitionNodeRef<'a>), - AugmentedAssignment(&'a ast::StmtAugAssign), - Comprehension(ComprehensionDefinitionNodeRef<'a>), - VariadicPositionalParameter(&'a ast::Parameter), - VariadicKeywordParameter(&'a ast::Parameter), - Parameter(&'a ast::ParameterWithDefault), - WithItem(WithItemDefinitionNodeRef<'a>), - MatchPattern(MatchPatternDefinitionNodeRef<'a>), - ExceptHandler(ExceptHandlerDefinitionNodeRef<'a>), - TypeVar(&'a ast::TypeParamTypeVar), - ParamSpec(&'a ast::TypeParamParamSpec), - TypeVarTuple(&'a ast::TypeParamTypeVarTuple), -} - -impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::StmtFunctionDef) -> Self { +pub(crate) enum DefinitionNodeRef<'ast, 'db> { + Import(ImportDefinitionNodeRef<'ast>), + ImportFrom(ImportFromDefinitionNodeRef<'ast>), + ImportStar(StarImportDefinitionNodeRef<'ast>), + For(ForStmtDefinitionNodeRef<'ast, 'db>), + Function(&'ast ast::StmtFunctionDef), + Class(&'ast ast::StmtClassDef), + TypeAlias(&'ast ast::StmtTypeAlias), + NamedExpression(&'ast ast::ExprNamed), + Assignment(AssignmentDefinitionNodeRef<'ast, 'db>), + AnnotatedAssignment(AnnotatedAssignmentDefinitionNodeRef<'ast>), + AugmentedAssignment(&'ast ast::StmtAugAssign), + Comprehension(ComprehensionDefinitionNodeRef<'ast, 'db>), + VariadicPositionalParameter(&'ast ast::Parameter), + VariadicKeywordParameter(&'ast ast::Parameter), + Parameter(&'ast ast::ParameterWithDefault), + WithItem(WithItemDefinitionNodeRef<'ast, 'db>), + MatchPattern(MatchPatternDefinitionNodeRef<'ast>), + ExceptHandler(ExceptHandlerDefinitionNodeRef<'ast>), + TypeVar(&'ast ast::TypeParamTypeVar), + ParamSpec(&'ast ast::TypeParamParamSpec), + TypeVarTuple(&'ast ast::TypeParamTypeVarTuple), +} + +impl<'ast> From<&'ast ast::StmtFunctionDef> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::StmtFunctionDef) -> Self { Self::Function(node) } } -impl<'a> From<&'a ast::StmtClassDef> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::StmtClassDef) -> Self { +impl<'ast> From<&'ast ast::StmtClassDef> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::StmtClassDef) -> Self { Self::Class(node) } } -impl<'a> From<&'a ast::StmtTypeAlias> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::StmtTypeAlias) -> Self { +impl<'ast> From<&'ast ast::StmtTypeAlias> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::StmtTypeAlias) -> Self { Self::TypeAlias(node) } } -impl<'a> From<&'a ast::ExprNamed> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::ExprNamed) -> Self { +impl<'ast> From<&'ast ast::ExprNamed> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::ExprNamed) -> Self { Self::NamedExpression(node) } } -impl<'a> From<&'a ast::StmtAugAssign> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::StmtAugAssign) -> Self { +impl<'ast> From<&'ast ast::StmtAugAssign> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::StmtAugAssign) -> Self { Self::AugmentedAssignment(node) } } -impl<'a> From<&'a ast::TypeParamTypeVar> for DefinitionNodeRef<'a> { - fn from(value: &'a ast::TypeParamTypeVar) -> Self { +impl<'ast> From<&'ast ast::TypeParamTypeVar> for DefinitionNodeRef<'ast, '_> { + fn from(value: &'ast ast::TypeParamTypeVar) -> Self { Self::TypeVar(value) } } -impl<'a> From<&'a ast::TypeParamParamSpec> for DefinitionNodeRef<'a> { - fn from(value: &'a ast::TypeParamParamSpec) -> Self { +impl<'ast> From<&'ast ast::TypeParamParamSpec> for DefinitionNodeRef<'ast, '_> { + fn from(value: &'ast ast::TypeParamParamSpec) -> Self { Self::ParamSpec(value) } } -impl<'a> From<&'a ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'a> { - fn from(value: &'a ast::TypeParamTypeVarTuple) -> Self { +impl<'ast> From<&'ast ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'ast, '_> { + fn from(value: &'ast ast::TypeParamTypeVarTuple) -> Self { Self::TypeVarTuple(value) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node_ref: ImportDefinitionNodeRef<'a>) -> Self { +impl<'ast> From> for DefinitionNodeRef<'ast, '_> { + fn from(node_ref: ImportDefinitionNodeRef<'ast>) -> Self { Self::Import(node_ref) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node_ref: ImportFromDefinitionNodeRef<'a>) -> Self { +impl<'ast> From> for DefinitionNodeRef<'ast, '_> { + fn from(node_ref: ImportFromDefinitionNodeRef<'ast>) -> Self { Self::ImportFrom(node_ref) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(value: ForStmtDefinitionNodeRef<'a>) -> Self { +impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { + fn from(value: ForStmtDefinitionNodeRef<'ast, 'db>) -> Self { Self::For(value) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node_ref: AssignmentDefinitionNodeRef<'a>) -> Self { +impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { + fn from(node_ref: AssignmentDefinitionNodeRef<'ast, 'db>) -> Self { Self::Assignment(node_ref) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node_ref: AnnotatedAssignmentDefinitionNodeRef<'a>) -> Self { +impl<'ast> From> for DefinitionNodeRef<'ast, '_> { + fn from(node_ref: AnnotatedAssignmentDefinitionNodeRef<'ast>) -> Self { Self::AnnotatedAssignment(node_ref) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node_ref: WithItemDefinitionNodeRef<'a>) -> Self { +impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { + fn from(node_ref: WithItemDefinitionNodeRef<'ast, 'db>) -> Self { Self::WithItem(node_ref) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node: ComprehensionDefinitionNodeRef<'a>) -> Self { +impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { + fn from(node: ComprehensionDefinitionNodeRef<'ast, 'db>) -> Self { Self::Comprehension(node) } } -impl<'a> From<&'a ast::ParameterWithDefault> for DefinitionNodeRef<'a> { - fn from(node: &'a ast::ParameterWithDefault) -> Self { +impl<'ast> From<&'ast ast::ParameterWithDefault> for DefinitionNodeRef<'ast, '_> { + fn from(node: &'ast ast::ParameterWithDefault) -> Self { Self::Parameter(node) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node: MatchPatternDefinitionNodeRef<'a>) -> Self { +impl<'ast> From> for DefinitionNodeRef<'ast, '_> { + fn from(node: MatchPatternDefinitionNodeRef<'ast>) -> Self { Self::MatchPattern(node) } } -impl<'a> From> for DefinitionNodeRef<'a> { - fn from(node: StarImportDefinitionNodeRef<'a>) -> Self { +impl<'ast> From> for DefinitionNodeRef<'ast, '_> { + fn from(node: StarImportDefinitionNodeRef<'ast>) -> Self { Self::ImportStar(node) } } #[derive(Copy, Clone, Debug)] -pub(crate) struct ImportDefinitionNodeRef<'a> { - pub(crate) node: &'a ast::StmtImport, +pub(crate) struct ImportDefinitionNodeRef<'ast> { + pub(crate) node: &'ast ast::StmtImport, pub(crate) alias_index: usize, pub(crate) is_reexported: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct StarImportDefinitionNodeRef<'a> { - pub(crate) node: &'a ast::StmtImportFrom, +pub(crate) struct StarImportDefinitionNodeRef<'ast> { + pub(crate) node: &'ast ast::StmtImportFrom, pub(crate) place_id: ScopedPlaceId, } #[derive(Copy, Clone, Debug)] -pub(crate) struct ImportFromDefinitionNodeRef<'a> { - pub(crate) node: &'a ast::StmtImportFrom, +pub(crate) struct ImportFromDefinitionNodeRef<'ast> { + pub(crate) node: &'ast ast::StmtImportFrom, pub(crate) alias_index: usize, pub(crate) is_reexported: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct AssignmentDefinitionNodeRef<'a> { - pub(crate) unpack: Option<(UnpackPosition, Unpack<'a>)>, - pub(crate) value: &'a ast::Expr, - pub(crate) target: &'a ast::Expr, +pub(crate) struct AssignmentDefinitionNodeRef<'ast, 'db> { + pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, + pub(crate) value: &'ast ast::Expr, + pub(crate) target: &'ast ast::Expr, } #[derive(Copy, Clone, Debug)] -pub(crate) struct AnnotatedAssignmentDefinitionNodeRef<'a> { - pub(crate) node: &'a ast::StmtAnnAssign, - pub(crate) annotation: &'a ast::Expr, - pub(crate) value: Option<&'a ast::Expr>, - pub(crate) target: &'a ast::Expr, +pub(crate) struct AnnotatedAssignmentDefinitionNodeRef<'ast> { + pub(crate) node: &'ast ast::StmtAnnAssign, + pub(crate) annotation: &'ast ast::Expr, + pub(crate) value: Option<&'ast ast::Expr>, + pub(crate) target: &'ast ast::Expr, } #[derive(Copy, Clone, Debug)] -pub(crate) struct WithItemDefinitionNodeRef<'a> { - pub(crate) unpack: Option<(UnpackPosition, Unpack<'a>)>, - pub(crate) context_expr: &'a ast::Expr, - pub(crate) target: &'a ast::Expr, +pub(crate) struct WithItemDefinitionNodeRef<'ast, 'db> { + pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, + pub(crate) context_expr: &'ast ast::Expr, + pub(crate) target: &'ast ast::Expr, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct ForStmtDefinitionNodeRef<'a> { - pub(crate) unpack: Option<(UnpackPosition, Unpack<'a>)>, - pub(crate) iterable: &'a ast::Expr, - pub(crate) target: &'a ast::Expr, +pub(crate) struct ForStmtDefinitionNodeRef<'ast, 'db> { + pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, + pub(crate) iterable: &'ast ast::Expr, + pub(crate) target: &'ast ast::Expr, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct ExceptHandlerDefinitionNodeRef<'a> { - pub(crate) handler: &'a ast::ExceptHandlerExceptHandler, +pub(crate) struct ExceptHandlerDefinitionNodeRef<'ast> { + pub(crate) handler: &'ast ast::ExceptHandlerExceptHandler, pub(crate) is_star: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct ComprehensionDefinitionNodeRef<'a> { - pub(crate) unpack: Option<(UnpackPosition, Unpack<'a>)>, - pub(crate) iterable: &'a ast::Expr, - pub(crate) target: &'a ast::Expr, +pub(crate) struct ComprehensionDefinitionNodeRef<'ast, 'db> { + pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, + pub(crate) iterable: &'ast ast::Expr, + pub(crate) target: &'ast ast::Expr, pub(crate) first: bool, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] -pub(crate) struct MatchPatternDefinitionNodeRef<'a> { +pub(crate) struct MatchPatternDefinitionNodeRef<'ast> { /// The outermost pattern node in which the identifier being defined occurs. - pub(crate) pattern: &'a ast::Pattern, + pub(crate) pattern: &'ast ast::Pattern, /// The identifier being defined. - pub(crate) identifier: &'a ast::Identifier, + pub(crate) identifier: &'ast ast::Identifier, /// The index of the identifier in the pattern when visiting the `pattern` node in evaluation /// order. pub(crate) index: u32, } -impl<'db> DefinitionNodeRef<'db> { +impl<'db> DefinitionNodeRef<'_, 'db> { #[expect(unsafe_code)] - pub(super) unsafe fn into_owned(self, parsed: ParsedModule) -> DefinitionKind<'db> { + pub(super) unsafe fn into_owned(self, parsed: ParsedModuleRef) -> DefinitionKind<'db> { match self { DefinitionNodeRef::Import(ImportDefinitionNodeRef { node, @@ -626,60 +626,74 @@ impl DefinitionKind<'_> { /// /// A definition target would mainly be the node representing the place being defined i.e., /// [`ast::ExprName`], [`ast::Identifier`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`] but could also be other nodes. - pub(crate) fn target_range(&self) -> TextRange { + pub(crate) fn target_range(&self, module: &ParsedModuleRef) -> TextRange { match self { - DefinitionKind::Import(import) => import.alias().range(), - DefinitionKind::ImportFrom(import) => import.alias().range(), - DefinitionKind::StarImport(import) => import.alias().range(), - DefinitionKind::Function(function) => function.name.range(), - DefinitionKind::Class(class) => class.name.range(), - DefinitionKind::TypeAlias(type_alias) => type_alias.name.range(), - DefinitionKind::NamedExpression(named) => named.target.range(), - DefinitionKind::Assignment(assignment) => assignment.target.range(), - DefinitionKind::AnnotatedAssignment(assign) => assign.target.range(), - DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.target.range(), - DefinitionKind::For(for_stmt) => for_stmt.target.range(), - DefinitionKind::Comprehension(comp) => comp.target().range(), - DefinitionKind::VariadicPositionalParameter(parameter) => parameter.name.range(), - DefinitionKind::VariadicKeywordParameter(parameter) => parameter.name.range(), - DefinitionKind::Parameter(parameter) => parameter.parameter.name.range(), - DefinitionKind::WithItem(with_item) => with_item.target.range(), - DefinitionKind::MatchPattern(match_pattern) => match_pattern.identifier.range(), - DefinitionKind::ExceptHandler(handler) => handler.node().range(), - DefinitionKind::TypeVar(type_var) => type_var.name.range(), - DefinitionKind::ParamSpec(param_spec) => param_spec.name.range(), - DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.name.range(), + DefinitionKind::Import(import) => import.alias(module).range(), + DefinitionKind::ImportFrom(import) => import.alias(module).range(), + DefinitionKind::StarImport(import) => import.alias(module).range(), + DefinitionKind::Function(function) => function.node(module).name.range(), + DefinitionKind::Class(class) => class.node(module).name.range(), + DefinitionKind::TypeAlias(type_alias) => type_alias.node(module).name.range(), + DefinitionKind::NamedExpression(named) => named.node(module).target.range(), + DefinitionKind::Assignment(assignment) => assignment.target.node(module).range(), + DefinitionKind::AnnotatedAssignment(assign) => assign.target.node(module).range(), + DefinitionKind::AugmentedAssignment(aug_assign) => { + aug_assign.node(module).target.range() + } + DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(), + DefinitionKind::Comprehension(comp) => comp.target(module).range(), + DefinitionKind::VariadicPositionalParameter(parameter) => { + parameter.node(module).name.range() + } + DefinitionKind::VariadicKeywordParameter(parameter) => { + parameter.node(module).name.range() + } + DefinitionKind::Parameter(parameter) => parameter.node(module).parameter.name.range(), + DefinitionKind::WithItem(with_item) => with_item.target.node(module).range(), + DefinitionKind::MatchPattern(match_pattern) => { + match_pattern.identifier.node(module).range() + } + DefinitionKind::ExceptHandler(handler) => handler.node(module).range(), + DefinitionKind::TypeVar(type_var) => type_var.node(module).name.range(), + DefinitionKind::ParamSpec(param_spec) => param_spec.node(module).name.range(), + DefinitionKind::TypeVarTuple(type_var_tuple) => { + type_var_tuple.node(module).name.range() + } } } /// Returns the [`TextRange`] of the entire definition. - pub(crate) fn full_range(&self) -> TextRange { + pub(crate) fn full_range(&self, module: &ParsedModuleRef) -> TextRange { match self { - DefinitionKind::Import(import) => import.alias().range(), - DefinitionKind::ImportFrom(import) => import.alias().range(), - DefinitionKind::StarImport(import) => import.import().range(), - DefinitionKind::Function(function) => function.range(), - DefinitionKind::Class(class) => class.range(), - DefinitionKind::TypeAlias(type_alias) => type_alias.range(), - DefinitionKind::NamedExpression(named) => named.range(), - DefinitionKind::Assignment(assignment) => assignment.target.range(), - DefinitionKind::AnnotatedAssignment(assign) => assign.target.range(), - DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.range(), - DefinitionKind::For(for_stmt) => for_stmt.target.range(), - DefinitionKind::Comprehension(comp) => comp.target().range(), - DefinitionKind::VariadicPositionalParameter(parameter) => parameter.range(), - DefinitionKind::VariadicKeywordParameter(parameter) => parameter.range(), - DefinitionKind::Parameter(parameter) => parameter.parameter.range(), - DefinitionKind::WithItem(with_item) => with_item.target.range(), - DefinitionKind::MatchPattern(match_pattern) => match_pattern.identifier.range(), - DefinitionKind::ExceptHandler(handler) => handler.node().range(), - DefinitionKind::TypeVar(type_var) => type_var.range(), - DefinitionKind::ParamSpec(param_spec) => param_spec.range(), - DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.range(), + DefinitionKind::Import(import) => import.alias(module).range(), + DefinitionKind::ImportFrom(import) => import.alias(module).range(), + DefinitionKind::StarImport(import) => import.import(module).range(), + DefinitionKind::Function(function) => function.node(module).range(), + DefinitionKind::Class(class) => class.node(module).range(), + DefinitionKind::TypeAlias(type_alias) => type_alias.node(module).range(), + DefinitionKind::NamedExpression(named) => named.node(module).range(), + DefinitionKind::Assignment(assignment) => assignment.target.node(module).range(), + DefinitionKind::AnnotatedAssignment(assign) => assign.target.node(module).range(), + DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.node(module).range(), + DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(), + DefinitionKind::Comprehension(comp) => comp.target(module).range(), + DefinitionKind::VariadicPositionalParameter(parameter) => { + parameter.node(module).range() + } + DefinitionKind::VariadicKeywordParameter(parameter) => parameter.node(module).range(), + DefinitionKind::Parameter(parameter) => parameter.node(module).parameter.range(), + DefinitionKind::WithItem(with_item) => with_item.target.node(module).range(), + DefinitionKind::MatchPattern(match_pattern) => { + match_pattern.identifier.node(module).range() + } + DefinitionKind::ExceptHandler(handler) => handler.node(module).range(), + DefinitionKind::TypeVar(type_var) => type_var.node(module).range(), + DefinitionKind::ParamSpec(param_spec) => param_spec.node(module).range(), + DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.node(module).range(), } } - pub(crate) fn category(&self, in_stub: bool) -> DefinitionCategory { + pub(crate) fn category(&self, in_stub: bool, module: &ParsedModuleRef) -> DefinitionCategory { match self { // functions, classes, and imports always bind, and we consider them declarations DefinitionKind::Function(_) @@ -694,7 +708,7 @@ impl DefinitionKind<'_> { // a parameter always binds a value, but is only a declaration if annotated DefinitionKind::VariadicPositionalParameter(parameter) | DefinitionKind::VariadicKeywordParameter(parameter) => { - if parameter.annotation.is_some() { + if parameter.node(module).annotation.is_some() { DefinitionCategory::DeclarationAndBinding } else { DefinitionCategory::Binding @@ -702,7 +716,12 @@ impl DefinitionKind<'_> { } // presence of a default is irrelevant, same logic as for a no-default parameter DefinitionKind::Parameter(parameter_with_default) => { - if parameter_with_default.parameter.annotation.is_some() { + if parameter_with_default + .node(module) + .parameter + .annotation + .is_some() + { DefinitionCategory::DeclarationAndBinding } else { DefinitionCategory::Binding @@ -753,15 +772,15 @@ pub struct StarImportDefinitionKind { } impl StarImportDefinitionKind { - pub(crate) fn import(&self) -> &ast::StmtImportFrom { - self.node.node() + pub(crate) fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImportFrom { + self.node.node(module) } - pub(crate) fn alias(&self) -> &ast::Alias { + pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { // INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list. self.node - .node() + .node(module) .names .iter() .find(|alias| &alias.name == "*") @@ -784,8 +803,8 @@ pub struct MatchPatternDefinitionKind { } impl MatchPatternDefinitionKind { - pub(crate) fn pattern(&self) -> &ast::Pattern { - self.pattern.node() + pub(crate) fn pattern<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Pattern { + self.pattern.node(module) } pub(crate) fn index(&self) -> u32 { @@ -808,16 +827,16 @@ pub struct ComprehensionDefinitionKind<'db> { } impl<'db> ComprehensionDefinitionKind<'db> { - pub(crate) fn iterable(&self) -> &ast::Expr { - self.iterable.node() + pub(crate) fn iterable<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.iterable.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } - pub(crate) fn target(&self) -> &ast::Expr { - self.target.node() + pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.target.node(module) } pub(crate) fn is_first(&self) -> bool { @@ -837,12 +856,12 @@ pub struct ImportDefinitionKind { } impl ImportDefinitionKind { - pub(crate) fn import(&self) -> &ast::StmtImport { - self.node.node() + pub(crate) fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImport { + self.node.node(module) } - pub(crate) fn alias(&self) -> &ast::Alias { - &self.node.node().names[self.alias_index] + pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { + &self.node.node(module).names[self.alias_index] } pub(crate) fn is_reexported(&self) -> bool { @@ -858,12 +877,12 @@ pub struct ImportFromDefinitionKind { } impl ImportFromDefinitionKind { - pub(crate) fn import(&self) -> &ast::StmtImportFrom { - self.node.node() + pub(crate) fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImportFrom { + self.node.node(module) } - pub(crate) fn alias(&self) -> &ast::Alias { - &self.node.node().names[self.alias_index] + pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { + &self.node.node(module).names[self.alias_index] } pub(crate) fn is_reexported(&self) -> bool { @@ -883,12 +902,12 @@ impl<'db> AssignmentDefinitionKind<'db> { self.target_kind } - pub(crate) fn value(&self) -> &ast::Expr { - self.value.node() + pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.value.node(module) } - pub(crate) fn target(&self) -> &ast::Expr { - self.target.node() + pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.target.node(module) } } @@ -900,16 +919,16 @@ pub struct AnnotatedAssignmentDefinitionKind { } impl AnnotatedAssignmentDefinitionKind { - pub(crate) fn value(&self) -> Option<&ast::Expr> { - self.value.as_deref() + pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> Option<&'ast ast::Expr> { + self.value.as_ref().map(|value| value.node(module)) } - pub(crate) fn annotation(&self) -> &ast::Expr { - self.annotation.node() + pub(crate) fn annotation<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.annotation.node(module) } - pub(crate) fn target(&self) -> &ast::Expr { - self.target.node() + pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.target.node(module) } } @@ -922,16 +941,16 @@ pub struct WithItemDefinitionKind<'db> { } impl<'db> WithItemDefinitionKind<'db> { - pub(crate) fn context_expr(&self) -> &ast::Expr { - self.context_expr.node() + pub(crate) fn context_expr<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.context_expr.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } - pub(crate) fn target(&self) -> &ast::Expr { - self.target.node() + pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.target.node(module) } pub(crate) const fn is_async(&self) -> bool { @@ -948,16 +967,16 @@ pub struct ForStmtDefinitionKind<'db> { } impl<'db> ForStmtDefinitionKind<'db> { - pub(crate) fn iterable(&self) -> &ast::Expr { - self.iterable.node() + pub(crate) fn iterable<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.iterable.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } - pub(crate) fn target(&self) -> &ast::Expr { - self.target.node() + pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { + self.target.node(module) } pub(crate) const fn is_async(&self) -> bool { @@ -972,12 +991,18 @@ pub struct ExceptHandlerDefinitionKind { } impl ExceptHandlerDefinitionKind { - pub(crate) fn node(&self) -> &ast::ExceptHandlerExceptHandler { - self.handler.node() - } - - pub(crate) fn handled_exceptions(&self) -> Option<&ast::Expr> { - self.node().type_.as_deref() + pub(crate) fn node<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::ExceptHandlerExceptHandler { + self.handler.node(module) + } + + pub(crate) fn handled_exceptions<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> Option<&'ast ast::Expr> { + self.node(module).type_.as_deref() } pub(crate) fn is_star(&self) -> bool { diff --git a/crates/ty_python_semantic/src/semantic_index/expression.rs b/crates/ty_python_semantic/src/semantic_index/expression.rs index 1c5178b244a878..18a64b54e72af6 100644 --- a/crates/ty_python_semantic/src/semantic_index/expression.rs +++ b/crates/ty_python_semantic/src/semantic_index/expression.rs @@ -2,6 +2,7 @@ use crate::ast_node_ref::AstNodeRef; use crate::db::Db; use crate::semantic_index::place::{FileScopeId, ScopeId}; use ruff_db::files::File; +use ruff_db::parsed::ParsedModuleRef; use ruff_python_ast as ast; use salsa; @@ -41,8 +42,8 @@ pub(crate) struct Expression<'db> { /// The expression node. #[no_eq] #[tracked] - #[returns(deref)] - pub(crate) node_ref: AstNodeRef, + #[returns(ref)] + pub(crate) _node_ref: AstNodeRef, /// An assignment statement, if this expression is immediately used as the rhs of that /// assignment. @@ -62,6 +63,14 @@ pub(crate) struct Expression<'db> { } impl<'db> Expression<'db> { + pub(crate) fn node_ref<'ast>( + self, + db: &'db dyn Db, + parsed: &'ast ParsedModuleRef, + ) -> &'ast ast::Expr { + self._node_ref(db).node(parsed) + } + pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> { self.file_scope(db).to_scope_id(db, self.file(db)) } diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs index 4862c61b2ad169..d8295ef50e81cc 100644 --- a/crates/ty_python_semantic/src/semantic_index/place.rs +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -5,7 +5,7 @@ use std::ops::Range; use bitflags::bitflags; use hashbrown::hash_map::RawEntryMut; use ruff_db::files::File; -use ruff_db::parsed::ParsedModule; +use ruff_db::parsed::ParsedModuleRef; use ruff_index::{IndexVec, newtype_index}; use ruff_python_ast as ast; use ruff_python_ast::name::Name; @@ -381,16 +381,19 @@ impl<'db> ScopeId<'db> { } #[cfg(test)] - pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { + pub(crate) fn name<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast str { match self.node(db) { NodeWithScopeKind::Module => "", NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => { - class.name.as_str() + class.node(module).name.as_str() } NodeWithScopeKind::Function(function) - | NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(), + | NodeWithScopeKind::FunctionTypeParameters(function) => { + function.node(module).name.as_str() + } NodeWithScopeKind::TypeAlias(type_alias) | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias + .node(module) .name .as_name_expr() .map(|name| name.id.as_str()) @@ -778,7 +781,7 @@ impl NodeWithScopeRef<'_> { /// # Safety /// The node wrapped by `self` must be a child of `module`. #[expect(unsafe_code)] - pub(super) unsafe fn to_kind(self, module: ParsedModule) -> NodeWithScopeKind { + pub(super) unsafe fn to_kind(self, module: ParsedModuleRef) -> NodeWithScopeKind { unsafe { match self { NodeWithScopeRef::Module => NodeWithScopeKind::Module, @@ -892,34 +895,46 @@ impl NodeWithScopeKind { } } - pub fn expect_class(&self) -> &ast::StmtClassDef { + pub fn expect_class<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef { match self { - Self::Class(class) => class.node(), + Self::Class(class) => class.node(module), _ => panic!("expected class"), } } - pub(crate) const fn as_class(&self) -> Option<&ast::StmtClassDef> { + pub(crate) fn as_class<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> Option<&'ast ast::StmtClassDef> { match self { - Self::Class(class) => Some(class.node()), + Self::Class(class) => Some(class.node(module)), _ => None, } } - pub fn expect_function(&self) -> &ast::StmtFunctionDef { - self.as_function().expect("expected function") + pub fn expect_function<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtFunctionDef { + self.as_function(module).expect("expected function") } - pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias { + pub fn expect_type_alias<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtTypeAlias { match self { - Self::TypeAlias(type_alias) => type_alias.node(), + Self::TypeAlias(type_alias) => type_alias.node(module), _ => panic!("expected type alias"), } } - pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> { + pub fn as_function<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> Option<&'ast ast::StmtFunctionDef> { match self { - Self::Function(function) => Some(function.node()), + Self::Function(function) => Some(function.node(module)), _ => None, } } diff --git a/crates/ty_python_semantic/src/semantic_index/re_exports.rs b/crates/ty_python_semantic/src/semantic_index/re_exports.rs index 049e751bf0099d..1f31e05f7dbe7f 100644 --- a/crates/ty_python_semantic/src/semantic_index/re_exports.rs +++ b/crates/ty_python_semantic/src/semantic_index/re_exports.rs @@ -45,7 +45,7 @@ fn exports_cycle_initial(_db: &dyn Db, _file: File) -> Box<[Name]> { #[salsa::tracked(returns(deref), cycle_fn=exports_cycle_recover, cycle_initial=exports_cycle_initial)] pub(super) fn exported_names(db: &dyn Db, file: File) -> Box<[Name]> { - let module = parsed_module(db.upcast(), file); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let mut finder = ExportFinder::new(db, file); finder.visit_body(module.suite()); finder.resolve_exports() diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index b850d9d6c37988..9237e75ee2ece4 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -232,7 +232,7 @@ mod tests { let foo = system_path_to_file(&db, "/src/foo.py").unwrap(); - let ast = parsed_module(&db, foo); + let ast = parsed_module(&db, foo).load(&db); let function = ast.suite()[0].as_function_def_stmt().unwrap(); let model = SemanticModel::new(&db, foo); @@ -251,7 +251,7 @@ mod tests { let foo = system_path_to_file(&db, "/src/foo.py").unwrap(); - let ast = parsed_module(&db, foo); + let ast = parsed_module(&db, foo).load(&db); let class = ast.suite()[0].as_class_def_stmt().unwrap(); let model = SemanticModel::new(&db, foo); @@ -271,7 +271,7 @@ mod tests { let bar = system_path_to_file(&db, "/src/bar.py").unwrap(); - let ast = parsed_module(&db, bar); + let ast = parsed_module(&db, bar).load(&db); let import = ast.suite()[0].as_import_from_stmt().unwrap(); let alias = &import.names[0]; diff --git a/crates/ty_python_semantic/src/suppression.rs b/crates/ty_python_semantic/src/suppression.rs index 51efb2c72d131f..26518ec7fad64c 100644 --- a/crates/ty_python_semantic/src/suppression.rs +++ b/crates/ty_python_semantic/src/suppression.rs @@ -88,7 +88,7 @@ declare_lint! { #[salsa::tracked(returns(ref))] pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { - let parsed = parsed_module(db.upcast(), file); + let parsed = parsed_module(db.upcast(), file).load(db.upcast()); let source = source_text(db.upcast(), file); let mut builder = SuppressionsBuilder::new(&source, db.lint_registry()); diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 468fd018e93b7e..c6407e00019147 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1,5 +1,6 @@ use infer::nearest_enclosing_class; use itertools::Either; +use ruff_db::parsed::parsed_module; use std::slice::Iter; @@ -5065,8 +5066,9 @@ impl<'db> Type<'db> { SpecialFormType::Callable => Ok(CallableType::unknown(db)), SpecialFormType::TypingSelf => { + let module = parsed_module(db.upcast(), scope_id.file(db)).load(db.upcast()); let index = semantic_index(db, scope_id.file(db)); - let Some(class) = nearest_enclosing_class(db, index, scope_id) else { + let Some(class) = nearest_enclosing_class(db, index, scope_id, &module) else { return Err(InvalidTypeExpressionError { fallback_type: Type::unknown(), invalid_expressions: smallvec::smallvec![ @@ -6302,7 +6304,7 @@ impl<'db> ContextManagerError<'db> { fn report_diagnostic( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, context_expression_type: Type<'db>, context_expression_node: ast::AnyNodeRef, ) { @@ -6475,7 +6477,7 @@ impl<'db> IterationError<'db> { /// Reports the diagnostic for this error. fn report_diagnostic( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, iterable_type: Type<'db>, iterable_node: ast::AnyNodeRef, ) { @@ -6951,7 +6953,7 @@ impl<'db> ConstructorCallError<'db> { fn report_diagnostic( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, context_expression_type: Type<'db>, context_expression_node: ast::AnyNodeRef, ) { @@ -7578,7 +7580,8 @@ pub struct PEP695TypeAliasType<'db> { impl<'db> PEP695TypeAliasType<'db> { pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { let scope = self.rhs_scope(db); - let type_alias_stmt_node = scope.node(db).expect_type_alias(); + let module = parsed_module(db.upcast(), scope.file(db)).load(db.upcast()); + 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) } @@ -7586,7 +7589,8 @@ impl<'db> PEP695TypeAliasType<'db> { #[salsa::tracked] pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> { let scope = self.rhs_scope(db); - let type_alias_stmt_node = scope.node(db).expect_type_alias(); + let module = parsed_module(db.upcast(), scope.file(db)).load(db.upcast()); + let type_alias_stmt_node = scope.node(db).expect_type_alias(&module); let definition = self.definition(db); definition_expression_type(db, definition, &type_alias_stmt_node.value) } @@ -8654,10 +8658,8 @@ pub(crate) mod tests { ); let events = db.take_salsa_events(); - let call = &*parsed_module(&db, bar).syntax().body[1] - .as_assign_stmt() - .unwrap() - .value; + let module = parsed_module(&db, bar).load(&db); + let call = &*module.syntax().body[1].as_assign_stmt().unwrap().value; let foo_call = semantic_index(&db, bar).expression(call); assert_function_query_was_not_run(&db, infer_expression_types, foo_call, &events); diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 14906fd6daa70b..a7b6e9d94bbd2b 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -4,6 +4,7 @@ //! union of types, each of which might contain multiple overloads. use itertools::Itertools; +use ruff_db::parsed::parsed_module; use smallvec::{SmallVec, smallvec}; use super::{ @@ -198,7 +199,11 @@ impl<'db> Bindings<'db> { /// report a single diagnostic if we couldn't match any union element or overload. /// TODO: Update this to add subdiagnostics about how we failed to match each union element and /// overload. - pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { + pub(crate) fn report_diagnostics( + &self, + context: &InferContext<'db, '_>, + node: ast::AnyNodeRef, + ) { // If all union elements are not callable, report that the union as a whole is not // callable. if self.into_iter().all(|b| !b.is_callable()) { @@ -1367,7 +1372,7 @@ impl<'db> CallableBinding<'db> { fn report_diagnostics( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, node: ast::AnyNodeRef, union_diag: Option<&UnionDiagnostic<'_, '_>>, ) { @@ -1840,7 +1845,7 @@ impl<'db> Binding<'db> { fn report_diagnostics( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, node: ast::AnyNodeRef, callable_ty: Type<'db>, callable_description: Option<&CallableDescription>, @@ -2128,7 +2133,7 @@ pub(crate) enum BindingError<'db> { impl<'db> BindingError<'db> { fn report_diagnostic( &self, - context: &InferContext<'db>, + context: &InferContext<'db, '_>, node: ast::AnyNodeRef, callable_ty: Type<'db>, callable_description: Option<&CallableDescription>, @@ -2285,7 +2290,10 @@ impl<'db> BindingError<'db> { )); if let Some(typevar_definition) = typevar.definition(context.db()) { - let typevar_range = typevar_definition.full_range(context.db()); + let module = + parsed_module(context.db().upcast(), typevar_definition.file(context.db())) + .load(context.db().upcast()); + let typevar_range = typevar_definition.full_range(context.db(), &module); let mut sub = SubDiagnostic::new(Severity::Info, "Type variable defined here"); sub.annotate(Annotation::primary(typevar_range.into())); diag.sub(sub); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ffc558e969724a..344c2002379e27 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -37,6 +37,7 @@ use indexmap::IndexSet; use itertools::Itertools as _; use ruff_db::diagnostic::Span; use ruff_db::files::File; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, PythonVersion}; use ruff_text_size::{Ranged, TextRange}; @@ -715,7 +716,8 @@ impl<'db> ClassLiteral<'db> { #[salsa::tracked(cycle_fn=pep695_generic_context_cycle_recover, cycle_initial=pep695_generic_context_cycle_initial)] pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option> { let scope = self.body_scope(db); - let class_def_node = scope.node(db).expect_class(); + let parsed = parsed_module(db.upcast(), scope.file(db)).load(db.upcast()); + let class_def_node = scope.node(db).expect_class(&parsed); class_def_node.type_params.as_ref().map(|type_params| { let index = semantic_index(db, scope.file(db)); GenericContext::from_type_params(db, index, type_params) @@ -754,14 +756,16 @@ impl<'db> ClassLiteral<'db> { /// ## Note /// Only call this function from queries in the same file or your /// query depends on the AST of another file (bad!). - fn node(self, db: &'db dyn Db) -> &'db ast::StmtClassDef { - self.body_scope(db).node(db).expect_class() + fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef { + let scope = self.body_scope(db); + scope.node(db).expect_class(module) } pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { let body_scope = self.body_scope(db); + let module = parsed_module(db.upcast(), body_scope.file(db)).load(db.upcast()); let index = semantic_index(db, body_scope.file(db)); - index.expect_single_definition(body_scope.node(db).expect_class()) + index.expect_single_definition(body_scope.node(db).expect_class(&module)) } pub(crate) fn apply_optional_specialization( @@ -835,7 +839,8 @@ impl<'db> ClassLiteral<'db> { pub(super) fn explicit_bases(self, db: &'db dyn Db) -> Box<[Type<'db>]> { tracing::trace!("ClassLiteral::explicit_bases_query: {}", self.name(db)); - let class_stmt = self.node(db); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + let class_stmt = self.node(db, &module); let class_definition = semantic_index(db, self.file(db)).expect_single_definition(class_stmt); @@ -897,7 +902,9 @@ impl<'db> ClassLiteral<'db> { fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> { tracing::trace!("ClassLiteral::decorators: {}", self.name(db)); - let class_stmt = self.node(db); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + + let class_stmt = self.node(db, &module); if class_stmt.decorator_list.is_empty() { return Box::new([]); } @@ -983,8 +990,8 @@ impl<'db> ClassLiteral<'db> { /// ## Note /// Only call this function from queries in the same file or your /// query depends on the AST of another file (bad!). - fn explicit_metaclass(self, db: &'db dyn Db) -> Option> { - let class_stmt = self.node(db); + fn explicit_metaclass(self, db: &'db dyn Db, module: &ParsedModuleRef) -> Option> { + let class_stmt = self.node(db, module); let metaclass_node = &class_stmt .arguments .as_ref()? @@ -1039,7 +1046,9 @@ impl<'db> ClassLiteral<'db> { return Ok((SubclassOfType::subclass_of_unknown(), None)); } - let explicit_metaclass = self.explicit_metaclass(db); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + + let explicit_metaclass = self.explicit_metaclass(db, &module); let (metaclass, class_metaclass_was_from) = if let Some(metaclass) = explicit_metaclass { (metaclass, self) } else if let Some(base_class) = base_classes.next() { @@ -1608,6 +1617,7 @@ impl<'db> ClassLiteral<'db> { let mut is_attribute_bound = Truthiness::AlwaysFalse; let file = class_body_scope.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let index = semantic_index(db, file); let class_map = use_def_map(db, class_body_scope); let class_table = place_table(db, class_body_scope); @@ -1619,19 +1629,20 @@ impl<'db> ClassLiteral<'db> { let method_map = use_def_map(db, method_scope); // The attribute assignment inherits the visibility of the method which contains it - let is_method_visible = if let Some(method_def) = method_scope.node(db).as_function() { - let method = index.expect_single_definition(method_def); - let method_place = class_table.place_id_by_name(&method_def.name).unwrap(); - class_map - .public_bindings(method_place) - .find_map(|bind| { - (bind.binding.is_defined_and(|def| def == method)) - .then(|| class_map.is_binding_visible(db, &bind)) - }) - .unwrap_or(Truthiness::AlwaysFalse) - } else { - Truthiness::AlwaysFalse - }; + let is_method_visible = + if let Some(method_def) = method_scope.node(db).as_function(&module) { + let method = index.expect_single_definition(method_def); + let method_place = class_table.place_id_by_name(&method_def.name).unwrap(); + class_map + .public_bindings(method_place) + .find_map(|bind| { + (bind.binding.is_defined_and(|def| def == method)) + .then(|| class_map.is_binding_visible(db, &bind)) + }) + .unwrap_or(Truthiness::AlwaysFalse) + } else { + Truthiness::AlwaysFalse + }; if is_method_visible.is_always_false() { continue; } @@ -1688,8 +1699,10 @@ impl<'db> ClassLiteral<'db> { // self.name: // self.name: = … - let annotation_ty = - infer_expression_type(db, index.expression(ann_assign.annotation())); + let annotation_ty = infer_expression_type( + db, + index.expression(ann_assign.annotation(&module)), + ); // TODO: check if there are conflicting declarations match is_attribute_bound { @@ -1714,8 +1727,9 @@ impl<'db> ClassLiteral<'db> { // [.., self.name, ..] = let unpacked = infer_unpack_types(db, unpack); - let target_ast_id = - assign.target().scoped_expression_id(db, method_scope); + let target_ast_id = assign + .target(&module) + .scoped_expression_id(db, method_scope); let inferred_ty = unpacked.expression_type(target_ast_id); union_of_inferred_types = union_of_inferred_types.add(inferred_ty); @@ -1725,8 +1739,10 @@ impl<'db> ClassLiteral<'db> { // // self.name = - let inferred_ty = - infer_expression_type(db, index.expression(assign.value())); + let inferred_ty = infer_expression_type( + db, + index.expression(assign.value(&module)), + ); union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } @@ -1740,8 +1756,9 @@ impl<'db> ClassLiteral<'db> { // for .., self.name, .. in : let unpacked = infer_unpack_types(db, unpack); - let target_ast_id = - for_stmt.target().scoped_expression_id(db, method_scope); + let target_ast_id = for_stmt + .target(&module) + .scoped_expression_id(db, method_scope); let inferred_ty = unpacked.expression_type(target_ast_id); union_of_inferred_types = union_of_inferred_types.add(inferred_ty); @@ -1753,7 +1770,7 @@ impl<'db> ClassLiteral<'db> { let iterable_ty = infer_expression_type( db, - index.expression(for_stmt.iterable()), + index.expression(for_stmt.iterable(&module)), ); // TODO: Potential diagnostics resulting from the iterable are currently not reported. let inferred_ty = iterable_ty.iterate(db); @@ -1770,8 +1787,9 @@ impl<'db> ClassLiteral<'db> { // with as .., self.name, ..: let unpacked = infer_unpack_types(db, unpack); - let target_ast_id = - with_item.target().scoped_expression_id(db, method_scope); + let target_ast_id = with_item + .target(&module) + .scoped_expression_id(db, method_scope); let inferred_ty = unpacked.expression_type(target_ast_id); union_of_inferred_types = union_of_inferred_types.add(inferred_ty); @@ -1783,7 +1801,7 @@ impl<'db> ClassLiteral<'db> { let context_ty = infer_expression_type( db, - index.expression(with_item.context_expr()), + index.expression(with_item.context_expr(&module)), ); let inferred_ty = context_ty.enter(db); @@ -1800,7 +1818,7 @@ impl<'db> ClassLiteral<'db> { let unpacked = infer_unpack_types(db, unpack); let target_ast_id = comprehension - .target() + .target(&module) .scoped_expression_id(db, unpack.target_scope(db)); let inferred_ty = unpacked.expression_type(target_ast_id); @@ -1813,7 +1831,7 @@ impl<'db> ClassLiteral<'db> { let iterable_ty = infer_expression_type( db, - index.expression(comprehension.iterable()), + index.expression(comprehension.iterable(&module)), ); // TODO: Potential diagnostics resulting from the iterable are currently not reported. let inferred_ty = iterable_ty.iterate(db); @@ -2003,8 +2021,8 @@ impl<'db> ClassLiteral<'db> { /// Returns a [`Span`] with the range of the class's header. /// /// See [`Self::header_range`] for more details. - pub(super) fn header_span(self, db: &'db dyn Db) -> Span { - Span::from(self.file(db)).with_range(self.header_range(db)) + pub(super) fn header_span(self, db: &'db dyn Db, module: &ParsedModuleRef) -> Span { + Span::from(self.file(db)).with_range(self.header_range(db, module)) } /// Returns the range of the class's "header": the class name @@ -2014,9 +2032,9 @@ impl<'db> ClassLiteral<'db> { /// class Foo(Bar, metaclass=Baz): ... /// ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` - pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange { + pub(super) fn header_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> TextRange { let class_scope = self.body_scope(db); - let class_node = class_scope.node(db).expect_class(); + let class_node = class_scope.node(db).expect_class(module); let class_name = &class_node.name; TextRange::new( class_name.start(), diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index 498a1b644a664a..f36b19873a7ca1 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -2,6 +2,7 @@ use std::fmt; use drop_bomb::DebugDropBomb; use ruff_db::diagnostic::{DiagnosticTag, SubDiagnostic}; +use ruff_db::parsed::ParsedModuleRef; use ruff_db::{ diagnostic::{Annotation, Diagnostic, DiagnosticId, IntoDiagnosticMessage, Severity, Span}, files::File, @@ -32,20 +33,22 @@ use crate::{ /// It's important that the context is explicitly consumed before dropping by calling /// [`InferContext::finish`] and the returned diagnostics must be stored /// on the current [`TypeInference`](super::infer::TypeInference) result. -pub(crate) struct InferContext<'db> { +pub(crate) struct InferContext<'db, 'ast> { db: &'db dyn Db, scope: ScopeId<'db>, file: File, + module: &'ast ParsedModuleRef, diagnostics: std::cell::RefCell, no_type_check: InNoTypeCheck, bomb: DebugDropBomb, } -impl<'db> InferContext<'db> { - pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self { +impl<'db, 'ast> InferContext<'db, 'ast> { + pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>, module: &'ast ParsedModuleRef) -> Self { Self { db, scope, + module, file: scope.file(db), diagnostics: std::cell::RefCell::new(TypeCheckDiagnostics::default()), no_type_check: InNoTypeCheck::default(), @@ -60,6 +63,11 @@ impl<'db> InferContext<'db> { self.file } + /// The module for which the types are inferred. + pub(crate) fn module(&self) -> &'ast ParsedModuleRef { + self.module + } + /// Create a span with the range of the given expression /// in the file being currently type checked. /// @@ -160,7 +168,7 @@ impl<'db> InferContext<'db> { // Inspect all ancestor function scopes by walking bottom up and infer the function's type. let mut function_scope_tys = index .ancestor_scopes(scope_id) - .filter_map(|(_, scope)| scope.node().as_function()) + .filter_map(|(_, scope)| scope.node().as_function(self.module())) .map(|node| binding_type(self.db, index.expect_single_definition(node))) .filter_map(Type::into_function_literal); @@ -187,7 +195,7 @@ impl<'db> InferContext<'db> { } } -impl fmt::Debug for InferContext<'_> { +impl fmt::Debug for InferContext<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("TyContext") .field("file", &self.file) @@ -221,7 +229,7 @@ pub(crate) enum InNoTypeCheck { /// will attach a message to the primary span on the diagnostic. pub(super) struct LintDiagnosticGuard<'db, 'ctx> { /// The typing context. - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, /// The diagnostic that we want to report. /// /// This is always `Some` until the `Drop` impl. @@ -363,7 +371,7 @@ impl Drop for LintDiagnosticGuard<'_, '_> { /// it is known that the diagnostic should not be reported. This can happen /// when the diagnostic is disabled or suppressed (among other reasons). pub(super) struct LintDiagnosticGuardBuilder<'db, 'ctx> { - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, id: DiagnosticId, severity: Severity, source: LintSource, @@ -372,7 +380,7 @@ pub(super) struct LintDiagnosticGuardBuilder<'db, 'ctx> { impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { fn new( - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, lint: &'static LintMetadata, range: TextRange, ) -> Option> { @@ -462,7 +470,7 @@ impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { /// if either is violated, then the `Drop` impl on `DiagnosticGuard` will /// panic. pub(super) struct DiagnosticGuard<'db, 'ctx> { - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, /// The diagnostic that we want to report. /// /// This is always `Some` until the `Drop` impl. @@ -550,14 +558,14 @@ impl Drop for DiagnosticGuard<'_, '_> { /// minimal amount of information with which to construct a diagnostic) before /// one can mutate the diagnostic. pub(super) struct DiagnosticGuardBuilder<'db, 'ctx> { - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, id: DiagnosticId, severity: Severity, } impl<'db, 'ctx> DiagnosticGuardBuilder<'db, 'ctx> { fn new( - ctx: &'ctx InferContext<'db>, + ctx: &'ctx InferContext<'db, 'ctx>, id: DiagnosticId, severity: Severity, ) -> Option> { diff --git a/crates/ty_python_semantic/src/types/definition.rs b/crates/ty_python_semantic/src/types/definition.rs index 466673b09f3ea5..e6b553d37445bc 100644 --- a/crates/ty_python_semantic/src/types/definition.rs +++ b/crates/ty_python_semantic/src/types/definition.rs @@ -1,6 +1,7 @@ use crate::semantic_index::definition::Definition; use crate::{Db, Module}; use ruff_db::files::FileRange; +use ruff_db::parsed::parsed_module; use ruff_db::source::source_text; use ruff_text_size::{TextLen, TextRange}; @@ -20,7 +21,10 @@ impl TypeDefinition<'_> { Self::Class(definition) | Self::Function(definition) | Self::TypeVar(definition) - | Self::TypeAlias(definition) => Some(definition.focus_range(db)), + | Self::TypeAlias(definition) => { + let module = parsed_module(db.upcast(), definition.file(db)).load(db.upcast()); + Some(definition.focus_range(db, &module)) + } } } @@ -34,7 +38,10 @@ impl TypeDefinition<'_> { Self::Class(definition) | Self::Function(definition) | Self::TypeVar(definition) - | Self::TypeAlias(definition) => Some(definition.full_range(db)), + | Self::TypeAlias(definition) => { + let module = parsed_module(db.upcast(), definition.file(db)).load(db.upcast()); + Some(definition.full_range(db, &module)) + } } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 8f62976c1e5ed0..7233c81b6e0c09 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1730,7 +1730,7 @@ pub(super) fn report_implicit_return_type( or `typing_extensions.Protocol` are considered protocol classes", ); sub_diagnostic.annotate( - Annotation::primary(class.header_span(db)).message(format_args!( + Annotation::primary(class.header_span(db, context.module())).message(format_args!( "`Protocol` not present in `{class}`'s immediate bases", class = class.name(db) )), @@ -1850,7 +1850,7 @@ pub(crate) fn report_bad_argument_to_get_protocol_members( class.name(db) ), ); - class_def_diagnostic.annotate(Annotation::primary(class.header_span(db))); + class_def_diagnostic.annotate(Annotation::primary(class.header_span(db, context.module()))); diagnostic.sub(class_def_diagnostic); diagnostic.info( @@ -1910,7 +1910,7 @@ pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol( ), ); class_def_diagnostic.annotate( - Annotation::primary(protocol.header_span(db)) + Annotation::primary(protocol.header_span(db, context.module())) .message(format_args!("`{class_name}` declared here")), ); diagnostic.sub(class_def_diagnostic); @@ -1941,7 +1941,7 @@ pub(crate) fn report_attempted_protocol_instantiation( format_args!("Protocol classes cannot be instantiated"), ); class_def_diagnostic.annotate( - Annotation::primary(protocol.header_span(db)) + Annotation::primary(protocol.header_span(db, context.module())) .message(format_args!("`{class_name}` declared as a protocol here")), ); diagnostic.sub(class_def_diagnostic); @@ -1955,7 +1955,9 @@ pub(crate) fn report_duplicate_bases( ) { let db = context.db(); - let Some(builder) = context.report_lint(&DUPLICATE_BASE, class.header_range(db)) else { + let Some(builder) = + context.report_lint(&DUPLICATE_BASE, class.header_range(db, context.module())) + else { return; }; @@ -2104,7 +2106,7 @@ fn report_unsupported_base( } fn report_invalid_base<'ctx, 'db>( - context: &'ctx InferContext<'db>, + context: &'ctx InferContext<'db, '_>, base_node: &ast::Expr, base_type: Type<'db>, class: ClassLiteral<'db>, diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 3b67e9c682fefb..c6164e6d929cad 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -54,6 +54,7 @@ use std::str::FromStr; use bitflags::bitflags; use ruff_db::diagnostic::Span; use ruff_db::files::{File, FileRange}; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -187,7 +188,12 @@ impl<'db> OverloadLiteral<'db> { self.has_known_decorator(db, FunctionDecorators::OVERLOAD) } - fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { + fn node<'ast>( + self, + db: &dyn Db, + file: File, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtFunctionDef { debug_assert_eq!( file, self.file(db), @@ -195,14 +201,18 @@ impl<'db> OverloadLiteral<'db> { the function is defined." ); - self.body_scope(db).node(db).expect_function() + self.body_scope(db).node(db).expect_function(module) } /// Returns the [`FileRange`] of the function's name. - pub(crate) fn focus_range(self, db: &dyn Db) -> FileRange { + pub(crate) fn focus_range(self, db: &dyn Db, module: &ParsedModuleRef) -> FileRange { FileRange::new( self.file(db), - self.body_scope(db).node(db).expect_function().name.range, + self.body_scope(db) + .node(db) + .expect_function(module) + .name + .range, ) } @@ -216,8 +226,9 @@ impl<'db> OverloadLiteral<'db> { /// over-invalidation. fn definition(self, db: &'db dyn Db) -> Definition<'db> { let body_scope = self.body_scope(db); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); let index = semantic_index(db, body_scope.file(db)); - index.expect_single_definition(body_scope.node(db).expect_function()) + index.expect_single_definition(body_scope.node(db).expect_function(&module)) } /// Returns the overload immediately before this one in the AST. Returns `None` if there is no @@ -226,11 +237,12 @@ impl<'db> OverloadLiteral<'db> { // The semantic model records a use for each function on the name node. This is used // here to get the previous function definition with the same name. let scope = self.definition(db).scope(db); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db)); let use_id = self .body_scope(db) .node(db) - .expect_function() + .expect_function(&module) .name .scoped_use_id(db, scope); @@ -266,7 +278,8 @@ impl<'db> OverloadLiteral<'db> { inherited_generic_context: Option>, ) -> Signature<'db> { let scope = self.body_scope(db); - let function_stmt_node = scope.node(db).expect_function(); + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + let function_stmt_node = scope.node(db).expect_function(&module); let definition = self.definition(db); let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| { let index = semantic_index(db, scope.file(db)); @@ -289,7 +302,8 @@ impl<'db> OverloadLiteral<'db> { let function_scope = self.body_scope(db); let span = Span::from(function_scope.file(db)); let node = function_scope.node(db); - let func_def = node.as_function()?; + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + let func_def = node.as_function(&module)?; let range = parameter_index .and_then(|parameter_index| { func_def @@ -308,7 +322,8 @@ impl<'db> OverloadLiteral<'db> { let function_scope = self.body_scope(db); let span = Span::from(function_scope.file(db)); let node = function_scope.node(db); - let func_def = node.as_function()?; + let module = parsed_module(db.upcast(), self.file(db)).load(db.upcast()); + let func_def = node.as_function(&module)?; 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); if let Some(return_type_range) = return_type_range { @@ -553,8 +568,13 @@ impl<'db> FunctionType<'db> { } /// Returns the AST node for this function. - pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { - self.literal(db).last_definition(db).node(db, file) + pub(crate) fn node<'ast>( + self, + db: &dyn Db, + file: File, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtFunctionDef { + self.literal(db).last_definition(db).node(db, file, module) } pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b0e6a19ddda30a..2c90d14cb2a7ca 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -36,7 +36,7 @@ use itertools::{Either, Itertools}; use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity}; use ruff_db::files::File; -use ruff_db::parsed::parsed_module; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast::visitor::{Visitor, walk_expr}; use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion}; use ruff_python_stdlib::builtins::version_builtin_was_added; @@ -136,11 +136,13 @@ pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Ty let file = scope.file(db); let _span = tracing::trace_span!("infer_scope_types", scope=?scope.as_id(), ?file).entered(); + let module = parsed_module(db.upcast(), file).load(db.upcast()); + // Using the index here is fine because the code below depends on the AST anyway. // The isolation of the query is by the return inferred types. let index = semantic_index(db, file); - TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index).finish() + TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index, &module).finish() } fn scope_cycle_recover<'db>( @@ -164,16 +166,17 @@ pub(crate) fn infer_definition_types<'db>( definition: Definition<'db>, ) -> TypeInference<'db> { let file = definition.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let _span = tracing::trace_span!( "infer_definition_types", - range = ?definition.kind(db).target_range(), + range = ?definition.kind(db).target_range(&module), ?file ) .entered(); let index = semantic_index(db, file); - TypeInferenceBuilder::new(db, InferenceRegion::Definition(definition), index).finish() + TypeInferenceBuilder::new(db, InferenceRegion::Definition(definition), index, &module).finish() } fn definition_cycle_recover<'db>( @@ -202,17 +205,18 @@ pub(crate) fn infer_deferred_types<'db>( definition: Definition<'db>, ) -> TypeInference<'db> { let file = definition.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let _span = tracing::trace_span!( "infer_deferred_types", definition = ?definition.as_id(), - range = ?definition.kind(db).target_range(), + range = ?definition.kind(db).target_range(&module), ?file ) .entered(); let index = semantic_index(db, file); - TypeInferenceBuilder::new(db, InferenceRegion::Deferred(definition), index).finish() + TypeInferenceBuilder::new(db, InferenceRegion::Deferred(definition), index, &module).finish() } fn deferred_cycle_recover<'db>( @@ -238,17 +242,18 @@ pub(crate) fn infer_expression_types<'db>( expression: Expression<'db>, ) -> TypeInference<'db> { let file = expression.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); let _span = tracing::trace_span!( "infer_expression_types", expression = ?expression.as_id(), - range = ?expression.node_ref(db).range(), + range = ?expression.node_ref(db, &module).range(), ?file ) .entered(); let index = semantic_index(db, file); - TypeInferenceBuilder::new(db, InferenceRegion::Expression(expression), index).finish() + TypeInferenceBuilder::new(db, InferenceRegion::Expression(expression), index, &module).finish() } fn expression_cycle_recover<'db>( @@ -275,10 +280,15 @@ fn expression_cycle_initial<'db>( pub(super) fn infer_same_file_expression_type<'db>( db: &'db dyn Db, expression: Expression<'db>, + parsed: &ParsedModuleRef, ) -> Type<'db> { let inference = infer_expression_types(db, expression); let scope = expression.scope(db); - inference.expression_type(expression.node_ref(db).scoped_expression_id(db, scope)) + inference.expression_type( + expression + .node_ref(db, parsed) + .scoped_expression_id(db, scope), + ) } /// Infers the type of an expression where the expression might come from another file. @@ -293,8 +303,11 @@ pub(crate) fn infer_expression_type<'db>( db: &'db dyn Db, expression: Expression<'db>, ) -> Type<'db> { + let file = expression.file(db); + let module = parsed_module(db.upcast(), file).load(db.upcast()); + // It's okay to call the "same file" version here because we're inside a salsa query. - infer_same_file_expression_type(db, expression) + infer_same_file_expression_type(db, expression, &module) } fn single_expression_cycle_recover<'db>( @@ -322,11 +335,12 @@ fn single_expression_cycle_initial<'db>( #[salsa::tracked(returns(ref), cycle_fn=unpack_cycle_recover, cycle_initial=unpack_cycle_initial)] pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> { let file = unpack.file(db); - let _span = - tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), ?file).entered(); + let module = parsed_module(db.upcast(), file).load(db.upcast()); + let _span = tracing::trace_span!("infer_unpack_types", range=?unpack.range(db, &module), ?file) + .entered(); - let mut unpacker = Unpacker::new(db, unpack.target_scope(db), unpack.value_scope(db)); - unpacker.unpack(unpack.target(db), unpack.value(db)); + let mut unpacker = Unpacker::new(db, unpack.target_scope(db), unpack.value_scope(db), &module); + unpacker.unpack(unpack.target(db, &module), unpack.value(db)); unpacker.finish() } @@ -356,11 +370,12 @@ pub(crate) fn nearest_enclosing_class<'db>( db: &'db dyn Db, semantic: &SemanticIndex<'db>, scope: ScopeId, + parsed: &ParsedModuleRef, ) -> Option> { semantic .ancestor_scopes(scope.file_scope_id(db)) .find_map(|(_, ancestor_scope)| { - let class = ancestor_scope.node().as_class()?; + let class = ancestor_scope.node().as_class(parsed)?; let definition = semantic.expect_single_definition(class); infer_definition_types(db, definition) .declaration_type(definition) @@ -569,8 +584,8 @@ enum DeclaredAndInferredType<'db> { /// Similarly, when we encounter a standalone-inferable expression (right-hand side of an /// assignment, type narrowing guard), we use the [`infer_expression_types()`] query to ensure we /// don't infer its types more than once. -pub(super) struct TypeInferenceBuilder<'db> { - context: InferContext<'db>, +pub(super) struct TypeInferenceBuilder<'db, 'ast> { + context: InferContext<'db, 'ast>, index: &'db SemanticIndex<'db>, region: InferenceRegion<'db>, @@ -617,7 +632,7 @@ pub(super) struct TypeInferenceBuilder<'db> { deferred_state: DeferredExpressionState, } -impl<'db> TypeInferenceBuilder<'db> { +impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { /// How big a string do we build before bailing? /// /// This is a fairly arbitrary number. It should be *far* more than enough @@ -629,11 +644,12 @@ impl<'db> TypeInferenceBuilder<'db> { db: &'db dyn Db, region: InferenceRegion<'db>, index: &'db SemanticIndex<'db>, + module: &'ast ParsedModuleRef, ) -> Self { let scope = region.scope(db); Self { - context: InferContext::new(db, scope), + context: InferContext::new(db, scope, module), index, region, return_types_and_ranges: vec![], @@ -659,6 +675,10 @@ impl<'db> TypeInferenceBuilder<'db> { self.context.file() } + fn module(&self) -> &'ast ParsedModuleRef { + self.context.module() + } + fn db(&self) -> &'db dyn Db { self.context.db() } @@ -756,35 +776,36 @@ impl<'db> TypeInferenceBuilder<'db> { let node = scope.node(self.db()); match node { NodeWithScopeKind::Module => { - let parsed = parsed_module(self.db().upcast(), self.file()); - self.infer_module(parsed.syntax()); + self.infer_module(self.module().syntax()); + } + NodeWithScopeKind::Function(function) => { + self.infer_function_body(function.node(self.module())); } - NodeWithScopeKind::Function(function) => self.infer_function_body(function.node()), - NodeWithScopeKind::Lambda(lambda) => self.infer_lambda_body(lambda.node()), - NodeWithScopeKind::Class(class) => self.infer_class_body(class.node()), + NodeWithScopeKind::Lambda(lambda) => self.infer_lambda_body(lambda.node(self.module())), + NodeWithScopeKind::Class(class) => self.infer_class_body(class.node(self.module())), NodeWithScopeKind::ClassTypeParameters(class) => { - self.infer_class_type_params(class.node()); + self.infer_class_type_params(class.node(self.module())); } NodeWithScopeKind::FunctionTypeParameters(function) => { - self.infer_function_type_params(function.node()); + self.infer_function_type_params(function.node(self.module())); } NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => { - self.infer_type_alias_type_params(type_alias.node()); + self.infer_type_alias_type_params(type_alias.node(self.module())); } NodeWithScopeKind::TypeAlias(type_alias) => { - self.infer_type_alias(type_alias.node()); + self.infer_type_alias(type_alias.node(self.module())); } NodeWithScopeKind::ListComprehension(comprehension) => { - self.infer_list_comprehension_expression_scope(comprehension.node()); + self.infer_list_comprehension_expression_scope(comprehension.node(self.module())); } NodeWithScopeKind::SetComprehension(comprehension) => { - self.infer_set_comprehension_expression_scope(comprehension.node()); + self.infer_set_comprehension_expression_scope(comprehension.node(self.module())); } NodeWithScopeKind::DictComprehension(comprehension) => { - self.infer_dict_comprehension_expression_scope(comprehension.node()); + self.infer_dict_comprehension_expression_scope(comprehension.node(self.module())); } NodeWithScopeKind::GeneratorExpression(generator) => { - self.infer_generator_expression_scope(generator.node()); + self.infer_generator_expression_scope(generator.node(self.module())); } } @@ -823,7 +844,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let DefinitionKind::Class(class) = definition.kind(self.db()) { ty.inner_type() .into_class_literal() - .map(|class_literal| (class_literal, class.node())) + .map(|class_literal| (class_literal, class.node(self.module()))) } else { None } @@ -1143,7 +1164,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Check that the overloaded function has at least two overloads if let [single_overload] = overloads.as_ref() { - let function_node = function.node(self.db(), self.file()); + let function_node = function.node(self.db(), self.file(), self.module()); if let Some(builder) = self .context .report_lint(&INVALID_OVERLOAD, &function_node.name) @@ -1154,7 +1175,7 @@ impl<'db> TypeInferenceBuilder<'db> { )); diagnostic.annotate( self.context - .secondary(single_overload.focus_range(self.db())) + .secondary(single_overload.focus_range(self.db(), self.module())) .message(format_args!("Only one overload defined here")), ); } @@ -1169,7 +1190,8 @@ impl<'db> TypeInferenceBuilder<'db> { if let NodeWithScopeKind::Class(class_node_ref) = scope { let class = binding_type( self.db(), - self.index.expect_single_definition(class_node_ref.node()), + self.index + .expect_single_definition(class_node_ref.node(self.module())), ) .expect_class_literal(); @@ -1187,7 +1209,7 @@ impl<'db> TypeInferenceBuilder<'db> { } if implementation_required { - let function_node = function.node(self.db(), self.file()); + let function_node = function.node(self.db(), self.file(), self.module()); if let Some(builder) = self .context .report_lint(&INVALID_OVERLOAD, &function_node.name) @@ -1222,7 +1244,7 @@ impl<'db> TypeInferenceBuilder<'db> { continue; } - let function_node = function.node(self.db(), self.file()); + let function_node = function.node(self.db(), self.file(), self.module()); if let Some(builder) = self .context .report_lint(&INVALID_OVERLOAD, &function_node.name) @@ -1235,7 +1257,7 @@ impl<'db> TypeInferenceBuilder<'db> { for function in decorator_missing { diagnostic.annotate( self.context - .secondary(function.focus_range(self.db())) + .secondary(function.focus_range(self.db(), self.module())) .message(format_args!("Missing here")), ); } @@ -1251,7 +1273,7 @@ impl<'db> TypeInferenceBuilder<'db> { if !overload.has_known_decorator(self.db(), decorator) { continue; } - let function_node = function.node(self.db(), self.file()); + let function_node = function.node(self.db(), self.file(), self.module()); let Some(builder) = self .context .report_lint(&INVALID_OVERLOAD, &function_node.name) @@ -1264,7 +1286,7 @@ impl<'db> TypeInferenceBuilder<'db> { )); diagnostic.annotate( self.context - .secondary(implementation.focus_range(self.db())) + .secondary(implementation.focus_range(self.db(), self.module())) .message(format_args!("Implementation defined here")), ); } @@ -1277,7 +1299,7 @@ impl<'db> TypeInferenceBuilder<'db> { if !overload.has_known_decorator(self.db(), decorator) { continue; } - let function_node = function.node(self.db(), self.file()); + let function_node = function.node(self.db(), self.file(), self.module()); let Some(builder) = self .context .report_lint(&INVALID_OVERLOAD, &function_node.name) @@ -1290,7 +1312,7 @@ impl<'db> TypeInferenceBuilder<'db> { )); diagnostic.annotate( self.context - .secondary(first_overload.focus_range(self.db())) + .secondary(first_overload.focus_range(self.db(), self.module())) .message(format_args!("First overload defined here")), ); } @@ -1302,24 +1324,34 @@ impl<'db> TypeInferenceBuilder<'db> { fn infer_region_definition(&mut self, definition: Definition<'db>) { match definition.kind(self.db()) { DefinitionKind::Function(function) => { - self.infer_function_definition(function.node(), definition); + self.infer_function_definition(function.node(self.module()), definition); + } + DefinitionKind::Class(class) => { + self.infer_class_definition(class.node(self.module()), definition); } - DefinitionKind::Class(class) => self.infer_class_definition(class.node(), definition), DefinitionKind::TypeAlias(type_alias) => { - self.infer_type_alias_definition(type_alias.node(), definition); + self.infer_type_alias_definition(type_alias.node(self.module()), definition); } DefinitionKind::Import(import) => { - self.infer_import_definition(import.import(), import.alias(), definition); + self.infer_import_definition( + import.import(self.module()), + import.alias(self.module()), + definition, + ); } DefinitionKind::ImportFrom(import_from) => { self.infer_import_from_definition( - import_from.import(), - import_from.alias(), + import_from.import(self.module()), + import_from.alias(self.module()), definition, ); } DefinitionKind::StarImport(import) => { - self.infer_import_from_definition(import.import(), import.alias(), definition); + self.infer_import_from_definition( + import.import(self.module()), + import.alias(self.module()), + definition, + ); } DefinitionKind::Assignment(assignment) => { self.infer_assignment_definition(assignment, definition); @@ -1328,32 +1360,47 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_annotated_assignment_definition(annotated_assignment, definition); } DefinitionKind::AugmentedAssignment(augmented_assignment) => { - self.infer_augment_assignment_definition(augmented_assignment.node(), definition); + self.infer_augment_assignment_definition( + augmented_assignment.node(self.module()), + definition, + ); } DefinitionKind::For(for_statement_definition) => { self.infer_for_statement_definition(for_statement_definition, definition); } DefinitionKind::NamedExpression(named_expression) => { - self.infer_named_expression_definition(named_expression.node(), definition); + self.infer_named_expression_definition( + named_expression.node(self.module()), + definition, + ); } DefinitionKind::Comprehension(comprehension) => { self.infer_comprehension_definition(comprehension, definition); } DefinitionKind::VariadicPositionalParameter(parameter) => { - self.infer_variadic_positional_parameter_definition(parameter, definition); + self.infer_variadic_positional_parameter_definition( + parameter.node(self.module()), + definition, + ); } DefinitionKind::VariadicKeywordParameter(parameter) => { - self.infer_variadic_keyword_parameter_definition(parameter, definition); + self.infer_variadic_keyword_parameter_definition( + parameter.node(self.module()), + definition, + ); } DefinitionKind::Parameter(parameter_with_default) => { - self.infer_parameter_definition(parameter_with_default, definition); + self.infer_parameter_definition( + parameter_with_default.node(self.module()), + definition, + ); } DefinitionKind::WithItem(with_item_definition) => { self.infer_with_item_definition(with_item_definition, definition); } DefinitionKind::MatchPattern(match_pattern) => { self.infer_match_pattern_definition( - match_pattern.pattern(), + match_pattern.pattern(self.module()), match_pattern.index(), definition, ); @@ -1362,13 +1409,13 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_except_handler_definition(except_handler_definition, definition); } DefinitionKind::TypeVar(node) => { - self.infer_typevar_definition(node, definition); + self.infer_typevar_definition(node.node(self.module()), definition); } DefinitionKind::ParamSpec(node) => { - self.infer_paramspec_definition(node, definition); + self.infer_paramspec_definition(node.node(self.module()), definition); } DefinitionKind::TypeVarTuple(node) => { - self.infer_typevartuple_definition(node, definition); + self.infer_typevartuple_definition(node.node(self.module()), definition); } } } @@ -1384,8 +1431,10 @@ impl<'db> TypeInferenceBuilder<'db> { // implementation to allow this "split" to happen. match definition.kind(self.db()) { - DefinitionKind::Function(function) => self.infer_function_deferred(function.node()), - DefinitionKind::Class(class) => self.infer_class_deferred(class.node()), + DefinitionKind::Function(function) => { + self.infer_function_deferred(function.node(self.module())); + } + DefinitionKind::Class(class) => self.infer_class_deferred(class.node(self.module())), _ => {} } } @@ -1393,10 +1442,10 @@ impl<'db> TypeInferenceBuilder<'db> { fn infer_region_expression(&mut self, expression: Expression<'db>) { match expression.kind(self.db()) { ExpressionKind::Normal => { - self.infer_expression_impl(expression.node_ref(self.db())); + self.infer_expression_impl(expression.node_ref(self.db(), self.module())); } ExpressionKind::TypeExpression => { - self.infer_type_expression(expression.node_ref(self.db())); + self.infer_type_expression(expression.node_ref(self.db(), self.module())); } } } @@ -1441,7 +1490,7 @@ impl<'db> TypeInferenceBuilder<'db> { debug_assert!( binding .kind(self.db()) - .category(self.context.in_stub()) + .category(self.context.in_stub(), self.module()) .is_binding() ); @@ -1555,7 +1604,7 @@ impl<'db> TypeInferenceBuilder<'db> { debug_assert!( declaration .kind(self.db()) - .category(self.context.in_stub()) + .category(self.context.in_stub(), self.module()) .is_declaration() ); let use_def = self.index.use_def_map(declaration.file_scope(self.db())); @@ -1601,13 +1650,13 @@ impl<'db> TypeInferenceBuilder<'db> { debug_assert!( definition .kind(self.db()) - .category(self.context.in_stub()) + .category(self.context.in_stub(), self.module()) .is_binding() ); debug_assert!( definition .kind(self.db()) - .category(self.context.in_stub()) + .category(self.context.in_stub(), self.module()) .is_declaration() ); @@ -1763,7 +1812,7 @@ impl<'db> TypeInferenceBuilder<'db> { _ => return None, }; - let class_stmt = class_scope.node().as_class()?; + 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).into_class_literal() } @@ -1784,17 +1833,21 @@ impl<'db> TypeInferenceBuilder<'db> { return false; }; - node_ref.decorator_list.iter().any(|decorator| { - let decorator_type = self.file_expression_type(&decorator.expression); + node_ref + .node(self.module()) + .decorator_list + .iter() + .any(|decorator| { + let decorator_type = self.file_expression_type(&decorator.expression); - match decorator_type { - Type::FunctionLiteral(function) => matches!( - function.known(self.db()), - Some(KnownFunction::Overload | KnownFunction::AbstractMethod) - ), - _ => false, - } - }) + match decorator_type { + Type::FunctionLiteral(function) => matches!( + function.known(self.db()), + Some(KnownFunction::Overload | KnownFunction::AbstractMethod) + ), + _ => false, + } + }) } fn infer_function_body(&mut self, function: &ast::StmtFunctionDef) { @@ -2558,8 +2611,8 @@ impl<'db> TypeInferenceBuilder<'db> { with_item: &WithItemDefinitionKind<'db>, definition: Definition<'db>, ) { - let context_expr = with_item.context_expr(); - let target = with_item.target(); + let context_expr = with_item.context_expr(self.module()); + let target = with_item.target(self.module()); let context_expr_ty = self.infer_standalone_expression(context_expr); @@ -2707,12 +2760,12 @@ impl<'db> TypeInferenceBuilder<'db> { definition: Definition<'db>, ) { let symbol_ty = self.infer_exception( - except_handler_definition.handled_exceptions(), + except_handler_definition.handled_exceptions(self.module()), except_handler_definition.is_star(), ); self.add_binding( - except_handler_definition.node().into(), + except_handler_definition.node(self.module()).into(), definition, symbol_ty, ); @@ -3001,7 +3054,7 @@ impl<'db> TypeInferenceBuilder<'db> { /// `target`. fn infer_target(&mut self, target: &ast::Expr, value: &ast::Expr, infer_value_expr: F) where - F: Fn(&mut TypeInferenceBuilder<'db>, &ast::Expr) -> Type<'db>, + F: Fn(&mut TypeInferenceBuilder<'db, '_>, &ast::Expr) -> Type<'db>, { let assigned_ty = match target { ast::Expr::Name(_) => None, @@ -3542,8 +3595,8 @@ impl<'db> TypeInferenceBuilder<'db> { assignment: &AssignmentDefinitionKind<'db>, definition: Definition<'db>, ) { - let value = assignment.value(); - let target = assignment.target(); + let value = assignment.value(self.module()); + let target = assignment.target(self.module()); let value_ty = self.infer_standalone_expression(value); @@ -3625,9 +3678,9 @@ impl<'db> TypeInferenceBuilder<'db> { assignment: &'db AnnotatedAssignmentDefinitionKind, definition: Definition<'db>, ) { - let annotation = assignment.annotation(); - let target = assignment.target(); - let value = assignment.value(); + let annotation = assignment.annotation(self.module()); + let target = assignment.target(self.module()); + let value = assignment.value(self.module()); let mut declared_ty = self.infer_annotation_expression( annotation, @@ -3858,8 +3911,8 @@ impl<'db> TypeInferenceBuilder<'db> { for_stmt: &ForStmtDefinitionKind<'db>, definition: Definition<'db>, ) { - let iterable = for_stmt.iterable(); - let target = for_stmt.target(); + let iterable = for_stmt.iterable(self.module()); + let target = for_stmt.target(self.module()); let iterable_type = self.infer_standalone_expression(iterable); @@ -3967,7 +4020,7 @@ impl<'db> TypeInferenceBuilder<'db> { fn infer_import_definition( &mut self, node: &ast::StmtImport, - alias: &'db ast::Alias, + alias: &ast::Alias, definition: Definition<'db>, ) { let ast::Alias { @@ -4146,7 +4199,7 @@ impl<'db> TypeInferenceBuilder<'db> { fn infer_import_from_definition( &mut self, - import_from: &'db ast::StmtImportFrom, + import_from: &ast::StmtImportFrom, alias: &ast::Alias, definition: Definition<'db>, ) { @@ -4804,7 +4857,11 @@ impl<'db> TypeInferenceBuilder<'db> { // but only if the target is a name. We should report a diagnostic here if the target isn't a name: // `[... for a.x in not_iterable] if is_first { - infer_same_file_expression_type(builder.db(), builder.index.expression(iter_expr)) + infer_same_file_expression_type( + builder.db(), + builder.index.expression(iter_expr), + builder.module(), + ) } else { builder.infer_standalone_expression(iter_expr) } @@ -4820,8 +4877,8 @@ impl<'db> TypeInferenceBuilder<'db> { comprehension: &ComprehensionDefinitionKind<'db>, definition: Definition<'db>, ) { - let iterable = comprehension.iterable(); - let target = comprehension.target(); + let iterable = comprehension.iterable(self.module()); + let target = comprehension.target(self.module()); let expression = self.index.expression(iterable); let result = infer_expression_types(self.db(), expression); @@ -5009,8 +5066,10 @@ impl<'db> TypeInferenceBuilder<'db> { /// Returns `None` if the scope is not function-like, or has no parameters. fn first_param_type_in_scope(&self, scope: ScopeId) -> Option> { let first_param = match scope.node(self.db()) { - NodeWithScopeKind::Function(f) => f.parameters.iter().next(), - NodeWithScopeKind::Lambda(l) => l.parameters.as_ref()?.iter().next(), + NodeWithScopeKind::Function(f) => f.node(self.module()).parameters.iter().next(), + NodeWithScopeKind::Lambda(l) => { + l.node(self.module()).parameters.as_ref()?.iter().next() + } _ => None, }?; @@ -5371,6 +5430,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.db(), self.index, scope, + self.module(), ) else { overload.set_return_type(Type::unknown()); BoundSuperError::UnavailableImplicitArguments @@ -5435,7 +5495,11 @@ impl<'db> TypeInferenceBuilder<'db> { let Some(target) = assigned_to.as_ref().and_then(|assigned_to| { - match assigned_to.node().targets.as_slice() { + match assigned_to + .node(self.module()) + .targets + .as_slice() + { [ast::Expr::Name(target)] => Some(target), _ => None, } @@ -5605,7 +5669,11 @@ impl<'db> TypeInferenceBuilder<'db> { let containing_assignment = assigned_to.as_ref().and_then(|assigned_to| { - match assigned_to.node().targets.as_slice() { + match assigned_to + .node(self.module()) + .targets + .as_slice() + { [ast::Expr::Name(target)] => Some( self.index.expect_single_definition(target), ), @@ -8125,7 +8193,7 @@ impl<'db> TypeInferenceBuilder<'db> { } /// Annotation expressions. -impl<'db> TypeInferenceBuilder<'db> { +impl<'db> TypeInferenceBuilder<'db, '_> { /// Infer the type of an annotation expression with the given [`DeferredExpressionState`]. fn infer_annotation_expression( &mut self, @@ -8314,7 +8382,7 @@ impl<'db> TypeInferenceBuilder<'db> { } /// Type expressions -impl<'db> TypeInferenceBuilder<'db> { +impl<'db> TypeInferenceBuilder<'db, '_> { /// Infer the type of a type expression. fn infer_type_expression(&mut self, expression: &ast::Expr) -> Type<'db> { let ty = self.infer_type_expression_no_store(expression); @@ -9340,10 +9408,10 @@ impl<'db> TypeInferenceBuilder<'db> { } } - fn infer_literal_parameter_type<'ast>( + fn infer_literal_parameter_type<'param>( &mut self, - parameters: &'ast ast::Expr, - ) -> Result, Vec<&'ast ast::Expr>> { + parameters: &'param ast::Expr, + ) -> Result, Vec<&'param ast::Expr>> { Ok(match parameters { // TODO handle type aliases ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { @@ -9723,6 +9791,7 @@ mod tests { symbol_name: &str, ) -> Place<'db> { let file = system_path_to_file(db, file_name).expect("file to exist"); + let module = parsed_module(db, file).load(db); let index = semantic_index(db, file); let mut file_scope_id = FileScopeId::global(); let mut scope = file_scope_id.to_scope_id(db, file); @@ -9733,7 +9802,7 @@ mod tests { .unwrap_or_else(|| panic!("scope of {expected_scope_name}")) .0; scope = file_scope_id.to_scope_id(db, file); - assert_eq!(scope.name(db), *expected_scope_name); + assert_eq!(scope.name(db, &module), *expected_scope_name); } symbol(db, scope, symbol_name).place @@ -10087,7 +10156,7 @@ mod tests { fn dependency_implicit_instance_attribute() -> anyhow::Result<()> { fn x_rhs_expression(db: &TestDb) -> Expression<'_> { let file_main = system_path_to_file(db, "/src/main.py").unwrap(); - let ast = parsed_module(db, file_main); + let ast = parsed_module(db, file_main).load(db); // Get the second statement in `main.py` (x = …) and extract the expression // node on the right-hand side: let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value; @@ -10170,7 +10239,7 @@ mod tests { fn dependency_own_instance_member() -> anyhow::Result<()> { fn x_rhs_expression(db: &TestDb) -> Expression<'_> { let file_main = system_path_to_file(db, "/src/main.py").unwrap(); - let ast = parsed_module(db, file_main); + let ast = parsed_module(db, file_main).load(db); // Get the second statement in `main.py` (x = …) and extract the expression // node on the right-hand side: let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value; diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index e6cc62c383758f..a0e4ce3624cc17 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -13,6 +13,7 @@ use crate::types::{ infer_expression_types, }; +use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_stdlib::identifiers::is_identifier; use itertools::Itertools; @@ -73,7 +74,8 @@ fn all_narrowing_constraints_for_pattern<'db>( db: &'db dyn Db, pattern: PatternPredicate<'db>, ) -> Option> { - NarrowingConstraintsBuilder::new(db, PredicateNode::Pattern(pattern), true).finish() + let module = parsed_module(db.upcast(), pattern.file(db)).load(db.upcast()); + NarrowingConstraintsBuilder::new(db, &module, PredicateNode::Pattern(pattern), true).finish() } #[salsa::tracked( @@ -85,7 +87,9 @@ fn all_narrowing_constraints_for_expression<'db>( db: &'db dyn Db, expression: Expression<'db>, ) -> Option> { - NarrowingConstraintsBuilder::new(db, PredicateNode::Expression(expression), true).finish() + let module = parsed_module(db.upcast(), expression.file(db)).load(db.upcast()); + NarrowingConstraintsBuilder::new(db, &module, PredicateNode::Expression(expression), true) + .finish() } #[salsa::tracked( @@ -97,7 +101,9 @@ fn all_negative_narrowing_constraints_for_expression<'db>( db: &'db dyn Db, expression: Expression<'db>, ) -> Option> { - NarrowingConstraintsBuilder::new(db, PredicateNode::Expression(expression), false).finish() + let module = parsed_module(db.upcast(), expression.file(db)).load(db.upcast()); + NarrowingConstraintsBuilder::new(db, &module, PredicateNode::Expression(expression), false) + .finish() } #[salsa::tracked(returns(as_ref))] @@ -105,7 +111,8 @@ fn all_negative_narrowing_constraints_for_pattern<'db>( db: &'db dyn Db, pattern: PatternPredicate<'db>, ) -> Option> { - NarrowingConstraintsBuilder::new(db, PredicateNode::Pattern(pattern), false).finish() + let module = parsed_module(db.upcast(), pattern.file(db)).load(db.upcast()); + NarrowingConstraintsBuilder::new(db, &module, PredicateNode::Pattern(pattern), false).finish() } #[expect(clippy::ref_option)] @@ -251,16 +258,23 @@ fn expr_name(expr: &ast::Expr) -> Option<&ast::name::Name> { } } -struct NarrowingConstraintsBuilder<'db> { +struct NarrowingConstraintsBuilder<'db, 'ast> { db: &'db dyn Db, + module: &'ast ParsedModuleRef, predicate: PredicateNode<'db>, is_positive: bool, } -impl<'db> NarrowingConstraintsBuilder<'db> { - fn new(db: &'db dyn Db, predicate: PredicateNode<'db>, is_positive: bool) -> Self { +impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { + fn new( + db: &'db dyn Db, + module: &'ast ParsedModuleRef, + predicate: PredicateNode<'db>, + is_positive: bool, + ) -> Self { Self { db, + module, predicate, is_positive, } @@ -289,7 +303,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> { expression: Expression<'db>, is_positive: bool, ) -> Option> { - let expression_node = expression.node_ref(self.db); + let expression_node = expression.node_ref(self.db, self.module); self.evaluate_expression_node_predicate(expression_node, expression, is_positive) } @@ -775,7 +789,8 @@ impl<'db> NarrowingConstraintsBuilder<'db> { subject: Expression<'db>, singleton: ast::Singleton, ) -> Option> { - let symbol = self.expect_expr_name_symbol(&subject.node_ref(self.db).as_name_expr()?.id); + let symbol = self + .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); let ty = match singleton { ast::Singleton::None => Type::none(self.db), @@ -790,8 +805,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> { subject: Expression<'db>, cls: Expression<'db>, ) -> Option> { - let symbol = self.expect_expr_name_symbol(&subject.node_ref(self.db).as_name_expr()?.id); - let ty = infer_same_file_expression_type(self.db, cls).to_instance(self.db)?; + let symbol = self + .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); + let ty = infer_same_file_expression_type(self.db, cls, self.module).to_instance(self.db)?; Some(NarrowingConstraints::from_iter([(symbol, ty)])) } @@ -801,8 +817,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> { subject: Expression<'db>, value: Expression<'db>, ) -> Option> { - let symbol = self.expect_expr_name_symbol(&subject.node_ref(self.db).as_name_expr()?.id); - let ty = infer_same_file_expression_type(self.db, value); + let symbol = self + .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); + let ty = infer_same_file_expression_type(self.db, value, self.module); Some(NarrowingConstraints::from_iter([(symbol, ty)])) } diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index f06ad7c5171894..af40005580b355 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::cmp::Ordering; +use ruff_db::parsed::ParsedModuleRef; use rustc_hash::FxHashMap; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -16,21 +17,22 @@ use super::diagnostic::INVALID_ASSIGNMENT; use super::{KnownClass, TupleType, UnionType}; /// Unpacks the value expression type to their respective targets. -pub(crate) struct Unpacker<'db> { - context: InferContext<'db>, +pub(crate) struct Unpacker<'db, 'ast> { + context: InferContext<'db, 'ast>, target_scope: ScopeId<'db>, value_scope: ScopeId<'db>, targets: FxHashMap>, } -impl<'db> Unpacker<'db> { +impl<'db, 'ast> Unpacker<'db, 'ast> { pub(crate) fn new( db: &'db dyn Db, target_scope: ScopeId<'db>, value_scope: ScopeId<'db>, + module: &'ast ParsedModuleRef, ) -> Self { Self { - context: InferContext::new(db, target_scope), + context: InferContext::new(db, target_scope, module), targets: FxHashMap::default(), target_scope, value_scope, @@ -41,6 +43,10 @@ impl<'db> Unpacker<'db> { self.context.db() } + fn module(&self) -> &'ast ParsedModuleRef { + self.context.module() + } + /// Unpack the value to the target expression. pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { debug_assert!( @@ -48,15 +54,16 @@ impl<'db> Unpacker<'db> { "Unpacking target must be a list or tuple expression" ); - let value_type = infer_expression_types(self.db(), value.expression()) - .expression_type(value.scoped_expression_id(self.db(), self.value_scope)); + let value_type = infer_expression_types(self.db(), value.expression()).expression_type( + value.scoped_expression_id(self.db(), self.value_scope, self.module()), + ); let value_type = match value.kind() { UnpackKind::Assign => { if self.context.in_stub() && value .expression() - .node_ref(self.db()) + .node_ref(self.db(), self.module()) .is_ellipsis_literal_expr() { Type::unknown() @@ -65,22 +72,34 @@ impl<'db> Unpacker<'db> { } } UnpackKind::Iterable => value_type.try_iterate(self.db()).unwrap_or_else(|err| { - err.report_diagnostic(&self.context, value_type, value.as_any_node_ref(self.db())); + err.report_diagnostic( + &self.context, + value_type, + value.as_any_node_ref(self.db(), self.module()), + ); err.fallback_element_type(self.db()) }), UnpackKind::ContextManager => value_type.try_enter(self.db()).unwrap_or_else(|err| { - err.report_diagnostic(&self.context, value_type, value.as_any_node_ref(self.db())); + err.report_diagnostic( + &self.context, + value_type, + value.as_any_node_ref(self.db(), self.module()), + ); err.fallback_enter_type(self.db()) }), }; - self.unpack_inner(target, value.as_any_node_ref(self.db()), value_type); + self.unpack_inner( + target, + value.as_any_node_ref(self.db(), self.module()), + value_type, + ); } fn unpack_inner( &mut self, target: &ast::Expr, - value_expr: AnyNodeRef<'db>, + value_expr: AnyNodeRef<'_>, value_ty: Type<'db>, ) { match target { diff --git a/crates/ty_python_semantic/src/unpack.rs b/crates/ty_python_semantic/src/unpack.rs index 0e34dbe7654c38..3dda4dd2f2336f 100644 --- a/crates/ty_python_semantic/src/unpack.rs +++ b/crates/ty_python_semantic/src/unpack.rs @@ -1,4 +1,5 @@ use ruff_db::files::File; +use ruff_db::parsed::ParsedModuleRef; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; @@ -37,9 +38,9 @@ pub(crate) struct Unpack<'db> { /// The target expression that is being unpacked. For example, in `(a, b) = (1, 2)`, the target /// expression is `(a, b)`. #[no_eq] - #[returns(deref)] #[tracked] - pub(crate) target: AstNodeRef, + #[returns(ref)] + pub(crate) _target: AstNodeRef, /// The ingredient representing the value expression of the unpacking. For example, in /// `(a, b) = (1, 2)`, the value expression is `(1, 2)`. @@ -49,6 +50,14 @@ pub(crate) struct Unpack<'db> { } impl<'db> Unpack<'db> { + pub(crate) fn target<'ast>( + self, + db: &'db dyn Db, + parsed: &'ast ParsedModuleRef, + ) -> &'ast ast::Expr { + self._target(db).node(parsed) + } + /// Returns the scope in which the unpack value expression belongs. /// /// The scope in which the target and value expression belongs to are usually the same @@ -65,8 +74,8 @@ impl<'db> Unpack<'db> { } /// Returns the range of the unpack target expression. - pub(crate) fn range(self, db: &'db dyn Db) -> TextRange { - self.target(db).range() + pub(crate) fn range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> TextRange { + self.target(db, module).range() } } @@ -94,15 +103,20 @@ impl<'db> UnpackValue<'db> { self, db: &'db dyn Db, scope: ScopeId<'db>, + module: &ParsedModuleRef, ) -> ScopedExpressionId { self.expression() - .node_ref(db) + .node_ref(db, module) .scoped_expression_id(db, scope) } /// Returns the expression as an [`AnyNodeRef`]. - pub(crate) fn as_any_node_ref(self, db: &'db dyn Db) -> AnyNodeRef<'db> { - self.expression().node_ref(db).into() + pub(crate) fn as_any_node_ref<'ast>( + self, + db: &'db dyn Db, + module: &'ast ParsedModuleRef, + ) -> AnyNodeRef<'ast> { + self.expression().node_ref(db, module).into() } pub(crate) const fn kind(self) -> UnpackKind { diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index 38ff0494d0856d..fd567dc60a37b0 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -57,7 +57,7 @@ impl InlineFileAssertions { pub(crate) fn from_file(db: &Db, file: File) -> Self { let source = source_text(db, file); let lines = line_index(db, file); - let parsed = parsed_module(db, file); + let parsed = parsed_module(db, file).load(db); let comment_ranges = CommentRanges::from(parsed.tokens()); Self { comment_ranges, diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index ba247811063c13..4749f477bdbabc 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -294,7 +294,7 @@ fn run_test( let failures: Failures = test_files .into_iter() .filter_map(|test_file| { - let parsed = parsed_module(db, test_file.file); + let parsed = parsed_module(db, test_file.file).load(db); let mut diagnostics: Vec = parsed .errors() diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 20dde86bc66b09..e20ab2a803d900 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -201,7 +201,7 @@ impl Workspace { /// Returns the parsed AST for `path` pub fn parsed(&self, file_id: &FileHandle) -> Result { - let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file); + let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file).load(&self.db); Ok(format!("{:#?}", parsed.syntax())) } @@ -212,7 +212,7 @@ impl Workspace { /// Returns the token stream for `path` serialized as a string. pub fn tokens(&self, file_id: &FileHandle) -> Result { - let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file); + let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file).load(&self.db); Ok(format!("{:#?}", parsed.tokens())) } From 33468cc8cc74c2bbbbe8602db9f31dd7d39e0723 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:16:29 +0200 Subject: [PATCH 344/487] [`pyupgrade`] Apply `UP035` only on py313+ for `get_type_hints()` (#18476) --- .../test/fixtures/pyupgrade/UP035.py | 2 ++ .../pyupgrade/rules/deprecated_import.rs | 5 +++- ...er__rules__pyupgrade__tests__UP035.py.snap | 26 +++++++++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py index 50e7cbc968916b..b9c3892a9b3819 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py @@ -110,6 +110,8 @@ # UP035 on py313+ only from typing_extensions import deprecated +# UP035 on py313+ only +from typing_extensions import get_type_hints # https://github.com/astral-sh/ruff/issues/15780 from typing_extensions import is_typeddict diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 765527b6d77991..022db3909737ce 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -271,7 +271,7 @@ const TYPING_TO_RE_39: &[&str] = &["Match", "Pattern"]; const TYPING_RE_TO_RE_39: &[&str] = &["Match", "Pattern"]; // Members of `typing_extensions` that were moved to `typing`. -const TYPING_EXTENSIONS_TO_TYPING_39: &[&str] = &["Annotated", "get_type_hints"]; +const TYPING_EXTENSIONS_TO_TYPING_39: &[&str] = &["Annotated"]; // Members of `typing` that were moved _and_ renamed (and thus cannot be // automatically fixed). @@ -373,6 +373,9 @@ const TYPING_EXTENSIONS_TO_TYPING_313: &[&str] = &[ "NoDefault", "ReadOnly", "TypeIs", + // Introduced in Python 3.5, + // but typing_extensions backports features from py313: + "get_type_hints", // Introduced in Python 3.6, // but typing_extensions backports features from py313: "ContextManager", diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap index fb3b19b94aef72..95c0bee0463652 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap @@ -1179,6 +1179,8 @@ UP035.py:111:1: UP035 [*] Import from `warnings` instead: `deprecated` 110 | # UP035 on py313+ only 111 | from typing_extensions import deprecated | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035 +112 | +113 | # UP035 on py313+ only | = help: Import from `warnings` @@ -1189,5 +1191,25 @@ UP035.py:111:1: UP035 [*] Import from `warnings` instead: `deprecated` 111 |-from typing_extensions import deprecated 111 |+from warnings import deprecated 112 112 | -113 113 | -114 114 | # https://github.com/astral-sh/ruff/issues/15780 +113 113 | # UP035 on py313+ only +114 114 | from typing_extensions import get_type_hints + +UP035.py:114:1: UP035 [*] Import from `typing` instead: `get_type_hints` + | +113 | # UP035 on py313+ only +114 | from typing_extensions import get_type_hints + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035 +115 | +116 | # https://github.com/astral-sh/ruff/issues/15780 + | + = help: Import from `typing` + +ℹ Safe fix +111 111 | from typing_extensions import deprecated +112 112 | +113 113 | # UP035 on py313+ only +114 |-from typing_extensions import get_type_hints + 114 |+from typing import get_type_hints +115 115 | +116 116 | # https://github.com/astral-sh/ruff/issues/15780 +117 117 | from typing_extensions import is_typeddict From ce216c79cc853181694439a5f6175bd25fd0d56d Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:48:29 -0400 Subject: [PATCH 345/487] Remove `Message::to_rule` (#18447) ## Summary As the title says, this PR removes the `Message::to_rule` method by replacing related uses of `Rule` with `NoqaCode` (or the rule's name in the case of the cache). Where it seemed a `Rule` was really needed, we convert back to the `Rule` by parsing either the rule name (with `str::parse`) or the `NoqaCode` (with `Rule::from_code`). I thought this was kind of like cheating and that it might not resolve this part of Micha's [comment](https://github.com/astral-sh/ruff/pull/18391#issuecomment-2933764275): > because we can't add Rule to Diagnostic or **have it anywhere in our shared rendering logic** but after looking again, the only remaining `Rule` conversion in rendering code is for the SARIF output format. The other two non-test `Rule` conversions are for caching and writing a fix summary, which I don't think fall into the shared rendering logic. That leaves the SARIF format as the only real problem, but maybe we can delay that for now. The motivation here is that we won't be able to store a `Rule` on the new `Diagnostic` type, but we should be able to store a `NoqaCode`, likely as a string. ## Test Plan Existing tests ## [Benchmarks](https://codspeed.io/astral-sh/ruff/branches/brent%2Fremove-to-rule) Almost no perf regression, only -1% on `linter/default-rules[large/dataset.py]`. --------- Co-authored-by: Micha Reiser --- crates/ruff/src/cache.rs | 5 +- crates/ruff/src/commands/rule.rs | 4 +- crates/ruff/src/diagnostics.rs | 12 ++-- crates/ruff/src/printer.rs | 18 ++--- crates/ruff_dev/src/generate_docs.rs | 4 +- crates/ruff_dev/src/generate_rules_table.rs | 2 +- crates/ruff_linter/src/codes.rs | 4 +- crates/ruff_linter/src/fix/mod.rs | 62 +++++++++------- crates/ruff_linter/src/linter.rs | 71 +++++++++++++++---- crates/ruff_linter/src/message/azure.rs | 2 +- crates/ruff_linter/src/message/github.rs | 4 +- crates/ruff_linter/src/message/gitlab.rs | 2 +- crates/ruff_linter/src/message/json.rs | 4 +- crates/ruff_linter/src/message/junit.rs | 2 +- crates/ruff_linter/src/message/mod.rs | 28 +++----- crates/ruff_linter/src/message/pylint.rs | 2 +- crates/ruff_linter/src/message/rdjson.rs | 4 +- crates/ruff_linter/src/message/sarif.rs | 28 +++++--- crates/ruff_linter/src/message/text.rs | 6 +- crates/ruff_linter/src/noqa.rs | 32 ++++----- crates/ruff_linter/src/registry.rs | 24 +++++-- crates/ruff_linter/src/registry/rule_set.rs | 3 +- crates/ruff_linter/src/rule_selector.rs | 3 +- crates/ruff_linter/src/rules/fastapi/mod.rs | 5 +- .../ruff_linter/src/rules/flake8_fixme/mod.rs | 2 +- .../src/rules/flake8_gettext/mod.rs | 2 +- .../ruff_linter/src/rules/flake8_raise/mod.rs | 3 +- .../ruff_linter/src/rules/flake8_self/mod.rs | 3 +- .../ruff_linter/src/rules/flake8_todos/mod.rs | 3 +- .../src/rules/flake8_type_checking/mod.rs | 25 +++---- crates/ruff_linter/src/rules/numpy/mod.rs | 3 +- crates/ruff_linter/src/rules/pydoclint/mod.rs | 9 ++- crates/ruff_linter/src/rules/pyflakes/mod.rs | 4 +- .../ruff_linter/src/rules/tryceratops/mod.rs | 3 +- crates/ruff_linter/src/test.rs | 6 +- crates/ruff_macros/src/map_codes.rs | 13 ++-- crates/ruff_server/src/lint.rs | 2 +- .../src/server/api/requests/hover.rs | 2 +- crates/ruff_wasm/src/lib.rs | 2 +- crates/ruff_workspace/src/configuration.rs | 2 +- 40 files changed, 232 insertions(+), 183 deletions(-) diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 4c0f11392f96ba..69530ea9531c61 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -439,7 +439,10 @@ impl LintCacheData { let messages = messages .iter() - .filter_map(|msg| msg.to_rule().map(|rule| (rule, msg))) + // Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so + // this also serves to filter them out, but we shouldn't be caching files with syntax + // errors anyway. + .filter_map(|msg| Some((msg.name().parse().ok()?, msg))) .map(|(rule, msg)| { // Make sure that all message use the same source file. assert_eq!( diff --git a/crates/ruff/src/commands/rule.rs b/crates/ruff/src/commands/rule.rs index 45b071d2ea1b76..adc761b3e5160a 100644 --- a/crates/ruff/src/commands/rule.rs +++ b/crates/ruff/src/commands/rule.rs @@ -30,7 +30,7 @@ impl<'a> Explanation<'a> { let (linter, _) = Linter::parse_code(&code).unwrap(); let fix = rule.fixable().to_string(); Self { - name: rule.as_ref(), + name: rule.name().as_str(), code, linter: linter.name(), summary: rule.message_formats()[0], @@ -44,7 +44,7 @@ impl<'a> Explanation<'a> { fn format_rule_text(rule: Rule) -> String { let mut output = String::new(); - let _ = write!(&mut output, "# {} ({})", rule.as_ref(), rule.noqa_code()); + let _ = write!(&mut output, "# {} ({})", rule.name(), rule.noqa_code()); output.push('\n'); output.push('\n'); diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index d562c009b1ffd1..dff416bc552fee 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -165,9 +165,9 @@ impl AddAssign for FixMap { continue; } let fixed_in_file = self.0.entry(filename).or_default(); - for (rule, count) in fixed { + for (rule, name, count) in fixed.iter() { if count > 0 { - *fixed_in_file.entry(rule).or_default() += count; + *fixed_in_file.entry(rule).or_default(name) += count; } } } @@ -305,7 +305,7 @@ pub(crate) fn lint_path( ParseSource::None, ); let transformed = source_kind; - let fixed = FxHashMap::default(); + let fixed = FixTable::default(); (result, transformed, fixed) } } else { @@ -319,7 +319,7 @@ pub(crate) fn lint_path( ParseSource::None, ); let transformed = source_kind; - let fixed = FxHashMap::default(); + let fixed = FixTable::default(); (result, transformed, fixed) }; @@ -473,7 +473,7 @@ pub(crate) fn lint_stdin( } let transformed = source_kind; - let fixed = FxHashMap::default(); + let fixed = FixTable::default(); (result, transformed, fixed) } } else { @@ -487,7 +487,7 @@ pub(crate) fn lint_stdin( ParseSource::None, ); let transformed = source_kind; - let fixed = FxHashMap::default(); + let fixed = FixTable::default(); (result, transformed, fixed) }; diff --git a/crates/ruff/src/printer.rs b/crates/ruff/src/printer.rs index 2f72e2bcd592f4..38530d18f4354b 100644 --- a/crates/ruff/src/printer.rs +++ b/crates/ruff/src/printer.rs @@ -7,6 +7,7 @@ use bitflags::bitflags; use colored::Colorize; use itertools::{Itertools, iterate}; use ruff_linter::codes::NoqaCode; +use ruff_linter::linter::FixTable; use serde::Serialize; use ruff_linter::fs::relativize_path; @@ -80,7 +81,7 @@ impl Printer { let fixed = diagnostics .fixed .values() - .flat_map(std::collections::HashMap::values) + .flat_map(FixTable::counts) .sum::(); if self.flags.intersects(Flags::SHOW_VIOLATIONS) { @@ -302,7 +303,7 @@ impl Printer { let statistics: Vec = diagnostics .messages .iter() - .map(|message| (message.to_noqa_code(), message)) + .map(|message| (message.noqa_code(), message)) .sorted_by_key(|(code, message)| (*code, message.fixable())) .fold( vec![], @@ -472,13 +473,13 @@ fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> { let total = fixed .values() - .map(|table| table.values().sum::()) + .map(|table| table.counts().sum::()) .sum::(); assert!(total > 0); let num_digits = num_digits( - *fixed + fixed .values() - .filter_map(|table| table.values().max()) + .filter_map(|table| table.counts().max()) .max() .unwrap(), ); @@ -498,12 +499,11 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> { relativize_path(filename).bold(), ":".cyan() )?; - for (rule, count) in table.iter().sorted_by_key(|(.., count)| Reverse(*count)) { + for (code, name, count) in table.iter().sorted_by_key(|(.., count)| Reverse(*count)) { writeln!( writer, - " {count:>num_digits$} × {} ({})", - rule.noqa_code().to_string().red().bold(), - rule.as_ref(), + " {count:>num_digits$} × {code} ({name})", + code = code.to_string().red().bold(), )?; } } diff --git a/crates/ruff_dev/src/generate_docs.rs b/crates/ruff_dev/src/generate_docs.rs index d191e826491efd..5f2309328ef0a9 100644 --- a/crates/ruff_dev/src/generate_docs.rs +++ b/crates/ruff_dev/src/generate_docs.rs @@ -29,7 +29,7 @@ pub(crate) fn main(args: &Args) -> Result<()> { if let Some(explanation) = rule.explanation() { let mut output = String::new(); - let _ = writeln!(&mut output, "# {} ({})", rule.as_ref(), rule.noqa_code()); + let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code()); let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap(); if linter.url().is_some() { @@ -101,7 +101,7 @@ pub(crate) fn main(args: &Args) -> Result<()> { let filename = PathBuf::from(ROOT_DIR) .join("docs") .join("rules") - .join(rule.as_ref()) + .join(&*rule.name()) .with_extension("md"); if args.dry_run { diff --git a/crates/ruff_dev/src/generate_rules_table.rs b/crates/ruff_dev/src/generate_rules_table.rs index 48b1cdc2c9569d..3255f8f42b276a 100644 --- a/crates/ruff_dev/src/generate_rules_table.rs +++ b/crates/ruff_dev/src/generate_rules_table.rs @@ -55,7 +55,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator, FixAvailability::None => format!(""), }; - let rule_name = rule.as_ref(); + let rule_name = rule.name(); // If the message ends in a bracketed expression (like: "Use {replacement}"), escape the // brackets. Otherwise, it'll be interpreted as an HTML attribute via the `attr_list` diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 69ac2201ce5405..c31e989a46c1b3 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -4,13 +4,13 @@ /// `--select`. For pylint this is e.g. C0414 and E0118 but also C and E01. use std::fmt::Formatter; -use strum_macros::{AsRefStr, EnumIter}; +use strum_macros::EnumIter; use crate::registry::Linter; use crate::rule_selector::is_single_rule_selector; use crate::rules; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NoqaCode(&'static str, &'static str); impl NoqaCode { diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index ae77973ed7f99b..93541a24f660c7 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use itertools::Itertools; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use ruff_diagnostics::{IsolationLevel, SourceMap}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -59,13 +59,13 @@ fn apply_fixes<'a>( let mut last_pos: Option = None; let mut applied: BTreeSet<&Edit> = BTreeSet::default(); let mut isolated: FxHashSet = FxHashSet::default(); - let mut fixed = FxHashMap::default(); + let mut fixed = FixTable::default(); let mut source_map = SourceMap::default(); - for (rule, fix) in diagnostics - .filter_map(|msg| msg.to_rule().map(|rule| (rule, msg))) - .filter_map(|(rule, diagnostic)| diagnostic.fix().map(|fix| (rule, fix))) - .sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2)) + for (code, name, fix) in diagnostics + .filter_map(|msg| msg.noqa_code().map(|code| (code, msg.name(), msg))) + .filter_map(|(code, name, diagnostic)| diagnostic.fix().map(|fix| (code, name, fix))) + .sorted_by(|(_, name1, fix1), (_, name2, fix2)| cmp_fix(name1, name2, fix1, fix2)) { let mut edits = fix .edits() @@ -110,7 +110,7 @@ fn apply_fixes<'a>( } applied.extend(applied_edits.drain(..)); - *fixed.entry(rule).or_default() += 1; + *fixed.entry(code).or_default(name) += 1; } // Add the remaining content. @@ -125,34 +125,44 @@ fn apply_fixes<'a>( } /// Compare two fixes. -fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering { +fn cmp_fix(name1: &str, name2: &str, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering { // Always apply `RedefinedWhileUnused` before `UnusedImport`, as the latter can end up fixing // the former. But we can't apply this just for `RedefinedWhileUnused` and `UnusedImport` because it violates // `< is transitive: a < b and b < c implies a < c. The same must hold for both == and >.` // See https://github.com/astral-sh/ruff/issues/12469#issuecomment-2244392085 - match (rule1, rule2) { - (Rule::RedefinedWhileUnused, Rule::RedefinedWhileUnused) => std::cmp::Ordering::Equal, - (Rule::RedefinedWhileUnused, _) => std::cmp::Ordering::Less, - (_, Rule::RedefinedWhileUnused) => std::cmp::Ordering::Greater, - _ => std::cmp::Ordering::Equal, + let redefined_while_unused = Rule::RedefinedWhileUnused.name().as_str(); + if (name1, name2) == (redefined_while_unused, redefined_while_unused) { + std::cmp::Ordering::Equal + } else if name1 == redefined_while_unused { + std::cmp::Ordering::Less + } else if name2 == redefined_while_unused { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal } // Apply fixes in order of their start position. .then_with(|| fix1.min_start().cmp(&fix2.min_start())) // Break ties in the event of overlapping rules, for some specific combinations. - .then_with(|| match (&rule1, &rule2) { + .then_with(|| { + let rules = (name1, name2); // Apply `MissingTrailingPeriod` fixes before `NewLineAfterLastParagraph` fixes. - (Rule::MissingTrailingPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less, - (Rule::NewLineAfterLastParagraph, Rule::MissingTrailingPeriod) => { + let missing_trailing_period = Rule::MissingTrailingPeriod.name().as_str(); + let newline_after_last_paragraph = Rule::NewLineAfterLastParagraph.name().as_str(); + let if_else_instead_of_dict_get = Rule::IfElseBlockInsteadOfDictGet.name().as_str(); + let if_else_instead_of_if_exp = Rule::IfElseBlockInsteadOfIfExp.name().as_str(); + if rules == (missing_trailing_period, newline_after_last_paragraph) { + std::cmp::Ordering::Less + } else if rules == (newline_after_last_paragraph, missing_trailing_period) { std::cmp::Ordering::Greater } // Apply `IfElseBlockInsteadOfDictGet` fixes before `IfElseBlockInsteadOfIfExp` fixes. - (Rule::IfElseBlockInsteadOfDictGet, Rule::IfElseBlockInsteadOfIfExp) => { + else if rules == (if_else_instead_of_dict_get, if_else_instead_of_if_exp) { std::cmp::Ordering::Less - } - (Rule::IfElseBlockInsteadOfIfExp, Rule::IfElseBlockInsteadOfDictGet) => { + } else if rules == (if_else_instead_of_if_exp, if_else_instead_of_dict_get) { std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal } - _ => std::cmp::Ordering::Equal, }) } @@ -197,7 +207,7 @@ mod tests { source_map, } = apply_fixes(diagnostics.iter(), &locator); assert_eq!(code, ""); - assert_eq!(fixes.values().sum::(), 0); + assert_eq!(fixes.counts().sum::(), 0); assert!(source_map.markers().is_empty()); } @@ -234,7 +244,7 @@ print("hello world") "# .trim() ); - assert_eq!(fixes.values().sum::(), 1); + assert_eq!(fixes.counts().sum::(), 1); assert_eq!( source_map.markers(), &[ @@ -275,7 +285,7 @@ class A(Bar): " .trim(), ); - assert_eq!(fixes.values().sum::(), 1); + assert_eq!(fixes.counts().sum::(), 1); assert_eq!( source_map.markers(), &[ @@ -312,7 +322,7 @@ class A: " .trim() ); - assert_eq!(fixes.values().sum::(), 1); + assert_eq!(fixes.counts().sum::(), 1); assert_eq!( source_map.markers(), &[ @@ -353,7 +363,7 @@ class A(object): " .trim() ); - assert_eq!(fixes.values().sum::(), 2); + assert_eq!(fixes.counts().sum::(), 2); assert_eq!( source_map.markers(), &[ @@ -395,7 +405,7 @@ class A: " .trim(), ); - assert_eq!(fixes.values().sum::(), 1); + assert_eq!(fixes.counts().sum::(), 1); assert_eq!( source_map.markers(), &[ diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 99e35681ec6ce7..2b31495b2a9ca0 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::hash_map::Entry; use std::path::Path; use anyhow::{Result, anyhow}; @@ -22,6 +23,7 @@ use crate::checkers::imports::check_imports; use crate::checkers::noqa::check_noqa; use crate::checkers::physical_lines::check_physical_lines; use crate::checkers::tokens::check_tokens; +use crate::codes::NoqaCode; use crate::directives::Directives; use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens}; use crate::fix::{FixResult, fix_file}; @@ -84,7 +86,53 @@ impl LinterResult { } } -pub type FixTable = FxHashMap; +#[derive(Debug, Default, PartialEq)] +struct FixCount { + rule_name: &'static str, + count: usize, +} + +/// A mapping from a noqa code to the corresponding lint name and a count of applied fixes. +#[derive(Debug, Default, PartialEq)] +pub struct FixTable(FxHashMap); + +impl FixTable { + pub fn counts(&self) -> impl Iterator { + self.0.values().map(|fc| fc.count) + } + + pub fn entry(&mut self, code: NoqaCode) -> FixTableEntry { + FixTableEntry(self.0.entry(code)) + } + + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .map(|(code, FixCount { rule_name, count })| (*code, *rule_name, *count)) + } + + pub fn keys(&self) -> impl Iterator { + self.0.keys().copied() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +pub struct FixTableEntry<'a>(Entry<'a, NoqaCode, FixCount>); + +impl<'a> FixTableEntry<'a> { + pub fn or_default(self, rule_name: &'static str) -> &'a mut usize { + &mut (self + .0 + .or_insert(FixCount { + rule_name, + count: 0, + }) + .count) + } +} pub struct FixerResult<'a> { /// The result returned by the linter, after applying any fixes. @@ -581,7 +629,7 @@ pub fn lint_fix<'a>( let mut transformed = Cow::Borrowed(source_kind); // Track the number of fixed errors across iterations. - let mut fixed = FxHashMap::default(); + let mut fixed = FixTable::default(); // As an escape hatch, bail after 100 iterations. let mut iterations = 0; @@ -650,12 +698,7 @@ pub fn lint_fix<'a>( // syntax error. Return the original code. if has_valid_syntax && has_no_syntax_errors { if let Some(error) = parsed.errors().first() { - report_fix_syntax_error( - path, - transformed.source_code(), - error, - fixed.keys().copied(), - ); + report_fix_syntax_error(path, transformed.source_code(), error, fixed.keys()); return Err(anyhow!("Fix introduced a syntax error")); } } @@ -670,8 +713,8 @@ pub fn lint_fix<'a>( { if iterations < MAX_ITERATIONS { // Count the number of fixed errors. - for (rule, count) in applied { - *fixed.entry(rule).or_default() += count; + for (rule, name, count) in applied.iter() { + *fixed.entry(rule).or_default(name) += count; } transformed = Cow::Owned(transformed.updated(fixed_contents, &source_map)); @@ -698,10 +741,10 @@ pub fn lint_fix<'a>( } } -fn collect_rule_codes(rules: impl IntoIterator) -> String { +fn collect_rule_codes(rules: impl IntoIterator) -> String { rules .into_iter() - .map(|rule| rule.noqa_code().to_string()) + .map(|rule| rule.to_string()) .sorted_unstable() .dedup() .join(", ") @@ -709,7 +752,7 @@ fn collect_rule_codes(rules: impl IntoIterator) -> String { #[expect(clippy::print_stderr)] fn report_failed_to_converge_error(path: &Path, transformed: &str, messages: &[Message]) { - let codes = collect_rule_codes(messages.iter().filter_map(Message::to_rule)); + let codes = collect_rule_codes(messages.iter().filter_map(Message::noqa_code)); if cfg!(debug_assertions) { eprintln!( "{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---", @@ -745,7 +788,7 @@ fn report_fix_syntax_error( path: &Path, transformed: &str, error: &ParseError, - rules: impl IntoIterator, + rules: impl IntoIterator, ) { let codes = collect_rule_codes(rules); if cfg!(debug_assertions) { diff --git a/crates/ruff_linter/src/message/azure.rs b/crates/ruff_linter/src/message/azure.rs index 107224d61ed4d8..1f0fe2c5b5207f 100644 --- a/crates/ruff_linter/src/message/azure.rs +++ b/crates/ruff_linter/src/message/azure.rs @@ -33,7 +33,7 @@ impl Emitter for AzureEmitter { line = location.line, col = location.column, code = message - .to_noqa_code() + .noqa_code() .map_or_else(String::new, |code| format!("code={code};")), body = message.body(), )?; diff --git a/crates/ruff_linter/src/message/github.rs b/crates/ruff_linter/src/message/github.rs index 748af7896439b5..5c31dc134cf1cf 100644 --- a/crates/ruff_linter/src/message/github.rs +++ b/crates/ruff_linter/src/message/github.rs @@ -33,7 +33,7 @@ impl Emitter for GithubEmitter { writer, "::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::", code = message - .to_noqa_code() + .noqa_code() .map_or_else(String::new, |code| format!(" ({code})")), file = message.filename(), row = source_location.line, @@ -50,7 +50,7 @@ impl Emitter for GithubEmitter { column = location.column, )?; - if let Some(code) = message.to_noqa_code() { + if let Some(code) = message.noqa_code() { write!(writer, " {code}")?; } diff --git a/crates/ruff_linter/src/message/gitlab.rs b/crates/ruff_linter/src/message/gitlab.rs index 3d7fd85e270c5c..b04cd86ad47013 100644 --- a/crates/ruff_linter/src/message/gitlab.rs +++ b/crates/ruff_linter/src/message/gitlab.rs @@ -90,7 +90,7 @@ impl Serialize for SerializedMessages<'_> { } fingerprints.insert(message_fingerprint); - let (description, check_name) = if let Some(code) = message.to_noqa_code() { + let (description, check_name) = if let Some(code) = message.noqa_code() { (message.body().to_string(), code.to_string()) } else { let description = message.body(); diff --git a/crates/ruff_linter/src/message/json.rs b/crates/ruff_linter/src/message/json.rs index 3492047f440b80..e116f55e96a2f4 100644 --- a/crates/ruff_linter/src/message/json.rs +++ b/crates/ruff_linter/src/message/json.rs @@ -81,8 +81,8 @@ pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) } json!({ - "code": message.to_noqa_code().map(|code| code.to_string()), - "url": message.to_rule().and_then(|rule| rule.url()), + "code": message.noqa_code().map(|code| code.to_string()), + "url": message.to_url(), "message": message.body(), "fix": fix, "cell": notebook_cell_index, diff --git a/crates/ruff_linter/src/message/junit.rs b/crates/ruff_linter/src/message/junit.rs index 38575d93d38b19..d0bbaca51530d6 100644 --- a/crates/ruff_linter/src/message/junit.rs +++ b/crates/ruff_linter/src/message/junit.rs @@ -59,7 +59,7 @@ impl Emitter for JunitEmitter { body = message.body() )); let mut case = TestCase::new( - if let Some(code) = message.to_noqa_code() { + if let Some(code) = message.noqa_code() { format!("org.ruff.{code}") } else { "org.ruff".to_string() diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 49bd23cd1ebbf6..ce4f1041a6f9d8 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -224,30 +224,22 @@ impl Message { self.fix().is_some() } - /// Returns the [`Rule`] corresponding to the diagnostic message. - pub fn to_rule(&self) -> Option { - if self.is_syntax_error() { - None - } else { - Some(self.name().parse().expect("Expected a valid rule name")) - } - } - /// Returns the [`NoqaCode`] corresponding to the diagnostic message. - pub fn to_noqa_code(&self) -> Option { + pub fn noqa_code(&self) -> Option { self.noqa_code } /// Returns the URL for the rule documentation, if it exists. pub fn to_url(&self) -> Option { - // TODO(brent) Rule::url calls Rule::explanation, which calls ViolationMetadata::explain, - // which when derived (seems always to be the case?) is always `Some`, so I think it's - // pretty safe to inline the Rule::url implementation here, using `self.name()`: - // - // format!("{}/rules/{}", env!("CARGO_PKG_HOMEPAGE"), self.name()) - // - // at least in the case of diagnostics, I guess syntax errors will return None - self.to_rule().and_then(|rule| rule.url()) + if self.is_syntax_error() { + None + } else { + Some(format!( + "{}/rules/{}", + env!("CARGO_PKG_HOMEPAGE"), + self.name() + )) + } } /// Returns the filename for the message. diff --git a/crates/ruff_linter/src/message/pylint.rs b/crates/ruff_linter/src/message/pylint.rs index 60c7a182dce1f0..a4bf41225f851f 100644 --- a/crates/ruff_linter/src/message/pylint.rs +++ b/crates/ruff_linter/src/message/pylint.rs @@ -26,7 +26,7 @@ impl Emitter for PylintEmitter { message.compute_start_location().line }; - let body = if let Some(code) = message.to_noqa_code() { + let body = if let Some(code) = message.noqa_code() { format!("[{code}] {body}", body = message.body()) } else { message.body().to_string() diff --git a/crates/ruff_linter/src/message/rdjson.rs b/crates/ruff_linter/src/message/rdjson.rs index 34c89d0bd05430..28be271ee05cca 100644 --- a/crates/ruff_linter/src/message/rdjson.rs +++ b/crates/ruff_linter/src/message/rdjson.rs @@ -71,7 +71,7 @@ fn message_to_rdjson_value(message: &Message) -> Value { "range": rdjson_range(start_location, end_location), }, "code": { - "value": message.to_noqa_code().map(|code| code.to_string()), + "value": message.noqa_code().map(|code| code.to_string()), "url": message.to_url(), }, "suggestions": rdjson_suggestions(fix.edits(), &source_code), @@ -84,7 +84,7 @@ fn message_to_rdjson_value(message: &Message) -> Value { "range": rdjson_range(start_location, end_location), }, "code": { - "value": message.to_noqa_code().map(|code| code.to_string()), + "value": message.noqa_code().map(|code| code.to_string()), "url": message.to_url(), }, }) diff --git a/crates/ruff_linter/src/message/sarif.rs b/crates/ruff_linter/src/message/sarif.rs index 5b5021b53162f5..b14a93497fa02a 100644 --- a/crates/ruff_linter/src/message/sarif.rs +++ b/crates/ruff_linter/src/message/sarif.rs @@ -8,7 +8,7 @@ use serde_json::json; use ruff_source_file::OneIndexed; use crate::VERSION; -use crate::codes::Rule; +use crate::codes::NoqaCode; use crate::fs::normalize_path; use crate::message::{Emitter, EmitterContext, Message}; use crate::registry::{Linter, RuleNamespace}; @@ -27,7 +27,7 @@ impl Emitter for SarifEmitter { .map(SarifResult::from_message) .collect::>>()?; - let unique_rules: HashSet<_> = results.iter().filter_map(|result| result.rule).collect(); + let unique_rules: HashSet<_> = results.iter().filter_map(|result| result.code).collect(); let mut rules: Vec = unique_rules.into_iter().map(SarifRule::from).collect(); rules.sort_by(|a, b| a.code.cmp(&b.code)); @@ -61,13 +61,19 @@ struct SarifRule<'a> { url: Option, } -impl From for SarifRule<'_> { - fn from(rule: Rule) -> Self { - let code = rule.noqa_code().to_string(); - let (linter, _) = Linter::parse_code(&code).unwrap(); +impl From for SarifRule<'_> { + fn from(code: NoqaCode) -> Self { + let code_str = code.to_string(); + // This is a manual re-implementation of Rule::from_code, but we also want the Linter. This + // avoids calling Linter::parse_code twice. + let (linter, suffix) = Linter::parse_code(&code_str).unwrap(); + let rule = linter + .all_rules() + .find(|rule| rule.noqa_code().suffix() == suffix) + .expect("Expected a valid noqa code corresponding to a rule"); Self { name: rule.into(), - code, + code: code_str, linter: linter.name(), summary: rule.message_formats()[0], explanation: rule.explanation(), @@ -106,7 +112,7 @@ impl Serialize for SarifRule<'_> { #[derive(Debug)] struct SarifResult { - rule: Option, + code: Option, level: String, message: String, uri: String, @@ -123,7 +129,7 @@ impl SarifResult { let end_location = message.compute_end_location(); let path = normalize_path(&*message.filename()); Ok(Self { - rule: message.to_rule(), + code: message.noqa_code(), level: "error".to_string(), message: message.body().to_string(), uri: url::Url::from_file_path(&path) @@ -143,7 +149,7 @@ impl SarifResult { let end_location = message.compute_end_location(); let path = normalize_path(&*message.filename()); Ok(Self { - rule: message.to_rule(), + code: message.noqa_code(), level: "error".to_string(), message: message.body().to_string(), uri: path.display().to_string(), @@ -178,7 +184,7 @@ impl Serialize for SarifResult { } } }], - "ruleId": self.rule.map(|rule| rule.noqa_code().to_string()), + "ruleId": self.code.map(|code| code.to_string()), }) .serialize(serializer) } diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs index 65b98d9c1bd1bf..c8f89fc49574fa 100644 --- a/crates/ruff_linter/src/message/text.rs +++ b/crates/ruff_linter/src/message/text.rs @@ -151,7 +151,7 @@ impl Display for RuleCodeAndBody<'_> { if let Some(fix) = self.message.fix() { // Do not display an indicator for inapplicable fixes if fix.applies(self.unsafe_fixes.required_applicability()) { - if let Some(code) = self.message.to_noqa_code() { + if let Some(code) = self.message.noqa_code() { write!(f, "{} ", code.to_string().red().bold())?; } return write!( @@ -164,7 +164,7 @@ impl Display for RuleCodeAndBody<'_> { } } - if let Some(code) = self.message.to_noqa_code() { + if let Some(code) = self.message.noqa_code() { write!( f, "{code} {body}", @@ -254,7 +254,7 @@ impl Display for MessageCodeFrame<'_> { let label = self .message - .to_noqa_code() + .noqa_code() .map_or_else(String::new, |code| code.to_string()); let line_start = self.notebook_index.map_or_else( diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index a7ff0c0c45b926..3d7ada3a110488 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -12,13 +12,14 @@ use log::warn; use ruff_python_trivia::{CommentRanges, Cursor, indentation_at_offset}; use ruff_source_file::{LineEnding, LineRanges}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use rustc_hash::FxHashSet; use crate::Edit; use crate::Locator; use crate::codes::NoqaCode; use crate::fs::relativize_path; use crate::message::Message; -use crate::registry::{Rule, RuleSet}; +use crate::registry::Rule; use crate::rule_redirects::get_redirect_target; /// Generates an array of edits that matches the length of `messages`. @@ -780,7 +781,7 @@ fn build_noqa_edits_by_diagnostic( if let Some(noqa_edit) = generate_noqa_edit( comment.directive, comment.line, - RuleSet::from_rule(comment.rule), + FxHashSet::from_iter([comment.code]), locator, line_ending, ) { @@ -816,7 +817,7 @@ fn build_noqa_edits_by_line<'a>( offset, matches .into_iter() - .map(|NoqaComment { rule, .. }| rule) + .map(|NoqaComment { code, .. }| code) .collect(), locator, line_ending, @@ -829,7 +830,7 @@ fn build_noqa_edits_by_line<'a>( struct NoqaComment<'a> { line: TextSize, - rule: Rule, + code: NoqaCode, directive: Option<&'a Directive<'a>>, } @@ -845,13 +846,11 @@ fn find_noqa_comments<'a>( // Mark any non-ignored diagnostics. for message in messages { - let Some(rule) = message.to_rule() else { + let Some(code) = message.noqa_code() else { comments_by_line.push(None); continue; }; - let code = rule.noqa_code(); - match &exemption { FileExemption::All(_) => { // If the file is exempted, don't add any noqa directives. @@ -900,7 +899,7 @@ fn find_noqa_comments<'a>( if !codes.includes(code) { comments_by_line.push(Some(NoqaComment { line: directive_line.start(), - rule, + code, directive: Some(directive), })); } @@ -912,7 +911,7 @@ fn find_noqa_comments<'a>( // There's no existing noqa directive that suppresses the diagnostic. comments_by_line.push(Some(NoqaComment { line: locator.line_start(noqa_offset), - rule, + code, directive: None, })); } @@ -922,7 +921,7 @@ fn find_noqa_comments<'a>( struct NoqaEdit<'a> { edit_range: TextRange, - rules: RuleSet, + noqa_codes: FxHashSet, codes: Option<&'a Codes<'a>>, line_ending: LineEnding, } @@ -941,18 +940,15 @@ impl NoqaEdit<'_> { Some(codes) => { push_codes( writer, - self.rules + self.noqa_codes .iter() - .map(|rule| rule.noqa_code().to_string()) + .map(ToString::to_string) .chain(codes.iter().map(ToString::to_string)) .sorted_unstable(), ); } None => { - push_codes( - writer, - self.rules.iter().map(|rule| rule.noqa_code().to_string()), - ); + push_codes(writer, self.noqa_codes.iter().map(ToString::to_string)); } } write!(writer, "{}", self.line_ending.as_str()).unwrap(); @@ -968,7 +964,7 @@ impl Ranged for NoqaEdit<'_> { fn generate_noqa_edit<'a>( directive: Option<&'a Directive>, offset: TextSize, - rules: RuleSet, + noqa_codes: FxHashSet, locator: &Locator, line_ending: LineEnding, ) -> Option> { @@ -997,7 +993,7 @@ fn generate_noqa_edit<'a>( Some(NoqaEdit { edit_range, - rules, + noqa_codes, codes, line_ending, }) diff --git a/crates/ruff_linter/src/registry.rs b/crates/ruff_linter/src/registry.rs index 9b03a8b7f35e09..9351cb5de26a09 100644 --- a/crates/ruff_linter/src/registry.rs +++ b/crates/ruff_linter/src/registry.rs @@ -1,6 +1,7 @@ //! Remnant of the registry of all [`Rule`] implementations, now it's reexporting from codes.rs //! with some helper symbols +use ruff_db::diagnostic::LintName; use strum_macros::EnumIter; pub use codes::Rule; @@ -348,9 +349,18 @@ impl Rule { /// Return the URL for the rule documentation, if it exists. pub fn url(&self) -> Option { - self.explanation() - .is_some() - .then(|| format!("{}/rules/{}", env!("CARGO_PKG_HOMEPAGE"), self.as_ref())) + self.explanation().is_some().then(|| { + format!( + "{}/rules/{name}", + env!("CARGO_PKG_HOMEPAGE"), + name = self.name() + ) + }) + } + + pub fn name(&self) -> LintName { + let name: &'static str = self.into(); + LintName::of(name) } } @@ -421,7 +431,7 @@ pub mod clap_completion { fn possible_values(&self) -> Option + '_>> { Some(Box::new(Rule::iter().map(|rule| { let name = rule.noqa_code().to_string(); - let help = rule.as_ref().to_string(); + let help = rule.name().as_str(); PossibleValue::new(name).help(help) }))) } @@ -443,7 +453,7 @@ mod tests { assert!( rule.explanation().is_some(), "Rule {} is missing documentation", - rule.as_ref() + rule.name() ); } } @@ -460,10 +470,10 @@ mod tests { .collect(); for rule in Rule::iter() { - let rule_name = rule.as_ref(); + let rule_name = rule.name(); for pattern in &patterns { assert!( - !pattern.matches(rule_name), + !pattern.matches(&rule_name), "{rule_name} does not match naming convention, see CONTRIBUTING.md" ); } diff --git a/crates/ruff_linter/src/registry/rule_set.rs b/crates/ruff_linter/src/registry/rule_set.rs index d83ff07712c0cc..62601d7229a1c6 100644 --- a/crates/ruff_linter/src/registry/rule_set.rs +++ b/crates/ruff_linter/src/registry/rule_set.rs @@ -302,9 +302,8 @@ impl Display for RuleSet { } else { writeln!(f, "[")?; for rule in self { - let name = rule.as_ref(); let code = rule.noqa_code(); - writeln!(f, "\t{name} ({code}),")?; + writeln!(f, "\t{name} ({code}),", name = rule.name())?; } write!(f, "]")?; } diff --git a/crates/ruff_linter/src/rule_selector.rs b/crates/ruff_linter/src/rule_selector.rs index 7588aa9f64479c..74f069b976497c 100644 --- a/crates/ruff_linter/src/rule_selector.rs +++ b/crates/ruff_linter/src/rule_selector.rs @@ -485,8 +485,7 @@ pub mod clap_completion { prefix.linter().common_prefix(), prefix.short_code() ); - let name: &'static str = rule.into(); - return Some(PossibleValue::new(code).help(name)); + return Some(PossibleValue::new(code).help(rule.name().as_str())); } None diff --git a/crates/ruff_linter/src/rules/fastapi/mod.rs b/crates/ruff_linter/src/rules/fastapi/mod.rs index 90f52b7d66ec6d..60f18097372b44 100644 --- a/crates/ruff_linter/src/rules/fastapi/mod.rs +++ b/crates/ruff_linter/src/rules/fastapi/mod.rs @@ -3,7 +3,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -18,7 +17,7 @@ mod tests { #[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))] #[test_case(Rule::FastApiUnusedPathParameter, Path::new("FAST003.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("fastapi").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), @@ -32,7 +31,7 @@ mod tests { #[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))] #[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))] fn rules_py38(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}_py38", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}_py38", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("fastapi").join(path).as_path(), &settings::LinterSettings { diff --git a/crates/ruff_linter/src/rules/flake8_fixme/mod.rs b/crates/ruff_linter/src/rules/flake8_fixme/mod.rs index d2433fc8b0495d..44071d1b7814ff 100644 --- a/crates/ruff_linter/src/rules/flake8_fixme/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_fixme/mod.rs @@ -16,7 +16,7 @@ mod tests { #[test_case(Rule::LineContainsTodo; "T003")] #[test_case(Rule::LineContainsXxx; "T004")] fn rules(rule_code: Rule) -> Result<()> { - let snapshot = format!("{}_T00.py", rule_code.as_ref()); + let snapshot = format!("{}_T00.py", rule_code.name()); let diagnostics = test_path( Path::new("flake8_fixme/T00.py"), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/flake8_gettext/mod.rs b/crates/ruff_linter/src/rules/flake8_gettext/mod.rs index 09d440526eb06e..cd385932ac07bc 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/mod.rs @@ -29,7 +29,7 @@ mod tests { #[test_case(Rule::FormatInGetTextFuncCall, Path::new("INT002.py"))] #[test_case(Rule::PrintfInGetTextFuncCall, Path::new("INT003.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_gettext").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/flake8_raise/mod.rs b/crates/ruff_linter/src/rules/flake8_raise/mod.rs index d2d636b1e8e412..1780454533a881 100644 --- a/crates/ruff_linter/src/rules/flake8_raise/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_raise/mod.rs @@ -3,7 +3,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -15,7 +14,7 @@ mod tests { #[test_case(Rule::UnnecessaryParenOnRaiseException, Path::new("RSE102.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_raise").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/flake8_self/mod.rs b/crates/ruff_linter/src/rules/flake8_self/mod.rs index a445b40bafaf14..7a3f83ddd3bbac 100644 --- a/crates/ruff_linter/src/rules/flake8_self/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_self/mod.rs @@ -4,7 +4,6 @@ pub mod settings; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use crate::registry::Rule; @@ -18,7 +17,7 @@ mod tests { #[test_case(Rule::PrivateMemberAccess, Path::new("SLF001.py"))] #[test_case(Rule::PrivateMemberAccess, Path::new("SLF001_1.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_self").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/flake8_todos/mod.rs b/crates/ruff_linter/src/rules/flake8_todos/mod.rs index 7a4129a78b6825..486c46af04bdd8 100644 --- a/crates/ruff_linter/src/rules/flake8_todos/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_todos/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -20,7 +19,7 @@ mod tests { #[test_case(Rule::InvalidTodoCapitalization, Path::new("TD006.py"))] #[test_case(Rule::MissingSpaceAfterTodoColon, Path::new("TD007.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_todos").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs index f5b777ec46b32f..acdb788f520669 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs @@ -6,7 +6,6 @@ pub mod settings; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -55,7 +54,7 @@ mod tests { #[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("typing_modules_1.py"))] #[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("typing_modules_2.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), @@ -70,7 +69,7 @@ mod tests { #[test_case(Rule::QuotedTypeAlias, Path::new("TC008.py"))] #[test_case(Rule::QuotedTypeAlias, Path::new("TC008_typing_execution_context.py"))] fn type_alias_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings::for_rules(vec![ @@ -84,11 +83,7 @@ mod tests { #[test_case(Rule::QuotedTypeAlias, Path::new("TC008_union_syntax_pre_py310.py"))] fn type_alias_rules_pre_py310(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "pre_py310_{}_{}", - rule_code.as_ref(), - path.to_string_lossy() - ); + let snapshot = format!("pre_py310_{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -107,7 +102,7 @@ mod tests { #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote3.py"))] #[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("quote3.py"))] fn quote(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("quote_{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("quote_{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -126,7 +121,7 @@ mod tests { #[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("init_var.py"))] #[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("kw_only.py"))] fn strict(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("strict_{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("strict_{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -170,7 +165,7 @@ mod tests { Path::new("exempt_type_checking_3.py") )] fn exempt_type_checking(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -207,7 +202,7 @@ mod tests { Path::new("runtime_evaluated_base_classes_5.py") )] fn runtime_evaluated_base_classes(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -238,7 +233,7 @@ mod tests { Path::new("runtime_evaluated_decorators_3.py") )] fn runtime_evaluated_decorators(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -264,7 +259,7 @@ mod tests { Path::new("module/undefined.py") )] fn base_class_same_file(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { @@ -282,7 +277,7 @@ mod tests { #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("module/app.py"))] #[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("module/routes.py"))] fn decorator_same_file(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { diff --git a/crates/ruff_linter/src/rules/numpy/mod.rs b/crates/ruff_linter/src/rules/numpy/mod.rs index 7a313eac2ecdfd..9403f25016ddeb 100644 --- a/crates/ruff_linter/src/rules/numpy/mod.rs +++ b/crates/ruff_linter/src/rules/numpy/mod.rs @@ -4,7 +4,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -22,7 +21,7 @@ mod tests { #[test_case(Rule::Numpy2Deprecation, Path::new("NPY201_2.py"))] #[test_case(Rule::Numpy2Deprecation, Path::new("NPY201_3.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("numpy").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/rules/pydoclint/mod.rs b/crates/ruff_linter/src/rules/pydoclint/mod.rs index af4131e4274252..6e2ff94021f612 100644 --- a/crates/ruff_linter/src/rules/pydoclint/mod.rs +++ b/crates/ruff_linter/src/rules/pydoclint/mod.rs @@ -4,7 +4,6 @@ pub mod settings; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -20,7 +19,7 @@ mod tests { #[test_case(Rule::DocstringMissingException, Path::new("DOC501.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), @@ -36,7 +35,7 @@ mod tests { #[test_case(Rule::DocstringMissingException, Path::new("DOC501_google.py"))] #[test_case(Rule::DocstringExtraneousException, Path::new("DOC502_google.py"))] fn rules_google_style(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { @@ -58,7 +57,7 @@ mod tests { #[test_case(Rule::DocstringMissingException, Path::new("DOC501_numpy.py"))] #[test_case(Rule::DocstringExtraneousException, Path::new("DOC502_numpy.py"))] fn rules_numpy_style(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { @@ -79,7 +78,7 @@ mod tests { fn rules_google_style_ignore_one_line(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "{}_{}_ignore_one_line", - rule_code.as_ref(), + rule_code.name(), path.to_string_lossy() ); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index ce2162e73d7fdc..51f32553950a2a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -776,8 +776,10 @@ mod tests { messages.sort_by_key(Ranged::start); let actual = messages .iter() - .filter_map(Message::to_rule) + .filter(|msg| !msg.is_syntax_error()) + .map(Message::name) .collect::>(); + let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect(); assert_eq!(actual, expected); } diff --git a/crates/ruff_linter/src/rules/tryceratops/mod.rs b/crates/ruff_linter/src/rules/tryceratops/mod.rs index 21f3a01382ddc2..8d431aae67e150 100644 --- a/crates/ruff_linter/src/rules/tryceratops/mod.rs +++ b/crates/ruff_linter/src/rules/tryceratops/mod.rs @@ -4,7 +4,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::convert::AsRef; use std::path::Path; use anyhow::Result; @@ -25,7 +24,7 @@ mod tests { #[test_case(Rule::ErrorInsteadOfException, Path::new("TRY400.py"))] #[test_case(Rule::VerboseLogMessage, Path::new("TRY401.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy()); let diagnostics = test_path( Path::new("tryceratops").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index 7148206df82f0c..ed05d626732b8a 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -20,6 +20,7 @@ use ruff_python_parser::{ParseError, ParseOptions}; use ruff_python_trivia::textwrap::dedent; use ruff_source_file::SourceFileBuilder; +use crate::codes::Rule; use crate::fix::{FixResult, fix_file}; use crate::linter::check_path; use crate::message::{Emitter, EmitterContext, Message, TextEmitter}; @@ -233,8 +234,9 @@ Source with applied fixes: let messages = messages .into_iter() - .filter_map(|msg| Some((msg.to_rule()?, msg))) - .map(|(rule, mut diagnostic)| { + .filter_map(|msg| Some((msg.noqa_code()?, msg))) + .map(|(code, mut diagnostic)| { + let rule = Rule::from_code(&code.to_string()).unwrap(); let fixable = diagnostic.fix().is_some_and(|fix| { matches!( fix.applicability(), diff --git a/crates/ruff_macros/src/map_codes.rs b/crates/ruff_macros/src/map_codes.rs index 7d1ccaf028f219..39993af6afb01a 100644 --- a/crates/ruff_macros/src/map_codes.rs +++ b/crates/ruff_macros/src/map_codes.rs @@ -174,7 +174,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result { output.extend(quote! { impl #linter { - pub fn rules(&self) -> ::std::vec::IntoIter { + pub(crate) fn rules(&self) -> ::std::vec::IntoIter { match self { #prefix_into_iter_match_arms } } } @@ -182,7 +182,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result { } output.extend(quote! { impl RuleCodePrefix { - pub fn parse(linter: &Linter, code: &str) -> Result { + pub(crate) fn parse(linter: &Linter, code: &str) -> Result { use std::str::FromStr; Ok(match linter { @@ -190,7 +190,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result { }) } - pub fn rules(&self) -> ::std::vec::IntoIter { + pub(crate) fn rules(&self) -> ::std::vec::IntoIter { match self { #(RuleCodePrefix::#linter_idents(prefix) => prefix.clone().rules(),)* } @@ -319,7 +319,7 @@ See also https://github.com/astral-sh/ruff/issues/2186. matches!(self.group(), RuleGroup::Preview) } - pub fn is_stable(&self) -> bool { + pub(crate) fn is_stable(&self) -> bool { matches!(self.group(), RuleGroup::Stable) } @@ -371,7 +371,7 @@ fn generate_iter_impl( quote! { impl Linter { /// Rules not in the preview. - pub fn rules(self: &Linter) -> ::std::vec::IntoIter { + pub(crate) fn rules(self: &Linter) -> ::std::vec::IntoIter { match self { #linter_rules_match_arms } @@ -385,7 +385,7 @@ fn generate_iter_impl( } impl RuleCodePrefix { - pub fn iter() -> impl Iterator { + pub(crate) fn iter() -> impl Iterator { use strum::IntoEnumIterator; let mut prefixes = Vec::new(); @@ -436,7 +436,6 @@ fn register_rules<'a>(input: impl Iterator) -> TokenStream { PartialOrd, Ord, ::ruff_macros::CacheKey, - AsRefStr, ::strum_macros::IntoStaticStr, ::strum_macros::EnumString, ::serde::Serialize, diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index bf20172cbaaa99..92c11068de296c 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -242,7 +242,7 @@ fn to_lsp_diagnostic( let body = diagnostic.body().to_string(); let fix = diagnostic.fix(); let suggestion = diagnostic.suggestion(); - let code = diagnostic.to_noqa_code(); + let code = diagnostic.noqa_code(); let fix = fix.and_then(|fix| fix.applies(Applicability::Unsafe).then_some(fix)); diff --git a/crates/ruff_server/src/server/api/requests/hover.rs b/crates/ruff_server/src/server/api/requests/hover.rs index 6b391e15bcc436..846f3654c5cc5b 100644 --- a/crates/ruff_server/src/server/api/requests/hover.rs +++ b/crates/ruff_server/src/server/api/requests/hover.rs @@ -85,7 +85,7 @@ pub(crate) fn hover( fn format_rule_text(rule: Rule) -> String { let mut output = String::new(); - let _ = write!(&mut output, "# {} ({})", rule.as_ref(), rule.noqa_code()); + let _ = write!(&mut output, "# {} ({})", rule.name(), rule.noqa_code()); output.push('\n'); output.push('\n'); diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 24e683042bc8c0..35488825035e38 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -208,7 +208,7 @@ impl Workspace { let messages: Vec = messages .into_iter() .map(|msg| ExpandedMessage { - code: msg.to_noqa_code().map(|code| code.to_string()), + code: msg.noqa_code().map(|code| code.to_string()), message: msg.body().to_string(), start_location: source_code.line_column(msg.start()).into(), end_location: source_code.line_column(msg.end()).into(), diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 684a2b434dfa05..ae0d231504caaf 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -1098,7 +1098,7 @@ impl LintConfiguration { // approach to give each pair it's own `warn_user_once`. for (preferred, expendable, message) in INCOMPATIBLE_CODES { if rules.enabled(*preferred) && rules.enabled(*expendable) { - warn_user_once_by_id!(expendable.as_ref(), "{}", message); + warn_user_once_by_id!(expendable.name().as_str(), "{}", message); rules.disable(*expendable); } } From 28dbc5c51eeb167f5e28308d77dbc697f19bdb10 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 5 Jun 2025 18:55:54 +0200 Subject: [PATCH 346/487] [ty] Fix completion order in playground (#18480) --- playground/ty/src/Editor/Editor.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/playground/ty/src/Editor/Editor.tsx b/playground/ty/src/Editor/Editor.tsx index a25e3b4ddb7abe..1fb7427df7fa1a 100644 --- a/playground/ty/src/Editor/Editor.tsx +++ b/playground/ty/src/Editor/Editor.tsx @@ -202,9 +202,13 @@ class PlaygroundServer new TyPosition(position.lineNumber, position.column), ); + // If completions is 100, this gives us "99" which has a length of two + const digitsLength = String(completions.length - 1).length; + return { - suggestions: completions.map((completion) => ({ + suggestions: completions.map((completion, i) => ({ label: completion.label, + sortText: String(i).padStart(digitsLength, "0"), kind: CompletionItemKind.Variable, insertText: completion.label, // TODO(micha): It's unclear why this field is required for monaco but not VS Code. @@ -491,7 +495,7 @@ class PlaygroundServer this.typeDefinitionProviderDisposable.dispose(); this.inlayHintsDisposable.dispose(); this.formatDisposable.dispose(); - this.editorOpenerDisposable.dispose(); + this.completionDisposable.dispose(); } } From 5faf72a4d9b50c6e330165685e57fae14ca68b73 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 5 Jun 2025 15:18:38 -0500 Subject: [PATCH 347/487] Bump 0.11.13 (#18484) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ Cargo.lock | 6 +++--- README.md | 6 +++--- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_wasm/Cargo.toml | 2 +- docs/integrations.md | 8 ++++---- docs/tutorial.md | 2 +- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 10 files changed, 42 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecea56102313ee..0551679010f4fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 0.11.13 + +### Preview features + +- \[`airflow`\] Add unsafe fix for module moved cases (`AIR301`,`AIR311`,`AIR312`,`AIR302`) ([#18367](https://github.com/astral-sh/ruff/pull/18367),[#18366](https://github.com/astral-sh/ruff/pull/18366),[#18363](https://github.com/astral-sh/ruff/pull/18363),[#18093](https://github.com/astral-sh/ruff/pull/18093)) +- \[`refurb`\] Add coverage of `set` and `frozenset` calls (`FURB171`) ([#18035](https://github.com/astral-sh/ruff/pull/18035)) +- \[`refurb`\] Mark `FURB180` fix unsafe when class has bases ([#18149](https://github.com/astral-sh/ruff/pull/18149)) + +### Bug fixes + +- \[`perflint`\] Fix missing parentheses for lambda and ternary conditions (`PERF401`, `PERF403`) ([#18412](https://github.com/astral-sh/ruff/pull/18412)) +- \[`pyupgrade`\] Apply `UP035` only on py313+ for `get_type_hints()` ([#18476](https://github.com/astral-sh/ruff/pull/18476)) +- \[`pyupgrade`\] Make fix unsafe if it deletes comments (`UP004`,`UP050`) ([#18393](https://github.com/astral-sh/ruff/pull/18393), [#18390](https://github.com/astral-sh/ruff/pull/18390)) + +### Rule changes + +- \[`fastapi`\] Avoid false positive for class dependencies (`FAST003`) ([#18271](https://github.com/astral-sh/ruff/pull/18271)) + +### Documentation + +- Update editor setup docs for Neovim and Vim ([#18324](https://github.com/astral-sh/ruff/pull/18324)) + +### Other changes + +- Support Python 3.14 template strings (t-strings) in formatter and parser ([#17851](https://github.com/astral-sh/ruff/pull/17851)) + ## 0.11.12 ### Preview features diff --git a/Cargo.lock b/Cargo.lock index 9f952927cc9a02..2db4525f8be270 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2501,7 +2501,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.11.12" +version = "0.11.13" dependencies = [ "anyhow", "argfile", @@ -2738,7 +2738,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.11.12" +version = "0.11.13" dependencies = [ "aho-corasick", "anyhow", @@ -3074,7 +3074,7 @@ dependencies = [ [[package]] name = "ruff_wasm" -version = "0.11.12" +version = "0.11.13" dependencies = [ "console_error_panic_hook", "console_log", diff --git a/README.md b/README.md index 451dec1f93e34d..1ee2807cb06ee3 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. -curl -LsSf https://astral.sh/ruff/0.11.12/install.sh | sh -powershell -c "irm https://astral.sh/ruff/0.11.12/install.ps1 | iex" +curl -LsSf https://astral.sh/ruff/0.11.13/install.sh | sh +powershell -c "irm https://astral.sh/ruff/0.11.13/install.ps1 | iex" ``` You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), @@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.12 + rev: v0.11.13 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index da31cad404ea83..8c6081ee000928 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.11.12" +version = "0.11.13" publish = true authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 64109ceef29103..a73b5df5bff724 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.11.12" +version = "0.11.13" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index d967cfd44c32f5..6ba3a5c321bde7 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_wasm" -version = "0.11.12" +version = "0.11.13" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/docs/integrations.md b/docs/integrations.md index 25c7e29c0b743f..2229d2a8ee0b4d 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma stage: build interruptible: true image: - name: ghcr.io/astral-sh/ruff:0.11.12-alpine + name: ghcr.io/astral-sh/ruff:0.11.13-alpine before_script: - cd $CI_PROJECT_DIR - ruff --version @@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.12 + rev: v0.11.13 hooks: # Run the linter. - id: ruff @@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.12 + rev: v0.11.13 hooks: # Run the linter. - id: ruff @@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.12 + rev: v0.11.13 hooks: # Run the linter. - id: ruff diff --git a/docs/tutorial.md b/docs/tutorial.md index 8f14f1b2e73fbf..520e167c290a6c 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.12 + rev: v0.11.13 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 5ce2b0ad169271..2957b55d808fb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.11.12" +version = "0.11.13" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index bf66533c9ffca3..83a4a94e7ce529 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scripts" -version = "0.11.12" +version = "0.11.13" description = "" authors = ["Charles Marsh "] From cb8246bc5f3f1cad5cb443d5122b944cba68fb3e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 5 Jun 2025 18:39:22 -0700 Subject: [PATCH 348/487] [ty] remove unnecessary Either (#18489) Just a quick review-comment follow-up. --- crates/ty_python_semantic/src/types/infer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 2c90d14cb2a7ca..01d01143a50130 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9021,9 +9021,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) -> Type<'db> { let arguments = &*subscript_node.slice; let (args, args_number) = if let ast::Expr::Tuple(t) = arguments { - (Either::Left(t), t.len()) + (t.iter(), t.len()) } else { - (Either::Right([arguments]), 1) + (std::slice::from_ref(arguments).iter(), 1) }; if args_number != expected_arg_count { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) { From db8db536f8b80dea48d79df84a85a8045d2c659e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 5 Jun 2025 22:46:26 -0700 Subject: [PATCH 349/487] [ty] clarify requirements for scope_id argument to in_type_expression (#18488) --- crates/ty_python_semantic/src/types.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c6407e00019147..22fa5316d5986f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4967,7 +4967,10 @@ impl<'db> Type<'db> { /// `Type::ClassLiteral(builtins.int)`, that is, it is the `int` class itself. As a type /// expression, it names the type `Type::NominalInstance(builtins.int)`, that is, all objects whose /// `__class__` is `int`. - pub fn in_type_expression( + /// + /// The `scope_id` argument must always be a scope from the file we are currently inferring, so + /// as to avoid cross-module AST dependency. + pub(crate) fn in_type_expression( &self, db: &'db dyn Db, scope_id: ScopeId<'db>, From 8d2476064325e11f489434d804a23df24b8fb744 Mon Sep 17 00:00:00 2001 From: shimies Date: Fri, 6 Jun 2025 18:49:16 +0900 Subject: [PATCH 350/487] Fix doc for Neovim setting examples (#18491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR fixes an error in the example Neovim configuration on [this documentation page](https://docs.astral.sh/ruff/editors/settings/#configuration). The `configuration` block should be nested under `settings`, consistent with other properties and as outlined [here](https://docs.astral.sh/ruff/editors/setup/#neovim). I encountered this issue when copying the example to configure ruff integration in my neovim - the config didn’t work until I corrected the nesting. ## Test Plan - [x] Confirmed that the corrected configuration works in a real Neovim + Ruff setup - [x] Verified that the updated configuration renders correctly in MkDocs image --- docs/editors/settings.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/editors/settings.md b/docs/editors/settings.md index 1eb36af3a6d28d..d15d145ef0323a 100644 --- a/docs/editors/settings.md +++ b/docs/editors/settings.md @@ -66,7 +66,9 @@ _Using configuration file path:_ ```lua require('lspconfig').ruff.setup { init_options = { - configuration = "~/path/to/ruff.toml" + settings = { + configuration = "~/path/to/ruff.toml" + } } } ``` @@ -117,20 +119,22 @@ _Using inline configuration:_ ```lua require('lspconfig').ruff.setup { init_options = { - configuration = { - lint = { - unfixable = {"F401"}, - ["extend-select"] = {"TID251"}, - ["flake8-tidy-imports"] = { - ["banned-api"] = { - ["typing.TypedDict"] = { - msg = "Use `typing_extensions.TypedDict` instead" + settings = { + configuration = { + lint = { + unfixable = {"F401"}, + ["extend-select"] = {"TID251"}, + ["flake8-tidy-imports"] = { + ["banned-api"] = { + ["typing.TypedDict"] = { + msg = "Use `typing_extensions.TypedDict` instead" + } } } + }, + format = { + ["quote-style"] = "single" } - }, - format = { - ["quote-style"] = "single" } } } From 1274521f9fda19507921c85442d0ca991b67b846 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 6 Jun 2025 13:36:41 +0100 Subject: [PATCH 351/487] [ty] Track the origin of the `environment.python` setting for better error messages (#18483) --- Cargo.lock | 2 + crates/ruff/tests/analyze_graph.rs | 2 +- crates/ty/tests/cli.rs | 103 +++++++- crates/ty_project/src/metadata/options.rs | 12 +- crates/ty_project/src/metadata/value.rs | 12 + crates/ty_python_semantic/Cargo.toml | 2 + .../src/module_resolver/resolver.rs | 4 +- crates/ty_python_semantic/src/program.rs | 2 - .../ty_python_semantic/src/site_packages.rs | 249 +++++++++++++----- 9 files changed, 313 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2db4525f8be270..ba761fc306ccf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3965,6 +3965,7 @@ dependencies = [ "anyhow", "bitflags 2.9.1", "camino", + "colored 3.0.0", "compact_str", "countme", "dir-test", @@ -3977,6 +3978,7 @@ dependencies = [ "ordermap", "quickcheck", "quickcheck_macros", + "ruff_annotate_snippets", "ruff_db", "ruff_index", "ruff_macros", diff --git a/crates/ruff/tests/analyze_graph.rs b/crates/ruff/tests/analyze_graph.rs index 3c5ba4498b3029..b6635d96bfc050 100644 --- a/crates/ruff/tests/analyze_graph.rs +++ b/crates/ruff/tests/analyze_graph.rs @@ -566,7 +566,7 @@ fn venv() -> Result<()> { ----- stderr ----- ruff failed Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` does not point to a Python executable or a directory on disk + Cause: Failed to discover the site-packages directory: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk "); }); diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index 3bf7d972dc566d..9fda2345dac297 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -974,7 +974,7 @@ fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/my-venv/foo/some_other_file.txt` does not point to a Python executable or a directory on disk + Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/my-venv/foo/some_other_file.txt`: does not point to a Python executable or a directory on disk "); // And so are paths that do not exist on disk @@ -987,7 +987,7 @@ fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/not-a-directory-or-executable` does not point to a Python executable or a directory on disk + Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/not-a-directory-or-executable`: does not point to a Python executable or a directory on disk "); Ok(()) @@ -1045,6 +1045,14 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> { ( "pyproject.toml", r#" + [project] + name = "test" + version = "0.1.0" + description = "Some description" + readme = "README.md" + requires-python = ">=3.13" + dependencies = [] + [tool.ty.environment] python = "not-a-directory-or-executable" "#, @@ -1052,8 +1060,7 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> { ("test.py", ""), ])?; - // TODO: this error message should say "invalid `python` configuration setting" rather than "invalid `--python` argument" - assert_cmd_snapshot!(case.command(), @r" + assert_cmd_snapshot!(case.command(), @r#" success: false exit_code: 2 ----- stdout ----- @@ -1062,8 +1069,92 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> { WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `/not-a-directory-or-executable` does not point to a Python executable or a directory on disk - "); + Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting + + --> Invalid setting in configuration file `/pyproject.toml` + | + 9 | + 10 | [tool.ty.environment] + 11 | python = "not-a-directory-or-executable" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not point to a Python executable or a directory on disk + | + "#); + + Ok(()) +} + +#[test] +fn config_file_python_setting_directory_with_no_site_packages() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "directory-but-no-site-packages" + "#, + ), + ("directory-but-no-site-packages/lib/foo.py", ""), + ("test.py", ""), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting + + --> Invalid setting in configuration file `/pyproject.toml` + | + 1 | + 2 | [tool.ty.environment] + 3 | python = "directory-but-no-site-packages" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find a `site-packages` directory for this Python installation/executable + | + "#); + + Ok(()) +} + +// This error message is never emitted on Windows, because Windows installations have simpler layouts +#[cfg(not(windows))] +#[test] +fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "directory-but-no-site-packages" + "#, + ), + ("directory-but-no-site-packages/foo.py", ""), + ("test.py", ""), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Failed to iterate over the contents of the `lib` directory of the Python installation + + --> Invalid setting in configuration file `/pyproject.toml` + | + 1 | + 2 | [tool.ty.environment] + 3 | python = "directory-but-no-site-packages" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + "#); Ok(()) } diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 4571655ce34424..9d3cb9f8e5c641 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -182,10 +182,14 @@ impl Options { custom_typeshed: typeshed.map(|path| path.absolute(project_root, system)), python_path: python .map(|python_path| { - PythonPath::sys_prefix( - python_path.absolute(project_root, system), - SysPrefixPathOrigin::PythonCliFlag, - ) + let origin = match python_path.source() { + ValueSource::Cli => SysPrefixPathOrigin::PythonCliFlag, + ValueSource::File(path) => SysPrefixPathOrigin::ConfigFileSetting( + path.clone(), + python_path.range(), + ), + }; + PythonPath::sys_prefix(python_path.absolute(project_root, system), origin) }) .or_else(|| { std::env::var("VIRTUAL_ENV").ok().map(|virtual_env| { diff --git a/crates/ty_project/src/metadata/value.rs b/crates/ty_project/src/metadata/value.rs index b73c59a3e7e63e..f2f8db1e225226 100644 --- a/crates/ty_project/src/metadata/value.rs +++ b/crates/ty_project/src/metadata/value.rs @@ -32,6 +32,10 @@ impl ValueSource { ValueSource::Cli => None, } } + + pub const fn is_cli(&self) -> bool { + matches!(self, ValueSource::Cli) + } } thread_local! { @@ -324,6 +328,14 @@ impl RelativePathBuf { &self.0 } + pub fn source(&self) -> &ValueSource { + self.0.source() + } + + pub fn range(&self) -> Option { + self.0.range() + } + /// Returns the owned relative path. pub fn into_path_buf(self) -> SystemPathBuf { self.0.into_inner() diff --git a/crates/ty_python_semantic/Cargo.toml b/crates/ty_python_semantic/Cargo.toml index 91e26b8fb5b285..542d31a8a08dcf 100644 --- a/crates/ty_python_semantic/Cargo.toml +++ b/crates/ty_python_semantic/Cargo.toml @@ -12,6 +12,7 @@ license = { workspace = true } [dependencies] ruff_db = { workspace = true } +ruff_annotate_snippets = { workspace = true } ruff_index = { workspace = true, features = ["salsa"] } ruff_macros = { workspace = true } ruff_python_ast = { workspace = true, features = ["salsa"] } @@ -25,6 +26,7 @@ ruff_python_trivia = { workspace = true } anyhow = { workspace = true } bitflags = { workspace = true } camino = { workspace = true } +colored = { workspace = true } compact_str = { workspace = true } countme = { workspace = true } drop_bomb = { workspace = true } diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index d5088828f9bcf1..621d94b73b0735 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -235,7 +235,7 @@ impl SearchPaths { let (site_packages_paths, python_version) = match python_path { PythonPath::IntoSysPrefix(path, origin) => { - if *origin == SysPrefixPathOrigin::LocalVenv { + if origin == &SysPrefixPathOrigin::LocalVenv { tracing::debug!("Discovering virtual environment in `{path}`"); let virtual_env_directory = path.join(".venv"); @@ -260,7 +260,7 @@ impl SearchPaths { }) } else { tracing::debug!("Resolving {origin}: {path}"); - PythonEnvironment::new(path, *origin, system)?.into_settings(system)? + PythonEnvironment::new(path, origin.clone(), system)?.into_settings(system)? } } diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index b968c57bd02c0c..30523e345b240a 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -228,7 +228,6 @@ impl Default for PythonVersionWithSource { /// Configures the search paths for module resolution. #[derive(Eq, PartialEq, Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct SearchPathSettings { /// List of user-provided paths that should take first priority in the module resolution. /// Examples in other type checkers are mypy's MYPYPATH environment variable, @@ -260,7 +259,6 @@ impl SearchPathSettings { } #[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PythonPath { /// A path that either represents the value of [`sys.prefix`] at runtime in Python /// for a given Python executable, or which represents a path relative to `sys.prefix` diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 677ab75880dd8e..bc3f6e626b160d 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -8,16 +8,17 @@ //! reasonably ask us to type-check code assuming that the code runs //! on Linux.) -use std::fmt::Display; use std::io; use std::num::NonZeroUsize; use std::ops::Deref; use std::{fmt, sync::Arc}; use indexmap::IndexSet; +use ruff_annotate_snippets::{Level, Renderer, Snippet}; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_python_ast::PythonVersion; use ruff_python_trivia::Cursor; +use ruff_source_file::{LineIndex, OneIndexed, SourceCode}; use ruff_text_size::{TextLen, TextRange}; use crate::{PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource}; @@ -102,7 +103,7 @@ impl PythonEnvironment { Ok(venv) => Ok(Self::Virtual(venv)), // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, _)) - if !origin.must_be_virtual_env() => + if !path.origin.must_be_virtual_env() => { Ok(Self::System(SystemEnvironment::new(path))) } @@ -530,33 +531,21 @@ impl SystemEnvironment { } /// Enumeration of ways in which `site-packages` discovery can fail. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub(crate) enum SitePackagesDiscoveryError { /// `site-packages` discovery failed because the provided path couldn't be canonicalized. - #[error("Invalid {1}: `{0}` could not be canonicalized")] - CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error), + CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, io::Error), /// `site-packages` discovery failed because the provided path doesn't appear to point to /// a Python executable or a `sys.prefix` directory. - #[error( - "Invalid {1}: `{0}` does not point to a {thing}", - - thing = if .1.must_point_directly_to_sys_prefix() { - "directory on disk" - } else { - "Python executable or a directory on disk" - } - )] - PathNotExecutableOrDirectory(SystemPathBuf, SysPrefixPathOrigin), + PathNotExecutableOrDirectory(SystemPathBuf, SysPrefixPathOrigin, Option), /// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that /// the provided path should point to the `sys.prefix` of a virtual environment, /// but there was no file at `/pyvenv.cfg`. - #[error("{} points to a broken venv with no pyvenv.cfg file", .0.origin)] - NoPyvenvCfgFile(SysPrefixPath, #[source] io::Error), + NoPyvenvCfgFile(SysPrefixPath, io::Error), /// `site-packages` discovery failed because the `pyvenv.cfg` file could not be parsed. - #[error("Failed to parse the pyvenv.cfg file at {0} because {1}")] PyvenvCfgParseError(SystemPathBuf, PyvenvCfgParseErrorKind), /// `site-packages` discovery failed because we're on a Unix system, @@ -564,17 +553,149 @@ pub(crate) enum SitePackagesDiscoveryError { /// would be relative to the `sys.prefix` path, and we tried to fallback to iterating /// through the `/lib` directory looking for a `site-packages` directory, /// but we came across some I/O error while trying to do so. - #[error( - "Failed to iterate over the contents of the `lib` directory of the Python installation at {1}" - )] - CouldNotReadLibDirectory(#[source] io::Error, SysPrefixPath), + CouldNotReadLibDirectory(SysPrefixPath, io::Error), /// We looked everywhere we could think of for the `site-packages` directory, /// but none could be found despite our best endeavours. - #[error("Could not find the `site-packages` directory for the Python installation at {0}")] NoSitePackagesDirFound(SysPrefixPath), } +impl std::error::Error for SitePackagesDiscoveryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::CanonicalizationError(_, _, io_err) => Some(io_err), + Self::PathNotExecutableOrDirectory(_, _, io_err) => { + io_err.as_ref().map(|e| e as &dyn std::error::Error) + } + Self::NoPyvenvCfgFile(_, io_err) => Some(io_err), + Self::PyvenvCfgParseError(_, _) => None, + Self::CouldNotReadLibDirectory(_, io_err) => Some(io_err), + Self::NoSitePackagesDirFound(_) => None, + } + } +} + +impl std::fmt::Display for SitePackagesDiscoveryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::CanonicalizationError(given_path, origin, _) => { + display_error(f, origin, given_path, "Failed to canonicalize", None) + } + Self::PathNotExecutableOrDirectory(path, origin, _) => { + let thing = if origin.must_point_directly_to_sys_prefix() { + "directory on disk" + } else { + "Python executable or a directory on disk" + }; + display_error( + f, + origin, + path, + &format!("Invalid {origin}"), + Some(&format!("does not point to a {thing}")), + ) + } + Self::NoPyvenvCfgFile(SysPrefixPath { inner, origin }, _) => display_error( + f, + origin, + inner, + &format!("Invalid {origin}"), + Some("points to a broken venv with no pyvenv.cfg file"), + ), + Self::PyvenvCfgParseError(path, kind) => { + write!( + f, + "Failed to parse the `pyvenv.cfg` file at `{path}` because {kind}" + ) + } + Self::CouldNotReadLibDirectory(SysPrefixPath { inner, origin }, _) => display_error( + f, + origin, + inner, + "Failed to iterate over the contents of the `lib` directory of the Python installation", + None, + ), + Self::NoSitePackagesDirFound(SysPrefixPath { inner, origin }) => display_error( + f, + origin, + inner, + &format!("Invalid {origin}"), + Some( + "Could not find a `site-packages` directory for this Python installation/executable", + ), + ), + } + } +} + +fn display_error( + f: &mut std::fmt::Formatter<'_>, + sys_prefix_origin: &SysPrefixPathOrigin, + given_path: &SystemPath, + primary_message: &str, + secondary_message: Option<&str>, +) -> std::fmt::Result { + let fallback: &mut dyn FnMut() -> std::fmt::Result = &mut || { + f.write_str(primary_message)?; + write!(f, " `{given_path}`")?; + if let Some(secondary_message) = secondary_message { + f.write_str(": ")?; + f.write_str(secondary_message)?; + } + Ok(()) + }; + + let SysPrefixPathOrigin::ConfigFileSetting(config_file_path, Some(setting_range)) = + sys_prefix_origin + else { + return fallback(); + }; + + let Ok(config_file_source) = std::fs::read_to_string((**config_file_path).as_ref()) else { + return fallback(); + }; + + let index = LineIndex::from_source_text(&config_file_source); + let source = SourceCode::new(&config_file_source, &index); + + let primary_message = format!( + "{primary_message} + +--> Invalid setting in configuration file `{config_file_path}`" + ); + + let start_index = source.line_index(setting_range.start()).saturating_sub(2); + let end_index = source + .line_index(setting_range.end()) + .saturating_add(2) + .min(OneIndexed::from_zero_indexed(source.line_count())); + + let start_offset = source.line_start(start_index); + let end_offset = source.line_end(end_index); + + let mut annotation = Level::Error.span((setting_range - start_offset).into()); + + if let Some(secondary_message) = secondary_message { + annotation = annotation.label(secondary_message); + } + + let snippet = Snippet::source(&config_file_source[TextRange::new(start_offset, end_offset)]) + .annotation(annotation) + .line_start(start_index.get()) + .fold(false); + + let message = Level::None.title(&primary_message).snippet(snippet); + + let renderer = if colored::control::SHOULD_COLORIZE.should_colorize() { + Renderer::styled() + } else { + Renderer::plain() + }; + let renderer = renderer.cut_indicator("…"); + + writeln!(f, "{}", renderer.render(message)) +} + /// The various ways in which parsing a `pyvenv.cfg` file could fail #[derive(Debug)] pub(crate) enum PyvenvCfgParseErrorKind { @@ -615,7 +736,10 @@ fn site_packages_directory_from_sys_prefix( implementation: PythonImplementation, system: &dyn System, ) -> SitePackagesDiscoveryResult { - tracing::debug!("Searching for site-packages directory in {sys_prefix_path}"); + tracing::debug!( + "Searching for site-packages directory in sys.prefix {}", + sys_prefix_path.inner + ); if cfg!(target_os = "windows") { let site_packages = sys_prefix_path.join(r"Lib\site-packages"); @@ -684,7 +808,7 @@ fn site_packages_directory_from_sys_prefix( for entry_result in system .read_directory(&sys_prefix_path.join("lib")) .map_err(|io_err| { - SitePackagesDiscoveryError::CouldNotReadLibDirectory(io_err, sys_prefix_path.to_owned()) + SitePackagesDiscoveryError::CouldNotReadLibDirectory(sys_prefix_path.to_owned(), io_err) })? { let Ok(entry) = entry_result else { @@ -743,14 +867,15 @@ impl SysPrefixPath { // It's important to resolve symlinks here rather than simply making the path absolute, // since system Python installations often only put symlinks in the "expected" // locations for `home` and `site-packages` - let canonicalized = system - .canonicalize_path(unvalidated_path) - .map_err(|io_err| { + let canonicalized = match system.canonicalize_path(unvalidated_path) { + Ok(path) => path, + Err(io_err) => { let unvalidated_path = unvalidated_path.to_path_buf(); - if io_err.kind() == io::ErrorKind::NotFound { + let err = if io_err.kind() == io::ErrorKind::NotFound { SitePackagesDiscoveryError::PathNotExecutableOrDirectory( unvalidated_path, origin, + Some(io_err), ) } else { SitePackagesDiscoveryError::CanonicalizationError( @@ -758,22 +883,24 @@ impl SysPrefixPath { origin, io_err, ) - } - })?; + }; + return Err(err); + } + }; if origin.must_point_directly_to_sys_prefix() { - return system - .is_directory(&canonicalized) - .then_some(Self { + return if system.is_directory(&canonicalized) { + Ok(Self { inner: canonicalized, origin, }) - .ok_or_else(|| { - SitePackagesDiscoveryError::PathNotExecutableOrDirectory( - unvalidated_path.to_path_buf(), - origin, - ) - }); + } else { + Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory( + unvalidated_path.to_path_buf(), + origin, + None, + )) + }; } let sys_prefix = if system.is_file(&canonicalized) @@ -800,18 +927,21 @@ impl SysPrefixPath { // regardless of whether it's a virtual environment or a system installation. canonicalized.ancestors().nth(2) }; - sys_prefix.map(SystemPath::to_path_buf).ok_or_else(|| { - SitePackagesDiscoveryError::PathNotExecutableOrDirectory( + let Some(sys_prefix) = sys_prefix else { + return Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory( unvalidated_path.to_path_buf(), origin, - ) - })? + None, + )); + }; + sys_prefix.to_path_buf() } else if system.is_directory(&canonicalized) { canonicalized } else { return Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory( unvalidated_path.to_path_buf(), origin, + None, )); }; @@ -847,16 +977,11 @@ impl Deref for SysPrefixPath { } } -impl fmt::Display for SysPrefixPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "`sys.prefix` path `{}`", self.inner) - } -} - /// Enumeration of sources a `sys.prefix` path can come from. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum SysPrefixPathOrigin { + /// The `sys.prefix` path came from a configuration file setting: `pyproject.toml` or `ty.toml` + ConfigFileSetting(Arc, Option), /// The `sys.prefix` path came from a `--python` CLI flag PythonCliFlag, /// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable @@ -875,10 +1000,13 @@ pub enum SysPrefixPathOrigin { impl SysPrefixPathOrigin { /// Whether the given `sys.prefix` path must be a virtual environment (rather than a system /// Python environment). - pub(crate) const fn must_be_virtual_env(self) -> bool { + pub(crate) const fn must_be_virtual_env(&self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, - Self::PythonCliFlag | Self::DerivedFromPyvenvCfg | Self::CondaPrefixVar => false, + Self::ConfigFileSetting(..) + | Self::PythonCliFlag + | Self::DerivedFromPyvenvCfg + | Self::CondaPrefixVar => false, } } @@ -886,9 +1014,9 @@ impl SysPrefixPathOrigin { /// /// Some variants can point either directly to `sys.prefix` or to a Python executable inside /// the `sys.prefix` directory, e.g. the `--python` CLI flag. - pub(crate) const fn must_point_directly_to_sys_prefix(self) -> bool { + pub(crate) const fn must_point_directly_to_sys_prefix(&self) -> bool { match self { - Self::PythonCliFlag => false, + Self::PythonCliFlag | Self::ConfigFileSetting(..) => false, Self::VirtualEnvVar | Self::CondaPrefixVar | Self::DerivedFromPyvenvCfg @@ -897,10 +1025,11 @@ impl SysPrefixPathOrigin { } } -impl Display for SysPrefixPathOrigin { +impl std::fmt::Display for SysPrefixPathOrigin { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::PythonCliFlag => f.write_str("`--python` argument"), + Self::ConfigFileSetting(_, _) => f.write_str("`environment.python` setting"), Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"), Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"), Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"), @@ -1107,7 +1236,7 @@ mod tests { #[track_caller] fn run(self) -> PythonEnvironment { let env_path = self.build(); - let env = PythonEnvironment::new(env_path.clone(), self.origin, &self.system) + let env = PythonEnvironment::new(env_path.clone(), self.origin.clone(), &self.system) .expect("Expected environment construction to succeed"); let expect_virtual_env = self.virtual_env.is_some(); @@ -1144,7 +1273,7 @@ mod tests { venv.root_path, SysPrefixPath { inner: self.system.canonicalize_path(expected_env_path).unwrap(), - origin: self.origin, + origin: self.origin.clone(), } ); assert_eq!( @@ -1216,7 +1345,7 @@ mod tests { env.root_path, SysPrefixPath { inner: self.system.canonicalize_path(expected_env_path).unwrap(), - origin: self.origin, + origin: self.origin.clone(), } ); From 6e785867c3a97691972e8a49c372b3effba90032 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 6 Jun 2025 18:28:55 +0100 Subject: [PATCH 352/487] [ty] Unify `Type::is_subtype_of()` and `Type::is_assignable_to()` (#18430) --- .../resources/mdtest/intersection_types.md | 17 +- .../resources/mdtest/protocols.md | 3 +- .../type_properties/is_assignable_to.md | 75 +++ crates/ty_python_semantic/src/types.rs | 628 +++++------------- crates/ty_python_semantic/src/types/class.rs | 63 +- .../src/types/class_base.rs | 4 - .../ty_python_semantic/src/types/function.rs | 16 +- .../ty_python_semantic/src/types/generics.rs | 75 +-- .../ty_python_semantic/src/types/instance.rs | 40 +- .../src/types/signatures.rs | 26 +- .../src/types/subclass_of.rs | 33 +- 11 files changed, 393 insertions(+), 587 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/intersection_types.md b/crates/ty_python_semantic/resources/mdtest/intersection_types.md index 4accdfe3685cf1..256727dd2f7457 100644 --- a/crates/ty_python_semantic/resources/mdtest/intersection_types.md +++ b/crates/ty_python_semantic/resources/mdtest/intersection_types.md @@ -305,10 +305,13 @@ simplify to `Never`, even in the presence of other types: ```py from ty_extensions import Intersection, Not -from typing import Any +from typing import Any, Generic, TypeVar + +T_co = TypeVar("T_co", covariant=True) class P: ... class Q: ... +class R(Generic[T_co]): ... def _( i1: Intersection[P, Not[P]], @@ -317,6 +320,8 @@ def _( i4: Intersection[Not[P], Q, P], i5: Intersection[P, Any, Not[P]], i6: Intersection[Not[P], Any, P], + i7: Intersection[R[P], Not[R[P]]], + i8: Intersection[R[P], Not[R[Q]]], ) -> None: reveal_type(i1) # revealed: Never reveal_type(i2) # revealed: Never @@ -324,6 +329,8 @@ def _( reveal_type(i4) # revealed: Never reveal_type(i5) # revealed: Never reveal_type(i6) # revealed: Never + reveal_type(i7) # revealed: Never + reveal_type(i8) # revealed: R[P] & ~R[Q] ``` ### Union of a type and its negation @@ -332,20 +339,28 @@ Similarly, if we have both `P` and `~P` in a _union_, we can simplify that to `o ```py from ty_extensions import Intersection, Not +from typing import Generic, TypeVar + +T_co = TypeVar("T_co", covariant=True) class P: ... class Q: ... +class R(Generic[T_co]): ... def _( i1: P | Not[P], i2: Not[P] | P, i3: P | Q | Not[P], i4: Not[P] | Q | P, + i5: R[P] | Not[R[P]], + i6: R[P] | Not[R[Q]], ) -> None: reveal_type(i1) # revealed: object reveal_type(i2) # revealed: object reveal_type(i3) # revealed: object reveal_type(i4) # revealed: object + reveal_type(i5) # revealed: object + reveal_type(i6) # revealed: R[P] | ~R[Q] ``` ### Negation is an involution diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index c51916ece76db4..51fe5e1b19a516 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -902,8 +902,7 @@ from ty_extensions import is_subtype_of, is_assignable_to, static_assert, TypeOf class HasX(Protocol): x: int -# TODO: this should pass -static_assert(is_subtype_of(TypeOf[module], HasX)) # error: [static-assert-error] +static_assert(is_subtype_of(TypeOf[module], HasX)) static_assert(is_assignable_to(TypeOf[module], HasX)) class ExplicitProtocolSubtype(HasX, Protocol): diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index a1e32e9ba74559..96993f16235884 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -209,6 +209,34 @@ class AnyMeta(metaclass=Any): ... static_assert(is_assignable_to(type[AnyMeta], type)) static_assert(is_assignable_to(type[AnyMeta], type[object])) static_assert(is_assignable_to(type[AnyMeta], type[Any])) + +from typing import TypeVar, Generic, Any + +T_co = TypeVar("T_co", covariant=True) + +class Foo(Generic[T_co]): ... +class Bar(Foo[T_co], Generic[T_co]): ... + +static_assert(is_assignable_to(TypeOf[Bar[int]], type[Foo[int]])) +static_assert(is_assignable_to(TypeOf[Bar[bool]], type[Foo[int]])) +static_assert(is_assignable_to(TypeOf[Bar], type[Foo[int]])) +static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]])) +static_assert(is_assignable_to(TypeOf[Bar], type[Foo])) +static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[Any]])) +static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]])) + +# TODO: these should pass (all subscripts inside `type[]` type expressions are currently TODO types) +static_assert(not is_assignable_to(TypeOf[Bar[int]], type[Foo[bool]])) # error: [static-assert-error] +static_assert(not is_assignable_to(TypeOf[Foo[bool]], type[Bar[int]])) # error: [static-assert-error] +``` + +## `type[]` is not assignable to types disjoint from `builtins.type` + +```py +from typing import Any +from ty_extensions import is_assignable_to, static_assert + +static_assert(not is_assignable_to(type[Any], None)) ``` ## Class-literals that inherit from `Any` @@ -717,6 +745,53 @@ def f(x: int, y: str) -> None: ... c1: Callable[[int], None] = partial(f, y="a") ``` +### Generic classes with `__call__` + +```toml +[environment] +python-version = "3.12" +``` + +```py +from typing_extensions import Callable, Any, Generic, TypeVar, ParamSpec +from ty_extensions import static_assert, is_assignable_to + +T = TypeVar("T") +P = ParamSpec("P") + +class Foo[T]: + def __call__(self): ... + +class FooLegacy(Generic[T]): + def __call__(self): ... + +class Bar[T, **P]: + def __call__(self): ... + +# TODO: should not error +class BarLegacy(Generic[T, P]): # error: [invalid-argument-type] "`ParamSpec` is not a valid argument to `Generic`" + def __call__(self): ... + +static_assert(is_assignable_to(Foo, Callable[..., Any])) +static_assert(is_assignable_to(FooLegacy, Callable[..., Any])) +static_assert(is_assignable_to(Bar, Callable[..., Any])) +static_assert(is_assignable_to(BarLegacy, Callable[..., Any])) + +class Spam[T]: ... +class SpamLegacy(Generic[T]): ... +class Eggs[T, **P]: ... + +# TODO: should not error +class EggsLegacy(Generic[T, P]): ... # error: [invalid-argument-type] "`ParamSpec` is not a valid argument to `Generic`" + +static_assert(not is_assignable_to(Spam, Callable[..., Any])) +static_assert(not is_assignable_to(SpamLegacy, Callable[..., Any])) +static_assert(not is_assignable_to(Eggs, Callable[..., Any])) + +# TODO: should pass +static_assert(not is_assignable_to(EggsLegacy, Callable[..., Any])) # error: [static-assert-error] +``` + ### Classes with `__call__` as attribute An instance type is assignable to a compatible callable type if the instance type's class has a diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 22fa5316d5986f..9a2d1b9243e2ef 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -611,6 +611,10 @@ impl<'db> Type<'db> { matches!(self, Type::GenericAlias(_)) } + const fn is_dynamic(&self) -> bool { + matches!(self, Type::Dynamic(_)) + } + /// Replace references to the class `class` with a self-reference marker. This is currently /// used for recursive protocols, but could probably be extended to self-referential type- /// aliases and similar. @@ -1050,34 +1054,26 @@ impl<'db> Type<'db> { /// /// [subtype of]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { - // Two equivalent types are always subtypes of each other. - // - // "Equivalent to" here means that the two types are both fully static - // and describe exactly the same set of possible runtime objects. - // For example, `int` is a subtype of `int` because `int` and `int` are equivalent to each other. - // Equally, `type[object]` is a subtype of `type`, - // because the former type expresses "all subclasses of `object`" - // while the latter expresses "all instances of `type`", - // and these are exactly the same set of objects at runtime. - if self.is_equivalent_to(db, target) { - return true; - } + self.has_relation_to(db, target, TypeRelation::Subtyping) + } - // Non-fully-static types do not participate in subtyping. - // - // Type `A` can only be a subtype of type `B` if the set of possible runtime objects - // that `A` represents is a subset of the set of possible runtime objects that `B` represents. - // But the set of objects described by a non-fully-static type is (either partially or wholly) unknown, - // so the question is simply unanswerable for non-fully-static types. - if !self.is_fully_static(db) || !target.is_fully_static(db) { + /// Return true if this type is [assignable to] type `target`. + /// + /// [assignable to]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation + pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { + self.has_relation_to(db, target, TypeRelation::Assignability) + } + + fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool { + if !relation.applies_to(db, self, target) { return false; } + if relation.are_equivalent(db, self, target) { + return true; + } match (self, target) { - // We should have handled these immediately above. - (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => { - unreachable!("Non-fully-static types do not participate in subtyping!") - } + (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => true, // `Never` is the bottom type, the empty set. // It is a subtype of all other fully static types. @@ -1115,12 +1111,12 @@ impl<'db> Type<'db> { match typevar.bound_or_constraints(db) { None => unreachable!(), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - bound.is_subtype_of(db, target) + bound.has_relation_to(db, target, relation) } Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints .elements(db) .iter() - .all(|constraint| constraint.is_subtype_of(db, target)), + .all(|constraint| constraint.has_relation_to(db, target, relation)), } } @@ -1131,7 +1127,7 @@ impl<'db> Type<'db> { if typevar.constraints(db).is_some_and(|constraints| { constraints .iter() - .all(|constraint| self.is_subtype_of(db, *constraint)) + .all(|constraint| self.has_relation_to(db, *constraint, relation)) }) => { true @@ -1140,12 +1136,12 @@ impl<'db> Type<'db> { (Type::Union(union), _) => union .elements(db) .iter() - .all(|&elem_ty| elem_ty.is_subtype_of(db, target)), + .all(|&elem_ty| elem_ty.has_relation_to(db, target, relation)), (_, Type::Union(union)) => union .elements(db) .iter() - .any(|&elem_ty| self.is_subtype_of(db, elem_ty)), + .any(|&elem_ty| self.has_relation_to(db, elem_ty, relation)), // If both sides are intersections we need to handle the right side first // (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B, @@ -1154,7 +1150,7 @@ impl<'db> Type<'db> { intersection .positive(db) .iter() - .all(|&pos_ty| self.is_subtype_of(db, pos_ty)) + .all(|&pos_ty| self.has_relation_to(db, pos_ty, relation)) && intersection .negative(db) .iter() @@ -1164,7 +1160,7 @@ impl<'db> Type<'db> { (Type::Intersection(intersection), _) => intersection .positive(db) .iter() - .any(|&elem_ty| elem_ty.is_subtype_of(db, target)), + .any(|&elem_ty| elem_ty.has_relation_to(db, target, relation)), // Other than the special cases checked above, no other types are a subtype of a // typevar, since there's no guarantee what type the typevar will be specialized to. @@ -1179,7 +1175,7 @@ impl<'db> Type<'db> { (left, Type::AlwaysTruthy) => left.bool(db).is_always_true(), // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance). (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { - target.is_equivalent_to(db, Type::object(db)) + relation.are_equivalent(db, target, Type::object(db)) } // These clauses handle type variants that include function literals. A function @@ -1188,13 +1184,13 @@ impl<'db> Type<'db> { // applied to the signature. Different specializations of the same function literal are // only subtypes of each other if they result in the same signature. (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.is_subtype_of(db, target_function) + self_function.has_relation_to(db, target_function, relation) } (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { - self_method.is_subtype_of(db, target_method) + self_method.has_relation_to(db, target_method, relation) } (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { - self_method.is_subtype_of(db, target_method) + self_method.has_relation_to(db, target_method, relation) } // No literal type is a subtype of any other literal type, unless they are the same @@ -1216,6 +1212,31 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(_), ) => false, + (Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => { + let call_symbol = self + .member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) + .place; + // If the type of __call__ is a subtype of a callable type, this instance is. + // Don't add other special cases here; our subtyping of a callable type + // shouldn't get out of sync with the calls we will actually allow. + if let Place::Type(t, Boundness::Bound) = call_symbol { + t.has_relation_to(db, target, relation) + } else { + false + } + } + + (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { + left.has_relation_to(db, right, relation) + } + // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. + (Type::ProtocolInstance(_), _) => false, + (_, Type::ProtocolInstance(protocol)) => self.satisfies_protocol(db, protocol), + // All `StringLiteral` types are a subtype of `LiteralString`. (Type::StringLiteral(_), Type::LiteralString) => true, @@ -1231,45 +1252,37 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(_), _, ) => (self.literal_fallback_instance(db)) - .is_some_and(|instance| instance.is_subtype_of(db, target)), - - // Function-like callables are subtypes of `FunctionType` - (Type::Callable(callable), Type::NominalInstance(target)) - if callable.is_function_like(db) - && target.class.is_known(db, KnownClass::FunctionType) => - { - true - } + .is_some_and(|instance| instance.has_relation_to(db, target, relation)), (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { self_function_literal .into_callable_type(db) - .is_subtype_of(db, target) + .has_relation_to(db, target, relation) } (Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method .into_callable_type(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), // A `FunctionLiteral` type is a single-valued type like the other literals handled above, // so it also, for now, just delegates to its instance fallback. (Type::FunctionLiteral(_), _) => KnownClass::FunctionType .to_instance(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), // The same reasoning applies for these special callable types: (Type::BoundMethod(_), _) => KnownClass::MethodType .to_instance(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), (Type::MethodWrapper(_), _) => KnownClass::WrapperDescriptorType .to_instance(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), (Type::WrapperDescriptor(_), _) => KnownClass::WrapperDescriptorType .to_instance(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), (Type::Callable(self_callable), Type::Callable(other_callable)) => { - self_callable.is_subtype_of(db, other_callable) + self_callable.has_relation_to(db, other_callable, relation) } (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { @@ -1277,29 +1290,15 @@ impl<'db> Type<'db> { false } - (Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => { - let call_symbol = self - .member_lookup_with_policy( - db, - Name::new_static("__call__"), - MemberLookupPolicy::NO_INSTANCE_FALLBACK, - ) - .place; - // If the type of __call__ is a subtype of a callable type, this instance is. - // Don't add other special cases here; our subtyping of a callable type - // shouldn't get out of sync with the calls we will actually allow. - if let Place::Type(t, Boundness::Bound) = call_symbol { - t.is_subtype_of(db, target) - } else { - false - } - } - (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { - left.is_subtype_of(db, right) + // Function-like callables are subtypes of `FunctionType` + (Type::Callable(callable), _) + if callable.is_function_like(db) + && KnownClass::FunctionType + .to_instance(db) + .has_relation_to(db, target, relation) => + { + true } - // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. - (Type::ProtocolInstance(_), _) => false, - (_, Type::ProtocolInstance(protocol)) => self.satisfies_protocol(db, protocol), (Type::Callable(_), _) => { // TODO: Implement subtyping between callable types and other types like @@ -1319,54 +1318,81 @@ impl<'db> Type<'db> { self_elements.len() == target_elements.len() && self_elements.iter().zip(target_elements).all( |(self_element, target_element)| { - self_element.is_subtype_of(db, *target_element) + self_element.has_relation_to(db, *target_element, relation) }, ) } // `tuple[A, B, C]` is a subtype of `tuple[A | B | C, ...]` - (Type::Tuple(tuple), _) => tuple.homogeneous_supertype(db).is_subtype_of(db, target), + (Type::Tuple(tuple), _) => tuple + .homogeneous_supertype(db) + .has_relation_to(db, target, relation), - (Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target), - (Type::BoundSuper(_), _) => KnownClass::Super.to_instance(db).is_subtype_of(db, target), + (Type::BoundSuper(_), Type::BoundSuper(_)) => relation.are_equivalent(db, self, target), + (Type::BoundSuper(_), _) => KnownClass::Super + .to_instance(db) + .has_relation_to(db, target, relation), // `Literal[]` is a subtype of `type[B]` if `C` is a subclass of `B`, // since `type[B]` describes all possible runtime subclasses of the class object `B`. (Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() - .is_some_and(|target_class| class.is_subclass_of(db, None, target_class)), + .is_none_or(|subclass_of_class| { + ClassType::NonGeneric(class).has_relation_to(db, subclass_of_class, relation) + }), (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() - .is_some_and(|target_class| { - ClassType::from(alias).is_subclass_of(db, target_class) + .is_none_or(|subclass_of_class| { + ClassType::Generic(alias).has_relation_to(db, subclass_of_class, relation) }), // This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`? (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => { - self_subclass_ty.is_subtype_of(db, target_subclass_ty) + self_subclass_ty.has_relation_to(db, target_subclass_ty, relation) } (Type::ClassLiteral(class_literal), Type::Callable(_)) => { ClassType::NonGeneric(class_literal) .into_callable(db) - .is_subtype_of(db, target) + .has_relation_to(db, target, relation) } (Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias) .into_callable(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), // `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`. // `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object // is an instance of its metaclass `abc.ABCMeta`. - (Type::ClassLiteral(class), _) => { - class.metaclass_instance_type(db).is_subtype_of(db, target) - } + (Type::ClassLiteral(class), _) => class + .metaclass_instance_type(db) + .has_relation_to(db, target, relation), (Type::GenericAlias(alias), _) => ClassType::from(alias) .metaclass_instance_type(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), + + // This branch upholds two properties: + // - For any type `T` that is assignable to `type`, `T` shall be assignable to `type[Any]`. + // - For any type `T` that is assignable to `type`, `type[Any]` shall be assignable to `T`. + // + // This is really the same as the very first branch in this `match` statement that handles dynamic types. + // That branch upholds two properties: + // - For any type `S` that is assignable to `object` (which is _all_ types), `S` shall be assignable to `Any` + // - For any type `S` that is assignable to `object` (which is _all_ types), `Any` shall be assignable to `S`. + // + // The only difference between this branch and the first branch is that the first branch deals with the type + // `object & Any` (which simplifies to `Any`!) whereas this branch deals with the type `type & Any`. + // + // See also: + (Type::SubclassOf(subclass_of_ty), other) + | (other, Type::SubclassOf(subclass_of_ty)) + if subclass_of_ty.is_dynamic() + && other.has_relation_to(db, KnownClass::Type.to_instance(db), relation) => + { + true + } // `type[str]` (== `SubclassOf("str")` in ty) describes all possible runtime subclasses // of the class object `str`. It is a subtype of `type` (== `Instance("type")`) because `str` @@ -1379,30 +1405,31 @@ impl<'db> Type<'db> { .subclass_of() .into_class() .map(|class| class.metaclass_instance_type(db)) - .is_some_and(|metaclass_instance_type| { - metaclass_instance_type.is_subtype_of(db, target) - }), + .unwrap_or_else(|| KnownClass::Type.to_instance(db)) + .has_relation_to(db, target, relation), // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it // (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime. - (Type::SpecialForm(left), right) => left.instance_fallback(db).is_subtype_of(db, right), + (Type::SpecialForm(left), right) => left + .instance_fallback(db) + .has_relation_to(db, right, relation), - (Type::KnownInstance(left), right) => { - left.instance_fallback(db).is_subtype_of(db, right) - } + (Type::KnownInstance(left), right) => left + .instance_fallback(db) + .has_relation_to(db, right, relation), // `bool` is a subtype of `int`, because `bool` subclasses `int`, // which means that all instances of `bool` are also instances of `int` (Type::NominalInstance(self_instance), Type::NominalInstance(target_instance)) => { - self_instance.is_subtype_of(db, target_instance) + self_instance.has_relation_to(db, target_instance, relation) } (Type::PropertyInstance(_), _) => KnownClass::Property .to_instance(db) - .is_subtype_of(db, target), + .has_relation_to(db, target, relation), (_, Type::PropertyInstance(_)) => { - self.is_subtype_of(db, KnownClass::Property.to_instance(db)) + self.has_relation_to(db, KnownClass::Property.to_instance(db), relation) } // Other than the special cases enumerated above, `Instance` types and typevars are @@ -1411,292 +1438,6 @@ impl<'db> Type<'db> { } } - /// Return true if this type is [assignable to] type `target`. - /// - /// [assignable to]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation - pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { - if self.is_gradual_equivalent_to(db, target) { - return true; - } - - match (self, target) { - // Never can be assigned to any type. - (Type::Never, _) => true, - - // The dynamic type is assignable-to and assignable-from any type. - (Type::Dynamic(_), _) => true, - (_, Type::Dynamic(_)) => true, - - // All types are assignable to `object`. - // TODO this special case might be removable once the below cases are comprehensive - (_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true, - - // In general, a TypeVar `T` is not assignable to a type `S` unless one of the two conditions is satisfied: - // 1. `T` is a bound TypeVar and `T`'s upper bound is assignable to `S`. - // TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`. - // 2. `T` is a constrained TypeVar and all of `T`'s constraints are assignable to `S`. - // - // However, there is one exception to this general rule: for any given typevar `T`, - // `T` will always be assignable to any union containing `T`. - // A similar rule applies in reverse to intersection types. - (Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true, - (Type::Intersection(intersection), Type::TypeVar(_)) - if intersection.positive(db).contains(&target) => - { - true - } - (Type::Intersection(intersection), Type::TypeVar(_)) - if intersection.negative(db).contains(&target) => - { - false - } - - // A typevar is assignable to its upper bound, and to something similar to the union of - // its constraints. An unbound, unconstrained typevar has an implicit upper bound of - // `object` (which is handled above). - (Type::TypeVar(typevar), _) if typevar.bound_or_constraints(db).is_some() => { - match typevar.bound_or_constraints(db) { - None => unreachable!(), - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - bound.is_assignable_to(db, target) - } - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints - .elements(db) - .iter() - .all(|constraint| constraint.is_assignable_to(db, target)), - } - } - - // If the typevar is constrained, there must be multiple constraints, and the typevar - // might be specialized to any one of them. However, the constraints do not have to be - // disjoint, which means an lhs type might be assignable to all of the constraints. - (_, Type::TypeVar(typevar)) - if typevar.constraints(db).is_some_and(|constraints| { - constraints - .iter() - .all(|constraint| self.is_assignable_to(db, *constraint)) - }) => - { - true - } - - // A union is assignable to a type T iff every element of the union is assignable to T. - (Type::Union(union), ty) => union - .elements(db) - .iter() - .all(|&elem_ty| elem_ty.is_assignable_to(db, ty)), - - // A type T is assignable to a union iff T is assignable to any element of the union. - (ty, Type::Union(union)) => union - .elements(db) - .iter() - .any(|&elem_ty| ty.is_assignable_to(db, elem_ty)), - - // If both sides are intersections we need to handle the right side first - // (A & B & C) is assignable to (A & B) because the left is assignable to both A and B, - // but none of A, B, or C is assignable to (A & B). - // - // A type S is assignable to an intersection type T if - // S is assignable to all positive elements of T (e.g. `str & int` is assignable to `str & Any`), and - // S is disjoint from all negative elements of T (e.g. `int` is not assignable to Intersection[int, Not[Literal[1]]]). - (ty, Type::Intersection(intersection)) => { - intersection - .positive(db) - .iter() - .all(|&elem_ty| ty.is_assignable_to(db, elem_ty)) - && intersection - .negative(db) - .iter() - .all(|&neg_ty| ty.is_disjoint_from(db, neg_ty)) - } - - // An intersection type S is assignable to a type T if - // Any element of S is assignable to T (e.g. `A & B` is assignable to `A`) - // Negative elements do not have an effect on assignability - if S is assignable to T then S & ~P is also assignable to T. - (Type::Intersection(intersection), ty) => intersection - .positive(db) - .iter() - .any(|&elem_ty| elem_ty.is_assignable_to(db, ty)), - - // Other than the special cases checked above, no other types are assignable to a - // typevar, since there's no guarantee what type the typevar will be specialized to. - // (If the typevar is bounded, it might be specialized to a smaller type than the - // bound. This is true even if the bound is a final class, since the typevar can still - // be specialized to `Never`.) - (_, Type::TypeVar(_)) => false, - - // A tuple type S is assignable to a tuple type T if their lengths are the same, and - // each element of S is assignable to the corresponding element of T. - (Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => { - let self_elements = self_tuple.elements(db); - let target_elements = target_tuple.elements(db); - self_elements.len() == target_elements.len() - && self_elements.iter().zip(target_elements).all( - |(self_element, target_element)| { - self_element.is_assignable_to(db, *target_element) - }, - ) - } - - // This special case is required because the left-hand side tuple might be a - // gradual type, so we can not rely on subtyping. This allows us to assign e.g. - // `tuple[Any, int]` to `tuple`. - // - // `tuple[A, B, C]` is assignable to `tuple[A | B | C, ...]` - (Type::Tuple(tuple), _) - if tuple.homogeneous_supertype(db).is_assignable_to(db, target) => - { - true - } - - // These clauses handle type variants that include function literals. A function - // literal is assignable to itself, and not to any other function literal. However, our - // representation of a function literal includes any specialization that should be - // applied to the signature. Different specializations of the same function literal are - // only assignable to each other if they result in the same signature. - (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.is_assignable_to(db, target_function) - } - (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { - self_method.is_assignable_to(db, target_method) - } - (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { - self_method.is_assignable_to(db, target_method) - } - - // `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can - // materialize to any `type[...]` type. - (Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_)) - if subclass_of_ty.is_dynamic() => - { - true - } - - (Type::ClassLiteral(class), Type::SubclassOf(_)) - if class - .iter_mro(db, None) - .any(class_base::ClassBase::is_dynamic) => - { - true - } - - // Every `type[...]` is assignable to `type` - (Type::SubclassOf(_), _) - if KnownClass::Type - .to_instance(db) - .is_assignable_to(db, target) => - { - true - } - - // All `type[...]` types are assignable to `type[Any]`, because `type[Any]` can - // materialize to any `type[...]` type. - // - // Every class literal type is also assignable to `type[Any]`, because the class - // literal type for a class `C` is a subtype of `type[C]`, and `type[C]` is assignable - // to `type[Any]`. - ( - Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_), - Type::SubclassOf(target_subclass_of), - ) if target_subclass_of.is_dynamic() => true, - - // `type[Any]` is assignable to any type that `type[object]` is assignable to, because - // `type[Any]` can materialize to `type[object]`. - // - // `type[Any]` is also assignable to any subtype of `type[object]`, because all - // subtypes of `type[object]` are `type[...]` types (or `Never`), and `type[Any]` can - // materialize to any `type[...]` type (or to `type[Never]`, which is equivalent to - // `Never`.) - (Type::SubclassOf(subclass_of_ty), Type::NominalInstance(_)) - if subclass_of_ty.is_dynamic() - && (KnownClass::Type - .to_instance(db) - .is_assignable_to(db, target) - || target.is_subtype_of(db, KnownClass::Type.to_instance(db))) => - { - true - } - - // Any type that is assignable to `type[object]` is also assignable to `type[Any]`, - // because `type[Any]` can materialize to `type[object]`. - (Type::NominalInstance(_), Type::SubclassOf(subclass_of_ty)) - if subclass_of_ty.is_dynamic() - && self.is_assignable_to(db, KnownClass::Type.to_instance(db)) => - { - true - } - - (Type::NominalInstance(self_instance), Type::NominalInstance(target_instance)) => { - self_instance.is_assignable_to(db, target_instance) - } - - (Type::Callable(self_callable), Type::Callable(target_callable)) => { - self_callable.is_assignable_to(db, target_callable) - } - - (Type::NominalInstance(instance), Type::Callable(_)) - if instance.class.is_subclass_of_any_or_unknown(db) => - { - true - } - - (Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => { - let call_symbol = self - .member_lookup_with_policy( - db, - Name::new_static("__call__"), - MemberLookupPolicy::NO_INSTANCE_FALLBACK, - ) - .place; - // shouldn't get out of sync with the calls we will actually allow. - if let Place::Type(t, Boundness::Bound) = call_symbol { - t.is_assignable_to(db, target) - } else { - false - } - } - - _ if self - .literal_fallback_instance(db) - .is_some_and(|instance| instance.is_assignable_to(db, target)) => - { - true - } - - (Type::ClassLiteral(class_literal), Type::Callable(_)) => { - ClassType::NonGeneric(class_literal) - .into_callable(db) - .is_assignable_to(db, target) - } - - (Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias) - .into_callable(db) - .is_assignable_to(db, target), - - (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { - self_function_literal - .into_callable_type(db) - .is_assignable_to(db, target) - } - - (Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method - .into_callable_type(db) - .is_assignable_to(db, target), - - (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { - left.is_assignable_to(db, right) - } - // Other than the dynamic types such as `Any`/`Unknown`/`Todo` handled above, - // a protocol instance can never be assignable to a nominal type, - // with the *sole* exception of `object`. - (Type::ProtocolInstance(_), _) => false, - (_, Type::ProtocolInstance(protocol)) => self.satisfies_protocol(db, protocol), - - // TODO other types containing gradual forms - _ => self.is_subtype_of(db, target), - } - } - /// Return true if this type is [equivalent to] type `other`. /// /// This method returns `false` if either `self` or `other` is not fully static. @@ -7027,6 +6768,45 @@ impl<'db> ConstructorCallError<'db> { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) enum TypeRelation { + Subtyping, + Assignability, +} + +impl TypeRelation { + /// Non-fully-static types do not participate in subtyping, only assignability, + /// so the subtyping relation does not even apply to them. + /// + /// Type `A` can only be a subtype of type `B` if the set of possible runtime objects + /// that `A` represents is a subset of the set of possible runtime objects that `B` represents. + /// But the set of objects described by a non-fully-static type is (either partially or wholly) unknown, + /// so the question is simply unanswerable for non-fully-static types. + /// + /// However, the assignability relation applies to all types, even non-fully-static ones. + fn applies_to<'db>(self, db: &'db dyn Db, type_1: Type<'db>, type_2: Type<'db>) -> bool { + match self { + TypeRelation::Subtyping => type_1.is_fully_static(db) && type_2.is_fully_static(db), + TypeRelation::Assignability => true, + } + } + + /// Determine whether `type_1` and `type_2` are equivalent. + /// + /// Depending on whether the context is a subtyping test or an assignability test, + /// this method may call [`Type::is_equivalent_to`] or [`Type::is_assignable_to`]. + fn are_equivalent<'db>(self, db: &'db dyn Db, type_1: Type<'db>, type_2: Type<'db>) -> bool { + match self { + TypeRelation::Subtyping => type_1.is_equivalent_to(db, type_2), + TypeRelation::Assignability => type_1.is_gradual_equivalent_to(db, type_2), + } + } + + const fn applies_to_non_fully_static_types(self) -> bool { + matches!(self, TypeRelation::Assignability) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Truthiness { /// For an object `x`, `bool(x)` will always return `True` @@ -7139,26 +6919,16 @@ impl<'db> BoundMethodType<'db> { ) } - fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + fn has_relation_to(self, db: &'db dyn Db, other: Self, relation: TypeRelation) -> bool { // A bound method is a typically a subtype of itself. However, we must explicitly verify // the subtyping of the underlying function signatures (since they might be specialized // differently), and of the bound self parameter (taking care that parameters, including a // bound self parameter, are contravariant.) - self.function(db).is_subtype_of(db, other.function(db)) - && other - .self_instance(db) - .is_subtype_of(db, self.self_instance(db)) - } - - fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - // A bound method is a typically assignable to itself. However, we must explicitly verify - // the assignability of the underlying function signatures (since they might be specialized - // differently), and of the bound self parameter (taking care that parameters, including a - // bound self parameter, are contravariant.) - self.function(db).is_assignable_to(db, other.function(db)) + self.function(db) + .has_relation_to(db, other.function(db), relation) && other .self_instance(db) - .is_assignable_to(db, self.self_instance(db)) + .has_relation_to(db, self.self_instance(db), relation) } fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { @@ -7276,26 +7046,15 @@ impl<'db> CallableType<'db> { self.signatures(db).is_fully_static(db) } - /// Check whether this callable type is a subtype of another callable type. + /// Check whether this callable type has the given relation to another callable type. /// - /// See [`Type::is_subtype_of`] for more details. - fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - let self_is_function_like = self.is_function_like(db); - let other_is_function_like = other.is_function_like(db); - (self_is_function_like || !other_is_function_like) - && self.signatures(db).is_subtype_of(db, other.signatures(db)) - } - - /// Check whether this callable type is assignable to another callable type. - /// - /// See [`Type::is_assignable_to`] for more details. - fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - let self_is_function_like = self.is_function_like(db); - let other_is_function_like = other.is_function_like(db); - (self_is_function_like || !other_is_function_like) - && self - .signatures(db) - .is_assignable_to(db, other.signatures(db)) + /// See [`Type::is_subtype_of`] and [`Type::is_assignable_to`] for more details. + fn has_relation_to(self, db: &'db dyn Db, other: Self, relation: TypeRelation) -> bool { + if other.is_function_like(db) && !self.is_function_like(db) { + return false; + } + self.signatures(db) + .has_relation_to(db, other.signatures(db), relation) } /// Check whether this callable type is equivalent to another callable type. @@ -7348,50 +7107,17 @@ pub enum MethodWrapperKind<'db> { } impl<'db> MethodWrapperKind<'db> { - fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - match (self, other) { - ( - MethodWrapperKind::FunctionTypeDunderGet(self_function), - MethodWrapperKind::FunctionTypeDunderGet(other_function), - ) => self_function.is_subtype_of(db, other_function), - - ( - MethodWrapperKind::FunctionTypeDunderCall(self_function), - MethodWrapperKind::FunctionTypeDunderCall(other_function), - ) => self_function.is_subtype_of(db, other_function), - - (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) - | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) - | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { - self == other - } - - ( - MethodWrapperKind::FunctionTypeDunderGet(_) - | MethodWrapperKind::FunctionTypeDunderCall(_) - | MethodWrapperKind::PropertyDunderGet(_) - | MethodWrapperKind::PropertyDunderSet(_) - | MethodWrapperKind::StrStartswith(_), - MethodWrapperKind::FunctionTypeDunderGet(_) - | MethodWrapperKind::FunctionTypeDunderCall(_) - | MethodWrapperKind::PropertyDunderGet(_) - | MethodWrapperKind::PropertyDunderSet(_) - | MethodWrapperKind::StrStartswith(_), - ) => false, - } - } - - fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + fn has_relation_to(self, db: &'db dyn Db, other: Self, relation: TypeRelation) -> bool { match (self, other) { ( MethodWrapperKind::FunctionTypeDunderGet(self_function), MethodWrapperKind::FunctionTypeDunderGet(other_function), - ) => self_function.is_assignable_to(db, other_function), + ) => self_function.has_relation_to(db, other_function, relation), ( MethodWrapperKind::FunctionTypeDunderCall(self_function), MethodWrapperKind::FunctionTypeDunderCall(other_function), - ) => self_function.is_assignable_to(db, other_function), + ) => self_function.has_relation_to(db, other_function, relation), (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 344c2002379e27..bf5e6f494af7fd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -12,7 +12,7 @@ use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ - CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance, + CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeRelation, TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -29,8 +29,8 @@ use crate::{ place_table, semantic_index, use_def_map, }, types::{ - CallArgumentTypes, CallError, CallErrorKind, DynamicType, MetaclassCandidate, TupleType, - UnionBuilder, UnionType, definition_expression_type, + CallArgumentTypes, CallError, CallErrorKind, MetaclassCandidate, TupleType, UnionBuilder, + UnionType, definition_expression_type, }, }; use indexmap::IndexSet; @@ -340,23 +340,22 @@ impl<'db> ClassType<'db> { class_literal.is_final(db) } - /// Is this class a subclass of `Any` or `Unknown`? - pub(crate) fn is_subclass_of_any_or_unknown(self, db: &'db dyn Db) -> bool { - self.iter_mro(db).any(|base| { - matches!( - base, - ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown) - ) - }) - } - /// Return `true` if `other` is present in this class's MRO. pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { + self.has_relation_to(db, other, TypeRelation::Subtyping) + } + + pub(super) fn has_relation_to( + self, + db: &'db dyn Db, + other: Self, + relation: TypeRelation, + ) -> bool { self.iter_mro(db).any(|base| { match base { - // `is_subclass_of` is checking the subtype relation, in which gradual types do not - // participate. - ClassBase::Dynamic(_) => false, + ClassBase::Dynamic(_) => { + relation.applies_to_non_fully_static_types() && !other.is_final(db) + } // Protocol and Generic are not represented by a ClassType. ClassBase::Protocol | ClassBase::Generic => false, @@ -365,9 +364,11 @@ impl<'db> ClassType<'db> { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, (ClassType::Generic(base), ClassType::Generic(other)) => { base.origin(db) == other.origin(db) - && base - .specialization(db) - .is_subtype_of(db, other.specialization(db)) + && base.specialization(db).has_relation_to( + db, + other.specialization(db), + relation, + ) } (ClassType::Generic(_), ClassType::NonGeneric(_)) | (ClassType::NonGeneric(_), ClassType::Generic(_)) => false, @@ -390,30 +391,6 @@ impl<'db> ClassType<'db> { } } - pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - self.iter_mro(db).any(|base| { - match base { - ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown) => !other.is_final(db), - ClassBase::Dynamic(_) => false, - - // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic => false, - - ClassBase::Class(base) => match (base, other) { - (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, - (ClassType::Generic(base), ClassType::Generic(other)) => { - base.origin(db) == other.origin(db) - && base - .specialization(db) - .is_assignable_to(db, other.specialization(db)) - } - (ClassType::Generic(_), ClassType::NonGeneric(_)) - | (ClassType::NonGeneric(_), ClassType::Generic(_)) => false, - }, - } - }) - } - pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { match (self, other) { (ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other, diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index cf13ee5b384530..8e6e27a7efe264 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -279,10 +279,6 @@ impl<'db> ClassBase<'db> { } } } - - pub(crate) const fn is_dynamic(self) -> bool { - matches!(self, Self::Dynamic(_)) - } } impl<'db> From> for ClassBase<'db> { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index c6164e6d929cad..144a508b03d652 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -67,7 +67,9 @@ use crate::semantic_index::semantic_index; use crate::types::generics::GenericContext; use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; -use crate::types::{BoundMethodType, CallableType, Type, TypeMapping, TypeVarInstance}; +use crate::types::{ + BoundMethodType, CallableType, Type, TypeMapping, TypeRelation, TypeVarInstance, +}; use crate::{Db, FxOrderSet}; /// A collection of useful spans for annotating functions. @@ -707,6 +709,18 @@ impl<'db> FunctionType<'db> { Type::BoundMethod(BoundMethodType::new(db, self, self_instance)) } + pub(crate) fn has_relation_to( + self, + db: &'db dyn Db, + other: Self, + relation: TypeRelation, + ) -> bool { + match relation { + TypeRelation::Subtyping => self.is_subtype_of(db, other), + TypeRelation::Assignability => self.is_assignable_to(db, other), + } + } + pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { // A function type is the subtype of itself, and not of any other function type. However, // our representation of a function type includes any specialization that should be applied diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 4866b73efdabfe..a4ebf02a6987c6 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -9,7 +9,7 @@ use crate::types::class_base::ClassBase; use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - KnownInstanceType, Type, TypeMapping, TypeVarBoundOrConstraints, TypeVarInstance, + KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, declaration_type, todo_type, }; use crate::{Db, FxOrderSet}; @@ -358,7 +358,12 @@ impl<'db> Specialization<'db> { Self::new(db, self.generic_context(db), types) } - pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Specialization<'db>) -> bool { + pub(crate) fn has_relation_to( + self, + db: &'db dyn Db, + other: Self, + relation: TypeRelation, + ) -> bool { let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { return false; @@ -368,20 +373,31 @@ impl<'db> Specialization<'db> { .zip(self.types(db)) .zip(other.types(db)) { - if matches!(self_type, Type::Dynamic(_)) || matches!(other_type, Type::Dynamic(_)) { - return false; + if self_type.is_dynamic() || other_type.is_dynamic() { + match relation { + TypeRelation::Assignability => continue, + TypeRelation::Subtyping => return false, + } } - // Subtyping of each type in the specialization depends on the variance of the - // corresponding typevar: + // Subtyping/assignability of each type in the specialization depends on the variance + // of the corresponding typevar: // - covariant: verify that self_type <: other_type // - contravariant: verify that other_type <: self_type - // - invariant: verify that self_type == other_type - // - bivariant: skip, can't make subtyping false + // - invariant: verify that self_type <: other_type AND other_type <: self_type + // - bivariant: skip, can't make subtyping/assignability false let compatible = match typevar.variance(db) { - TypeVarVariance::Invariant => self_type.is_equivalent_to(db, *other_type), - TypeVarVariance::Covariant => self_type.is_subtype_of(db, *other_type), - TypeVarVariance::Contravariant => other_type.is_subtype_of(db, *self_type), + TypeVarVariance::Invariant => match relation { + TypeRelation::Subtyping => self_type.is_equivalent_to(db, *other_type), + TypeRelation::Assignability => { + self_type.is_assignable_to(db, *other_type) + && other_type.is_assignable_to(db, *self_type) + } + }, + TypeVarVariance::Covariant => self_type.has_relation_to(db, *other_type, relation), + TypeVarVariance::Contravariant => { + other_type.has_relation_to(db, *self_type, relation) + } TypeVarVariance::Bivariant => true, }; if !compatible { @@ -426,43 +442,6 @@ impl<'db> Specialization<'db> { true } - pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Specialization<'db>) -> bool { - let generic_context = self.generic_context(db); - if generic_context != other.generic_context(db) { - return false; - } - - for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter()) - .zip(self.types(db)) - .zip(other.types(db)) - { - if matches!(self_type, Type::Dynamic(_)) || matches!(other_type, Type::Dynamic(_)) { - continue; - } - - // Assignability of each type in the specialization depends on the variance of the - // corresponding typevar: - // - covariant: verify that self_type <: other_type - // - contravariant: verify that other_type <: self_type - // - invariant: verify that self_type <: other_type AND other_type <: self_type - // - bivariant: skip, can't make assignability false - let compatible = match typevar.variance(db) { - TypeVarVariance::Invariant => { - self_type.is_assignable_to(db, *other_type) - && other_type.is_assignable_to(db, *self_type) - } - TypeVarVariance::Covariant => self_type.is_assignable_to(db, *other_type), - TypeVarVariance::Contravariant => other_type.is_assignable_to(db, *self_type), - TypeVarVariance::Bivariant => true, - }; - if !compatible { - return false; - } - } - - true - } - pub(crate) fn is_gradual_equivalent_to( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index c62c6f0b4f6b86..40ba5d671972da 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -5,14 +5,16 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; use crate::place::{Boundness, Place, PlaceAndQualifiers}; -use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance}; +use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance}; use crate::{Db, FxOrderSet}; pub(super) use synthesized_protocol::SynthesizedProtocolType; impl<'db> Type<'db> { pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self { - if class.class_literal(db).0.is_protocol(db) { + if class.is_known(db, KnownClass::Any) { + Self::Dynamic(DynamicType::Any) + } else if class.class_literal(db).0.is_protocol(db) { Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) } else { Self::NominalInstance(NominalInstanceType::from_class(class)) @@ -78,19 +80,19 @@ impl<'db> NominalInstanceType<'db> { Self::from_class(self.class.normalized(db)) } - pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - // N.B. The subclass relation is fully static - self.class.is_subclass_of(db, other.class) + pub(super) fn has_relation_to( + self, + db: &'db dyn Db, + other: Self, + relation: TypeRelation, + ) -> bool { + self.class.has_relation_to(db, other.class, relation) } pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { self.class.is_equivalent_to(db, other.class) } - pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - self.class.is_assignable_to(db, other.class) - } - pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool { if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) { return true; @@ -254,16 +256,20 @@ impl<'db> ProtocolInstanceType<'db> { self.inner.interface(db).is_fully_static(db) } - /// Return `true` if this protocol type is a subtype of the protocol `other`. - pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - self.is_fully_static(db) && other.is_fully_static(db) && self.is_assignable_to(db, other) - } - - /// Return `true` if this protocol type is assignable to the protocol `other`. + /// Return `true` if this protocol type has the given type relation to the protocol `other`. /// /// TODO: consider the types of the members as well as their existence - pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - other + pub(super) fn has_relation_to( + self, + db: &'db dyn Db, + other: Self, + relation: TypeRelation, + ) -> bool { + relation.applies_to( + db, + Type::ProtocolInstance(self), + Type::ProtocolInstance(other), + ) && other .inner .interface(db) .is_sub_interface_of(db, self.inner.interface(db)) diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 1f440f782f576e..0e17986b1eee64 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -18,7 +18,7 @@ use smallvec::{SmallVec, smallvec}; use super::{DynamicType, Type, definition_expression_type}; use crate::semantic_index::definition::Definition; use crate::types::generics::GenericContext; -use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; +use crate::types::{ClassLiteral, TypeMapping, TypeRelation, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -98,11 +98,23 @@ impl<'db> CallableSignature<'db> { .all(|signature| signature.is_fully_static(db)) } + pub(crate) fn has_relation_to( + &self, + db: &'db dyn Db, + other: &Self, + relation: TypeRelation, + ) -> bool { + match relation { + TypeRelation::Subtyping => self.is_subtype_of(db, other), + TypeRelation::Assignability => self.is_assignable_to(db, other), + } + } + /// Check whether this callable type is a subtype of another callable type. /// /// See [`Type::is_subtype_of`] for more details. pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { - Self::is_assignable_to_impl( + Self::has_relation_to_impl( &self.overloads, &other.overloads, &|self_signature, other_signature| self_signature.is_subtype_of(db, other_signature), @@ -113,7 +125,7 @@ impl<'db> CallableSignature<'db> { /// /// See [`Type::is_assignable_to`] for more details. pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool { - Self::is_assignable_to_impl( + Self::has_relation_to_impl( &self.overloads, &other.overloads, &|self_signature, other_signature| self_signature.is_assignable_to(db, other_signature), @@ -124,7 +136,7 @@ impl<'db> CallableSignature<'db> { /// types. /// /// The `check_signature` closure is used to check the relation between two [`Signature`]s. - fn is_assignable_to_impl( + fn has_relation_to_impl( self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], check_signature: &F, @@ -140,7 +152,7 @@ impl<'db> CallableSignature<'db> { // `self` is possibly overloaded while `other` is definitely not overloaded. (_, [_]) => self_signatures.iter().any(|self_signature| { - Self::is_assignable_to_impl( + Self::has_relation_to_impl( std::slice::from_ref(self_signature), other_signatures, check_signature, @@ -149,7 +161,7 @@ impl<'db> CallableSignature<'db> { // `self` is definitely not overloaded while `other` is possibly overloaded. ([_], _) => other_signatures.iter().all(|other_signature| { - Self::is_assignable_to_impl( + Self::has_relation_to_impl( self_signatures, std::slice::from_ref(other_signature), check_signature, @@ -158,7 +170,7 @@ impl<'db> CallableSignature<'db> { // `self` is definitely overloaded while `other` is possibly overloaded. (_, _) => other_signatures.iter().all(|other_signature| { - Self::is_assignable_to_impl( + Self::has_relation_to_impl( self_signatures, std::slice::from_ref(other_signature), check_signature, diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index b2febf439be6c8..2143d2e1e25bbd 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,6 +1,7 @@ use crate::place::PlaceAndQualifiers; use crate::types::{ - ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeVarInstance, + ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation, + TypeVarInstance, }; use crate::{Db, FxOrderSet}; @@ -30,10 +31,14 @@ impl<'db> SubclassOfType<'db> { SubclassOfInner::Class(class) => { if class.is_final(db) { Type::from(class) - } else if class.is_object(db) { - KnownClass::Type.to_instance(db) } else { - Type::SubclassOf(Self { subclass_of }) + match class.known(db) { + Some(KnownClass::Object) => KnownClass::Type.to_instance(db), + Some(KnownClass::Any) => Type::SubclassOf(Self { + subclass_of: SubclassOfInner::Dynamic(DynamicType::Any), + }), + _ => Type::SubclassOf(Self { subclass_of }), + } } } } @@ -103,21 +108,23 @@ impl<'db> SubclassOfType<'db> { Type::from(self.subclass_of).find_name_in_mro_with_policy(db, name, policy) } - /// Return `true` if `self` is a subtype of `other`. - /// - /// This can only return `true` if `self.subclass_of` is a [`SubclassOfInner::Class`] variant; - /// only fully static types participate in subtyping. - pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool { + /// Return `true` if `self` has a certain relation to `other`. + pub(crate) fn has_relation_to( + self, + db: &'db dyn Db, + other: SubclassOfType<'db>, + relation: TypeRelation, + ) -> bool { match (self.subclass_of, other.subclass_of) { - // Non-fully-static types do not participate in subtyping - (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => false, + (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { + relation.applies_to_non_fully_static_types() + } // For example, `type[bool]` describes all possible runtime subclasses of the class `bool`, // and `type[int]` describes all possible runtime subclasses of the class `int`. // The first set is a subset of the second set, because `bool` is itself a subclass of `int`. (SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { - // N.B. The subclass relation is fully static - self_class.is_subclass_of(db, other_class) + self_class.has_relation_to(db, other_class, relation) } } } From 503427855df63cf3543c7adb7cf17e499412bbdd Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 7 Jun 2025 14:18:25 +0100 Subject: [PATCH 353/487] [ty] Enable more corpus tests (#18531) --- crates/ty_project/tests/check.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/ty_project/tests/check.rs b/crates/ty_project/tests/check.rs index 9d4d461ff531b1..ddb4a8718c665b 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_project/tests/check.rs @@ -59,7 +59,6 @@ fn linter_gz_no_panic() -> anyhow::Result<()> { } #[test] -#[ignore = "Enable running once there are fewer failures"] fn linter_stubs_no_panic() -> anyhow::Result<()> { let workspace_root = get_cargo_workspace_root()?; run_corpus_tests(&format!( @@ -68,7 +67,6 @@ fn linter_stubs_no_panic() -> anyhow::Result<()> { } #[test] -#[ignore = "Enable running over typeshed stubs once there are fewer failures"] fn typeshed_no_panic() -> anyhow::Result<()> { let workspace_root = get_cargo_workspace_root()?; run_corpus_tests(&format!( @@ -119,6 +117,11 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { let code = std::fs::read_to_string(source)?; let mut check_with_file_name = |path: &SystemPath| { + if DO_NOT_ATTEMPT.contains(&&*relative_path.as_str().replace('\\', "/")) { + println!("Skipping {relative_path:?} due to known stack overflow"); + return; + } + memory_fs.write_file_all(path, &code).unwrap(); File::sync_path(&mut db, path); @@ -298,4 +301,29 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // Fails with too-many-cycle-iterations due to a self-referential // type alias, see https://github.com/astral-sh/ty/issues/256 ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py", true, true), + + // These are all "expression should belong to this TypeInference region and TypeInferenceBuilder should have inferred a type for it" + ("crates/ty_vendored/vendor/typeshed/stdlib/abc.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/curses/__init__.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/pstats.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/signal.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi", true, true), + ("crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi", true, true), +]; + +/// Attempting to check one of these files causes a stack overflow +const DO_NOT_ATTEMPT: &[&str] = &[ + "crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi", + "crates/ty_vendored/vendor/typeshed/stdlib/types.pyi", + "crates/ty_vendored/vendor/typeshed/stdlib/wsgiref/types.pyi", ]; From b3b900dc1eb036065220b697ca66f781cc293fe8 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 7 Jun 2025 16:02:43 +0200 Subject: [PATCH 354/487] Treat `ty: ` comments as pragma comments (#18532) ## Summary Add support for ty's `ty:` pragma comments to ruff's formatter and E501 Fixes https://github.com/astral-sh/ruff/issues/18529 ## Test Plan Added test --- .../resources/test/fixtures/ruff/trailing_comments.py | 1 + .../tests/snapshots/format@trailing_comments.py.snap | 3 ++- crates/ruff_python_trivia/src/pragmas.rs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py index 3e7002456cf6f3..dfc5e61fd1e072 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py @@ -2,6 +2,7 @@ i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # NoQA: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # type: This shouldn't break +i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # ty: ignore This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pyright: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pylint: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa This shouldn't break diff --git a/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap index d3d09022ea7d1c..be5ad8e01f1188 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py -snapshot_kind: text --- ## Input ```python @@ -9,6 +8,7 @@ snapshot_kind: text i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # NoQA: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # type: This shouldn't break +i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # ty: ignore This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pyright: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pylint: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa This shouldn't break @@ -44,6 +44,7 @@ i = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # NoQA: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # type: This shouldn't break +i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # ty: ignore This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pyright: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # pylint: This shouldn't break i = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # noqa This shouldn't break diff --git a/crates/ruff_python_trivia/src/pragmas.rs b/crates/ruff_python_trivia/src/pragmas.rs index 7fe50d71a0eb89..4d4cfde98abc7b 100644 --- a/crates/ruff_python_trivia/src/pragmas.rs +++ b/crates/ruff_python_trivia/src/pragmas.rs @@ -26,5 +26,5 @@ pub fn is_pragma_comment(comment: &str) -> bool { // Case-sensitive match against a variety of pragmas that _do_ require a trailing colon. trimmed .split_once(':') - .is_some_and(|(maybe_pragma, _)| matches!(maybe_pragma, "isort" | "type" | "pyright" | "pylint" | "flake8" | "ruff")) + .is_some_and(|(maybe_pragma, _)| matches!(maybe_pragma, "isort" | "type" | "pyright" | "pylint" | "flake8" | "ruff" | "ty")) } From 95497ffaaba5bbceebda7c58c50a47292f9bdd39 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 7 Jun 2025 15:59:12 +0100 Subject: [PATCH 355/487] [ty] Fix panic when trying to pull types for attribute expressions inside `Literal` type expressions (#18535) --- crates/ty_project/resources/test/corpus/literal_slices.py | 6 ++++++ crates/ty_project/tests/check.rs | 4 ---- crates/ty_python_semantic/src/types/infer.rs | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 crates/ty_project/resources/test/corpus/literal_slices.py diff --git a/crates/ty_project/resources/test/corpus/literal_slices.py b/crates/ty_project/resources/test/corpus/literal_slices.py new file mode 100644 index 00000000000000..748762c0d1d344 --- /dev/null +++ b/crates/ty_project/resources/test/corpus/literal_slices.py @@ -0,0 +1,6 @@ +from typing import Literal + +class Format: + STRING = "string" + +def evaluate(format: Literal[Format.STRING]) -> str: ... diff --git a/crates/ty_project/tests/check.rs b/crates/ty_project/tests/check.rs index ddb4a8718c665b..e42df7228d7eb9 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_project/tests/check.rs @@ -304,18 +304,14 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // These are all "expression should belong to this TypeInference region and TypeInferenceBuilder should have inferred a type for it" ("crates/ty_vendored/vendor/typeshed/stdlib/abc.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/annotationlib.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/curses/__init__.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/dataclasses.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/inspect.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/pstats.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/signal.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi", true, true), diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 01d01143a50130..4992a3fcebb527 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9468,11 +9468,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => { let value_ty = self.infer_expression(value); // TODO: Check that value type is enum otherwise return None - value_ty + let ty = value_ty .member(self.db(), &attr.id) .place .ignore_possibly_unbound() - .unwrap_or(Type::unknown()) + .unwrap_or(Type::unknown()); + self.store_expression_type(parameters, ty); + ty } // for negative and positive numbers ast::Expr::UnaryOp(u) From 72552f31e40577f32195624279ead0a5364a82e0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 7 Jun 2025 16:26:10 +0100 Subject: [PATCH 356/487] [ty] Fix panic when pulling types for `UnaryOp` expressions inside `Literal` slices (#18536) --- .../ty_project/resources/test/corpus/literal_slices.py | 2 +- crates/ty_project/tests/check.rs | 9 --------- crates/ty_python_semantic/src/types/infer.rs | 4 +++- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/crates/ty_project/resources/test/corpus/literal_slices.py b/crates/ty_project/resources/test/corpus/literal_slices.py index 748762c0d1d344..b1b8ae6fe5bd62 100644 --- a/crates/ty_project/resources/test/corpus/literal_slices.py +++ b/crates/ty_project/resources/test/corpus/literal_slices.py @@ -3,4 +3,4 @@ class Format: STRING = "string" -def evaluate(format: Literal[Format.STRING]) -> str: ... +def evaluate(a: Literal[Format.STRING], b: Literal[-1]) -> str: ... diff --git a/crates/ty_project/tests/check.rs b/crates/ty_project/tests/check.rs index e42df7228d7eb9..ae1ab54cb628d3 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_project/tests/check.rs @@ -304,17 +304,8 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // These are all "expression should belong to this TypeInference region and TypeInferenceBuilder should have inferred a type for it" ("crates/ty_vendored/vendor/typeshed/stdlib/abc.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/ast.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi", true, true), ("crates/ty_vendored/vendor/typeshed/stdlib/curses/__init__.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/os/__init__.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/__init__.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/pstats.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/sqlite3/__init__.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/tempfile.pyi", true, true), ]; /// Attempting to check one of these files causes a stack overflow diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 4992a3fcebb527..6650552b1e87c9 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9481,7 +9481,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if matches!(u.op, ast::UnaryOp::USub | ast::UnaryOp::UAdd) && u.operand.is_number_literal_expr() => { - self.infer_unary_expression(u) + let ty = self.infer_unary_expression(u); + self.store_expression_type(parameters, ty); + ty } _ => { self.infer_expression(parameters); From 0c20010bb9c35ec2aa4dc6aa91f349c9d85a928e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 7 Jun 2025 18:43:28 +0200 Subject: [PATCH 357/487] [ty] Split CLI tests into multiple files (#18537) --- crates/ty/tests/cli.rs | 2207 --------------------- crates/ty/tests/cli/config_option.rs | 206 ++ crates/ty/tests/cli/exit_code.rs | 272 +++ crates/ty/tests/cli/main.rs | 690 +++++++ crates/ty/tests/cli/python_environment.rs | 774 ++++++++ crates/ty/tests/cli/rule_selection.rs | 292 +++ 6 files changed, 2234 insertions(+), 2207 deletions(-) delete mode 100644 crates/ty/tests/cli.rs create mode 100644 crates/ty/tests/cli/config_option.rs create mode 100644 crates/ty/tests/cli/exit_code.rs create mode 100644 crates/ty/tests/cli/main.rs create mode 100644 crates/ty/tests/cli/python_environment.rs create mode 100644 crates/ty/tests/cli/rule_selection.rs diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs deleted file mode 100644 index 9fda2345dac297..00000000000000 --- a/crates/ty/tests/cli.rs +++ /dev/null @@ -1,2207 +0,0 @@ -use anyhow::Context; -use insta::internals::SettingsBindDropGuard; -use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; -use ruff_python_ast::PythonVersion; -use std::fmt::Write; -use std::path::{Path, PathBuf}; -use std::process::Command; -use tempfile::TempDir; - -#[test] -fn test_run_in_sub_directory() -> anyhow::Result<()> { - let case = TestCase::with_files([("test.py", "~"), ("subdir/nothing", "")])?; - assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> /test.py:1:2 - | - 1 | ~ - | ^ Expected an expression - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - Ok(()) -} - -#[test] -fn test_include_hidden_files_by_default() -> anyhow::Result<()> { - let case = TestCase::with_files([(".test.py", "~")])?; - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> .test.py:1:2 - | - 1 | ~ - | ^ Expected an expression - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - Ok(()) -} - -#[test] -fn test_respect_ignore_files() -> anyhow::Result<()> { - // First test that the default option works correctly (the file is skipped) - let case = TestCase::with_files([(".ignore", "test.py"), ("test.py", "~")])?; - assert_cmd_snapshot!(case.command(), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - WARN No python files found under the given path(s) - "); - - // Test that we can set to false via CLI - assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> test.py:1:2 - | - 1 | ~ - | ^ Expected an expression - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Test that we can set to false via config file - case.write_file("ty.toml", "src.respect-ignore-files = false")?; - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> test.py:1:2 - | - 1 | ~ - | ^ Expected an expression - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Ensure CLI takes precedence - case.write_file("ty.toml", "src.respect-ignore-files = true")?; - assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> test.py:1:2 - | - 1 | ~ - | ^ Expected an expression - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - Ok(()) -} -/// Specifying an option on the CLI should take precedence over the same setting in the -/// project's configuration. Here, this is tested for the Python version. -#[test] -fn config_override_python_version() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python-version = "3.11" - "#, - ), - ( - "test.py", - r#" - import sys - - # Access `sys.last_exc` that was only added in Python 3.12 - print(sys.last_exc) - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-attribute]: Type `` has no attribute `last_exc` - --> test.py:5:7 - | - 4 | # Access `sys.last_exc` that was only added in Python 3.12 - 5 | print(sys.last_exc) - | ^^^^^^^^^^^^ - | - info: rule `unresolved-attribute` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// Same as above, but for the Python platform. -#[test] -fn config_override_python_platform() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python-platform = "linux" - "#, - ), - ( - "test.py", - r#" - import sys - from typing_extensions import reveal_type - - reveal_type(sys.platform) - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type]: Revealed type - --> test.py:5:13 - | - 3 | from typing_extensions import reveal_type - 4 | - 5 | reveal_type(sys.platform) - | ^^^^^^^^^^^^ `Literal["linux"]` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "#); - - assert_cmd_snapshot!(case.command().arg("--python-platform").arg("all"), @r" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type]: Revealed type - --> test.py:5:13 - | - 3 | from typing_extensions import reveal_type - 4 | - 5 | reveal_type(sys.platform) - | ^^^^^^^^^^^^ `LiteralString` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn config_file_annotation_showing_where_python_version_set_typing_error() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python-version = "3.8" - "#, - ), - ( - "test.py", - r#" - aiter - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `aiter` used when not defined - --> test.py:2:1 - | - 2 | aiter - | ^^^^^ - | - info: `aiter` was added as a builtin in Python 3.10 - info: Python 3.8 was assumed when resolving types - --> pyproject.toml:3:18 - | - 2 | [tool.ty.environment] - 3 | python-version = "3.8" - | ^^^^^ Python 3.8 assumed due to this configuration setting - | - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "#); - - assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `aiter` used when not defined - --> test.py:2:1 - | - 2 | aiter - | ^^^^^ - | - info: `aiter` was added as a builtin in Python 3.10 - info: Python 3.9 was assumed when resolving types because it was specified on the command line - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python = "venv" - "#, - ), - ( - "venv/pyvenv.cfg", - r#" - version = 3.8 - home = foo/bar/bin - "#, - ), - if cfg!(target_os = "windows") { - ("foo/bar/bin/python.exe", "") - } else { - ("foo/bar/bin/python", "") - }, - if cfg!(target_os = "windows") { - ("venv/Lib/site-packages/foo.py", "") - } else { - ("venv/lib/python3.8/site-packages/foo.py", "") - }, - ("test.py", "aiter"), - ])?; - - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `aiter` used when not defined - --> test.py:1:1 - | - 1 | aiter - | ^^^^^ - | - info: `aiter` was added as a builtin in Python 3.10 - info: Python 3.8 was assumed when resolving types because of your virtual environment - --> venv/pyvenv.cfg:2:11 - | - 2 | version = 3.8 - | ^^^ Python version inferred from virtual environment metadata file - 3 | home = foo/bar/bin - | - info: No Python version was specified on the command line or in a configuration file - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python = "venv" - "#, - ), - ( - "venv/pyvenv.cfg", - r#"home = foo/bar/bin - - - version = 3.8"#, - ), - if cfg!(target_os = "windows") { - ("foo/bar/bin/python.exe", "") - } else { - ("foo/bar/bin/python", "") - }, - if cfg!(target_os = "windows") { - ("venv/Lib/site-packages/foo.py", "") - } else { - ("venv/lib/python3.8/site-packages/foo.py", "") - }, - ("test.py", "aiter"), - ])?; - - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `aiter` used when not defined - --> test.py:1:1 - | - 1 | aiter - | ^^^^^ - | - info: `aiter` was added as a builtin in Python 3.10 - info: Python 3.8 was assumed when resolving types because of your virtual environment - --> venv/pyvenv.cfg:4:23 - | - 4 | version = 3.8 - | ^^^ Python version inferred from virtual environment metadata file - | - info: No Python version was specified on the command line or in a configuration file - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn config_file_annotation_showing_where_python_version_set_syntax_error() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [project] - requires-python = ">=3.8" - "#, - ), - ( - "test.py", - r#" - match object(): - case int(): - pass - case _: - pass - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> test.py:2:1 - | - 2 | match object(): - | ^^^^^ Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10) - 3 | case int(): - 4 | pass - | - info: Python 3.8 was assumed when parsing syntax - --> pyproject.toml:3:19 - | - 2 | [project] - 3 | requires-python = ">=3.8" - | ^^^^^^^ Python 3.8 assumed due to this configuration setting - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "#); - - assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[invalid-syntax] - --> test.py:2:1 - | - 2 | match object(): - | ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) - 3 | case int(): - 4 | pass - | - info: Python 3.9 was assumed when parsing syntax because it was specified on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// Paths specified on the CLI are relative to the current working directory and not the project root. -/// -/// We test this by adding an extra search path from the CLI to the libs directory when -/// running the CLI from the child directory (using relative paths). -/// -/// Project layout: -/// ``` -/// - libs -/// |- utils.py -/// - child -/// | - test.py -/// - pyproject.toml -/// ``` -/// -/// And the command is run in the `child` directory. -#[test] -fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python-version = "3.11" - "#, - ), - ( - "libs/utils.py", - r#" - def add(a: int, b: int) -> int: - return a + b - "#, - ), - ( - "child/test.py", - r#" - from utils import add - - stat = add(10, 15) - "#, - ), - ])?; - - // Make sure that the CLI fails when the `libs` directory is not in the search path. - assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-import]: Cannot resolve imported module `utils` - --> test.py:2:6 - | - 2 | from utils import add - | ^^^^^ - 3 | - 4 | stat = add(10, 15) - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")).arg("--extra-search-path").arg("../libs"), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// Paths specified in a configuration file are relative to the project root. -/// -/// We test this by adding `libs` (as a relative path) to the extra search path in the configuration and run -/// the CLI from a subdirectory. -/// -/// Project layout: -/// ``` -/// - libs -/// |- utils.py -/// - child -/// | - test.py -/// - pyproject.toml -/// ``` -#[test] -fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python-version = "3.11" - extra-paths = ["libs"] - "#, - ), - ( - "libs/utils.py", - r#" - def add(a: int, b: int) -> int: - return a + b - "#, - ), - ( - "child/test.py", - r#" - from utils import add - - stat = add(10, 15) - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// The rule severity can be changed in the configuration file -#[test] -fn configuration_rule_severity() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - y = 4 / 0 - - for a in range(0, int(y)): - x = a - - prin(x) # unresolved-reference - "#, - )?; - - // Assert that there's an `unresolved-reference` diagnostic (error). - assert_cmd_snapshot!(case.command(), @r###" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `prin` used when not defined - --> test.py:7:1 - | - 5 | x = a - 6 | - 7 | prin(x) # unresolved-reference - | ^^^^ - | - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "###); - - case.write_file( - "pyproject.toml", - r#" - [tool.ty.rules] - division-by-zero = "warn" # promote to warn - unresolved-reference = "ignore" - "#, - )?; - - assert_cmd_snapshot!(case.command(), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero - --> test.py:2:5 - | - 2 | y = 4 / 0 - | ^^^^^ - 3 | - 4 | for a in range(0, int(y)): - | - info: rule `division-by-zero` was selected in the configuration file - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// The rule severity can be changed using `--ignore`, `--warn`, and `--error` -#[test] -fn cli_rule_severity() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - import does_not_exit - - y = 4 / 0 - - for a in range(0, int(y)): - x = a - - prin(x) # unresolved-reference - "#, - )?; - - // Assert that there's an `unresolved-reference` diagnostic (error) - // and an unresolved-import (error) diagnostic by default. - assert_cmd_snapshot!(case.command(), @r###" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-import]: Cannot resolve imported module `does_not_exit` - --> test.py:2:8 - | - 2 | import does_not_exit - | ^^^^^^^^^^^^^ - 3 | - 4 | y = 4 / 0 - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - error[unresolved-reference]: Name `prin` used when not defined - --> test.py:9:1 - | - 7 | x = a - 8 | - 9 | prin(x) # unresolved-reference - | ^^^^ - | - info: rule `unresolved-reference` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "###); - - assert_cmd_snapshot!( - case - .command() - .arg("--ignore") - .arg("unresolved-reference") - .arg("--warn") - .arg("division-by-zero") - .arg("--warn") - .arg("unresolved-import"), - @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unresolved-import]: Cannot resolve imported module `does_not_exit` - --> test.py:2:8 - | - 2 | import does_not_exit - | ^^^^^^^^^^^^^ - 3 | - 4 | y = 4 / 0 - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` was selected on the command line - - warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero - --> test.py:4:5 - | - 2 | import does_not_exit - 3 | - 4 | y = 4 / 0 - | ^^^^^ - 5 | - 6 | for a in range(0, int(y)): - | - info: rule `division-by-zero` was selected on the command line - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - " - ); - - Ok(()) -} - -/// The rule severity can be changed using `--ignore`, `--warn`, and `--error` and -/// values specified last override previous severities. -#[test] -fn cli_rule_severity_precedence() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - y = 4 / 0 - - for a in range(0, int(y)): - x = a - - prin(x) # unresolved-reference - "#, - )?; - - // Assert that there's a `unresolved-reference` diagnostic (error) by default. - assert_cmd_snapshot!(case.command(), @r###" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `prin` used when not defined - --> test.py:7:1 - | - 5 | x = a - 6 | - 7 | prin(x) # unresolved-reference - | ^^^^ - | - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "###); - - assert_cmd_snapshot!( - case - .command() - .arg("--warn") - .arg("unresolved-reference") - .arg("--warn") - .arg("division-by-zero") - .arg("--ignore") - .arg("unresolved-reference"), - @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero - --> test.py:2:5 - | - 2 | y = 4 / 0 - | ^^^^^ - 3 | - 4 | for a in range(0, int(y)): - | - info: rule `division-by-zero` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - " - ); - - Ok(()) -} - -/// ty warns about unknown rules specified in a configuration file -#[test] -fn configuration_unknown_rules() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.rules] - division-by-zer = "warn" # incorrect rule name - "#, - ), - ("test.py", "print(10)"), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: true - exit_code: 0 - ----- stdout ----- - warning[unknown-rule] - --> pyproject.toml:3:1 - | - 2 | [tool.ty.rules] - 3 | division-by-zer = "warn" # incorrect rule name - | ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "#); - - Ok(()) -} - -/// ty warns about unknown rules specified in a CLI argument -#[test] -fn cli_unknown_rules() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", "print(10)")?; - - assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unknown-rule]: Unknown lint rule `division-by-zer` - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { - let path_to_executable = if cfg!(windows) { - "my-venv/Scripts/python.exe" - } else { - "my-venv/bin/python" - }; - - let other_venv_path = "my-venv/foo/some_other_file.txt"; - - let case = TestCase::with_files([ - ("test.py", ""), - ( - if cfg!(windows) { - "my-venv/Lib/site-packages/foo.py" - } else { - "my-venv/lib/python3.13/site-packages/foo.py" - }, - "", - ), - (path_to_executable, ""), - (other_venv_path, ""), - ])?; - - // Passing a path to the installation works - assert_cmd_snapshot!(case.command().arg("--python").arg("my-venv"), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // And so does passing a path to the executable inside the installation - assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // But random other paths inside the installation are rejected - assert_cmd_snapshot!(case.command().arg("--python").arg(other_venv_path), @r" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - ty failed - Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/my-venv/foo/some_other_file.txt`: does not point to a Python executable or a directory on disk - "); - - // And so are paths that do not exist on disk - assert_cmd_snapshot!(case.command().arg("--python").arg("not-a-directory-or-executable"), @r" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - ty failed - Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/not-a-directory-or-executable`: does not point to a Python executable or a directory on disk - "); - - Ok(()) -} - -#[test] -fn python_cli_argument_system_installation() -> anyhow::Result<()> { - let path_to_executable = if cfg!(windows) { - "Python3.11/python.exe" - } else { - "Python3.11/bin/python" - }; - - let case = TestCase::with_files([ - ("test.py", ""), - ( - if cfg!(windows) { - "Python3.11/Lib/site-packages/foo.py" - } else { - "Python3.11/lib/python3.11/site-packages/foo.py" - }, - "", - ), - (path_to_executable, ""), - ])?; - - // Passing a path to the installation works - assert_cmd_snapshot!(case.command().arg("--python").arg("Python3.11"), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // And so does passing a path to the executable inside the installation - assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn config_file_broken_python_setting() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [project] - name = "test" - version = "0.1.0" - description = "Some description" - readme = "README.md" - requires-python = ">=3.13" - dependencies = [] - - [tool.ty.environment] - python = "not-a-directory-or-executable" - "#, - ), - ("test.py", ""), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - ty failed - Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting - - --> Invalid setting in configuration file `/pyproject.toml` - | - 9 | - 10 | [tool.ty.environment] - 11 | python = "not-a-directory-or-executable" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not point to a Python executable or a directory on disk - | - "#); - - Ok(()) -} - -#[test] -fn config_file_python_setting_directory_with_no_site_packages() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python = "directory-but-no-site-packages" - "#, - ), - ("directory-but-no-site-packages/lib/foo.py", ""), - ("test.py", ""), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - ty failed - Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting - - --> Invalid setting in configuration file `/pyproject.toml` - | - 1 | - 2 | [tool.ty.environment] - 3 | python = "directory-but-no-site-packages" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find a `site-packages` directory for this Python installation/executable - | - "#); - - Ok(()) -} - -// This error message is never emitted on Windows, because Windows installations have simpler layouts -#[cfg(not(windows))] -#[test] -fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "pyproject.toml", - r#" - [tool.ty.environment] - python = "directory-but-no-site-packages" - "#, - ), - ("directory-but-no-site-packages/foo.py", ""), - ("test.py", ""), - ])?; - - assert_cmd_snapshot!(case.command(), @r#" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - ty failed - Cause: Invalid search path settings - Cause: Failed to discover the site-packages directory: Failed to iterate over the contents of the `lib` directory of the Python installation - - --> Invalid setting in configuration file `/pyproject.toml` - | - 1 | - 2 | [tool.ty.environment] - 3 | python = "directory-but-no-site-packages" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - "#); - - Ok(()) -} - -#[test] -fn exit_code_only_warnings() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; - - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_only_info() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - from typing_extensions import reveal_type - reveal_type(1) - "#, - )?; - - assert_cmd_snapshot!(case.command(), @r" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type]: Revealed type - --> test.py:3:13 - | - 2 | from typing_extensions import reveal_type - 3 | reveal_type(1) - | ^ `Literal[1]` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - from typing_extensions import reveal_type - reveal_type(1) - "#, - )?; - - assert_cmd_snapshot!(case.command().arg("--error-on-warning"), @r" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type]: Revealed type - --> test.py:3:13 - | - 2 | from typing_extensions import reveal_type - 3 | reveal_type(1) - | ^ `Literal[1]` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; - - assert_cmd_snapshot!(case.command().arg("--error-on-warning").arg("--warn").arg("unresolved-reference"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ("test.py", r"print(x) # [unresolved-reference]"), - ( - "ty.toml", - r#" - [terminal] - error-on-warning = true - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - print(x) # [unresolved-reference] - print(4[1]) # [non-subscriptable] - "#, - )?; - - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:2:7 - | - 2 | print(x) # [unresolved-reference] - | ^ - 3 | print(4[1]) # [non-subscriptable] - | - info: rule `unresolved-reference` was selected on the command line - - error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method - --> test.py:3:7 - | - 2 | print(x) # [unresolved-reference] - 3 | print(4[1]) # [non-subscriptable] - | ^ - | - info: rule `non-subscriptable` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r###" - print(x) # [unresolved-reference] - print(4[1]) # [non-subscriptable] - "###, - )?; - - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--error-on-warning"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:2:7 - | - 2 | print(x) # [unresolved-reference] - | ^ - 3 | print(4[1]) # [non-subscriptable] - | - info: rule `unresolved-reference` was selected on the command line - - error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method - --> test.py:3:7 - | - 2 | print(x) # [unresolved-reference] - 3 | print(4[1]) # [non-subscriptable] - | ^ - | - info: rule `non-subscriptable` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn exit_code_exit_zero_is_true() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - print(x) # [unresolved-reference] - print(4[1]) # [non-subscriptable] - "#, - )?; - - assert_cmd_snapshot!(case.command().arg("--exit-zero").arg("--warn").arg("unresolved-reference"), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:2:7 - | - 2 | print(x) # [unresolved-reference] - | ^ - 3 | print(4[1]) # [non-subscriptable] - | - info: rule `unresolved-reference` was selected on the command line - - error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method - --> test.py:3:7 - | - 2 | print(x) # [unresolved-reference] - 3 | print(4[1]) # [non-subscriptable] - | ^ - | - info: rule `non-subscriptable` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn user_configuration() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "project/ty.toml", - r#" - [rules] - division-by-zero = "warn" - "#, - ), - ( - "project/main.py", - r#" - y = 4 / 0 - - for a in range(0, int(y)): - x = a - - prin(x) - "#, - ), - ])?; - - let config_directory = case.root().join("home/.config"); - let config_env_var = if cfg!(windows) { - "APPDATA" - } else { - "XDG_CONFIG_HOME" - }; - - assert_cmd_snapshot!( - case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), - @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero - --> main.py:2:5 - | - 2 | y = 4 / 0 - | ^^^^^ - 3 | - 4 | for a in range(0, int(y)): - | - info: rule `division-by-zero` was selected in the configuration file - - error[unresolved-reference]: Name `prin` used when not defined - --> main.py:7:1 - | - 5 | x = a - 6 | - 7 | prin(x) - | ^^^^ - | - info: rule `unresolved-reference` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - " - ); - - // The user-level configuration sets the severity for `unresolved-reference` to warn. - // Changing the level for `division-by-zero` has no effect, because the project-level configuration - // has higher precedence. - case.write_file( - config_directory.join("ty/ty.toml"), - r#" - [rules] - division-by-zero = "error" - unresolved-reference = "warn" - "#, - )?; - - assert_cmd_snapshot!( - case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), - @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero - --> main.py:2:5 - | - 2 | y = 4 / 0 - | ^^^^^ - 3 | - 4 | for a in range(0, int(y)): - | - info: rule `division-by-zero` was selected in the configuration file - - warning[unresolved-reference]: Name `prin` used when not defined - --> main.py:7:1 - | - 5 | x = a - 6 | - 7 | prin(x) - | ^^^^ - | - info: rule `unresolved-reference` was selected in the configuration file - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - " - ); - - Ok(()) -} - -#[test] -fn check_specific_paths() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "project/main.py", - r#" - y = 4 / 0 # error: division-by-zero - "#, - ), - ( - "project/tests/test_main.py", - r#" - import does_not_exist # error: unresolved-import - "#, - ), - ( - "project/other.py", - r#" - from main2 import z # error: unresolved-import - - print(z) - "#, - ), - ])?; - - assert_cmd_snapshot!( - case.command(), - @r###" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-import]: Cannot resolve imported module `main2` - --> project/other.py:2:6 - | - 2 | from main2 import z # error: unresolved-import - | ^^^^^ - 3 | - 4 | print(z) - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - error[unresolved-import]: Cannot resolve imported module `does_not_exist` - --> project/tests/test_main.py:2:8 - | - 2 | import does_not_exist # error: unresolved-import - | ^^^^^^^^^^^^^^ - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "### - ); - - // Now check only the `tests` and `other.py` files. - // We should no longer see any diagnostics related to `main.py`. - assert_cmd_snapshot!( - case.command().arg("project/tests").arg("project/other.py"), - @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-import]: Cannot resolve imported module `main2` - --> project/other.py:2:6 - | - 2 | from main2 import z # error: unresolved-import - | ^^^^^ - 3 | - 4 | print(z) - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - error[unresolved-import]: Cannot resolve imported module `does_not_exist` - --> project/tests/test_main.py:2:8 - | - 2 | import does_not_exist # error: unresolved-import - | ^^^^^^^^^^^^^^ - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - " - ); - - Ok(()) -} - -#[test] -fn check_non_existing_path() -> anyhow::Result<()> { - let case = TestCase::with_files([])?; - - let mut settings = insta::Settings::clone_current(); - settings.add_filter( - ®ex::escape("The system cannot find the path specified. (os error 3)"), - "No such file or directory (os error 2)", - ); - let _s = settings.bind_to_scope(); - - assert_cmd_snapshot!( - case.command().arg("project/main.py").arg("project/tests"), - @r" - success: false - exit_code: 1 - ----- stdout ----- - error[io]: `/project/main.py`: No such file or directory (os error 2) - - error[io]: `/project/tests`: No such file or directory (os error 2) - - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - WARN No python files found under the given path(s) - " - ); - - Ok(()) -} - -#[test] -fn concise_diagnostics() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - print(x) # [unresolved-reference] - print(4[1]) # [non-subscriptable] - "#, - )?; - - assert_cmd_snapshot!(case.command().arg("--output-format=concise").arg("--warn").arg("unresolved-reference"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference] test.py:2:7: Name `x` used when not defined - error[non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method - Found 2 diagnostics - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -/// This tests the diagnostic format for revealed type. -/// -/// This test was introduced because changes were made to -/// how the revealed type diagnostic was constructed and -/// formatted in "verbose" mode. But it required extra -/// logic to ensure the concise version didn't regress on -/// information content. So this test was introduced to -/// capture that. -#[test] -fn concise_revealed_type() -> anyhow::Result<()> { - let case = TestCase::with_file( - "test.py", - r#" - from typing_extensions import reveal_type - - x = "hello" - reveal_type(x) - "#, - )?; - - assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r#" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type] test.py:5:13: Revealed type: `Literal["hello"]` - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "#); - - Ok(()) -} - -#[test] -fn can_handle_large_binop_expressions() -> anyhow::Result<()> { - let mut content = String::new(); - writeln!( - &mut content, - " - from typing_extensions import reveal_type - total = 1{plus_one_repeated} - reveal_type(total) - ", - plus_one_repeated = " + 1".repeat(2000 - 1) - )?; - - let case = TestCase::with_file("test.py", &ruff_python_trivia::textwrap::dedent(&content))?; - - assert_cmd_snapshot!(case.command(), @r" - success: true - exit_code: 0 - ----- stdout ----- - info[revealed-type]: Revealed type - --> test.py:4:13 - | - 2 | from typing_extensions import reveal_type - 3 | total = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1... - 4 | reveal_type(total) - | ^^^^^ `Literal[2000]` - | - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn defaults_to_a_new_python_version() -> anyhow::Result<()> { - let case = TestCase::with_files([ - ( - "ty.toml", - &*format!( - r#" - [environment] - python-version = "{}" - python-platform = "linux" - "#, - PythonVersion::default() - ), - ), - ( - "main.py", - r#" - import os - - os.grantpt(1) # only available on unix, Python 3.13 or newer - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-attribute]: Type `` has no attribute `grantpt` - --> main.py:4:1 - | - 2 | import os - 3 | - 4 | os.grantpt(1) # only available on unix, Python 3.13 or newer - | ^^^^^^^^^^ - | - info: rule `unresolved-attribute` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Use default (which should be latest supported) - let case = TestCase::with_files([ - ( - "ty.toml", - r#" - [environment] - python-platform = "linux" - "#, - ), - ( - "main.py", - r#" - import os - - os.grantpt(1) # only available on unix, Python 3.13 or newer - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command(), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; - - // Long flag - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Short flag - assert_cmd_snapshot!(case.command().arg("-c").arg("terminal.error-on-warning=true"), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> { - let case = TestCase::with_files(vec![ - ( - "ty.toml", - r#" - [terminal] - error-on-warning = true - "#, - ), - ("test.py", r"print(x) # [unresolved-reference]"), - ])?; - - // Exit code of 1 due to the setting in `ty.toml` - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Exit code of 0 because the `ty.toml` setting is overwritten by `--config` - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?; - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true").arg("--config").arg("terminal.error-on-warning=false"), @r" - success: true - exit_code: 0 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn cli_config_args_invalid_option() -> anyhow::Result<()> { - let case = TestCase::with_file("test.py", r"print(1)")?; - assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r###" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: TOML parse error at line 1, column 1 - | - 1 | bad-option=true - | ^^^^^^^^^^ - unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal` - - - Usage: ty - - For more information, try '--help'. - "###); - - Ok(()) -} - -/// The `site-packages` directory is used by ty for external import. -/// Ty does the following checks to discover the `site-packages` directory in the order: -/// 1) If `VIRTUAL_ENV` environment variable is set -/// 2) If `CONDA_PREFIX` environment variable is set -/// 3) If a `.venv` directory exists at the project root -/// -/// This test is aiming at validating the logic around `CONDA_PREFIX`. -/// -/// A conda-like environment file structure is used -/// We test by first not setting the `CONDA_PREFIX` and expect a fail. -/// Then we test by setting `CONDA_PREFIX` to `conda-env` and expect a pass. -/// -/// ├── project -/// │ └── test.py -/// └── conda-env -/// └── lib -/// └── python3.13 -/// └── site-packages -/// └── package1 -/// └── __init__.py -/// -/// test.py imports package1 -/// And the command is run in the `project` directory. -#[test] -fn check_conda_prefix_var_to_resolve_path() -> anyhow::Result<()> { - let conda_package1_path = if cfg!(windows) { - "conda-env/Lib/site-packages/package1/__init__.py" - } else { - "conda-env/lib/python3.13/site-packages/package1/__init__.py" - }; - - let case = TestCase::with_files([ - ( - "project/test.py", - r#" - import package1 - "#, - ), - ( - conda_package1_path, - r#" - "#, - ), - ])?; - - assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")), @r" - success: false - exit_code: 1 - ----- stdout ----- - error[unresolved-import]: Cannot resolve imported module `package1` - --> test.py:2:8 - | - 2 | import package1 - | ^^^^^^^^ - | - info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment - info: rule `unresolved-import` is enabled by default - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // do command : CONDA_PREFIX=/conda_env - assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")).env("CONDA_PREFIX", case.root().join("conda-env")), @r" - success: true - exit_code: 0 - ----- stdout ----- - All checks passed! - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -#[test] -fn config_file_override() -> anyhow::Result<()> { - // Set `error-on-warning` to true in the configuration file - // Explicitly set `--warn unresolved-reference` to ensure the rule warns instead of errors - let case = TestCase::with_files(vec![ - ("test.py", r"print(x) # [unresolved-reference]"), - ( - "ty-override.toml", - r#" - [terminal] - error-on-warning = true - "#, - ), - ])?; - - // Ensure flag works via CLI arg - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config-file").arg("ty-override.toml"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - // Ensure the flag works via an environment variable - assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").env("TY_CONFIG_FILE", "ty-override.toml"), @r" - success: false - exit_code: 1 - ----- stdout ----- - warning[unresolved-reference]: Name `x` used when not defined - --> test.py:1:7 - | - 1 | print(x) # [unresolved-reference] - | ^ - | - info: rule `unresolved-reference` was selected on the command line - - Found 1 diagnostic - - ----- stderr ----- - WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "); - - Ok(()) -} - -struct TestCase { - _temp_dir: TempDir, - _settings_scope: SettingsBindDropGuard, - project_dir: PathBuf, -} - -impl TestCase { - fn new() -> anyhow::Result { - let temp_dir = TempDir::new()?; - - // Canonicalize the tempdir path because macos uses symlinks for tempdirs - // and that doesn't play well with our snapshot filtering. - // Simplify with dunce because otherwise we get UNC paths on Windows. - let project_dir = dunce::simplified( - &temp_dir - .path() - .canonicalize() - .context("Failed to canonicalize project path")?, - ) - .to_path_buf(); - - let mut settings = insta::Settings::clone_current(); - settings.add_filter(&tempdir_filter(&project_dir), "/"); - settings.add_filter(r#"\\(\w\w|\s|\.|")"#, "/$1"); - - let settings_scope = settings.bind_to_scope(); - - Ok(Self { - project_dir, - _temp_dir: temp_dir, - _settings_scope: settings_scope, - }) - } - - fn with_files<'a>(files: impl IntoIterator) -> anyhow::Result { - let case = Self::new()?; - case.write_files(files)?; - Ok(case) - } - - fn with_file(path: impl AsRef, content: &str) -> anyhow::Result { - let case = Self::new()?; - case.write_file(path, content)?; - Ok(case) - } - - fn write_files<'a>( - &self, - files: impl IntoIterator, - ) -> anyhow::Result<()> { - for (path, content) in files { - self.write_file(path, content)?; - } - - Ok(()) - } - - fn write_file(&self, path: impl AsRef, content: &str) -> anyhow::Result<()> { - let path = path.as_ref(); - let path = self.project_dir.join(path); - - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent) - .with_context(|| format!("Failed to create directory `{}`", parent.display()))?; - } - std::fs::write(&path, &*ruff_python_trivia::textwrap::dedent(content)) - .with_context(|| format!("Failed to write file `{path}`", path = path.display()))?; - - Ok(()) - } - - fn root(&self) -> &Path { - &self.project_dir - } - - fn command(&self) -> Command { - let mut command = Command::new(get_cargo_bin("ty")); - command.current_dir(&self.project_dir).arg("check"); - command - } -} - -fn tempdir_filter(path: &Path) -> String { - format!(r"{}\\?/?", regex::escape(path.to_str().unwrap())) -} diff --git a/crates/ty/tests/cli/config_option.rs b/crates/ty/tests/cli/config_option.rs new file mode 100644 index 00000000000000..4ea24fa4f31edb --- /dev/null +++ b/crates/ty/tests/cli/config_option.rs @@ -0,0 +1,206 @@ +use insta_cmd::assert_cmd_snapshot; + +use crate::CliTest; + +#[test] +fn cli_config_args_toml_string_basic() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", r"print(x) # [unresolved-reference]")?; + + // Long flag + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Short flag + assert_cmd_snapshot!(case.command().arg("-c").arg("terminal.error-on-warning=true"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> { + let case = CliTest::with_files(vec![ + ( + "ty.toml", + r#" + [terminal] + error-on-warning = true + "#, + ), + ("test.py", r"print(x) # [unresolved-reference]"), + ])?; + + // Exit code of 1 due to the setting in `ty.toml` + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Exit code of 0 because the `ty.toml` setting is overwritten by `--config` + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", r"print(x) # [unresolved-reference]")?; + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true").arg("--config").arg("terminal.error-on-warning=false"), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn cli_config_args_invalid_option() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", r"print(1)")?; + assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: TOML parse error at line 1, column 1 + | + 1 | bad-option=true + | ^^^^^^^^^^ + unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal` + + + Usage: ty + + For more information, try '--help'. + "###); + + Ok(()) +} + +#[test] +fn config_file_override() -> anyhow::Result<()> { + // Set `error-on-warning` to true in the configuration file + // Explicitly set `--warn unresolved-reference` to ensure the rule warns instead of errors + let case = CliTest::with_files(vec![ + ("test.py", r"print(x) # [unresolved-reference]"), + ( + "ty-override.toml", + r#" + [terminal] + error-on-warning = true + "#, + ), + ])?; + + // Ensure flag works via CLI arg + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config-file").arg("ty-override.toml"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Ensure the flag works via an environment variable + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").env("TY_CONFIG_FILE", "ty-override.toml"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} diff --git a/crates/ty/tests/cli/exit_code.rs b/crates/ty/tests/cli/exit_code.rs new file mode 100644 index 00000000000000..7c7a93e4885613 --- /dev/null +++ b/crates/ty/tests/cli/exit_code.rs @@ -0,0 +1,272 @@ +use insta_cmd::assert_cmd_snapshot; + +use crate::CliTest; + +#[test] +fn only_warnings() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", r"print(x) # [unresolved-reference]")?; + + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn only_info() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + from typing_extensions import reveal_type + reveal_type(1) + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type]: Revealed type + --> test.py:3:13 + | + 2 | from typing_extensions import reveal_type + 3 | reveal_type(1) + | ^ `Literal[1]` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn only_info_and_error_on_warning_is_true() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + from typing_extensions import reveal_type + reveal_type(1) + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--error-on-warning"), @r" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type]: Revealed type + --> test.py:3:13 + | + 2 | from typing_extensions import reveal_type + 3 | reveal_type(1) + | ^ `Literal[1]` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", r"print(x) # [unresolved-reference]")?; + + assert_cmd_snapshot!(case.command().arg("--error-on-warning").arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn no_errors_but_error_on_warning_is_enabled_in_configuration() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ("test.py", r"print(x) # [unresolved-reference]"), + ( + "ty.toml", + r#" + [terminal] + error-on-warning = true + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:1:7 + | + 1 | print(x) # [unresolved-reference] + | ^ + | + info: rule `unresolved-reference` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn both_warnings_and_errors() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + print(x) # [unresolved-reference] + print(4[1]) # [non-subscriptable] + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:2:7 + | + 2 | print(x) # [unresolved-reference] + | ^ + 3 | print(4[1]) # [non-subscriptable] + | + info: rule `unresolved-reference` was selected on the command line + + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + --> test.py:3:7 + | + 2 | print(x) # [unresolved-reference] + 3 | print(4[1]) # [non-subscriptable] + | ^ + | + info: rule `non-subscriptable` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r###" + print(x) # [unresolved-reference] + print(4[1]) # [non-subscriptable] + "###, + )?; + + assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--error-on-warning"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:2:7 + | + 2 | print(x) # [unresolved-reference] + | ^ + 3 | print(4[1]) # [non-subscriptable] + | + info: rule `unresolved-reference` was selected on the command line + + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + --> test.py:3:7 + | + 2 | print(x) # [unresolved-reference] + 3 | print(4[1]) # [non-subscriptable] + | ^ + | + info: rule `non-subscriptable` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn exit_zero_is_true() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + print(x) # [unresolved-reference] + print(4[1]) # [non-subscriptable] + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--exit-zero").arg("--warn").arg("unresolved-reference"), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unresolved-reference]: Name `x` used when not defined + --> test.py:2:7 + | + 2 | print(x) # [unresolved-reference] + | ^ + 3 | print(4[1]) # [non-subscriptable] + | + info: rule `unresolved-reference` was selected on the command line + + error[non-subscriptable]: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + --> test.py:3:7 + | + 2 | print(x) # [unresolved-reference] + 3 | print(4[1]) # [non-subscriptable] + | ^ + | + info: rule `non-subscriptable` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} diff --git a/crates/ty/tests/cli/main.rs b/crates/ty/tests/cli/main.rs new file mode 100644 index 00000000000000..023e14cefc891c --- /dev/null +++ b/crates/ty/tests/cli/main.rs @@ -0,0 +1,690 @@ +mod config_option; +mod exit_code; +mod python_environment; +mod rule_selection; + +use anyhow::Context as _; +use insta::internals::SettingsBindDropGuard; +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + process::Command, +}; +use tempfile::TempDir; + +#[test] +fn test_run_in_sub_directory() -> anyhow::Result<()> { + let case = CliTest::with_files([("test.py", "~"), ("subdir/nothing", "")])?; + assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> /test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) +} + +#[test] +fn test_include_hidden_files_by_default() -> anyhow::Result<()> { + let case = CliTest::with_files([(".test.py", "~")])?; + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> .test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) +} + +#[test] +fn test_respect_ignore_files() -> anyhow::Result<()> { + // First test that the default option works correctly (the file is skipped) + let case = CliTest::with_files([(".ignore", "test.py"), ("test.py", "~")])?; + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + WARN No python files found under the given path(s) + "); + + // Test that we can set to false via CLI + assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Test that we can set to false via config file + case.write_file("ty.toml", "src.respect-ignore-files = false")?; + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Ensure CLI takes precedence + case.write_file("ty.toml", "src.respect-ignore-files = true")?; + assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:1:2 + | + 1 | ~ + | ^ Expected an expression + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) +} + +/// Paths specified on the CLI are relative to the current working directory and not the project root. +/// +/// We test this by adding an extra search path from the CLI to the libs directory when +/// running the CLI from the child directory (using relative paths). +/// +/// Project layout: +/// ``` +/// - libs +/// |- utils.py +/// - child +/// | - test.py +/// - pyproject.toml +/// ``` +/// +/// And the command is run in the `child` directory. +#[test] +fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python-version = "3.11" + "#, + ), + ( + "libs/utils.py", + r#" + def add(a: int, b: int) -> int: + return a + b + "#, + ), + ( + "child/test.py", + r#" + from utils import add + + stat = add(10, 15) + "#, + ), + ])?; + + // Make sure that the CLI fails when the `libs` directory is not in the search path. + assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `utils` + --> test.py:2:6 + | + 2 | from utils import add + | ^^^^^ + 3 | + 4 | stat = add(10, 15) + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")).arg("--extra-search-path").arg("../libs"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Paths specified in a configuration file are relative to the project root. +/// +/// We test this by adding `libs` (as a relative path) to the extra search path in the configuration and run +/// the CLI from a subdirectory. +/// +/// Project layout: +/// ``` +/// - libs +/// |- utils.py +/// - child +/// | - test.py +/// - pyproject.toml +/// ``` +#[test] +fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python-version = "3.11" + extra-paths = ["libs"] + "#, + ), + ( + "libs/utils.py", + r#" + def add(a: int, b: int) -> int: + return a + b + "#, + ), + ( + "child/test.py", + r#" + from utils import add + + stat = add(10, 15) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command().current_dir(case.root().join("child")), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn user_configuration() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "project/ty.toml", + r#" + [rules] + division-by-zero = "warn" + "#, + ), + ( + "project/main.py", + r#" + y = 4 / 0 + + for a in range(0, int(y)): + x = a + + prin(x) + "#, + ), + ])?; + + let config_directory = case.root().join("home/.config"); + let config_env_var = if cfg!(windows) { + "APPDATA" + } else { + "XDG_CONFIG_HOME" + }; + + assert_cmd_snapshot!( + case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), + @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> main.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + 3 | + 4 | for a in range(0, int(y)): + | + info: rule `division-by-zero` was selected in the configuration file + + error[unresolved-reference]: Name `prin` used when not defined + --> main.py:7:1 + | + 5 | x = a + 6 | + 7 | prin(x) + | ^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + " + ); + + // The user-level configuration sets the severity for `unresolved-reference` to warn. + // Changing the level for `division-by-zero` has no effect, because the project-level configuration + // has higher precedence. + case.write_file( + config_directory.join("ty/ty.toml"), + r#" + [rules] + division-by-zero = "error" + unresolved-reference = "warn" + "#, + )?; + + assert_cmd_snapshot!( + case.command().current_dir(case.root().join("project")).env(config_env_var, config_directory.as_os_str()), + @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> main.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + 3 | + 4 | for a in range(0, int(y)): + | + info: rule `division-by-zero` was selected in the configuration file + + warning[unresolved-reference]: Name `prin` used when not defined + --> main.py:7:1 + | + 5 | x = a + 6 | + 7 | prin(x) + | ^^^^ + | + info: rule `unresolved-reference` was selected in the configuration file + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + " + ); + + Ok(()) +} + +#[test] +fn check_specific_paths() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "project/main.py", + r#" + y = 4 / 0 # error: division-by-zero + "#, + ), + ( + "project/tests/test_main.py", + r#" + import does_not_exist # error: unresolved-import + "#, + ), + ( + "project/other.py", + r#" + from main2 import z # error: unresolved-import + + print(z) + "#, + ), + ])?; + + assert_cmd_snapshot!( + case.command(), + @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `main2` + --> project/other.py:2:6 + | + 2 | from main2 import z # error: unresolved-import + | ^^^^^ + 3 | + 4 | print(z) + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + error[unresolved-import]: Cannot resolve imported module `does_not_exist` + --> project/tests/test_main.py:2:8 + | + 2 | import does_not_exist # error: unresolved-import + | ^^^^^^^^^^^^^^ + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "### + ); + + // Now check only the `tests` and `other.py` files. + // We should no longer see any diagnostics related to `main.py`. + assert_cmd_snapshot!( + case.command().arg("project/tests").arg("project/other.py"), + @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `main2` + --> project/other.py:2:6 + | + 2 | from main2 import z # error: unresolved-import + | ^^^^^ + 3 | + 4 | print(z) + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + error[unresolved-import]: Cannot resolve imported module `does_not_exist` + --> project/tests/test_main.py:2:8 + | + 2 | import does_not_exist # error: unresolved-import + | ^^^^^^^^^^^^^^ + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + " + ); + + Ok(()) +} + +#[test] +fn check_non_existing_path() -> anyhow::Result<()> { + let case = CliTest::with_files([])?; + + let mut settings = insta::Settings::clone_current(); + settings.add_filter( + ®ex::escape("The system cannot find the path specified. (os error 3)"), + "No such file or directory (os error 2)", + ); + let _s = settings.bind_to_scope(); + + assert_cmd_snapshot!( + case.command().arg("project/main.py").arg("project/tests"), + @r" + success: false + exit_code: 1 + ----- stdout ----- + error[io]: `/project/main.py`: No such file or directory (os error 2) + + error[io]: `/project/tests`: No such file or directory (os error 2) + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + WARN No python files found under the given path(s) + " + ); + + Ok(()) +} + +#[test] +fn concise_diagnostics() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + print(x) # [unresolved-reference] + print(4[1]) # [non-subscriptable] + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--output-format=concise").arg("--warn").arg("unresolved-reference"), @r" + success: false + exit_code: 1 + ----- stdout ----- + warning[unresolved-reference] test.py:2:7: Name `x` used when not defined + error[non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// This tests the diagnostic format for revealed type. +/// +/// This test was introduced because changes were made to +/// how the revealed type diagnostic was constructed and +/// formatted in "verbose" mode. But it required extra +/// logic to ensure the concise version didn't regress on +/// information content. So this test was introduced to +/// capture that. +#[test] +fn concise_revealed_type() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + from typing_extensions import reveal_type + + x = "hello" + reveal_type(x) + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type] test.py:5:13: Revealed type: `Literal["hello"]` + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +#[test] +fn can_handle_large_binop_expressions() -> anyhow::Result<()> { + let mut content = String::new(); + writeln!( + &mut content, + " + from typing_extensions import reveal_type + total = 1{plus_one_repeated} + reveal_type(total) + ", + plus_one_repeated = " + 1".repeat(2000 - 1) + )?; + + let case = CliTest::with_file("test.py", &ruff_python_trivia::textwrap::dedent(&content))?; + + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type]: Revealed type + --> test.py:4:13 + | + 2 | from typing_extensions import reveal_type + 3 | total = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1... + 4 | reveal_type(total) + | ^^^^^ `Literal[2000]` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +pub(crate) struct CliTest { + _temp_dir: TempDir, + _settings_scope: SettingsBindDropGuard, + project_dir: PathBuf, +} + +impl CliTest { + pub(crate) fn new() -> anyhow::Result { + let temp_dir = TempDir::new()?; + + // Canonicalize the tempdir path because macos uses symlinks for tempdirs + // and that doesn't play well with our snapshot filtering. + // Simplify with dunce because otherwise we get UNC paths on Windows. + let project_dir = dunce::simplified( + &temp_dir + .path() + .canonicalize() + .context("Failed to canonicalize project path")?, + ) + .to_path_buf(); + + let mut settings = insta::Settings::clone_current(); + settings.add_filter(&tempdir_filter(&project_dir), "/"); + settings.add_filter(r#"\\(\w\w|\s|\.|")"#, "/$1"); + + let settings_scope = settings.bind_to_scope(); + + Ok(Self { + project_dir, + _temp_dir: temp_dir, + _settings_scope: settings_scope, + }) + } + + pub(crate) fn with_files<'a>( + files: impl IntoIterator, + ) -> anyhow::Result { + let case = Self::new()?; + case.write_files(files)?; + Ok(case) + } + + pub(crate) fn with_file(path: impl AsRef, content: &str) -> anyhow::Result { + let case = Self::new()?; + case.write_file(path, content)?; + Ok(case) + } + + pub(crate) fn write_files<'a>( + &self, + files: impl IntoIterator, + ) -> anyhow::Result<()> { + for (path, content) in files { + self.write_file(path, content)?; + } + + Ok(()) + } + + pub(crate) fn write_file(&self, path: impl AsRef, content: &str) -> anyhow::Result<()> { + let path = path.as_ref(); + let path = self.project_dir.join(path); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("Failed to create directory `{}`", parent.display()))?; + } + std::fs::write(&path, &*ruff_python_trivia::textwrap::dedent(content)) + .with_context(|| format!("Failed to write file `{path}`", path = path.display()))?; + + Ok(()) + } + + pub(crate) fn root(&self) -> &Path { + &self.project_dir + } + + pub(crate) fn command(&self) -> Command { + let mut command = Command::new(get_cargo_bin("ty")); + command.current_dir(&self.project_dir).arg("check"); + + // Unset environment variables that can affect test behavior + command.env_remove("VIRTUAL_ENV"); + command.env_remove("CONDA_PREFIX"); + + command + } +} + +fn tempdir_filter(path: &Path) -> String { + format!(r"{}\\?/?", regex::escape(path.to_str().unwrap())) +} diff --git a/crates/ty/tests/cli/python_environment.rs b/crates/ty/tests/cli/python_environment.rs new file mode 100644 index 00000000000000..ab9192242d63c1 --- /dev/null +++ b/crates/ty/tests/cli/python_environment.rs @@ -0,0 +1,774 @@ +use insta_cmd::assert_cmd_snapshot; +use ruff_python_ast::PythonVersion; + +use crate::CliTest; + +/// Specifying an option on the CLI should take precedence over the same setting in the +/// project's configuration. Here, this is tested for the Python version. +#[test] +fn config_override_python_version() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python-version = "3.11" + "#, + ), + ( + "test.py", + r#" + import sys + + # Access `sys.last_exc` that was only added in Python 3.12 + print(sys.last_exc) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-attribute]: Type `` has no attribute `last_exc` + --> test.py:5:7 + | + 4 | # Access `sys.last_exc` that was only added in Python 3.12 + 5 | print(sys.last_exc) + | ^^^^^^^^^^^^ + | + info: rule `unresolved-attribute` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Same as above, but for the Python platform. +#[test] +fn config_override_python_platform() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python-platform = "linux" + "#, + ), + ( + "test.py", + r#" + import sys + from typing_extensions import reveal_type + + reveal_type(sys.platform) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type]: Revealed type + --> test.py:5:13 + | + 3 | from typing_extensions import reveal_type + 4 | + 5 | reveal_type(sys.platform) + | ^^^^^^^^^^^^ `Literal["linux"]` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + assert_cmd_snapshot!(case.command().arg("--python-platform").arg("all"), @r" + success: true + exit_code: 0 + ----- stdout ----- + info[revealed-type]: Revealed type + --> test.py:5:13 + | + 3 | from typing_extensions import reveal_type + 4 | + 5 | reveal_type(sys.platform) + | ^^^^^^^^^^^^ `LiteralString` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn config_file_annotation_showing_where_python_version_set_typing_error() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python-version = "3.8" + "#, + ), + ( + "test.py", + r#" + aiter + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:2:1 + | + 2 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types + --> pyproject.toml:3:18 + | + 2 | [tool.ty.environment] + 3 | python-version = "3.8" + | ^^^^^ Python 3.8 assumed due to this configuration setting + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:2:1 + | + 2 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.9 was assumed when resolving types because it was specified on the command line + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "venv" + "#, + ), + ( + "venv/pyvenv.cfg", + r#" + version = 3.8 + home = foo/bar/bin + "#, + ), + if cfg!(target_os = "windows") { + ("foo/bar/bin/python.exe", "") + } else { + ("foo/bar/bin/python", "") + }, + if cfg!(target_os = "windows") { + ("venv/Lib/site-packages/foo.py", "") + } else { + ("venv/lib/python3.8/site-packages/foo.py", "") + }, + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of your virtual environment + --> venv/pyvenv.cfg:2:11 + | + 2 | version = 3.8 + | ^^^ Python version inferred from virtual environment metadata file + 3 | home = foo/bar/bin + | + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "venv" + "#, + ), + ( + "venv/pyvenv.cfg", + r#"home = foo/bar/bin + + + version = 3.8"#, + ), + if cfg!(target_os = "windows") { + ("foo/bar/bin/python.exe", "") + } else { + ("foo/bar/bin/python", "") + }, + if cfg!(target_os = "windows") { + ("venv/Lib/site-packages/foo.py", "") + } else { + ("venv/lib/python3.8/site-packages/foo.py", "") + }, + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of your virtual environment + --> venv/pyvenv.cfg:4:23 + | + 4 | version = 3.8 + | ^^^ Python version inferred from virtual environment metadata file + | + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn config_file_annotation_showing_where_python_version_set_syntax_error() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [project] + requires-python = ">=3.8" + "#, + ), + ( + "test.py", + r#" + match object(): + case int(): + pass + case _: + pass + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:2:1 + | + 2 | match object(): + | ^^^^^ Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10) + 3 | case int(): + 4 | pass + | + info: Python 3.8 was assumed when parsing syntax + --> pyproject.toml:3:19 + | + 2 | [project] + 3 | requires-python = ">=3.8" + | ^^^^^^^ Python 3.8 assumed due to this configuration setting + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[invalid-syntax] + --> test.py:2:1 + | + 2 | match object(): + | ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + 3 | case int(): + 4 | pass + | + info: Python 3.9 was assumed when parsing syntax because it was specified on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn python_cli_argument_virtual_environment() -> anyhow::Result<()> { + let path_to_executable = if cfg!(windows) { + "my-venv/Scripts/python.exe" + } else { + "my-venv/bin/python" + }; + + let other_venv_path = "my-venv/foo/some_other_file.txt"; + + let case = CliTest::with_files([ + ("test.py", ""), + ( + if cfg!(windows) { + "my-venv/Lib/site-packages/foo.py" + } else { + "my-venv/lib/python3.13/site-packages/foo.py" + }, + "", + ), + (path_to_executable, ""), + (other_venv_path, ""), + ])?; + + // Passing a path to the installation works + assert_cmd_snapshot!(case.command().arg("--python").arg("my-venv"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // And so does passing a path to the executable inside the installation + assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // But random other paths inside the installation are rejected + assert_cmd_snapshot!(case.command().arg("--python").arg(other_venv_path), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/my-venv/foo/some_other_file.txt`: does not point to a Python executable or a directory on disk + "); + + // And so are paths that do not exist on disk + assert_cmd_snapshot!(case.command().arg("--python").arg("not-a-directory-or-executable"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `--python` argument `/not-a-directory-or-executable`: does not point to a Python executable or a directory on disk + "); + + Ok(()) +} + +#[test] +fn python_cli_argument_system_installation() -> anyhow::Result<()> { + let path_to_executable = if cfg!(windows) { + "Python3.11/python.exe" + } else { + "Python3.11/bin/python" + }; + + let case = CliTest::with_files([ + ("test.py", ""), + ( + if cfg!(windows) { + "Python3.11/Lib/site-packages/foo.py" + } else { + "Python3.11/lib/python3.11/site-packages/foo.py" + }, + "", + ), + (path_to_executable, ""), + ])?; + + // Passing a path to the installation works + assert_cmd_snapshot!(case.command().arg("--python").arg("Python3.11"), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // And so does passing a path to the executable inside the installation + assert_cmd_snapshot!(case.command().arg("--python").arg(path_to_executable), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn config_file_broken_python_setting() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [project] + name = "test" + version = "0.1.0" + description = "Some description" + readme = "README.md" + requires-python = ">=3.13" + dependencies = [] + + [tool.ty.environment] + python = "not-a-directory-or-executable" + "#, + ), + ("test.py", ""), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting + + --> Invalid setting in configuration file `/pyproject.toml` + | + 9 | + 10 | [tool.ty.environment] + 11 | python = "not-a-directory-or-executable" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not point to a Python executable or a directory on disk + | + "#); + + Ok(()) +} + +#[test] +fn config_file_python_setting_directory_with_no_site_packages() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "directory-but-no-site-packages" + "#, + ), + ("directory-but-no-site-packages/lib/foo.py", ""), + ("test.py", ""), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting + + --> Invalid setting in configuration file `/pyproject.toml` + | + 1 | + 2 | [tool.ty.environment] + 3 | python = "directory-but-no-site-packages" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find a `site-packages` directory for this Python installation/executable + | + "#); + + Ok(()) +} + +// This error message is never emitted on Windows, because Windows installations have simpler layouts +#[cfg(not(windows))] +#[test] +fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.environment] + python = "directory-but-no-site-packages" + "#, + ), + ("directory-but-no-site-packages/foo.py", ""), + ("test.py", ""), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: Invalid search path settings + Cause: Failed to discover the site-packages directory: Failed to iterate over the contents of the `lib` directory of the Python installation + + --> Invalid setting in configuration file `/pyproject.toml` + | + 1 | + 2 | [tool.ty.environment] + 3 | python = "directory-but-no-site-packages" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + "#); + + Ok(()) +} + +#[test] +fn defaults_to_a_new_python_version() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "ty.toml", + &*format!( + r#" + [environment] + python-version = "{}" + python-platform = "linux" + "#, + PythonVersion::default() + ), + ), + ( + "main.py", + r#" + import os + + os.grantpt(1) # only available on unix, Python 3.13 or newer + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-attribute]: Type `` has no attribute `grantpt` + --> main.py:4:1 + | + 2 | import os + 3 | + 4 | os.grantpt(1) # only available on unix, Python 3.13 or newer + | ^^^^^^^^^^ + | + info: rule `unresolved-attribute` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Use default (which should be latest supported) + let case = CliTest::with_files([ + ( + "ty.toml", + r#" + [environment] + python-platform = "linux" + "#, + ), + ( + "main.py", + r#" + import os + + os.grantpt(1) # only available on unix, Python 3.13 or newer + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// The `site-packages` directory is used by ty for external import. +/// Ty does the following checks to discover the `site-packages` directory in the order: +/// 1) If `VIRTUAL_ENV` environment variable is set +/// 2) If `CONDA_PREFIX` environment variable is set +/// 3) If a `.venv` directory exists at the project root +/// +/// This test is aiming at validating the logic around `CONDA_PREFIX`. +/// +/// A conda-like environment file structure is used +/// We test by first not setting the `CONDA_PREFIX` and expect a fail. +/// Then we test by setting `CONDA_PREFIX` to `conda-env` and expect a pass. +/// +/// ├── project +/// │ └── test.py +/// └── conda-env +/// └── lib +/// └── python3.13 +/// └── site-packages +/// └── package1 +/// └── __init__.py +/// +/// test.py imports package1 +/// And the command is run in the `child` directory. +#[test] +fn check_conda_prefix_var_to_resolve_path() -> anyhow::Result<()> { + let conda_package1_path = if cfg!(windows) { + "conda-env/Lib/site-packages/package1/__init__.py" + } else { + "conda-env/lib/python3.13/site-packages/package1/__init__.py" + }; + + let case = CliTest::with_files([ + ( + "project/test.py", + r#" + import package1 + "#, + ), + ( + conda_package1_path, + r#" + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `package1` + --> test.py:2:8 + | + 2 | import package1 + | ^^^^^^^^ + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // do command : CONDA_PREFIX=/conda_env + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")).env("CONDA_PREFIX", case.root().join("conda-env")), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} diff --git a/crates/ty/tests/cli/rule_selection.rs b/crates/ty/tests/cli/rule_selection.rs new file mode 100644 index 00000000000000..9269790e3a6cea --- /dev/null +++ b/crates/ty/tests/cli/rule_selection.rs @@ -0,0 +1,292 @@ +use insta_cmd::assert_cmd_snapshot; + +use crate::CliTest; + +/// The rule severity can be changed in the configuration file +#[test] +fn configuration_rule_severity() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + y = 4 / 0 + + for a in range(0, int(y)): + x = a + + prin(x) # unresolved-reference + "#, + )?; + + // Assert that there's an `unresolved-reference` diagnostic (error). + assert_cmd_snapshot!(case.command(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `prin` used when not defined + --> test.py:7:1 + | + 5 | x = a + 6 | + 7 | prin(x) # unresolved-reference + | ^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "###); + + case.write_file( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "warn" # promote to warn + unresolved-reference = "ignore" + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + 3 | + 4 | for a in range(0, int(y)): + | + info: rule `division-by-zero` was selected in the configuration file + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// The rule severity can be changed using `--ignore`, `--warn`, and `--error` +#[test] +fn cli_rule_severity() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + import does_not_exit + + y = 4 / 0 + + for a in range(0, int(y)): + x = a + + prin(x) # unresolved-reference + "#, + )?; + + // Assert that there's an `unresolved-reference` diagnostic (error) + // and an unresolved-import (error) diagnostic by default. + assert_cmd_snapshot!(case.command(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `does_not_exit` + --> test.py:2:8 + | + 2 | import does_not_exit + | ^^^^^^^^^^^^^ + 3 | + 4 | y = 4 / 0 + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + error[unresolved-reference]: Name `prin` used when not defined + --> test.py:9:1 + | + 7 | x = a + 8 | + 9 | prin(x) # unresolved-reference + | ^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "###); + + assert_cmd_snapshot!( + case + .command() + .arg("--ignore") + .arg("unresolved-reference") + .arg("--warn") + .arg("division-by-zero") + .arg("--warn") + .arg("unresolved-import"), + @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unresolved-import]: Cannot resolve imported module `does_not_exit` + --> test.py:2:8 + | + 2 | import does_not_exit + | ^^^^^^^^^^^^^ + 3 | + 4 | y = 4 / 0 + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` was selected on the command line + + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:4:5 + | + 2 | import does_not_exit + 3 | + 4 | y = 4 / 0 + | ^^^^^ + 5 | + 6 | for a in range(0, int(y)): + | + info: rule `division-by-zero` was selected on the command line + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + " + ); + + Ok(()) +} + +/// The rule severity can be changed using `--ignore`, `--warn`, and `--error` and +/// values specified last override previous severities. +#[test] +fn cli_rule_severity_precedence() -> anyhow::Result<()> { + let case = CliTest::with_file( + "test.py", + r#" + y = 4 / 0 + + for a in range(0, int(y)): + x = a + + prin(x) # unresolved-reference + "#, + )?; + + // Assert that there's a `unresolved-reference` diagnostic (error) by default. + assert_cmd_snapshot!(case.command(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `prin` used when not defined + --> test.py:7:1 + | + 5 | x = a + 6 | + 7 | prin(x) # unresolved-reference + | ^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "###); + + assert_cmd_snapshot!( + case + .command() + .arg("--warn") + .arg("unresolved-reference") + .arg("--warn") + .arg("division-by-zero") + .arg("--ignore") + .arg("unresolved-reference"), + @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + 3 | + 4 | for a in range(0, int(y)): + | + info: rule `division-by-zero` was selected on the command line + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + " + ); + + Ok(()) +} + +/// ty warns about unknown rules specified in a configuration file +#[test] +fn configuration_unknown_rules() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zer = "warn" # incorrect rule name + "#, + ), + ("test.py", "print(10)"), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + warning[unknown-rule] + --> pyproject.toml:3:1 + | + 2 | [tool.ty.rules] + 3 | division-by-zer = "warn" # incorrect rule name + | ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer` + | + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +/// ty warns about unknown rules specified in a CLI argument +#[test] +fn cli_unknown_rules() -> anyhow::Result<()> { + let case = CliTest::with_file("test.py", "print(10)")?; + + assert_cmd_snapshot!(case.command().arg("--ignore").arg("division-by-zer"), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[unknown-rule]: Unknown lint rule `division-by-zer` + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} From 86e5a311f05943095f8e7480dea1a69fdc8c787e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 7 Jun 2025 19:56:58 +0200 Subject: [PATCH 358/487] [ty] Introduce and use `System::env_var` for better test isolation (#18538) --- crates/ruff_db/src/system.rs | 15 +++++++++++++++ crates/ruff_db/src/system/os.rs | 4 ++++ crates/ty_project/src/metadata/options.rs | 4 ++-- crates/ty_server/src/system.rs | 4 ++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/ruff_db/src/system.rs b/crates/ruff_db/src/system.rs index b4ff1ea439c6b2..836010241fa3eb 100644 --- a/crates/ruff_db/src/system.rs +++ b/crates/ruff_db/src/system.rs @@ -171,6 +171,21 @@ pub trait System: Debug { PatternError, >; + /// Fetches the environment variable `key` from the current process. + /// + /// # Errors + /// + /// Returns [`std::env::VarError::NotPresent`] if: + /// - The variable is not set. + /// - The variable's name contains an equal sign or NUL (`'='` or `'\0'`). + /// + /// Returns [`std::env::VarError::NotUnicode`] if the variable's value is not valid + /// Unicode. + fn env_var(&self, name: &str) -> std::result::Result { + let _ = name; + Err(std::env::VarError::NotPresent) + } + fn as_any(&self) -> &dyn std::any::Any; fn as_any_mut(&mut self) -> &mut dyn std::any::Any; diff --git a/crates/ruff_db/src/system/os.rs b/crates/ruff_db/src/system/os.rs index 3d5c905c944924..41b7bd0a1e293f 100644 --- a/crates/ruff_db/src/system/os.rs +++ b/crates/ruff_db/src/system/os.rs @@ -214,6 +214,10 @@ impl System for OsSystem { }) }))) } + + fn env_var(&self, name: &str) -> std::result::Result { + std::env::var(name) + } } impl OsSystem { diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 9d3cb9f8e5c641..11dd846d58c039 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -192,12 +192,12 @@ impl Options { PythonPath::sys_prefix(python_path.absolute(project_root, system), origin) }) .or_else(|| { - std::env::var("VIRTUAL_ENV").ok().map(|virtual_env| { + system.env_var("VIRTUAL_ENV").ok().map(|virtual_env| { PythonPath::sys_prefix(virtual_env, SysPrefixPathOrigin::VirtualEnvVar) }) }) .or_else(|| { - std::env::var("CONDA_PREFIX").ok().map(|path| { + system.env_var("CONDA_PREFIX").ok().map(|path| { PythonPath::sys_prefix(path, SysPrefixPathOrigin::CondaPrefixVar) }) }) diff --git a/crates/ty_server/src/system.rs b/crates/ty_server/src/system.rs index 553e3ccbb6660d..4a38ed9046b263 100644 --- a/crates/ty_server/src/system.rs +++ b/crates/ty_server/src/system.rs @@ -247,6 +247,10 @@ impl System for LSPSystem { fn case_sensitivity(&self) -> CaseSensitivity { self.os_system.case_sensitivity() } + + fn env_var(&self, name: &str) -> std::result::Result { + self.os_system.env_var(name) + } } fn not_a_text_document(path: impl Display) -> std::io::Error { From 301b9f41353eed20d5b4b1c2a923ffe8f59b5215 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 8 Jun 2025 12:00:30 -0400 Subject: [PATCH 359/487] Add trailing space around `readlines` (#18542) Closes https://github.com/astral-sh/ruff/issues/17683. --- .../resources/test/fixtures/refurb/FURB129.py | 5 ++++ .../rules/refurb/rules/readlines_in_for.rs | 17 +++++++++++-- ...es__refurb__tests__FURB129_FURB129.py.snap | 24 +++++++++++++++++++ ...b__tests__preview__FURB129_FURB129.py.snap | 24 +++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py index 3a163d8f21e193..05a04f3d652d96 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py @@ -102,3 +102,8 @@ def readlines(self) -> list[str]: pass for line in(f).readlines(): pass + # Test case for issue #17683 - missing space before keyword + print([line for line in f.readlines()if True]) + print([line for line in f.readlines()and True]) + print([line for line in f.readlines()or True]) + print([line for line in f.readlines()in ["test"]]) diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index c78ae1fdb013a0..76431b2ec93d83 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -6,6 +6,7 @@ use ruff_python_semantic::analyze::typing::is_io_base_expr; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::fix::edits::pad_end; use crate::preview::is_readlines_in_for_fix_safe_enabled; use crate::{AlwaysFixableViolation, Edit, Fix}; @@ -91,9 +92,21 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { checker.comment_ranges(), checker.source(), ) { - Edit::range_deletion(expr_call.range().add_start(parenthesized_range.len())) + let deletion_range = expr_call.range().add_start(parenthesized_range.len()); + let padded = pad_end(String::new(), deletion_range.end(), checker.locator()); + if padded.is_empty() { + Edit::range_deletion(deletion_range) + } else { + Edit::range_replacement(padded, deletion_range) + } } else { - Edit::range_deletion(expr_call.range().add_start(expr_attr.value.range().len())) + let deletion_range = expr_call.range().add_start(expr_attr.value.range().len()); + let padded = pad_end(String::new(), deletion_range.end(), checker.locator()); + if padded.is_empty() { + Edit::range_deletion(deletion_range) + } else { + Edit::range_replacement(padded, deletion_range) + } }; let mut diagnostic = checker.report_diagnostic(ReadlinesInFor, expr_call.range()); diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap index 9a1c006794f4fd..412c2ea5fa0e06 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap @@ -307,6 +307,7 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 | for line in(f).readlines(): | ^^^^^^^^^^^^^^^ FURB129 104 | pass +105 | # Test case for issue #17683 - missing space before keyword | = help: Remove `readlines()` @@ -317,3 +318,26 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 |- for line in(f).readlines(): 103 |+ for line in(f): 104 104 | pass +105 105 | # Test case for issue #17683 - missing space before keyword +106 106 | print([line for line in f.readlines()if True]) + +FURB129.py:106:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +104 | pass +105 | # Test case for issue #17683 - missing space before keyword +106 | print([line for line in f.readlines()if True]) + | ^^^^^^^^^^^^^ FURB129 +107 | print([line for line in f.readlines()and True]) +108 | print([line for line in f.readlines()or True]) + | + = help: Remove `readlines()` + +ℹ Unsafe fix +103 103 | for line in(f).readlines(): +104 104 | pass +105 105 | # Test case for issue #17683 - missing space before keyword +106 |- print([line for line in f.readlines()if True]) + 106 |+ print([line for line in f if True]) +107 107 | print([line for line in f.readlines()and True]) +108 108 | print([line for line in f.readlines()or True]) +109 109 | print([line for line in f.readlines()in ["test"]]) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap index ff4ac5132715bc..2f6f162df7a0bf 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap @@ -307,6 +307,7 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 | for line in(f).readlines(): | ^^^^^^^^^^^^^^^ FURB129 104 | pass +105 | # Test case for issue #17683 - missing space before keyword | = help: Remove `readlines()` @@ -317,3 +318,26 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 |- for line in(f).readlines(): 103 |+ for line in(f): 104 104 | pass +105 105 | # Test case for issue #17683 - missing space before keyword +106 106 | print([line for line in f.readlines()if True]) + +FURB129.py:106:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly + | +104 | pass +105 | # Test case for issue #17683 - missing space before keyword +106 | print([line for line in f.readlines()if True]) + | ^^^^^^^^^^^^^ FURB129 +107 | print([line for line in f.readlines()and True]) +108 | print([line for line in f.readlines()or True]) + | + = help: Remove `readlines()` + +ℹ Safe fix +103 103 | for line in(f).readlines(): +104 104 | pass +105 105 | # Test case for issue #17683 - missing space before keyword +106 |- print([line for line in f.readlines()if True]) + 106 |+ print([line for line in f if True]) +107 107 | print([line for line in f.readlines()and True]) +108 108 | print([line for line in f.readlines()or True]) +109 109 | print([line for line in f.readlines()in ["test"]]) From 1dc8f8f903180cccfe1d9470a9d2638f19a25f4a Mon Sep 17 00:00:00 2001 From: Ben Bar-Or Date: Mon, 9 Jun 2025 02:40:05 +0300 Subject: [PATCH 360/487] [ty] Add hints to `invalid-type-form` for common mistakes (#18543) Co-authored-by: Ben Bar-Or Co-authored-by: Alex Waygood --- .../resources/mdtest/annotations/annotated.md | 2 +- .../resources/mdtest/annotations/callable.md | 15 ++ .../resources/mdtest/annotations/invalid.md | 61 ++++- ...ed_wh\342\200\246_(f80dbf5dd571c940).snap" | 91 +++++++ ...sed_w\342\200\246_(f61204fc81905069).snap" | 129 ++++++++++ .../src/types/diagnostic.rs | 5 +- crates/ty_python_semantic/src/types/infer.rs | 229 ++++++++++++------ 7 files changed, 460 insertions(+), 72 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(f80dbf5dd571c940).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md b/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md index ed566b859edb51..893fb3c6372dd8 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md @@ -47,7 +47,7 @@ def _(flag: bool): def _(x: Annotated | bool): reveal_type(x) # revealed: Unknown | bool -# error: [invalid-type-form] +# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)" def _(x: Annotated[()]): reveal_type(x) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 49d05eb6535541..05adfad25b7b2c 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -116,6 +116,21 @@ def _(c: Callable[ reveal_type(c) # revealed: (...) -> Unknown ``` +### Tuple as the second argument + +```py +from typing import Callable + +# fmt: off + +def _(c: Callable[ + int, # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" + (str, ) # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression" + ] + ): + reveal_type(c) # revealed: (...) -> Unknown +``` + ### List as both arguments ```py diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md index ed4571330e8f38..5392e38c77f9df 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md @@ -95,6 +95,11 @@ async def outer(): # avoid unrelated syntax errors on yield, yield from, and aw ## Invalid Collection based AST nodes +```toml +[environment] +python-version = "3.12" +``` + ```py def _( a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions" @@ -103,7 +108,11 @@ def _( d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions" e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions" f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions" - g: [int, str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?" + g: [int, str], + # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?" + h: (int, str), + i: (), # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?" ): reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown @@ -112,6 +121,17 @@ def _( reveal_type(e) # revealed: Unknown reveal_type(f) # revealed: Unknown reveal_type(g) # revealed: Unknown + reveal_type(h) # revealed: Unknown + reveal_type(i) # revealed: Unknown + +# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?" +class name_0[name_2: [int]]: + pass + +# error: [invalid-type-form] "List literals are not allowed in this context in a type expression" +# error: [invalid-type-form] "Dict literals are not allowed in type expressions" +class name_4[name_1: [{}]]: + pass ``` ## Diagnostics for common errors @@ -145,3 +165,42 @@ from PIL import Image def g(x: Image): ... # error: [invalid-type-form] ``` + +### List-literal used when you meant to use a list or tuple + +```py +def _( + x: [int], # error: [invalid-type-form] +) -> [int]: # error: [invalid-type-form] + return x +``` + +```py +def _( + x: [int, str], # error: [invalid-type-form] +) -> [int, str]: # error: [invalid-type-form] + return x +``` + +### Tuple-literal used when you meant to use a tuple + +```py +def _( + x: (), # error: [invalid-type-form] +) -> (): # error: [invalid-type-form] + return x +``` + +```py +def _( + x: (int,), # error: [invalid-type-form] +) -> (int,): # error: [invalid-type-form] + return x +``` + +```py +def _( + x: (int, str), # error: [invalid-type-form] +) -> (int, str): # error: [invalid-type-form] + return x +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(f80dbf5dd571c940).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(f80dbf5dd571c940).snap" new file mode 100644 index 00000000000000..6e2b2559036546 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(f80dbf5dd571c940).snap" @@ -0,0 +1,91 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid.md - Tests for invalid types in type expressions - Diagnostics for common errors - List-literal used when you meant to use a list or tuple +mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | def _( +2 | x: [int], # error: [invalid-type-form] +3 | ) -> [int]: # error: [invalid-type-form] +4 | return x +5 | def _( +6 | x: [int, str], # error: [invalid-type-form] +7 | ) -> [int, str]: # error: [invalid-type-form] +8 | return x +``` + +# Diagnostics + +``` +error[invalid-type-form]: List literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:2:8 + | +1 | def _( +2 | x: [int], # error: [invalid-type-form] + | ^^^^^ Did you mean `list[int]`? +3 | ) -> [int]: # error: [invalid-type-form] +4 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: List literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:3:6 + | +1 | def _( +2 | x: [int], # error: [invalid-type-form] +3 | ) -> [int]: # error: [invalid-type-form] + | ^^^^^ Did you mean `list[int]`? +4 | return x +5 | def _( + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: List literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:6:8 + | +4 | return x +5 | def _( +6 | x: [int, str], # error: [invalid-type-form] + | ^^^^^^^^^^ Did you mean `tuple[int, str]`? +7 | ) -> [int, str]: # error: [invalid-type-form] +8 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: List literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:7:6 + | +5 | def _( +6 | x: [int, str], # error: [invalid-type-form] +7 | ) -> [int, str]: # error: [invalid-type-form] + | ^^^^^^^^^^ Did you mean `tuple[int, str]`? +8 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" new file mode 100644 index 00000000000000..5e268cd80ce593 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" @@ -0,0 +1,129 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid.md - Tests for invalid types in type expressions - Diagnostics for common errors - Tuple-literal used when you meant to use a tuple +mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def _( + 2 | x: (), # error: [invalid-type-form] + 3 | ) -> (): # error: [invalid-type-form] + 4 | return x + 5 | def _( + 6 | x: (int,), # error: [invalid-type-form] + 7 | ) -> (int,): # error: [invalid-type-form] + 8 | return x + 9 | def _( +10 | x: (int, str), # error: [invalid-type-form] +11 | ) -> (int, str): # error: [invalid-type-form] +12 | return x +``` + +# Diagnostics + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:2:8 + | +1 | def _( +2 | x: (), # error: [invalid-type-form] + | ^^ Did you mean `tuple[()]`? +3 | ) -> (): # error: [invalid-type-form] +4 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:3:6 + | +1 | def _( +2 | x: (), # error: [invalid-type-form] +3 | ) -> (): # error: [invalid-type-form] + | ^^ Did you mean `tuple[()]`? +4 | return x +5 | def _( + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:6:8 + | +4 | return x +5 | def _( +6 | x: (int,), # error: [invalid-type-form] + | ^^^^^^ Did you mean `tuple[int]`? +7 | ) -> (int,): # error: [invalid-type-form] +8 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:7:6 + | +5 | def _( +6 | x: (int,), # error: [invalid-type-form] +7 | ) -> (int,): # error: [invalid-type-form] + | ^^^^^^ Did you mean `tuple[int]`? +8 | return x +9 | def _( + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:10:8 + | + 8 | return x + 9 | def _( +10 | x: (int, str), # error: [invalid-type-form] + | ^^^^^^^^^^ Did you mean `tuple[int, str]`? +11 | ) -> (int, str): # error: [invalid-type-form] +12 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` + +``` +error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression + --> src/mdtest_snippet.py:11:6 + | + 9 | def _( +10 | x: (int, str), # error: [invalid-type-form] +11 | ) -> (int, str): # error: [invalid-type-form] + | ^^^^^^^^^^ Did you mean `tuple[int, str]`? +12 | return x + | +info: See the following page for a reference on valid type expressions: +info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions +info: rule `invalid-type-form` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 7233c81b6e0c09..8bcf8a8bb09a66 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1878,11 +1878,14 @@ pub(crate) fn report_invalid_arguments_to_callable( )); } -pub(crate) fn add_type_expression_reference_link(mut diag: LintDiagnosticGuard) { +pub(crate) fn add_type_expression_reference_link<'db, 'ctx>( + mut diag: LintDiagnosticGuard<'db, 'ctx>, +) -> LintDiagnosticGuard<'db, 'ctx> { diag.info("See the following page for a reference on valid type expressions:"); diag.info( "https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions", ); + diag } pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol( diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 6650552b1e87c9..8de15d66f4f5cb 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -33,6 +33,7 @@ //! the query cycle until a fixed-point is reached. Salsa has a built-in fixed limit on the number //! of iterations, so if we fail to converge, Salsa will eventually panic. (This should of course //! be considered a bug.) + use itertools::{Either, Itertools}; use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity}; use ruff_db::files::File; @@ -95,11 +96,11 @@ use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, - KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, - ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness, - TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, - TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, - UnionType, binding_type, todo_type, + KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, + PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType, + SubclassOfType, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, + TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, + TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -8308,7 +8309,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.store_expression_type(slice, inner_annotation_ty.inner_type()); inner_annotation_ty } else { - self.infer_type_expression(slice); + for argument in arguments { + self.infer_expression(argument); + } + self.store_expression_type(slice, Type::unknown()); TypeAndQualifiers::unknown() } } else { @@ -8416,15 +8420,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } fn report_invalid_type_expression( - &mut self, + &self, expression: &ast::Expr, message: std::fmt::Arguments, - ) -> Type<'db> { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, expression) { - let diag = builder.into_diagnostic(message); - diagnostic::add_type_expression_reference_link(diag); - } - Type::unknown() + ) -> Option { + self.context + .report_lint(&INVALID_TYPE_FORM, expression) + .map(|builder| { + diagnostic::add_type_expression_reference_link(builder.into_diagnostic(message)) + }) } /// Infer the type of a type expression without storing the result. @@ -8511,56 +8515,126 @@ impl<'db> TypeInferenceBuilder<'db, '_> { // TODO: add a subdiagnostic linking to type-expression grammar // and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]` - ast::Expr::BytesLiteral(_) => self.report_invalid_type_expression( - expression, - format_args!("Bytes literals are not allowed in this context in a type expression"), - ), + ast::Expr::BytesLiteral(_) => { + self.report_invalid_type_expression( + expression, + format_args!( + "Bytes literals are not allowed in this context in a type expression" + ), + ); + Type::unknown() + } - // TODO: add a subdiagnostic linking to type-expression grammar - // and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]` ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(_), .. - }) => self.report_invalid_type_expression( - expression, - format_args!("Int literals are not allowed in this context in a type expression"), - ), + }) => { + self.report_invalid_type_expression( + expression, + format_args!( + "Int literals are not allowed in this context in a type expression" + ), + ); + + Type::unknown() + } ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Float(_), .. - }) => self.report_invalid_type_expression( - expression, - format_args!("Float literals are not allowed in type expressions"), - ), + }) => { + self.report_invalid_type_expression( + expression, + format_args!("Float literals are not allowed in type expressions"), + ); + Type::unknown() + } ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Complex { .. }, .. - }) => self.report_invalid_type_expression( - expression, - format_args!("Complex literals are not allowed in type expressions"), - ), + }) => { + self.report_invalid_type_expression( + expression, + format_args!("Complex literals are not allowed in type expressions"), + ); + Type::unknown() + } - // TODO: add a subdiagnostic linking to type-expression grammar - // and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]` - ast::Expr::BooleanLiteral(_) => self.report_invalid_type_expression( - expression, - format_args!( - "Boolean literals are not allowed in this context in a type expression" - ), - ), + ast::Expr::BooleanLiteral(_) => { + self.report_invalid_type_expression( + expression, + format_args!( + "Boolean literals are not allowed in this context in a type expression" + ), + ); + Type::unknown() + } - // TODO: add a subdiagnostic linking to type-expression grammar - // and stating that it is only valid as first argument to `typing.Callable[]` ast::Expr::List(list) => { - self.infer_list_expression(list); - self.report_invalid_type_expression( + let db = self.db(); + + let inner_types: Vec> = list + .iter() + .map(|element| self.infer_type_expression(element)) + .collect(); + + if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, format_args!( "List literals are not allowed in this context in a type expression" ), - ) + ) { + if !inner_types.iter().any(|ty| { + matches!( + ty, + Type::Dynamic(DynamicType::Todo(_) | DynamicType::Unknown) + ) + }) { + let hinted_type = if list.len() == 1 { + KnownClass::List.to_specialized_instance(db, inner_types) + } else { + TupleType::from_elements(db, inner_types) + }; + + diagnostic.set_primary_message(format_args!( + "Did you mean `{}`?", + hinted_type.display(self.db()), + )); + } + } + Type::unknown() + } + + ast::Expr::Tuple(tuple) => { + let inner_types: Vec> = tuple + .elts + .iter() + .map(|expr| self.infer_type_expression(expr)) + .collect(); + + if tuple.parenthesized { + if let Some(mut diagnostic) = self.report_invalid_type_expression( + expression, + format_args!( + "Tuple literals are not allowed in this context in a type expression" + ), + ) { + if !inner_types.iter().any(|ty| { + matches!( + ty, + Type::Dynamic(DynamicType::Todo(_) | DynamicType::Unknown) + ) + }) { + let hinted_type = TupleType::from_elements(self.db(), inner_types); + diagnostic.set_primary_message(format_args!( + "Did you mean `{}`?", + hinted_type.display(self.db()), + )); + } + } + } + Type::unknown() } ast::Expr::BoolOp(bool_op) => { @@ -8568,7 +8642,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Boolean operations are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Named(named) => { @@ -8576,7 +8651,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Named expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::UnaryOp(unary) => { @@ -8584,7 +8660,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Unary operations are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Lambda(lambda_expression) => { @@ -8592,7 +8669,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("`lambda` expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::If(if_expression) => { @@ -8600,7 +8678,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("`if` expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Dict(dict) => { @@ -8608,7 +8687,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Dict literals are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Set(set) => { @@ -8616,7 +8696,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Set literals are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::DictComp(dictcomp) => { @@ -8624,7 +8705,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Dict comprehensions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::ListComp(listcomp) => { @@ -8632,7 +8714,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("List comprehensions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::SetComp(setcomp) => { @@ -8640,7 +8723,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Set comprehensions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Generator(generator) => { @@ -8648,7 +8732,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Generator expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Await(await_expression) => { @@ -8656,7 +8741,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("`await` expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Yield(yield_expression) => { @@ -8664,7 +8750,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("`yield` expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::YieldFrom(yield_from) => { @@ -8672,7 +8759,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("`yield from` expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Compare(compare) => { @@ -8680,7 +8768,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Comparison expressions are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Call(call_expr) => { @@ -8688,7 +8777,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Function calls are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::FString(fstring) => { @@ -8696,7 +8786,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("F-strings are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::TString(tstring) => { @@ -8704,7 +8795,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("T-strings are not allowed in type expressions"), - ) + ); + Type::unknown() } ast::Expr::Slice(slice) => { @@ -8712,7 +8804,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.report_invalid_type_expression( expression, format_args!("Slices are not allowed in type expressions"), - ) + ); + Type::unknown() } // ================================================================================= @@ -8724,11 +8817,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { todo_type!("ellipsis literal in type expression") } - ast::Expr::Tuple(tuple) => { - self.infer_tuple_expression(tuple); - Type::unknown() - } - ast::Expr::Starred(starred) => { self.infer_starred_expression(starred); todo_type!("PEP 646") @@ -9076,7 +9164,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } let [type_expr, metadata @ ..] = &arguments[..] else { - self.infer_type_expression(arguments_slice); + for argument in arguments { + self.infer_expression(argument); + } + self.store_expression_type(arguments_slice, Type::unknown()); return Type::unknown(); }; From 331821244bb519b970d5a6de99c83ca6a059ed2b Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 8 Jun 2025 20:10:13 -0400 Subject: [PATCH 361/487] Refactor fix in `readlines-in-for` (#18573) ## Summary Post-merge feedback from https://github.com/astral-sh/ruff/pull/18542. --- .../resources/test/fixtures/refurb/FURB129.py | 6 ++--- .../rules/refurb/rules/readlines_in_for.rs | 26 ++++++++----------- ...es__refurb__tests__FURB129_FURB129.py.snap | 25 +++++++----------- ...b__tests__preview__FURB129_FURB129.py.snap | 25 +++++++----------- 4 files changed, 31 insertions(+), 51 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py index 05a04f3d652d96..52b4591a4eddae 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py @@ -102,8 +102,6 @@ def readlines(self) -> list[str]: pass for line in(f).readlines(): pass - # Test case for issue #17683 - missing space before keyword + + # Test case for issue #17683 (missing space before keyword) print([line for line in f.readlines()if True]) - print([line for line in f.readlines()and True]) - print([line for line in f.readlines()or True]) - print([line for line in f.readlines()in ["test"]]) diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index 76431b2ec93d83..f945b8fdb2c928 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -86,27 +86,23 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { return; } } - let edit = if let Some(parenthesized_range) = parenthesized_range( + + let deletion_range = if let Some(parenthesized_range) = parenthesized_range( expr_attr.value.as_ref().into(), expr_attr.into(), checker.comment_ranges(), checker.source(), ) { - let deletion_range = expr_call.range().add_start(parenthesized_range.len()); - let padded = pad_end(String::new(), deletion_range.end(), checker.locator()); - if padded.is_empty() { - Edit::range_deletion(deletion_range) - } else { - Edit::range_replacement(padded, deletion_range) - } + expr_call.range().add_start(parenthesized_range.len()) } else { - let deletion_range = expr_call.range().add_start(expr_attr.value.range().len()); - let padded = pad_end(String::new(), deletion_range.end(), checker.locator()); - if padded.is_empty() { - Edit::range_deletion(deletion_range) - } else { - Edit::range_replacement(padded, deletion_range) - } + expr_call.range().add_start(expr_attr.value.range().len()) + }; + + let padded = pad_end(String::new(), deletion_range.end(), checker.locator()); + let edit = if padded.is_empty() { + Edit::range_deletion(deletion_range) + } else { + Edit::range_replacement(padded, deletion_range) }; let mut diagnostic = checker.report_diagnostic(ReadlinesInFor, expr_call.range()); diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap index 412c2ea5fa0e06..ce9c375ba06500 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap @@ -307,7 +307,6 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 | for line in(f).readlines(): | ^^^^^^^^^^^^^^^ FURB129 104 | pass -105 | # Test case for issue #17683 - missing space before keyword | = help: Remove `readlines()` @@ -318,26 +317,20 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 |- for line in(f).readlines(): 103 |+ for line in(f): 104 104 | pass -105 105 | # Test case for issue #17683 - missing space before keyword -106 106 | print([line for line in f.readlines()if True]) +105 105 | +106 106 | # Test case for issue #17683 (missing space before keyword) -FURB129.py:106:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:107:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -104 | pass -105 | # Test case for issue #17683 - missing space before keyword -106 | print([line for line in f.readlines()if True]) +106 | # Test case for issue #17683 (missing space before keyword) +107 | print([line for line in f.readlines()if True]) | ^^^^^^^^^^^^^ FURB129 -107 | print([line for line in f.readlines()and True]) -108 | print([line for line in f.readlines()or True]) | = help: Remove `readlines()` ℹ Unsafe fix -103 103 | for line in(f).readlines(): 104 104 | pass -105 105 | # Test case for issue #17683 - missing space before keyword -106 |- print([line for line in f.readlines()if True]) - 106 |+ print([line for line in f if True]) -107 107 | print([line for line in f.readlines()and True]) -108 108 | print([line for line in f.readlines()or True]) -109 109 | print([line for line in f.readlines()in ["test"]]) +105 105 | +106 106 | # Test case for issue #17683 (missing space before keyword) +107 |- print([line for line in f.readlines()if True]) + 107 |+ print([line for line in f if True]) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap index 2f6f162df7a0bf..f466c31940af82 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap @@ -307,7 +307,6 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 | for line in(f).readlines(): | ^^^^^^^^^^^^^^^ FURB129 104 | pass -105 | # Test case for issue #17683 - missing space before keyword | = help: Remove `readlines()` @@ -318,26 +317,20 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi 103 |- for line in(f).readlines(): 103 |+ for line in(f): 104 104 | pass -105 105 | # Test case for issue #17683 - missing space before keyword -106 106 | print([line for line in f.readlines()if True]) +105 105 | +106 106 | # Test case for issue #17683 (missing space before keyword) -FURB129.py:106:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly +FURB129.py:107:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly | -104 | pass -105 | # Test case for issue #17683 - missing space before keyword -106 | print([line for line in f.readlines()if True]) +106 | # Test case for issue #17683 (missing space before keyword) +107 | print([line for line in f.readlines()if True]) | ^^^^^^^^^^^^^ FURB129 -107 | print([line for line in f.readlines()and True]) -108 | print([line for line in f.readlines()or True]) | = help: Remove `readlines()` ℹ Safe fix -103 103 | for line in(f).readlines(): 104 104 | pass -105 105 | # Test case for issue #17683 - missing space before keyword -106 |- print([line for line in f.readlines()if True]) - 106 |+ print([line for line in f if True]) -107 107 | print([line for line in f.readlines()and True]) -108 108 | print([line for line in f.readlines()or True]) -109 109 | print([line for line in f.readlines()in ["test"]]) +105 105 | +106 106 | # Test case for issue #17683 (missing space before keyword) +107 |- print([line for line in f.readlines()if True]) + 107 |+ print([line for line in f if True]) From 0232e422b2b4c7224637e92e54adc554d9328ba1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 8 Jun 2025 20:20:35 -0400 Subject: [PATCH 362/487] Add `CONDA_PREFIX` to `--python` documentation (#18574) ## Summary Noticed this while working on https://github.com/astral-sh/ty/pull/612. --- crates/ty/docs/cli.md | 4 ++-- crates/ty/src/args.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index ae4382bd5d8fee..719ebd3b9397d1 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -65,9 +65,9 @@ over all configuration files.

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    --python path

    Path to the Python environment.

    ty uses the Python environment to resolve type information and third-party dependencies.

    -

    If not specified, ty will attempt to infer it from the VIRTUAL_ENV environment variable or discover a .venv directory in the project root or working directory.

    +

    If not specified, ty will attempt to infer it from the VIRTUAL_ENV or CONDA_PREFIX environment variables, or discover a .venv directory in the project root or working directory.

    If a path to a Python interpreter is provided, e.g., .venv/bin/python3, ty will attempt to find an environment two directories up from the interpreter's path, e.g., .venv. At this time, ty does not invoke the interpreter to determine the location of the environment. This means that ty will not resolve dynamic executables such as a shim.

    -

    ty will search in the resolved environments's site-packages directories for type information and third-party imports.

    +

    ty will search in the resolved environment's site-packages directories for type information and third-party imports.

    --python-platform, --platform platform

    Target platform to assume when resolving types.

    This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

    --python-version, --target-version version

    Python version to assume when resolving types.

    diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index d2cfadcff90757..21e44d9920e0be 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -55,15 +55,16 @@ pub(crate) struct CheckCommand { /// /// ty uses the Python environment to resolve type information and third-party dependencies. /// - /// If not specified, ty will attempt to infer it from the `VIRTUAL_ENV` environment variable or - /// discover a `.venv` directory in the project root or working directory. + /// If not specified, ty will attempt to infer it from the `VIRTUAL_ENV` or `CONDA_PREFIX` + /// environment variables, or discover a `.venv` directory in the project root or working + /// directory. /// /// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to /// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this /// time, ty does not invoke the interpreter to determine the location of the environment. This /// means that ty will not resolve dynamic executables such as a shim. /// - /// ty will search in the resolved environments's `site-packages` directories for type + /// ty will search in the resolved environment's `site-packages` directories for type /// information and third-party imports. #[arg(long, value_name = "PATH")] pub(crate) python: Option, From 6cefbb6b3884ba464ba4c298dc2b9f29f5e48297 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:23:11 +0100 Subject: [PATCH 363/487] Update dependency ruff to v0.11.13 (#18580) --- docs/requirements-insiders.txt | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index 4848ecca167fd8..9927a86fd7f462 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.12 +ruff==0.11.13 mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 diff --git a/docs/requirements.txt b/docs/requirements.txt index c288da37d816bb..dee97e99b425a6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ PyYAML==6.0.2 -ruff==0.11.12 +ruff==0.11.13 mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 From c18dc41f1ac105b6bbcbf5faa6ccfb20d83c5915 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:28:52 +0100 Subject: [PATCH 364/487] Update Rust crate camino to v1.1.10 (#18584) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba761fc306ccf9..b64f6f9cc9406b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] From 1cf7b67e854559bb35ed0547e2a1b89829b7dc5a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:03:19 +0200 Subject: [PATCH 365/487] Update Rust crate anstream to v0.6.19 (#18582) --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b64f6f9cc9406b..5795590335d814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -498,7 +498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -507,7 +507,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -921,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1460,7 +1460,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.1", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1524,7 +1524,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3176,7 +3176,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3545,7 +3545,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4542,7 +4542,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From 8d1d0be648c59c62279f1e3845e224110c5f6aff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:03:58 +0200 Subject: [PATCH 366/487] Update Rust crate hashbrown to v0.15.4 (#18585) --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5795590335d814..3b08f20604472a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,9 +1125,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -1140,7 +1140,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -1338,7 +1338,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -1358,7 +1358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -3201,7 +3201,7 @@ dependencies = [ "crossbeam-queue", "dashmap 6.1.0", "hashbrown 0.14.5", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "hashlink", "indexmap", "parking_lot", @@ -3970,7 +3970,7 @@ dependencies = [ "countme", "dir-test", "drop_bomb", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap", "insta", "itertools 0.14.0", From b5a77df46f915e19d3f4d0064e03047cacea59d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:04:29 +0200 Subject: [PATCH 367/487] Update Rust crate smallvec to v1.15.1 (#18586) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b08f20604472a..1131908222e3c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3431,9 +3431,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snapbox" From 3fa5a9ff3ba7db384bf129883f637e923971bfc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:05:16 +0200 Subject: [PATCH 368/487] Update dependency pyodide to v0.27.7 (#18579) --- playground/package-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/playground/package-lock.json b/playground/package-lock.json index e6958158764aaf..c140b0524c105c 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -5218,10 +5218,10 @@ } }, "node_modules/pyodide": { - "version": "0.27.6", - "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.6.tgz", - "integrity": "sha512-ahiSHHs6iFKl2f8aO1wALINAlMNDLAtb44xCI87GQyH2tLDk8F8VWip3u1ZNIyglGSCYAOSFzWKwS1f9gBFVdg==", - "license": "Apache-2.0", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.7.tgz", + "integrity": "sha512-RUSVJlhQdfWfgO9hVHCiXoG+nVZQRS5D9FzgpLJ/VcgGBLSAKoPL8kTiOikxbHQm1kRISeWUBdulEgO26qpSRA==", + "license": "MPL-2.0", "dependencies": { "ws": "^8.5.0" }, @@ -6504,7 +6504,7 @@ "classnames": "^2.5.1", "lz-string": "^1.5.0", "monaco-editor": "^0.52.2", - "pyodide": "^0.27.4", + "pyodide": "^0.27.7", "react": "^19.0.0", "react-dom": "^19.0.0", "react-resizable-panels": "^3.0.0", From ea64c01524639b336c5b09a0298b15fba57c60c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:06:02 +0200 Subject: [PATCH 369/487] Update cargo-bins/cargo-binstall action to v1.12.7 (#18578) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f715c88389bd6..e3d1cc95187a7c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -437,7 +437,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install cargo-binstall" - uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6 + uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7 with: tool: cargo-fuzz@0.11.2 - name: "Install cargo-fuzz" @@ -690,7 +690,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: cargo-bins/cargo-binstall@e8c9cc3599f6c4063d143083205f98ca25d91677 # v1.12.6 + - uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7 - run: cargo binstall --no-confirm cargo-shear - run: cargo shear From 5fe6fa74a0deb18bb2849e513f0306634bb26372 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 07:06:19 +0200 Subject: [PATCH 370/487] Update rui314/setup-mold digest to b395809 (#18577) --- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/daily_fuzz.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e3d1cc95187a7c..a18c5e393918a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -237,7 +237,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 + uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: @@ -295,7 +295,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 + uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: @@ -380,7 +380,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 + uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1 - name: "Build" run: cargo build --release --locked @@ -405,7 +405,7 @@ jobs: MSRV: ${{ steps.msrv.outputs.value }} run: rustup default "${MSRV}" - name: "Install mold" - uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 + uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1 - name: "Install cargo nextest" uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4 with: diff --git a/.github/workflows/daily_fuzz.yaml b/.github/workflows/daily_fuzz.yaml index fea1adfc1b6cc0..751254bdc7741e 100644 --- a/.github/workflows/daily_fuzz.yaml +++ b/.github/workflows/daily_fuzz.yaml @@ -38,7 +38,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - name: "Install mold" - uses: rui314/setup-mold@67424c1b3680e35255d95971cbd5de0047bf31c3 # v1 + uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1 - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 - name: Build ruff # A debug build means the script runs slower once it gets started, From b4b53183b7b571c4c38c729a8549f00714429fb6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:08:02 +0200 Subject: [PATCH 371/487] Update actions/checkout digest to 09d2aca (#18576) --- .github/workflows/release.yml | 8 ++++---- dist-workspace.toml | 36 +++++++++++++++++------------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 847160c25b3683..88048a0bccd041 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 + - uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f with: persist-credentials: false submodules: recursive @@ -124,7 +124,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json steps: - - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 + - uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f with: persist-credentials: false submodules: recursive @@ -175,7 +175,7 @@ jobs: outputs: val: ${{ steps.host.outputs.manifest }} steps: - - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 + - uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f with: persist-credentials: false submodules: recursive @@ -251,7 +251,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 + - uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f with: persist-credentials: false submodules: recursive diff --git a/dist-workspace.toml b/dist-workspace.toml index e3968dfbe7c452..15782a99a6bf8e 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -18,22 +18,22 @@ windows-archive = ".zip" unix-archive = ".tar.gz" # Target platforms to build apps for (Rust target-triple syntax) targets = [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl", - "aarch64-pc-windows-msvc", - "arm-unknown-linux-musleabihf", - "armv7-unknown-linux-gnueabihf", - "armv7-unknown-linux-musleabihf", - "x86_64-apple-darwin", - "powerpc64-unknown-linux-gnu", - "powerpc64le-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "x86_64-unknown-linux-gnu", + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "aarch64-pc-windows-msvc", + "arm-unknown-linux-musleabihf", + "armv7-unknown-linux-gnueabihf", + "armv7-unknown-linux-musleabihf", + "x86_64-apple-darwin", + "powerpc64-unknown-linux-gnu", + "powerpc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", - "x86_64-pc-windows-msvc", - "i686-unknown-linux-gnu", - "i686-unknown-linux-musl", + "x86_64-pc-windows-msvc", + "i686-unknown-linux-gnu", + "i686-unknown-linux-musl", "i686-pc-windows-msvc" ] # Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) @@ -54,8 +54,8 @@ local-artifacts-jobs = ["./build-binaries", "./build-docker"] publish-jobs = ["./publish-pypi", "./publish-wasm"] # Post-announce jobs to run in CI post-announce-jobs = [ - "./notify-dependents", - "./publish-docs", + "./notify-dependents", + "./publish-docs", "./publish-playground" ] # Custom permissions for GitHub Jobs @@ -69,7 +69,7 @@ install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"] global = "depot-ubuntu-latest-4" [dist.github-action-commits] -"actions/checkout" = "85e6279cec87321a52edac9c87bce653a07cf6c2" # v4 +"actions/checkout" = "09d2acae674a48949e3602304ab46fd20ae0c42f" # v4 "actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2 "actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0 "actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 From 475a02b725d43d7e38bb243b3ed40afbfaf85da6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:08:17 +0200 Subject: [PATCH 372/487] Update pre-commit dependencies (#18581) --- .pre-commit-config.yaml | 4 ++-- CHANGELOG.md | 4 ++-- crates/ruff_db/src/diagnostic/render.rs | 22 +++++++++---------- .../ty_python_semantic/src/util/subscript.rs | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df1fb295f1630a..69a024e4a364d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: - black==25.1.0 - repo: https://github.com/crate-ci/typos - rev: v1.32.0 + rev: v1.33.1 hooks: - id: typos @@ -80,7 +80,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.12 + rev: v0.11.13 hooks: - id: ruff-format - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index 0551679010f4fb..382ff304eb51ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -230,7 +230,7 @@ - \[`airflow`\] Add missing `AIR302` attribute check ([#17115](https://github.com/astral-sh/ruff/pull/17115)) - \[`airflow`\] Expand module path check to individual symbols (`AIR302`) ([#17278](https://github.com/astral-sh/ruff/pull/17278)) - \[`airflow`\] Extract `AIR312` from `AIR302` rules (`AIR302`, `AIR312`) ([#17152](https://github.com/astral-sh/ruff/pull/17152)) -- \[`airflow`\] Update oudated `AIR301`, `AIR302` rules ([#17123](https://github.com/astral-sh/ruff/pull/17123)) +- \[`airflow`\] Update outdated `AIR301`, `AIR302` rules ([#17123](https://github.com/astral-sh/ruff/pull/17123)) - [syntax-errors] Async comprehension in sync comprehension ([#17177](https://github.com/astral-sh/ruff/pull/17177)) - [syntax-errors] Check annotations in annotated assignments ([#17283](https://github.com/astral-sh/ruff/pull/17283)) - [syntax-errors] Extend annotation checks to `await` ([#17282](https://github.com/astral-sh/ruff/pull/17282)) @@ -1313,7 +1313,7 @@ The following fixes have been stabilized: - Detect items that hash to same value in duplicate sets (`B033`, `PLC0208`) ([#14064](https://github.com/astral-sh/ruff/pull/14064)) - \[`eradicate`\] Better detection of IntelliJ language injection comments (`ERA001`) ([#14094](https://github.com/astral-sh/ruff/pull/14094)) - \[`flake8-pyi`\] Add autofix for `docstring-in-stub` (`PYI021`) ([#14150](https://github.com/astral-sh/ruff/pull/14150)) -- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to alawys provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188)) +- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to always provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188)) - \[`pyflakes`\] Detect items that hash to same value in duplicate dictionaries (`F601`) ([#14065](https://github.com/astral-sh/ruff/pull/14065)) - \[`ruff`\] Fix false positive for decorators (`RUF028`) ([#14061](https://github.com/astral-sh/ruff/pull/14061)) diff --git a/crates/ruff_db/src/diagnostic/render.rs b/crates/ruff_db/src/diagnostic/render.rs index af720e45d94540..b00332c2c114b0 100644 --- a/crates/ruff_db/src/diagnostic/render.rs +++ b/crates/ruff_db/src/diagnostic/render.rs @@ -265,7 +265,7 @@ impl<'a> ResolvedDiagnostic<'a> { .get(); // The boundary case here is when `prev_context_ends` // is exactly one less than `this_context_begins`. In - // that case, the context windows are adajcent and we + // that case, the context windows are adjacent and we // should fall through below to add this annotation to // the existing snippet. if this_context_begins.saturating_sub(prev_context_ends) > 1 { @@ -754,7 +754,7 @@ kangaroo static FRUITS: &str = "\ apple banana -cantelope +cantaloupe lime orange pear @@ -1376,8 +1376,8 @@ watermelon | 1 | apple 2 | banana - 3 | cantelope - | ^^^^^^^^^ + 3 | cantaloupe + | ^^^^^^^^^^ 4 | lime 5 | orange | @@ -1479,8 +1479,8 @@ watermelon | 1 | apple 2 | banana - 3 | cantelope - | ^^^^^^^^^ + 3 | cantaloupe + | ^^^^^^^^^^ 4 | lime 5 | orange | @@ -1515,8 +1515,8 @@ watermelon | 1 | apple 2 | banana - 3 | cantelope - | ^^^^^^^^^ + 3 | cantaloupe + | ^^^^^^^^^^ 4 | lime 5 | orange | @@ -1562,8 +1562,8 @@ watermelon | 1 | apple 2 | banana - 3 | cantelope - | ^^^^^^^^^ + 3 | cantaloupe + | ^^^^^^^^^^ 4 | lime 5 | orange | @@ -2040,7 +2040,7 @@ watermelon 1 | apple | ^^^^^ primary 2 | banana - 3 | cantelope + 3 | cantaloupe | ::: animals:1:1 | diff --git a/crates/ty_python_semantic/src/util/subscript.rs b/crates/ty_python_semantic/src/util/subscript.rs index bf0cdaec84c9bf..92765faf716a7c 100644 --- a/crates/ty_python_semantic/src/util/subscript.rs +++ b/crates/ty_python_semantic/src/util/subscript.rs @@ -342,7 +342,7 @@ mod tests { } #[test] - fn py_slice_negatice_indices() { + fn py_slice_negative_indices() { let input = ['a', 'b', 'c', 'd', 'e']; assert_eq_slice(&input, Some(-6), None, None, &['a', 'b', 'c', 'd', 'e']); From aa3c312f5f4e83d7375e37548f8da1677a7303fe Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 9 Jun 2025 11:26:10 +0100 Subject: [PATCH 373/487] [ty] Fix panic when trying to pull types for subscript expressions inside `Callable` type expressions (#18534) --- .../test/corpus/callable_with_concatenate.py | 6 ++++ crates/ty_project/tests/check.rs | 18 +++--------- .../resources/mdtest/annotations/callable.md | 25 ++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 29 +++++++++++++++++-- 4 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 crates/ty_project/resources/test/corpus/callable_with_concatenate.py diff --git a/crates/ty_project/resources/test/corpus/callable_with_concatenate.py b/crates/ty_project/resources/test/corpus/callable_with_concatenate.py new file mode 100644 index 00000000000000..19e984edbbe398 --- /dev/null +++ b/crates/ty_project/resources/test/corpus/callable_with_concatenate.py @@ -0,0 +1,6 @@ +from typing_extensions import TypeVar, Callable, Concatenate, ParamSpec + +_T = TypeVar("_T") +_P = ParamSpec("_P") + +def f(self, callable: Callable[Concatenate[_T, _P], _T]) -> Callable[_P, _T]: ... diff --git a/crates/ty_project/tests/check.rs b/crates/ty_project/tests/check.rs index ae1ab54cb628d3..93a4f727fd3760 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_project/tests/check.rs @@ -117,8 +117,10 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { let code = std::fs::read_to_string(source)?; let mut check_with_file_name = |path: &SystemPath| { - if DO_NOT_ATTEMPT.contains(&&*relative_path.as_str().replace('\\', "/")) { - println!("Skipping {relative_path:?} due to known stack overflow"); + if relative_path.file_name() == Some("types.pyi") { + println!( + "Skipping {relative_path:?}: paths with `types.pyi` as their final segment cause a stack overflow" + ); return; } @@ -301,16 +303,4 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // Fails with too-many-cycle-iterations due to a self-referential // type alias, see https://github.com/astral-sh/ty/issues/256 ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py", true, true), - - // These are all "expression should belong to this TypeInference region and TypeInferenceBuilder should have inferred a type for it" - ("crates/ty_vendored/vendor/typeshed/stdlib/abc.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi", true, true), - ("crates/ty_vendored/vendor/typeshed/stdlib/curses/__init__.pyi", true, true), -]; - -/// Attempting to check one of these files causes a stack overflow -const DO_NOT_ATTEMPT: &[&str] = &[ - "crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi", - "crates/ty_vendored/vendor/typeshed/stdlib/types.pyi", - "crates/ty_vendored/vendor/typeshed/stdlib/wsgiref/types.pyi", ]; diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 05adfad25b7b2c..4b398acef0ccfa 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -252,6 +252,31 @@ def _(c: Callable[[Concatenate[int, str, ...], int], int]): reveal_type(c) # revealed: (...) -> int ``` +Other type expressions can be nested inside `Concatenate`: + +```py +def _(c: Callable[[Concatenate[int | str, type[str], ...], int], int]): + # TODO: Should reveal the correct signature + reveal_type(c) # revealed: (...) -> int +``` + +But providing fewer than 2 arguments to `Concatenate` is an error: + +```py +# fmt: off + +def _( + c: Callable[Concatenate[int], int], # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 1" + d: Callable[Concatenate[(int,)], int], # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 1" + e: Callable[Concatenate[()], int] # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 0" +): + reveal_type(c) # revealed: (...) -> int + reveal_type(d) # revealed: (...) -> int + reveal_type(e) # revealed: (...) -> int + +# fmt: on +``` + ## Using `typing.ParamSpec` ```toml diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 8de15d66f4f5cb..ea20cace2c39e6 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9441,8 +9441,29 @@ impl<'db> TypeInferenceBuilder<'db, '_> { todo_type!("`TypeGuard[]` special form") } SpecialFormType::Concatenate => { - self.infer_type_expression(arguments_slice); - todo_type!("`Concatenate[]` special form") + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + for argument in arguments { + self.infer_type_expression(argument); + } + let num_arguments = arguments.len(); + let inferred_type = if num_arguments < 2 { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "Special form `{special_form}` expected at least 2 parameters but got {num_arguments}", + )); + } + Type::unknown() + } else { + todo_type!("`Concatenate[]` special form") + }; + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, inferred_type); + } + inferred_type } SpecialFormType::Unpack => { self.infer_type_expression(arguments_slice); @@ -9622,7 +9643,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { })) }); } - ast::Expr::Subscript(_) => { + ast::Expr::Subscript(subscript) => { + let value_ty = self.infer_expression(&subscript.value); + self.infer_subscript_type_expression(subscript, value_ty); // TODO: Support `Concatenate[...]` return Some(Parameters::todo()); } From b01c95d460226cce75e703000b2ae08f2160b6ac Mon Sep 17 00:00:00 2001 From: Frazer McLean Date: Mon, 9 Jun 2025 12:34:19 +0200 Subject: [PATCH 374/487] `ruff/__main__.py`: Remove unnecessary `os.fsdecode` (#18551) --- python/ruff/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ruff/__main__.py b/python/ruff/__main__.py index 21fa9a20fa8e7c..8569844c4ed345 100644 --- a/python/ruff/__main__.py +++ b/python/ruff/__main__.py @@ -78,7 +78,7 @@ def get_last_three_path_parts(path: str) -> list[str]: if __name__ == "__main__": - ruff = os.fsdecode(find_ruff_bin()) + ruff = find_ruff_bin() if sys.platform == "win32": import subprocess From 07cb84426d5e2d9fb544f3c77500eadf5966fcfa Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:01:24 +1000 Subject: [PATCH 375/487] [ty] document the `"all"` option for `python-platform` (#18548) Co-authored-by: detachhead --- crates/ty/docs/configuration.md | 3 ++- crates/ty_project/src/metadata/options.rs | 3 ++- ty.schema.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index a2986eeb2709eb..2b2f4a72826e85 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -76,6 +76,7 @@ python = "./.venv" Specifies the target platform that will be used to analyze the source code. If specified, ty will understand conditions based on comparisons with `sys.platform`, such as are commonly found in typeshed to reflect the differing contents of the standard library across platforms. +If `all` is specified, ty will assume that the source code can run on any platform. If no platform is specified, ty will use the current platform: - `win32` for Windows @@ -86,7 +87,7 @@ If no platform is specified, ty will use the current platform: **Default value**: `` -**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | str` +**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str` **Example usage** (`pyproject.toml`): diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 11dd846d58c039..44324209a1edd9 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -333,6 +333,7 @@ pub struct EnvironmentOptions { /// Specifies the target platform that will be used to analyze the source code. /// If specified, ty will understand conditions based on comparisons with `sys.platform`, such /// as are commonly found in typeshed to reflect the differing contents of the standard library across platforms. + /// If `all` is specified, ty will assume that the source code can run on any platform. /// /// If no platform is specified, ty will use the current platform: /// - `win32` for Windows @@ -343,7 +344,7 @@ pub struct EnvironmentOptions { #[serde(skip_serializing_if = "Option::is_none")] #[option( default = r#""#, - value_type = r#""win32" | "darwin" | "android" | "ios" | "linux" | str"#, + value_type = r#""win32" | "darwin" | "android" | "ios" | "linux" | "all" | str"#, example = r#" # Tailor type stubs and conditionalized type definitions to windows. python-platform = "win32" diff --git a/ty.schema.json b/ty.schema.json index f4ae8d241babff..2864a6796373fa 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -88,7 +88,7 @@ ] }, "python-platform": { - "description": "Specifies the target platform that will be used to analyze the source code. If specified, ty will understand conditions based on comparisons with `sys.platform`, such as are commonly found in typeshed to reflect the differing contents of the standard library across platforms.\n\nIf no platform is specified, ty will use the current platform: - `win32` for Windows - `darwin` for macOS - `android` for Android - `ios` for iOS - `linux` for everything else", + "description": "Specifies the target platform that will be used to analyze the source code. If specified, ty will understand conditions based on comparisons with `sys.platform`, such as are commonly found in typeshed to reflect the differing contents of the standard library across platforms. If `all` is specified, ty will assume that the source code can run on any platform.\n\nIf no platform is specified, ty will use the current platform: - `win32` for Windows - `darwin` for macOS - `android` for Android - `ios` for iOS - `linux` for everything else", "anyOf": [ { "$ref": "#/definitions/PythonPlatform" From ae2150bfa35fe2e4c306f32e1609e76e9ca5520f Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:32:43 +1000 Subject: [PATCH 376/487] [ty] document how the default value for `python-version` is determined (#18549) Co-authored-by: detachhead Co-authored-by: Alex Waygood --- crates/ty/docs/configuration.md | 15 ++++++++++++--- crates/ty_project/src/metadata/options.rs | 15 ++++++++++++--- ty.schema.json | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 2b2f4a72826e85..fac5fbe108340c 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -106,9 +106,18 @@ The version should be specified as a string in the format `M.m` where `M` is the and `m` is the minor (e.g. `"3.0"` or `"3.6"`). If a version is provided, ty will generate errors if the source code makes use of language features that are not supported in that version. -It will also understand conditionals based on comparisons with `sys.version_info`, such -as are commonly found in typeshed to reflect the differing contents of the standard -library across Python versions. + +If a version is not specified, ty will try the following techniques in order of preference +to determine a value: +1. Check for the `project.requires-python` setting in a `pyproject.toml` file + and use the minimum version from the specified range +2. Check for an activated or configured virtual environment + and use the Python version of that environment +3. Fall back to the default value (see below) + +For some language features, ty can also understand conditionals based on comparisons +with `sys.version_info`. These are commonly found in typeshed, for example, +to reflect the differing contents of the standard library across Python versions. **Default value**: `"3.13"` diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 44324209a1edd9..5be3629808c50c 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -317,9 +317,18 @@ pub struct EnvironmentOptions { /// and `m` is the minor (e.g. `"3.0"` or `"3.6"`). /// If a version is provided, ty will generate errors if the source code makes use of language features /// that are not supported in that version. - /// It will also understand conditionals based on comparisons with `sys.version_info`, such - /// as are commonly found in typeshed to reflect the differing contents of the standard - /// library across Python versions. + /// + /// If a version is not specified, ty will try the following techniques in order of preference + /// to determine a value: + /// 1. Check for the `project.requires-python` setting in a `pyproject.toml` file + /// and use the minimum version from the specified range + /// 2. Check for an activated or configured virtual environment + /// and use the Python version of that environment + /// 3. Fall back to the default value (see below) + /// + /// For some language features, ty can also understand conditionals based on comparisons + /// with `sys.version_info`. These are commonly found in typeshed, for example, + /// to reflect the differing contents of the standard library across Python versions. #[serde(skip_serializing_if = "Option::is_none")] #[option( default = r#""3.13""#, diff --git a/ty.schema.json b/ty.schema.json index 2864a6796373fa..77c0194eea7961 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -99,7 +99,7 @@ ] }, "python-version": { - "description": "Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. `\"3.0\"` or `\"3.6\"`). If a version is provided, ty will generate errors if the source code makes use of language features that are not supported in that version. It will also understand conditionals based on comparisons with `sys.version_info`, such as are commonly found in typeshed to reflect the differing contents of the standard library across Python versions.", + "description": "Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. `\"3.0\"` or `\"3.6\"`). If a version is provided, ty will generate errors if the source code makes use of language features that are not supported in that version.\n\nIf a version is not specified, ty will try the following techniques in order of preference to determine a value: 1. Check for the `project.requires-python` setting in a `pyproject.toml` file and use the minimum version from the specified range 2. Check for an activated or configured virtual environment and use the Python version of that environment 3. Fall back to the default value (see below)\n\nFor some language features, ty can also understand conditionals based on comparisons with `sys.version_info`. These are commonly found in typeshed, for example, to reflect the differing contents of the standard library across Python versions.", "anyOf": [ { "$ref": "#/definitions/PythonVersion" From b44062b9ae08dcd8941ac76dbce2ab2c61840691 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 9 Jun 2025 16:39:11 +0200 Subject: [PATCH 377/487] [ty] Fix stale documents on Windows (#18544) --- crates/ty_server/src/document.rs | 45 ++++-- crates/ty_server/src/lib.rs | 2 +- crates/ty_server/src/server/api.rs | 4 +- .../ty_server/src/server/api/diagnostics.rs | 28 ++-- .../server/api/notifications/did_change.rs | 18 +-- .../notifications/did_change_watched_files.rs | 8 +- .../src/server/api/notifications/did_close.rs | 21 +-- .../api/notifications/did_close_notebook.rs | 17 ++- .../src/server/api/notifications/did_open.rs | 17 +-- .../api/notifications/did_open_notebook.rs | 16 +-- crates/ty_server/src/session.rs | 36 +++-- crates/ty_server/src/session/index.rs | 131 ++++++++---------- crates/ty_server/src/system.rs | 99 +++++++------ 13 files changed, 249 insertions(+), 193 deletions(-) diff --git a/crates/ty_server/src/document.rs b/crates/ty_server/src/document.rs index dade6a7fd1867e..fff51d2f493a13 100644 --- a/crates/ty_server/src/document.rs +++ b/crates/ty_server/src/document.rs @@ -7,6 +7,8 @@ mod text_document; pub(crate) use location::ToLink; use lsp_types::{PositionEncodingKind, Url}; + +use crate::system::AnySystemPath; pub use notebook::NotebookDocument; pub(crate) use range::{FileRangeExt, PositionExt, RangeExt, TextSizeExt, ToRangeExt}; pub(crate) use text_document::DocumentVersion; @@ -40,19 +42,38 @@ impl From for ruff_source_file::PositionEncoding { /// A unique document ID, derived from a URL passed as part of an LSP request. /// This document ID can point to either be a standalone Python file, a full notebook, or a cell within a notebook. #[derive(Clone, Debug)] -pub enum DocumentKey { - Notebook(Url), - NotebookCell(Url), - Text(Url), +pub(crate) enum DocumentKey { + Notebook(AnySystemPath), + NotebookCell { + cell_url: Url, + notebook_path: AnySystemPath, + }, + Text(AnySystemPath), } impl DocumentKey { - /// Returns the URL associated with the key. - pub(crate) fn url(&self) -> &Url { + /// Returns the file path associated with the key. + pub(crate) fn path(&self) -> &AnySystemPath { + match self { + DocumentKey::Notebook(path) | DocumentKey::Text(path) => path, + DocumentKey::NotebookCell { notebook_path, .. } => notebook_path, + } + } + + pub(crate) fn from_path(path: AnySystemPath) -> Self { + // For text documents, we assume it's a text document unless it's a notebook file. + match path.extension() { + Some("ipynb") => Self::Notebook(path), + _ => Self::Text(path), + } + } + + /// Returns the URL for this document key. For notebook cells, returns the cell URL. + /// For other document types, converts the path to a URL. + pub(crate) fn to_url(&self) -> Option { match self { - DocumentKey::NotebookCell(url) - | DocumentKey::Notebook(url) - | DocumentKey::Text(url) => url, + DocumentKey::NotebookCell { cell_url, .. } => Some(cell_url.clone()), + DocumentKey::Notebook(path) | DocumentKey::Text(path) => path.to_url(), } } } @@ -60,7 +81,11 @@ impl DocumentKey { impl std::fmt::Display for DocumentKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::NotebookCell(url) | Self::Notebook(url) | Self::Text(url) => url.fmt(f), + Self::NotebookCell { cell_url, .. } => cell_url.fmt(f), + Self::Notebook(path) | Self::Text(path) => match path { + AnySystemPath::System(system_path) => system_path.fmt(f), + AnySystemPath::SystemVirtual(virtual_path) => virtual_path.fmt(f), + }, } } } diff --git a/crates/ty_server/src/lib.rs b/crates/ty_server/src/lib.rs index 5589c60464c380..cd7d71a73c1868 100644 --- a/crates/ty_server/src/lib.rs +++ b/crates/ty_server/src/lib.rs @@ -1,6 +1,6 @@ use crate::server::{ConnectionInitializer, Server}; use anyhow::Context; -pub use document::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument}; +pub use document::{NotebookDocument, PositionEncoding, TextDocument}; pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session}; use std::num::NonZeroUsize; diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index db0c1bb1a729fd..f937adb2e4caba 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -1,6 +1,6 @@ use crate::server::schedule::Task; use crate::session::Session; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use anyhow::anyhow; use lsp_server as server; use lsp_server::RequestId; @@ -154,7 +154,7 @@ where let url = R::document_url(¶ms).into_owned(); - let Ok(path) = url_to_any_system_path(&url) else { + let Ok(path) = AnySystemPath::try_from_url(&url) else { tracing::warn!("Ignoring request for invalid `{url}`"); return Box::new(|_| {}); }; diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index 57ffbd6650dd50..812a11f9a8db32 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -12,10 +12,9 @@ use ruff_db::source::{line_index, source_text}; use ty_project::{Db, ProjectDatabase}; use super::LSPResult; -use crate::document::{FileRangeExt, ToRangeExt}; +use crate::document::{DocumentKey, FileRangeExt, ToRangeExt}; use crate::server::Result; use crate::session::client::Client; -use crate::system::url_to_any_system_path; use crate::{DocumentSnapshot, PositionEncoding, Session}; /// Represents the diagnostics for a text document or a notebook document. @@ -42,13 +41,20 @@ impl Diagnostics { } } -/// Clears the diagnostics for the document at `uri`. +/// Clears the diagnostics for the document identified by `key`. /// /// This is done by notifying the client with an empty list of diagnostics for the document. -pub(super) fn clear_diagnostics(uri: &Url, client: &Client) -> Result<()> { +/// For notebook cells, this clears diagnostics for the specific cell. +/// For other document types, this clears diagnostics for the main document. +pub(super) fn clear_diagnostics(key: &DocumentKey, client: &Client) -> Result<()> { + let Some(uri) = key.to_url() else { + // If we can't convert to URL, we can't clear diagnostics + return Ok(()); + }; + client .send_notification::(PublishDiagnosticsParams { - uri: uri.clone(), + uri, diagnostics: vec![], version: None, }) @@ -62,21 +68,27 @@ pub(super) fn clear_diagnostics(uri: &Url, client: &Client) -> Result<()> { /// This function is a no-op if the client supports pull diagnostics. /// /// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics -pub(super) fn publish_diagnostics(session: &Session, url: Url, client: &Client) -> Result<()> { +pub(super) fn publish_diagnostics( + session: &Session, + key: &DocumentKey, + client: &Client, +) -> Result<()> { if session.client_capabilities().pull_diagnostics { return Ok(()); } - let Ok(path) = url_to_any_system_path(&url) else { + let Some(url) = key.to_url() else { return Ok(()); }; + let path = key.path(); + let snapshot = session .take_snapshot(url.clone()) .ok_or_else(|| anyhow::anyhow!("Unable to take snapshot for document with URL {url}")) .with_failure_code(lsp_server::ErrorCode::InternalError)?; - let db = session.project_db_or_default(&path); + let db = session.project_db_or_default(path); let Some(diagnostics) = compute_diagnostics(db, &snapshot) else { return Ok(()); diff --git a/crates/ty_server/src/server/api/notifications/did_change.rs b/crates/ty_server/src/server/api/notifications/did_change.rs index 43b8f88c7d49d3..b7df7961c4a8a9 100644 --- a/crates/ty_server/src/server/api/notifications/did_change.rs +++ b/crates/ty_server/src/server/api/notifications/did_change.rs @@ -8,7 +8,7 @@ use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use ty_project::watch::ChangeEvent; pub(crate) struct DidChangeTextDocumentHandler; @@ -28,30 +28,32 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler { content_changes, } = params; - let Ok(path) = url_to_any_system_path(&uri) else { + let Ok(key) = session.key_from_url(uri.clone()) else { + tracing::debug!("Failed to create document key from URI: {}", uri); return Ok(()); }; - let key = session.key_from_url(uri.clone()); - session .update_text_document(&key, content_changes, version) .with_failure_code(ErrorCode::InternalError)?; - match path { + match key.path() { AnySystemPath::System(path) => { let db = match session.project_db_for_path_mut(path.as_std_path()) { Some(db) => db, None => session.default_project_db_mut(), }; - db.apply_changes(vec![ChangeEvent::file_content_changed(path)], None); + db.apply_changes(vec![ChangeEvent::file_content_changed(path.clone())], None); } AnySystemPath::SystemVirtual(virtual_path) => { let db = session.default_project_db_mut(); - db.apply_changes(vec![ChangeEvent::ChangedVirtual(virtual_path)], None); + db.apply_changes( + vec![ChangeEvent::ChangedVirtual(virtual_path.clone())], + None, + ); } } - publish_diagnostics(session, uri, client) + publish_diagnostics(session, &key, client) } } diff --git a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs index 2d27495cfe3e73..98c67829b1cf7a 100644 --- a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs +++ b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs @@ -4,7 +4,7 @@ use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use lsp_types as types; use lsp_types::{FileChangeType, notification as notif}; use rustc_hash::FxHashMap; @@ -26,7 +26,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { let mut events_by_db: FxHashMap<_, Vec> = FxHashMap::default(); for change in params.changes { - let path = match url_to_any_system_path(&change.uri) { + let path = match AnySystemPath::try_from_url(&change.uri) { Ok(path) => path, Err(err) => { tracing::warn!( @@ -111,8 +111,8 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { ) .with_failure_code(lsp_server::ErrorCode::InternalError)?; } else { - for url in session.text_document_urls() { - publish_diagnostics(session, url.clone(), client)?; + for key in session.text_document_keys() { + publish_diagnostics(session, &key, client)?; } } diff --git a/crates/ty_server/src/server/api/notifications/did_close.rs b/crates/ty_server/src/server/api/notifications/did_close.rs index 649546595e166d..b7b06cdc82a641 100644 --- a/crates/ty_server/src/server/api/notifications/did_close.rs +++ b/crates/ty_server/src/server/api/notifications/did_close.rs @@ -4,7 +4,7 @@ use crate::server::api::diagnostics::clear_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use lsp_server::ErrorCode; use lsp_types::DidCloseTextDocumentParams; use lsp_types::notification::DidCloseTextDocument; @@ -22,22 +22,25 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler { client: &Client, params: DidCloseTextDocumentParams, ) -> Result<()> { - let Ok(path) = url_to_any_system_path(¶ms.text_document.uri) else { + let Ok(key) = session.key_from_url(params.text_document.uri.clone()) else { + tracing::debug!( + "Failed to create document key from URI: {}", + params.text_document.uri + ); return Ok(()); }; - - let key = session.key_from_url(params.text_document.uri); session .close_document(&key) .with_failure_code(ErrorCode::InternalError)?; - if let AnySystemPath::SystemVirtual(virtual_path) = path { + if let AnySystemPath::SystemVirtual(virtual_path) = key.path() { let db = session.default_project_db_mut(); - db.apply_changes(vec![ChangeEvent::DeletedVirtual(virtual_path)], None); + db.apply_changes( + vec![ChangeEvent::DeletedVirtual(virtual_path.clone())], + None, + ); } - clear_diagnostics(key.url(), client)?; - - Ok(()) + clear_diagnostics(&key, client) } } diff --git a/crates/ty_server/src/server/api/notifications/did_close_notebook.rs b/crates/ty_server/src/server/api/notifications/did_close_notebook.rs index 627e5415e3923f..6916e56c32e6e9 100644 --- a/crates/ty_server/src/server/api/notifications/did_close_notebook.rs +++ b/crates/ty_server/src/server/api/notifications/did_close_notebook.rs @@ -6,7 +6,7 @@ use crate::server::api::LSPResult; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use ty_project::watch::ChangeEvent; pub(crate) struct DidCloseNotebookHandler; @@ -21,18 +21,23 @@ impl SyncNotificationHandler for DidCloseNotebookHandler { _client: &Client, params: DidCloseNotebookDocumentParams, ) -> Result<()> { - let Ok(path) = url_to_any_system_path(¶ms.notebook_document.uri) else { + let Ok(key) = session.key_from_url(params.notebook_document.uri.clone()) else { + tracing::debug!( + "Failed to create document key from URI: {}", + params.notebook_document.uri + ); return Ok(()); }; - - let key = session.key_from_url(params.notebook_document.uri); session .close_document(&key) .with_failure_code(lsp_server::ErrorCode::InternalError)?; - if let AnySystemPath::SystemVirtual(virtual_path) = path { + if let AnySystemPath::SystemVirtual(virtual_path) = key.path() { let db = session.default_project_db_mut(); - db.apply_changes(vec![ChangeEvent::DeletedVirtual(virtual_path)], None); + db.apply_changes( + vec![ChangeEvent::DeletedVirtual(virtual_path.clone())], + None, + ); } Ok(()) diff --git a/crates/ty_server/src/server/api/notifications/did_open.rs b/crates/ty_server/src/server/api/notifications/did_open.rs index 9f2dea3691233f..1f61d07f7c7f2a 100644 --- a/crates/ty_server/src/server/api/notifications/did_open.rs +++ b/crates/ty_server/src/server/api/notifications/did_open.rs @@ -7,7 +7,7 @@ use crate::server::api::diagnostics::publish_diagnostics; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; use ruff_db::Db; use ty_project::watch::ChangeEvent; @@ -31,20 +31,21 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler { }, }: DidOpenTextDocumentParams, ) -> Result<()> { - let Ok(path) = url_to_any_system_path(&uri) else { + let Ok(key) = session.key_from_url(uri.clone()) else { + tracing::debug!("Failed to create document key from URI: {}", uri); return Ok(()); }; let document = TextDocument::new(text, version).with_language_id(&language_id); - session.open_text_document(uri.clone(), document); + session.open_text_document(key.path(), document); - match &path { - AnySystemPath::System(path) => { - let db = match session.project_db_for_path_mut(path.as_std_path()) { + match key.path() { + AnySystemPath::System(system_path) => { + let db = match session.project_db_for_path_mut(system_path.as_std_path()) { Some(db) => db, None => session.default_project_db_mut(), }; - db.apply_changes(vec![ChangeEvent::Opened(path.clone())], None); + db.apply_changes(vec![ChangeEvent::Opened(system_path.clone())], None); } AnySystemPath::SystemVirtual(virtual_path) => { let db = session.default_project_db_mut(); @@ -52,6 +53,6 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler { } } - publish_diagnostics(session, uri, client) + publish_diagnostics(session, &key, client) } } diff --git a/crates/ty_server/src/server/api/notifications/did_open_notebook.rs b/crates/ty_server/src/server/api/notifications/did_open_notebook.rs index 9a8d24bb9ce504..ccae65a15d1be4 100644 --- a/crates/ty_server/src/server/api/notifications/did_open_notebook.rs +++ b/crates/ty_server/src/server/api/notifications/did_open_notebook.rs @@ -11,7 +11,7 @@ use crate::server::api::LSPResult; use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; use crate::session::Session; use crate::session::client::Client; -use crate::system::{AnySystemPath, url_to_any_system_path}; +use crate::system::AnySystemPath; pub(crate) struct DidOpenNotebookHandler; @@ -25,7 +25,7 @@ impl SyncNotificationHandler for DidOpenNotebookHandler { _client: &Client, params: DidOpenNotebookDocumentParams, ) -> Result<()> { - let Ok(path) = url_to_any_system_path(¶ms.notebook_document.uri) else { + let Ok(path) = AnySystemPath::try_from_url(¶ms.notebook_document.uri) else { return Ok(()); }; @@ -36,19 +36,19 @@ impl SyncNotificationHandler for DidOpenNotebookHandler { params.cell_text_documents, ) .with_failure_code(ErrorCode::InternalError)?; - session.open_notebook_document(params.notebook_document.uri, notebook); + session.open_notebook_document(&path, notebook); - match path { - AnySystemPath::System(path) => { - let db = match session.project_db_for_path_mut(path.as_std_path()) { + match &path { + AnySystemPath::System(system_path) => { + let db = match session.project_db_for_path_mut(system_path.as_std_path()) { Some(db) => db, None => session.default_project_db_mut(), }; - db.apply_changes(vec![ChangeEvent::Opened(path)], None); + db.apply_changes(vec![ChangeEvent::Opened(system_path.clone())], None); } AnySystemPath::SystemVirtual(virtual_path) => { let db = session.default_project_db_mut(); - db.files().virtual_file(db, &virtual_path); + db.files().virtual_file(db, virtual_path); } } diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index cced715ad56f1e..f488ddecc987f1 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -19,7 +19,7 @@ pub use self::settings::ClientSettings; pub(crate) use self::settings::Experimental; use crate::document::{DocumentKey, DocumentVersion, NotebookDocument}; use crate::session::request_queue::RequestQueue; -use crate::system::{AnySystemPath, LSPSystem, url_to_any_system_path}; +use crate::system::{AnySystemPath, LSPSystem}; use crate::{PositionEncoding, TextDocument}; mod capabilities; @@ -158,35 +158,43 @@ impl Session { .unwrap() } - pub fn key_from_url(&self, url: Url) -> DocumentKey { + pub(crate) fn key_from_url(&self, url: Url) -> crate::Result { self.index().key_from_url(url) } /// Creates a document snapshot with the URL referencing the document to snapshot. + /// + /// Returns `None` if the url can't be converted to a document key or if the document isn't open. pub fn take_snapshot(&self, url: Url) -> Option { - let key = self.key_from_url(url); + let key = self.key_from_url(url).ok()?; Some(DocumentSnapshot { resolved_client_capabilities: self.resolved_client_capabilities.clone(), - document_ref: self.index().make_document_ref(key)?, + document_ref: self.index().make_document_ref(&key)?, position_encoding: self.position_encoding, }) } - /// Iterates over the LSP URLs for all open text documents. These URLs are valid file paths. - pub(super) fn text_document_urls(&self) -> impl Iterator + '_ { - self.index().text_document_urls() + /// Iterates over the document keys for all open text documents. + pub(super) fn text_document_keys(&self) -> impl Iterator + '_ { + self.index() + .text_document_paths() + .map(|path| DocumentKey::Text(path.clone())) } - /// Registers a notebook document at the provided `url`. + /// Registers a notebook document at the provided `path`. /// If a document is already open here, it will be overwritten. - pub fn open_notebook_document(&mut self, url: Url, document: NotebookDocument) { - self.index_mut().open_notebook_document(url, document); + pub(crate) fn open_notebook_document( + &mut self, + path: &AnySystemPath, + document: NotebookDocument, + ) { + self.index_mut().open_notebook_document(path, document); } - /// Registers a text document at the provided `url`. + /// Registers a text document at the provided `path`. /// If a document is already open here, it will be overwritten. - pub(crate) fn open_text_document(&mut self, url: Url, document: TextDocument) { - self.index_mut().open_text_document(url, document); + pub(crate) fn open_text_document(&mut self, path: &AnySystemPath, document: TextDocument) { + self.index_mut().open_text_document(path, document); } /// Updates a text document at the associated `key`. @@ -314,7 +322,7 @@ impl DocumentSnapshot { } pub(crate) fn file(&self, db: &dyn Db) -> Option { - match url_to_any_system_path(self.document_ref.file_url()).ok()? { + match AnySystemPath::try_from_url(self.document_ref.file_url()).ok()? { AnySystemPath::System(path) => system_path_to_file(db, path).ok(), AnySystemPath::SystemVirtual(virtual_path) => db .files() diff --git a/crates/ty_server/src/session/index.rs b/crates/ty_server/src/session/index.rs index da0b7570ef9754..3a15b1c9a3f85c 100644 --- a/crates/ty_server/src/session/index.rs +++ b/crates/ty_server/src/session/index.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::sync::Arc; use lsp_types::Url; @@ -7,6 +6,7 @@ use rustc_hash::FxHashMap; use crate::{ PositionEncoding, TextDocument, document::{DocumentKey, DocumentVersion, NotebookDocument}, + system::AnySystemPath, }; use super::ClientSettings; @@ -14,11 +14,11 @@ use super::ClientSettings; /// Stores and tracks all open documents in a session, along with their associated settings. #[derive(Default, Debug)] pub(crate) struct Index { - /// Maps all document file URLs to the associated document controller - documents: FxHashMap, + /// Maps all document file paths to the associated document controller + documents: FxHashMap, - /// Maps opaque cell URLs to a notebook URL (document) - notebook_cells: FxHashMap, + /// Maps opaque cell URLs to a notebook path (document) + notebook_cells: FxHashMap, /// Global settings provided by the client. #[expect(dead_code)] @@ -34,18 +34,18 @@ impl Index { } } - pub(super) fn text_document_urls(&self) -> impl Iterator + '_ { + pub(super) fn text_document_paths(&self) -> impl Iterator + '_ { self.documents .iter() - .filter_map(|(url, doc)| doc.as_text().and(Some(url))) + .filter_map(|(path, doc)| doc.as_text().and(Some(path))) } #[expect(dead_code)] - pub(super) fn notebook_document_urls(&self) -> impl Iterator + '_ { + pub(super) fn notebook_document_paths(&self) -> impl Iterator + '_ { self.documents .iter() .filter(|(_, doc)| doc.as_notebook().is_some()) - .map(|(url, _)| url) + .map(|(path, _)| path) } pub(super) fn update_text_document( @@ -57,7 +57,7 @@ impl Index { ) -> crate::Result<()> { let controller = self.document_controller_for_key(key)?; let Some(document) = controller.as_text_mut() else { - anyhow::bail!("Text document URI does not point to a text document"); + anyhow::bail!("Text document path does not point to a text document"); }; if content_changes.is_empty() { @@ -70,16 +70,24 @@ impl Index { Ok(()) } - pub(crate) fn key_from_url(&self, url: Url) -> DocumentKey { - if self.notebook_cells.contains_key(&url) { - DocumentKey::NotebookCell(url) - } else if Path::new(url.path()) - .extension() - .is_some_and(|ext| ext.eq_ignore_ascii_case("ipynb")) - { - DocumentKey::Notebook(url) + pub(crate) fn key_from_url(&self, url: Url) -> crate::Result { + if let Some(notebook_path) = self.notebook_cells.get(&url) { + Ok(DocumentKey::NotebookCell { + cell_url: url, + notebook_path: notebook_path.clone(), + }) } else { - DocumentKey::Text(url) + let path = AnySystemPath::try_from_url(&url) + .map_err(|()| anyhow::anyhow!("Failed to convert URL to system path: {}", url))?; + + if path + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("ipynb")) + { + Ok(DocumentKey::Notebook(path)) + } else { + Ok(DocumentKey::Text(path)) + } } } @@ -98,48 +106,55 @@ impl Index { .. }) = cells.as_ref().and_then(|cells| cells.structure.as_ref()) { - let Some(path) = self.url_for_key(key).cloned() else { - anyhow::bail!("Tried to open unavailable document `{key}`"); - }; + let notebook_path = key.path().clone(); for opened_cell in did_open { self.notebook_cells - .insert(opened_cell.uri.clone(), path.clone()); + .insert(opened_cell.uri.clone(), notebook_path.clone()); } // deleted notebook cells are closed via textDocument/didClose - we don't close them here. } let controller = self.document_controller_for_key(key)?; let Some(notebook) = controller.as_notebook_mut() else { - anyhow::bail!("Notebook document URI does not point to a notebook document"); + anyhow::bail!("Notebook document path does not point to a notebook document"); }; notebook.update(cells, metadata, new_version, encoding)?; Ok(()) } - pub(crate) fn make_document_ref(&self, key: DocumentKey) -> Option { - let url = self.url_for_key(&key)?.clone(); - let controller = self.documents.get(&url)?; - let cell_url = match key { - DocumentKey::NotebookCell(cell_url) => Some(cell_url), - _ => None, + pub(crate) fn make_document_ref(&self, key: &DocumentKey) -> Option { + let path = key.path(); + let controller = self.documents.get(path)?; + let (cell_url, file_url) = match &key { + DocumentKey::NotebookCell { + cell_url, + notebook_path, + } => (Some(cell_url.clone()), notebook_path.to_url()?), + DocumentKey::Notebook(path) | DocumentKey::Text(path) => (None, path.to_url()?), }; - Some(controller.make_ref(cell_url, url)) + Some(controller.make_ref(cell_url, file_url)) } - pub(super) fn open_text_document(&mut self, url: Url, document: TextDocument) { + pub(super) fn open_text_document(&mut self, path: &AnySystemPath, document: TextDocument) { self.documents - .insert(url, DocumentController::new_text(document)); + .insert(path.clone(), DocumentController::new_text(document)); } - pub(super) fn open_notebook_document(&mut self, notebook_url: Url, document: NotebookDocument) { + pub(super) fn open_notebook_document( + &mut self, + notebook_path: &AnySystemPath, + document: NotebookDocument, + ) { for cell_url in document.cell_urls() { self.notebook_cells - .insert(cell_url.clone(), notebook_url.clone()); + .insert(cell_url.clone(), notebook_path.clone()); } - self.documents - .insert(notebook_url, DocumentController::new_notebook(document)); + self.documents.insert( + notebook_path.clone(), + DocumentController::new_notebook(document), + ); } pub(super) fn close_document(&mut self, key: &DocumentKey) -> crate::Result<()> { @@ -148,18 +163,16 @@ impl Index { // is requested to be `closed` by VS Code after the notebook gets updated. // This is not documented in the LSP specification explicitly, and this assumption // may need revisiting in the future as we support more editors with notebook support. - if let DocumentKey::NotebookCell(uri) = key { - if self.notebook_cells.remove(uri).is_none() { - tracing::warn!("Tried to remove a notebook cell that does not exist: {uri}",); + if let DocumentKey::NotebookCell { cell_url, .. } = key { + if self.notebook_cells.remove(cell_url).is_none() { + tracing::warn!("Tried to remove a notebook cell that does not exist: {cell_url}",); } return Ok(()); } - let Some(url) = self.url_for_key(key).cloned() else { - anyhow::bail!("Tried to close unavailable document `{key}`"); - }; + let path = key.path(); - let Some(_) = self.documents.remove(&url) else { - anyhow::bail!("tried to close document that didn't exist at {}", url) + let Some(_) = self.documents.remove(path) else { + anyhow::bail!("tried to close document that didn't exist at {}", key) }; Ok(()) } @@ -168,21 +181,12 @@ impl Index { &mut self, key: &DocumentKey, ) -> crate::Result<&mut DocumentController> { - let Some(url) = self.url_for_key(key).cloned() else { - anyhow::bail!("Tried to open unavailable document `{key}`"); - }; - let Some(controller) = self.documents.get_mut(&url) else { - anyhow::bail!("Document controller not available at `{}`", url); + let path = key.path(); + let Some(controller) = self.documents.get_mut(path) else { + anyhow::bail!("Document controller not available at `{}`", key); }; Ok(controller) } - - fn url_for_key<'a>(&'a self, key: &'a DocumentKey) -> Option<&'a Url> { - match key { - DocumentKey::Notebook(path) | DocumentKey::Text(path) => Some(path), - DocumentKey::NotebookCell(uri) => self.notebook_cells.get(uri), - } - } } /// A mutable handler to an underlying document. @@ -263,19 +267,6 @@ pub enum DocumentQuery { } impl DocumentQuery { - /// Retrieve the original key that describes this document query. - #[expect(dead_code)] - pub(crate) fn make_key(&self) -> DocumentKey { - match self { - Self::Text { file_url, .. } => DocumentKey::Text(file_url.clone()), - Self::Notebook { - cell_url: Some(cell_uri), - .. - } => DocumentKey::NotebookCell(cell_uri.clone()), - Self::Notebook { file_url, .. } => DocumentKey::Notebook(file_url.clone()), - } - } - /// Attempts to access the underlying notebook document that this query is selecting. pub fn as_notebook(&self) -> Option<&NotebookDocument> { match self { diff --git a/crates/ty_server/src/system.rs b/crates/ty_server/src/system.rs index 4a38ed9046b263..e946029ff34769 100644 --- a/crates/ty_server/src/system.rs +++ b/crates/ty_server/src/system.rs @@ -14,28 +14,9 @@ use ruff_notebook::{Notebook, NotebookError}; use ty_python_semantic::Db; use crate::DocumentQuery; +use crate::document::DocumentKey; use crate::session::index::Index; -/// Converts the given [`Url`] to an [`AnySystemPath`]. -/// -/// If the URL scheme is `file`, then the path is converted to a [`SystemPathBuf`]. Otherwise, the -/// URL is converted to a [`SystemVirtualPathBuf`]. -/// -/// This fails in the following cases: -/// * The URL cannot be converted to a file path (refer to [`Url::to_file_path`]). -/// * If the URL is not a valid UTF-8 string. -pub(crate) fn url_to_any_system_path(url: &Url) -> std::result::Result { - if url.scheme() == "file" { - Ok(AnySystemPath::System( - SystemPathBuf::from_path_buf(url.to_file_path()?).map_err(|_| ())?, - )) - } else { - Ok(AnySystemPath::SystemVirtual( - SystemVirtualPath::new(url.as_str()).to_path_buf(), - )) - } -} - pub(crate) fn file_to_url(db: &dyn Db, file: File) -> Option { match file.path(db) { FilePath::System(system) => Url::from_file_path(system.as_std_path()).ok(), @@ -47,19 +28,57 @@ pub(crate) fn file_to_url(db: &dyn Db, file: File) -> Option { } /// Represents either a [`SystemPath`] or a [`SystemVirtualPath`]. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) enum AnySystemPath { System(SystemPathBuf), SystemVirtual(SystemVirtualPathBuf), } impl AnySystemPath { + /// Converts the given [`Url`] to an [`AnySystemPath`]. + /// + /// If the URL scheme is `file`, then the path is converted to a [`SystemPathBuf`]. Otherwise, the + /// URL is converted to a [`SystemVirtualPathBuf`]. + /// + /// This fails in the following cases: + /// * The URL cannot be converted to a file path (refer to [`Url::to_file_path`]). + /// * If the URL is not a valid UTF-8 string. + pub(crate) fn try_from_url(url: &Url) -> std::result::Result { + if url.scheme() == "file" { + Ok(AnySystemPath::System( + SystemPathBuf::from_path_buf(url.to_file_path()?).map_err(|_| ())?, + )) + } else { + Ok(AnySystemPath::SystemVirtual( + SystemVirtualPath::new(url.as_str()).to_path_buf(), + )) + } + } + pub(crate) const fn as_system(&self) -> Option<&SystemPathBuf> { match self { AnySystemPath::System(system_path_buf) => Some(system_path_buf), AnySystemPath::SystemVirtual(_) => None, } } + + /// Returns the extension of the path, if any. + pub(crate) fn extension(&self) -> Option<&str> { + match self { + AnySystemPath::System(system_path) => system_path.extension(), + AnySystemPath::SystemVirtual(virtual_path) => virtual_path.extension(), + } + } + + /// Converts the path to a URL. + pub(crate) fn to_url(&self) -> Option { + match self { + AnySystemPath::System(system_path) => { + Url::from_file_path(system_path.as_std_path()).ok() + } + AnySystemPath::SystemVirtual(virtual_path) => Url::parse(virtual_path.as_str()).ok(), + } + } } #[derive(Debug)] @@ -107,39 +126,29 @@ impl LSPSystem { self.index.as_ref().unwrap() } - fn make_document_ref(&self, url: Url) -> Option { + fn make_document_ref(&self, path: AnySystemPath) -> Option { let index = self.index(); - let key = index.key_from_url(url); - index.make_document_ref(key) + let key = DocumentKey::from_path(path); + index.make_document_ref(&key) } - fn system_path_to_document_ref(&self, path: &SystemPath) -> Result> { - let url = Url::from_file_path(path.as_std_path()).map_err(|()| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Failed to convert system path to URL: {path:?}"), - ) - })?; - Ok(self.make_document_ref(url)) + fn system_path_to_document_ref(&self, path: &SystemPath) -> Option { + let any_path = AnySystemPath::System(path.to_path_buf()); + self.make_document_ref(any_path) } fn system_virtual_path_to_document_ref( &self, path: &SystemVirtualPath, - ) -> Result> { - let url = Url::parse(path.as_str()).map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Failed to convert virtual path to URL: {path:?}"), - ) - })?; - Ok(self.make_document_ref(url)) + ) -> Option { + let any_path = AnySystemPath::SystemVirtual(path.to_path_buf()); + self.make_document_ref(any_path) } } impl System for LSPSystem { fn path_metadata(&self, path: &SystemPath) -> Result { - let document = self.system_path_to_document_ref(path)?; + let document = self.system_path_to_document_ref(path); if let Some(document) = document { Ok(Metadata::new( @@ -161,7 +170,7 @@ impl System for LSPSystem { } fn read_to_string(&self, path: &SystemPath) -> Result { - let document = self.system_path_to_document_ref(path)?; + let document = self.system_path_to_document_ref(path); match document { Some(DocumentQuery::Text { document, .. }) => Ok(document.contents().to_string()), @@ -170,7 +179,7 @@ impl System for LSPSystem { } fn read_to_notebook(&self, path: &SystemPath) -> std::result::Result { - let document = self.system_path_to_document_ref(path)?; + let document = self.system_path_to_document_ref(path); match document { Some(DocumentQuery::Text { document, .. }) => { @@ -183,7 +192,7 @@ impl System for LSPSystem { fn read_virtual_path_to_string(&self, path: &SystemVirtualPath) -> Result { let document = self - .system_virtual_path_to_document_ref(path)? + .system_virtual_path_to_document_ref(path) .ok_or_else(|| virtual_path_not_found(path))?; if let DocumentQuery::Text { document, .. } = &document { @@ -198,7 +207,7 @@ impl System for LSPSystem { path: &SystemVirtualPath, ) -> std::result::Result { let document = self - .system_virtual_path_to_document_ref(path)? + .system_virtual_path_to_document_ref(path) .ok_or_else(|| virtual_path_not_found(path))?; match document { From 79006dfb52be2a02b9f6b0e5231cca301d8d5cad Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:07:34 -0400 Subject: [PATCH 378/487] [`refurb`] Parenthesize lambda and ternary expressions in iter (`FURB122`, `FURB142`) (#18592) Summary -- Fixes #18590 by adding parentheses around lambdas and if expressions in `for` loop iterators for FURB122 and FURB142. I also updated the docs on the helper function to reflect the part actually being parenthesized and the new checks. The `lambda` case actually causes a `TypeError` at runtime, but I think it's still worth handling to avoid causing a syntax error. ```pycon >>> s = set() ... for x in (1,) if True else (2,): ... s.add(-x) ... for x in lambda: 0: ... s.discard(-x) ... Traceback (most recent call last): File "", line 4, in for x in lambda: 0: ^^^^^^^^^ TypeError: 'function' object is not iterable ``` Test Plan -- New test cases based on the bug report --------- Co-authored-by: Dylan --- .../resources/test/fixtures/refurb/FURB122.py | 40 ++++++ .../resources/test/fixtures/refurb/FURB142.py | 25 ++++ .../refurb/rules/for_loop_set_mutations.rs | 5 +- .../src/rules/refurb/rules/for_loop_writes.rs | 5 +- .../src/rules/refurb/rules/helpers.rs | 31 ++++- ...es__refurb__tests__FURB122_FURB122.py.snap | 123 ++++++++++++++++ ...es__refurb__tests__FURB142_FURB142.py.snap | 131 ++++++++++++++++++ 7 files changed, 350 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB122.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB122.py index 9964945f63051c..c5af0eb11b0af2 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB122.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB122.py @@ -174,3 +174,43 @@ def _(): global global_foo for [a, b, (global_foo, c)] in d: f.write((a, b)) + + +# Test cases for lambda and ternary expressions - https://github.com/astral-sh/ruff/issues/18590 + +def _(): + with Path("file.txt").open("w", encoding="utf-8") as f: + for l in lambda: 0: + f.write(f"[{l}]") + + +def _(): + with Path("file.txt").open("w", encoding="utf-8") as f: + for l in (1,) if True else (2,): + f.write(f"[{l}]") + + +# don't need to add parentheses when making a function argument +def _(): + with open("file", "w") as f: + for line in lambda: 0: + f.write(line) + + +def _(): + with open("file", "w") as f: + for line in (1,) if True else (2,): + f.write(line) + + +# don't add extra parentheses if they're already parenthesized +def _(): + with open("file", "w") as f: + for line in (lambda: 0): + f.write(f"{line}") + + +def _(): + with open("file", "w") as f: + for line in ((1,) if True else (2,)): + f.write(f"{line}") diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py index 88d4188b9b76ac..cd39b2f9831c0d 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB142.py @@ -74,3 +74,28 @@ async def f(y): def g(): for x in (set(),): x.add(x) + + +# Test cases for lambda and ternary expressions - https://github.com/astral-sh/ruff/issues/18590 + +s = set() + +for x in lambda: 0: + s.discard(-x) + +for x in (1,) if True else (2,): + s.add(-x) + +# don't add extra parens +for x in (lambda: 0): + s.discard(-x) + +for x in ((1,) if True else (2,)): + s.add(-x) + +# don't add parens directly in function call +for x in lambda: 0: + s.discard(x) + +for x in (1,) if True else (2,): + s.add(x) diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs index 08ccf08d78e5b2..71e7c4f729094e 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_set_mutations.rs @@ -3,6 +3,7 @@ use ruff_python_ast::{Expr, Stmt, StmtFor}; use ruff_python_semantic::analyze::typing; use crate::checkers::ast::Checker; +use crate::rules::refurb::rules::helpers::IterLocation; use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; use super::helpers::parenthesize_loop_iter_if_necessary; @@ -106,7 +107,7 @@ pub(crate) fn for_loop_set_mutations(checker: &Checker, for_stmt: &StmtFor) { format!( "{}.{batch_method_name}({})", set.id, - parenthesize_loop_iter_if_necessary(for_stmt, checker), + parenthesize_loop_iter_if_necessary(for_stmt, checker, IterLocation::Call), ) } (for_target, arg) => format!( @@ -114,7 +115,7 @@ pub(crate) fn for_loop_set_mutations(checker: &Checker, for_stmt: &StmtFor) { set.id, locator.slice(arg), locator.slice(for_target), - parenthesize_loop_iter_if_necessary(for_stmt, checker), + parenthesize_loop_iter_if_necessary(for_stmt, checker, IterLocation::Comprehension), ), }; diff --git a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs index 11ec8333703d91..6abb7fa3cbc469 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs @@ -5,6 +5,7 @@ use ruff_python_semantic::{Binding, ScopeId, SemanticModel, TypingOnlyBindingsSt use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::rules::refurb::rules::helpers::IterLocation; use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; use super::helpers::parenthesize_loop_iter_if_necessary; @@ -182,7 +183,7 @@ fn for_loop_writes( format!( "{}.writelines({})", locator.slice(io_object_name), - parenthesize_loop_iter_if_necessary(for_stmt, checker), + parenthesize_loop_iter_if_necessary(for_stmt, checker, IterLocation::Call), ) } (for_target, write_arg) => { @@ -191,7 +192,7 @@ fn for_loop_writes( locator.slice(io_object_name), locator.slice(write_arg), locator.slice(for_target), - parenthesize_loop_iter_if_necessary(for_stmt, checker), + parenthesize_loop_iter_if_necessary(for_stmt, checker, IterLocation::Comprehension), ) } }; diff --git a/crates/ruff_linter/src/rules/refurb/rules/helpers.rs b/crates/ruff_linter/src/rules/refurb/rules/helpers.rs index 8f66b92c46d78d..86d98737372f70 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/helpers.rs @@ -4,16 +4,24 @@ use ruff_python_ast::{self as ast, parenthesize::parenthesized_range}; use crate::checkers::ast::Checker; -/// A helper function that extracts the `iter` from a [`ast::StmtFor`] node and, -/// if the `iter` is an unparenthesized tuple, adds parentheses: +/// A helper function that extracts the `iter` from a [`ast::StmtFor`] node and +/// adds parentheses if needed. /// -/// - `for x in z: ...` -> `"x"` -/// - `for (x, y) in z: ...` -> `"(x, y)"` -/// - `for [x, y] in z: ...` -> `"[x, y]"` -/// - `for x, y in z: ...` -> `"(x, y)"` # <-- Parentheses added only for this example +/// These cases are okay and will not be modified: +/// +/// - `for x in z: ...` -> `"z"` +/// - `for x in (y, z): ...` -> `"(y, z)"` +/// - `for x in [y, z]: ...` -> `"[y, z]"` +/// +/// While these cases require parentheses: +/// +/// - `for x in y, z: ...` -> `"(y, z)"` +/// - `for x in lambda: 0: ...` -> `"(lambda: 0)"` +/// - `for x in (1,) if True else (2,): ...` -> `"((1,) if True else (2,))"` pub(super) fn parenthesize_loop_iter_if_necessary<'a>( for_stmt: &'a ast::StmtFor, checker: &'a Checker, + location: IterLocation, ) -> Cow<'a, str> { let locator = checker.locator(); let iter = for_stmt.iter.as_ref(); @@ -35,6 +43,17 @@ pub(super) fn parenthesize_loop_iter_if_necessary<'a>( ast::Expr::Tuple(tuple) if !tuple.parenthesized => { Cow::Owned(format!("({iter_in_source})")) } + ast::Expr::Lambda(_) | ast::Expr::If(_) + if matches!(location, IterLocation::Comprehension) => + { + Cow::Owned(format!("({iter_in_source})")) + } _ => Cow::Borrowed(iter_in_source), } } + +#[derive(Copy, Clone)] +pub(super) enum IterLocation { + Call, + Comprehension, +} diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB122_FURB122.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB122_FURB122.py.snap index 693030678e3d0e..3f90c115644e44 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB122_FURB122.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB122_FURB122.py.snap @@ -307,3 +307,126 @@ FURB122.py:93:9: FURB122 [*] Use of `f.write` in a for loop 98 97 | 99 98 | 100 99 | # OK + +FURB122.py:183:9: FURB122 [*] Use of `f.write` in a for loop + | +181 | def _(): +182 | with Path("file.txt").open("w", encoding="utf-8") as f: +183 | / for l in lambda: 0: +184 | | f.write(f"[{l}]") + | |_____________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +180 180 | +181 181 | def _(): +182 182 | with Path("file.txt").open("w", encoding="utf-8") as f: +183 |- for l in lambda: 0: +184 |- f.write(f"[{l}]") + 183 |+ f.writelines(f"[{l}]" for l in (lambda: 0)) +185 184 | +186 185 | +187 186 | def _(): + +FURB122.py:189:9: FURB122 [*] Use of `f.write` in a for loop + | +187 | def _(): +188 | with Path("file.txt").open("w", encoding="utf-8") as f: +189 | / for l in (1,) if True else (2,): +190 | | f.write(f"[{l}]") + | |_____________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +186 186 | +187 187 | def _(): +188 188 | with Path("file.txt").open("w", encoding="utf-8") as f: +189 |- for l in (1,) if True else (2,): +190 |- f.write(f"[{l}]") + 189 |+ f.writelines(f"[{l}]" for l in ((1,) if True else (2,))) +191 190 | +192 191 | +193 192 | # don't need to add parentheses when making a function argument + +FURB122.py:196:9: FURB122 [*] Use of `f.write` in a for loop + | +194 | def _(): +195 | with open("file", "w") as f: +196 | / for line in lambda: 0: +197 | | f.write(line) + | |_________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +193 193 | # don't need to add parentheses when making a function argument +194 194 | def _(): +195 195 | with open("file", "w") as f: +196 |- for line in lambda: 0: +197 |- f.write(line) + 196 |+ f.writelines(lambda: 0) +198 197 | +199 198 | +200 199 | def _(): + +FURB122.py:202:9: FURB122 [*] Use of `f.write` in a for loop + | +200 | def _(): +201 | with open("file", "w") as f: +202 | / for line in (1,) if True else (2,): +203 | | f.write(line) + | |_________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +199 199 | +200 200 | def _(): +201 201 | with open("file", "w") as f: +202 |- for line in (1,) if True else (2,): +203 |- f.write(line) + 202 |+ f.writelines((1,) if True else (2,)) +204 203 | +205 204 | +206 205 | # don't add extra parentheses if they're already parenthesized + +FURB122.py:209:9: FURB122 [*] Use of `f.write` in a for loop + | +207 | def _(): +208 | with open("file", "w") as f: +209 | / for line in (lambda: 0): +210 | | f.write(f"{line}") + | |______________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +206 206 | # don't add extra parentheses if they're already parenthesized +207 207 | def _(): +208 208 | with open("file", "w") as f: +209 |- for line in (lambda: 0): +210 |- f.write(f"{line}") + 209 |+ f.writelines(f"{line}" for line in (lambda: 0)) +211 210 | +212 211 | +213 212 | def _(): + +FURB122.py:215:9: FURB122 [*] Use of `f.write` in a for loop + | +213 | def _(): +214 | with open("file", "w") as f: +215 | / for line in ((1,) if True else (2,)): +216 | | f.write(f"{line}") + | |______________________________^ FURB122 + | + = help: Replace with `f.writelines` + +ℹ Safe fix +212 212 | +213 213 | def _(): +214 214 | with open("file", "w") as f: +215 |- for line in ((1,) if True else (2,)): +216 |- f.write(f"{line}") + 215 |+ f.writelines(f"{line}" for line in ((1,) if True else (2,))) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB142_FURB142.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB142_FURB142.py.snap index 8f655fa8ee54d7..be96dc7fcd5f81 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB142_FURB142.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB142_FURB142.py.snap @@ -280,3 +280,134 @@ FURB142.py:41:1: FURB142 [*] Use of `set.add()` in a for loop 46 45 | 47 46 | 48 47 | # False negative + +FURB142.py:83:1: FURB142 [*] Use of `set.discard()` in a for loop + | +81 | s = set() +82 | +83 | / for x in lambda: 0: +84 | | s.discard(-x) + | |_________________^ FURB142 +85 | +86 | for x in (1,) if True else (2,): + | + = help: Replace with `.difference_update()` + +ℹ Safe fix +80 80 | +81 81 | s = set() +82 82 | +83 |-for x in lambda: 0: +84 |- s.discard(-x) + 83 |+s.difference_update(-x for x in (lambda: 0)) +85 84 | +86 85 | for x in (1,) if True else (2,): +87 86 | s.add(-x) + +FURB142.py:86:1: FURB142 [*] Use of `set.add()` in a for loop + | +84 | s.discard(-x) +85 | +86 | / for x in (1,) if True else (2,): +87 | | s.add(-x) + | |_____________^ FURB142 +88 | +89 | # don't add extra parens + | + = help: Replace with `.update()` + +ℹ Safe fix +83 83 | for x in lambda: 0: +84 84 | s.discard(-x) +85 85 | +86 |-for x in (1,) if True else (2,): +87 |- s.add(-x) + 86 |+s.update(-x for x in ((1,) if True else (2,))) +88 87 | +89 88 | # don't add extra parens +90 89 | for x in (lambda: 0): + +FURB142.py:90:1: FURB142 [*] Use of `set.discard()` in a for loop + | +89 | # don't add extra parens +90 | / for x in (lambda: 0): +91 | | s.discard(-x) + | |_________________^ FURB142 +92 | +93 | for x in ((1,) if True else (2,)): + | + = help: Replace with `.difference_update()` + +ℹ Safe fix +87 87 | s.add(-x) +88 88 | +89 89 | # don't add extra parens +90 |-for x in (lambda: 0): +91 |- s.discard(-x) + 90 |+s.difference_update(-x for x in (lambda: 0)) +92 91 | +93 92 | for x in ((1,) if True else (2,)): +94 93 | s.add(-x) + +FURB142.py:93:1: FURB142 [*] Use of `set.add()` in a for loop + | +91 | s.discard(-x) +92 | +93 | / for x in ((1,) if True else (2,)): +94 | | s.add(-x) + | |_____________^ FURB142 +95 | +96 | # don't add parens directly in function call + | + = help: Replace with `.update()` + +ℹ Safe fix +90 90 | for x in (lambda: 0): +91 91 | s.discard(-x) +92 92 | +93 |-for x in ((1,) if True else (2,)): +94 |- s.add(-x) + 93 |+s.update(-x for x in ((1,) if True else (2,))) +95 94 | +96 95 | # don't add parens directly in function call +97 96 | for x in lambda: 0: + +FURB142.py:97:1: FURB142 [*] Use of `set.discard()` in a for loop + | + 96 | # don't add parens directly in function call + 97 | / for x in lambda: 0: + 98 | | s.discard(x) + | |________________^ FURB142 + 99 | +100 | for x in (1,) if True else (2,): + | + = help: Replace with `.difference_update()` + +ℹ Safe fix +94 94 | s.add(-x) +95 95 | +96 96 | # don't add parens directly in function call +97 |-for x in lambda: 0: +98 |- s.discard(x) + 97 |+s.difference_update(lambda: 0) +99 98 | +100 99 | for x in (1,) if True else (2,): +101 100 | s.add(x) + +FURB142.py:100:1: FURB142 [*] Use of `set.add()` in a for loop + | + 98 | s.discard(x) + 99 | +100 | / for x in (1,) if True else (2,): +101 | | s.add(x) + | |____________^ FURB142 + | + = help: Replace with `.update()` + +ℹ Safe fix +97 97 | for x in lambda: 0: +98 98 | s.discard(x) +99 99 | +100 |-for x in (1,) if True else (2,): +101 |- s.add(x) + 100 |+s.update((1,) if True else (2,)) From caf885c20af4cca25489f7559c20f2e56ee0c3a8 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 15:38:39 -0500 Subject: [PATCH 379/487] [`ruff`] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) (#18598) Closes #18552 --- .../resources/test/fixtures/ruff/RUF037.py | 3 +++ .../unnecessary_literal_within_deque_call.rs | 11 ++++++++++- ...er__rules__ruff__tests__RUF037_RUF037.py.snap | 16 +++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py index 949f30691f4fc2..2893daecc6c49a 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py @@ -56,3 +56,6 @@ def f(): def f(): queue = deque() # Ok + +def f(): + x = 0 or(deque)([]) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs index 8ed8719a401582..7050f43b9f2f09 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs @@ -1,4 +1,5 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; @@ -107,7 +108,15 @@ fn fix_unnecessary_literal_in_deque( deque: &ast::ExprCall, maxlen: Option<&Expr>, ) -> Fix { - let deque_name = checker.locator().slice(deque.func.range()); + let deque_name = checker.locator().slice( + parenthesized_range( + deque.func.as_ref().into(), + deque.into(), + checker.comment_ranges(), + checker.source(), + ) + .unwrap_or(deque.func.range()), + ); let deque_str = match maxlen { Some(maxlen) => { let len_str = checker.locator().slice(maxlen); diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap index 288b5bded4d307..09d6f8617158ac 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- RUF037.py:6:13: RUF037 [*] Unnecessary empty iterable within a deque call | @@ -127,3 +126,18 @@ RUF037.py:30:13: RUF037 [*] Unnecessary empty iterable within a deque call 31 31 | 32 32 | 33 33 | def f(): + +RUF037.py:61:13: RUF037 [*] Unnecessary empty iterable within a deque call + | +60 | def f(): +61 | x = 0 or(deque)([]) + | ^^^^^^^^^^^ RUF037 + | + = help: Replace with `deque()` + +ℹ Safe fix +58 58 | queue = deque() # Ok +59 59 | +60 60 | def f(): +61 |- x = 0 or(deque)([]) + 61 |+ x = 0 or(deque)() From 161446a47a7f49b1da96e2547acdb1f963d9af10 Mon Sep 17 00:00:00 2001 From: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com> Date: Tue, 10 Jun 2025 02:48:59 -0400 Subject: [PATCH 380/487] [ty] Add support for global __debug__ constant (#18540) ## Summary Closes https://github.com/astral-sh/ty/issues/577. Make global `__debug__` a `bool` constant. ## Test Plan Mdtest `global-constants.md` was created to check if resolved type was `bool`. --------- Co-authored-by: David Peter --- .../resources/mdtest/scopes/global-constants.md | 14 ++++++++++++++ crates/ty_python_semantic/src/place.rs | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 crates/ty_python_semantic/resources/mdtest/scopes/global-constants.md diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/global-constants.md b/crates/ty_python_semantic/resources/mdtest/scopes/global-constants.md new file mode 100644 index 00000000000000..7163aa69808c3f --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/scopes/global-constants.md @@ -0,0 +1,14 @@ +# Global Constants + +## `__debug__` constant + +The [`__debug__` constant] should be globally available: + +```py +reveal_type(__debug__) # revealed: bool + +def foo(): + reveal_type(__debug__) # revealed: bool +``` + +[`__debug__` constant]: https://docs.python.org/3/library/constants.html#debug__ diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 0f25a3ff30abd3..253cef46e87c6f 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -1073,6 +1073,8 @@ mod implicit_globals { Place::bound(KnownClass::Str.to_instance(db)).into() } else if name == "__builtins__" { Place::bound(Type::any()).into() + } else if name == "__debug__" { + Place::bound(KnownClass::Bool.to_instance(db)).into() } // In general we wouldn't check to see whether a symbol exists on a class before doing the // `.member()` call on the instance type -- we'd just do the `.member`() call on the instance From 6051a118d16355b0c5a5d4ff763b383b7e88c064 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:27:06 -0400 Subject: [PATCH 381/487] [`flake8-pyi`] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) (#18611) ## Summary Fixes https://github.com/astral-sh/ruff/issues/18602 by: 1. Avoiding a fix when `*args` are present 2. Inserting the `Generic` base class right before the first keyword argument, if one is present In an intermediate commit, I also had special handling to avoid a fix in the `**kwargs` case, but this is treated (roughly) as a normal keyword, and I believe handling it properly falls out of the other keyword fix. I also updated the `add_argument` utility function to insert new arguments right before the keyword argument list instead of at the very end of the argument list. This changed a couple of snapshots unrelated to `PYI059`, but there shouldn't be any functional changes to other rules because all other calls to `add_argument` were adding a keyword argument anyway. ## Test Plan Existing PYI059 cases, plus new tests based on the issue --------- Co-authored-by: Alex Waygood --- .../test/fixtures/flake8_pyi/PYI059.py | 12 ++++ crates/ruff_linter/src/fix/edits.rs | 16 +++-- ...__flake8_bugbear__tests__B028_B028.py.snap | 9 ++- .../rules/generic_not_last_base_class.rs | 38 +++++++++++- ...__flake8_pyi__tests__PYI059_PYI059.py.snap | 58 +++++++++++++++++-- ..._flake8_pyi__tests__PYI059_PYI059.pyi.snap | 8 +-- ...W1510_subprocess_run_without_check.py.snap | 6 +- 7 files changed, 122 insertions(+), 25 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py index 0e4d877d4b485d..cfe42a12d38367 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py @@ -52,3 +52,15 @@ def __init__(self) -> None: class SomeGeneric(Generic[T]): # Only one generic pass + + +# syntax errors with starred and keyword arguments from +# https://github.com/astral-sh/ruff/issues/18602 +class C1(Generic[T], str, **{"metaclass": type}): # PYI059 + ... + +class C2(Generic[T], str, metaclass=type): # PYI059 + ... + +class C3(Generic[T], metaclass=type, *[str]): # PYI059 but no fix + ... diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 7d324380229850..2ed6f4c9cf7e3f 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -2,9 +2,9 @@ use anyhow::{Context, Result}; +use ruff_python_ast::AnyNodeRef; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt}; -use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::textwrap::dedent_to; @@ -257,19 +257,23 @@ pub(crate) fn remove_argument( } /// Generic function to add arguments or keyword arguments to function calls. +/// +/// The new argument will be inserted before the first existing keyword argument in `arguments`, if +/// there are any present. Otherwise, the new argument is added to the end of the argument list. pub(crate) fn add_argument( argument: &str, arguments: &Arguments, comment_ranges: &CommentRanges, source: &str, ) -> Edit { - if let Some(last) = arguments.arguments_source_order().last() { + if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() { + let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source) + .unwrap_or(*range); + Edit::insertion(format!("{argument}, "), keyword.start()) + } else if let Some(last) = arguments.arguments_source_order().last() { // Case 1: existing arguments, so append after the last argument. let last = parenthesized_range( - match last { - ArgOrKeyword::Arg(arg) => arg.into(), - ArgOrKeyword::Keyword(keyword) => (&keyword.value).into(), - }, + last.value().into(), arguments.into(), comment_ranges, source, diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap index db323efe66afbd..ddaf48098f7574 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap @@ -37,11 +37,10 @@ B028.py:9:1: B028 [*] No explicit `stacklevel` keyword argument found 7 7 | 8 8 | warnings.warn("test", DeprecationWarning) 9 |-warnings.warn("test", DeprecationWarning, source=None) -10 9 | warnings.warn("test", DeprecationWarning, source=None, stacklevel=2) - 10 |+warnings.warn("test", DeprecationWarning, source=None, stacklevel=2) + 9 |+warnings.warn("test", DeprecationWarning, stacklevel=2, source=None) +10 10 | warnings.warn("test", DeprecationWarning, source=None, stacklevel=2) 11 11 | warnings.warn("test", DeprecationWarning, stacklevel=1) 12 12 | warnings.warn("test", DeprecationWarning, 1) -13 13 | warnings.warn("test", category=DeprecationWarning, stacklevel=1) B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found | @@ -59,7 +58,7 @@ B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found 24 24 | DeprecationWarning, 25 25 | # some comments here 26 |- source = None # no trailing comma - 26 |+ source = None, stacklevel=2 # no trailing comma + 26 |+ stacklevel=2, source = None # no trailing comma 27 27 | ) 28 28 | 29 29 | # https://github.com/astral-sh/ruff/issues/18011 @@ -80,7 +79,7 @@ B028.py:32:1: B028 [*] No explicit `stacklevel` keyword argument found 30 30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) 31 31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value 32 |-warnings.warn("test", skip_file_prefixes=()) - 32 |+warnings.warn("test", skip_file_prefixes=(), stacklevel=2) + 32 |+warnings.warn("test", stacklevel=2, skip_file_prefixes=()) 33 33 | 34 34 | _my_prefixes = ("this","that") 35 35 | warnings.warn("test", skip_file_prefixes = _my_prefixes) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs index b7ab81f3d315b0..4d40bfafd94d07 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs @@ -13,8 +13,11 @@ use crate::{Fix, FixAvailability, Violation}; /// ## Why is this bad? /// If `Generic[]` is not the final class in the bases tuple, unexpected /// behaviour can occur at runtime (See [this CPython issue][1] for an example). -/// The rule is also applied to stub files, but, unlike at runtime, -/// in stubs it is purely enforced for stylistic consistency. +/// +/// The rule is also applied to stub files, where it won't cause issues at +/// runtime. This is because type checkers may not be able to infer an +/// accurate [MRO] for the class, which could lead to unexpected or +/// inaccurate results when they analyze your code. /// /// For example: /// ```python @@ -43,10 +46,23 @@ use crate::{Fix, FixAvailability, Violation}; /// ): /// ... /// ``` +/// +/// ## Fix safety +/// +/// This rule's fix is always unsafe because reordering base classes can change +/// the behavior of the code by modifying the class's MRO. The fix will also +/// delete trailing comments after the `Generic` base class in multi-line base +/// class lists, if any are present. +/// +/// ## Fix availability +/// +/// This rule's fix is only available when there are no `*args` present in the base class list. +/// /// ## References /// - [`typing.Generic` documentation](https://docs.python.org/3/library/typing.html#typing.Generic) /// /// [1]: https://github.com/python/cpython/issues/106102 +/// [MRO]: https://docs.python.org/3/glossary.html#term-method-resolution-order #[derive(ViolationMetadata)] pub(crate) struct GenericNotLastBaseClass; @@ -94,6 +110,22 @@ pub(crate) fn generic_not_last_base_class(checker: &Checker, class_def: &ast::St let mut diagnostic = checker.report_diagnostic(GenericNotLastBaseClass, bases.range()); + // Avoid suggesting a fix if any of the arguments is starred. This avoids tricky syntax errors + // in cases like + // + // ```python + // class C3(Generic[T], metaclass=type, *[str]): ... + // ``` + // + // where we would naively try to put `Generic[T]` after `*[str]`, which is also after a keyword + // argument, causing the error. + if bases + .arguments_source_order() + .any(|arg| arg.value().is_starred_expr()) + { + return; + } + // No fix if multiple `Generic[]`s are seen in the class bases. if generic_base_iter.next().is_none() { diagnostic.try_set_fix(|| generate_fix(generic_base, bases, checker)); @@ -116,5 +148,5 @@ fn generate_fix( source, ); - Ok(Fix::safe_edits(deletion, [insertion])) + Ok(Fix::unsafe_edits(deletion, [insertion])) } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap index bc91fb867cef4d..d5f7860983c0cf 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap @@ -12,7 +12,7 @@ PYI059.py:8:17: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 5 5 | K = TypeVar('K') 6 6 | V = TypeVar('V') 7 7 | @@ -37,7 +37,7 @@ PYI059.py:15:16: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 13 13 | self._items.append(item) 14 14 | 15 15 | class MyMapping( # PYI059 @@ -59,7 +59,7 @@ PYI059.py:26:10: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 23 23 | # Inheriting from just `Generic` is a TypeError, but it's probably fine 24 24 | # to flag this issue in this case as well, since after fixing the error 25 25 | # the Generic's position issue persists. @@ -88,7 +88,7 @@ PYI059.py:30:10: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 30 30 | class Foo( # comment about the bracket 31 31 | # Part 1 of multiline comment 1 32 32 | # Part 2 of multiline comment 1 @@ -111,3 +111,53 @@ PYI059.py:45:8: PYI059 `Generic[]` should always be the last base class | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 | = help: Move `Generic[]` to the end + +PYI059.py:59:9: PYI059 [*] `Generic[]` should always be the last base class + | +57 | # syntax errors with starred and keyword arguments from +58 | # https://github.com/astral-sh/ruff/issues/18602 +59 | class C1(Generic[T], str, **{"metaclass": type}): # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 +60 | ... + | + = help: Move `Generic[]` to the end + +ℹ Unsafe fix +56 56 | +57 57 | # syntax errors with starred and keyword arguments from +58 58 | # https://github.com/astral-sh/ruff/issues/18602 +59 |-class C1(Generic[T], str, **{"metaclass": type}): # PYI059 + 59 |+class C1(str, Generic[T], **{"metaclass": type}): # PYI059 +60 60 | ... +61 61 | +62 62 | class C2(Generic[T], str, metaclass=type): # PYI059 + +PYI059.py:62:9: PYI059 [*] `Generic[]` should always be the last base class + | +60 | ... +61 | +62 | class C2(Generic[T], str, metaclass=type): # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 +63 | ... + | + = help: Move `Generic[]` to the end + +ℹ Unsafe fix +59 59 | class C1(Generic[T], str, **{"metaclass": type}): # PYI059 +60 60 | ... +61 61 | +62 |-class C2(Generic[T], str, metaclass=type): # PYI059 + 62 |+class C2(str, Generic[T], metaclass=type): # PYI059 +63 63 | ... +64 64 | +65 65 | class C3(Generic[T], metaclass=type, *[str]): # PYI059 but no fix + +PYI059.py:65:9: PYI059 `Generic[]` should always be the last base class + | +63 | ... +64 | +65 | class C3(Generic[T], metaclass=type, *[str]): # PYI059 but no fix + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 +66 | ... + | + = help: Move `Generic[]` to the end diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap index 275dd20dbaa087..16c5c72d087055 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap @@ -12,7 +12,7 @@ PYI059.pyi:8:17: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 5 5 | K = TypeVar('K') 6 6 | V = TypeVar('V') 7 7 | @@ -37,7 +37,7 @@ PYI059.pyi:12:16: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 10 10 | def push(self, item: T) -> None: ... 11 11 | 12 12 | class MyMapping( # PYI059 @@ -58,7 +58,7 @@ PYI059.pyi:22:10: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 19 19 | # Inheriting from just `Generic` is a TypeError, but it's probably fine 20 20 | # to flag this issue in this case as well, since after fixing the error 21 21 | # the Generic's position issue persists. @@ -87,7 +87,7 @@ PYI059.pyi:25:10: PYI059 [*] `Generic[]` should always be the last base class | = help: Move `Generic[]` to the end -ℹ Safe fix +ℹ Unsafe fix 25 25 | class Foo( # comment about the bracket 26 26 | # Part 1 of multiline comment 1 27 27 | # Part 2 of multiline comment 1 diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1510_subprocess_run_without_check.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1510_subprocess_run_without_check.py.snap index 7e6539ec04c0b1..b0d1645a5d07fd 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1510_subprocess_run_without_check.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1510_subprocess_run_without_check.py.snap @@ -37,7 +37,7 @@ subprocess_run_without_check.py:5:1: PLW1510 [*] `subprocess.run` without explic 3 3 | # Errors. 4 4 | subprocess.run("ls") 5 |-subprocess.run("ls", shell=True) - 5 |+subprocess.run("ls", shell=True, check=False) + 5 |+subprocess.run("ls", check=False, shell=True) 6 6 | subprocess.run( 7 7 | ["ls"], 8 8 | shell=False, @@ -58,7 +58,7 @@ subprocess_run_without_check.py:6:1: PLW1510 [*] `subprocess.run` without explic 6 6 | subprocess.run( 7 7 | ["ls"], 8 |- shell=False, - 8 |+ shell=False, check=False, + 8 |+ check=False, shell=False, 9 9 | ) 10 10 | subprocess.run(["ls"], **kwargs) 11 11 | @@ -79,7 +79,7 @@ subprocess_run_without_check.py:10:1: PLW1510 [*] `subprocess.run` without expli 8 8 | shell=False, 9 9 | ) 10 |-subprocess.run(["ls"], **kwargs) - 10 |+subprocess.run(["ls"], **kwargs, check=False) + 10 |+subprocess.run(["ls"], check=False, **kwargs) 11 11 | 12 12 | # Non-errors. 13 13 | subprocess.run("ls", check=True) From 6cd0669475a9161eab05d1598fe2f40faab4ec25 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 10 Jun 2025 14:21:34 -0400 Subject: [PATCH 382/487] [`pylint`] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) (#18613) Summary -- This PR updates the docs for PLW1641 to place less emphasis on the example of inheriting a parent class's `__hash__` implementation by both reducing the length of the example and warning that it may be unsound in general, as @AlexWaygood pointed out on Notion. Test plan -- Existing tests --------- Co-authored-by: Alex Waygood --- .../src/rules/pylint/rules/eq_without_hash.rs | 56 ++++--------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs index 72e1efbb03134d..963bcf18a281bd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/eq_without_hash.rs @@ -16,10 +16,10 @@ use crate::checkers::ast::Checker; /// /// ## Why is this bad? /// A class that implements `__eq__` but not `__hash__` will have its hash -/// method implicitly set to `None`, regardless of if a super class defines -/// `__hash__`. This will cause the class to be unhashable, will in turn -/// cause issues when using the class as a key in a dictionary or a member -/// of a set. +/// method implicitly set to `None`, regardless of if a superclass defines +/// `__hash__`. This will cause the class to be unhashable, which will in turn +/// cause issues when using instances of the class as keys in a dictionary or +/// members of a set. /// /// ## Example /// @@ -46,52 +46,16 @@ use crate::checkers::ast::Checker; /// return hash(self.name) /// ``` /// -/// This issue is particularly tricky with inheritance. Even if a parent class correctly implements -/// both `__eq__` and `__hash__`, overriding `__eq__` in a child class without also implementing -/// `__hash__` will make the child class unhashable: +/// In general, it is unsound to inherit a `__hash__` implementation from a parent class while +/// overriding the `__eq__` implementation because the two must be kept in sync. However, an easy +/// way to resolve this error in cases where it _is_ sound is to explicitly set `__hash__` to the +/// parent class's implementation: /// /// ```python -/// class Person: -/// def __init__(self): -/// self.name = "monty" -/// -/// def __eq__(self, other): -/// return isinstance(other, Person) and other.name == self.name -/// -/// def __hash__(self): -/// return hash(self.name) -/// -/// /// class Developer(Person): -/// def __init__(self): -/// super().__init__() -/// self.language = "python" +/// def __init__(self): ... /// -/// def __eq__(self, other): -/// return ( -/// super().__eq__(other) -/// and isinstance(other, Developer) -/// and self.language == other.language -/// ) -/// -/// -/// hash(Developer()) # TypeError: unhashable type: 'Developer' -/// ``` -/// -/// One way to fix this is to retain the implementation of `__hash__` from the parent class: -/// -/// ```python -/// class Developer(Person): -/// def __init__(self): -/// super().__init__() -/// self.language = "python" -/// -/// def __eq__(self, other): -/// return ( -/// super().__eq__(other) -/// and isinstance(other, Developer) -/// and self.language == other.language -/// ) +/// def __eq__(self, other): ... /// /// __hash__ = Person.__hash__ /// ``` From b21ac567e15cf686733ec01d57fb57dd6beecac4 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:09:08 -0400 Subject: [PATCH 383/487] [`refurb`] Add a note about float literal handling (`FURB157`) (#18615) Summary -- Updates the rule docs to explicitly state how cases like `Decimal("0.1")` are handled (not affected) because the discussion of "float casts" referring to values like `nan` and `inf` is otherwise a bit confusing. These changes are based on suggestions from @AlexWaygood on Notion, with a slight adjustment to use 0.1 instead of 0.5 since it causes a more immediate issue in the REPL: ```pycon >>> from decimal import Decimal >>> Decimal(0.5) == Decimal("0.5") True >>> Decimal(0.1) == Decimal("0.1") False ``` Test plan -- N/a Co-authored-by: Alex Waygood --- .../src/rules/refurb/rules/verbose_decimal_constructor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs index 2b8060a0c31170..b2d3b12b9ed9e6 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/verbose_decimal_constructor.rs @@ -23,6 +23,9 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// Prefer the more concise form of argument passing for `Decimal` /// constructors, as it's more readable and idiomatic. /// +/// Note that this rule does not flag quoted float literals such as `Decimal("0.1")`, which will +/// produce a more precise `Decimal` value than the unquoted `Decimal(0.1)`. +/// /// ## Example /// ```python /// Decimal("0") From eb60bd64fd5ed23111d3672681ac73e84699878d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 10 Jun 2025 13:22:25 -0700 Subject: [PATCH 384/487] [ty] more simplification of infer_parameterized_legacy_typing_alias (#18526) Address post-land review on https://github.com/astral-sh/ruff/pull/18489 --- crates/ty_python_semantic/src/types/infer.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index ea20cace2c39e6..5c99354bb61f01 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9108,12 +9108,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> { class: KnownClass, ) -> Type<'db> { let arguments = &*subscript_node.slice; - let (args, args_number) = if let ast::Expr::Tuple(t) = arguments { - (t.iter(), t.len()) + let args = if let ast::Expr::Tuple(t) = arguments { + &*t.elts } else { - (std::slice::from_ref(arguments).iter(), 1) + std::slice::from_ref(arguments) }; - if args_number != expected_arg_count { + if args.len() != expected_arg_count { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) { let noun = if expected_arg_count == 1 { "argument" @@ -9122,14 +9122,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }; builder.into_diagnostic(format_args!( "Legacy alias `{alias}` expected exactly {expected_arg_count} {noun}, \ - got {args_number}", + got {}", + args.len() )); } } let ty = class.to_specialized_instance( self.db(), - args.into_iter() - .map(|node| self.infer_type_expression(node)), + args.iter().map(|node| self.infer_type_expression(node)), ); if arguments.is_tuple_expr() { self.store_expression_type(arguments, ty); From a2de81cb27541b7f09d57644539cea566cc0ece1 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 10 Jun 2025 13:25:08 -0700 Subject: [PATCH 385/487] [ty] implement disjointness of Callable vs SpecialForm (#18503) ## Summary Fixes https://github.com/astral-sh/ty/issues/557 ## Test Plan Stable property tests succeed with a million iterations. Added mdtests. --------- Co-authored-by: Alex Waygood --- .../type_properties/is_disjoint_from.md | 47 ++++++++++++++++ crates/ty_python_semantic/src/types.rs | 9 ++++ .../src/types/special_form.rs | 53 +++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index a39826671be6f4..20725ebd89f1f2 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -498,3 +498,50 @@ def possibly_unbound_with_invalid_type(flag: bool): static_assert(is_disjoint_from(G, Callable[..., Any])) static_assert(is_disjoint_from(Callable[..., Any], G)) ``` + +A callable type is disjoint from special form types, except for callable special forms. + +```py +from ty_extensions import is_disjoint_from, static_assert, TypeOf +from typing_extensions import Any, Callable, TypedDict +from typing import Literal, Union, Optional, Final, Type, ChainMap, Counter, OrderedDict, DefaultDict, Deque + +# Most special forms are disjoint from callable types because they are +# type constructors/annotations that are subscripted, not called. +static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Literal])) +static_assert(is_disjoint_from(TypeOf[Literal], Callable[..., Any])) + +static_assert(is_disjoint_from(Callable[[], None], TypeOf[Union])) +static_assert(is_disjoint_from(TypeOf[Union], Callable[[], None])) + +static_assert(is_disjoint_from(Callable[[int], str], TypeOf[Optional])) +static_assert(is_disjoint_from(TypeOf[Optional], Callable[[int], str])) + +static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Type])) +static_assert(is_disjoint_from(TypeOf[Type], Callable[..., Any])) + +static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Final])) +static_assert(is_disjoint_from(TypeOf[Final], Callable[..., Any])) + +static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Callable])) +static_assert(is_disjoint_from(TypeOf[Callable], Callable[..., Any])) + +# However, some special forms are callable (TypedDict and collection constructors) +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[TypedDict])) +static_assert(not is_disjoint_from(TypeOf[TypedDict], Callable[..., Any])) + +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[ChainMap])) +static_assert(not is_disjoint_from(TypeOf[ChainMap], Callable[..., Any])) + +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Counter])) +static_assert(not is_disjoint_from(TypeOf[Counter], Callable[..., Any])) + +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[DefaultDict])) +static_assert(not is_disjoint_from(TypeOf[DefaultDict], Callable[..., Any])) + +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Deque])) +static_assert(not is_disjoint_from(TypeOf[Deque], Callable[..., Any])) + +static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[OrderedDict])) +static_assert(not is_disjoint_from(TypeOf[OrderedDict], Callable[..., Any])) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9a2d1b9243e2ef..6169de067e7356 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1926,6 +1926,15 @@ impl<'db> Type<'db> { true } + (Type::Callable(_), Type::SpecialForm(special_form)) + | (Type::SpecialForm(special_form), Type::Callable(_)) => { + // A callable type is disjoint from special form types, except for special forms + // that are callable (like TypedDict and collection constructors). + // Most special forms are type constructors/annotations (like `typing.Literal`, + // `typing.Union`, etc.) that are subscripted, not called. + !special_form.is_callable() + } + ( Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), instance @ Type::NominalInstance(NominalInstanceType { class, .. }), diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index a17531cb1339fe..be8995018d8954 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -247,6 +247,59 @@ impl SpecialFormType { self.class().to_class_literal(db) } + /// Return true if this special form is callable at runtime. + /// Most special forms are not callable (they are type constructors that are subscripted), + /// but some like `TypedDict` and collection constructors can be called. + pub(super) const fn is_callable(self) -> bool { + match self { + // TypedDict can be called as a constructor to create TypedDict types + Self::TypedDict + // Collection constructors are callable + // TODO actually implement support for calling them + | Self::ChainMap + | Self::Counter + | Self::DefaultDict + | Self::Deque + | Self::OrderedDict => true, + + // All other special forms are not callable + Self::Annotated + | Self::Literal + | Self::LiteralString + | Self::Optional + | Self::Union + | Self::NoReturn + | Self::Never + | Self::Tuple + | Self::List + | Self::Dict + | Self::Set + | Self::FrozenSet + | Self::Type + | Self::Unknown + | Self::AlwaysTruthy + | Self::AlwaysFalsy + | Self::Not + | Self::Intersection + | Self::TypeOf + | Self::CallableTypeOf + | Self::Callable + | Self::TypingSelf + | Self::Final + | Self::ClassVar + | Self::Concatenate + | Self::Unpack + | Self::Required + | Self::NotRequired + | Self::TypeAlias + | Self::TypeGuard + | Self::TypeIs + | Self::ReadOnly + | Self::Protocol + | Self::Generic => false, + } + } + /// Return the repr of the symbol at runtime pub(super) const fn repr(self) -> &'static str { match self { From dc322d23dd736909d86b0436cb1adb137fa041fb Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 11 Jun 2025 08:58:05 +0300 Subject: [PATCH 386/487] [`ruff`] skip fix for `RUF059` if dummy name is already bound (unused-unpacked-variable) (#18509) --- .../resources/test/fixtures/ruff/RUF059_0.py | 6 ++++++ .../src/rules/ruff/rules/unused_unpacked_variable.rs | 6 ++++++ ...nter__rules__ruff__tests__RUF059_RUF059_0.py.snap | 12 +++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF059_0.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF059_0.py index 558060bc0f14a3..a9f4df2e9623ca 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF059_0.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF059_0.py @@ -94,3 +94,9 @@ def f(): (exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2) } return hash_map + + +# see: https://github.com/astral-sh/ruff/issues/18507 +def f(_x): + x, = "1" + print(_x) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs index 2e78152b6318b9..65cb79563c396a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_unpacked_variable.rs @@ -3,6 +3,7 @@ use ruff_python_semantic::Binding; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::renamer::ShadowedKind; use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does @@ -63,6 +64,11 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option { let name = binding.name(checker.source()); let renamed = format!("_{name}"); + + if ShadowedKind::new(binding, &renamed, checker).shadows_any() { + return None; + } + if checker.settings.dummy_variable_rgx.is_match(&renamed) { let edit = Edit::range_replacement(renamed, binding.range()); diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF059_RUF059_0.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF059_RUF059_0.py.snap index 03d9674ce00d80..0f7d1505c3c3e5 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF059_RUF059_0.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF059_RUF059_0.py.snap @@ -197,4 +197,14 @@ RUF059_0.py:86:29: RUF059 [*] Unpacked variable `that` is never used 86 |+ open("") as ((this, _that)), 87 87 | ): 88 88 | print("hello") -89 89 | +89 89 | + +RUF059_0.py:101:5: RUF059 Unpacked variable `x` is never used + | + 99 | # see: https://github.com/astral-sh/ruff/issues/18507 +100 | def f(_x): +101 | x, = "1" + | ^ RUF059 +102 | print(_x) + | + = help: Prefix it with an underscore or any other dummy variable pattern From 2213698a5d10d41784e0f1ab478d721630281a62 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Wed, 11 Jun 2025 02:58:55 -0300 Subject: [PATCH 387/487] [`pep8-naming`] Suppress fix for `N804` and `N805` if the recommend name is already used (#18472) --- .../test/fixtures/pep8_naming/N804.py | 6 +++++ .../test/fixtures/pep8_naming/N805.py | 6 +++++ .../rules/invalid_first_argument_name.rs | 14 ++++++++++- ...les__pep8_naming__tests__N804_N804.py.snap | 10 ++++++++ ...les__pep8_naming__tests__N805_N805.py.snap | 24 +++++++++---------- ...naming__tests__classmethod_decorators.snap | 24 +++++++++---------- ...aming__tests__staticmethod_decorators.snap | 24 +++++++++---------- 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N804.py b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N804.py index 3fbf2d7c70229d..3001365404d89b 100644 --- a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N804.py +++ b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N804.py @@ -81,3 +81,9 @@ def func(x): class Bar(type(foo)): def foo_method(self): pass + +# https://github.com/astral-sh/ruff/issues/18459 +class Example: + @classmethod + def function(this): + cls = 1234 diff --git a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N805.py b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N805.py index d350a39c1236f3..03f9d9746e350a 100644 --- a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N805.py +++ b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N805.py @@ -134,3 +134,9 @@ def __subclasscheck__(cls, other): ... class MyProtocolMeta(type(Protocol)): def __subclasscheck__(cls, other): ... + + +# https://github.com/astral-sh/ruff/issues/18459 +class C: + def f(this): + self = 123 diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index ecbc4eb24fa130..93f06cdea10b3d 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -11,7 +11,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::{Checker, DiagnosticGuard}; use crate::registry::Rule; -use crate::renamer::Renamer; +use crate::renamer::{Renamer, ShadowedKind}; use crate::{Fix, Violation}; /// ## What it does @@ -272,6 +272,7 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { function_type.diagnostic_kind(checker, self_or_cls.name.to_string(), self_or_cls.range()); diagnostic.try_set_optional_fix(|| { rename_parameter( + checker, scope, self_or_cls, parameters, @@ -284,6 +285,7 @@ pub(crate) fn invalid_first_argument_name(checker: &Checker, scope: &Scope) { /// Rename the first parameter to `self` or `cls`, if no other parameter has the target name. fn rename_parameter( + checker: &Checker, scope: &Scope<'_>, self_or_cls: &ast::Parameter, parameters: &ast::Parameters, @@ -299,6 +301,16 @@ fn rename_parameter( { return Ok(None); } + let binding = scope + .get(&self_or_cls.name) + .map(|binding_id| semantic.binding(binding_id)) + .unwrap(); + + // Don't provide autofix if `self` or `cls` is already defined in the scope. + if ShadowedKind::new(binding, function_type.valid_first_argument_name(), checker).shadows_any() + { + return Ok(None); + } let (edit, rest) = Renamer::rename( &self_or_cls.name, diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N804_N804.py.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N804_N804.py.snap index 140631d9ff4c26..79cd708650e39c 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N804_N804.py.snap +++ b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N804_N804.py.snap @@ -153,3 +153,13 @@ N804.py:74:20: N804 [*] First argument of a class method should be named `cls` 76 76 | 77 77 | def func(x): 78 78 | return x + +N804.py:88:18: N804 First argument of a class method should be named `cls` + | +86 | class Example: +87 | @classmethod +88 | def function(this): + | ^^^^ N804 +89 | cls = 1234 + | + = help: Rename `this` to `cls` diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N805_N805.py.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N805_N805.py.snap index 89d578bc7f1863..27e3b3262c526a 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N805_N805.py.snap +++ b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__N805_N805.py.snap @@ -247,7 +247,7 @@ N805.py:115:20: N805 [*] First argument of a method should be named `self` 119 119 | def bad_method(this): 120 120 | self = this -N805.py:119:20: N805 [*] First argument of a method should be named `self` +N805.py:119:20: N805 First argument of a method should be named `self` | 117 | this 118 | @@ -257,18 +257,6 @@ N805.py:119:20: N805 [*] First argument of a method should be named `self` | = help: Rename `this` to `self` -ℹ Unsafe fix -116 116 | this = this -117 117 | this -118 118 | -119 |- def bad_method(this): -120 |- self = this - 119 |+ def bad_method(self): - 120 |+ self = self -121 121 | -122 122 | -123 123 | class RenamingWithNFKC: - N805.py:124:17: N805 [*] First argument of a method should be named `self` | 123 | class RenamingWithNFKC: @@ -289,3 +277,13 @@ N805.py:124:17: N805 [*] First argument of a method should be named `self` 126 126 | 127 127 | 128 128 | from typing import Protocol + +N805.py:141:11: N805 First argument of a method should be named `self` + | +139 | # https://github.com/astral-sh/ruff/issues/18459 +140 | class C: +141 | def f(this): + | ^^^^ N805 +142 | self = 123 + | + = help: Rename `this` to `self` diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__classmethod_decorators.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__classmethod_decorators.snap index 6d28da75c68065..94befdac59454b 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__classmethod_decorators.snap +++ b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__classmethod_decorators.snap @@ -190,7 +190,7 @@ N805.py:115:20: N805 [*] First argument of a method should be named `self` 119 119 | def bad_method(this): 120 120 | self = this -N805.py:119:20: N805 [*] First argument of a method should be named `self` +N805.py:119:20: N805 First argument of a method should be named `self` | 117 | this 118 | @@ -200,18 +200,6 @@ N805.py:119:20: N805 [*] First argument of a method should be named `self` | = help: Rename `this` to `self` -ℹ Unsafe fix -116 116 | this = this -117 117 | this -118 118 | -119 |- def bad_method(this): -120 |- self = this - 119 |+ def bad_method(self): - 120 |+ self = self -121 121 | -122 122 | -123 123 | class RenamingWithNFKC: - N805.py:124:17: N805 [*] First argument of a method should be named `self` | 123 | class RenamingWithNFKC: @@ -232,3 +220,13 @@ N805.py:124:17: N805 [*] First argument of a method should be named `self` 126 126 | 127 127 | 128 128 | from typing import Protocol + +N805.py:141:11: N805 First argument of a method should be named `self` + | +139 | # https://github.com/astral-sh/ruff/issues/18459 +140 | class C: +141 | def f(this): + | ^^^^ N805 +142 | self = 123 + | + = help: Rename `this` to `self` diff --git a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__staticmethod_decorators.snap b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__staticmethod_decorators.snap index c1a6d0298a115b..7ba83b01fd852a 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__staticmethod_decorators.snap +++ b/crates/ruff_linter/src/rules/pep8_naming/snapshots/ruff_linter__rules__pep8_naming__tests__staticmethod_decorators.snap @@ -228,7 +228,7 @@ N805.py:115:20: N805 [*] First argument of a method should be named `self` 119 119 | def bad_method(this): 120 120 | self = this -N805.py:119:20: N805 [*] First argument of a method should be named `self` +N805.py:119:20: N805 First argument of a method should be named `self` | 117 | this 118 | @@ -238,18 +238,6 @@ N805.py:119:20: N805 [*] First argument of a method should be named `self` | = help: Rename `this` to `self` -ℹ Unsafe fix -116 116 | this = this -117 117 | this -118 118 | -119 |- def bad_method(this): -120 |- self = this - 119 |+ def bad_method(self): - 120 |+ self = self -121 121 | -122 122 | -123 123 | class RenamingWithNFKC: - N805.py:124:17: N805 [*] First argument of a method should be named `self` | 123 | class RenamingWithNFKC: @@ -270,3 +258,13 @@ N805.py:124:17: N805 [*] First argument of a method should be named `self` 126 126 | 127 127 | 128 128 | from typing import Protocol + +N805.py:141:11: N805 First argument of a method should be named `self` + | +139 | # https://github.com/astral-sh/ruff/issues/18459 +140 | class C: +141 | def f(this): + | ^^^^ N805 +142 | self = 123 + | + = help: Rename `this` to `self` From 0724bee59c12da9cedc84f4238b08aabe9ecaaa1 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Wed, 11 Jun 2025 06:19:00 +0000 Subject: [PATCH 388/487] [`pyupgrade`] Don't offer fix for `Optional[None]` in non-pep604-annotation-optional (`UP045)` or non-pep604-annotation-union (`UP007`) (#18545) --- .../resources/test/fixtures/pyupgrade/UP045.py | 5 +++++ .../rules/pyupgrade/rules/use_pep604_annotation.rs | 12 +++++++++--- ...f_linter__rules__pyupgrade__tests__UP045.py.snap | 13 ++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP045.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP045.py index 904c1510eabbe6..13be285aa01de8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP045.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP045.py @@ -42,3 +42,8 @@ class ServiceRefOrValue: # Regression test for: https://github.com/astral-sh/ruff/issues/7201 class ServiceRefOrValue: service_specification: Optional[str]is not True = None + + +# Test for: https://github.com/astral-sh/ruff/issues/18508 +# Optional[None] should not be offered a fix +foo: Optional[None] = None diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index b1b888cf4c719a..bc49d8df08de5b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -100,8 +100,8 @@ impl Violation for NonPEP604AnnotationUnion { /// ``` /// /// ## Fix safety -/// This rule's fix is marked as unsafe, as it may lead to runtime errors when -/// alongside libraries that rely on runtime type annotations, like Pydantic, +/// This rule's fix is marked as unsafe, as it may lead to runtime errors +/// using libraries that rely on runtime type annotations, like Pydantic, /// on Python versions prior to Python 3.10. It may also lead to runtime errors /// in unusual and likely incorrect type annotations where the type does not /// support the `|` operator. @@ -138,7 +138,8 @@ pub(crate) fn non_pep604_annotation( // lead to invalid syntax. let fixable = checker.semantic().in_type_definition() && !checker.semantic().in_complex_string_type_definition() - && is_allowed_value(slice); + && is_allowed_value(slice) + && !is_optional_none(operator, slice); let applicability = if checker.target_version() >= PythonVersion::PY310 { Applicability::Safe @@ -276,3 +277,8 @@ fn is_allowed_value(expr: &Expr) -> bool { | Expr::IpyEscapeCommand(_) => false, } } + +/// Return `true` if this is an `Optional[None]` annotation. +fn is_optional_none(operator: Pep604Operator, slice: &Expr) -> bool { + matches!(operator, Pep604Operator::Optional) && matches!(slice, Expr::NoneLiteral(_)) +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap index 357987139526af..9e78420b083c09 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs -snapshot_kind: text --- UP045.py:5:10: UP007 [*] Use `X | Y` for type annotations | @@ -149,3 +148,15 @@ UP045.py:44:28: UP007 [*] Use `X | Y` for type annotations 43 43 | class ServiceRefOrValue: 44 |- service_specification: Optional[str]is not True = None 44 |+ service_specification: str | None is not True = None +45 45 | +46 46 | +47 47 | # Test for: https://github.com/astral-sh/ruff/issues/18508 + +UP045.py:49:6: UP007 Use `X | Y` for type annotations + | +47 | # Test for: https://github.com/astral-sh/ruff/issues/18508 +48 | # Optional[None] should not be offered a fix +49 | foo: Optional[None] = None + | ^^^^^^^^^^^^^^ UP007 + | + = help: Convert to `X | Y` From 5dcfc9f074e5c9c2d2d88fb279ff9b9a5e6b56ce Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 11 Jun 2025 08:55:30 +0200 Subject: [PATCH 389/487] Move corpus tests to `ty_python_semantic` (#18609) --- .pre-commit-config.yaml | 1 + Cargo.lock | 2 +- crates/ty_project/Cargo.toml | 2 - .../test/corpus/04_assign_invalid_target.py | 1 - .../04_assign_named_expr_invalid_target.py | 1 - .../corpus/04_aug_assign_invalid_target.py | 1 - .../83_jupyter_notebook_ipython_magic.ipynb | 1 - .../corpus/89_type_alias_invalid_bound.py | 1 - .../corpus/98_ann_assign_invalid_target.py | 1 - crates/ty_python_semantic/Cargo.toml | 1 + .../resources}/corpus/00_const.py | 0 .../resources}/corpus/00_empty.py | 0 .../resources}/corpus/00_expr_discard.py | 0 .../resources}/corpus/00_expr_var1.py | 0 .../resources}/corpus/01_expr_unary.py | 0 .../resources}/corpus/02_expr_attr.py | 0 .../corpus/02_expr_attr_multiline.py | 0 .../corpus/02_expr_attr_multiline_assign.py | 0 .../resources}/corpus/02_expr_bin_bool.py | 0 .../resources}/corpus/02_expr_binary.py | 0 .../corpus/02_expr_bool_op_multiline.py | 0 .../corpus/02_expr_bool_op_multiline2.py | 0 .../resources}/corpus/02_expr_rel.py | 0 .../resources}/corpus/02_expr_rel_multiple.py | 0 .../resources}/corpus/02_expr_subscr.py | 0 .../resources}/corpus/03_dict.py | 0 .../resources}/corpus/03_dict_ex.py | 0 .../corpus/03_dict_literal_large.py | 0 .../resources}/corpus/03_dict_unpack_huge.py | 0 .../resources}/corpus/03_list.py | 0 .../resources}/corpus/03_list_ex.py | 0 .../resources}/corpus/03_list_large.py | 0 .../resources}/corpus/03_set.py | 0 .../resources}/corpus/03_set_multi.py | 0 .../resources}/corpus/03_slice.py | 0 .../resources}/corpus/03_slice_ext.py | 0 .../resources}/corpus/03_tuple.py | 0 .../resources}/corpus/03_tuple_ex.py | 0 .../resources}/corpus/04_assign.py | 0 .../resources}/corpus/04_assign_attr.py | 0 .../resources}/corpus/04_assign_attr_func.py | 0 .../resources}/corpus/04_assign_named_expr.py | 0 .../resources}/corpus/04_assign_subscr.py | 0 .../resources}/corpus/04_assign_unpack.py | 0 .../resources}/corpus/04_assign_unpack_ex.py | 0 .../corpus/04_assign_unpack_tuple.py | 0 .../resources}/corpus/04_aug_assign.py | 0 .../corpus/04_aug_assign_attr_multiline.py | 0 .../corpus/04_aug_assign_attr_sub.py | 0 .../resources}/corpus/05_funcall.py | 0 .../resources}/corpus/05_funcall_1.py | 0 .../resources}/corpus/05_funcall_2.py | 0 .../corpus/05_funcall_in_multiline_tuple.py | 0 .../resources}/corpus/05_funcall_kw.py | 0 .../resources}/corpus/05_funcall_kw_many.py | 0 .../resources}/corpus/05_funcall_kw_pos.py | 0 .../corpus/05_funcall_method_multiline.py | 0 .../resources}/corpus/06_funcall_kwargs.py | 0 .../resources}/corpus/06_funcall_many_args.py | 0 .../corpus/06_funcall_starargs_ex.py | 0 .../resources}/corpus/06_funcall_varargs.py | 0 .../corpus/06_funcall_varargs_kwargs.py | 0 .../corpus/06_funcall_varargs_kwargs_mixed.py | 0 .../resources}/corpus/07_ifexpr.py | 0 .../resources}/corpus/07_ifexpr_multiline.py | 0 .../resources}/corpus/07_ifexpr_multiline2.py | 0 .../resources}/corpus/08_del.py | 0 .../resources}/corpus/08_del_multi.py | 0 .../resources}/corpus/09_pass.py | 0 .../resources}/corpus/10_if.py | 0 .../corpus/10_if_chained_compare.py | 0 .../resources}/corpus/10_if_false.py | 0 .../resources}/corpus/10_if_invalid.py | 0 .../resources}/corpus/10_if_true.py | 0 .../corpus/10_if_with_named_expr.py | 0 .../resources}/corpus/11_if_else.py | 0 .../corpus/11_if_else_deeply_nested_for.py | 0 .../resources}/corpus/11_if_else_false.py | 0 .../resources}/corpus/11_if_else_true.py | 0 .../resources}/corpus/12_if_elif.py | 0 .../resources}/corpus/12_if_elif_else.py | 0 .../resources}/corpus/13_ifelse_complex1.py | 0 .../resources}/corpus/13_ifelse_many.py | 0 .../resources}/corpus/15_while.py | 0 .../resources}/corpus/15_while_break.py | 0 .../corpus/15_while_break_in_finally.py | 0 .../corpus/15_while_break_invalid_in_class.py | 0 .../corpus/15_while_break_invalid_in_func.py | 0 .../corpus/15_while_break_non_empty.py | 0 .../corpus/15_while_break_non_exit.py | 0 .../resources}/corpus/15_while_continue.py | 0 .../resources}/corpus/15_while_false.py | 0 .../resources}/corpus/15_while_infinite.py | 0 .../resources}/corpus/15_while_true.py | 0 .../resources}/corpus/16_for.py | 0 .../resources}/corpus/16_for_break.py | 0 .../corpus/16_for_break_invalid_in_class.py | 0 .../corpus/16_for_break_invalid_in_func.py | 0 .../resources}/corpus/16_for_continue.py | 0 .../resources}/corpus/16_for_else.py | 0 .../resources}/corpus/16_for_invalid.py | 0 .../resources}/corpus/16_for_list_literal.py | 0 .../resources}/corpus/16_for_nested_ifs.py | 0 .../resources}/corpus/20_lambda.py | 0 .../resources}/corpus/20_lambda_const.py | 0 .../corpus/20_lambda_default_arg.py | 0 .../resources}/corpus/20_lambda_ifelse.py | 0 .../resources}/corpus/21_func1.py | 0 .../resources}/corpus/21_func1_ret.py | 0 .../resources}/corpus/21_func_assign.py | 0 .../resources}/corpus/21_func_assign2.py | 0 .../resources}/corpus/22_func_arg.py | 0 .../resources}/corpus/22_func_vararg.py | 0 .../resources}/corpus/23_func_ret.py | 0 .../resources}/corpus/23_func_ret_val.py | 0 .../resources}/corpus/24_func_if_ret.py | 0 .../resources}/corpus/24_func_ifelse_ret.py | 0 .../resources}/corpus/24_func_ifnot_ret.py | 0 .../resources}/corpus/25_func_annotations.py | 0 .../corpus/25_func_annotations_nested.py | 0 .../corpus/25_func_annotations_same_name.py | 0 .../corpus/25_func_annotations_scope.py | 0 .../corpus/25_func_annotations_starred.py | 0 .../corpus/26_func_const_defaults.py | 0 .../corpus/26_func_defaults_same_name.py | 0 .../resources}/corpus/27_func_generic.py | 0 .../corpus/27_func_generic_bound.py | 0 .../corpus/27_func_generic_constraint.py | 0 .../corpus/27_func_generic_default.py | 0 .../corpus/27_func_generic_paramspec.py | 0 .../27_func_generic_paramspec_default.py | 0 .../corpus/27_func_generic_tuple.py | 0 .../corpus/27_func_generic_tuple_default.py | 0 .../resources}/corpus/30_func_enclosed.py | 0 .../corpus/30_func_enclosed_many.py | 0 .../resources}/corpus/31_func_global.py | 0 .../corpus/31_func_global_annotated_later.py | 0 .../resources}/corpus/31_func_nonlocal.py | 0 .../corpus/32_func_global_nested.py | 0 ..._docstring_optimizable_tuple_and_return.py | 0 .../resources}/corpus/40_import.py | 0 .../resources}/corpus/41_from_import.py | 0 .../resources}/corpus/42_import_from_dot.py | 0 .../resources}/corpus/50_yield.py | 0 .../resources}/corpus/51_gen_comp.py | 0 .../resources}/corpus/51_gen_comp2.py | 0 .../resources}/corpus/52_gen_comp_if.py | 0 .../resources}/corpus/53_dict_comp.py | 0 .../resources}/corpus/53_list_comp.py | 0 .../resources}/corpus/53_list_comp_method.py | 0 .../resources}/corpus/53_set_comp.py | 0 .../resources}/corpus/54_list_comp_func.py | 0 .../resources}/corpus/54_list_comp_lambda.py | 0 .../corpus/54_list_comp_lambda_listcomp.py | 0 .../corpus/54_list_comp_recur_func.py | 0 .../resources}/corpus/55_list_comp_nested.py | 0 .../resources}/corpus/56_yield_from.py | 0 .../resources}/corpus/57_await.py | 0 .../resources}/corpus/58_async_for.py | 0 .../resources}/corpus/58_async_for_break.py | 0 .../corpus/58_async_for_continue.py | 0 .../corpus/58_async_for_dict_comp.py | 0 .../resources}/corpus/58_async_for_else.py | 0 .../corpus/58_async_for_gen_comp.py | 0 .../corpus/58_async_for_list_comp.py | 0 .../corpus/58_async_for_set_comp.py | 0 .../resources}/corpus/59_async_with.py | 0 .../corpus/59_async_with_nested_with.py | 0 .../resources}/corpus/60_try_except.py | 0 .../resources}/corpus/60_try_except2.py | 0 .../resources}/corpus/60_try_except_bare.py | 0 .../resources}/corpus/60_try_finally.py | 0 .../corpus/60_try_finally_codeobj.py | 0 .../resources}/corpus/60_try_finally_cond.py | 0 .../resources}/corpus/60_try_finally_for.py | 0 .../resources}/corpus/60_try_finally_ret.py | 0 .../corpus/61_try_except_finally.py | 0 .../resources}/corpus/62_try_except_as.py | 0 .../resources}/corpus/62_try_except_break.py | 0 .../resources}/corpus/62_try_except_cond.py | 0 ...try_except_double_nested_inside_if_else.py | 0 .../resources}/corpus/62_try_except_return.py | 0 .../resources}/corpus/63_raise.py | 0 .../resources}/corpus/63_raise_func.py | 0 .../resources}/corpus/63_raise_x.py | 0 .../resources}/corpus/63_raise_x_from_y.py | 0 .../resources}/corpus/64_assert.py | 0 .../resources}/corpus/67_with.py | 0 .../resources}/corpus/67_with_as.py | 0 .../resources}/corpus/67_with_as_func.py | 0 .../resources}/corpus/67_with_cond_return.py | 0 ...side_try_finally_multiple_terminal_elif.py | 0 ...e_try_finally_preceding_terminal_except.py | 0 .../resources}/corpus/67_with_multi_exit.py | 0 .../corpus/67_with_non_name_target.py | 0 .../resources}/corpus/67_with_return.py | 0 .../resources}/corpus/68_with2.py | 0 .../corpus/69_for_try_except_continue1.py | 0 .../corpus/69_for_try_except_continue2.py | 0 .../corpus/69_for_try_except_continue3.py | 0 .../resources}/corpus/70_class.py | 0 .../resources}/corpus/70_class_base.py | 0 .../resources}/corpus/70_class_doc_str.py | 0 .../resources}/corpus/71_class_meth.py | 0 .../resources}/corpus/71_class_var.py | 0 .../resources}/corpus/72_class_mix.py | 0 .../resources}/corpus/73_class_generic.py | 0 .../corpus/73_class_generic_bounds.py | 0 .../corpus/73_class_generic_constraints.py | 0 .../corpus/73_class_generic_defaults.py | 0 .../corpus/73_class_generic_paramspec.py | 0 .../73_class_generic_paramspec_default.py | 0 .../corpus/73_class_generic_tuple.py | 0 .../corpus/73_class_generic_tuple_default.py | 0 .../resources}/corpus/74_class_kwargs.py | 0 .../resources}/corpus/74_class_kwargs_2.py | 0 .../resources}/corpus/74_class_super.py | 0 .../corpus/74_class_super_nested.py | 0 .../resources}/corpus/74_just_super.py | 0 .../resources}/corpus/75_classderef.py | 0 .../resources}/corpus/75_classderef_no.py | 0 .../resources}/corpus/76_class_nonlocal1.py | 0 .../resources}/corpus/76_class_nonlocal2.py | 0 .../resources}/corpus/76_class_nonlocal3.py | 0 .../resources}/corpus/76_class_nonlocal4.py | 0 .../resources}/corpus/76_class_nonlocal5.py | 0 .../resources}/corpus/77_class__class__.py | 0 .../corpus/77_class__class__nested.py | 0 .../corpus/77_class__class__no_class.py | 0 .../corpus/77_class__class__nonlocals.py | 0 .../corpus/77_class__class__nonlocals_2.py | 0 .../corpus/77_class__class__param.py | 0 .../corpus/77_class__class__param_lambda.py | 0 .../resources}/corpus/78_class_body_cond.py | 0 .../resources}/corpus/78_class_dec.py | 0 .../resources}/corpus/78_class_dec_member.py | 0 .../corpus/78_class_dec_member_func.py | 0 .../resources}/corpus/79_metaclass.py | 0 .../resources}/corpus/80_func_kwonlyargs1.py | 0 .../resources}/corpus/80_func_kwonlyargs2.py | 0 .../resources}/corpus/80_func_kwonlyargs3.py | 0 .../corpus/81_func_kwonlyargs_defaults.py | 0 .../83_jupyter_notebook_ipython_magic.ipynb | 1 + .../resources}/corpus/85_match.py | 0 .../resources}/corpus/85_match_as.py | 0 .../resources}/corpus/85_match_attr.py | 0 .../resources}/corpus/85_match_class.py | 0 .../resources}/corpus/85_match_default.py | 0 .../resources}/corpus/85_match_guard.py | 0 .../corpus/85_match_guard_with_named_expr.py | 0 .../resources}/corpus/85_match_in_func.py | 0 .../corpus/85_match_in_func_with_rest.py | 0 .../corpus/85_match_in_func_with_star.py | 0 .../resources}/corpus/85_match_invalid.py | 0 .../resources}/corpus/85_match_mapping.py | 0 .../corpus/85_match_mapping_subpattern.py | 0 .../resources}/corpus/85_match_or.py | 0 .../resources}/corpus/85_match_sequence.py | 0 .../corpus/85_match_sequence_wildcard.py | 0 .../resources}/corpus/85_match_singleton.py | 0 ...ion_generic_method_with_nested_function.py | 0 .../corpus/88_regression_issue_17792.py | 0 .../88_regression_tuple_type_short_circuit.py | 0 .../resources}/corpus/89_type_alias.py | 0 .../resources}/corpus/90_docstring_class.py | 0 .../resources}/corpus/90_docstring_func.py | 0 .../resources}/corpus/90_docstring_mod.py | 0 .../resources}/corpus/91_line_numbers1.py | 0 .../resources}/corpus/91_line_numbers2.py | 0 .../corpus/91_line_numbers2_comp.py | 0 .../resources}/corpus/91_line_numbers3.py | 0 .../resources}/corpus/91_line_numbers4.py | 0 .../resources}/corpus/91_line_numbers_dict.py | 0 .../corpus/91_line_numbers_dict_comp.py | 0 .../corpus/92_qual_class_in_class.py | 0 .../corpus/92_qual_class_in_func.py | 0 .../resources}/corpus/93_deadcode.py | 0 .../resources}/corpus/94_strformat.py | 0 .../resources}/corpus/94_strformat_complex.py | 0 .../resources}/corpus/94_strformat_conv.py | 0 .../corpus/94_strformat_conversion.py | 0 .../resources}/corpus/94_strformat_spec.py | 0 .../95_annotation_assign_subscript_no_rhs.py | 0 .../corpus/95_annotation_assign_tuple.py | 0 .../resources}/corpus/95_annotation_class.py | 0 .../corpus/95_annotation_class_multiline.py | 0 .../corpus/95_annotation_class_no_value.py | 0 .../corpus/95_annotation_fstring_invalid.py | 0 .../resources}/corpus/95_annotation_func.py | 0 .../corpus/95_annotation_func_future.py | 0 .../resources}/corpus/95_annotation_global.py | 0 .../corpus/95_annotation_global_simple.py | 0 .../corpus/95_annotation_local_attr.py | 0 .../resources}/corpus/95_annotation_module.py | 0 .../corpus/95_annotation_string_tuple.py | 0 .../resources}/corpus/95_annotation_union.py | 0 .../resources}/corpus/96_debug.py | 0 .../corpus/97_global_nonlocal_store.py | 0 ...nn_assign_annotation_future_annotations.py | 0 .../98_ann_assign_annotation_wrong_future.py | 0 .../corpus/98_ann_assign_simple_annotation.py | 0 .../corpus/99_empty_jump_target_insts.py | 0 .../corpus/callable_with_concatenate.py | 0 .../corpus/cycle_narrowing_constraints.py | 0 .../cycle_negative_narrowing_constraints.py | 0 .../except_handler_with_Any_bound_typevar.py | 0 .../resources}/corpus/literal_slices.py | 0 .../self_referential_function_annotation.py | 0 ...xprs_not_found_in_evaluate_expr_compare.py | 0 .../resources}/corpus/ty_extensions.py | 0 .../tests/corpus.rs} | 136 +++++++++++++++--- 311 files changed, 119 insertions(+), 30 deletions(-) delete mode 120000 crates/ty_project/resources/test/corpus/04_assign_invalid_target.py delete mode 120000 crates/ty_project/resources/test/corpus/04_assign_named_expr_invalid_target.py delete mode 120000 crates/ty_project/resources/test/corpus/04_aug_assign_invalid_target.py delete mode 120000 crates/ty_project/resources/test/corpus/83_jupyter_notebook_ipython_magic.ipynb delete mode 120000 crates/ty_project/resources/test/corpus/89_type_alias_invalid_bound.py delete mode 120000 crates/ty_project/resources/test/corpus/98_ann_assign_invalid_target.py rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/00_const.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/00_empty.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/00_expr_discard.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/00_expr_var1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/01_expr_unary.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_attr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_attr_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_attr_multiline_assign.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_bin_bool.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_binary.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_bool_op_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_bool_op_multiline2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_rel.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_rel_multiple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/02_expr_subscr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_dict.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_dict_ex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_dict_literal_large.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_dict_unpack_huge.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_list.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_list_ex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_list_large.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_set.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_set_multi.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_slice.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_slice_ext.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/03_tuple_ex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_attr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_attr_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_named_expr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_subscr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_unpack.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_unpack_ex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_assign_unpack_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_aug_assign.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_aug_assign_attr_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/04_aug_assign_attr_sub.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_in_multiline_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_kw.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_kw_many.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_kw_pos.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/05_funcall_method_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_kwargs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_many_args.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_starargs_ex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_varargs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_varargs_kwargs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/06_funcall_varargs_kwargs_mixed.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/07_ifexpr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/07_ifexpr_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/07_ifexpr_multiline2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/08_del.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/08_del_multi.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/09_pass.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if_chained_compare.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if_false.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if_invalid.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if_true.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/10_if_with_named_expr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/11_if_else.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/11_if_else_deeply_nested_for.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/11_if_else_false.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/11_if_else_true.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/12_if_elif.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/12_if_elif_else.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/13_ifelse_complex1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/13_ifelse_many.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break_in_finally.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break_invalid_in_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break_invalid_in_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break_non_empty.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_break_non_exit.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_continue.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_false.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_infinite.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/15_while_true.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_break.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_break_invalid_in_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_break_invalid_in_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_continue.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_else.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_invalid.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_list_literal.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/16_for_nested_ifs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/20_lambda.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/20_lambda_const.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/20_lambda_default_arg.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/20_lambda_ifelse.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/21_func1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/21_func1_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/21_func_assign.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/21_func_assign2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/22_func_arg.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/22_func_vararg.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/23_func_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/23_func_ret_val.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/24_func_if_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/24_func_ifelse_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/24_func_ifnot_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/25_func_annotations.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/25_func_annotations_nested.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/25_func_annotations_same_name.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/25_func_annotations_scope.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/25_func_annotations_starred.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/26_func_const_defaults.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/26_func_defaults_same_name.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_bound.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_constraint.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_paramspec.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_paramspec_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/27_func_generic_tuple_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/30_func_enclosed.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/30_func_enclosed_many.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/31_func_global.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/31_func_global_annotated_later.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/31_func_nonlocal.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/32_func_global_nested.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/33_func_with_docstring_optimizable_tuple_and_return.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/40_import.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/41_from_import.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/42_import_from_dot.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/50_yield.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/51_gen_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/51_gen_comp2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/52_gen_comp_if.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/53_dict_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/53_list_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/53_list_comp_method.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/53_set_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/54_list_comp_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/54_list_comp_lambda.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/54_list_comp_lambda_listcomp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/54_list_comp_recur_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/55_list_comp_nested.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/56_yield_from.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/57_await.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_break.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_continue.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_dict_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_else.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_gen_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_list_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/58_async_for_set_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/59_async_with.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/59_async_with_nested_with.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_except.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_except2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_except_bare.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_finally.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_finally_codeobj.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_finally_cond.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_finally_for.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/60_try_finally_ret.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/61_try_except_finally.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/62_try_except_as.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/62_try_except_break.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/62_try_except_cond.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/62_try_except_double_nested_inside_if_else.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/62_try_except_return.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/63_raise.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/63_raise_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/63_raise_x.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/63_raise_x_from_y.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/64_assert.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_as.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_as_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_cond_return.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_inside_try_finally_multiple_terminal_elif.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_inside_try_finally_preceding_terminal_except.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_multi_exit.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_non_name_target.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/67_with_return.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/68_with2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/69_for_try_except_continue1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/69_for_try_except_continue2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/69_for_try_except_continue3.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/70_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/70_class_base.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/70_class_doc_str.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/71_class_meth.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/71_class_var.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/72_class_mix.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_bounds.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_constraints.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_defaults.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_paramspec.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_paramspec_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/73_class_generic_tuple_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/74_class_kwargs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/74_class_kwargs_2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/74_class_super.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/74_class_super_nested.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/74_just_super.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/75_classderef.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/75_classderef_no.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/76_class_nonlocal1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/76_class_nonlocal2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/76_class_nonlocal3.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/76_class_nonlocal4.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/76_class_nonlocal5.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__nested.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__no_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__nonlocals.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__nonlocals_2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__param.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/77_class__class__param_lambda.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/78_class_body_cond.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/78_class_dec.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/78_class_dec_member.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/78_class_dec_member_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/79_metaclass.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/80_func_kwonlyargs1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/80_func_kwonlyargs2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/80_func_kwonlyargs3.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/81_func_kwonlyargs_defaults.py (100%) create mode 120000 crates/ty_python_semantic/resources/corpus/83_jupyter_notebook_ipython_magic.ipynb rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_as.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_attr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_default.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_guard.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_guard_with_named_expr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_in_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_in_func_with_rest.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_in_func_with_star.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_invalid.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_mapping.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_mapping_subpattern.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_or.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_sequence.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_sequence_wildcard.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/85_match_singleton.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/88_regression_generic_method_with_nested_function.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/88_regression_issue_17792.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/88_regression_tuple_type_short_circuit.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/89_type_alias.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/90_docstring_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/90_docstring_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/90_docstring_mod.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers1.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers2.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers2_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers3.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers4.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers_dict.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/91_line_numbers_dict_comp.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/92_qual_class_in_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/92_qual_class_in_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/93_deadcode.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/94_strformat.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/94_strformat_complex.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/94_strformat_conv.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/94_strformat_conversion.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/94_strformat_spec.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_assign_subscript_no_rhs.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_assign_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_class.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_class_multiline.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_class_no_value.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_fstring_invalid.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_func.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_func_future.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_global.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_global_simple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_local_attr.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_module.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_string_tuple.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/95_annotation_union.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/96_debug.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/97_global_nonlocal_store.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/98_ann_assign_annotation_future_annotations.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/98_ann_assign_annotation_wrong_future.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/98_ann_assign_simple_annotation.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/99_empty_jump_target_insts.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/callable_with_concatenate.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/cycle_narrowing_constraints.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/cycle_negative_narrowing_constraints.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/except_handler_with_Any_bound_typevar.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/literal_slices.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/self_referential_function_annotation.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py (100%) rename crates/{ty_project/resources/test => ty_python_semantic/resources}/corpus/ty_extensions.py (100%) rename crates/{ty_project/tests/check.rs => ty_python_semantic/tests/corpus.rs} (73%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 69a024e4a364d1..8649ac613eca86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ exclude: | .github/workflows/release.yml| crates/ty_vendored/vendor/.*| crates/ty_project/resources/.*| + crates/ty_python_semantic/resources/corpus/.*| crates/ty/docs/(configuration|rules|cli).md| crates/ruff_benchmark/resources/.*| crates/ruff_linter/resources/.*| diff --git a/Cargo.lock b/Cargo.lock index 1131908222e3c7..16ac7635a1cfdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3934,7 +3934,6 @@ version = "0.0.0" dependencies = [ "anyhow", "crossbeam", - "glob", "insta", "notify", "pep440_rs", @@ -3970,6 +3969,7 @@ dependencies = [ "countme", "dir-test", "drop_bomb", + "glob", "hashbrown 0.15.4", "indexmap", "insta", diff --git a/crates/ty_project/Cargo.toml b/crates/ty_project/Cargo.toml index c6e30a6abc2250..e8df92d5662797 100644 --- a/crates/ty_project/Cargo.toml +++ b/crates/ty_project/Cargo.toml @@ -25,7 +25,6 @@ ty_vendored = { workspace = true } anyhow = { workspace = true } crossbeam = { workspace = true } -glob = { workspace = true } notify = { workspace = true } pep440_rs = { workspace = true, features = ["version-ranges"] } rayon = { workspace = true } @@ -39,7 +38,6 @@ tracing = { workspace = true } [dev-dependencies] ruff_db = { workspace = true, features = ["testing"] } -glob = { workspace = true } insta = { workspace = true, features = ["redactions", "ron"] } [features] diff --git a/crates/ty_project/resources/test/corpus/04_assign_invalid_target.py b/crates/ty_project/resources/test/corpus/04_assign_invalid_target.py deleted file mode 120000 index 8d551b9f78d099..00000000000000 --- a/crates/ty_project/resources/test/corpus/04_assign_invalid_target.py +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_python_parser/resources/invalid/statements/invalid_assignment_targets.py \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/04_assign_named_expr_invalid_target.py b/crates/ty_project/resources/test/corpus/04_assign_named_expr_invalid_target.py deleted file mode 120000 index c751f9658700a2..00000000000000 --- a/crates/ty_project/resources/test/corpus/04_assign_named_expr_invalid_target.py +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_python_parser/resources/invalid/expressions/named/invalid_target.py \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/04_aug_assign_invalid_target.py b/crates/ty_project/resources/test/corpus/04_aug_assign_invalid_target.py deleted file mode 120000 index f3ebd5de4abc3b..00000000000000 --- a/crates/ty_project/resources/test/corpus/04_aug_assign_invalid_target.py +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_python_parser/resources/invalid/statements/invalid_augmented_assignment_target.py \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/83_jupyter_notebook_ipython_magic.ipynb b/crates/ty_project/resources/test/corpus/83_jupyter_notebook_ipython_magic.ipynb deleted file mode 120000 index 82df885aaa183a..00000000000000 --- a/crates/ty_project/resources/test/corpus/83_jupyter_notebook_ipython_magic.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_notebook/resources/test/fixtures/jupyter/unused_variable.ipynb \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/89_type_alias_invalid_bound.py b/crates/ty_project/resources/test/corpus/89_type_alias_invalid_bound.py deleted file mode 120000 index 5dfcadebb0d174..00000000000000 --- a/crates/ty_project/resources/test/corpus/89_type_alias_invalid_bound.py +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/98_ann_assign_invalid_target.py b/crates/ty_project/resources/test/corpus/98_ann_assign_invalid_target.py deleted file mode 120000 index ebc265cfa2500c..00000000000000 --- a/crates/ty_project/resources/test/corpus/98_ann_assign_invalid_target.py +++ /dev/null @@ -1 +0,0 @@ -../../../../ruff_python_parser/resources/inline/err/ann_assign_stmt_invalid_target.py \ No newline at end of file diff --git a/crates/ty_python_semantic/Cargo.toml b/crates/ty_python_semantic/Cargo.toml index 542d31a8a08dcf..1bbdf10a4926e7 100644 --- a/crates/ty_python_semantic/Cargo.toml +++ b/crates/ty_python_semantic/Cargo.toml @@ -55,6 +55,7 @@ ty_vendored = { workspace = true } anyhow = { workspace = true } dir-test = { workspace = true } +glob = { workspace = true } insta = { workspace = true } tempfile = { workspace = true } quickcheck = { version = "1.0.3", default-features = false } diff --git a/crates/ty_project/resources/test/corpus/00_const.py b/crates/ty_python_semantic/resources/corpus/00_const.py similarity index 100% rename from crates/ty_project/resources/test/corpus/00_const.py rename to crates/ty_python_semantic/resources/corpus/00_const.py diff --git a/crates/ty_project/resources/test/corpus/00_empty.py b/crates/ty_python_semantic/resources/corpus/00_empty.py similarity index 100% rename from crates/ty_project/resources/test/corpus/00_empty.py rename to crates/ty_python_semantic/resources/corpus/00_empty.py diff --git a/crates/ty_project/resources/test/corpus/00_expr_discard.py b/crates/ty_python_semantic/resources/corpus/00_expr_discard.py similarity index 100% rename from crates/ty_project/resources/test/corpus/00_expr_discard.py rename to crates/ty_python_semantic/resources/corpus/00_expr_discard.py diff --git a/crates/ty_project/resources/test/corpus/00_expr_var1.py b/crates/ty_python_semantic/resources/corpus/00_expr_var1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/00_expr_var1.py rename to crates/ty_python_semantic/resources/corpus/00_expr_var1.py diff --git a/crates/ty_project/resources/test/corpus/01_expr_unary.py b/crates/ty_python_semantic/resources/corpus/01_expr_unary.py similarity index 100% rename from crates/ty_project/resources/test/corpus/01_expr_unary.py rename to crates/ty_python_semantic/resources/corpus/01_expr_unary.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_attr.py b/crates/ty_python_semantic/resources/corpus/02_expr_attr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_attr.py rename to crates/ty_python_semantic/resources/corpus/02_expr_attr.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_attr_multiline.py b/crates/ty_python_semantic/resources/corpus/02_expr_attr_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_attr_multiline.py rename to crates/ty_python_semantic/resources/corpus/02_expr_attr_multiline.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_attr_multiline_assign.py b/crates/ty_python_semantic/resources/corpus/02_expr_attr_multiline_assign.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_attr_multiline_assign.py rename to crates/ty_python_semantic/resources/corpus/02_expr_attr_multiline_assign.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_bin_bool.py b/crates/ty_python_semantic/resources/corpus/02_expr_bin_bool.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_bin_bool.py rename to crates/ty_python_semantic/resources/corpus/02_expr_bin_bool.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_binary.py b/crates/ty_python_semantic/resources/corpus/02_expr_binary.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_binary.py rename to crates/ty_python_semantic/resources/corpus/02_expr_binary.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_bool_op_multiline.py b/crates/ty_python_semantic/resources/corpus/02_expr_bool_op_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_bool_op_multiline.py rename to crates/ty_python_semantic/resources/corpus/02_expr_bool_op_multiline.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_bool_op_multiline2.py b/crates/ty_python_semantic/resources/corpus/02_expr_bool_op_multiline2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_bool_op_multiline2.py rename to crates/ty_python_semantic/resources/corpus/02_expr_bool_op_multiline2.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_rel.py b/crates/ty_python_semantic/resources/corpus/02_expr_rel.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_rel.py rename to crates/ty_python_semantic/resources/corpus/02_expr_rel.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_rel_multiple.py b/crates/ty_python_semantic/resources/corpus/02_expr_rel_multiple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_rel_multiple.py rename to crates/ty_python_semantic/resources/corpus/02_expr_rel_multiple.py diff --git a/crates/ty_project/resources/test/corpus/02_expr_subscr.py b/crates/ty_python_semantic/resources/corpus/02_expr_subscr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/02_expr_subscr.py rename to crates/ty_python_semantic/resources/corpus/02_expr_subscr.py diff --git a/crates/ty_project/resources/test/corpus/03_dict.py b/crates/ty_python_semantic/resources/corpus/03_dict.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_dict.py rename to crates/ty_python_semantic/resources/corpus/03_dict.py diff --git a/crates/ty_project/resources/test/corpus/03_dict_ex.py b/crates/ty_python_semantic/resources/corpus/03_dict_ex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_dict_ex.py rename to crates/ty_python_semantic/resources/corpus/03_dict_ex.py diff --git a/crates/ty_project/resources/test/corpus/03_dict_literal_large.py b/crates/ty_python_semantic/resources/corpus/03_dict_literal_large.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_dict_literal_large.py rename to crates/ty_python_semantic/resources/corpus/03_dict_literal_large.py diff --git a/crates/ty_project/resources/test/corpus/03_dict_unpack_huge.py b/crates/ty_python_semantic/resources/corpus/03_dict_unpack_huge.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_dict_unpack_huge.py rename to crates/ty_python_semantic/resources/corpus/03_dict_unpack_huge.py diff --git a/crates/ty_project/resources/test/corpus/03_list.py b/crates/ty_python_semantic/resources/corpus/03_list.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_list.py rename to crates/ty_python_semantic/resources/corpus/03_list.py diff --git a/crates/ty_project/resources/test/corpus/03_list_ex.py b/crates/ty_python_semantic/resources/corpus/03_list_ex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_list_ex.py rename to crates/ty_python_semantic/resources/corpus/03_list_ex.py diff --git a/crates/ty_project/resources/test/corpus/03_list_large.py b/crates/ty_python_semantic/resources/corpus/03_list_large.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_list_large.py rename to crates/ty_python_semantic/resources/corpus/03_list_large.py diff --git a/crates/ty_project/resources/test/corpus/03_set.py b/crates/ty_python_semantic/resources/corpus/03_set.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_set.py rename to crates/ty_python_semantic/resources/corpus/03_set.py diff --git a/crates/ty_project/resources/test/corpus/03_set_multi.py b/crates/ty_python_semantic/resources/corpus/03_set_multi.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_set_multi.py rename to crates/ty_python_semantic/resources/corpus/03_set_multi.py diff --git a/crates/ty_project/resources/test/corpus/03_slice.py b/crates/ty_python_semantic/resources/corpus/03_slice.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_slice.py rename to crates/ty_python_semantic/resources/corpus/03_slice.py diff --git a/crates/ty_project/resources/test/corpus/03_slice_ext.py b/crates/ty_python_semantic/resources/corpus/03_slice_ext.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_slice_ext.py rename to crates/ty_python_semantic/resources/corpus/03_slice_ext.py diff --git a/crates/ty_project/resources/test/corpus/03_tuple.py b/crates/ty_python_semantic/resources/corpus/03_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_tuple.py rename to crates/ty_python_semantic/resources/corpus/03_tuple.py diff --git a/crates/ty_project/resources/test/corpus/03_tuple_ex.py b/crates/ty_python_semantic/resources/corpus/03_tuple_ex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/03_tuple_ex.py rename to crates/ty_python_semantic/resources/corpus/03_tuple_ex.py diff --git a/crates/ty_project/resources/test/corpus/04_assign.py b/crates/ty_python_semantic/resources/corpus/04_assign.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign.py rename to crates/ty_python_semantic/resources/corpus/04_assign.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_attr.py b/crates/ty_python_semantic/resources/corpus/04_assign_attr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_attr.py rename to crates/ty_python_semantic/resources/corpus/04_assign_attr.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_attr_func.py b/crates/ty_python_semantic/resources/corpus/04_assign_attr_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_attr_func.py rename to crates/ty_python_semantic/resources/corpus/04_assign_attr_func.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_named_expr.py b/crates/ty_python_semantic/resources/corpus/04_assign_named_expr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_named_expr.py rename to crates/ty_python_semantic/resources/corpus/04_assign_named_expr.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_subscr.py b/crates/ty_python_semantic/resources/corpus/04_assign_subscr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_subscr.py rename to crates/ty_python_semantic/resources/corpus/04_assign_subscr.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_unpack.py b/crates/ty_python_semantic/resources/corpus/04_assign_unpack.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_unpack.py rename to crates/ty_python_semantic/resources/corpus/04_assign_unpack.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_unpack_ex.py b/crates/ty_python_semantic/resources/corpus/04_assign_unpack_ex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_unpack_ex.py rename to crates/ty_python_semantic/resources/corpus/04_assign_unpack_ex.py diff --git a/crates/ty_project/resources/test/corpus/04_assign_unpack_tuple.py b/crates/ty_python_semantic/resources/corpus/04_assign_unpack_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_assign_unpack_tuple.py rename to crates/ty_python_semantic/resources/corpus/04_assign_unpack_tuple.py diff --git a/crates/ty_project/resources/test/corpus/04_aug_assign.py b/crates/ty_python_semantic/resources/corpus/04_aug_assign.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_aug_assign.py rename to crates/ty_python_semantic/resources/corpus/04_aug_assign.py diff --git a/crates/ty_project/resources/test/corpus/04_aug_assign_attr_multiline.py b/crates/ty_python_semantic/resources/corpus/04_aug_assign_attr_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_aug_assign_attr_multiline.py rename to crates/ty_python_semantic/resources/corpus/04_aug_assign_attr_multiline.py diff --git a/crates/ty_project/resources/test/corpus/04_aug_assign_attr_sub.py b/crates/ty_python_semantic/resources/corpus/04_aug_assign_attr_sub.py similarity index 100% rename from crates/ty_project/resources/test/corpus/04_aug_assign_attr_sub.py rename to crates/ty_python_semantic/resources/corpus/04_aug_assign_attr_sub.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall.py b/crates/ty_python_semantic/resources/corpus/05_funcall.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall.py rename to crates/ty_python_semantic/resources/corpus/05_funcall.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_1.py b/crates/ty_python_semantic/resources/corpus/05_funcall_1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_1.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_1.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_2.py b/crates/ty_python_semantic/resources/corpus/05_funcall_2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_2.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_2.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_in_multiline_tuple.py b/crates/ty_python_semantic/resources/corpus/05_funcall_in_multiline_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_in_multiline_tuple.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_in_multiline_tuple.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_kw.py b/crates/ty_python_semantic/resources/corpus/05_funcall_kw.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_kw.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_kw.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_kw_many.py b/crates/ty_python_semantic/resources/corpus/05_funcall_kw_many.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_kw_many.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_kw_many.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_kw_pos.py b/crates/ty_python_semantic/resources/corpus/05_funcall_kw_pos.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_kw_pos.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_kw_pos.py diff --git a/crates/ty_project/resources/test/corpus/05_funcall_method_multiline.py b/crates/ty_python_semantic/resources/corpus/05_funcall_method_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/05_funcall_method_multiline.py rename to crates/ty_python_semantic/resources/corpus/05_funcall_method_multiline.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_kwargs.py b/crates/ty_python_semantic/resources/corpus/06_funcall_kwargs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_kwargs.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_kwargs.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_many_args.py b/crates/ty_python_semantic/resources/corpus/06_funcall_many_args.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_many_args.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_many_args.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_starargs_ex.py b/crates/ty_python_semantic/resources/corpus/06_funcall_starargs_ex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_starargs_ex.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_starargs_ex.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_varargs.py b/crates/ty_python_semantic/resources/corpus/06_funcall_varargs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_varargs.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_varargs.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_varargs_kwargs.py b/crates/ty_python_semantic/resources/corpus/06_funcall_varargs_kwargs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_varargs_kwargs.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_varargs_kwargs.py diff --git a/crates/ty_project/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py b/crates/ty_python_semantic/resources/corpus/06_funcall_varargs_kwargs_mixed.py similarity index 100% rename from crates/ty_project/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py rename to crates/ty_python_semantic/resources/corpus/06_funcall_varargs_kwargs_mixed.py diff --git a/crates/ty_project/resources/test/corpus/07_ifexpr.py b/crates/ty_python_semantic/resources/corpus/07_ifexpr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/07_ifexpr.py rename to crates/ty_python_semantic/resources/corpus/07_ifexpr.py diff --git a/crates/ty_project/resources/test/corpus/07_ifexpr_multiline.py b/crates/ty_python_semantic/resources/corpus/07_ifexpr_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/07_ifexpr_multiline.py rename to crates/ty_python_semantic/resources/corpus/07_ifexpr_multiline.py diff --git a/crates/ty_project/resources/test/corpus/07_ifexpr_multiline2.py b/crates/ty_python_semantic/resources/corpus/07_ifexpr_multiline2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/07_ifexpr_multiline2.py rename to crates/ty_python_semantic/resources/corpus/07_ifexpr_multiline2.py diff --git a/crates/ty_project/resources/test/corpus/08_del.py b/crates/ty_python_semantic/resources/corpus/08_del.py similarity index 100% rename from crates/ty_project/resources/test/corpus/08_del.py rename to crates/ty_python_semantic/resources/corpus/08_del.py diff --git a/crates/ty_project/resources/test/corpus/08_del_multi.py b/crates/ty_python_semantic/resources/corpus/08_del_multi.py similarity index 100% rename from crates/ty_project/resources/test/corpus/08_del_multi.py rename to crates/ty_python_semantic/resources/corpus/08_del_multi.py diff --git a/crates/ty_project/resources/test/corpus/09_pass.py b/crates/ty_python_semantic/resources/corpus/09_pass.py similarity index 100% rename from crates/ty_project/resources/test/corpus/09_pass.py rename to crates/ty_python_semantic/resources/corpus/09_pass.py diff --git a/crates/ty_project/resources/test/corpus/10_if.py b/crates/ty_python_semantic/resources/corpus/10_if.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if.py rename to crates/ty_python_semantic/resources/corpus/10_if.py diff --git a/crates/ty_project/resources/test/corpus/10_if_chained_compare.py b/crates/ty_python_semantic/resources/corpus/10_if_chained_compare.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if_chained_compare.py rename to crates/ty_python_semantic/resources/corpus/10_if_chained_compare.py diff --git a/crates/ty_project/resources/test/corpus/10_if_false.py b/crates/ty_python_semantic/resources/corpus/10_if_false.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if_false.py rename to crates/ty_python_semantic/resources/corpus/10_if_false.py diff --git a/crates/ty_project/resources/test/corpus/10_if_invalid.py b/crates/ty_python_semantic/resources/corpus/10_if_invalid.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if_invalid.py rename to crates/ty_python_semantic/resources/corpus/10_if_invalid.py diff --git a/crates/ty_project/resources/test/corpus/10_if_true.py b/crates/ty_python_semantic/resources/corpus/10_if_true.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if_true.py rename to crates/ty_python_semantic/resources/corpus/10_if_true.py diff --git a/crates/ty_project/resources/test/corpus/10_if_with_named_expr.py b/crates/ty_python_semantic/resources/corpus/10_if_with_named_expr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/10_if_with_named_expr.py rename to crates/ty_python_semantic/resources/corpus/10_if_with_named_expr.py diff --git a/crates/ty_project/resources/test/corpus/11_if_else.py b/crates/ty_python_semantic/resources/corpus/11_if_else.py similarity index 100% rename from crates/ty_project/resources/test/corpus/11_if_else.py rename to crates/ty_python_semantic/resources/corpus/11_if_else.py diff --git a/crates/ty_project/resources/test/corpus/11_if_else_deeply_nested_for.py b/crates/ty_python_semantic/resources/corpus/11_if_else_deeply_nested_for.py similarity index 100% rename from crates/ty_project/resources/test/corpus/11_if_else_deeply_nested_for.py rename to crates/ty_python_semantic/resources/corpus/11_if_else_deeply_nested_for.py diff --git a/crates/ty_project/resources/test/corpus/11_if_else_false.py b/crates/ty_python_semantic/resources/corpus/11_if_else_false.py similarity index 100% rename from crates/ty_project/resources/test/corpus/11_if_else_false.py rename to crates/ty_python_semantic/resources/corpus/11_if_else_false.py diff --git a/crates/ty_project/resources/test/corpus/11_if_else_true.py b/crates/ty_python_semantic/resources/corpus/11_if_else_true.py similarity index 100% rename from crates/ty_project/resources/test/corpus/11_if_else_true.py rename to crates/ty_python_semantic/resources/corpus/11_if_else_true.py diff --git a/crates/ty_project/resources/test/corpus/12_if_elif.py b/crates/ty_python_semantic/resources/corpus/12_if_elif.py similarity index 100% rename from crates/ty_project/resources/test/corpus/12_if_elif.py rename to crates/ty_python_semantic/resources/corpus/12_if_elif.py diff --git a/crates/ty_project/resources/test/corpus/12_if_elif_else.py b/crates/ty_python_semantic/resources/corpus/12_if_elif_else.py similarity index 100% rename from crates/ty_project/resources/test/corpus/12_if_elif_else.py rename to crates/ty_python_semantic/resources/corpus/12_if_elif_else.py diff --git a/crates/ty_project/resources/test/corpus/13_ifelse_complex1.py b/crates/ty_python_semantic/resources/corpus/13_ifelse_complex1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/13_ifelse_complex1.py rename to crates/ty_python_semantic/resources/corpus/13_ifelse_complex1.py diff --git a/crates/ty_project/resources/test/corpus/13_ifelse_many.py b/crates/ty_python_semantic/resources/corpus/13_ifelse_many.py similarity index 100% rename from crates/ty_project/resources/test/corpus/13_ifelse_many.py rename to crates/ty_python_semantic/resources/corpus/13_ifelse_many.py diff --git a/crates/ty_project/resources/test/corpus/15_while.py b/crates/ty_python_semantic/resources/corpus/15_while.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while.py rename to crates/ty_python_semantic/resources/corpus/15_while.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break.py b/crates/ty_python_semantic/resources/corpus/15_while_break.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break.py rename to crates/ty_python_semantic/resources/corpus/15_while_break.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break_in_finally.py b/crates/ty_python_semantic/resources/corpus/15_while_break_in_finally.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break_in_finally.py rename to crates/ty_python_semantic/resources/corpus/15_while_break_in_finally.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break_invalid_in_class.py b/crates/ty_python_semantic/resources/corpus/15_while_break_invalid_in_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break_invalid_in_class.py rename to crates/ty_python_semantic/resources/corpus/15_while_break_invalid_in_class.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break_invalid_in_func.py b/crates/ty_python_semantic/resources/corpus/15_while_break_invalid_in_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break_invalid_in_func.py rename to crates/ty_python_semantic/resources/corpus/15_while_break_invalid_in_func.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break_non_empty.py b/crates/ty_python_semantic/resources/corpus/15_while_break_non_empty.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break_non_empty.py rename to crates/ty_python_semantic/resources/corpus/15_while_break_non_empty.py diff --git a/crates/ty_project/resources/test/corpus/15_while_break_non_exit.py b/crates/ty_python_semantic/resources/corpus/15_while_break_non_exit.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_break_non_exit.py rename to crates/ty_python_semantic/resources/corpus/15_while_break_non_exit.py diff --git a/crates/ty_project/resources/test/corpus/15_while_continue.py b/crates/ty_python_semantic/resources/corpus/15_while_continue.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_continue.py rename to crates/ty_python_semantic/resources/corpus/15_while_continue.py diff --git a/crates/ty_project/resources/test/corpus/15_while_false.py b/crates/ty_python_semantic/resources/corpus/15_while_false.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_false.py rename to crates/ty_python_semantic/resources/corpus/15_while_false.py diff --git a/crates/ty_project/resources/test/corpus/15_while_infinite.py b/crates/ty_python_semantic/resources/corpus/15_while_infinite.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_infinite.py rename to crates/ty_python_semantic/resources/corpus/15_while_infinite.py diff --git a/crates/ty_project/resources/test/corpus/15_while_true.py b/crates/ty_python_semantic/resources/corpus/15_while_true.py similarity index 100% rename from crates/ty_project/resources/test/corpus/15_while_true.py rename to crates/ty_python_semantic/resources/corpus/15_while_true.py diff --git a/crates/ty_project/resources/test/corpus/16_for.py b/crates/ty_python_semantic/resources/corpus/16_for.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for.py rename to crates/ty_python_semantic/resources/corpus/16_for.py diff --git a/crates/ty_project/resources/test/corpus/16_for_break.py b/crates/ty_python_semantic/resources/corpus/16_for_break.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_break.py rename to crates/ty_python_semantic/resources/corpus/16_for_break.py diff --git a/crates/ty_project/resources/test/corpus/16_for_break_invalid_in_class.py b/crates/ty_python_semantic/resources/corpus/16_for_break_invalid_in_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_break_invalid_in_class.py rename to crates/ty_python_semantic/resources/corpus/16_for_break_invalid_in_class.py diff --git a/crates/ty_project/resources/test/corpus/16_for_break_invalid_in_func.py b/crates/ty_python_semantic/resources/corpus/16_for_break_invalid_in_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_break_invalid_in_func.py rename to crates/ty_python_semantic/resources/corpus/16_for_break_invalid_in_func.py diff --git a/crates/ty_project/resources/test/corpus/16_for_continue.py b/crates/ty_python_semantic/resources/corpus/16_for_continue.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_continue.py rename to crates/ty_python_semantic/resources/corpus/16_for_continue.py diff --git a/crates/ty_project/resources/test/corpus/16_for_else.py b/crates/ty_python_semantic/resources/corpus/16_for_else.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_else.py rename to crates/ty_python_semantic/resources/corpus/16_for_else.py diff --git a/crates/ty_project/resources/test/corpus/16_for_invalid.py b/crates/ty_python_semantic/resources/corpus/16_for_invalid.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_invalid.py rename to crates/ty_python_semantic/resources/corpus/16_for_invalid.py diff --git a/crates/ty_project/resources/test/corpus/16_for_list_literal.py b/crates/ty_python_semantic/resources/corpus/16_for_list_literal.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_list_literal.py rename to crates/ty_python_semantic/resources/corpus/16_for_list_literal.py diff --git a/crates/ty_project/resources/test/corpus/16_for_nested_ifs.py b/crates/ty_python_semantic/resources/corpus/16_for_nested_ifs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/16_for_nested_ifs.py rename to crates/ty_python_semantic/resources/corpus/16_for_nested_ifs.py diff --git a/crates/ty_project/resources/test/corpus/20_lambda.py b/crates/ty_python_semantic/resources/corpus/20_lambda.py similarity index 100% rename from crates/ty_project/resources/test/corpus/20_lambda.py rename to crates/ty_python_semantic/resources/corpus/20_lambda.py diff --git a/crates/ty_project/resources/test/corpus/20_lambda_const.py b/crates/ty_python_semantic/resources/corpus/20_lambda_const.py similarity index 100% rename from crates/ty_project/resources/test/corpus/20_lambda_const.py rename to crates/ty_python_semantic/resources/corpus/20_lambda_const.py diff --git a/crates/ty_project/resources/test/corpus/20_lambda_default_arg.py b/crates/ty_python_semantic/resources/corpus/20_lambda_default_arg.py similarity index 100% rename from crates/ty_project/resources/test/corpus/20_lambda_default_arg.py rename to crates/ty_python_semantic/resources/corpus/20_lambda_default_arg.py diff --git a/crates/ty_project/resources/test/corpus/20_lambda_ifelse.py b/crates/ty_python_semantic/resources/corpus/20_lambda_ifelse.py similarity index 100% rename from crates/ty_project/resources/test/corpus/20_lambda_ifelse.py rename to crates/ty_python_semantic/resources/corpus/20_lambda_ifelse.py diff --git a/crates/ty_project/resources/test/corpus/21_func1.py b/crates/ty_python_semantic/resources/corpus/21_func1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/21_func1.py rename to crates/ty_python_semantic/resources/corpus/21_func1.py diff --git a/crates/ty_project/resources/test/corpus/21_func1_ret.py b/crates/ty_python_semantic/resources/corpus/21_func1_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/21_func1_ret.py rename to crates/ty_python_semantic/resources/corpus/21_func1_ret.py diff --git a/crates/ty_project/resources/test/corpus/21_func_assign.py b/crates/ty_python_semantic/resources/corpus/21_func_assign.py similarity index 100% rename from crates/ty_project/resources/test/corpus/21_func_assign.py rename to crates/ty_python_semantic/resources/corpus/21_func_assign.py diff --git a/crates/ty_project/resources/test/corpus/21_func_assign2.py b/crates/ty_python_semantic/resources/corpus/21_func_assign2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/21_func_assign2.py rename to crates/ty_python_semantic/resources/corpus/21_func_assign2.py diff --git a/crates/ty_project/resources/test/corpus/22_func_arg.py b/crates/ty_python_semantic/resources/corpus/22_func_arg.py similarity index 100% rename from crates/ty_project/resources/test/corpus/22_func_arg.py rename to crates/ty_python_semantic/resources/corpus/22_func_arg.py diff --git a/crates/ty_project/resources/test/corpus/22_func_vararg.py b/crates/ty_python_semantic/resources/corpus/22_func_vararg.py similarity index 100% rename from crates/ty_project/resources/test/corpus/22_func_vararg.py rename to crates/ty_python_semantic/resources/corpus/22_func_vararg.py diff --git a/crates/ty_project/resources/test/corpus/23_func_ret.py b/crates/ty_python_semantic/resources/corpus/23_func_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/23_func_ret.py rename to crates/ty_python_semantic/resources/corpus/23_func_ret.py diff --git a/crates/ty_project/resources/test/corpus/23_func_ret_val.py b/crates/ty_python_semantic/resources/corpus/23_func_ret_val.py similarity index 100% rename from crates/ty_project/resources/test/corpus/23_func_ret_val.py rename to crates/ty_python_semantic/resources/corpus/23_func_ret_val.py diff --git a/crates/ty_project/resources/test/corpus/24_func_if_ret.py b/crates/ty_python_semantic/resources/corpus/24_func_if_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/24_func_if_ret.py rename to crates/ty_python_semantic/resources/corpus/24_func_if_ret.py diff --git a/crates/ty_project/resources/test/corpus/24_func_ifelse_ret.py b/crates/ty_python_semantic/resources/corpus/24_func_ifelse_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/24_func_ifelse_ret.py rename to crates/ty_python_semantic/resources/corpus/24_func_ifelse_ret.py diff --git a/crates/ty_project/resources/test/corpus/24_func_ifnot_ret.py b/crates/ty_python_semantic/resources/corpus/24_func_ifnot_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/24_func_ifnot_ret.py rename to crates/ty_python_semantic/resources/corpus/24_func_ifnot_ret.py diff --git a/crates/ty_project/resources/test/corpus/25_func_annotations.py b/crates/ty_python_semantic/resources/corpus/25_func_annotations.py similarity index 100% rename from crates/ty_project/resources/test/corpus/25_func_annotations.py rename to crates/ty_python_semantic/resources/corpus/25_func_annotations.py diff --git a/crates/ty_project/resources/test/corpus/25_func_annotations_nested.py b/crates/ty_python_semantic/resources/corpus/25_func_annotations_nested.py similarity index 100% rename from crates/ty_project/resources/test/corpus/25_func_annotations_nested.py rename to crates/ty_python_semantic/resources/corpus/25_func_annotations_nested.py diff --git a/crates/ty_project/resources/test/corpus/25_func_annotations_same_name.py b/crates/ty_python_semantic/resources/corpus/25_func_annotations_same_name.py similarity index 100% rename from crates/ty_project/resources/test/corpus/25_func_annotations_same_name.py rename to crates/ty_python_semantic/resources/corpus/25_func_annotations_same_name.py diff --git a/crates/ty_project/resources/test/corpus/25_func_annotations_scope.py b/crates/ty_python_semantic/resources/corpus/25_func_annotations_scope.py similarity index 100% rename from crates/ty_project/resources/test/corpus/25_func_annotations_scope.py rename to crates/ty_python_semantic/resources/corpus/25_func_annotations_scope.py diff --git a/crates/ty_project/resources/test/corpus/25_func_annotations_starred.py b/crates/ty_python_semantic/resources/corpus/25_func_annotations_starred.py similarity index 100% rename from crates/ty_project/resources/test/corpus/25_func_annotations_starred.py rename to crates/ty_python_semantic/resources/corpus/25_func_annotations_starred.py diff --git a/crates/ty_project/resources/test/corpus/26_func_const_defaults.py b/crates/ty_python_semantic/resources/corpus/26_func_const_defaults.py similarity index 100% rename from crates/ty_project/resources/test/corpus/26_func_const_defaults.py rename to crates/ty_python_semantic/resources/corpus/26_func_const_defaults.py diff --git a/crates/ty_project/resources/test/corpus/26_func_defaults_same_name.py b/crates/ty_python_semantic/resources/corpus/26_func_defaults_same_name.py similarity index 100% rename from crates/ty_project/resources/test/corpus/26_func_defaults_same_name.py rename to crates/ty_python_semantic/resources/corpus/26_func_defaults_same_name.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic.py b/crates/ty_python_semantic/resources/corpus/27_func_generic.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_bound.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_bound.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_bound.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_bound.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_constraint.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_constraint.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_constraint.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_constraint.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_default.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_default.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_default.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_paramspec.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_paramspec.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_paramspec.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_paramspec.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_paramspec_default.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_paramspec_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_paramspec_default.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_paramspec_default.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_tuple.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_tuple.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_tuple.py diff --git a/crates/ty_project/resources/test/corpus/27_func_generic_tuple_default.py b/crates/ty_python_semantic/resources/corpus/27_func_generic_tuple_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/27_func_generic_tuple_default.py rename to crates/ty_python_semantic/resources/corpus/27_func_generic_tuple_default.py diff --git a/crates/ty_project/resources/test/corpus/30_func_enclosed.py b/crates/ty_python_semantic/resources/corpus/30_func_enclosed.py similarity index 100% rename from crates/ty_project/resources/test/corpus/30_func_enclosed.py rename to crates/ty_python_semantic/resources/corpus/30_func_enclosed.py diff --git a/crates/ty_project/resources/test/corpus/30_func_enclosed_many.py b/crates/ty_python_semantic/resources/corpus/30_func_enclosed_many.py similarity index 100% rename from crates/ty_project/resources/test/corpus/30_func_enclosed_many.py rename to crates/ty_python_semantic/resources/corpus/30_func_enclosed_many.py diff --git a/crates/ty_project/resources/test/corpus/31_func_global.py b/crates/ty_python_semantic/resources/corpus/31_func_global.py similarity index 100% rename from crates/ty_project/resources/test/corpus/31_func_global.py rename to crates/ty_python_semantic/resources/corpus/31_func_global.py diff --git a/crates/ty_project/resources/test/corpus/31_func_global_annotated_later.py b/crates/ty_python_semantic/resources/corpus/31_func_global_annotated_later.py similarity index 100% rename from crates/ty_project/resources/test/corpus/31_func_global_annotated_later.py rename to crates/ty_python_semantic/resources/corpus/31_func_global_annotated_later.py diff --git a/crates/ty_project/resources/test/corpus/31_func_nonlocal.py b/crates/ty_python_semantic/resources/corpus/31_func_nonlocal.py similarity index 100% rename from crates/ty_project/resources/test/corpus/31_func_nonlocal.py rename to crates/ty_python_semantic/resources/corpus/31_func_nonlocal.py diff --git a/crates/ty_project/resources/test/corpus/32_func_global_nested.py b/crates/ty_python_semantic/resources/corpus/32_func_global_nested.py similarity index 100% rename from crates/ty_project/resources/test/corpus/32_func_global_nested.py rename to crates/ty_python_semantic/resources/corpus/32_func_global_nested.py diff --git a/crates/ty_project/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py b/crates/ty_python_semantic/resources/corpus/33_func_with_docstring_optimizable_tuple_and_return.py similarity index 100% rename from crates/ty_project/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py rename to crates/ty_python_semantic/resources/corpus/33_func_with_docstring_optimizable_tuple_and_return.py diff --git a/crates/ty_project/resources/test/corpus/40_import.py b/crates/ty_python_semantic/resources/corpus/40_import.py similarity index 100% rename from crates/ty_project/resources/test/corpus/40_import.py rename to crates/ty_python_semantic/resources/corpus/40_import.py diff --git a/crates/ty_project/resources/test/corpus/41_from_import.py b/crates/ty_python_semantic/resources/corpus/41_from_import.py similarity index 100% rename from crates/ty_project/resources/test/corpus/41_from_import.py rename to crates/ty_python_semantic/resources/corpus/41_from_import.py diff --git a/crates/ty_project/resources/test/corpus/42_import_from_dot.py b/crates/ty_python_semantic/resources/corpus/42_import_from_dot.py similarity index 100% rename from crates/ty_project/resources/test/corpus/42_import_from_dot.py rename to crates/ty_python_semantic/resources/corpus/42_import_from_dot.py diff --git a/crates/ty_project/resources/test/corpus/50_yield.py b/crates/ty_python_semantic/resources/corpus/50_yield.py similarity index 100% rename from crates/ty_project/resources/test/corpus/50_yield.py rename to crates/ty_python_semantic/resources/corpus/50_yield.py diff --git a/crates/ty_project/resources/test/corpus/51_gen_comp.py b/crates/ty_python_semantic/resources/corpus/51_gen_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/51_gen_comp.py rename to crates/ty_python_semantic/resources/corpus/51_gen_comp.py diff --git a/crates/ty_project/resources/test/corpus/51_gen_comp2.py b/crates/ty_python_semantic/resources/corpus/51_gen_comp2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/51_gen_comp2.py rename to crates/ty_python_semantic/resources/corpus/51_gen_comp2.py diff --git a/crates/ty_project/resources/test/corpus/52_gen_comp_if.py b/crates/ty_python_semantic/resources/corpus/52_gen_comp_if.py similarity index 100% rename from crates/ty_project/resources/test/corpus/52_gen_comp_if.py rename to crates/ty_python_semantic/resources/corpus/52_gen_comp_if.py diff --git a/crates/ty_project/resources/test/corpus/53_dict_comp.py b/crates/ty_python_semantic/resources/corpus/53_dict_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/53_dict_comp.py rename to crates/ty_python_semantic/resources/corpus/53_dict_comp.py diff --git a/crates/ty_project/resources/test/corpus/53_list_comp.py b/crates/ty_python_semantic/resources/corpus/53_list_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/53_list_comp.py rename to crates/ty_python_semantic/resources/corpus/53_list_comp.py diff --git a/crates/ty_project/resources/test/corpus/53_list_comp_method.py b/crates/ty_python_semantic/resources/corpus/53_list_comp_method.py similarity index 100% rename from crates/ty_project/resources/test/corpus/53_list_comp_method.py rename to crates/ty_python_semantic/resources/corpus/53_list_comp_method.py diff --git a/crates/ty_project/resources/test/corpus/53_set_comp.py b/crates/ty_python_semantic/resources/corpus/53_set_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/53_set_comp.py rename to crates/ty_python_semantic/resources/corpus/53_set_comp.py diff --git a/crates/ty_project/resources/test/corpus/54_list_comp_func.py b/crates/ty_python_semantic/resources/corpus/54_list_comp_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/54_list_comp_func.py rename to crates/ty_python_semantic/resources/corpus/54_list_comp_func.py diff --git a/crates/ty_project/resources/test/corpus/54_list_comp_lambda.py b/crates/ty_python_semantic/resources/corpus/54_list_comp_lambda.py similarity index 100% rename from crates/ty_project/resources/test/corpus/54_list_comp_lambda.py rename to crates/ty_python_semantic/resources/corpus/54_list_comp_lambda.py diff --git a/crates/ty_project/resources/test/corpus/54_list_comp_lambda_listcomp.py b/crates/ty_python_semantic/resources/corpus/54_list_comp_lambda_listcomp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/54_list_comp_lambda_listcomp.py rename to crates/ty_python_semantic/resources/corpus/54_list_comp_lambda_listcomp.py diff --git a/crates/ty_project/resources/test/corpus/54_list_comp_recur_func.py b/crates/ty_python_semantic/resources/corpus/54_list_comp_recur_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/54_list_comp_recur_func.py rename to crates/ty_python_semantic/resources/corpus/54_list_comp_recur_func.py diff --git a/crates/ty_project/resources/test/corpus/55_list_comp_nested.py b/crates/ty_python_semantic/resources/corpus/55_list_comp_nested.py similarity index 100% rename from crates/ty_project/resources/test/corpus/55_list_comp_nested.py rename to crates/ty_python_semantic/resources/corpus/55_list_comp_nested.py diff --git a/crates/ty_project/resources/test/corpus/56_yield_from.py b/crates/ty_python_semantic/resources/corpus/56_yield_from.py similarity index 100% rename from crates/ty_project/resources/test/corpus/56_yield_from.py rename to crates/ty_python_semantic/resources/corpus/56_yield_from.py diff --git a/crates/ty_project/resources/test/corpus/57_await.py b/crates/ty_python_semantic/resources/corpus/57_await.py similarity index 100% rename from crates/ty_project/resources/test/corpus/57_await.py rename to crates/ty_python_semantic/resources/corpus/57_await.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for.py b/crates/ty_python_semantic/resources/corpus/58_async_for.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for.py rename to crates/ty_python_semantic/resources/corpus/58_async_for.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_break.py b/crates/ty_python_semantic/resources/corpus/58_async_for_break.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_break.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_break.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_continue.py b/crates/ty_python_semantic/resources/corpus/58_async_for_continue.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_continue.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_continue.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_dict_comp.py b/crates/ty_python_semantic/resources/corpus/58_async_for_dict_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_dict_comp.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_dict_comp.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_else.py b/crates/ty_python_semantic/resources/corpus/58_async_for_else.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_else.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_else.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_gen_comp.py b/crates/ty_python_semantic/resources/corpus/58_async_for_gen_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_gen_comp.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_gen_comp.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_list_comp.py b/crates/ty_python_semantic/resources/corpus/58_async_for_list_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_list_comp.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_list_comp.py diff --git a/crates/ty_project/resources/test/corpus/58_async_for_set_comp.py b/crates/ty_python_semantic/resources/corpus/58_async_for_set_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/58_async_for_set_comp.py rename to crates/ty_python_semantic/resources/corpus/58_async_for_set_comp.py diff --git a/crates/ty_project/resources/test/corpus/59_async_with.py b/crates/ty_python_semantic/resources/corpus/59_async_with.py similarity index 100% rename from crates/ty_project/resources/test/corpus/59_async_with.py rename to crates/ty_python_semantic/resources/corpus/59_async_with.py diff --git a/crates/ty_project/resources/test/corpus/59_async_with_nested_with.py b/crates/ty_python_semantic/resources/corpus/59_async_with_nested_with.py similarity index 100% rename from crates/ty_project/resources/test/corpus/59_async_with_nested_with.py rename to crates/ty_python_semantic/resources/corpus/59_async_with_nested_with.py diff --git a/crates/ty_project/resources/test/corpus/60_try_except.py b/crates/ty_python_semantic/resources/corpus/60_try_except.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_except.py rename to crates/ty_python_semantic/resources/corpus/60_try_except.py diff --git a/crates/ty_project/resources/test/corpus/60_try_except2.py b/crates/ty_python_semantic/resources/corpus/60_try_except2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_except2.py rename to crates/ty_python_semantic/resources/corpus/60_try_except2.py diff --git a/crates/ty_project/resources/test/corpus/60_try_except_bare.py b/crates/ty_python_semantic/resources/corpus/60_try_except_bare.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_except_bare.py rename to crates/ty_python_semantic/resources/corpus/60_try_except_bare.py diff --git a/crates/ty_project/resources/test/corpus/60_try_finally.py b/crates/ty_python_semantic/resources/corpus/60_try_finally.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_finally.py rename to crates/ty_python_semantic/resources/corpus/60_try_finally.py diff --git a/crates/ty_project/resources/test/corpus/60_try_finally_codeobj.py b/crates/ty_python_semantic/resources/corpus/60_try_finally_codeobj.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_finally_codeobj.py rename to crates/ty_python_semantic/resources/corpus/60_try_finally_codeobj.py diff --git a/crates/ty_project/resources/test/corpus/60_try_finally_cond.py b/crates/ty_python_semantic/resources/corpus/60_try_finally_cond.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_finally_cond.py rename to crates/ty_python_semantic/resources/corpus/60_try_finally_cond.py diff --git a/crates/ty_project/resources/test/corpus/60_try_finally_for.py b/crates/ty_python_semantic/resources/corpus/60_try_finally_for.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_finally_for.py rename to crates/ty_python_semantic/resources/corpus/60_try_finally_for.py diff --git a/crates/ty_project/resources/test/corpus/60_try_finally_ret.py b/crates/ty_python_semantic/resources/corpus/60_try_finally_ret.py similarity index 100% rename from crates/ty_project/resources/test/corpus/60_try_finally_ret.py rename to crates/ty_python_semantic/resources/corpus/60_try_finally_ret.py diff --git a/crates/ty_project/resources/test/corpus/61_try_except_finally.py b/crates/ty_python_semantic/resources/corpus/61_try_except_finally.py similarity index 100% rename from crates/ty_project/resources/test/corpus/61_try_except_finally.py rename to crates/ty_python_semantic/resources/corpus/61_try_except_finally.py diff --git a/crates/ty_project/resources/test/corpus/62_try_except_as.py b/crates/ty_python_semantic/resources/corpus/62_try_except_as.py similarity index 100% rename from crates/ty_project/resources/test/corpus/62_try_except_as.py rename to crates/ty_python_semantic/resources/corpus/62_try_except_as.py diff --git a/crates/ty_project/resources/test/corpus/62_try_except_break.py b/crates/ty_python_semantic/resources/corpus/62_try_except_break.py similarity index 100% rename from crates/ty_project/resources/test/corpus/62_try_except_break.py rename to crates/ty_python_semantic/resources/corpus/62_try_except_break.py diff --git a/crates/ty_project/resources/test/corpus/62_try_except_cond.py b/crates/ty_python_semantic/resources/corpus/62_try_except_cond.py similarity index 100% rename from crates/ty_project/resources/test/corpus/62_try_except_cond.py rename to crates/ty_python_semantic/resources/corpus/62_try_except_cond.py diff --git a/crates/ty_project/resources/test/corpus/62_try_except_double_nested_inside_if_else.py b/crates/ty_python_semantic/resources/corpus/62_try_except_double_nested_inside_if_else.py similarity index 100% rename from crates/ty_project/resources/test/corpus/62_try_except_double_nested_inside_if_else.py rename to crates/ty_python_semantic/resources/corpus/62_try_except_double_nested_inside_if_else.py diff --git a/crates/ty_project/resources/test/corpus/62_try_except_return.py b/crates/ty_python_semantic/resources/corpus/62_try_except_return.py similarity index 100% rename from crates/ty_project/resources/test/corpus/62_try_except_return.py rename to crates/ty_python_semantic/resources/corpus/62_try_except_return.py diff --git a/crates/ty_project/resources/test/corpus/63_raise.py b/crates/ty_python_semantic/resources/corpus/63_raise.py similarity index 100% rename from crates/ty_project/resources/test/corpus/63_raise.py rename to crates/ty_python_semantic/resources/corpus/63_raise.py diff --git a/crates/ty_project/resources/test/corpus/63_raise_func.py b/crates/ty_python_semantic/resources/corpus/63_raise_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/63_raise_func.py rename to crates/ty_python_semantic/resources/corpus/63_raise_func.py diff --git a/crates/ty_project/resources/test/corpus/63_raise_x.py b/crates/ty_python_semantic/resources/corpus/63_raise_x.py similarity index 100% rename from crates/ty_project/resources/test/corpus/63_raise_x.py rename to crates/ty_python_semantic/resources/corpus/63_raise_x.py diff --git a/crates/ty_project/resources/test/corpus/63_raise_x_from_y.py b/crates/ty_python_semantic/resources/corpus/63_raise_x_from_y.py similarity index 100% rename from crates/ty_project/resources/test/corpus/63_raise_x_from_y.py rename to crates/ty_python_semantic/resources/corpus/63_raise_x_from_y.py diff --git a/crates/ty_project/resources/test/corpus/64_assert.py b/crates/ty_python_semantic/resources/corpus/64_assert.py similarity index 100% rename from crates/ty_project/resources/test/corpus/64_assert.py rename to crates/ty_python_semantic/resources/corpus/64_assert.py diff --git a/crates/ty_project/resources/test/corpus/67_with.py b/crates/ty_python_semantic/resources/corpus/67_with.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with.py rename to crates/ty_python_semantic/resources/corpus/67_with.py diff --git a/crates/ty_project/resources/test/corpus/67_with_as.py b/crates/ty_python_semantic/resources/corpus/67_with_as.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_as.py rename to crates/ty_python_semantic/resources/corpus/67_with_as.py diff --git a/crates/ty_project/resources/test/corpus/67_with_as_func.py b/crates/ty_python_semantic/resources/corpus/67_with_as_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_as_func.py rename to crates/ty_python_semantic/resources/corpus/67_with_as_func.py diff --git a/crates/ty_project/resources/test/corpus/67_with_cond_return.py b/crates/ty_python_semantic/resources/corpus/67_with_cond_return.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_cond_return.py rename to crates/ty_python_semantic/resources/corpus/67_with_cond_return.py diff --git a/crates/ty_project/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py b/crates/ty_python_semantic/resources/corpus/67_with_inside_try_finally_multiple_terminal_elif.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py rename to crates/ty_python_semantic/resources/corpus/67_with_inside_try_finally_multiple_terminal_elif.py diff --git a/crates/ty_project/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py b/crates/ty_python_semantic/resources/corpus/67_with_inside_try_finally_preceding_terminal_except.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py rename to crates/ty_python_semantic/resources/corpus/67_with_inside_try_finally_preceding_terminal_except.py diff --git a/crates/ty_project/resources/test/corpus/67_with_multi_exit.py b/crates/ty_python_semantic/resources/corpus/67_with_multi_exit.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_multi_exit.py rename to crates/ty_python_semantic/resources/corpus/67_with_multi_exit.py diff --git a/crates/ty_project/resources/test/corpus/67_with_non_name_target.py b/crates/ty_python_semantic/resources/corpus/67_with_non_name_target.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_non_name_target.py rename to crates/ty_python_semantic/resources/corpus/67_with_non_name_target.py diff --git a/crates/ty_project/resources/test/corpus/67_with_return.py b/crates/ty_python_semantic/resources/corpus/67_with_return.py similarity index 100% rename from crates/ty_project/resources/test/corpus/67_with_return.py rename to crates/ty_python_semantic/resources/corpus/67_with_return.py diff --git a/crates/ty_project/resources/test/corpus/68_with2.py b/crates/ty_python_semantic/resources/corpus/68_with2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/68_with2.py rename to crates/ty_python_semantic/resources/corpus/68_with2.py diff --git a/crates/ty_project/resources/test/corpus/69_for_try_except_continue1.py b/crates/ty_python_semantic/resources/corpus/69_for_try_except_continue1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/69_for_try_except_continue1.py rename to crates/ty_python_semantic/resources/corpus/69_for_try_except_continue1.py diff --git a/crates/ty_project/resources/test/corpus/69_for_try_except_continue2.py b/crates/ty_python_semantic/resources/corpus/69_for_try_except_continue2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/69_for_try_except_continue2.py rename to crates/ty_python_semantic/resources/corpus/69_for_try_except_continue2.py diff --git a/crates/ty_project/resources/test/corpus/69_for_try_except_continue3.py b/crates/ty_python_semantic/resources/corpus/69_for_try_except_continue3.py similarity index 100% rename from crates/ty_project/resources/test/corpus/69_for_try_except_continue3.py rename to crates/ty_python_semantic/resources/corpus/69_for_try_except_continue3.py diff --git a/crates/ty_project/resources/test/corpus/70_class.py b/crates/ty_python_semantic/resources/corpus/70_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/70_class.py rename to crates/ty_python_semantic/resources/corpus/70_class.py diff --git a/crates/ty_project/resources/test/corpus/70_class_base.py b/crates/ty_python_semantic/resources/corpus/70_class_base.py similarity index 100% rename from crates/ty_project/resources/test/corpus/70_class_base.py rename to crates/ty_python_semantic/resources/corpus/70_class_base.py diff --git a/crates/ty_project/resources/test/corpus/70_class_doc_str.py b/crates/ty_python_semantic/resources/corpus/70_class_doc_str.py similarity index 100% rename from crates/ty_project/resources/test/corpus/70_class_doc_str.py rename to crates/ty_python_semantic/resources/corpus/70_class_doc_str.py diff --git a/crates/ty_project/resources/test/corpus/71_class_meth.py b/crates/ty_python_semantic/resources/corpus/71_class_meth.py similarity index 100% rename from crates/ty_project/resources/test/corpus/71_class_meth.py rename to crates/ty_python_semantic/resources/corpus/71_class_meth.py diff --git a/crates/ty_project/resources/test/corpus/71_class_var.py b/crates/ty_python_semantic/resources/corpus/71_class_var.py similarity index 100% rename from crates/ty_project/resources/test/corpus/71_class_var.py rename to crates/ty_python_semantic/resources/corpus/71_class_var.py diff --git a/crates/ty_project/resources/test/corpus/72_class_mix.py b/crates/ty_python_semantic/resources/corpus/72_class_mix.py similarity index 100% rename from crates/ty_project/resources/test/corpus/72_class_mix.py rename to crates/ty_python_semantic/resources/corpus/72_class_mix.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic.py b/crates/ty_python_semantic/resources/corpus/73_class_generic.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_bounds.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_bounds.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_bounds.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_bounds.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_constraints.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_constraints.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_constraints.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_constraints.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_defaults.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_defaults.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_defaults.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_defaults.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_paramspec.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_paramspec.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_paramspec.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_paramspec.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_paramspec_default.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_paramspec_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_paramspec_default.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_paramspec_default.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_tuple.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_tuple.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_tuple.py diff --git a/crates/ty_project/resources/test/corpus/73_class_generic_tuple_default.py b/crates/ty_python_semantic/resources/corpus/73_class_generic_tuple_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/73_class_generic_tuple_default.py rename to crates/ty_python_semantic/resources/corpus/73_class_generic_tuple_default.py diff --git a/crates/ty_project/resources/test/corpus/74_class_kwargs.py b/crates/ty_python_semantic/resources/corpus/74_class_kwargs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/74_class_kwargs.py rename to crates/ty_python_semantic/resources/corpus/74_class_kwargs.py diff --git a/crates/ty_project/resources/test/corpus/74_class_kwargs_2.py b/crates/ty_python_semantic/resources/corpus/74_class_kwargs_2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/74_class_kwargs_2.py rename to crates/ty_python_semantic/resources/corpus/74_class_kwargs_2.py diff --git a/crates/ty_project/resources/test/corpus/74_class_super.py b/crates/ty_python_semantic/resources/corpus/74_class_super.py similarity index 100% rename from crates/ty_project/resources/test/corpus/74_class_super.py rename to crates/ty_python_semantic/resources/corpus/74_class_super.py diff --git a/crates/ty_project/resources/test/corpus/74_class_super_nested.py b/crates/ty_python_semantic/resources/corpus/74_class_super_nested.py similarity index 100% rename from crates/ty_project/resources/test/corpus/74_class_super_nested.py rename to crates/ty_python_semantic/resources/corpus/74_class_super_nested.py diff --git a/crates/ty_project/resources/test/corpus/74_just_super.py b/crates/ty_python_semantic/resources/corpus/74_just_super.py similarity index 100% rename from crates/ty_project/resources/test/corpus/74_just_super.py rename to crates/ty_python_semantic/resources/corpus/74_just_super.py diff --git a/crates/ty_project/resources/test/corpus/75_classderef.py b/crates/ty_python_semantic/resources/corpus/75_classderef.py similarity index 100% rename from crates/ty_project/resources/test/corpus/75_classderef.py rename to crates/ty_python_semantic/resources/corpus/75_classderef.py diff --git a/crates/ty_project/resources/test/corpus/75_classderef_no.py b/crates/ty_python_semantic/resources/corpus/75_classderef_no.py similarity index 100% rename from crates/ty_project/resources/test/corpus/75_classderef_no.py rename to crates/ty_python_semantic/resources/corpus/75_classderef_no.py diff --git a/crates/ty_project/resources/test/corpus/76_class_nonlocal1.py b/crates/ty_python_semantic/resources/corpus/76_class_nonlocal1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/76_class_nonlocal1.py rename to crates/ty_python_semantic/resources/corpus/76_class_nonlocal1.py diff --git a/crates/ty_project/resources/test/corpus/76_class_nonlocal2.py b/crates/ty_python_semantic/resources/corpus/76_class_nonlocal2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/76_class_nonlocal2.py rename to crates/ty_python_semantic/resources/corpus/76_class_nonlocal2.py diff --git a/crates/ty_project/resources/test/corpus/76_class_nonlocal3.py b/crates/ty_python_semantic/resources/corpus/76_class_nonlocal3.py similarity index 100% rename from crates/ty_project/resources/test/corpus/76_class_nonlocal3.py rename to crates/ty_python_semantic/resources/corpus/76_class_nonlocal3.py diff --git a/crates/ty_project/resources/test/corpus/76_class_nonlocal4.py b/crates/ty_python_semantic/resources/corpus/76_class_nonlocal4.py similarity index 100% rename from crates/ty_project/resources/test/corpus/76_class_nonlocal4.py rename to crates/ty_python_semantic/resources/corpus/76_class_nonlocal4.py diff --git a/crates/ty_project/resources/test/corpus/76_class_nonlocal5.py b/crates/ty_python_semantic/resources/corpus/76_class_nonlocal5.py similarity index 100% rename from crates/ty_project/resources/test/corpus/76_class_nonlocal5.py rename to crates/ty_python_semantic/resources/corpus/76_class_nonlocal5.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__.py b/crates/ty_python_semantic/resources/corpus/77_class__class__.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__nested.py b/crates/ty_python_semantic/resources/corpus/77_class__class__nested.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__nested.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__nested.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__no_class.py b/crates/ty_python_semantic/resources/corpus/77_class__class__no_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__no_class.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__no_class.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__nonlocals.py b/crates/ty_python_semantic/resources/corpus/77_class__class__nonlocals.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__nonlocals.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__nonlocals.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__nonlocals_2.py b/crates/ty_python_semantic/resources/corpus/77_class__class__nonlocals_2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__nonlocals_2.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__nonlocals_2.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__param.py b/crates/ty_python_semantic/resources/corpus/77_class__class__param.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__param.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__param.py diff --git a/crates/ty_project/resources/test/corpus/77_class__class__param_lambda.py b/crates/ty_python_semantic/resources/corpus/77_class__class__param_lambda.py similarity index 100% rename from crates/ty_project/resources/test/corpus/77_class__class__param_lambda.py rename to crates/ty_python_semantic/resources/corpus/77_class__class__param_lambda.py diff --git a/crates/ty_project/resources/test/corpus/78_class_body_cond.py b/crates/ty_python_semantic/resources/corpus/78_class_body_cond.py similarity index 100% rename from crates/ty_project/resources/test/corpus/78_class_body_cond.py rename to crates/ty_python_semantic/resources/corpus/78_class_body_cond.py diff --git a/crates/ty_project/resources/test/corpus/78_class_dec.py b/crates/ty_python_semantic/resources/corpus/78_class_dec.py similarity index 100% rename from crates/ty_project/resources/test/corpus/78_class_dec.py rename to crates/ty_python_semantic/resources/corpus/78_class_dec.py diff --git a/crates/ty_project/resources/test/corpus/78_class_dec_member.py b/crates/ty_python_semantic/resources/corpus/78_class_dec_member.py similarity index 100% rename from crates/ty_project/resources/test/corpus/78_class_dec_member.py rename to crates/ty_python_semantic/resources/corpus/78_class_dec_member.py diff --git a/crates/ty_project/resources/test/corpus/78_class_dec_member_func.py b/crates/ty_python_semantic/resources/corpus/78_class_dec_member_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/78_class_dec_member_func.py rename to crates/ty_python_semantic/resources/corpus/78_class_dec_member_func.py diff --git a/crates/ty_project/resources/test/corpus/79_metaclass.py b/crates/ty_python_semantic/resources/corpus/79_metaclass.py similarity index 100% rename from crates/ty_project/resources/test/corpus/79_metaclass.py rename to crates/ty_python_semantic/resources/corpus/79_metaclass.py diff --git a/crates/ty_project/resources/test/corpus/80_func_kwonlyargs1.py b/crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/80_func_kwonlyargs1.py rename to crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs1.py diff --git a/crates/ty_project/resources/test/corpus/80_func_kwonlyargs2.py b/crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/80_func_kwonlyargs2.py rename to crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs2.py diff --git a/crates/ty_project/resources/test/corpus/80_func_kwonlyargs3.py b/crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs3.py similarity index 100% rename from crates/ty_project/resources/test/corpus/80_func_kwonlyargs3.py rename to crates/ty_python_semantic/resources/corpus/80_func_kwonlyargs3.py diff --git a/crates/ty_project/resources/test/corpus/81_func_kwonlyargs_defaults.py b/crates/ty_python_semantic/resources/corpus/81_func_kwonlyargs_defaults.py similarity index 100% rename from crates/ty_project/resources/test/corpus/81_func_kwonlyargs_defaults.py rename to crates/ty_python_semantic/resources/corpus/81_func_kwonlyargs_defaults.py diff --git a/crates/ty_python_semantic/resources/corpus/83_jupyter_notebook_ipython_magic.ipynb b/crates/ty_python_semantic/resources/corpus/83_jupyter_notebook_ipython_magic.ipynb new file mode 120000 index 00000000000000..a323f576562ddd --- /dev/null +++ b/crates/ty_python_semantic/resources/corpus/83_jupyter_notebook_ipython_magic.ipynb @@ -0,0 +1 @@ +../../../ruff_notebook/resources/test/fixtures/jupyter/unused_variable.ipynb \ No newline at end of file diff --git a/crates/ty_project/resources/test/corpus/85_match.py b/crates/ty_python_semantic/resources/corpus/85_match.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match.py rename to crates/ty_python_semantic/resources/corpus/85_match.py diff --git a/crates/ty_project/resources/test/corpus/85_match_as.py b/crates/ty_python_semantic/resources/corpus/85_match_as.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_as.py rename to crates/ty_python_semantic/resources/corpus/85_match_as.py diff --git a/crates/ty_project/resources/test/corpus/85_match_attr.py b/crates/ty_python_semantic/resources/corpus/85_match_attr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_attr.py rename to crates/ty_python_semantic/resources/corpus/85_match_attr.py diff --git a/crates/ty_project/resources/test/corpus/85_match_class.py b/crates/ty_python_semantic/resources/corpus/85_match_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_class.py rename to crates/ty_python_semantic/resources/corpus/85_match_class.py diff --git a/crates/ty_project/resources/test/corpus/85_match_default.py b/crates/ty_python_semantic/resources/corpus/85_match_default.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_default.py rename to crates/ty_python_semantic/resources/corpus/85_match_default.py diff --git a/crates/ty_project/resources/test/corpus/85_match_guard.py b/crates/ty_python_semantic/resources/corpus/85_match_guard.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_guard.py rename to crates/ty_python_semantic/resources/corpus/85_match_guard.py diff --git a/crates/ty_project/resources/test/corpus/85_match_guard_with_named_expr.py b/crates/ty_python_semantic/resources/corpus/85_match_guard_with_named_expr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_guard_with_named_expr.py rename to crates/ty_python_semantic/resources/corpus/85_match_guard_with_named_expr.py diff --git a/crates/ty_project/resources/test/corpus/85_match_in_func.py b/crates/ty_python_semantic/resources/corpus/85_match_in_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_in_func.py rename to crates/ty_python_semantic/resources/corpus/85_match_in_func.py diff --git a/crates/ty_project/resources/test/corpus/85_match_in_func_with_rest.py b/crates/ty_python_semantic/resources/corpus/85_match_in_func_with_rest.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_in_func_with_rest.py rename to crates/ty_python_semantic/resources/corpus/85_match_in_func_with_rest.py diff --git a/crates/ty_project/resources/test/corpus/85_match_in_func_with_star.py b/crates/ty_python_semantic/resources/corpus/85_match_in_func_with_star.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_in_func_with_star.py rename to crates/ty_python_semantic/resources/corpus/85_match_in_func_with_star.py diff --git a/crates/ty_project/resources/test/corpus/85_match_invalid.py b/crates/ty_python_semantic/resources/corpus/85_match_invalid.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_invalid.py rename to crates/ty_python_semantic/resources/corpus/85_match_invalid.py diff --git a/crates/ty_project/resources/test/corpus/85_match_mapping.py b/crates/ty_python_semantic/resources/corpus/85_match_mapping.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_mapping.py rename to crates/ty_python_semantic/resources/corpus/85_match_mapping.py diff --git a/crates/ty_project/resources/test/corpus/85_match_mapping_subpattern.py b/crates/ty_python_semantic/resources/corpus/85_match_mapping_subpattern.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_mapping_subpattern.py rename to crates/ty_python_semantic/resources/corpus/85_match_mapping_subpattern.py diff --git a/crates/ty_project/resources/test/corpus/85_match_or.py b/crates/ty_python_semantic/resources/corpus/85_match_or.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_or.py rename to crates/ty_python_semantic/resources/corpus/85_match_or.py diff --git a/crates/ty_project/resources/test/corpus/85_match_sequence.py b/crates/ty_python_semantic/resources/corpus/85_match_sequence.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_sequence.py rename to crates/ty_python_semantic/resources/corpus/85_match_sequence.py diff --git a/crates/ty_project/resources/test/corpus/85_match_sequence_wildcard.py b/crates/ty_python_semantic/resources/corpus/85_match_sequence_wildcard.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_sequence_wildcard.py rename to crates/ty_python_semantic/resources/corpus/85_match_sequence_wildcard.py diff --git a/crates/ty_project/resources/test/corpus/85_match_singleton.py b/crates/ty_python_semantic/resources/corpus/85_match_singleton.py similarity index 100% rename from crates/ty_project/resources/test/corpus/85_match_singleton.py rename to crates/ty_python_semantic/resources/corpus/85_match_singleton.py diff --git a/crates/ty_project/resources/test/corpus/88_regression_generic_method_with_nested_function.py b/crates/ty_python_semantic/resources/corpus/88_regression_generic_method_with_nested_function.py similarity index 100% rename from crates/ty_project/resources/test/corpus/88_regression_generic_method_with_nested_function.py rename to crates/ty_python_semantic/resources/corpus/88_regression_generic_method_with_nested_function.py diff --git a/crates/ty_project/resources/test/corpus/88_regression_issue_17792.py b/crates/ty_python_semantic/resources/corpus/88_regression_issue_17792.py similarity index 100% rename from crates/ty_project/resources/test/corpus/88_regression_issue_17792.py rename to crates/ty_python_semantic/resources/corpus/88_regression_issue_17792.py diff --git a/crates/ty_project/resources/test/corpus/88_regression_tuple_type_short_circuit.py b/crates/ty_python_semantic/resources/corpus/88_regression_tuple_type_short_circuit.py similarity index 100% rename from crates/ty_project/resources/test/corpus/88_regression_tuple_type_short_circuit.py rename to crates/ty_python_semantic/resources/corpus/88_regression_tuple_type_short_circuit.py diff --git a/crates/ty_project/resources/test/corpus/89_type_alias.py b/crates/ty_python_semantic/resources/corpus/89_type_alias.py similarity index 100% rename from crates/ty_project/resources/test/corpus/89_type_alias.py rename to crates/ty_python_semantic/resources/corpus/89_type_alias.py diff --git a/crates/ty_project/resources/test/corpus/90_docstring_class.py b/crates/ty_python_semantic/resources/corpus/90_docstring_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/90_docstring_class.py rename to crates/ty_python_semantic/resources/corpus/90_docstring_class.py diff --git a/crates/ty_project/resources/test/corpus/90_docstring_func.py b/crates/ty_python_semantic/resources/corpus/90_docstring_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/90_docstring_func.py rename to crates/ty_python_semantic/resources/corpus/90_docstring_func.py diff --git a/crates/ty_project/resources/test/corpus/90_docstring_mod.py b/crates/ty_python_semantic/resources/corpus/90_docstring_mod.py similarity index 100% rename from crates/ty_project/resources/test/corpus/90_docstring_mod.py rename to crates/ty_python_semantic/resources/corpus/90_docstring_mod.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers1.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers1.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers1.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers1.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers2.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers2.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers2.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers2.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers2_comp.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers2_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers2_comp.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers2_comp.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers3.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers3.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers3.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers3.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers4.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers4.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers4.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers4.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers_dict.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers_dict.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers_dict.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers_dict.py diff --git a/crates/ty_project/resources/test/corpus/91_line_numbers_dict_comp.py b/crates/ty_python_semantic/resources/corpus/91_line_numbers_dict_comp.py similarity index 100% rename from crates/ty_project/resources/test/corpus/91_line_numbers_dict_comp.py rename to crates/ty_python_semantic/resources/corpus/91_line_numbers_dict_comp.py diff --git a/crates/ty_project/resources/test/corpus/92_qual_class_in_class.py b/crates/ty_python_semantic/resources/corpus/92_qual_class_in_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/92_qual_class_in_class.py rename to crates/ty_python_semantic/resources/corpus/92_qual_class_in_class.py diff --git a/crates/ty_project/resources/test/corpus/92_qual_class_in_func.py b/crates/ty_python_semantic/resources/corpus/92_qual_class_in_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/92_qual_class_in_func.py rename to crates/ty_python_semantic/resources/corpus/92_qual_class_in_func.py diff --git a/crates/ty_project/resources/test/corpus/93_deadcode.py b/crates/ty_python_semantic/resources/corpus/93_deadcode.py similarity index 100% rename from crates/ty_project/resources/test/corpus/93_deadcode.py rename to crates/ty_python_semantic/resources/corpus/93_deadcode.py diff --git a/crates/ty_project/resources/test/corpus/94_strformat.py b/crates/ty_python_semantic/resources/corpus/94_strformat.py similarity index 100% rename from crates/ty_project/resources/test/corpus/94_strformat.py rename to crates/ty_python_semantic/resources/corpus/94_strformat.py diff --git a/crates/ty_project/resources/test/corpus/94_strformat_complex.py b/crates/ty_python_semantic/resources/corpus/94_strformat_complex.py similarity index 100% rename from crates/ty_project/resources/test/corpus/94_strformat_complex.py rename to crates/ty_python_semantic/resources/corpus/94_strformat_complex.py diff --git a/crates/ty_project/resources/test/corpus/94_strformat_conv.py b/crates/ty_python_semantic/resources/corpus/94_strformat_conv.py similarity index 100% rename from crates/ty_project/resources/test/corpus/94_strformat_conv.py rename to crates/ty_python_semantic/resources/corpus/94_strformat_conv.py diff --git a/crates/ty_project/resources/test/corpus/94_strformat_conversion.py b/crates/ty_python_semantic/resources/corpus/94_strformat_conversion.py similarity index 100% rename from crates/ty_project/resources/test/corpus/94_strformat_conversion.py rename to crates/ty_python_semantic/resources/corpus/94_strformat_conversion.py diff --git a/crates/ty_project/resources/test/corpus/94_strformat_spec.py b/crates/ty_python_semantic/resources/corpus/94_strformat_spec.py similarity index 100% rename from crates/ty_project/resources/test/corpus/94_strformat_spec.py rename to crates/ty_python_semantic/resources/corpus/94_strformat_spec.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py b/crates/ty_python_semantic/resources/corpus/95_annotation_assign_subscript_no_rhs.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_assign_subscript_no_rhs.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_assign_tuple.py b/crates/ty_python_semantic/resources/corpus/95_annotation_assign_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_assign_tuple.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_assign_tuple.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_class.py b/crates/ty_python_semantic/resources/corpus/95_annotation_class.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_class.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_class.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_class_multiline.py b/crates/ty_python_semantic/resources/corpus/95_annotation_class_multiline.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_class_multiline.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_class_multiline.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_class_no_value.py b/crates/ty_python_semantic/resources/corpus/95_annotation_class_no_value.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_class_no_value.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_class_no_value.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_fstring_invalid.py b/crates/ty_python_semantic/resources/corpus/95_annotation_fstring_invalid.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_fstring_invalid.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_fstring_invalid.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_func.py b/crates/ty_python_semantic/resources/corpus/95_annotation_func.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_func.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_func.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_func_future.py b/crates/ty_python_semantic/resources/corpus/95_annotation_func_future.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_func_future.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_func_future.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_global.py b/crates/ty_python_semantic/resources/corpus/95_annotation_global.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_global.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_global.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_global_simple.py b/crates/ty_python_semantic/resources/corpus/95_annotation_global_simple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_global_simple.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_global_simple.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_local_attr.py b/crates/ty_python_semantic/resources/corpus/95_annotation_local_attr.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_local_attr.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_local_attr.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_module.py b/crates/ty_python_semantic/resources/corpus/95_annotation_module.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_module.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_module.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_string_tuple.py b/crates/ty_python_semantic/resources/corpus/95_annotation_string_tuple.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_string_tuple.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_string_tuple.py diff --git a/crates/ty_project/resources/test/corpus/95_annotation_union.py b/crates/ty_python_semantic/resources/corpus/95_annotation_union.py similarity index 100% rename from crates/ty_project/resources/test/corpus/95_annotation_union.py rename to crates/ty_python_semantic/resources/corpus/95_annotation_union.py diff --git a/crates/ty_project/resources/test/corpus/96_debug.py b/crates/ty_python_semantic/resources/corpus/96_debug.py similarity index 100% rename from crates/ty_project/resources/test/corpus/96_debug.py rename to crates/ty_python_semantic/resources/corpus/96_debug.py diff --git a/crates/ty_project/resources/test/corpus/97_global_nonlocal_store.py b/crates/ty_python_semantic/resources/corpus/97_global_nonlocal_store.py similarity index 100% rename from crates/ty_project/resources/test/corpus/97_global_nonlocal_store.py rename to crates/ty_python_semantic/resources/corpus/97_global_nonlocal_store.py diff --git a/crates/ty_project/resources/test/corpus/98_ann_assign_annotation_future_annotations.py b/crates/ty_python_semantic/resources/corpus/98_ann_assign_annotation_future_annotations.py similarity index 100% rename from crates/ty_project/resources/test/corpus/98_ann_assign_annotation_future_annotations.py rename to crates/ty_python_semantic/resources/corpus/98_ann_assign_annotation_future_annotations.py diff --git a/crates/ty_project/resources/test/corpus/98_ann_assign_annotation_wrong_future.py b/crates/ty_python_semantic/resources/corpus/98_ann_assign_annotation_wrong_future.py similarity index 100% rename from crates/ty_project/resources/test/corpus/98_ann_assign_annotation_wrong_future.py rename to crates/ty_python_semantic/resources/corpus/98_ann_assign_annotation_wrong_future.py diff --git a/crates/ty_project/resources/test/corpus/98_ann_assign_simple_annotation.py b/crates/ty_python_semantic/resources/corpus/98_ann_assign_simple_annotation.py similarity index 100% rename from crates/ty_project/resources/test/corpus/98_ann_assign_simple_annotation.py rename to crates/ty_python_semantic/resources/corpus/98_ann_assign_simple_annotation.py diff --git a/crates/ty_project/resources/test/corpus/99_empty_jump_target_insts.py b/crates/ty_python_semantic/resources/corpus/99_empty_jump_target_insts.py similarity index 100% rename from crates/ty_project/resources/test/corpus/99_empty_jump_target_insts.py rename to crates/ty_python_semantic/resources/corpus/99_empty_jump_target_insts.py diff --git a/crates/ty_project/resources/test/corpus/callable_with_concatenate.py b/crates/ty_python_semantic/resources/corpus/callable_with_concatenate.py similarity index 100% rename from crates/ty_project/resources/test/corpus/callable_with_concatenate.py rename to crates/ty_python_semantic/resources/corpus/callable_with_concatenate.py diff --git a/crates/ty_project/resources/test/corpus/cycle_narrowing_constraints.py b/crates/ty_python_semantic/resources/corpus/cycle_narrowing_constraints.py similarity index 100% rename from crates/ty_project/resources/test/corpus/cycle_narrowing_constraints.py rename to crates/ty_python_semantic/resources/corpus/cycle_narrowing_constraints.py diff --git a/crates/ty_project/resources/test/corpus/cycle_negative_narrowing_constraints.py b/crates/ty_python_semantic/resources/corpus/cycle_negative_narrowing_constraints.py similarity index 100% rename from crates/ty_project/resources/test/corpus/cycle_negative_narrowing_constraints.py rename to crates/ty_python_semantic/resources/corpus/cycle_negative_narrowing_constraints.py diff --git a/crates/ty_project/resources/test/corpus/except_handler_with_Any_bound_typevar.py b/crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py similarity index 100% rename from crates/ty_project/resources/test/corpus/except_handler_with_Any_bound_typevar.py rename to crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py diff --git a/crates/ty_project/resources/test/corpus/literal_slices.py b/crates/ty_python_semantic/resources/corpus/literal_slices.py similarity index 100% rename from crates/ty_project/resources/test/corpus/literal_slices.py rename to crates/ty_python_semantic/resources/corpus/literal_slices.py diff --git a/crates/ty_project/resources/test/corpus/self_referential_function_annotation.py b/crates/ty_python_semantic/resources/corpus/self_referential_function_annotation.py similarity index 100% rename from crates/ty_project/resources/test/corpus/self_referential_function_annotation.py rename to crates/ty_python_semantic/resources/corpus/self_referential_function_annotation.py diff --git a/crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py b/crates/ty_python_semantic/resources/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py similarity index 100% rename from crates/ty_project/resources/test/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py rename to crates/ty_python_semantic/resources/corpus/sub_exprs_not_found_in_evaluate_expr_compare.py diff --git a/crates/ty_project/resources/test/corpus/ty_extensions.py b/crates/ty_python_semantic/resources/corpus/ty_extensions.py similarity index 100% rename from crates/ty_project/resources/test/corpus/ty_extensions.py rename to crates/ty_python_semantic/resources/corpus/ty_extensions.py diff --git a/crates/ty_project/tests/check.rs b/crates/ty_python_semantic/tests/corpus.rs similarity index 73% rename from crates/ty_project/tests/check.rs rename to crates/ty_python_semantic/tests/corpus.rs index 93a4f727fd3760..6880dac918ff9f 100644 --- a/crates/ty_project/tests/check.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -1,19 +1,20 @@ use anyhow::{Context, anyhow}; -use ruff_db::files::{File, system_path_to_file}; +use ruff_db::Upcast; +use ruff_db::files::{File, Files, system_path_to_file}; use ruff_db::parsed::parsed_module; -use ruff_db::system::{SystemPath, SystemPathBuf, TestSystem}; +use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem}; +use ruff_db::vendored::VendoredFileSystem; use ruff_python_ast::visitor::source_order; use ruff_python_ast::visitor::source_order::SourceOrderVisitor; use ruff_python_ast::{ - self as ast, Alias, Comprehension, Expr, Parameter, ParameterWithDefault, Stmt, + self as ast, Alias, Comprehension, Expr, Parameter, ParameterWithDefault, PythonVersion, Stmt, }; -use ty_project::{ProjectDatabase, ProjectMetadata}; -use ty_python_semantic::{HasType, SemanticModel}; -fn setup_db(project_root: &SystemPath, system: TestSystem) -> anyhow::Result { - let project = ProjectMetadata::discover(project_root, &system)?; - ProjectDatabase::new(project, system) -} +use ty_python_semantic::lint::{LintRegistry, RuleSelection}; +use ty_python_semantic::{ + Db, HasType, Program, ProgramSettings, PythonPlatform, PythonVersionSource, + PythonVersionWithSource, SearchPathSettings, SemanticModel, default_lint_registry, +}; fn get_cargo_workspace_root() -> anyhow::Result { Ok(SystemPathBuf::from(String::from_utf8( @@ -31,7 +32,7 @@ fn get_cargo_workspace_root() -> anyhow::Result { #[test] fn corpus_no_panic() -> anyhow::Result<()> { let crate_root = String::from(env!("CARGO_MANIFEST_DIR")); - run_corpus_tests(&format!("{crate_root}/resources/test/corpus/**/*.py")) + run_corpus_tests(&format!("{crate_root}/resources/corpus/**/*.py")) } #[test] @@ -78,11 +79,9 @@ fn typeshed_no_panic() -> anyhow::Result<()> { fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { let root = SystemPathBuf::from("/src"); - let system = TestSystem::default(); - let memory_fs = system.memory_file_system(); - memory_fs.create_directory_all(root.as_ref())?; - - let mut db = setup_db(&root, system.clone())?; + let mut db = CorpusDb::new(); + db.memory_file_system() + .create_directory_all(root.as_ref())?; let workspace_root = get_cargo_workspace_root()?; let workspace_root = workspace_root.to_string(); @@ -114,7 +113,8 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { let source = path.as_path(); let source_filename = source.file_name().unwrap(); - let code = std::fs::read_to_string(source)?; + let code = std::fs::read_to_string(source) + .with_context(|| format!("Failed to read test file: {path}"))?; let mut check_with_file_name = |path: &SystemPath| { if relative_path.file_name() == Some("types.pyi") { @@ -124,7 +124,7 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { return; } - memory_fs.write_file_all(path, &code).unwrap(); + db.memory_file_system().write_file_all(path, &code).unwrap(); File::sync_path(&mut db, path); // this test is only asserting that we can pull every expression type without a panic @@ -152,7 +152,7 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { ); } - memory_fs.remove_file(path).unwrap(); + db.memory_file_system().remove_file(path).unwrap(); file.sync(&mut db); }; @@ -174,10 +174,10 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { Ok(()) } -fn pull_types(db: &ProjectDatabase, file: File) { +fn pull_types(db: &dyn Db, file: File) { let mut visitor = PullTypesVisitor::new(db, file); - let ast = parsed_module(db, file).load(db); + let ast = parsed_module(db.upcast(), file).load(db.upcast()); visitor.visit_body(ast.suite()); } @@ -187,7 +187,7 @@ struct PullTypesVisitor<'db> { } impl<'db> PullTypesVisitor<'db> { - fn new(db: &'db ProjectDatabase, file: File) -> Self { + fn new(db: &'db dyn Db, file: File) -> Self { Self { model: SemanticModel::new(db, file), } @@ -304,3 +304,97 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // type alias, see https://github.com/astral-sh/ty/issues/256 ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py", true, true), ]; + +#[salsa::db] +#[derive(Clone)] +pub struct CorpusDb { + storage: salsa::Storage, + files: Files, + rule_selection: RuleSelection, + system: TestSystem, + vendored: VendoredFileSystem, +} + +impl CorpusDb { + #[expect(clippy::new_without_default)] + pub fn new() -> Self { + let db = Self { + storage: salsa::Storage::new(None), + system: TestSystem::default(), + vendored: ty_vendored::file_system().clone(), + rule_selection: RuleSelection::from_registry(default_lint_registry()), + files: Files::default(), + }; + + Program::from_settings( + &db, + ProgramSettings { + python_version: Some(PythonVersionWithSource { + version: PythonVersion::latest_ty(), + source: PythonVersionSource::default(), + }), + python_platform: PythonPlatform::default(), + search_paths: SearchPathSettings::new(vec![]), + }, + ) + .unwrap(); + + db + } +} + +impl DbWithTestSystem for CorpusDb { + fn test_system(&self) -> &TestSystem { + &self.system + } + + fn test_system_mut(&mut self) -> &mut TestSystem { + &mut self.system + } +} + +#[salsa::db] +impl ruff_db::Db for CorpusDb { + fn vendored(&self) -> &VendoredFileSystem { + &self.vendored + } + + fn system(&self) -> &dyn System { + &self.system + } + + fn files(&self) -> &Files { + &self.files + } + + fn python_version(&self) -> PythonVersion { + Program::get(self).python_version(self) + } +} + +impl Upcast for CorpusDb { + fn upcast(&self) -> &(dyn ruff_db::Db + 'static) { + self + } + fn upcast_mut(&mut self) -> &mut (dyn ruff_db::Db + 'static) { + self + } +} + +#[salsa::db] +impl ty_python_semantic::Db for CorpusDb { + fn is_file_open(&self, file: File) -> bool { + !file.path(self).is_vendored_path() + } + + fn rule_selection(&self) -> &RuleSelection { + &self.rule_selection + } + + fn lint_registry(&self) -> &LintRegistry { + default_lint_registry() + } +} + +#[salsa::db] +impl salsa::Database for CorpusDb {} From 3aae1cd59b6490c72af055d28add6cc2f983e545 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 11 Jun 2025 09:19:57 +0200 Subject: [PATCH 390/487] Fix incorrect salsa `return_ref` attribute (#18605) --- Cargo.lock | 44 ++++++++++++++------------ Cargo.toml | 14 +++----- crates/ty_python_semantic/src/types.rs | 1 - fuzz/Cargo.toml | 2 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16ac7635a1cfdc..ab0cce25b2eeae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -1118,10 +1106,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -1431,6 +1415,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -1758,6 +1751,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mimalloc" version = "0.1.46" @@ -3194,16 +3196,16 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" version = "0.22.0" -source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" +source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" dependencies = [ "boxcar", "compact_str", "crossbeam-queue", - "dashmap 6.1.0", - "hashbrown 0.14.5", + "crossbeam-utils", "hashbrown 0.15.4", "hashlink", "indexmap", + "intrusive-collections", "parking_lot", "portable-atomic", "rayon", @@ -3218,12 +3220,12 @@ dependencies = [ [[package]] name = "salsa-macro-rules" version = "0.22.0" -source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" +source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" [[package]] name = "salsa-macros" version = "0.22.0" -source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3" +source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 989b595f6affc1..743ab0658524fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "dc9066d66723d5842fcf78aa5725691a9168703f" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } @@ -165,7 +165,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = "env-filter", "fmt", "ansi", - "smallvec" + "smallvec", ] } tryfn = { version = "0.2.1" } typed-arena = { version = "2.0.2" } @@ -175,11 +175,7 @@ unicode-width = { version = "0.2.0" } unicode_names2 = { version = "1.2.2" } unicode-normalization = { version = "0.1.23" } url = { version = "2.5.0" } -uuid = { version = "1.6.1", features = [ - "v4", - "fast-rng", - "macro-diagnostics", -] } +uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] } walkdir = { version = "2.3.2" } wasm-bindgen = { version = "0.2.92" } wasm-bindgen-test = { version = "0.3.42" } @@ -214,8 +210,8 @@ must_use_candidate = "allow" similar_names = "allow" single_match_else = "allow" too_many_lines = "allow" -needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. -unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often. +needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. +unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often. # Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250 needless_raw_string_hashes = "allow" # Disallowed restriction lints diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6169de067e7356..80c7bd569bb943 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8120,7 +8120,6 @@ impl<'db> From> for Type<'db> { #[salsa::interned(debug)] pub struct BoundSuperType<'db> { pub pivot_class: ClassBase<'db>, - #[return_ref] pub owner: SuperOwnerKind<'db>, } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 0549d4f9b78c24..6d7febc4f25914 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "dc9066d66723d5842fcf78aa5725691a9168703f" } similar = { version = "2.5.0" } tracing = { version = "0.1.40" } From a863000cbc6a40d39553239ec33142245797bd89 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Wed, 11 Jun 2025 10:38:42 -0300 Subject: [PATCH 391/487] [`flake8-return`] Fix `RET504` autofix generating a syntax error (#18428) --- .../test/fixtures/flake8_return/RET504.py | 11 +++++ .../src/rules/flake8_return/rules/function.rs | 29 +++++------- ...lake8_return__tests__RET504_RET504.py.snap | 47 ++++++++++++++++++- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py index 91a60a7540dc94..fe9320bac15afc 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py @@ -421,3 +421,14 @@ def func(a: dict[str, int]) -> list[dict[str, int]]: if "services" in a: services = a["services"] return services + +# See: https://github.com/astral-sh/ruff/issues/18411 +def f(): + (#= + x) = 1 + return x + +def f(): + x = (1 + ) + return x diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 9a42a7497ca324..3ba3e975274a72 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -1,5 +1,3 @@ -use std::ops::Add; - use anyhow::Result; use ruff_macros::{ViolationMetadata, derive_message_formats}; @@ -8,6 +6,7 @@ use ruff_python_ast::stmt_if::elif_else_range; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{self as ast, Decorator, ElifElseClause, Expr, Stmt}; +use ruff_python_parser::TokenKind; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::visibility::is_property; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer, is_python_whitespace}; @@ -614,17 +613,17 @@ fn unnecessary_assign(checker: &Checker, stack: &Stack) { let delete_return = edits::delete_stmt(stmt, None, checker.locator(), checker.indexer()); - // Replace the `x = 1` statement with `return 1`. - let content = checker.locator().slice(assign); - let equals_index = content - .find('=') - .ok_or(anyhow::anyhow!("expected '=' in assignment statement"))?; - let after_equals = equals_index + 1; + let eq_token = checker + .tokens() + .before(assign.value.start()) + .iter() + .rfind(|token| token.kind() == TokenKind::Equal) + .unwrap(); + let content = checker.source(); + // Replace the `x = 1` statement with `return 1`. let replace_assign = Edit::range_replacement( - // If necessary, add whitespace after the `return` keyword. - // Ex) Convert `x=y` to `return y` (instead of `returny`). - if content[after_equals..] + if content[eq_token.end().to_usize()..] .chars() .next() .is_some_and(is_python_whitespace) @@ -635,13 +634,7 @@ fn unnecessary_assign(checker: &Checker, stack: &Stack) { }, // Replace from the start of the assignment statement to the end of the equals // sign. - TextRange::new( - assign.start(), - assign - .range() - .start() - .add(TextSize::try_from(after_equals)?), - ), + TextRange::new(assign.start(), eq_token.range().end()), ); Ok(Fix::unsafe_edits(replace_assign, [delete_return])) diff --git a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap index 8cf3328508b5db..c0edb6643fc6bc 100644 --- a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap +++ b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_return/mod.rs -snapshot_kind: text --- RET504.py:6:12: RET504 [*] Unnecessary assignment to `a` before `return` statement | @@ -248,6 +247,8 @@ RET504.py:423:16: RET504 [*] Unnecessary assignment to `services` before `return 422 | services = a["services"] 423 | return services | ^^^^^^^^ RET504 +424 | +425 | # See: https://github.com/astral-sh/ruff/issues/18411 | = help: Remove unnecessary assignment @@ -258,3 +259,47 @@ RET504.py:423:16: RET504 [*] Unnecessary assignment to `services` before `return 422 |- services = a["services"] 423 |- return services 422 |+ return a["services"] +424 423 | +425 424 | # See: https://github.com/astral-sh/ruff/issues/18411 +426 425 | def f(): + +RET504.py:429:12: RET504 [*] Unnecessary assignment to `x` before `return` statement + | +427 | (#= +428 | x) = 1 +429 | return x + | ^ RET504 +430 | +431 | def f(): + | + = help: Remove unnecessary assignment + +ℹ Unsafe fix +424 424 | +425 425 | # See: https://github.com/astral-sh/ruff/issues/18411 +426 426 | def f(): +427 |- (#= +428 |- x) = 1 +429 |- return x + 427 |+ return 1 +430 428 | +431 429 | def f(): +432 430 | x = (1 + +RET504.py:434:12: RET504 [*] Unnecessary assignment to `x` before `return` statement + | +432 | x = (1 +433 | ) +434 | return x + | ^ RET504 + | + = help: Remove unnecessary assignment + +ℹ Unsafe fix +429 429 | return x +430 430 | +431 431 | def f(): +432 |- x = (1 + 432 |+ return (1 +433 433 | ) +434 |- return x From e84406d8bee5c14b11c0b4fde4bc9ebf50c2346e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 11 Jun 2025 15:32:33 +0100 Subject: [PATCH 392/487] [ty] Infer the Python version from `--python=` on Unix (#18550) --- Cargo.lock | 1 + crates/ruff_python_ast/Cargo.toml | 1 + crates/ruff_python_ast/src/python_version.rs | 86 ++++++++------ crates/ty/docs/cli.md | 2 +- crates/ty/docs/configuration.md | 4 +- crates/ty/src/args.rs | 12 +- crates/ty/tests/cli/python_environment.rs | 104 +++++++++++++++++ crates/ty_project/src/metadata/options.rs | 4 +- .../src/module_resolver/resolver.rs | 65 +++++++++-- .../src/module_resolver/typeshed.rs | 110 ++++++------------ crates/ty_python_semantic/src/program.rs | 63 ++++++++-- .../src/util/diagnostics.rs | 17 +++ ty.schema.json | 2 +- 13 files changed, 334 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab0cce25b2eeae..6b0711fa69527e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2857,6 +2857,7 @@ dependencies = [ "salsa", "schemars", "serde", + "thiserror 2.0.12", ] [[package]] diff --git a/crates/ruff_python_ast/Cargo.toml b/crates/ruff_python_ast/Cargo.toml index 22f484fdbc0d1f..305d85097b2985 100644 --- a/crates/ruff_python_ast/Cargo.toml +++ b/crates/ruff_python_ast/Cargo.toml @@ -29,6 +29,7 @@ rustc-hash = { workspace = true } salsa = { workspace = true, optional = true } schemars = { workspace = true, optional = true } serde = { workspace = true, optional = true } +thiserror = { workspace = true } [features] schemars = ["dep:schemars"] diff --git a/crates/ruff_python_ast/src/python_version.rs b/crates/ruff_python_ast/src/python_version.rs index bc5a2689de1412..2a3b79594b4ac8 100644 --- a/crates/ruff_python_ast/src/python_version.rs +++ b/crates/ruff_python_ast/src/python_version.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, str::FromStr}; /// Representation of a Python version. /// @@ -97,18 +97,6 @@ impl Default for PythonVersion { } } -impl TryFrom<(&str, &str)> for PythonVersion { - type Error = std::num::ParseIntError; - - fn try_from(value: (&str, &str)) -> Result { - let (major, minor) = value; - Ok(Self { - major: major.parse()?, - minor: minor.parse()?, - }) - } -} - impl From<(u8, u8)> for PythonVersion { fn from(value: (u8, u8)) -> Self { let (major, minor) = value; @@ -123,6 +111,55 @@ impl fmt::Display for PythonVersion { } } +#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] +pub enum PythonVersionDeserializationError { + #[error("Invalid python version `{0}`: expected `major.minor`")] + WrongPeriodNumber(Box), + #[error("Invalid major version `{0}`: {1}")] + InvalidMajorVersion(Box, #[source] std::num::ParseIntError), + #[error("Invalid minor version `{0}`: {1}")] + InvalidMinorVersion(Box, #[source] std::num::ParseIntError), +} + +impl TryFrom<(&str, &str)> for PythonVersion { + type Error = PythonVersionDeserializationError; + + fn try_from(value: (&str, &str)) -> Result { + let (major, minor) = value; + Ok(Self { + major: major.parse().map_err(|err| { + PythonVersionDeserializationError::InvalidMajorVersion(Box::from(major), err) + })?, + minor: minor.parse().map_err(|err| { + PythonVersionDeserializationError::InvalidMinorVersion(Box::from(minor), err) + })?, + }) + } +} + +impl FromStr for PythonVersion { + type Err = PythonVersionDeserializationError; + + fn from_str(s: &str) -> Result { + let (major, minor) = s + .split_once('.') + .ok_or_else(|| PythonVersionDeserializationError::WrongPeriodNumber(Box::from(s)))?; + + Self::try_from((major, minor)).map_err(|err| { + // Give a better error message for something like `3.8.5` or `3..8` + if matches!( + err, + PythonVersionDeserializationError::InvalidMinorVersion(_, _) + ) && minor.contains('.') + { + PythonVersionDeserializationError::WrongPeriodNumber(Box::from(s)) + } else { + err + } + }) + } +} + #[cfg(feature = "serde")] mod serde { use super::PythonVersion; @@ -132,26 +169,9 @@ mod serde { where D: serde::Deserializer<'de>, { - let as_str = String::deserialize(deserializer)?; - - if let Some((major, minor)) = as_str.split_once('.') { - let major = major.parse().map_err(|err| { - serde::de::Error::custom(format!("invalid major version: {err}")) - })?; - let minor = minor.parse().map_err(|err| { - serde::de::Error::custom(format!("invalid minor version: {err}")) - })?; - - Ok((major, minor).into()) - } else { - let major = as_str.parse().map_err(|err| { - serde::de::Error::custom(format!( - "invalid python-version: {err}, expected: `major.minor`" - )) - })?; - - Ok((major, 0).into()) - } + String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom) } } diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index 719ebd3b9397d1..a68b04eb35459c 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -72,7 +72,7 @@ over all configuration files.

    This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

    --python-version, --target-version version

    Python version to assume when resolving types.

    The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.

    -

    By default, the Python version is inferred as the lower bound of the project's requires-python field from the pyproject.toml, if available. Otherwise, if a virtual environment has been configured or detected and a Python version can be inferred from the virtual environment's metadata, that version will be used. If neither of these applies, ty will fall back to the latest stable Python version supported by ty (currently 3.13).

    +

    If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the project.requires-python setting in a pyproject.toml file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13)

    Possible values:

    • 3.7
    • diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index fac5fbe108340c..a5333dc9314434 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -111,8 +111,8 @@ If a version is not specified, ty will try the following techniques in order of to determine a value: 1. Check for the `project.requires-python` setting in a `pyproject.toml` file and use the minimum version from the specified range -2. Check for an activated or configured virtual environment - and use the Python version of that environment +2. Check for an activated or configured Python environment + and attempt to infer the Python version of that environment 3. Fall back to the default value (see below) For some language features, ty can also understand conditionals based on comparisons diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index 21e44d9920e0be..8d02b941d89eca 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -82,11 +82,13 @@ pub(crate) struct CheckCommand { /// The Python version affects allowed syntax, type definitions of the standard library, and /// type definitions of first- and third-party modules that are conditional on the Python version. /// - /// By default, the Python version is inferred as the lower bound of the project's - /// `requires-python` field from the `pyproject.toml`, if available. Otherwise, if a virtual - /// environment has been configured or detected and a Python version can be inferred from the - /// virtual environment's metadata, that version will be used. If neither of these applies, ty - /// will fall back to the latest stable Python version supported by ty (currently 3.13). + /// If a version is not specified on the command line or in a configuration file, + /// ty will try the following techniques in order of preference to determine a value: + /// 1. Check for the `project.requires-python` setting in a `pyproject.toml` file + /// and use the minimum version from the specified range + /// 2. Check for an activated or configured Python environment + /// and attempt to infer the Python version of that environment + /// 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13) #[arg(long, value_name = "VERSION", alias = "target-version")] pub(crate) python_version: Option, diff --git a/crates/ty/tests/cli/python_environment.rs b/crates/ty/tests/cli/python_environment.rs index ab9192242d63c1..bcaad42d67bfaa 100644 --- a/crates/ty/tests/cli/python_environment.rs +++ b/crates/ty/tests/cli/python_environment.rs @@ -188,6 +188,110 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any Ok(()) } +/// This tests that, even if no Python *version* has been specified on the CLI or in a config file, +/// ty is still able to infer the Python version from a `--python` argument on the CLI, +/// *even if* the `--python` argument points to a system installation. +/// +/// We currently cannot infer the Python version from a system installation on Windows: +/// on Windows, we can only infer the Python version from a virtual environment. +/// This is because we use the layout of the Python installation to infer the Python version: +/// on Unix, the `site-packages` directory of an installation will be located at +/// `/lib/pythonX.Y/site-packages`. On Windows, however, the `site-packages` +/// directory will be located at `/Lib/site-packages`, which doesn't give us the +/// same information. +#[cfg(not(windows))] +#[test] +fn python_version_inferred_from_system_installation() -> anyhow::Result<()> { + let cpython_case = CliTest::with_files([ + ("pythons/Python3.8/bin/python", ""), + ("pythons/Python3.8/lib/python3.8/site-packages/foo.py", ""), + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(cpython_case.command().arg("--python").arg("pythons/Python3.8/bin/python"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of the layout of your Python installation + info: The primary `site-packages` directory of your installation was found at `lib/python3.8/site-packages/` + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + let pypy_case = CliTest::with_files([ + ("pythons/pypy3.8/bin/python", ""), + ("pythons/pypy3.8/lib/pypy3.8/site-packages/foo.py", ""), + ("test.py", "aiter"), + ])?; + + assert_cmd_snapshot!(pypy_case.command().arg("--python").arg("pythons/pypy3.8/bin/python"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `aiter` used when not defined + --> test.py:1:1 + | + 1 | aiter + | ^^^^^ + | + info: `aiter` was added as a builtin in Python 3.10 + info: Python 3.8 was assumed when resolving types because of the layout of your Python installation + info: The primary `site-packages` directory of your installation was found at `lib/pypy3.8/site-packages/` + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + let free_threaded_case = CliTest::with_files([ + ("pythons/Python3.13t/bin/python", ""), + ( + "pythons/Python3.13t/lib/python3.13t/site-packages/foo.py", + "", + ), + ("test.py", "import string.templatelib"), + ])?; + + assert_cmd_snapshot!(free_threaded_case.command().arg("--python").arg("pythons/Python3.13t/bin/python"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `string.templatelib` + --> test.py:1:8 + | + 1 | import string.templatelib + | ^^^^^^^^^^^^^^^^^^ + | + info: The stdlib module `string.templatelib` is only available on Python 3.14+ + info: Python 3.13 was assumed when resolving modules because of the layout of your Python installation + info: The primary `site-packages` directory of your installation was found at `lib/python3.13t/site-packages/` + info: No Python version was specified on the command line or in a configuration file + info: rule `unresolved-import` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + #[test] fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> { let case = CliTest::with_files([ diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 5be3629808c50c..50e07aad1d136b 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -322,8 +322,8 @@ pub struct EnvironmentOptions { /// to determine a value: /// 1. Check for the `project.requires-python` setting in a `pyproject.toml` file /// and use the minimum version from the specified range - /// 2. Check for an activated or configured virtual environment - /// and use the Python version of that environment + /// 2. Check for an activated or configured Python environment + /// and attempt to infer the Python version of that environment /// 3. Fall back to the default value (see below) /// /// For some language features, ty can also understand conditionals based on comparisons diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 621d94b73b0735..0a5a7eb8088a86 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; use std::fmt; use std::iter::FusedIterator; -use std::str::Split; +use std::str::{FromStr, Split}; +use camino::Utf8Component; use compact_str::format_compact; use rustc_hash::{FxBuildHasher, FxHashSet}; @@ -15,7 +16,9 @@ use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions}; use crate::site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin}; -use crate::{Program, PythonPath, PythonVersionWithSource, SearchPathSettings}; +use crate::{ + Program, PythonPath, PythonVersionSource, PythonVersionWithSource, SearchPathSettings, +}; use super::module::{Module, ModuleKind}; use super::path::{ModulePath, SearchPath, SearchPathValidationError}; @@ -155,10 +158,12 @@ pub struct SearchPaths { typeshed_versions: TypeshedVersions, - /// The Python version for the search paths, if any. + /// The Python version implied by the virtual environment. /// - /// This is read from the `pyvenv.cfg` if present. - python_version: Option, + /// If this environment was a system installation or the `pyvenv.cfg` file + /// of the virtual environment did not contain a `version` or `version_info` key, + /// this field will be `None`. + python_version_from_pyvenv_cfg: Option, } impl SearchPaths { @@ -304,7 +309,7 @@ impl SearchPaths { static_paths, site_packages, typeshed_versions, - python_version, + python_version_from_pyvenv_cfg: python_version, }) } @@ -330,8 +335,50 @@ impl SearchPaths { &self.typeshed_versions } - pub fn python_version(&self) -> Option<&PythonVersionWithSource> { - self.python_version.as_ref() + pub fn try_resolve_installation_python_version(&self) -> Option> { + if let Some(version) = self.python_version_from_pyvenv_cfg.as_ref() { + return Some(Cow::Borrowed(version)); + } + + if cfg!(windows) { + // The path to `site-packages` on Unix is + // `/lib/pythonX.Y/site-packages`, + // but on Windows it's `/Lib/site-packages`. + return None; + } + + let primary_site_packages = self.site_packages.first()?.as_system_path()?; + + let mut site_packages_ancestor_components = + primary_site_packages.components().rev().skip(1).map(|c| { + // This should have all been validated in `site_packages.rs` + // when we resolved the search paths for the project. + debug_assert!( + matches!(c, Utf8Component::Normal(_)), + "Unexpected component in site-packages path `{c:?}` \ + (expected `site-packages` to be an absolute path with symlinks resolved, \ + located at `/lib/pythonX.Y/site-packages`)" + ); + + c.as_str() + }); + + let parent_component = site_packages_ancestor_components.next()?; + + if site_packages_ancestor_components.next()? != "lib" { + return None; + } + + let version = parent_component + .strip_prefix("python") + .or_else(|| parent_component.strip_prefix("pypy"))? + .trim_end_matches('t'); + + let version = PythonVersion::from_str(version).ok()?; + let source = PythonVersionSource::InstallationDirectoryLayout { + site_packages_parent_dir: Box::from(parent_component), + }; + Some(Cow::Owned(PythonVersionWithSource { version, source })) } } @@ -351,7 +398,7 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec { static_paths, site_packages, typeshed_versions: _, - python_version: _, + python_version_from_pyvenv_cfg: _, } = Program::get(db).search_paths(db); let mut dynamic_paths = Vec::new(); diff --git a/crates/ty_python_semantic/src/module_resolver/typeshed.rs b/crates/ty_python_semantic/src/module_resolver/typeshed.rs index 850882106f4066..2d5ace3f2d99e3 100644 --- a/crates/ty_python_semantic/src/module_resolver/typeshed.rs +++ b/crates/ty_python_semantic/src/module_resolver/typeshed.rs @@ -4,7 +4,7 @@ use std::num::{NonZeroU16, NonZeroUsize}; use std::ops::{RangeFrom, RangeInclusive}; use std::str::FromStr; -use ruff_python_ast::PythonVersion; +use ruff_python_ast::{PythonVersion, PythonVersionDeserializationError}; use rustc_hash::FxHashMap; use crate::Program; @@ -49,55 +49,26 @@ impl fmt::Display for TypeshedVersionsParseError { impl std::error::Error for TypeshedVersionsParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - if let TypeshedVersionsParseErrorKind::IntegerParsingFailure { err, .. } = &self.reason { - Some(err) + if let TypeshedVersionsParseErrorKind::VersionParseError(err) = &self.reason { + err.source() } else { None } } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] pub(crate) enum TypeshedVersionsParseErrorKind { + #[error("File has too many lines ({0}); maximum allowed is {max_allowed}", max_allowed = NonZeroU16::MAX)] TooManyLines(NonZeroUsize), + #[error("Expected every non-comment line to have exactly one colon")] UnexpectedNumberOfColons, + #[error("Expected all components of '{0}' to be valid Python identifiers")] InvalidModuleName(String), + #[error("Expected every non-comment line to have exactly one '-' character")] UnexpectedNumberOfHyphens, - UnexpectedNumberOfPeriods(String), - IntegerParsingFailure { - version: String, - err: std::num::ParseIntError, - }, -} - -impl fmt::Display for TypeshedVersionsParseErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooManyLines(num_lines) => write!( - f, - "File has too many lines ({num_lines}); maximum allowed is {}", - NonZeroU16::MAX - ), - Self::UnexpectedNumberOfColons => { - f.write_str("Expected every non-comment line to have exactly one colon") - } - Self::InvalidModuleName(name) => write!( - f, - "Expected all components of '{name}' to be valid Python identifiers" - ), - Self::UnexpectedNumberOfHyphens => { - f.write_str("Expected every non-comment line to have exactly one '-' character") - } - Self::UnexpectedNumberOfPeriods(format) => write!( - f, - "Expected all versions to be in the form {{MAJOR}}.{{MINOR}}; got '{format}'" - ), - Self::IntegerParsingFailure { version, err } => write!( - f, - "Failed to convert '{version}' to a pair of integers due to {err}", - ), - } - } + #[error("{0}")] + VersionParseError(#[from] PythonVersionDeserializationError), } #[derive(Debug, PartialEq, Eq)] @@ -304,12 +275,12 @@ impl FromStr for PyVersionRange { let mut parts = s.split('-').map(str::trim); match (parts.next(), parts.next(), parts.next()) { (Some(lower), Some(""), None) => { - let lower = python_version_from_versions_file_string(lower)?; + let lower = PythonVersion::from_str(lower)?; Ok(Self::AvailableFrom(lower..)) } (Some(lower), Some(upper), None) => { - let lower = python_version_from_versions_file_string(lower)?; - let upper = python_version_from_versions_file_string(upper)?; + let lower = PythonVersion::from_str(lower)?; + let upper = PythonVersion::from_str(upper)?; Ok(Self::AvailableWithin(lower..=upper)) } _ => Err(TypeshedVersionsParseErrorKind::UnexpectedNumberOfHyphens), @@ -328,23 +299,6 @@ impl fmt::Display for PyVersionRange { } } -fn python_version_from_versions_file_string( - s: &str, -) -> Result { - let mut parts = s.split('.').map(str::trim); - let (Some(major), Some(minor), None) = (parts.next(), parts.next(), parts.next()) else { - return Err(TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods( - s.to_string(), - )); - }; - PythonVersion::try_from((major, minor)).map_err(|int_parse_error| { - TypeshedVersionsParseErrorKind::IntegerParsingFailure { - version: s.to_string(), - err: int_parse_error, - } - }) -} - #[cfg(test)] mod tests { use std::fmt::Write as _; @@ -681,15 +635,17 @@ foo: 3.8- # trailing comment TypeshedVersions::from_str("foo: 38-"), Err(TypeshedVersionsParseError { line_number: ONE, - reason: TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods("38".to_string()) + reason: TypeshedVersionsParseErrorKind::VersionParseError( + PythonVersionDeserializationError::WrongPeriodNumber(Box::from("38")) + ) }) ); assert_eq!( TypeshedVersions::from_str("foo: 3..8-"), Err(TypeshedVersionsParseError { line_number: ONE, - reason: TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods( - "3..8".to_string() + reason: TypeshedVersionsParseErrorKind::VersionParseError( + PythonVersionDeserializationError::WrongPeriodNumber(Box::from("3..8")) ) }) ); @@ -697,8 +653,8 @@ foo: 3.8- # trailing comment TypeshedVersions::from_str("foo: 3.8-3..11"), Err(TypeshedVersionsParseError { line_number: ONE, - reason: TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods( - "3..11".to_string() + reason: TypeshedVersionsParseErrorKind::VersionParseError( + PythonVersionDeserializationError::WrongPeriodNumber(Box::from("3..11")) ) }) ); @@ -708,20 +664,30 @@ foo: 3.8- # trailing comment fn invalid_typeshed_versions_non_digits() { let err = TypeshedVersions::from_str("foo: 1.two-").unwrap_err(); assert_eq!(err.line_number, ONE); - let TypeshedVersionsParseErrorKind::IntegerParsingFailure { version, err } = err.reason + let TypeshedVersionsParseErrorKind::VersionParseError( + PythonVersionDeserializationError::InvalidMinorVersion(invalid_minor, parse_error), + ) = err.reason else { - panic!() + panic!( + "Expected an invalid-minor-version parse error, got `{}`", + err.reason + ) }; - assert_eq!(version, "1.two".to_string()); - assert_eq!(*err.kind(), IntErrorKind::InvalidDigit); + assert_eq!(&*invalid_minor, "two"); + assert_eq!(*parse_error.kind(), IntErrorKind::InvalidDigit); let err = TypeshedVersions::from_str("foo: 3.8-four.9").unwrap_err(); assert_eq!(err.line_number, ONE); - let TypeshedVersionsParseErrorKind::IntegerParsingFailure { version, err } = err.reason + let TypeshedVersionsParseErrorKind::VersionParseError( + PythonVersionDeserializationError::InvalidMajorVersion(invalid_major, parse_error), + ) = err.reason else { - panic!() + panic!( + "Expected an invalid-major-version parse error, got `{}`", + err.reason + ) }; - assert_eq!(version, "four.9".to_string()); - assert_eq!(*err.kind(), IntErrorKind::InvalidDigit); + assert_eq!(&*invalid_major, "four"); + assert_eq!(*parse_error.kind(), IntErrorKind::InvalidDigit); } } diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 30523e345b240a..6facef1de71b0d 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::sync::Arc; use crate::Db; @@ -38,7 +39,7 @@ impl Program { .with_context(|| "Invalid search path settings")?; let python_version_with_source = - Self::resolve_python_version(python_version_with_source, search_paths.python_version()); + Self::resolve_python_version(python_version_with_source, &search_paths); tracing::info!( "Python version: Python {python_version}, platform: {python_platform}", @@ -58,10 +59,14 @@ impl Program { fn resolve_python_version( config_value: Option, - environment_value: Option<&PythonVersionWithSource>, + search_paths: &SearchPaths, ) -> PythonVersionWithSource { config_value - .or_else(|| environment_value.cloned()) + .or_else(|| { + search_paths + .try_resolve_installation_python_version() + .map(Cow::into_owned) + }) .unwrap_or_default() } @@ -79,7 +84,7 @@ impl Program { let search_paths = SearchPaths::from_settings(db, &search_paths)?; let new_python_version = - Self::resolve_python_version(python_version_with_source, search_paths.python_version()); + Self::resolve_python_version(python_version_with_source, &search_paths); if self.search_paths(db) != &search_paths { tracing::debug!("Updating search paths"); @@ -112,8 +117,11 @@ impl Program { let search_paths = SearchPaths::from_settings(db, search_path_settings)?; let current_python_version = self.python_version_with_source(db); - let python_version_from_environment = - search_paths.python_version().cloned().unwrap_or_default(); + + let python_version_from_environment = search_paths + .try_resolve_installation_python_version() + .map(Cow::into_owned) + .unwrap_or_default(); if current_python_version != &python_version_from_environment && current_python_version.source.priority() @@ -153,6 +161,13 @@ pub enum PythonVersionSource { /// The virtual environment might have been configured, activated or inferred. PyvenvCfgFile(PythonVersionFileSource), + /// Value inferred from the layout of the Python installation. + /// + /// This only ever applies on Unix. On Unix, the `site-packages` directory + /// will always be at `sys.prefix/lib/pythonX.Y/site-packages`, + /// so we can infer the Python version from the parent directory of `site-packages`. + InstallationDirectoryLayout { site_packages_parent_dir: Box }, + /// The value comes from a CLI argument, while it's left open if specified using a short argument, /// long argument (`--extra-paths`) or `--config key=value`. Cli, @@ -169,22 +184,26 @@ impl PythonVersionSource { PythonVersionSource::PyvenvCfgFile(_) => PythonSourcePriority::PyvenvCfgFile, PythonVersionSource::ConfigFile(_) => PythonSourcePriority::ConfigFile, PythonVersionSource::Cli => PythonSourcePriority::Cli, + PythonVersionSource::InstallationDirectoryLayout { .. } => { + PythonSourcePriority::InstallationDirectoryLayout + } } } } /// The priority in which Python version sources are considered. -/// A higher value means a higher priority. +/// The lower down the variant appears in this enum, the higher its priority. /// /// For example, if a Python version is specified in a pyproject.toml file /// but *also* via a CLI argument, the CLI argument will take precedence. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] #[cfg_attr(test, derive(strum_macros::EnumIter))] enum PythonSourcePriority { - Default = 0, - PyvenvCfgFile = 1, - ConfigFile = 2, - Cli = 3, + Default, + InstallationDirectoryLayout, + PyvenvCfgFile, + ConfigFile, + Cli, } /// Information regarding the file and [`TextRange`] of the configuration @@ -312,7 +331,9 @@ mod tests { match other { PythonSourcePriority::Cli => assert!(other > priority, "{other:?}"), PythonSourcePriority::ConfigFile => assert_eq!(priority, other), - PythonSourcePriority::PyvenvCfgFile | PythonSourcePriority::Default => { + PythonSourcePriority::PyvenvCfgFile + | PythonSourcePriority::Default + | PythonSourcePriority::InstallationDirectoryLayout => { assert!(priority > other, "{other:?}"); } } @@ -327,6 +348,24 @@ mod tests { assert!(other > priority, "{other:?}"); } PythonSourcePriority::PyvenvCfgFile => assert_eq!(priority, other), + PythonSourcePriority::Default + | PythonSourcePriority::InstallationDirectoryLayout => { + assert!(priority > other, "{other:?}"); + } + } + } + } + PythonSourcePriority::InstallationDirectoryLayout => { + for other in PythonSourcePriority::iter() { + match other { + PythonSourcePriority::Cli + | PythonSourcePriority::ConfigFile + | PythonSourcePriority::PyvenvCfgFile => { + assert!(other > priority, "{other:?}"); + } + PythonSourcePriority::InstallationDirectoryLayout => { + assert_eq!(priority, other); + } PythonSourcePriority::Default => assert!(priority > other, "{other:?}"), } } diff --git a/crates/ty_python_semantic/src/util/diagnostics.rs b/crates/ty_python_semantic/src/util/diagnostics.rs index c2419c2feb4f5b..05c7700e2ddcf2 100644 --- a/crates/ty_python_semantic/src/util/diagnostics.rs +++ b/crates/ty_python_semantic/src/util/diagnostics.rs @@ -61,6 +61,23 @@ pub fn add_inferred_python_version_hint_to_diagnostic( or in a configuration file", ); } + crate::PythonVersionSource::InstallationDirectoryLayout { + site_packages_parent_dir, + } => { + // TODO: it would also be nice to tell them how we resolved this Python installation... + diagnostic.info(format_args!( + "Python {version} was assumed when {action} \ + because of the layout of your Python installation" + )); + diagnostic.info(format_args!( + "The primary `site-packages` directory of your installation was found \ + at `lib/{site_packages_parent_dir}/site-packages/`" + )); + diagnostic.info( + "No Python version was specified on the command line \ + or in a configuration file", + ); + } crate::PythonVersionSource::Default => { diagnostic.info(format_args!( "Python {version} was assumed when {action} \ diff --git a/ty.schema.json b/ty.schema.json index 77c0194eea7961..fc6306d7ec224f 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -99,7 +99,7 @@ ] }, "python-version": { - "description": "Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. `\"3.0\"` or `\"3.6\"`). If a version is provided, ty will generate errors if the source code makes use of language features that are not supported in that version.\n\nIf a version is not specified, ty will try the following techniques in order of preference to determine a value: 1. Check for the `project.requires-python` setting in a `pyproject.toml` file and use the minimum version from the specified range 2. Check for an activated or configured virtual environment and use the Python version of that environment 3. Fall back to the default value (see below)\n\nFor some language features, ty can also understand conditionals based on comparisons with `sys.version_info`. These are commonly found in typeshed, for example, to reflect the differing contents of the standard library across Python versions.", + "description": "Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. `\"3.0\"` or `\"3.6\"`). If a version is provided, ty will generate errors if the source code makes use of language features that are not supported in that version.\n\nIf a version is not specified, ty will try the following techniques in order of preference to determine a value: 1. Check for the `project.requires-python` setting in a `pyproject.toml` file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the default value (see below)\n\nFor some language features, ty can also understand conditionals based on comparisons with `sys.version_info`. These are commonly found in typeshed, for example, to reflect the differing contents of the standard library across Python versions.", "anyOf": [ { "$ref": "#/definitions/PythonVersion" From 65f32edbc7de60c97816f8efe89069b60c24d23a Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 9 Jun 2025 14:13:41 -0400 Subject: [PATCH 393/487] [ty] Refactor covering node representation This commit doesn't change any functionality, but instead changes the representation of `CoveringNode` to make the implementation simpler (as well as planned future additions). By putting the found node last in the list of ancestors (now just generically called `nodes`), we reduce the amount of special case handling we need. The downside is that the representation now allows invalid states (a `CoveringNode` with no elements). But I think this is well mitigated by encapsulation. --- crates/ty_ide/src/find_node.rs | 43 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/crates/ty_ide/src/find_node.rs b/crates/ty_ide/src/find_node.rs index c1dfecc4c392db..a4b32b9a890298 100644 --- a/crates/ty_ide/src/find_node.rs +++ b/crates/ty_ide/src/find_node.rs @@ -49,48 +49,41 @@ pub(crate) fn covering_node(root: AnyNodeRef, range: TextRange) -> CoveringNode }; root.visit_source_order(&mut visitor); - - let minimal = visitor.ancestors.pop().unwrap_or(root); + if visitor.ancestors.is_empty() { + visitor.ancestors.push(root); + } CoveringNode { - node: minimal, - ancestors: visitor.ancestors, + nodes: visitor.ancestors, } } /// The node with a minimal range that fully contains the search range. pub(crate) struct CoveringNode<'a> { - /// The node with a minimal range that fully contains the search range. - node: AnyNodeRef<'a>, - - /// The node's ancestor (the spine up to the root). - ancestors: Vec>, + /// The covering node, along with all of its ancestors up to the + /// root. The root is always the first element and the covering + /// node found is always the last node. This sequence is guaranteed + /// to be non-empty. + nodes: Vec>, } impl<'a> CoveringNode<'a> { + /// Returns the covering node found. pub(crate) fn node(&self) -> AnyNodeRef<'a> { - self.node + *self.nodes.last().unwrap() } /// Returns the node's parent. pub(crate) fn parent(&self) -> Option> { - self.ancestors.last().copied() + let penultimate = self.nodes.len().checked_sub(2)?; + self.nodes.get(penultimate).copied() } /// Finds the minimal node that fully covers the range and fulfills the given predicate. pub(crate) fn find(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result { - if f(self.node) { - return Ok(self); - } - - match self.ancestors.iter().rposition(|node| f(*node)) { + match self.nodes.iter().rposition(|node| f(*node)) { Some(index) => { - let node = self.ancestors[index]; - self.ancestors.truncate(index); - - Ok(Self { - node, - ancestors: self.ancestors, - }) + self.nodes.truncate(index + 1); + Ok(self) } None => Err(self), } @@ -99,8 +92,6 @@ impl<'a> CoveringNode<'a> { impl fmt::Debug for CoveringNode<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("NodeWithAncestors") - .field(&self.node) - .finish() + f.debug_tuple("CoveringNode").field(&self.node()).finish() } } From 8fdf3fc47fda82330785723903e5d57e465c5ca4 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 9 Jun 2025 15:03:14 -0400 Subject: [PATCH 394/487] [ty] Add `CoveringNode::find_last` This routine lets us climb up the AST tree when we find a contiguous sequence of nodes that satisfy our predicate. This will be useful for making things like `a.b.` work. That is, we don't want the `ExprAttribute` closest to a leaf. We also don't always want the `ExprAttribute` closest to the root. Rather, (I think) we want the `ExprAttribute` closest to the root that has an unbroken chain to the `ExprAttribute` closest to the leaf. --- crates/ty_ide/src/find_node.rs | 45 ++++++++++++++++++++++++++++------ crates/ty_ide/src/goto.rs | 2 +- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/crates/ty_ide/src/find_node.rs b/crates/ty_ide/src/find_node.rs index a4b32b9a890298..db993bdc7d3e17 100644 --- a/crates/ty_ide/src/find_node.rs +++ b/crates/ty_ide/src/find_node.rs @@ -78,15 +78,44 @@ impl<'a> CoveringNode<'a> { self.nodes.get(penultimate).copied() } - /// Finds the minimal node that fully covers the range and fulfills the given predicate. - pub(crate) fn find(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result { - match self.nodes.iter().rposition(|node| f(*node)) { - Some(index) => { - self.nodes.truncate(index + 1); - Ok(self) - } - None => Err(self), + /// Finds the first node that fully covers the range and fulfills + /// the given predicate. + /// + /// The "first" here means that the node closest to a leaf is + /// returned. + pub(crate) fn find_first(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result { + let Some(index) = self.find_first_index(f) else { + return Err(self); + }; + self.nodes.truncate(index + 1); + Ok(self) + } + + /// Finds the last node that fully covers the range and fulfills + /// the given predicate. + /// + /// The "last" here means that after finding the "first" such node, + /// the highest ancestor found satisfying the given predicate is + /// returned. Note that this is *not* the same as finding the node + /// closest to the root that satisfies the given predictate. + pub(crate) fn find_last(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result { + let Some(mut index) = self.find_first_index(&f) else { + return Err(self); + }; + while index > 0 && f(self.nodes[index - 1]) { + index -= 1; } + self.nodes.truncate(index + 1); + Ok(self) + } + + /// Finds the index of the node that fully covers the range and + /// fulfills the given predicate. + /// + /// If there are no nodes matching the given predictate, then + /// `None` is returned. + fn find_first_index(&self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Option { + self.nodes.iter().rposition(|node| f(*node)) } } diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index 89861b73ff976b..80d26f8f7d53a0 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -200,7 +200,7 @@ pub(crate) fn find_goto_target( })?; let covering_node = covering_node(parsed.syntax().into(), token.range()) - .find(|node| node.is_identifier() || node.is_expression()) + .find_first(|node| node.is_identifier() || node.is_expression()) .ok()?; tracing::trace!("Covering node is of kind {:?}", covering_node.node().kind()); From 7893cf9fe126a692dcce71470b3b6aed98375378 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 5 Jun 2025 15:02:17 -0400 Subject: [PATCH 395/487] [ty] Improve support for `object.` completions This makes it work for a number of additional cases, like nested attribute access and things like `[].`. The basic idea is that instead of selecting a covering node closest to a leaf that contains the cursor, we walk up the tree as much as we can. This lets us access the correct `ExprAttribute` node when performing nested access. --- crates/ty_ide/src/completion.rs | 440 +++++++++++++++++++++++++++++--- crates/ty_ide/src/find_node.rs | 5 +- 2 files changed, 412 insertions(+), 33 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 10bc2971d1307b..9581a719939f7d 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -17,7 +17,10 @@ pub struct Completion { pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec { let parsed = parsed_module(db.upcast(), file).load(db.upcast()); - let Some(target) = CompletionTargetTokens::find(&parsed, offset).ast(&parsed) else { + let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else { + return vec![]; + }; + let Some(target) = target_token.ast(&parsed, offset) else { return vec![]; }; @@ -41,7 +44,14 @@ enum CompletionTargetTokens<'t> { /// `attribute` may be empty. /// /// This requires a name token followed by a dot token. - ObjectDot { + /// + /// This is "possibly" an `object.attribute` because + /// the object token may not correspond to an object + /// or it may correspond to *part* of an object. + /// This is resolved when we try to find an overlapping + /// AST `ExprAttribute` node later. If we couldn't, then + /// this is probably not an `object.attribute`. + PossibleObjectDot { /// The token preceding the dot. object: &'t Token, /// The token, if non-empty, following the dot. @@ -63,45 +73,79 @@ enum CompletionTargetTokens<'t> { impl<'t> CompletionTargetTokens<'t> { /// Look for the best matching token pattern at the given offset. - fn find(parsed: &ParsedModuleRef, offset: TextSize) -> CompletionTargetTokens<'_> { - static OBJECT_DOT_EMPTY: [TokenKind; 2] = [TokenKind::Name, TokenKind::Dot]; - static OBJECT_DOT_NON_EMPTY: [TokenKind; 3] = - [TokenKind::Name, TokenKind::Dot, TokenKind::Name]; + fn find(parsed: &ParsedModuleRef, offset: TextSize) -> Option> { + static OBJECT_DOT_EMPTY: [TokenKind; 1] = [TokenKind::Dot]; + static OBJECT_DOT_NON_EMPTY: [TokenKind; 2] = [TokenKind::Dot, TokenKind::Name]; let offset = match parsed.tokens().at_offset(offset) { - TokenAt::None => return CompletionTargetTokens::Unknown { offset }, + TokenAt::None => return Some(CompletionTargetTokens::Unknown { offset }), TokenAt::Single(tok) => tok.end(), TokenAt::Between(_, tok) => tok.start(), }; let before = parsed.tokens().before(offset); - if let Some([object, _dot]) = token_suffix_by_kinds(before, OBJECT_DOT_EMPTY) { - CompletionTargetTokens::ObjectDot { - object, - attribute: None, - } - } else if let Some([object, _dot, attribute]) = - token_suffix_by_kinds(before, OBJECT_DOT_NON_EMPTY) - { - CompletionTargetTokens::ObjectDot { - object, - attribute: Some(attribute), - } - } else { - let Some(last) = before.last() else { - return CompletionTargetTokens::Unknown { offset }; - }; - CompletionTargetTokens::Generic { token: last } - } + Some( + // Our strategy when it comes to `object.attribute` here is + // to look for the `.` and then take the token immediately + // preceding it. Later, we look for an `ExprAttribute` AST + // node that overlaps (even partially) with this token. And + // that's the object we try to complete attributes for. + if let Some([_dot]) = token_suffix_by_kinds(before, OBJECT_DOT_EMPTY) { + let object = before[..before.len() - 1].last()?; + CompletionTargetTokens::PossibleObjectDot { + object, + attribute: None, + } + } else if let Some([_dot, attribute]) = + token_suffix_by_kinds(before, OBJECT_DOT_NON_EMPTY) + { + let object = before[..before.len() - 2].last()?; + CompletionTargetTokens::PossibleObjectDot { + object, + attribute: Some(attribute), + } + } else if let Some([_]) = token_suffix_by_kinds(before, [TokenKind::Float]) { + // If we're writing a `float`, then we should + // specifically not offer completions. This wouldn't + // normally be an issue, but if completions are + // automatically triggered by a `.` (which is what we + // request as an LSP server), then we can get here + // in the course of just writing a decimal number. + return None; + } else if let Some([_]) = token_suffix_by_kinds(before, [TokenKind::Ellipsis]) { + // Similarly as above. If we've just typed an ellipsis, + // then we shouldn't show completions. Note that + // this doesn't prevent `....` from showing + // completions (which would be the attributes available + // on an `ellipsis` object). + return None; + } else { + let Some(last) = before.last() else { + return Some(CompletionTargetTokens::Unknown { offset }); + }; + CompletionTargetTokens::Generic { token: last } + }, + ) } /// Returns a corresponding AST node for these tokens. /// + /// `offset` should be the offset of the cursor. + /// /// If no plausible AST node could be found, then `None` is returned. - fn ast(&self, parsed: &'t ParsedModuleRef) -> Option> { + fn ast( + &self, + parsed: &'t ParsedModuleRef, + offset: TextSize, + ) -> Option> { match *self { - CompletionTargetTokens::ObjectDot { object, .. } => { + CompletionTargetTokens::PossibleObjectDot { object, .. } => { let covering_node = covering_node(parsed.syntax().into(), object.range()) - .find(|node| node.is_expr_attribute()) + // We require that the end of the node range not + // exceed the cursor offset. This avoids selecting + // a node "too high" in the AST in cases where + // completions are requested in the middle of an + // expression. e.g., `foo..bar`. + .find_last(|node| node.is_expr_attribute() && node.range().end() <= offset) .ok()?; match covering_node.node() { ast::AnyNodeRef::ExprAttribute(expr) => { @@ -1207,7 +1251,119 @@ Re } #[test] - fn nested_attribute_access() { + fn attribute_access_empty_list() { + let test = cursor_test( + "\ +[]. +", + ); + + test.assert_completions_include("append"); + } + + #[test] + fn attribute_access_empty_dict() { + let test = cursor_test( + "\ +{}. +", + ); + + test.assert_completions_include("values"); + test.assert_completions_do_not_include("add"); + } + + #[test] + fn attribute_access_set() { + let test = cursor_test( + "\ +{1}. +", + ); + + test.assert_completions_include("add"); + test.assert_completions_do_not_include("values"); + } + + #[test] + fn attribute_parens() { + let test = cursor_test( + "\ +class A: + x: str + +a = A() +(a). +", + ); + + test.assert_completions_include("x"); + } + + #[test] + fn attribute_double_parens() { + let test = cursor_test( + "\ +class A: + x: str + +a = A() +((a)). +", + ); + + test.assert_completions_include("x"); + } + + #[test] + fn attribute_on_constructor_directly() { + let test = cursor_test( + "\ +class A: + x: str + +A(). +", + ); + + test.assert_completions_include("x"); + } + + #[test] + fn attribute_not_on_integer() { + let test = cursor_test( + "\ +3. +", + ); + + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn attribute_on_integer() { + let test = cursor_test( + "\ +(3). +", + ); + + test.assert_completions_include("bit_length"); + } + + #[test] + fn attribute_on_float() { + let test = cursor_test( + "\ +3.14. +", + ); + + test.assert_completions_include("conjugate"); + } + + #[test] + fn nested_attribute_access1() { let test = cursor_test( "\ class A: @@ -1221,9 +1377,229 @@ b.a. ", ); - // FIXME: These should be flipped. - test.assert_completions_include("a"); - test.assert_completions_do_not_include("x"); + test.assert_completions_do_not_include("a"); + test.assert_completions_include("x"); + } + + #[test] + fn nested_attribute_access2() { + let test = cursor_test( + "\ +class B: + c: int + +class A: + b: B + +a = A() +([1] + [a.b.] + [3]).pop() +", + ); + + test.assert_completions_include("c"); + test.assert_completions_do_not_include("b"); + test.assert_completions_do_not_include("pop"); + } + + #[test] + fn nested_attribute_access3() { + let test = cursor_test( + "\ +a = A() +([1] + [\"abc\".] + [3]).pop() +", + ); + + test.assert_completions_include("capitalize"); + test.assert_completions_do_not_include("append"); + test.assert_completions_do_not_include("pop"); + } + + #[test] + fn nested_attribute_access4() { + let test = cursor_test( + "\ +class B: + c: int + +class A: + b: B + +def foo() -> A: + return A() + +foo(). +", + ); + + test.assert_completions_include("b"); + test.assert_completions_do_not_include("c"); + } + + #[test] + fn nested_attribute_access5() { + let test = cursor_test( + "\ +class B: + c: int + +class A: + b: B + +def foo() -> A: + return A() + +foo().b. +", + ); + + test.assert_completions_include("c"); + test.assert_completions_do_not_include("b"); + } + + #[test] + fn betwixt_attribute_access1() { + let test = cursor_test( + "\ +class Foo: + xyz: str + +class Bar: + foo: Foo + +class Quux: + bar: Bar + +quux = Quux() +quux..foo.xyz +", + ); + + test.assert_completions_include("bar"); + test.assert_completions_do_not_include("xyz"); + test.assert_completions_do_not_include("foo"); + } + + #[test] + fn betwixt_attribute_access2() { + let test = cursor_test( + "\ +class Foo: + xyz: str + +class Bar: + foo: Foo + +class Quux: + bar: Bar + +quux = Quux() +quux.b.foo.xyz +", + ); + + test.assert_completions_include("bar"); + test.assert_completions_do_not_include("xyz"); + test.assert_completions_do_not_include("foo"); + } + + #[test] + fn betwixt_attribute_access3() { + let test = cursor_test( + "\ +class Foo: + xyz: str + +class Bar: + foo: Foo + +class Quux: + bar: Bar + +quux = Quux() +.foo.xyz +", + ); + + test.assert_completions_include("quux"); + } + + #[test] + fn betwixt_attribute_access4() { + let test = cursor_test( + "\ +class Foo: + xyz: str + +class Bar: + foo: Foo + +class Quux: + bar: Bar + +quux = Quux() +q.foo.xyz +", + ); + + test.assert_completions_include("quux"); + } + + #[test] + fn ellipsis1() { + let test = cursor_test( + "\ +... +", + ); + + assert_snapshot!(test.completions(), @""); + } + + #[test] + fn ellipsis2() { + let test = cursor_test( + "\ +.... +", + ); + + assert_snapshot!(test.completions(), @r" + __annotations__ + __class__ + __delattr__ + __dict__ + __dir__ + __doc__ + __eq__ + __format__ + __getattribute__ + __getstate__ + __hash__ + __init__ + __init_subclass__ + __module__ + __ne__ + __new__ + __reduce__ + __reduce_ex__ + __repr__ + __setattr__ + __sizeof__ + __str__ + __subclasshook__ + "); + } + + #[test] + fn ellipsis3() { + let test = cursor_test( + "\ +class Foo: ... +", + ); + + assert_snapshot!(test.completions(), @""); } #[test] diff --git a/crates/ty_ide/src/find_node.rs b/crates/ty_ide/src/find_node.rs index db993bdc7d3e17..7e8dec4b1b575d 100644 --- a/crates/ty_ide/src/find_node.rs +++ b/crates/ty_ide/src/find_node.rs @@ -69,7 +69,10 @@ pub(crate) struct CoveringNode<'a> { impl<'a> CoveringNode<'a> { /// Returns the covering node found. pub(crate) fn node(&self) -> AnyNodeRef<'a> { - *self.nodes.last().unwrap() + *self + .nodes + .last() + .expect("`CoveringNode::nodes` should always be non-empty") } /// Returns the node's parent. From 1a3befe8d639aba12e666fd8f3e7cc96bb666197 Mon Sep 17 00:00:00 2001 From: justin Date: Wed, 11 Jun 2025 23:50:37 -0400 Subject: [PATCH 396/487] [ty] Update mypy_primer doc (#18638) ## Summary Minor documentation update to make `mypy_primer` instructions a bit more verbose/helpful for running against a local branch ## Test Plan N/A --- crates/ty/docs/mypy_primer.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/ty/docs/mypy_primer.md b/crates/ty/docs/mypy_primer.md index 95345c6282beea..26cc17c23874cb 100644 --- a/crates/ty/docs/mypy_primer.md +++ b/crates/ty/docs/mypy_primer.md @@ -51,10 +51,19 @@ If you are working on a local branch, you can use `mypy_primer`'s `--repo` optio This allows `mypy_primer` to check out local branches: ```sh -mypy_primer --repo /path/to/ruff --old origin/main --new my/local-branch … +mypy_primer --type-checker ty \ + --repo ./ruff \ + --old main \ + --new my/local-branch \ + --project-selector '/beartype$' \ + --debug \ + --output concise ``` -Note that you might need to clean up `/tmp/mypy_primer` in order for this to work correctly. +Notes: + +- You might need to clean up `/tmp/mypy_primer` in order for this to work correctly. +- This must be run from _outside_ the `ruff` repo. [full list of ecosystem projects]: https://github.com/hauntsaninja/mypy_primer/blob/master/mypy_primer/projects.py [tool executables]: https://docs.astral.sh/uv/concepts/tools/#tool-executables From 65a2c6d4eb70bf5863854ca38a712f3b8379a9ee Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 12 Jun 2025 01:17:00 -0400 Subject: [PATCH 397/487] Update salsa (#18636) --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b0711fa69527e..d3a87602003c74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "boxcar" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bb12751a83493ef4b8da1120451a262554e216a247f14b48cb5e8fe7ed8bdf" +checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa" [[package]] name = "bstr" @@ -3197,7 +3197,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" +source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d" dependencies = [ "boxcar", "compact_str", @@ -3221,12 +3221,12 @@ dependencies = [ [[package]] name = "salsa-macro-rules" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" +source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d" [[package]] name = "salsa-macros" version = "0.22.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=dc9066d66723d5842fcf78aa5725691a9168703f#dc9066d66723d5842fcf78aa5725691a9168703f" +source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 743ab0658524fa..d86984336d4cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "dc9066d66723d5842fcf78aa5725691a9168703f" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "09627e450566f894956710a3fd923dc80462ae6d" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6d7febc4f25914..e5b2572349d885 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "dc9066d66723d5842fcf78aa5725691a9168703f" } +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "09627e450566f894956710a3fd923dc80462ae6d" } similar = { version = "2.5.0" } tracing = { version = "0.1.40" } From f74527f4e9d7e25badb08665868a8fb6a5a8ee01 Mon Sep 17 00:00:00 2001 From: Jia Chen Date: Wed, 11 Jun 2025 23:20:14 -0700 Subject: [PATCH 398/487] `SourceOrderVisitor` should visit the `Identifier` part of the `PatternKeyword` node (#18635) --- crates/ruff_python_ast/src/visitor/source_order.rs | 2 +- .../tests/snapshots/source_order__match_class_pattern.snap | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ruff_python_ast/src/visitor/source_order.rs b/crates/ruff_python_ast/src/visitor/source_order.rs index af57ee48dbdcbc..9e42766f0d4ac8 100644 --- a/crates/ruff_python_ast/src/visitor/source_order.rs +++ b/crates/ruff_python_ast/src/visitor/source_order.rs @@ -501,7 +501,7 @@ where { let node = AnyNodeRef::from(pattern_keyword); if visitor.enter_node(node).is_traverse() { - visitor.visit_pattern(&pattern_keyword.pattern); + pattern_keyword.visit_source_order(visitor); } visitor.leave_node(node); } diff --git a/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__match_class_pattern.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__match_class_pattern.snap index ddd8b0c3d10791..6a2e1f07f5f872 100644 --- a/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__match_class_pattern.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/source_order__match_class_pattern.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_ast_integration_tests/tests/source_order.rs expression: trace -snapshot_kind: text --- - ModModule - StmtMatch @@ -21,12 +20,15 @@ snapshot_kind: text - ExprName - PatternArguments - PatternKeyword + - Identifier - PatternMatchValue - ExprNumberLiteral - PatternKeyword + - Identifier - PatternMatchValue - ExprNumberLiteral - PatternKeyword + - Identifier - PatternMatchValue - ExprNumberLiteral - StmtExpr From ef4108af2aeb55d201b5e815e8102b2383cd1ef6 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 12 Jun 2025 12:06:16 +0530 Subject: [PATCH 399/487] [ty] Generate the top and bottom materialization of a type (#18594) ## Summary This is to support https://github.com/astral-sh/ruff/pull/18607. This PR adds support for generating the top materialization (or upper bound materialization) and the bottom materialization (or lower bound materialization) of a type. This is the most general and the most specific form of the type which is fully static, respectively. More concretely, `T'`, the top materialization of `T`, is the type `T` with all occurrences of dynamic type (`Any`, `Unknown`, `@Todo`) replaced as follows: - In covariant position, it's replaced with `object` - In contravariant position, it's replaced with `Never` - In invariant position, it's replaced with an unresolved type variable (For an invariant position, it should actually be replaced with an existential type, but this is not currently representable in our type system, so we use an unresolved type variable for now instead.) The bottom materialization is implemented in the same way, except we start out in "contravariant" position. ## Test Plan Add test cases for various types. --- .../mdtest/type_properties/materialization.md | 418 ++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 183 ++++++++ .../ty_python_semantic/src/types/call/bind.rs | 12 + crates/ty_python_semantic/src/types/class.rs | 16 + .../ty_python_semantic/src/types/function.rs | 8 + .../ty_python_semantic/src/types/generics.rs | 19 + .../ty_python_semantic/src/types/instance.rs | 22 +- .../src/types/property_tests.rs | 16 + .../src/types/protocol_class.rs | 24 + .../src/types/signatures.rs | 47 +- .../src/types/subclass_of.rs | 30 ++ .../ty_extensions/ty_extensions.pyi | 6 + 12 files changed, 798 insertions(+), 3 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md new file mode 100644 index 00000000000000..638ad5ff20887d --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md @@ -0,0 +1,418 @@ +# Materialization + +There are two materializations of a type: + +- The top materialization (or upper bound materialization) of a type, which is the most general form + of that type that is fully static +- The bottom materialization (or lower bound materialization) of a type, which is the most specific + form of that type that is fully static + +More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of `Any` and +`Unknown` replaced as follows: + +- In covariant position, it's replaced with `object` +- In contravariant position, it's replaced with `Never` +- In invariant position, it's replaced with an unresolved type variable + +The top materialization starts from the covariant position while the bottom materialization starts +from the contravariant position. + +TODO: For an invariant position, e.g. `list[Any]`, it should be replaced with an existential type +representing "all lists, containing any type". We currently represent this by replacing `Any` in +invariant position with an unresolved type variable. + +## Replacement rules + +### Top materialization + +The dynamic type at the top-level is replaced with `object`. + +```py +from typing import Any, Callable +from ty_extensions import Unknown, top_materialization + +reveal_type(top_materialization(Any)) # revealed: object +reveal_type(top_materialization(Unknown)) # revealed: object +``` + +The contravariant position is replaced with `Never`. + +```py +reveal_type(top_materialization(Callable[[Any], None])) # revealed: (Never, /) -> None +``` + +The invariant position is replaced with an unresolved type variable. + +```py +reveal_type(top_materialization(list[Any])) # revealed: list[T_all] +``` + +### Bottom materialization + +The dynamic type at the top-level is replaced with `Never`. + +```py +from typing import Any, Callable +from ty_extensions import Unknown, bottom_materialization + +reveal_type(bottom_materialization(Any)) # revealed: Never +reveal_type(bottom_materialization(Unknown)) # revealed: Never +``` + +The contravariant position is replaced with `object`. + +```py +# revealed: (object, object, /) -> None +reveal_type(bottom_materialization(Callable[[Any, Unknown], None])) +``` + +The invariant position is replaced in the same way as the top materialization, with an unresolved +type variable. + +```py +reveal_type(bottom_materialization(list[Any])) # revealed: list[T_all] +``` + +## Fully static types + +The top / bottom (and only) materialization of any fully static type is just itself. + +```py +from typing import Any, Literal +from ty_extensions import TypeOf, bottom_materialization, top_materialization + +reveal_type(top_materialization(int)) # revealed: int +reveal_type(bottom_materialization(int)) # revealed: int + +reveal_type(top_materialization(Literal[1])) # revealed: Literal[1] +reveal_type(bottom_materialization(Literal[1])) # revealed: Literal[1] + +reveal_type(top_materialization(Literal[True])) # revealed: Literal[True] +reveal_type(bottom_materialization(Literal[True])) # revealed: Literal[True] + +reveal_type(top_materialization(Literal["abc"])) # revealed: Literal["abc"] +reveal_type(bottom_materialization(Literal["abc"])) # revealed: Literal["abc"] + +reveal_type(top_materialization(int | str)) # revealed: int | str +reveal_type(bottom_materialization(int | str)) # revealed: int | str +``` + +We currently treat function literals as fully static types, so they remain unchanged even though the +signature might have `Any` in it. (TODO: this is probably not right.) + +```py +def function(x: Any) -> None: ... + +class A: + def method(self, x: Any) -> None: ... + +reveal_type(top_materialization(TypeOf[function])) # revealed: def function(x: Any) -> None +reveal_type(bottom_materialization(TypeOf[function])) # revealed: def function(x: Any) -> None + +reveal_type(top_materialization(TypeOf[A().method])) # revealed: bound method A.method(x: Any) -> None +reveal_type(bottom_materialization(TypeOf[A().method])) # revealed: bound method A.method(x: Any) -> None +``` + +## Callable + +For a callable, the parameter types are in a contravariant position, and the return type is in a +covariant position. + +```py +from typing import Any, Callable +from ty_extensions import TypeOf, Unknown, bottom_materialization, top_materialization + +def _(callable: Callable[[Any, Unknown], Any]) -> None: + # revealed: (Never, Never, /) -> object + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (object, object, /) -> Never + reveal_type(bottom_materialization(TypeOf[callable])) +``` + +The parameter types in a callable inherits the contravariant position. + +```py +def _(callable: Callable[[int, tuple[int | Any]], tuple[Any]]) -> None: + # revealed: (int, tuple[int], /) -> tuple[object] + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (int, tuple[object], /) -> Never + reveal_type(bottom_materialization(TypeOf[callable])) +``` + +But, if the callable itself is in a contravariant position, then the variance is flipped i.e., if +the outer variance is covariant, it's flipped to contravariant, and if it's contravariant, it's +flipped to covariant, invariant remains invariant. + +```py +def _(callable: Callable[[Any, Callable[[Unknown], Any]], Callable[[Any, int], Any]]) -> None: + # revealed: (Never, (object, /) -> Never, /) -> (Never, int, /) -> object + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (object, (Never, /) -> object, /) -> (object, int, /) -> Never + reveal_type(bottom_materialization(TypeOf[callable])) +``` + +## Tuple + +All positions in a tuple are covariant. + +```py +from typing import Any +from ty_extensions import Unknown, bottom_materialization, top_materialization + +reveal_type(top_materialization(tuple[Any, int])) # revealed: tuple[object, int] +reveal_type(bottom_materialization(tuple[Any, int])) # revealed: Never + +reveal_type(top_materialization(tuple[Unknown, int])) # revealed: tuple[object, int] +reveal_type(bottom_materialization(tuple[Unknown, int])) # revealed: Never + +reveal_type(top_materialization(tuple[Any, int, Unknown])) # revealed: tuple[object, int, object] +reveal_type(bottom_materialization(tuple[Any, int, Unknown])) # revealed: Never +``` + +Except for when the tuple itself is in a contravariant position, then all positions in the tuple +inherit the contravariant position. + +```py +from typing import Callable +from ty_extensions import TypeOf + +def _(callable: Callable[[tuple[Any, int], tuple[str, Unknown]], None]) -> None: + # revealed: (Never, Never, /) -> None + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (tuple[object, int], tuple[str, object], /) -> None + reveal_type(bottom_materialization(TypeOf[callable])) +``` + +And, similarly for an invariant position. + +```py +reveal_type(top_materialization(list[tuple[Any, int]])) # revealed: list[tuple[T_all, int]] +reveal_type(bottom_materialization(list[tuple[Any, int]])) # revealed: list[tuple[T_all, int]] + +reveal_type(top_materialization(list[tuple[str, Unknown]])) # revealed: list[tuple[str, T_all]] +reveal_type(bottom_materialization(list[tuple[str, Unknown]])) # revealed: list[tuple[str, T_all]] + +reveal_type(top_materialization(list[tuple[Any, int, Unknown]])) # revealed: list[tuple[T_all, int, T_all]] +reveal_type(bottom_materialization(list[tuple[Any, int, Unknown]])) # revealed: list[tuple[T_all, int, T_all]] +``` + +## Union + +All positions in a union are covariant. + +```py +from typing import Any +from ty_extensions import Unknown, bottom_materialization, top_materialization + +reveal_type(top_materialization(Any | int)) # revealed: object +reveal_type(bottom_materialization(Any | int)) # revealed: int + +reveal_type(top_materialization(Unknown | int)) # revealed: object +reveal_type(bottom_materialization(Unknown | int)) # revealed: int + +reveal_type(top_materialization(int | str | Any)) # revealed: object +reveal_type(bottom_materialization(int | str | Any)) # revealed: int | str +``` + +Except for when the union itself is in a contravariant position, then all positions in the union +inherit the contravariant position. + +```py +from typing import Callable +from ty_extensions import TypeOf + +def _(callable: Callable[[Any | int, str | Unknown], None]) -> None: + # revealed: (int, str, /) -> None + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (object, object, /) -> None + reveal_type(bottom_materialization(TypeOf[callable])) +``` + +And, similarly for an invariant position. + +```py +reveal_type(top_materialization(list[Any | int])) # revealed: list[T_all | int] +reveal_type(bottom_materialization(list[Any | int])) # revealed: list[T_all | int] + +reveal_type(top_materialization(list[str | Unknown])) # revealed: list[str | T_all] +reveal_type(bottom_materialization(list[str | Unknown])) # revealed: list[str | T_all] + +reveal_type(top_materialization(list[Any | int | Unknown])) # revealed: list[T_all | int] +reveal_type(bottom_materialization(list[Any | int | Unknown])) # revealed: list[T_all | int] +``` + +## Intersection + +All positions in an intersection are covariant. + +```py +from typing import Any +from ty_extensions import Intersection, Unknown, bottom_materialization, top_materialization + +reveal_type(top_materialization(Intersection[Any, int])) # revealed: int +reveal_type(bottom_materialization(Intersection[Any, int])) # revealed: Never + +# Here, the top materialization of `Any | int` is `object` and the intersection of it with tuple +# revealed: tuple[str, object] +reveal_type(top_materialization(Intersection[Any | int, tuple[str, Unknown]])) +# revealed: Never +reveal_type(bottom_materialization(Intersection[Any | int, tuple[str, Unknown]])) + +# revealed: int & tuple[str] +reveal_type(bottom_materialization(Intersection[Any | int, tuple[str]])) + +reveal_type(top_materialization(Intersection[list[Any], list[int]])) # revealed: list[T_all] & list[int] +reveal_type(bottom_materialization(Intersection[list[Any], list[int]])) # revealed: list[T_all] & list[int] +``` + +## Negation (via `Not`) + +All positions in a negation are contravariant. + +```py +from typing import Any +from ty_extensions import Not, Unknown, bottom_materialization, top_materialization + +# ~Any is still Any, so the top materialization is object +reveal_type(top_materialization(Not[Any])) # revealed: object +reveal_type(bottom_materialization(Not[Any])) # revealed: Never + +# tuple[Any, int] is in a contravariant position, so the +# top materialization is Never and the negation of it +# revealed: object +reveal_type(top_materialization(Not[tuple[Any, int]])) +# revealed: ~tuple[object, int] +reveal_type(bottom_materialization(Not[tuple[Any, int]])) +``` + +## `type` + +```py +from typing import Any +from ty_extensions import Unknown, bottom_materialization, top_materialization + +reveal_type(top_materialization(type[Any])) # revealed: type +reveal_type(bottom_materialization(type[Any])) # revealed: Never + +reveal_type(top_materialization(type[Unknown])) # revealed: type +reveal_type(bottom_materialization(type[Unknown])) # revealed: Never + +reveal_type(top_materialization(type[int | Any])) # revealed: type +reveal_type(bottom_materialization(type[int | Any])) # revealed: type[int] + +# Here, `T` has an upper bound of `type` +reveal_type(top_materialization(list[type[Any]])) # revealed: list[T_all] +reveal_type(bottom_materialization(list[type[Any]])) # revealed: list[T_all] +``` + +## Type variables + +```toml +[environment] +python-version = "3.12" +``` + +```py +from typing import Any, Never, TypeVar +from ty_extensions import ( + TypeOf, + Unknown, + bottom_materialization, + top_materialization, + is_fully_static, + static_assert, + is_subtype_of, +) + +def bounded_by_gradual[T: Any](t: T) -> None: + static_assert(not is_fully_static(T)) + + # Top materialization of `T: Any` is `T: object` + static_assert(is_fully_static(TypeOf[top_materialization(T)])) + + # Bottom materialization of `T: Any` is `T: Never` + static_assert(is_fully_static(TypeOf[bottom_materialization(T)])) + # TODO: This should not error, see https://github.com/astral-sh/ty/issues/638 + # error: [static-assert-error] + static_assert(is_subtype_of(TypeOf[bottom_materialization(T)], Never)) + +def constrained_by_gradual[T: (int, Any)](t: T) -> None: + static_assert(not is_fully_static(T)) + + # Top materialization of `T: (int, Any)` is `T: (int, object)` + static_assert(is_fully_static(TypeOf[top_materialization(T)])) + + # Bottom materialization of `T: (int, Any)` is `T: (int, Never)` + static_assert(is_fully_static(TypeOf[bottom_materialization(T)])) + static_assert(is_subtype_of(TypeOf[bottom_materialization(T)], int)) +``` + +## Generics + +For generics, the materialization depends on the surrounding variance and the variance of the type +variable itself. + +- If the type variable is invariant, the materialization happens in an invariant position +- If the type variable is covariant, the materialization happens as per the surrounding variance +- If the type variable is contravariant, the materialization happens as per the surrounding + variance, but the variance is flipped + +```py +from typing import Any, Generic, TypeVar +from ty_extensions import bottom_materialization, top_materialization + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class GenericInvariant(Generic[T]): + pass + +class GenericCovariant(Generic[T_co]): + pass + +class GenericContravariant(Generic[T_contra]): + pass + +reveal_type(top_materialization(GenericInvariant[Any])) # revealed: GenericInvariant[T_all] +reveal_type(bottom_materialization(GenericInvariant[Any])) # revealed: GenericInvariant[T_all] + +reveal_type(top_materialization(GenericCovariant[Any])) # revealed: GenericCovariant[object] +reveal_type(bottom_materialization(GenericCovariant[Any])) # revealed: GenericCovariant[Never] + +reveal_type(top_materialization(GenericContravariant[Any])) # revealed: GenericContravariant[Never] +reveal_type(bottom_materialization(GenericContravariant[Any])) # revealed: GenericContravariant[object] +``` + +Parameters in callable are contravariant, so the variance should be flipped: + +```py +from typing import Callable +from ty_extensions import TypeOf + +def invariant(callable: Callable[[GenericInvariant[Any]], None]) -> None: + # revealed: (GenericInvariant[T_all], /) -> None + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (GenericInvariant[T_all], /) -> None + reveal_type(bottom_materialization(TypeOf[callable])) + +def covariant(callable: Callable[[GenericCovariant[Any]], None]) -> None: + # revealed: (GenericCovariant[Never], /) -> None + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (GenericCovariant[object], /) -> None + reveal_type(bottom_materialization(TypeOf[callable])) + +def contravariant(callable: Callable[[GenericContravariant[Any]], None]) -> None: + # revealed: (GenericContravariant[object], /) -> None + reveal_type(top_materialization(TypeOf[callable])) + + # revealed: (GenericContravariant[Never], /) -> None + reveal_type(bottom_materialization(TypeOf[callable])) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 80c7bd569bb943..94dbe1be273c3a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -615,6 +615,120 @@ impl<'db> Type<'db> { matches!(self, Type::Dynamic(_)) } + /// Returns the top materialization (or upper bound materialization) of this type, which is the + /// most general form of the type that is fully static. + #[must_use] + pub(crate) fn top_materialization(&self, db: &'db dyn Db) -> Type<'db> { + self.materialize(db, TypeVarVariance::Covariant) + } + + /// Returns the bottom materialization (or lower bound materialization) of this type, which is + /// the most specific form of the type that is fully static. + #[must_use] + pub(crate) fn bottom_materialization(&self, db: &'db dyn Db) -> Type<'db> { + self.materialize(db, TypeVarVariance::Contravariant) + } + + /// Returns the materialization of this type depending on the given `variance`. + /// + /// More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of + /// the dynamic types (`Any`, `Unknown`, `Todo`) replaced as follows: + /// + /// - In covariant position, it's replaced with `object` + /// - In contravariant position, it's replaced with `Never` + /// - In invariant position, it's replaced with an unresolved type variable + fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> { + match self { + Type::Dynamic(_) => match variance { + // TODO: For an invariant position, e.g. `list[Any]`, it should be replaced with an + // existential type representing "all lists, containing any type." We currently + // represent this by replacing `Any` in invariant position with an unresolved type + // variable. + TypeVarVariance::Invariant => Type::TypeVar(TypeVarInstance::new( + db, + Name::new_static("T_all"), + None, + None, + variance, + None, + TypeVarKind::Pep695, + )), + TypeVarVariance::Covariant => Type::object(db), + TypeVarVariance::Contravariant => Type::Never, + TypeVarVariance::Bivariant => unreachable!(), + }, + + Type::Never + | Type::WrapperDescriptor(_) + | Type::MethodWrapper(_) + | Type::DataclassDecorator(_) + | Type::DataclassTransformer(_) + | Type::ModuleLiteral(_) + | Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::StringLiteral(_) + | Type::LiteralString + | Type::BytesLiteral(_) + | Type::SpecialForm(_) + | Type::KnownInstance(_) + | Type::AlwaysFalsy + | Type::AlwaysTruthy + | Type::PropertyInstance(_) + | Type::ClassLiteral(_) + | Type::BoundSuper(_) => *self, + + Type::FunctionLiteral(_) | Type::BoundMethod(_) => { + // TODO: Subtyping between function / methods with a callable accounts for the + // signature (parameters and return type), so we might need to do something here + *self + } + + Type::NominalInstance(nominal_instance_type) => { + Type::NominalInstance(nominal_instance_type.materialize(db, variance)) + } + Type::GenericAlias(generic_alias) => { + Type::GenericAlias(generic_alias.materialize(db, variance)) + } + Type::Callable(callable_type) => { + Type::Callable(callable_type.materialize(db, variance)) + } + Type::SubclassOf(subclass_of_type) => subclass_of_type.materialize(db, variance), + Type::ProtocolInstance(protocol_instance_type) => { + // TODO: Add tests for this once subtyping/assignability is implemented for + // protocols. It _might_ require changing the logic here because: + // + // > Subtyping for protocol instances involves taking account of the fact that + // > read-only property members, and method members, on protocols act covariantly; + // > write-only property members act contravariantly; and read/write attribute + // > members on protocols act invariantly + Type::ProtocolInstance(protocol_instance_type.materialize(db, variance)) + } + Type::Union(union_type) => union_type.map(db, |ty| ty.materialize(db, variance)), + Type::Intersection(intersection_type) => IntersectionBuilder::new(db) + .positive_elements( + intersection_type + .positive(db) + .iter() + .map(|ty| ty.materialize(db, variance)), + ) + .negative_elements( + intersection_type + .negative(db) + .iter() + .map(|ty| ty.materialize(db, variance.flip())), + ) + .build(), + Type::Tuple(tuple_type) => TupleType::from_elements( + db, + tuple_type + .elements(db) + .iter() + .map(|ty| ty.materialize(db, variance)), + ), + Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)), + } + } + /// Replace references to the class `class` with a self-reference marker. This is currently /// used for recursive protocols, but could probably be extended to self-referential type- /// aliases and similar. @@ -3634,6 +3748,21 @@ impl<'db> Type<'db> { ) .into(), + Some(KnownFunction::TopMaterialization | KnownFunction::BottomMaterialization) => { + Binding::single( + self, + Signature::new( + Parameters::new([Parameter::positional_only(Some(Name::new_static( + "type", + ))) + .type_form() + .with_annotated_type(Type::any())]), + Some(Type::any()), + ), + ) + .into() + } + Some(KnownFunction::AssertType) => Binding::single( self, Signature::new( @@ -5984,6 +6113,19 @@ impl<'db> TypeVarInstance<'db> { self.kind(db), ) } + + fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self::new( + db, + self.name(db), + self.definition(db), + self.bound_or_constraints(db) + .map(|b| b.materialize(db, variance)), + self.variance(db), + self.default_ty(db), + self.kind(db), + ) + } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)] @@ -5994,6 +6136,20 @@ pub enum TypeVarVariance { Bivariant, } +impl TypeVarVariance { + /// Flips the polarity of the variance. + /// + /// Covariant becomes contravariant, contravariant becomes covariant, others remain unchanged. + pub(crate) const fn flip(self) -> Self { + match self { + TypeVarVariance::Invariant => TypeVarVariance::Invariant, + TypeVarVariance::Covariant => TypeVarVariance::Contravariant, + TypeVarVariance::Contravariant => TypeVarVariance::Covariant, + TypeVarVariance::Bivariant => TypeVarVariance::Bivariant, + } + } +} + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)] pub enum TypeVarBoundOrConstraints<'db> { UpperBound(Type<'db>), @@ -6011,6 +6167,25 @@ impl<'db> TypeVarBoundOrConstraints<'db> { } } } + + fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + match self { + TypeVarBoundOrConstraints::UpperBound(bound) => { + TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, variance)) + } + TypeVarBoundOrConstraints::Constraints(constraints) => { + TypeVarBoundOrConstraints::Constraints(UnionType::new( + db, + constraints + .elements(db) + .iter() + .map(|ty| ty.materialize(db, variance)) + .collect::>() + .into_boxed_slice(), + )) + } + } + } } /// Error returned if a type is not (or may not be) a context manager. @@ -7012,6 +7187,14 @@ impl<'db> CallableType<'db> { )) } + fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + CallableType::new( + db, + self.signatures(db).materialize(db, variance), + self.is_function_like(db), + ) + } + /// Create a callable type which represents a fully-static "bottom" callable. /// /// Specifically, this represents a callable type with a single signature: diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index a7b6e9d94bbd2b..45f7d5694da007 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -675,6 +675,18 @@ impl<'db> Bindings<'db> { } } + Some(KnownFunction::TopMaterialization) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(ty.top_materialization(db)); + } + } + + Some(KnownFunction::BottomMaterialization) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(ty.bottom_materialization(db)); + } + } + Some(KnownFunction::Len) => { if let [Some(first_arg)] = overload.parameter_types() { if let Some(len_ty) = first_arg.len(db) { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index bf5e6f494af7fd..78532e12de12bd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1,6 +1,7 @@ use std::hash::BuildHasherDefault; use std::sync::{LazyLock, Mutex}; +use super::TypeVarVariance; use super::{ IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type, @@ -173,6 +174,14 @@ impl<'db> GenericAlias<'db> { Self::new(db, self.origin(db), self.specialization(db).normalized(db)) } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self::new( + db, + self.origin(db), + self.specialization(db).materialize(db, variance), + ) + } + pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { self.origin(db).definition(db) } @@ -223,6 +232,13 @@ impl<'db> ClassType<'db> { } } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + match self { + Self::NonGeneric(_) => self, + Self::Generic(generic) => Self::Generic(generic.materialize(db, variance)), + } + } + pub(super) fn has_pep_695_type_params(self, db: &'db dyn Db) -> bool { match self { Self::NonGeneric(class) => class.has_pep_695_type_params(db), diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 144a508b03d652..ae693bf731433a 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -890,6 +890,10 @@ pub enum KnownFunction { DunderAllNames, /// `ty_extensions.all_members` AllMembers, + /// `ty_extensions.top_materialization` + TopMaterialization, + /// `ty_extensions.bottom_materialization` + BottomMaterialization, } impl KnownFunction { @@ -947,6 +951,8 @@ impl KnownFunction { | Self::IsSingleValued | Self::IsSingleton | Self::IsSubtypeOf + | Self::TopMaterialization + | Self::BottomMaterialization | Self::GenericContext | Self::DunderAllNames | Self::StaticAssert @@ -1007,6 +1013,8 @@ pub(crate) mod tests { | KnownFunction::IsAssignableTo | KnownFunction::IsEquivalentTo | KnownFunction::IsGradualEquivalentTo + | KnownFunction::TopMaterialization + | KnownFunction::BottomMaterialization | KnownFunction::AllMembers => KnownModule::TyExtensions, }; diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index a4ebf02a6987c6..56c4df34262e68 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -358,6 +358,25 @@ impl<'db> Specialization<'db> { Self::new(db, self.generic_context(db), types) } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + let types: Box<[_]> = self + .generic_context(db) + .variables(db) + .into_iter() + .zip(self.types(db)) + .map(|(typevar, vartype)| { + let variance = match typevar.variance(db) { + TypeVarVariance::Invariant => TypeVarVariance::Invariant, + TypeVarVariance::Covariant => variance, + TypeVarVariance::Contravariant => variance.flip(), + TypeVarVariance::Bivariant => unreachable!(), + }; + vartype.materialize(db, variance) + }) + .collect(); + Specialization::new(db, self.generic_context(db), types) + } + pub(crate) fn has_relation_to( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 40ba5d671972da..715fe51c6df265 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; -use super::{ClassType, KnownClass, SubclassOfType, Type}; +use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use crate::place::{Boundness, Place, PlaceAndQualifiers}; use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance}; use crate::{Db, FxOrderSet}; @@ -80,6 +80,10 @@ impl<'db> NominalInstanceType<'db> { Self::from_class(self.class.normalized(db)) } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self::from_class(self.class.materialize(db, variance)) + } + pub(super) fn has_relation_to( self, db: &'db dyn Db, @@ -314,6 +318,16 @@ impl<'db> ProtocolInstanceType<'db> { } } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + match self.inner { + // TODO: This should also materialize via `class.materialize(db, variance)` + Protocol::FromClass(class) => Self::from_class(class), + Protocol::Synthesized(synthesized) => { + Self::synthesized(synthesized.materialize(db, variance)) + } + } + } + pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, @@ -370,7 +384,7 @@ impl<'db> Protocol<'db> { mod synthesized_protocol { use crate::types::protocol_class::ProtocolInterface; - use crate::types::{TypeMapping, TypeVarInstance}; + use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance}; use crate::{Db, FxOrderSet}; /// A "synthesized" protocol type that is dissociated from a class definition in source code. @@ -390,6 +404,10 @@ mod synthesized_protocol { Self(interface.normalized(db)) } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self(self.0.materialize(db, variance)) + } + pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/property_tests.rs b/crates/ty_python_semantic/src/types/property_tests.rs index e0bca1c509f828..de97903296ddce 100644 --- a/crates/ty_python_semantic/src/types/property_tests.rs +++ b/crates/ty_python_semantic/src/types/property_tests.rs @@ -303,4 +303,20 @@ mod flaky { negation_reverses_subtype_order, db, forall types s, t. s.is_subtype_of(db, t) => t.negate(db).is_subtype_of(db, s.negate(db)) ); + + // Both the top and bottom materialization tests are flaky in part due to various failures that + // it discovers in the current implementation of assignability of the types. + // TODO: Create a issue with some example failures to keep track of it + + // `T'`, the top materialization of `T`, should be assignable to `T`. + type_property_test!( + top_materialization_of_type_is_assignable_to_type, db, + forall types t. t.top_materialization(db).is_assignable_to(db, t) + ); + + // Similarly, `T'`, the bottom materialization of `T`, should also be assignable to `T`. + type_property_test!( + bottom_materialization_of_type_is_assigneble_to_type, db, + forall types t. t.bottom_materialization(db).is_assignable_to(db, t) + ); } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index b17864490e32e7..df3a633367c8b3 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -13,6 +13,8 @@ use crate::{ {Db, FxOrderSet}, }; +use super::TypeVarVariance; + impl<'db> ClassLiteral<'db> { /// Returns `Some` if this is a protocol class, `None` otherwise. pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option> { @@ -177,6 +179,28 @@ impl<'db> ProtocolInterface<'db> { } } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + match self { + Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( + db, + members + .inner(db) + .iter() + .map(|(name, data)| { + ( + name.clone(), + ProtocolMemberData { + ty: data.ty.materialize(db, variance), + qualifiers: data.qualifiers, + }, + ) + }) + .collect::>(), + )), + Self::SelfReference => Self::SelfReference, + } + } + pub(super) fn specialized_and_normalized<'a>( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 0e17986b1eee64..810a5a8a8e36f1 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -15,7 +15,7 @@ use std::{collections::HashMap, slice::Iter}; use itertools::EitherOrBoth; use smallvec::{SmallVec, smallvec}; -use super::{DynamicType, Type, definition_expression_type}; +use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; use crate::semantic_index::definition::Definition; use crate::types::generics::GenericContext; use crate::types::{ClassLiteral, TypeMapping, TypeRelation, TypeVarInstance, todo_type}; @@ -53,6 +53,14 @@ impl<'db> CallableSignature<'db> { self.overloads.iter() } + pub(super) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self::from_overloads( + self.overloads + .iter() + .map(|signature| signature.materialize(db, variance)), + ) + } + pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { Self::from_overloads( self.overloads @@ -353,6 +361,20 @@ impl<'db> Signature<'db> { Self::new(Parameters::object(db), Some(Type::Never)) } + fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self { + generic_context: self.generic_context, + inherited_generic_context: self.inherited_generic_context, + // Parameters are at contravariant position, so the variance is flipped. + parameters: self.parameters.materialize(db, variance.flip()), + return_ty: Some( + self.return_ty + .unwrap_or(Type::unknown()) + .materialize(db, variance), + ), + } + } + pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { Self { generic_context: self.generic_context.map(|ctx| ctx.normalized(db)), @@ -984,6 +1006,17 @@ impl<'db> Parameters<'db> { } } + fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + if self.is_gradual { + Parameters::object(db) + } else { + Parameters::new( + self.iter() + .map(|parameter| parameter.materialize(db, variance)), + ) + } + } + pub(crate) fn as_slice(&self) -> &[Parameter<'db>] { self.value.as_slice() } @@ -1304,6 +1337,18 @@ impl<'db> Parameter<'db> { self } + fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { + Self { + annotated_type: Some( + self.annotated_type + .unwrap_or(Type::unknown()) + .materialize(db, variance), + ), + kind: self.kind.clone(), + form: self.form, + } + } + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { Self { annotated_type: self diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 2143d2e1e25bbd..244945b5ca186e 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,3 +1,5 @@ +use ruff_python_ast::name::Name; + use crate::place::PlaceAndQualifiers; use crate::types::{ ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation, @@ -5,6 +7,8 @@ use crate::types::{ }; use crate::{Db, FxOrderSet}; +use super::{TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance}; + /// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] pub struct SubclassOfType<'db> { @@ -73,6 +77,32 @@ impl<'db> SubclassOfType<'db> { !self.is_dynamic() } + pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> { + match self.subclass_of { + SubclassOfInner::Dynamic(_) => match variance { + TypeVarVariance::Covariant => KnownClass::Type.to_instance(db), + TypeVarVariance::Contravariant => Type::Never, + TypeVarVariance::Invariant => { + // We need to materialize this to `type[T]` but that isn't representable so + // we instead use a type variable with an upper bound of `type`. + Type::TypeVar(TypeVarInstance::new( + db, + Name::new_static("T_all"), + None, + Some(TypeVarBoundOrConstraints::UpperBound( + KnownClass::Type.to_instance(db), + )), + variance, + None, + TypeVarKind::Pep695, + )) + } + TypeVarVariance::Bivariant => unreachable!(), + }, + SubclassOfInner::Class(_) => Type::SubclassOf(self), + } + } + pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 0b98b7a55e7227..81838fdacc8f51 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -44,6 +44,12 @@ def generic_context(type: Any) -> Any: ... # either the module does not have `__all__` or it has invalid elements. def dunder_all_names(module: Any) -> Any: ... +# Returns the type that's an upper bound of materializing the given (gradual) type. +def top_materialization(type: Any) -> Any: ... + +# Returns the type that's a lower bound of materializing the given (gradual) type. +def bottom_materialization(type: Any) -> Any: ... + # Returns a tuple of all members of the given object, similar to `dir(obj)` and # `inspect.getmembers(obj)`, with at least the following differences: # From dbb0d60caaa63fa9e1736d1cc977b1f93759df0e Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 12 Jun 2025 09:52:45 +0300 Subject: [PATCH 400/487] [`pyupgrade`] Fix `super(__class__, self)` detection in UP008 (super-call-with-parameters) (#18478) --- .../test/fixtures/pyupgrade/UP008.py | 11 +++++++++++ .../rules/super_call_with_parameters.rs | 4 +++- ...er__rules__pyupgrade__tests__UP008.py.snap | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py index e9d68ebc3a73ed..d1a5ed5923d5de 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py @@ -79,3 +79,14 @@ def normal(self): def normal(self): super(DataClass, self).f() # OK super().f() # OK (`TypeError` in practice) + + +# see: https://github.com/astral-sh/ruff/issues/18477 +class A: + def foo(self): + pass + + +class B(A): + def bar(self): + super(__class__, self).foo() \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index 2c106da20da278..ba67756946f420 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -121,7 +121,9 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall return; }; - if !(first_arg_id == parent_name.as_str() && second_arg_id == parent_arg.name().as_str()) { + if !((first_arg_id == "__class__" || first_arg_id == parent_name.as_str()) + && second_arg_id == parent_arg.name().as_str()) + { return; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap index 45c029d2d08aad..47a3305a796a9d 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs -snapshot_kind: text --- UP008.py:17:23: UP008 [*] Use `super()` instead of `super(__class__, self)` | @@ -126,4 +125,20 @@ UP008.py:74:14: UP008 [*] Use `super()` instead of `super(__class__, self)` 74 |+ super().f() # Error 75 75 | super().f() # OK 76 76 | -77 77 | +77 77 | + +UP008.py:92:14: UP008 [*] Use `super()` instead of `super(__class__, self)` + | +90 | class B(A): +91 | def bar(self): +92 | super(__class__, self).foo() + | ^^^^^^^^^^^^^^^^^ UP008 + | + = help: Remove `__super__` parameters + +ℹ Unsafe fix +89 89 | +90 90 | class B(A): +91 91 | def bar(self): +92 |- super(__class__, self).foo() + 92 |+ super().foo() From e6fe2af292e7be87c2917863464a5455c5b22c38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:54:38 +0200 Subject: [PATCH 401/487] Update Rust crate anstyle to v1.0.11 (#18583) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3a87602003c74..f48ba867f11770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-lossy" From 324e5cbc196e4f20ea3d59e41b8c15f8b0d0183f Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 12 Jun 2025 10:32:17 +0100 Subject: [PATCH 402/487] [ty] Pull types on synthesized Python files created by mdtest (#18539) --- Cargo.lock | 2 + crates/ty_python_semantic/Cargo.toml | 2 + .../resources/mdtest/annotations/any.md | 2 + .../resources/mdtest/annotations/callable.md | 2 + .../resources/mdtest/type_api.md | 6 + .../mdtest/type_qualifiers/classvar.md | 2 + .../resources/mdtest/type_qualifiers/final.md | 2 + crates/ty_python_semantic/src/lib.rs | 3 + crates/ty_python_semantic/src/pull_types.rs | 134 ++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 6 +- crates/ty_python_semantic/tests/corpus.rs | 135 +------------- crates/ty_test/Cargo.toml | 3 +- crates/ty_test/src/lib.rs | 166 +++++++++++++----- crates/ty_test/src/parser.rs | 87 +++++++-- 14 files changed, 361 insertions(+), 191 deletions(-) create mode 100644 crates/ty_python_semantic/src/pull_types.rs diff --git a/Cargo.lock b/Cargo.lock index f48ba867f11770..9c8c36681ec0db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4004,6 +4004,7 @@ dependencies = [ "test-case", "thiserror 2.0.12", "tracing", + "ty_python_semantic", "ty_test", "ty_vendored", ] @@ -4039,6 +4040,7 @@ name = "ty_test" version = "0.0.0" dependencies = [ "anyhow", + "bitflags 2.9.1", "camino", "colored 3.0.0", "insta", diff --git a/crates/ty_python_semantic/Cargo.toml b/crates/ty_python_semantic/Cargo.toml index 1bbdf10a4926e7..7c5268299ddf87 100644 --- a/crates/ty_python_semantic/Cargo.toml +++ b/crates/ty_python_semantic/Cargo.toml @@ -50,6 +50,7 @@ strum_macros = { workspace = true } [dev-dependencies] ruff_db = { workspace = true, features = ["testing", "os"] } ruff_python_parser = { workspace = true } +ty_python_semantic = { workspace = true, features = ["testing"] } ty_test = { workspace = true } ty_vendored = { workspace = true } @@ -63,6 +64,7 @@ quickcheck_macros = { version = "1.0.0" } [features] serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"] +testing = [] [lints] workspace = true diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/any.md b/crates/ty_python_semantic/resources/mdtest/annotations/any.md index d4b1e6f502224e..a35b18168e5c95 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/any.md @@ -139,6 +139,8 @@ x: int = MagicMock() ## Invalid + + `Any` cannot be parameterized: ```py diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 4b398acef0ccfa..b5420c28739008 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -58,6 +58,8 @@ def _(c: Callable[[int, 42, str, False], None]): ### Missing return type + + Using a parameter list: ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_api.md b/crates/ty_python_semantic/resources/mdtest/type_api.md index 639bd4f83a193e..8074e1fa432da6 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_api.md +++ b/crates/ty_python_semantic/resources/mdtest/type_api.md @@ -14,6 +14,8 @@ directly. ### Negation + + ```py from typing import Literal from ty_extensions import Not, static_assert @@ -371,6 +373,8 @@ static_assert(not is_single_valued(Literal["a"] | Literal["b"])) ## `TypeOf` + + We use `TypeOf` to get the inferred type of an expression. This is useful when we want to refer to it in a type expression. For example, if we want to make sure that the class literal type `str` is a subtype of `type[str]`, we can not use `is_subtype_of(str, type[str])`, as that would test if the @@ -412,6 +416,8 @@ def f(x: TypeOf) -> None: ## `CallableTypeOf` + + The `CallableTypeOf` special form can be used to extract the `Callable` structural type inhabited by a given callable object. This can be used to get the externally visibly signature of the object, which can then be used to test various type properties. diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md index fdf443db9cc04c..c92901341e32c1 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md @@ -84,6 +84,8 @@ d.a = 2 ## Too many arguments + + ```py from typing import ClassVar diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md index 32e84412eb681c..c5f5a863756a69 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md @@ -45,6 +45,8 @@ reveal_type(FINAL_E) # revealed: int ## Too many arguments + + ```py from typing import Final diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index 0123d28c171e96..9728cf373194d7 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -35,6 +35,9 @@ pub mod types; mod unpack; mod util; +#[cfg(feature = "testing")] +pub mod pull_types; + type FxOrderSet = ordermap::set::OrderSet>; /// Returns the default registry with all known semantic lints. diff --git a/crates/ty_python_semantic/src/pull_types.rs b/crates/ty_python_semantic/src/pull_types.rs new file mode 100644 index 00000000000000..68feb73edcc4da --- /dev/null +++ b/crates/ty_python_semantic/src/pull_types.rs @@ -0,0 +1,134 @@ +//! A utility visitor for testing, which attempts to "pull a type" for ever sub-node in a given AST. +//! +//! This is used in the "corpus" and (indirectly) the "mdtest" integration tests for this crate. +//! (Mdtest uses the `pull_types` function via the `ty_test` crate.) + +use crate::{Db, HasType, SemanticModel}; +use ruff_db::{files::File, parsed::parsed_module}; +use ruff_python_ast::{ + self as ast, visitor::source_order, visitor::source_order::SourceOrderVisitor, +}; + +pub fn pull_types(db: &dyn Db, file: File) { + let mut visitor = PullTypesVisitor::new(db, file); + + let ast = parsed_module(db.upcast(), file).load(db.upcast()); + + visitor.visit_body(ast.suite()); +} + +struct PullTypesVisitor<'db> { + model: SemanticModel<'db>, +} + +impl<'db> PullTypesVisitor<'db> { + fn new(db: &'db dyn Db, file: File) -> Self { + Self { + model: SemanticModel::new(db, file), + } + } + + fn visit_target(&mut self, target: &ast::Expr) { + match target { + ast::Expr::List(ast::ExprList { elts, .. }) + | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => { + for element in elts { + self.visit_target(element); + } + } + _ => self.visit_expr(target), + } + } +} + +impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> { + fn visit_stmt(&mut self, stmt: &ast::Stmt) { + match stmt { + ast::Stmt::FunctionDef(function) => { + let _ty = function.inferred_type(&self.model); + } + ast::Stmt::ClassDef(class) => { + let _ty = class.inferred_type(&self.model); + } + ast::Stmt::Assign(assign) => { + for target in &assign.targets { + self.visit_target(target); + } + self.visit_expr(&assign.value); + return; + } + ast::Stmt::For(for_stmt) => { + self.visit_target(&for_stmt.target); + self.visit_expr(&for_stmt.iter); + self.visit_body(&for_stmt.body); + self.visit_body(&for_stmt.orelse); + return; + } + ast::Stmt::With(with_stmt) => { + for item in &with_stmt.items { + if let Some(target) = &item.optional_vars { + self.visit_target(target); + } + self.visit_expr(&item.context_expr); + } + + self.visit_body(&with_stmt.body); + return; + } + ast::Stmt::AnnAssign(_) + | ast::Stmt::Return(_) + | ast::Stmt::Delete(_) + | ast::Stmt::AugAssign(_) + | ast::Stmt::TypeAlias(_) + | ast::Stmt::While(_) + | ast::Stmt::If(_) + | ast::Stmt::Match(_) + | ast::Stmt::Raise(_) + | ast::Stmt::Try(_) + | ast::Stmt::Assert(_) + | ast::Stmt::Import(_) + | ast::Stmt::ImportFrom(_) + | ast::Stmt::Global(_) + | ast::Stmt::Nonlocal(_) + | ast::Stmt::Expr(_) + | ast::Stmt::Pass(_) + | ast::Stmt::Break(_) + | ast::Stmt::Continue(_) + | ast::Stmt::IpyEscapeCommand(_) => {} + } + + source_order::walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &ast::Expr) { + let _ty = expr.inferred_type(&self.model); + + source_order::walk_expr(self, expr); + } + + fn visit_comprehension(&mut self, comprehension: &ast::Comprehension) { + self.visit_expr(&comprehension.iter); + self.visit_target(&comprehension.target); + for if_expr in &comprehension.ifs { + self.visit_expr(if_expr); + } + } + + fn visit_parameter(&mut self, parameter: &ast::Parameter) { + let _ty = parameter.inferred_type(&self.model); + + source_order::walk_parameter(self, parameter); + } + + fn visit_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) { + let _ty = parameter_with_default.inferred_type(&self.model); + + source_order::walk_parameter_with_default(self, parameter_with_default); + } + + fn visit_alias(&mut self, alias: &ast::Alias) { + let _ty = alias.inferred_type(&self.model); + + source_order::walk_alias(self, alias); + } +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 5c99354bb61f01..d1ea86b1c69e02 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -471,8 +471,10 @@ impl<'db> TypeInference<'db> { #[track_caller] pub(crate) fn expression_type(&self, expression: ScopedExpressionId) -> Type<'db> { self.try_expression_type(expression).expect( - "expression should belong to this TypeInference region and \ - TypeInferenceBuilder should have inferred a type for it", + "Failed to retrieve the inferred type for an `ast::Expr` node \ + passed to `TypeInference::expression_type()`. The `TypeInferenceBuilder` \ + should infer and store types for all `ast::Expr` nodes in any `TypeInference` \ + region it analyzes.", ) } diff --git a/crates/ty_python_semantic/tests/corpus.rs b/crates/ty_python_semantic/tests/corpus.rs index 6880dac918ff9f..0a9222a3d6f60b 100644 --- a/crates/ty_python_semantic/tests/corpus.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -1,19 +1,15 @@ use anyhow::{Context, anyhow}; use ruff_db::Upcast; use ruff_db::files::{File, Files, system_path_to_file}; -use ruff_db::parsed::parsed_module; use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem}; use ruff_db::vendored::VendoredFileSystem; -use ruff_python_ast::visitor::source_order; -use ruff_python_ast::visitor::source_order::SourceOrderVisitor; -use ruff_python_ast::{ - self as ast, Alias, Comprehension, Expr, Parameter, ParameterWithDefault, PythonVersion, Stmt, -}; +use ruff_python_ast::PythonVersion; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; +use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::{ - Db, HasType, Program, ProgramSettings, PythonPlatform, PythonVersionSource, - PythonVersionWithSource, SearchPathSettings, SemanticModel, default_lint_registry, + Program, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource, + SearchPathSettings, default_lint_registry, }; fn get_cargo_workspace_root() -> anyhow::Result { @@ -174,129 +170,6 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> { Ok(()) } -fn pull_types(db: &dyn Db, file: File) { - let mut visitor = PullTypesVisitor::new(db, file); - - let ast = parsed_module(db.upcast(), file).load(db.upcast()); - - visitor.visit_body(ast.suite()); -} - -struct PullTypesVisitor<'db> { - model: SemanticModel<'db>, -} - -impl<'db> PullTypesVisitor<'db> { - fn new(db: &'db dyn Db, file: File) -> Self { - Self { - model: SemanticModel::new(db, file), - } - } - - fn visit_target(&mut self, target: &Expr) { - match target { - Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => { - for element in elts { - self.visit_target(element); - } - } - _ => self.visit_expr(target), - } - } -} - -impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> { - fn visit_stmt(&mut self, stmt: &Stmt) { - match stmt { - Stmt::FunctionDef(function) => { - let _ty = function.inferred_type(&self.model); - } - Stmt::ClassDef(class) => { - let _ty = class.inferred_type(&self.model); - } - Stmt::Assign(assign) => { - for target in &assign.targets { - self.visit_target(target); - } - self.visit_expr(&assign.value); - return; - } - Stmt::For(for_stmt) => { - self.visit_target(&for_stmt.target); - self.visit_expr(&for_stmt.iter); - self.visit_body(&for_stmt.body); - self.visit_body(&for_stmt.orelse); - return; - } - Stmt::With(with_stmt) => { - for item in &with_stmt.items { - if let Some(target) = &item.optional_vars { - self.visit_target(target); - } - self.visit_expr(&item.context_expr); - } - - self.visit_body(&with_stmt.body); - return; - } - Stmt::AnnAssign(_) - | Stmt::Return(_) - | Stmt::Delete(_) - | Stmt::AugAssign(_) - | Stmt::TypeAlias(_) - | Stmt::While(_) - | Stmt::If(_) - | Stmt::Match(_) - | Stmt::Raise(_) - | Stmt::Try(_) - | Stmt::Assert(_) - | Stmt::Import(_) - | Stmt::ImportFrom(_) - | Stmt::Global(_) - | Stmt::Nonlocal(_) - | Stmt::Expr(_) - | Stmt::Pass(_) - | Stmt::Break(_) - | Stmt::Continue(_) - | Stmt::IpyEscapeCommand(_) => {} - } - - source_order::walk_stmt(self, stmt); - } - - fn visit_expr(&mut self, expr: &Expr) { - let _ty = expr.inferred_type(&self.model); - - source_order::walk_expr(self, expr); - } - - fn visit_comprehension(&mut self, comprehension: &Comprehension) { - self.visit_expr(&comprehension.iter); - self.visit_target(&comprehension.target); - for if_expr in &comprehension.ifs { - self.visit_expr(if_expr); - } - } - - fn visit_parameter(&mut self, parameter: &Parameter) { - let _ty = parameter.inferred_type(&self.model); - - source_order::walk_parameter(self, parameter); - } - - fn visit_parameter_with_default(&mut self, parameter_with_default: &ParameterWithDefault) { - let _ty = parameter_with_default.inferred_type(&self.model); - - source_order::walk_parameter_with_default(self, parameter_with_default); - } - - fn visit_alias(&mut self, alias: &Alias) { - let _ty = alias.inferred_type(&self.model); - - source_order::walk_alias(self, alias); - } -} - /// Whether or not the .py/.pyi version of this file is expected to fail #[rustfmt::skip] const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ diff --git a/crates/ty_test/Cargo.toml b/crates/ty_test/Cargo.toml index c80bf76011e9db..f3d698f21f09cd 100644 --- a/crates/ty_test/Cargo.toml +++ b/crates/ty_test/Cargo.toml @@ -18,10 +18,11 @@ ruff_python_trivia = { workspace = true } ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } ruff_python_ast = { workspace = true } -ty_python_semantic = { workspace = true, features = ["serde"] } +ty_python_semantic = { workspace = true, features = ["serde", "testing"] } ty_vendored = { workspace = true } anyhow = { workspace = true } +bitflags = { workspace = true } camino = { workspace = true } colored = { workspace = true } insta = { workspace = true, features = ["filters"] } diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 4749f477bdbabc..f9e779fd0e7125 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -1,4 +1,5 @@ use crate::config::Log; +use crate::db::Db; use crate::parser::{BacktickOffsets, EmbeddedFileSourceMap}; use camino::Utf8Path; use colored::Colorize; @@ -17,6 +18,7 @@ use ruff_db::testing::{setup_logging, setup_logging_with_filter}; use ruff_source_file::{LineIndex, OneIndexed}; use std::backtrace::BacktraceStatus; use std::fmt::Write; +use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::types::check_types; use ty_python_semantic::{ Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource, @@ -291,9 +293,31 @@ fn run_test( // all diagnostics. Otherwise it remains empty. let mut snapshot_diagnostics = vec![]; - let failures: Failures = test_files - .into_iter() + let mut any_pull_types_failures = false; + + let mut failures: Failures = test_files + .iter() .filter_map(|test_file| { + let pull_types_result = attempt_test( + db, + pull_types, + test_file, + "\"pull types\"", + Some( + "Note: either fix the panic or add the `` \ + directive to this test", + ), + ); + match pull_types_result { + Ok(()) => {} + Err(failures) => { + any_pull_types_failures = true; + if !test.should_skip_pulling_types() { + return Some(failures); + } + } + } + let parsed = parsed_module(db, test_file.file).load(db); let mut diagnostics: Vec = parsed @@ -309,64 +333,50 @@ fn run_test( .map(|error| create_unsupported_syntax_diagnostic(test_file.file, error)), ); - let type_diagnostics = match catch_unwind(|| check_types(db, test_file.file)) { - Ok(type_diagnostics) => type_diagnostics, - Err(info) => { - let mut by_line = matcher::FailuresByLine::default(); - let mut messages = vec![]; - match info.location { - Some(location) => messages.push(format!("panicked at {location}")), - None => messages.push("panicked at unknown location".to_string()), - } - match info.payload.as_str() { - Some(message) => messages.push(message.to_string()), - // Mimic the default panic hook's rendering of the panic payload if it's - // not a string. - None => messages.push("Box".to_string()), - } - if let Some(backtrace) = info.backtrace { - match backtrace.status() { - BacktraceStatus::Disabled => { - let msg = "run with `RUST_BACKTRACE=1` environment variable to display a backtrace"; - messages.push(msg.to_string()); - } - BacktraceStatus::Captured => { - messages.extend(backtrace.to_string().split('\n').map(String::from)); - } - _ => {} - } - } - - if let Some(backtrace) = info.salsa_backtrace { - salsa::attach(db, || { - messages.extend(format!("{backtrace:#}").split('\n').map(String::from)); - }); - } - - by_line.push(OneIndexed::from_zero_indexed(0), messages); - return Some(FileFailures { - backtick_offsets: test_file.backtick_offsets, - by_line, - }); - } + let mdtest_result = attempt_test(db, check_types, test_file, "run mdtest", None); + let type_diagnostics = match mdtest_result { + Ok(diagnostics) => diagnostics, + Err(failures) => return Some(failures), }; + diagnostics.extend(type_diagnostics.into_iter().cloned()); - diagnostics.sort_by(|left, right|left.rendering_sort_key(db).cmp(&right.rendering_sort_key(db))); + diagnostics.sort_by(|left, right| { + left.rendering_sort_key(db) + .cmp(&right.rendering_sort_key(db)) + }); let failure = match matcher::match_file(db, test_file.file, &diagnostics) { Ok(()) => None, Err(line_failures) => Some(FileFailures { - backtick_offsets: test_file.backtick_offsets, + backtick_offsets: test_file.backtick_offsets.clone(), by_line: line_failures, }), }; if test.should_snapshot_diagnostics() { snapshot_diagnostics.extend(diagnostics); } + failure }) .collect(); + if test.should_skip_pulling_types() && !any_pull_types_failures { + let mut by_line = matcher::FailuresByLine::default(); + by_line.push( + OneIndexed::from_zero_indexed(0), + vec![ + "Remove the `` directive from this test: pulling types \ + succeeded for all files in the test." + .to_string(), + ], + ); + let failure = FileFailures { + backtick_offsets: test_files[0].backtick_offsets.clone(), + by_line, + }; + failures.push(failure); + } + if snapshot_diagnostics.is_empty() && test.should_snapshot_diagnostics() { panic!( "Test `{}` requested snapshotting diagnostics but it didn't produce any.", @@ -462,3 +472,71 @@ fn create_diagnostic_snapshot( } snapshot } + +/// Run a function over an embedded test file, catching any panics that occur in the process. +/// +/// If no panic occurs, the result of the function is returned as an `Ok()` variant. +/// +/// If a panic occurs, a nicely formatted [`FileFailures`] is returned as an `Err()` variant. +/// This will be formatted into a diagnostic message by `ty_test`. +fn attempt_test<'db, T, F>( + db: &'db Db, + test_fn: F, + test_file: &TestFile, + action: &str, + clarification: Option<&str>, +) -> Result +where + F: FnOnce(&'db dyn ty_python_semantic::Db, File) -> T + std::panic::UnwindSafe, +{ + catch_unwind(|| test_fn(db, test_file.file)).map_err(|info| { + let mut by_line = matcher::FailuresByLine::default(); + let mut messages = vec![]; + match info.location { + Some(location) => messages.push(format!( + "Attempting to {action} caused a panic at {location}" + )), + None => messages.push(format!( + "Attempting to {action} caused a panic at an unknown location", + )), + } + if let Some(clarification) = clarification { + messages.push(clarification.to_string()); + } + messages.push(String::new()); + match info.payload.as_str() { + Some(message) => messages.push(message.to_string()), + // Mimic the default panic hook's rendering of the panic payload if it's + // not a string. + None => messages.push("Box".to_string()), + } + messages.push(String::new()); + + if let Some(backtrace) = info.backtrace { + match backtrace.status() { + BacktraceStatus::Disabled => { + let msg = + "run with `RUST_BACKTRACE=1` environment variable to display a backtrace"; + messages.push(msg.to_string()); + } + BacktraceStatus::Captured => { + messages.extend(backtrace.to_string().split('\n').map(String::from)); + } + _ => {} + } + } + + if let Some(backtrace) = info.salsa_backtrace { + salsa::attach(db, || { + messages.extend(format!("{backtrace:#}").split('\n').map(String::from)); + }); + } + + by_line.push(OneIndexed::from_zero_indexed(0), messages); + + FileFailures { + backtick_offsets: test_file.backtick_offsets.clone(), + by_line, + } + }) +} diff --git a/crates/ty_test/src/parser.rs b/crates/ty_test/src/parser.rs index b1cc448beb8ef0..68e9ab5198ef48 100644 --- a/crates/ty_test/src/parser.rs +++ b/crates/ty_test/src/parser.rs @@ -143,7 +143,15 @@ impl<'m, 's> MarkdownTest<'m, 's> { } pub(super) fn should_snapshot_diagnostics(&self) -> bool { - self.section.snapshot_diagnostics + self.section + .directives + .contains(MdtestDirectives::SNAPSHOT_DIAGNOSTICS) + } + + pub(super) fn should_skip_pulling_types(&self) -> bool { + self.section + .directives + .contains(MdtestDirectives::PULL_TYPES_SKIP) } } @@ -194,7 +202,7 @@ struct Section<'s> { level: u8, parent_id: Option, config: MarkdownTestConfig, - snapshot_diagnostics: bool, + directives: MdtestDirectives, } #[newtype_index] @@ -428,7 +436,7 @@ impl<'s> Parser<'s> { level: 0, parent_id: None, config: MarkdownTestConfig::default(), - snapshot_diagnostics: false, + directives: MdtestDirectives::default(), }); Self { sections, @@ -486,6 +494,7 @@ impl<'s> Parser<'s> { fn parse_impl(&mut self) -> anyhow::Result<()> { const SECTION_CONFIG_SNAPSHOT: &str = "snapshot-diagnostics"; + const SECTION_CONFIG_PULLTYPES: &str = "pull-types:skip"; const HTML_COMMENT_ALLOWLIST: &[&str] = &["blacken-docs:on", "blacken-docs:off"]; const CODE_BLOCK_END: &[u8] = b"```"; const HTML_COMMENT_END: &[u8] = b"-->"; @@ -498,10 +507,12 @@ impl<'s> Parser<'s> { { let html_comment = self.cursor.as_str()[..position].trim(); if html_comment == SECTION_CONFIG_SNAPSHOT { - self.process_snapshot_diagnostics()?; + self.process_mdtest_directive(MdtestDirective::SnapshotDiagnostics)?; + } else if html_comment == SECTION_CONFIG_PULLTYPES { + self.process_mdtest_directive(MdtestDirective::PullTypesSkip)?; } else if !HTML_COMMENT_ALLOWLIST.contains(&html_comment) { bail!( - "Unknown HTML comment `{}` -- possibly a `snapshot-diagnostics` typo? \ + "Unknown HTML comment `{}` -- possibly a typo? \ (Add to `HTML_COMMENT_ALLOWLIST` if this is a false positive)", html_comment ); @@ -636,7 +647,7 @@ impl<'s> Parser<'s> { level: header_level.try_into()?, parent_id: Some(parent), config: self.sections[parent].config.clone(), - snapshot_diagnostics: self.sections[parent].snapshot_diagnostics, + directives: self.sections[parent].directives, }; if !self.current_section_files.is_empty() { @@ -784,28 +795,28 @@ impl<'s> Parser<'s> { Ok(()) } - fn process_snapshot_diagnostics(&mut self) -> anyhow::Result<()> { + fn process_mdtest_directive(&mut self, directive: MdtestDirective) -> anyhow::Result<()> { if self.current_section_has_config { bail!( - "Section config to enable snapshotting diagnostics must come before \ + "Section config to enable {directive} must come before \ everything else (including TOML configuration blocks).", ); } if !self.current_section_files.is_empty() { bail!( - "Section config to enable snapshotting diagnostics must come before \ + "Section config to enable {directive} must come before \ everything else (including embedded files).", ); } let current_section = &mut self.sections[self.stack.top()]; - if current_section.snapshot_diagnostics { + if current_section.directives.has_directive_set(directive) { bail!( - "Section config to enable snapshotting diagnostics should appear \ + "Section config to enable {directive} should appear \ at most once.", ); } - current_section.snapshot_diagnostics = true; + current_section.directives.add_directive(directive); Ok(()) } @@ -824,6 +835,56 @@ impl<'s> Parser<'s> { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MdtestDirective { + /// A directive to enable snapshotting diagnostics. + SnapshotDiagnostics, + /// A directive to skip pull types. + PullTypesSkip, +} + +impl std::fmt::Display for MdtestDirective { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + MdtestDirective::SnapshotDiagnostics => f.write_str("snapshotting diagnostics"), + MdtestDirective::PullTypesSkip => f.write_str("skipping the pull-types visitor"), + } + } +} + +bitflags::bitflags! { + /// Directives that can be applied to a Markdown test section. + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] + pub(crate) struct MdtestDirectives: u8 { + /// We should snapshot diagnostics for this section. + const SNAPSHOT_DIAGNOSTICS = 1 << 0; + /// We should skip pulling types for this section. + const PULL_TYPES_SKIP = 1 << 1; + } +} + +impl MdtestDirectives { + const fn has_directive_set(self, directive: MdtestDirective) -> bool { + match directive { + MdtestDirective::SnapshotDiagnostics => { + self.contains(MdtestDirectives::SNAPSHOT_DIAGNOSTICS) + } + MdtestDirective::PullTypesSkip => self.contains(MdtestDirectives::PULL_TYPES_SKIP), + } + } + + fn add_directive(&mut self, directive: MdtestDirective) { + match directive { + MdtestDirective::SnapshotDiagnostics => { + self.insert(MdtestDirectives::SNAPSHOT_DIAGNOSTICS); + } + MdtestDirective::PullTypesSkip => { + self.insert(MdtestDirectives::PULL_TYPES_SKIP); + } + } + } +} + #[cfg(test)] mod tests { use ruff_python_ast::PySourceType; @@ -1906,7 +1967,7 @@ mod tests { let err = super::parse("file.md", &source).expect_err("Should fail to parse"); assert_eq!( err.to_string(), - "Unknown HTML comment `snpshotttt-digggggnosstic` -- possibly a `snapshot-diagnostics` typo? \ + "Unknown HTML comment `snpshotttt-digggggnosstic` -- possibly a typo? \ (Add to `HTML_COMMENT_ALLOWLIST` if this is a false positive)", ); } From 8123dab05aaf46bb893cf82854c34af1572cc130 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 12 Jun 2025 07:40:39 -0400 Subject: [PATCH 403/487] [ty] Add some "inside string" tests for `object.` completions Ref https://github.com/astral-sh/ruff/pull/18629#pullrequestreview-2919922754 --- crates/ty_ide/src/completion.rs | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 9581a719939f7d..40064ceaccdc26 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1786,6 +1786,56 @@ def _(): assert_snapshot!(test.completions(), @""); } + #[test] + fn string_dot_attr1() { + let test = cursor_test( + r#" +foo = 1 +bar = 2 + +class Foo: + def method(self): ... + +f = Foo() + +# String, this is not an attribute access +"f. +"#, + ); + + // TODO: This should not have any completions suggested for it. + // We do correctly avoid giving `object.attr` completions here, + // but we instead fall back to scope based completions. Since + // we're inside a string, we should avoid giving completions at + // all. + assert_snapshot!(test.completions(), @r" + Foo + bar + f + foo + "); + } + + #[test] + fn string_dot_attr2() { + let test = cursor_test( + r#" +foo = 1 +bar = 2 + +class Foo: + def method(self): ... + +f = Foo() + +# F-string, this is an attribute access +f"{f. +"#, + ); + + test.assert_completions_include("method"); + } + impl CursorTest { fn completions(&self) -> String { self.completions_if(|_| true) From 96171f41c23d342c4a60bfaa9fd2ac52f4e7c304 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:07:17 -0400 Subject: [PATCH 404/487] [`ruff`] Handle extra arguments to `deque` (`RUF037`) (#18614) ## Summary Fixes https://github.com/astral-sh/ruff/issues/18612 by: - Bailing out without a fix in the case of `*args`, which I don't think we can fix reliably - Using an `Edit::deletion` from `remove_argument` instead of an `Edit::range_replacement` in the presence of unrecognized keyword arguments I thought we could always switch to the `Edit::deletion` approach initially, but it caused problems when `maxlen` was passed positionally, which we didn't have any existing tests for. The replacement fix can easily delete comments, so I also marked the fix unsafe in these cases and updated the docs accordingly. ## Test Plan New test cases derived from the issue. ## Stabilization These are pretty significant changes, much like those to PYI059 in https://github.com/astral-sh/ruff/pull/18611 (and based a bit on the implementation there!), so I think it probably makes sense to un-stabilize this for the 0.12 release, but I'm open to other thoughts there. --- .../resources/test/fixtures/ruff/RUF037.py | 32 +++++ .../unnecessary_literal_within_deque_call.rs | 87 +++++++++---- ..._rules__ruff__tests__RUF037_RUF037.py.snap | 114 ++++++++++++++++++ 3 files changed, 212 insertions(+), 21 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py index 2893daecc6c49a..18807d4794e80a 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py @@ -59,3 +59,35 @@ def f(): def f(): x = 0 or(deque)([]) + + +# regression tests for https://github.com/astral-sh/ruff/issues/18612 +def f(): + deque([], *[10]) # RUF037 but no fix + deque([], **{"maxlen": 10}) # RUF037 + deque([], foo=1) # RUF037 + + +# Somewhat related to the issue, both okay because we can't generally look +# inside *args or **kwargs +def f(): + deque(*([], 10)) # Ok + deque(**{"iterable": [], "maxlen": 10}) # Ok + +# The fix was actually always unsafe in the presence of comments. all of these +# are deleted +def f(): + deque( # a comment in deque, deleted + [ # a comment _in_ the list, deleted + ], # a comment after the list, deleted + maxlen=10, # a comment on maxlen, deleted + ) # only this is preserved + + +# `maxlen` can also be passed positionally +def f(): + deque([], 10) + + +def f(): + deque([], iterable=[]) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs index 7050f43b9f2f09..115fc5c136b5c2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs @@ -1,10 +1,12 @@ +use ruff_diagnostics::{Applicability, Edit}; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::{AlwaysFixableViolation, Edit, Fix}; +use crate::fix::edits::{Parentheses, remove_argument}; +use crate::{Fix, FixAvailability, Violation}; /// ## What it does /// Checks for usages of `collections.deque` that have an empty iterable as the first argument. @@ -30,6 +32,15 @@ use crate::{AlwaysFixableViolation, Edit, Fix}; /// queue = deque(maxlen=10) /// ``` /// +/// ## Fix safety +/// +/// The fix is marked as unsafe whenever it would delete comments present in the `deque` call or if +/// there are unrecognized arguments other than `iterable` and `maxlen`. +/// +/// ## Fix availability +/// +/// This rule's fix is unavailable if any starred arguments are present after the initial iterable. +/// /// ## References /// - [Python documentation: `collections.deque`](https://docs.python.org/3/library/collections.html#collections.deque) #[derive(ViolationMetadata)] @@ -37,19 +48,21 @@ pub(crate) struct UnnecessaryEmptyIterableWithinDequeCall { has_maxlen: bool, } -impl AlwaysFixableViolation for UnnecessaryEmptyIterableWithinDequeCall { +impl Violation for UnnecessaryEmptyIterableWithinDequeCall { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { "Unnecessary empty iterable within a deque call".to_string() } - fn fix_title(&self) -> String { + fn fix_title(&self) -> Option { let title = if self.has_maxlen { "Replace with `deque(maxlen=...)`" } else { "Replace with `deque()`" }; - title.to_string() + Some(title.to_string()) } } @@ -66,13 +79,13 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a return; } - let Some(iterable) = arguments.find_argument_value("iterable", 0) else { + let Some(iterable) = arguments.find_argument("iterable", 0) else { return; }; let maxlen = arguments.find_argument_value("maxlen", 1); - let is_empty_literal = match iterable { + let is_empty_literal = match iterable.value() { Expr::Dict(dict) => dict.is_empty(), Expr::List(list) => list.is_empty(), Expr::Tuple(tuple) => tuple.is_empty(), @@ -100,29 +113,61 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a deque.range, ); - diagnostic.set_fix(fix_unnecessary_literal_in_deque(checker, deque, maxlen)); + // Return without a fix in the presence of a starred argument because we can't accurately + // generate the fix. If all of the arguments are unpacked (e.g. `deque(*([], 10))`), we will + // have already returned after the first `find_argument_value` call. + if deque.arguments.args.iter().any(Expr::is_starred_expr) { + return; + } + + diagnostic.try_set_fix(|| fix_unnecessary_literal_in_deque(checker, iterable, deque, maxlen)); } fn fix_unnecessary_literal_in_deque( checker: &Checker, + iterable: ast::ArgOrKeyword, deque: &ast::ExprCall, maxlen: Option<&Expr>, -) -> Fix { - let deque_name = checker.locator().slice( - parenthesized_range( - deque.func.as_ref().into(), - deque.into(), +) -> anyhow::Result { + // if `maxlen` is `Some`, we know there were exactly two arguments, and we can replace the whole + // call. otherwise, we only delete the `iterable` argument and leave the others untouched. + let edit = if let Some(maxlen) = maxlen { + let deque_name = checker.locator().slice( + parenthesized_range( + deque.func.as_ref().into(), + deque.into(), + checker.comment_ranges(), + checker.source(), + ) + .unwrap_or(deque.func.range()), + ); + let len_str = checker.locator().slice(maxlen); + let deque_str = format!("{deque_name}(maxlen={len_str})"); + Edit::range_replacement(deque_str, deque.range) + } else { + let range = parenthesized_range( + iterable.value().into(), + (&deque.arguments).into(), checker.comment_ranges(), checker.source(), ) - .unwrap_or(deque.func.range()), - ); - let deque_str = match maxlen { - Some(maxlen) => { - let len_str = checker.locator().slice(maxlen); - format!("{deque_name}(maxlen={len_str})") - } - None => format!("{deque_name}()"), + .unwrap_or(iterable.range()); + remove_argument( + &range, + &deque.arguments, + Parentheses::Preserve, + checker.source(), + )? + }; + let has_comments = checker.comment_ranges().intersects(edit.range()); + // we've already checked maxlen.is_some() && args != 2 above, so this is the only problematic + // case left + let unknown_arguments = maxlen.is_none() && deque.arguments.len() != 1; + let applicability = if has_comments || unknown_arguments { + Applicability::Unsafe + } else { + Applicability::Safe }; - Fix::safe_edit(Edit::range_replacement(deque_str, deque.range)) + + Ok(Fix::applicable_edit(edit, applicability)) } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap index 09d6f8617158ac..3b8f271e2813e5 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap @@ -141,3 +141,117 @@ RUF037.py:61:13: RUF037 [*] Unnecessary empty iterable within a deque call 60 60 | def f(): 61 |- x = 0 or(deque)([]) 61 |+ x = 0 or(deque)() +62 62 | +63 63 | +64 64 | # regression tests for https://github.com/astral-sh/ruff/issues/18612 + +RUF037.py:66:5: RUF037 Unnecessary empty iterable within a deque call + | +64 | # regression tests for https://github.com/astral-sh/ruff/issues/18612 +65 | def f(): +66 | deque([], *[10]) # RUF037 but no fix + | ^^^^^^^^^^^^^^^^ RUF037 +67 | deque([], **{"maxlen": 10}) # RUF037 +68 | deque([], foo=1) # RUF037 + | + = help: Replace with `deque()` + +RUF037.py:67:5: RUF037 [*] Unnecessary empty iterable within a deque call + | +65 | def f(): +66 | deque([], *[10]) # RUF037 but no fix +67 | deque([], **{"maxlen": 10}) # RUF037 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF037 +68 | deque([], foo=1) # RUF037 + | + = help: Replace with `deque()` + +ℹ Unsafe fix +64 64 | # regression tests for https://github.com/astral-sh/ruff/issues/18612 +65 65 | def f(): +66 66 | deque([], *[10]) # RUF037 but no fix +67 |- deque([], **{"maxlen": 10}) # RUF037 + 67 |+ deque(**{"maxlen": 10}) # RUF037 +68 68 | deque([], foo=1) # RUF037 +69 69 | +70 70 | + +RUF037.py:68:5: RUF037 [*] Unnecessary empty iterable within a deque call + | +66 | deque([], *[10]) # RUF037 but no fix +67 | deque([], **{"maxlen": 10}) # RUF037 +68 | deque([], foo=1) # RUF037 + | ^^^^^^^^^^^^^^^^ RUF037 + | + = help: Replace with `deque()` + +ℹ Unsafe fix +65 65 | def f(): +66 66 | deque([], *[10]) # RUF037 but no fix +67 67 | deque([], **{"maxlen": 10}) # RUF037 +68 |- deque([], foo=1) # RUF037 + 68 |+ deque(foo=1) # RUF037 +69 69 | +70 70 | +71 71 | # Somewhat related to the issue, both okay because we can't generally look + +RUF037.py:80:5: RUF037 [*] Unnecessary empty iterable within a deque call + | +78 | # are deleted +79 | def f(): +80 | / deque( # a comment in deque, deleted +81 | | [ # a comment _in_ the list, deleted +82 | | ], # a comment after the list, deleted +83 | | maxlen=10, # a comment on maxlen, deleted +84 | | ) # only this is preserved + | |_________^ RUF037 + | + = help: Replace with `deque(maxlen=...)` + +ℹ Unsafe fix +77 77 | # The fix was actually always unsafe in the presence of comments. all of these +78 78 | # are deleted +79 79 | def f(): +80 |- deque( # a comment in deque, deleted +81 |- [ # a comment _in_ the list, deleted +82 |- ], # a comment after the list, deleted +83 |- maxlen=10, # a comment on maxlen, deleted +84 |- ) # only this is preserved + 80 |+ deque(maxlen=10) # only this is preserved +85 81 | +86 82 | +87 83 | # `maxlen` can also be passed positionally + +RUF037.py:89:5: RUF037 [*] Unnecessary empty iterable within a deque call + | +87 | # `maxlen` can also be passed positionally +88 | def f(): +89 | deque([], 10) + | ^^^^^^^^^^^^^ RUF037 + | + = help: Replace with `deque(maxlen=...)` + +ℹ Safe fix +86 86 | +87 87 | # `maxlen` can also be passed positionally +88 88 | def f(): +89 |- deque([], 10) + 89 |+ deque(maxlen=10) +90 90 | +91 91 | +92 92 | def f(): + +RUF037.py:93:5: RUF037 [*] Unnecessary empty iterable within a deque call + | +92 | def f(): +93 | deque([], iterable=[]) + | ^^^^^^^^^^^^^^^^^^^^^^ RUF037 + | + = help: Replace with `deque()` + +ℹ Unsafe fix +90 90 | +91 91 | +92 92 | def f(): +93 |- deque([], iterable=[]) + 93 |+ deque([]) From ef564094a9a4278a8157ba5cd6449a710e501c23 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama <45118249+mtshiba@users.noreply.github.com> Date: Thu, 12 Jun 2025 23:44:42 +0900 Subject: [PATCH 405/487] [ty] support del statement and deletion of except handler names (#18593) ## Summary This PR closes https://github.com/astral-sh/ty/issues/238. Since `DefinitionState::Deleted` was introduced in #18041, support for the `del` statement (and deletion of except handler names) is straightforward. However, it is difficult to determine whether references to attributes or subscripts are unresolved after they are deleted. This PR only invalidates narrowing by assignment if the attribute or subscript is deleted. ## Test Plan `mdtest/del.md` is added. --------- Co-authored-by: Alex Waygood --- .../resources/mdtest/del.md | 121 ++++++++++++++++++ .../resources/mdtest/exception/basic.md | 38 ++++++ .../src/semantic_index/builder.rs | 23 +++- crates/ty_python_semantic/src/types/infer.rs | 18 ++- 4 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/del.md diff --git a/crates/ty_python_semantic/resources/mdtest/del.md b/crates/ty_python_semantic/resources/mdtest/del.md new file mode 100644 index 00000000000000..2007bca7382a33 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/del.md @@ -0,0 +1,121 @@ +# `del` statement + +## Basic + +```py +a = 1 +del a +# error: [unresolved-reference] +reveal_type(a) # revealed: Unknown + +# error: [invalid-syntax] "Invalid delete target" +del 1 + +# error: [unresolved-reference] +del a + +x, y = 1, 2 +del x, y +# error: [unresolved-reference] +reveal_type(x) # revealed: Unknown +# error: [unresolved-reference] +reveal_type(y) # revealed: Unknown + +def cond() -> bool: + return True + +b = 1 +if cond(): + del b + +# error: [possibly-unresolved-reference] +reveal_type(b) # revealed: Literal[1] + +c = 1 +if cond(): + c = 2 +else: + del c + +# error: [possibly-unresolved-reference] +reveal_type(c) # revealed: Literal[2] + +d = 1 + +def delete(): + # TODO: this results in `UnboundLocalError`; we should emit `unresolved-reference` + del d + +delete() +reveal_type(d) # revealed: Literal[1] + +def delete_global(): + global d + del d + +delete_global() +# The variable should have been removed, but we won't check it for now. +reveal_type(d) # revealed: Literal[1] +``` + +## Delete attributes + +If an attribute is referenced after being deleted, it will be an error at runtime. But we don't +treat this as an error (because there may have been a redefinition by a method between the `del` +statement and the reference). However, deleting an attribute invalidates type narrowing by +assignment, and the attribute type will be the originally declared type. + +### Invalidate narrowing + +```py +class C: + x: int = 1 + +c = C() +del c.x +reveal_type(c.x) # revealed: int + +# error: [unresolved-attribute] +del c.non_existent + +c.x = 1 +reveal_type(c.x) # revealed: Literal[1] +del c.x +reveal_type(c.x) # revealed: int +``` + +### Delete an instance attribute definition + +```py +class C: + x: int = 1 + +c = C() +reveal_type(c.x) # revealed: int + +del C.x +c = C() +# This attribute is unresolved, but we won't check it for now. +reveal_type(c.x) # revealed: int +``` + +## Delete items + +Deleting an item also invalidates the narrowing by the assignment, but accessing the item itself is +still valid. + +```py +def f(l: list[int]): + del l[0] + # If the length of `l` was 1, this will be a runtime error, + # but if it was greater than that, it will not be an error. + reveal_type(l[0]) # revealed: int + + # error: [call-non-callable] + del l["string"] + + l[0] = 1 + reveal_type(l[0]) # revealed: Literal[1] + del l[0] + reveal_type(l[0]) # revealed: int +``` diff --git a/crates/ty_python_semantic/resources/mdtest/exception/basic.md b/crates/ty_python_semantic/resources/mdtest/exception/basic.md index 7e4ebcd4b4c4c6..87f05e55fcda38 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/basic.md @@ -240,3 +240,41 @@ def _(e: Exception | type[Exception] | None): def _(e: int | None): raise IndexError from e # error: [invalid-raise] ``` + +## The caught exception is cleared at the end of the except clause + +```py +e = None +reveal_type(e) # revealed: None + +try: + raise ValueError() +except ValueError as e: + reveal_type(e) # revealed: ValueError +# error: [unresolved-reference] +reveal_type(e) # revealed: Unknown + +e = None + +def cond() -> bool: + return True + +try: + if cond(): + raise ValueError() +except ValueError as e: + reveal_type(e) # revealed: ValueError +# error: [possibly-unresolved-reference] +reveal_type(e) # revealed: None + +def f(x: type[Exception]): + e = None + try: + raise x + except ValueError as e: + pass + except: + pass + # error: [possibly-unresolved-reference] + reveal_type(e) # revealed: None +``` diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index bb5f40d3be6dd2..5c0c5dc7ff3a1b 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -449,6 +449,12 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { } } + fn delete_binding(&mut self, place: ScopedPlaceId) { + let is_place_name = self.current_place_table().place_expr(place).is_name(); + self.current_use_def_map_mut() + .delete_binding(place, is_place_name); + } + /// Push a new [`Definition`] onto the list of definitions /// associated with the `definition_node` AST node. /// @@ -1817,7 +1823,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // If `handled_exceptions` above was `None`, it's something like `except as e:`, // which is invalid syntax. However, it's still pretty obvious here that the user // *wanted* `e` to be bound, so we should still create a definition here nonetheless. - if let Some(symbol_name) = symbol_name { + let symbol = if let Some(symbol_name) = symbol_name { let symbol = self.add_symbol(symbol_name.id.clone()); self.add_definition( @@ -1827,9 +1833,16 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { is_star: *is_star, }), ); - } + Some(symbol) + } else { + None + }; self.visit_body(handler_body); + // The caught exception is cleared at the end of the except clause + if let Some(symbol) = symbol { + self.delete_binding(symbol); + } // Each `except` block is mutually exclusive with all other `except` blocks. post_except_states.push(self.flow_snapshot()); @@ -1903,13 +1916,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { walk_stmt(self, stmt); } ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { + // We will check the target expressions and then delete them. + walk_stmt(self, stmt); for target in targets { if let Ok(target) = PlaceExpr::try_from(target) { let place_id = self.add_place(target); self.current_place_table().mark_place_used(place_id); + self.delete_binding(place_id); } } - walk_stmt(self, stmt); } ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) if self.in_module_scope() => { if let Some(expr) = dunder_all_extend_argument(value) { @@ -1956,7 +1971,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { } (ast::ExprContext::Load, _) => (true, false), (ast::ExprContext::Store, _) => (false, true), - (ast::ExprContext::Del, _) => (false, true), + (ast::ExprContext::Del, _) => (true, true), (ast::ExprContext::Invalid, _) => (false, false), }; let place_id = self.add_place(place_expr); diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index d1ea86b1c69e02..c0897c80092a76 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -6145,7 +6145,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_name_expression(&mut self, name: &ast::ExprName) -> Type<'db> { match name.ctx { ExprContext::Load => self.infer_name_load(name), - ExprContext::Store | ExprContext::Del => Type::Never, + ExprContext::Store => Type::Never, + ExprContext::Del => { + self.infer_name_load(name); + Type::Never + } ExprContext::Invalid => Type::unknown(), } } @@ -6254,10 +6258,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match ctx { ExprContext::Load => self.infer_attribute_load(attribute), - ExprContext::Store | ExprContext::Del => { + ExprContext::Store => { self.infer_expression(value); Type::Never } + ExprContext::Del => { + self.infer_attribute_load(attribute); + Type::Never + } ExprContext::Invalid => { self.infer_expression(value); Type::unknown() @@ -7646,12 +7654,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match ctx { ExprContext::Load => self.infer_subscript_load(subscript), - ExprContext::Store | ExprContext::Del => { + ExprContext::Store => { let value_ty = self.infer_expression(value); let slice_ty = self.infer_expression(slice); self.infer_subscript_expression_types(value, value_ty, slice_ty); Type::Never } + ExprContext::Del => { + self.infer_subscript_load(subscript); + Type::Never + } ExprContext::Invalid => { let value_ty = self.infer_expression(value); let slice_ty = self.infer_expression(slice); From 3c6c017950db492dd71e33b0e114fda5dcdfb308 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 12 Jun 2025 18:58:30 +0200 Subject: [PATCH 406/487] Centralize client options validation (#18623) --- crates/ruff_server/src/lib.rs | 2 +- crates/ruff_server/src/server.rs | 29 +- crates/ruff_server/src/session.rs | 30 +- crates/ruff_server/src/session/index.rs | 57 +- .../src/session/index/ruff_settings.rs | 21 +- crates/ruff_server/src/session/options.rs | 1005 +++++++++++++++++ crates/ruff_server/src/session/settings.rs | 973 +--------------- crates/ruff_server/src/workspace.rs | 41 +- crates/ruff_server/tests/notebook.rs | 9 +- 9 files changed, 1164 insertions(+), 1003 deletions(-) create mode 100644 crates/ruff_server/src/session/options.rs diff --git a/crates/ruff_server/src/lib.rs b/crates/ruff_server/src/lib.rs index 44d5e1bb52181c..ca4ee50ab892f0 100644 --- a/crates/ruff_server/src/lib.rs +++ b/crates/ruff_server/src/lib.rs @@ -3,7 +3,7 @@ pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument}; use lsp_types::CodeActionKind; pub use server::Server; -pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session}; +pub use session::{ClientOptions, DocumentQuery, DocumentSnapshot, GlobalOptions, Session}; pub use workspace::{Workspace, Workspaces}; #[macro_use] diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 6614565f034f59..9e9c462f3fd75b 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -30,7 +30,7 @@ use self::schedule::Scheduler; use self::schedule::Task; use self::schedule::event_loop_thread; use crate::PositionEncoding; -use crate::session::AllSettings; +use crate::session::AllOptions; use crate::session::Session; use crate::workspace::Workspaces; @@ -77,39 +77,36 @@ impl Server { .. } = init_params; - let mut all_settings = AllSettings::from_value( + let mut all_options = AllOptions::from_value( initialization_options .unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())), ); if let Some(preview) = preview { - all_settings.set_preview(preview); + all_options.set_preview(preview); } - let AllSettings { - global_settings, - workspace_settings, - } = all_settings; + let AllOptions { + global: global_options, + workspace: workspace_options, + } = all_options; crate::logging::init_logging( - global_settings.tracing.log_level.unwrap_or_default(), - global_settings.tracing.log_file.as_deref(), + global_options.tracing.log_level.unwrap_or_default(), + global_options.tracing.log_file.as_deref(), ); let workspaces = Workspaces::from_workspace_folders( workspace_folders, - workspace_settings.unwrap_or_default(), + workspace_options.unwrap_or_default(), )?; tracing::debug!("Negotiated position encoding: {position_encoding:?}"); + let global = global_options.into_settings(); + Ok(Self { connection, worker_threads, - session: Session::new( - &client_capabilities, - position_encoding, - global_settings, - &workspaces, - )?, + session: Session::new(&client_capabilities, position_encoding, global, &workspaces)?, client_capabilities, }) } diff --git a/crates/ruff_server/src/session.rs b/crates/ruff_server/src/session.rs index 00fd9d6013c032..79cc059b4c8d91 100644 --- a/crates/ruff_server/src/session.rs +++ b/crates/ruff_server/src/session.rs @@ -4,19 +4,21 @@ use std::path::Path; use std::sync::Arc; use lsp_types::{ClientCapabilities, FileEvent, NotebookDocumentCellChange, Url}; -use settings::ResolvedClientSettings; +use settings::ClientSettings; use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument}; +use crate::session::settings::GlobalClientSettings; use crate::workspace::Workspaces; use crate::{PositionEncoding, TextDocument}; pub(crate) use self::capabilities::ResolvedClientCapabilities; pub use self::index::DocumentQuery; -pub use self::settings::ClientSettings; -pub(crate) use self::settings::{AllSettings, WorkspaceSettingsMap}; +pub(crate) use self::options::{AllOptions, WorkspaceOptionsMap}; +pub use self::options::{ClientOptions, GlobalOptions}; mod capabilities; mod index; +mod options; mod settings; /// The global state for the LSP @@ -26,7 +28,8 @@ pub struct Session { /// The global position encoding, negotiated during LSP initialization. position_encoding: PositionEncoding, /// Global settings provided by the client. - global_settings: ClientSettings, + global_settings: GlobalClientSettings, + /// Tracks what LSP features the client supports and doesn't support. resolved_client_capabilities: Arc, } @@ -35,7 +38,7 @@ pub struct Session { /// a specific document. pub struct DocumentSnapshot { resolved_client_capabilities: Arc, - client_settings: settings::ResolvedClientSettings, + client_settings: Arc, document_ref: index::DocumentQuery, position_encoding: PositionEncoding, } @@ -44,13 +47,13 @@ impl Session { pub fn new( client_capabilities: &ClientCapabilities, position_encoding: PositionEncoding, - global_settings: ClientSettings, + global: GlobalClientSettings, workspaces: &Workspaces, ) -> crate::Result { Ok(Self { position_encoding, - index: index::Index::new(workspaces, &global_settings)?, - global_settings, + index: index::Index::new(workspaces, &global)?, + global_settings: global, resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new( client_capabilities, )), @@ -66,7 +69,10 @@ impl Session { let key = self.key_from_url(url); Some(DocumentSnapshot { resolved_client_capabilities: self.resolved_client_capabilities.clone(), - client_settings: self.index.client_settings(&key, &self.global_settings), + client_settings: self + .index + .client_settings(&key) + .unwrap_or_else(|| self.global_settings.to_settings_arc()), document_ref: self.index.make_document_ref(key, &self.global_settings)?, position_encoding: self.position_encoding, }) @@ -163,8 +169,8 @@ impl Session { } /// Returns the resolved global client settings. - pub(crate) fn global_client_settings(&self) -> ResolvedClientSettings { - ResolvedClientSettings::global(&self.global_settings) + pub(crate) fn global_client_settings(&self) -> &ClientSettings { + self.global_settings.to_settings() } /// Returns the number of open documents in the session. @@ -183,7 +189,7 @@ impl DocumentSnapshot { &self.resolved_client_capabilities } - pub(crate) fn client_settings(&self) -> &settings::ResolvedClientSettings { + pub(crate) fn client_settings(&self) -> &settings::ClientSettings { &self.client_settings } diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 8f1993e12c97b4..2e56499eccadf6 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -11,13 +11,15 @@ use thiserror::Error; pub(crate) use ruff_settings::RuffSettings; use crate::edit::LanguageId; +use crate::session::options::Combine; +use crate::session::settings::GlobalClientSettings; use crate::workspace::{Workspace, Workspaces}; use crate::{ PositionEncoding, TextDocument, edit::{DocumentKey, DocumentVersion, NotebookDocument}, }; -use super::{ClientSettings, settings::ResolvedClientSettings}; +use super::settings::ClientSettings; mod ruff_settings; @@ -36,7 +38,7 @@ pub(crate) struct Index { /// Settings associated with a workspace. struct WorkspaceSettings { - client_settings: ResolvedClientSettings, + client_settings: Arc, ruff_settings: ruff_settings::RuffSettingsIndex, } @@ -70,11 +72,11 @@ pub enum DocumentQuery { impl Index { pub(super) fn new( workspaces: &Workspaces, - global_settings: &ClientSettings, + global: &GlobalClientSettings, ) -> crate::Result { let mut settings = WorkspaceSettingsIndex::default(); for workspace in &**workspaces { - settings.register_workspace(workspace, global_settings)?; + settings.register_workspace(workspace, global)?; } Ok(Self { @@ -170,11 +172,11 @@ impl Index { pub(super) fn open_workspace_folder( &mut self, url: Url, - global_settings: &ClientSettings, + global: &GlobalClientSettings, ) -> crate::Result<()> { // TODO(jane): Find a way for workspace client settings to be added or changed dynamically. self.settings - .register_workspace(&Workspace::new(url), global_settings) + .register_workspace(&Workspace::new(url), global) } pub(super) fn close_workspace_folder(&mut self, workspace_url: &Url) -> crate::Result<()> { @@ -201,7 +203,7 @@ impl Index { pub(super) fn make_document_ref( &self, key: DocumentKey, - global_settings: &ClientSettings, + global: &GlobalClientSettings, ) -> Option { let url = self.url_for_key(&key)?.clone(); @@ -230,13 +232,12 @@ impl Index { "No settings available for {} - falling back to default settings", url ); - let resolved_global = ResolvedClientSettings::global(global_settings); // The path here is only for completeness, it's okay to use a non-existing path // in case this is an unsaved (untitled) document. let path = Path::new(url.path()); let root = path.parent().unwrap_or(path); Arc::new(RuffSettings::fallback( - resolved_global.editor_settings(), + global.to_settings().editor_settings(), root, )) }); @@ -330,21 +331,12 @@ impl Index { Ok(()) } - pub(super) fn client_settings( - &self, - key: &DocumentKey, - global_settings: &ClientSettings, - ) -> ResolvedClientSettings { - let Some(url) = self.url_for_key(key) else { - return ResolvedClientSettings::global(global_settings); - }; - let Some(WorkspaceSettings { + pub(super) fn client_settings(&self, key: &DocumentKey) -> Option> { + let url = self.url_for_key(key)?; + let WorkspaceSettings { client_settings, .. - }) = self.settings_for_url(url) - else { - return ResolvedClientSettings::global(global_settings); - }; - client_settings.clone() + } = self.settings_for_url(url)?; + Some(client_settings.clone()) } fn document_controller_for_key( @@ -422,7 +414,7 @@ impl WorkspaceSettingsIndex { fn register_workspace( &mut self, workspace: &Workspace, - global_settings: &ClientSettings, + global: &GlobalClientSettings, ) -> crate::Result<()> { let workspace_url = workspace.url(); if workspace_url.scheme() != "file" { @@ -434,10 +426,21 @@ impl WorkspaceSettingsIndex { anyhow!("Failed to convert workspace URL to file path: {workspace_url}") })?; - let client_settings = if let Some(workspace_settings) = workspace.settings() { - ResolvedClientSettings::with_workspace(workspace_settings, global_settings) + let client_settings = if let Some(workspace_options) = workspace.options() { + let options = workspace_options.clone().combine(global.options().clone()); + let settings = match options.into_settings() { + Ok(settings) => settings, + Err(settings) => { + show_err_msg!( + "The settings for the workspace {workspace_path} are invalid. Refer to the logs for more information.", + workspace_path = workspace_path.display() + ); + settings + } + }; + Arc::new(settings) } else { - ResolvedClientSettings::global(global_settings) + global.to_settings_arc() }; let workspace_settings_index = ruff_settings::RuffSettingsIndex::new( diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index 3156d6e0f07932..0f58ac2d941d60 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -18,9 +18,8 @@ use ruff_workspace::{ resolver::ConfigurationTransformer, }; -use crate::session::settings::{ - ConfigurationPreference, ResolvedConfiguration, ResolvedEditorSettings, -}; +use crate::session::options::ConfigurationPreference; +use crate::session::settings::{EditorSettings, ResolvedConfiguration}; #[derive(Debug)] pub struct RuffSettings { @@ -64,7 +63,7 @@ impl RuffSettings { /// /// In the absence of a valid configuration file, it gracefully falls back to /// editor-only settings. - pub(crate) fn fallback(editor_settings: &ResolvedEditorSettings, root: &Path) -> RuffSettings { + pub(crate) fn fallback(editor_settings: &EditorSettings, root: &Path) -> RuffSettings { struct FallbackTransformer<'a> { inner: EditorConfigurationTransformer<'a>, } @@ -122,14 +121,14 @@ impl RuffSettings { /// Constructs [`RuffSettings`] by merging the editor-defined settings with the /// default configuration. - fn editor_only(editor_settings: &ResolvedEditorSettings, root: &Path) -> RuffSettings { + fn editor_only(editor_settings: &EditorSettings, root: &Path) -> RuffSettings { Self::with_editor_settings(editor_settings, root, Configuration::default()) .expect("editor configuration should merge successfully with default configuration") } /// Merges the `configuration` with the editor defined settings. fn with_editor_settings( - editor_settings: &ResolvedEditorSettings, + editor_settings: &EditorSettings, root: &Path, configuration: Configuration, ) -> anyhow::Result { @@ -157,7 +156,7 @@ impl RuffSettingsIndex { /// skipping (3). pub(super) fn new( root: &Path, - editor_settings: &ResolvedEditorSettings, + editor_settings: &EditorSettings, is_default_workspace: bool, ) -> Self { if editor_settings.configuration_preference == ConfigurationPreference::EditorOnly { @@ -392,11 +391,11 @@ impl RuffSettingsIndex { } } -struct EditorConfigurationTransformer<'a>(&'a ResolvedEditorSettings, &'a Path); +struct EditorConfigurationTransformer<'a>(&'a EditorSettings, &'a Path); impl ConfigurationTransformer for EditorConfigurationTransformer<'_> { fn transform(&self, filesystem_configuration: Configuration) -> Configuration { - let ResolvedEditorSettings { + let EditorSettings { configuration, format_preview, lint_preview, @@ -515,7 +514,7 @@ mod tests { /// This test ensures that the inline configuration is correctly applied to the configuration. #[test] fn inline_settings() { - let editor_settings = ResolvedEditorSettings { + let editor_settings = EditorSettings { configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { line_length: Some(LineLength::try_from(120).unwrap()), ..Default::default() @@ -533,7 +532,7 @@ mod tests { /// settings is prioritized. #[test] fn inline_and_specific_settings_resolution_order() { - let editor_settings = ResolvedEditorSettings { + let editor_settings = EditorSettings { configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { line_length: Some(LineLength::try_from(120).unwrap()), ..Default::default() diff --git a/crates/ruff_server/src/session/options.rs b/crates/ruff_server/src/session/options.rs new file mode 100644 index 00000000000000..47ce2fa83e677e --- /dev/null +++ b/crates/ruff_server/src/session/options.rs @@ -0,0 +1,1005 @@ +use std::{path::PathBuf, str::FromStr as _}; + +use lsp_types::Url; +use rustc_hash::FxHashMap; +use serde::Deserialize; +use serde_json::{Map, Value}; + +use ruff_linter::{RuleSelector, line_width::LineLength, rule_selector::ParseError}; + +use crate::session::settings::{ + ClientSettings, EditorSettings, GlobalClientSettings, ResolvedConfiguration, +}; + +pub(crate) type WorkspaceOptionsMap = FxHashMap; + +/// Determines how multiple conflicting configurations should be resolved - in this +/// case, the configuration from the client settings and configuration from local +/// `.toml` files (aka 'workspace' configuration). +#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub(crate) enum ConfigurationPreference { + /// Configuration set in the editor takes priority over configuration set in `.toml` files. + #[default] + EditorFirst, + /// Configuration set in `.toml` files takes priority over configuration set in the editor. + FilesystemFirst, + /// `.toml` files are ignored completely, and only the editor configuration is used. + EditorOnly, +} + +/// A direct representation of of `configuration` schema within the client settings. +#[derive(Clone, Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(untagged)] +pub(super) enum ClientConfiguration { + /// A path to a configuration file. + String(String), + /// An object containing the configuration options. + Object(Map), +} + +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub struct GlobalOptions { + #[serde(flatten)] + client: ClientOptions, + + // These settings are only needed for tracing, and are only read from the global configuration. + // These will not be in the resolved settings. + #[serde(flatten)] + pub(crate) tracing: TracingOptions, +} + +impl GlobalOptions { + pub(crate) fn set_preview(&mut self, preview: bool) { + self.client.set_preview(preview); + } + + #[cfg(test)] + pub(crate) fn client(&self) -> &ClientOptions { + &self.client + } + + pub fn into_settings(self) -> GlobalClientSettings { + GlobalClientSettings { + options: self.client, + settings: std::cell::OnceCell::default(), + } + } +} + +/// This is a direct representation of the settings schema sent by the client. +#[derive(Clone, Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub struct ClientOptions { + configuration: Option, + fix_all: Option, + organize_imports: Option, + lint: Option, + format: Option, + code_action: Option, + exclude: Option>, + line_length: Option, + configuration_preference: Option, + + /// If `true` or [`None`], show syntax errors as diagnostics. + /// + /// This is useful when using Ruff with other language servers, allowing the user to refer + /// to syntax errors from only one source. + show_syntax_errors: Option, +} + +impl ClientOptions { + /// Resolves the options. + /// + /// Returns `Ok` if all options are valid. Otherwise, returns `Err` with the partially resolved settings + /// (ignoring any invalid settings). Error messages about the invalid settings are logged with tracing. + #[expect( + clippy::result_large_err, + reason = "The error is as large as the Ok variant" + )] + pub(crate) fn into_settings(self) -> Result { + let code_action = self.code_action.unwrap_or_default(); + let lint = self.lint.unwrap_or_default(); + let format = self.format.unwrap_or_default(); + let mut contains_invalid_settings = false; + + let configuration = self.configuration.and_then(|configuration| { + match ResolvedConfiguration::try_from(configuration) { + Ok(configuration) => Some(configuration), + Err(err) => { + tracing::error!("Failed to load settings from `configuration`: {err}"); + contains_invalid_settings = true; + None + } + } + }); + + let editor_settings = EditorSettings { + configuration, + lint_preview: lint.preview, + format_preview: format.preview, + select: lint.select.and_then(|select| { + Self::resolve_rules( + &select, + RuleSelectorKey::Select, + &mut contains_invalid_settings, + ) + }), + extend_select: lint.extend_select.and_then(|select| { + Self::resolve_rules( + &select, + RuleSelectorKey::ExtendSelect, + &mut contains_invalid_settings, + ) + }), + ignore: lint.ignore.and_then(|ignore| { + Self::resolve_rules( + &ignore, + RuleSelectorKey::Ignore, + &mut contains_invalid_settings, + ) + }), + exclude: self.exclude.clone(), + line_length: self.line_length, + configuration_preference: self.configuration_preference.unwrap_or_default(), + }; + + let resolved = ClientSettings { + editor_settings, + fix_all: self.fix_all.unwrap_or(true), + organize_imports: self.organize_imports.unwrap_or(true), + lint_enable: lint.enable.unwrap_or(true), + disable_rule_comment_enable: code_action + .disable_rule_comment + .and_then(|disable| disable.enable) + .unwrap_or(true), + fix_violation_enable: code_action + .fix_violation + .and_then(|fix| fix.enable) + .unwrap_or(true), + + show_syntax_errors: self.show_syntax_errors.unwrap_or(true), + }; + + if contains_invalid_settings { + Err(resolved) + } else { + Ok(resolved) + } + } + + fn resolve_rules( + rules: &[String], + key: RuleSelectorKey, + contains_invalid_settings: &mut bool, + ) -> Option> { + let (mut known, mut unknown) = (vec![], vec![]); + for rule in rules { + match RuleSelector::from_str(rule) { + Ok(selector) => known.push(selector), + Err(ParseError::Unknown(_)) => unknown.push(rule), + } + } + if !unknown.is_empty() { + *contains_invalid_settings = true; + tracing::error!("Unknown rule selectors found in `{key}`: {unknown:?}"); + } + if known.is_empty() { None } else { Some(known) } + } + + /// Update the preview flag for the linter and the formatter with the given value. + pub(crate) fn set_preview(&mut self, preview: bool) { + match self.lint.as_mut() { + None => self.lint = Some(LintOptions::default().with_preview(preview)), + Some(lint) => lint.set_preview(preview), + } + match self.format.as_mut() { + None => self.format = Some(FormatOptions::default().with_preview(preview)), + Some(format) => format.set_preview(preview), + } + } +} + +impl Combine for ClientOptions { + fn combine_with(&mut self, other: Self) { + self.configuration.combine_with(other.configuration); + self.fix_all.combine_with(other.fix_all); + self.organize_imports.combine_with(other.organize_imports); + self.lint.combine_with(other.lint); + self.format.combine_with(other.format); + self.code_action.combine_with(other.code_action); + self.exclude.combine_with(other.exclude); + self.line_length.combine_with(other.line_length); + self.configuration_preference + .combine_with(other.configuration_preference); + self.show_syntax_errors + .combine_with(other.show_syntax_errors); + } +} + +/// Settings needed to initialize tracing. These will only be +/// read from the global configuration. +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub(crate) struct TracingOptions { + pub(crate) log_level: Option, + /// Path to the log file - tildes and environment variables are supported. + pub(crate) log_file: Option, +} + +/// This is a direct representation of the workspace settings schema, +/// which inherits the schema of [`ClientOptions`] and adds extra fields +/// to describe the workspace it applies to. +#[derive(Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct WorkspaceOptions { + #[serde(flatten)] + options: ClientOptions, + workspace: Url, +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct LintOptions { + enable: Option, + preview: Option, + select: Option>, + extend_select: Option>, + ignore: Option>, +} + +impl LintOptions { + fn with_preview(mut self, preview: bool) -> LintOptions { + self.preview = Some(preview); + self + } + + fn set_preview(&mut self, preview: bool) { + self.preview = Some(preview); + } +} + +impl Combine for LintOptions { + fn combine_with(&mut self, other: Self) { + self.enable.combine_with(other.enable); + self.preview.combine_with(other.preview); + self.select.combine_with(other.select); + self.extend_select.combine_with(other.extend_select); + self.ignore.combine_with(other.ignore); + } +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct FormatOptions { + preview: Option, +} + +impl Combine for FormatOptions { + fn combine_with(&mut self, other: Self) { + self.preview.combine_with(other.preview); + } +} + +impl FormatOptions { + fn with_preview(mut self, preview: bool) -> FormatOptions { + self.preview = Some(preview); + self + } + + fn set_preview(&mut self, preview: bool) { + self.preview = Some(preview); + } +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct CodeActionOptions { + disable_rule_comment: Option, + fix_violation: Option, +} + +impl Combine for CodeActionOptions { + fn combine_with(&mut self, other: Self) { + self.disable_rule_comment + .combine_with(other.disable_rule_comment); + self.fix_violation.combine_with(other.fix_violation); + } +} + +#[derive(Clone, Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct CodeActionParameters { + enable: Option, +} + +impl Combine for CodeActionParameters { + fn combine_with(&mut self, other: Self) { + self.enable.combine_with(other.enable); + } +} + +/// This is the exact schema for initialization options sent in by the client +/// during initialization. +#[derive(Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(untagged)] +enum InitializationOptions { + #[serde(rename_all = "camelCase")] + HasWorkspaces { + #[serde(rename = "globalSettings")] + global: GlobalOptions, + #[serde(rename = "settings")] + workspace: Vec, + }, + GlobalOnly { + #[serde(default)] + settings: GlobalOptions, + }, +} + +impl Default for InitializationOptions { + fn default() -> Self { + Self::GlobalOnly { + settings: GlobalOptions::default(), + } + } +} + +/// Built from the initialization options provided by the client. +#[derive(Debug)] +pub(crate) struct AllOptions { + pub(crate) global: GlobalOptions, + /// If this is `None`, the client only passed in global settings. + pub(crate) workspace: Option, +} + +impl AllOptions { + /// Initializes the controller from the serialized initialization options. + /// This fails if `options` are not valid initialization options. + pub(crate) fn from_value(options: serde_json::Value) -> Self { + Self::from_init_options( + serde_json::from_value(options) + .map_err(|err| { + tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); + show_err_msg!("Ruff received invalid client settings - falling back to default client settings."); + }) + .unwrap_or_default(), + ) + } + + /// Update the preview flag for both the global and all workspace settings. + pub(crate) fn set_preview(&mut self, preview: bool) { + self.global.set_preview(preview); + if let Some(workspace_options) = self.workspace.as_mut() { + for options in workspace_options.values_mut() { + options.set_preview(preview); + } + } + } + + fn from_init_options(options: InitializationOptions) -> Self { + let (global_options, workspace_options) = match options { + InitializationOptions::GlobalOnly { settings: options } => (options, None), + InitializationOptions::HasWorkspaces { + global: global_options, + workspace: workspace_options, + } => (global_options, Some(workspace_options)), + }; + + Self { + global: global_options, + workspace: workspace_options.map(|workspace_options| { + workspace_options + .into_iter() + .map(|workspace_options| { + (workspace_options.workspace, workspace_options.options) + }) + .collect() + }), + } + } +} + +#[derive(Copy, Clone)] +enum RuleSelectorKey { + Select, + ExtendSelect, + Ignore, +} + +impl std::fmt::Display for RuleSelectorKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RuleSelectorKey::Select => f.write_str("lint.select"), + RuleSelectorKey::ExtendSelect => f.write_str("lint.extendSelect"), + RuleSelectorKey::Ignore => f.write_str("lint.ignore"), + } + } +} + +pub(crate) trait Combine { + #[must_use] + fn combine(mut self, other: Self) -> Self + where + Self: Sized, + { + self.combine_with(other); + self + } + + fn combine_with(&mut self, other: Self); +} + +impl Combine for Option +where + T: Combine, +{ + fn combine(self, other: Self) -> Self + where + Self: Sized, + { + match (self, other) { + (Some(a), Some(b)) => Some(a.combine(b)), + (None, Some(b)) => Some(b), + (a, _) => a, + } + } + + fn combine_with(&mut self, other: Self) { + match (self, other) { + (Some(a), Some(b)) => { + a.combine_with(b); + } + (a @ None, Some(b)) => { + *a = Some(b); + } + _ => {} + } + } +} + +impl Combine for Vec { + fn combine_with(&mut self, _other: Self) { + // No-op, use own elements + } +} + +/// Implements [`Combine`] for a value that always returns `self` when combined with another value. +macro_rules! impl_noop_combine { + ($name:ident) => { + impl Combine for $name { + #[inline(always)] + fn combine_with(&mut self, _other: Self) {} + + #[inline(always)] + fn combine(self, _other: Self) -> Self { + self + } + } + }; +} + +// std types +impl_noop_combine!(bool); +impl_noop_combine!(usize); +impl_noop_combine!(u8); +impl_noop_combine!(u16); +impl_noop_combine!(u32); +impl_noop_combine!(u64); +impl_noop_combine!(u128); +impl_noop_combine!(isize); +impl_noop_combine!(i8); +impl_noop_combine!(i16); +impl_noop_combine!(i32); +impl_noop_combine!(i64); +impl_noop_combine!(i128); +impl_noop_combine!(String); + +// Custom types +impl_noop_combine!(ConfigurationPreference); +impl_noop_combine!(ClientConfiguration); +impl_noop_combine!(LineLength); + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + use ruff_python_formatter::QuoteStyle; + use ruff_workspace::options::{ + FormatOptions as RuffFormatOptions, LintCommonOptions, LintOptions, Options, + }; + use serde::de::DeserializeOwned; + + #[cfg(not(windows))] + use ruff_linter::registry::Linter; + + use super::*; + + #[cfg(not(windows))] + const VS_CODE_INIT_OPTIONS_FIXTURE: &str = + include_str!("../../resources/test/fixtures/settings/vs_code_initialization_options.json"); + const GLOBAL_ONLY_INIT_OPTIONS_FIXTURE: &str = + include_str!("../../resources/test/fixtures/settings/global_only.json"); + const EMPTY_INIT_OPTIONS_FIXTURE: &str = + include_str!("../../resources/test/fixtures/settings/empty.json"); + + // This fixture contains multiple workspaces with empty initialization options. It only sets + // the `cwd` and the `workspace` value. + const EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE: &str = + include_str!("../../resources/test/fixtures/settings/empty_multiple_workspace.json"); + + const INLINE_CONFIGURATION_FIXTURE: &str = + include_str!("../../resources/test/fixtures/settings/inline_configuration.json"); + + fn deserialize_fixture(content: &str) -> T { + serde_json::from_str(content).expect("test fixture JSON should deserialize") + } + + #[cfg(not(windows))] + #[test] + fn test_vs_code_init_options_deserialize() { + let options: InitializationOptions = deserialize_fixture(VS_CODE_INIT_OPTIONS_FIXTURE); + + assert_debug_snapshot!(options, @r#" + HasWorkspaces { + global: GlobalOptions { + client: ClientOptions { + configuration: None, + fix_all: Some( + false, + ), + organize_imports: Some( + true, + ), + lint: Some( + LintOptions { + enable: Some( + true, + ), + preview: Some( + true, + ), + select: Some( + [ + "F", + "I", + ], + ), + extend_select: None, + ignore: None, + }, + ), + format: Some( + FormatOptions { + preview: None, + }, + ), + code_action: Some( + CodeActionOptions { + disable_rule_comment: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + fix_violation: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + }, + ), + exclude: None, + line_length: None, + configuration_preference: None, + show_syntax_errors: Some( + true, + ), + }, + tracing: TracingOptions { + log_level: None, + log_file: None, + }, + }, + workspace: [ + WorkspaceOptions { + options: ClientOptions { + configuration: None, + fix_all: Some( + true, + ), + organize_imports: Some( + true, + ), + lint: Some( + LintOptions { + enable: Some( + true, + ), + preview: None, + select: None, + extend_select: None, + ignore: None, + }, + ), + format: Some( + FormatOptions { + preview: None, + }, + ), + code_action: Some( + CodeActionOptions { + disable_rule_comment: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + fix_violation: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + }, + ), + exclude: None, + line_length: None, + configuration_preference: None, + show_syntax_errors: Some( + true, + ), + }, + workspace: Url { + scheme: "file", + cannot_be_a_base: false, + username: "", + password: None, + host: None, + port: None, + path: "/Users/test/projects/pandas", + query: None, + fragment: None, + }, + }, + WorkspaceOptions { + options: ClientOptions { + configuration: None, + fix_all: Some( + true, + ), + organize_imports: Some( + true, + ), + lint: Some( + LintOptions { + enable: Some( + true, + ), + preview: Some( + false, + ), + select: None, + extend_select: None, + ignore: None, + }, + ), + format: Some( + FormatOptions { + preview: None, + }, + ), + code_action: Some( + CodeActionOptions { + disable_rule_comment: Some( + CodeActionParameters { + enable: Some( + true, + ), + }, + ), + fix_violation: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + }, + ), + exclude: None, + line_length: None, + configuration_preference: None, + show_syntax_errors: Some( + true, + ), + }, + workspace: Url { + scheme: "file", + cannot_be_a_base: false, + username: "", + password: None, + host: None, + port: None, + path: "/Users/test/projects/scipy", + query: None, + fragment: None, + }, + }, + ], + } + "#); + } + + #[cfg(not(windows))] + #[test] + fn test_vs_code_workspace_settings_resolve() { + let options = deserialize_fixture(VS_CODE_INIT_OPTIONS_FIXTURE); + let AllOptions { + global, + workspace: workspace_options, + } = AllOptions::from_init_options(options); + let path = + Url::from_str("file:///Users/test/projects/pandas").expect("path should be valid"); + let all_workspace_options = workspace_options.expect("workspace options should exist"); + + let workspace_options = all_workspace_options + .get(&path) + .expect("workspace options should exist") + .clone(); + let workspace_settings = workspace_options + .combine(global.client().clone()) + .into_settings() + .unwrap(); + + assert_eq!( + workspace_settings, + ClientSettings { + fix_all: true, + organize_imports: true, + lint_enable: true, + disable_rule_comment_enable: false, + fix_violation_enable: false, + show_syntax_errors: true, + editor_settings: EditorSettings { + configuration: None, + lint_preview: Some(true), + format_preview: None, + select: Some(vec![ + RuleSelector::Linter(Linter::Pyflakes), + RuleSelector::Linter(Linter::Isort) + ]), + extend_select: None, + ignore: None, + exclude: None, + line_length: None, + configuration_preference: ConfigurationPreference::default(), + }, + } + ); + let path = + Url::from_str("file:///Users/test/projects/scipy").expect("path should be valid"); + let workspace_options = all_workspace_options + .get(&path) + .expect("workspace setting should exist") + .clone(); + + let workspace_settings = workspace_options + .combine(global.client().clone()) + .into_settings() + .unwrap(); + + assert_eq!( + workspace_settings, + ClientSettings { + fix_all: true, + organize_imports: true, + lint_enable: true, + disable_rule_comment_enable: true, + fix_violation_enable: false, + show_syntax_errors: true, + editor_settings: EditorSettings { + configuration: None, + lint_preview: Some(false), + format_preview: None, + select: Some(vec![ + RuleSelector::Linter(Linter::Pyflakes), + RuleSelector::Linter(Linter::Isort) + ]), + extend_select: None, + ignore: None, + exclude: None, + line_length: None, + configuration_preference: ConfigurationPreference::EditorFirst, + }, + } + ); + } + + #[test] + fn test_global_only_init_options_deserialize() { + let options: InitializationOptions = deserialize_fixture(GLOBAL_ONLY_INIT_OPTIONS_FIXTURE); + + assert_debug_snapshot!(options, @r#" + GlobalOnly { + settings: GlobalOptions { + client: ClientOptions { + configuration: None, + fix_all: Some( + false, + ), + organize_imports: None, + lint: Some( + LintOptions { + enable: None, + preview: None, + select: None, + extend_select: None, + ignore: Some( + [ + "RUF001", + ], + ), + }, + ), + format: None, + code_action: Some( + CodeActionOptions { + disable_rule_comment: Some( + CodeActionParameters { + enable: Some( + false, + ), + }, + ), + fix_violation: None, + }, + ), + exclude: Some( + [ + "third_party", + ], + ), + line_length: Some( + LineLength( + 80, + ), + ), + configuration_preference: None, + show_syntax_errors: None, + }, + tracing: TracingOptions { + log_level: Some( + Warn, + ), + log_file: None, + }, + }, + } + "#); + } + + #[test] + fn test_global_only_resolves_correctly() { + let options = deserialize_fixture(GLOBAL_ONLY_INIT_OPTIONS_FIXTURE); + + let AllOptions { global, .. } = AllOptions::from_init_options(options); + let global = global.into_settings(); + assert_eq!( + global.to_settings(), + &ClientSettings { + fix_all: false, + organize_imports: true, + lint_enable: true, + disable_rule_comment_enable: false, + fix_violation_enable: true, + show_syntax_errors: true, + editor_settings: EditorSettings { + configuration: None, + lint_preview: None, + format_preview: None, + select: None, + extend_select: None, + ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), + exclude: Some(vec!["third_party".into()]), + line_length: Some(LineLength::try_from(80).unwrap()), + configuration_preference: ConfigurationPreference::EditorFirst, + }, + } + ); + } + + #[test] + fn test_empty_init_options_deserialize() { + let options: InitializationOptions = deserialize_fixture(EMPTY_INIT_OPTIONS_FIXTURE); + + assert_eq!(options, InitializationOptions::default()); + } + + fn assert_preview_client_options(options: &ClientOptions, preview: bool) { + assert_eq!(options.lint.as_ref().unwrap().preview.unwrap(), preview); + assert_eq!(options.format.as_ref().unwrap().preview.unwrap(), preview); + } + + fn assert_preview_all_options(all_options: &AllOptions, preview: bool) { + assert_preview_client_options(all_options.global.client(), preview); + if let Some(workspace_options) = all_options.workspace.as_ref() { + for options in workspace_options.values() { + assert_preview_client_options(options, preview); + } + } + } + + #[test] + fn test_preview_flag() { + let options = deserialize_fixture(EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE); + let mut all_options = AllOptions::from_init_options(options); + + all_options.set_preview(false); + assert_preview_all_options(&all_options, false); + + all_options.set_preview(true); + assert_preview_all_options(&all_options, true); + } + + #[test] + fn inline_configuration() { + let options: InitializationOptions = deserialize_fixture(INLINE_CONFIGURATION_FIXTURE); + + let AllOptions { + global, + workspace: None, + } = AllOptions::from_init_options(options) + else { + panic!("Expected global settings only"); + }; + + let global = global.into_settings(); + + assert_eq!( + global.to_settings(), + &ClientSettings { + fix_all: true, + organize_imports: true, + lint_enable: true, + disable_rule_comment_enable: true, + fix_violation_enable: true, + show_syntax_errors: true, + editor_settings: EditorSettings { + configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { + line_length: Some(LineLength::try_from(100).unwrap()), + lint: Some(LintOptions { + common: LintCommonOptions { + extend_select: Some(vec![RuleSelector::from_str("I001").unwrap()]), + ..Default::default() + }, + ..Default::default() + }), + format: Some(RuffFormatOptions { + quote_style: Some(QuoteStyle::Single), + ..Default::default() + }), + ..Default::default() + }))), + extend_select: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), + ..Default::default() + } + } + ); + } +} diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs index f251594a0aa33f..4015109e74df80 100644 --- a/crates/ruff_server/src/session/settings.rs +++ b/crates/ruff_server/src/session/settings.rs @@ -1,18 +1,58 @@ -use std::{ops::Deref, path::PathBuf, str::FromStr}; +use std::{path::PathBuf, sync::Arc}; -use lsp_types::Url; -use rustc_hash::FxHashMap; -use serde::Deserialize; -use serde_json::{Map, Value}; use thiserror::Error; use ruff_linter::RuleSelector; use ruff_linter::line_width::LineLength; -use ruff_linter::rule_selector::ParseError; use ruff_workspace::options::Options; -/// Maps a workspace URI to its associated client settings. Used during server initialization. -pub(crate) type WorkspaceSettingsMap = FxHashMap; +use crate::{ + ClientOptions, + session::options::{ClientConfiguration, ConfigurationPreference}, +}; + +pub struct GlobalClientSettings { + pub(super) options: ClientOptions, + + /// Lazily initialized client settings to avoid showing error warnings + /// when a field of the global settings has any errors but the field is overridden + /// in the workspace settings. This can avoid showing unnecessary errors + /// when the workspace settings e.g. select some rules that aren't available in a specific workspace + /// and said workspace overrides the selected rules. + pub(super) settings: std::cell::OnceCell>, +} + +impl GlobalClientSettings { + pub(super) fn options(&self) -> &ClientOptions { + &self.options + } + + fn settings_impl(&self) -> &Arc { + self.settings.get_or_init(|| { + let settings = self.options.clone().into_settings(); + let settings = match settings { + Ok(settings) => settings, + Err(settings) => { + show_err_msg!( + "Ruff received invalid settings from the editor. Refer to the logs for more information." + ); + settings + } + }; + Arc::new(settings) + }) + } + + /// Lazily resolves the client options to the settings. + pub(super) fn to_settings(&self) -> &ClientSettings { + self.settings_impl() + } + + /// Lazily resolves the client options to the settings. + pub(super) fn to_settings_arc(&self) -> Arc { + self.settings_impl().clone() + } +} /// Resolved client settings for a specific document. These settings are meant to be /// used directly by the server, and are *not* a 1:1 representation with how the client @@ -20,14 +60,14 @@ pub(crate) type WorkspaceSettingsMap = FxHashMap; #[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq, Eq))] #[expect(clippy::struct_excessive_bools)] -pub(crate) struct ResolvedClientSettings { - fix_all: bool, - organize_imports: bool, - lint_enable: bool, - disable_rule_comment_enable: bool, - fix_violation_enable: bool, - show_syntax_errors: bool, - editor_settings: ResolvedEditorSettings, +pub(crate) struct ClientSettings { + pub(super) fix_all: bool, + pub(super) organize_imports: bool, + pub(super) lint_enable: bool, + pub(super) disable_rule_comment_enable: bool, + pub(super) fix_violation_enable: bool, + pub(super) show_syntax_errors: bool, + pub(super) editor_settings: EditorSettings, } /// Contains the resolved values of 'editor settings' - Ruff configuration for the linter/formatter that was passed in via @@ -35,7 +75,7 @@ pub(crate) struct ResolvedClientSettings { /// if these were un-set. #[derive(Clone, Debug)] #[cfg_attr(test, derive(Default, PartialEq, Eq))] -pub(crate) struct ResolvedEditorSettings { +pub(crate) struct EditorSettings { pub(super) configuration: Option, pub(super) lint_preview: Option, pub(super) format_preview: Option, @@ -55,13 +95,13 @@ pub(crate) enum ResolvedConfiguration { Inline(Box), } -impl TryFrom<&ClientConfiguration> for ResolvedConfiguration { +impl TryFrom for ResolvedConfiguration { type Error = ResolvedConfigurationError; - fn try_from(value: &ClientConfiguration) -> Result { + fn try_from(value: ClientConfiguration) -> Result { match value { ClientConfiguration::String(path) => Ok(ResolvedConfiguration::FilePath( - PathBuf::from(shellexpand::full(path)?.as_ref()), + PathBuf::from(shellexpand::full(&path)?.as_ref()), )), ClientConfiguration::Object(map) => { let options = toml::Table::try_from(map)?.try_into::()?; @@ -89,404 +129,7 @@ pub(crate) enum ResolvedConfigurationError { ExtendNotSupported, } -/// Determines how multiple conflicting configurations should be resolved - in this -/// case, the configuration from the client settings and configuration from local -/// `.toml` files (aka 'workspace' configuration). -#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub(crate) enum ConfigurationPreference { - /// Configuration set in the editor takes priority over configuration set in `.toml` files. - #[default] - EditorFirst, - /// Configuration set in `.toml` files takes priority over configuration set in the editor. - FilesystemFirst, - /// `.toml` files are ignored completely, and only the editor configuration is used. - EditorOnly, -} - -/// A direct representation of of `configuration` schema within the client settings. -#[derive(Debug, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(untagged)] -enum ClientConfiguration { - /// A path to a configuration file. - String(String), - /// An object containing the configuration options. - Object(Map), -} - -/// This is a direct representation of the settings schema sent by the client. -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -pub struct ClientSettings { - configuration: Option, - fix_all: Option, - organize_imports: Option, - lint: Option, - format: Option, - code_action: Option, - exclude: Option>, - line_length: Option, - configuration_preference: Option, - - /// If `true` or [`None`], show syntax errors as diagnostics. - /// - /// This is useful when using Ruff with other language servers, allowing the user to refer - /// to syntax errors from only one source. - show_syntax_errors: Option, - - // These settings are only needed for tracing, and are only read from the global configuration. - // These will not be in the resolved settings. - #[serde(flatten)] - pub(crate) tracing: TracingSettings, -} - impl ClientSettings { - /// Update the preview flag for the linter and the formatter with the given value. - pub(crate) fn set_preview(&mut self, preview: bool) { - match self.lint.as_mut() { - None => self.lint = Some(LintOptions::default().with_preview(preview)), - Some(lint) => lint.set_preview(preview), - } - match self.format.as_mut() { - None => self.format = Some(FormatOptions::default().with_preview(preview)), - Some(format) => format.set_preview(preview), - } - } -} - -/// Settings needed to initialize tracing. These will only be -/// read from the global configuration. -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -pub(crate) struct TracingSettings { - pub(crate) log_level: Option, - /// Path to the log file - tildes and environment variables are supported. - pub(crate) log_file: Option, -} - -/// This is a direct representation of the workspace settings schema, -/// which inherits the schema of [`ClientSettings`] and adds extra fields -/// to describe the workspace it applies to. -#[derive(Debug, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct WorkspaceSettings { - #[serde(flatten)] - settings: ClientSettings, - workspace: Url, -} - -#[derive(Debug, Default, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct LintOptions { - enable: Option, - preview: Option, - select: Option>, - extend_select: Option>, - ignore: Option>, -} - -impl LintOptions { - fn with_preview(mut self, preview: bool) -> LintOptions { - self.preview = Some(preview); - self - } - - fn set_preview(&mut self, preview: bool) { - self.preview = Some(preview); - } -} - -#[derive(Debug, Default, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct FormatOptions { - preview: Option, -} - -impl FormatOptions { - fn with_preview(mut self, preview: bool) -> FormatOptions { - self.preview = Some(preview); - self - } - - fn set_preview(&mut self, preview: bool) { - self.preview = Some(preview); - } -} - -#[derive(Debug, Default, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct CodeActionOptions { - disable_rule_comment: Option, - fix_violation: Option, -} - -#[derive(Debug, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct CodeActionParameters { - enable: Option, -} - -/// This is the exact schema for initialization options sent in by the client -/// during initialization. -#[derive(Debug, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(untagged)] -enum InitializationOptions { - #[serde(rename_all = "camelCase")] - HasWorkspaces { - global_settings: ClientSettings, - #[serde(rename = "settings")] - workspace_settings: Vec, - }, - GlobalOnly { - #[serde(default)] - settings: ClientSettings, - }, -} - -/// Built from the initialization options provided by the client. -#[derive(Debug)] -pub(crate) struct AllSettings { - pub(crate) global_settings: ClientSettings, - /// If this is `None`, the client only passed in global settings. - pub(crate) workspace_settings: Option, -} - -impl AllSettings { - /// Initializes the controller from the serialized initialization options. - /// This fails if `options` are not valid initialization options. - pub(crate) fn from_value(options: serde_json::Value) -> Self { - Self::from_init_options( - serde_json::from_value(options) - .map_err(|err| { - tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); - show_err_msg!("Ruff received invalid client settings - falling back to default client settings."); - }) - .unwrap_or_default(), - ) - } - - /// Update the preview flag for both the global and all workspace settings. - pub(crate) fn set_preview(&mut self, preview: bool) { - self.global_settings.set_preview(preview); - if let Some(workspace_settings) = self.workspace_settings.as_mut() { - for settings in workspace_settings.values_mut() { - settings.set_preview(preview); - } - } - } - - fn from_init_options(options: InitializationOptions) -> Self { - let (global_settings, workspace_settings) = match options { - InitializationOptions::GlobalOnly { settings } => (settings, None), - InitializationOptions::HasWorkspaces { - global_settings, - workspace_settings, - } => (global_settings, Some(workspace_settings)), - }; - - Self { - global_settings, - workspace_settings: workspace_settings.map(|workspace_settings| { - workspace_settings - .into_iter() - .map(|settings| (settings.workspace, settings.settings)) - .collect() - }), - } - } -} - -impl ResolvedClientSettings { - /// Resolves a series of client settings, prioritizing workspace settings over global settings. - /// Any fields not specified by either are set to their defaults. - pub(super) fn with_workspace( - workspace_settings: &ClientSettings, - global_settings: &ClientSettings, - ) -> Self { - Self::new_impl(&[workspace_settings, global_settings]) - } - - /// Resolves global settings only. - pub(super) fn global(global_settings: &ClientSettings) -> Self { - Self::new_impl(&[global_settings]) - } - - fn new_impl(all_settings: &[&ClientSettings]) -> Self { - let mut contains_invalid_settings = false; - - let settings = Self { - fix_all: Self::resolve_or(all_settings, |settings| settings.fix_all, true), - organize_imports: Self::resolve_or( - all_settings, - |settings| settings.organize_imports, - true, - ), - lint_enable: Self::resolve_or( - all_settings, - |settings| settings.lint.as_ref()?.enable, - true, - ), - disable_rule_comment_enable: Self::resolve_or( - all_settings, - |settings| { - settings - .code_action - .as_ref()? - .disable_rule_comment - .as_ref()? - .enable - }, - true, - ), - fix_violation_enable: Self::resolve_or( - all_settings, - |settings| { - settings - .code_action - .as_ref()? - .fix_violation - .as_ref()? - .enable - }, - true, - ), - show_syntax_errors: Self::resolve_or( - all_settings, - |settings| settings.show_syntax_errors, - true, - ), - editor_settings: ResolvedEditorSettings { - configuration: Self::resolve_optional(all_settings, |settings| { - settings.configuration.as_ref().and_then(|configuration| { - match ResolvedConfiguration::try_from(configuration) { - Ok(configuration) => Some(configuration), - Err(err) => { - tracing::error!( - "Failed to load settings from `configuration`: {err}" - ); - contains_invalid_settings = true; - None - } - } - }) - }), - lint_preview: Self::resolve_optional(all_settings, |settings| { - settings.lint.as_ref()?.preview - }), - format_preview: Self::resolve_optional(all_settings, |settings| { - settings.format.as_ref()?.preview - }), - select: Self::resolve_optional(all_settings, |settings| { - Self::resolve_rules( - settings.lint.as_ref()?.select.as_ref()?, - RuleSelectorKey::Select, - &mut contains_invalid_settings, - ) - }), - extend_select: Self::resolve_optional(all_settings, |settings| { - Self::resolve_rules( - settings.lint.as_ref()?.extend_select.as_ref()?, - RuleSelectorKey::ExtendSelect, - &mut contains_invalid_settings, - ) - }), - ignore: Self::resolve_optional(all_settings, |settings| { - Self::resolve_rules( - settings.lint.as_ref()?.ignore.as_ref()?, - RuleSelectorKey::Ignore, - &mut contains_invalid_settings, - ) - }), - exclude: Self::resolve_optional(all_settings, |settings| settings.exclude.clone()), - line_length: Self::resolve_optional(all_settings, |settings| settings.line_length), - configuration_preference: Self::resolve_or( - all_settings, - |settings| settings.configuration_preference, - ConfigurationPreference::EditorFirst, - ), - }, - }; - - if contains_invalid_settings { - show_err_msg!( - "Ruff received invalid settings from the editor. Refer to the logs for more information." - ); - } - - settings - } - - fn resolve_rules( - rules: &[String], - key: RuleSelectorKey, - contains_invalid_settings: &mut bool, - ) -> Option> { - let (mut known, mut unknown) = (vec![], vec![]); - for rule in rules { - match RuleSelector::from_str(rule) { - Ok(selector) => known.push(selector), - Err(ParseError::Unknown(_)) => unknown.push(rule), - } - } - if !unknown.is_empty() { - *contains_invalid_settings = true; - tracing::error!("Unknown rule selectors found in `{key}`: {unknown:?}"); - } - if known.is_empty() { None } else { Some(known) } - } - - /// Attempts to resolve a setting using a list of available client settings as sources. - /// Client settings that come earlier in the list take priority. This function is for fields - /// that do not have a default value and should be left unset. - /// Use [`ResolvedClientSettings::resolve_or`] for settings that should have default values. - fn resolve_optional( - all_settings: &[&ClientSettings], - get: impl FnMut(&ClientSettings) -> Option, - ) -> Option { - all_settings.iter().map(Deref::deref).find_map(get) - } - - /// Attempts to resolve a setting using a list of available client settings as sources. - /// Client settings that come earlier in the list take priority. `default` will be returned - /// if none of the settings specify the requested setting. - /// Use [`ResolvedClientSettings::resolve_optional`] if the setting should be optional instead - /// of having a default value. - fn resolve_or( - all_settings: &[&ClientSettings], - get: impl Fn(&ClientSettings) -> Option, - default: T, - ) -> T { - Self::resolve_optional(all_settings, get).unwrap_or(default) - } -} - -#[derive(Copy, Clone)] -enum RuleSelectorKey { - Select, - ExtendSelect, - Ignore, -} - -impl std::fmt::Display for RuleSelectorKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RuleSelectorKey::Select => f.write_str("lint.select"), - RuleSelectorKey::ExtendSelect => f.write_str("lint.extendSelect"), - RuleSelectorKey::Ignore => f.write_str("lint.ignore"), - } - } -} - -impl ResolvedClientSettings { pub(crate) fn fix_all(&self) -> bool { self.fix_all } @@ -511,501 +154,7 @@ impl ResolvedClientSettings { self.show_syntax_errors } - pub(crate) fn editor_settings(&self) -> &ResolvedEditorSettings { + pub(crate) fn editor_settings(&self) -> &EditorSettings { &self.editor_settings } } - -impl Default for InitializationOptions { - fn default() -> Self { - Self::GlobalOnly { - settings: ClientSettings::default(), - } - } -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - use ruff_python_formatter::QuoteStyle; - use ruff_workspace::options::{ - FormatOptions as RuffFormatOptions, LintCommonOptions, LintOptions, - }; - use serde::de::DeserializeOwned; - - #[cfg(not(windows))] - use ruff_linter::registry::Linter; - - use super::*; - - #[cfg(not(windows))] - const VS_CODE_INIT_OPTIONS_FIXTURE: &str = - include_str!("../../resources/test/fixtures/settings/vs_code_initialization_options.json"); - const GLOBAL_ONLY_INIT_OPTIONS_FIXTURE: &str = - include_str!("../../resources/test/fixtures/settings/global_only.json"); - const EMPTY_INIT_OPTIONS_FIXTURE: &str = - include_str!("../../resources/test/fixtures/settings/empty.json"); - - // This fixture contains multiple workspaces with empty initialization options. It only sets - // the `cwd` and the `workspace` value. - const EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE: &str = - include_str!("../../resources/test/fixtures/settings/empty_multiple_workspace.json"); - - const INLINE_CONFIGURATION_FIXTURE: &str = - include_str!("../../resources/test/fixtures/settings/inline_configuration.json"); - - fn deserialize_fixture(content: &str) -> T { - serde_json::from_str(content).expect("test fixture JSON should deserialize") - } - - #[cfg(not(windows))] - #[test] - fn test_vs_code_init_options_deserialize() { - let options: InitializationOptions = deserialize_fixture(VS_CODE_INIT_OPTIONS_FIXTURE); - - assert_debug_snapshot!(options, @r###" - HasWorkspaces { - global_settings: ClientSettings { - configuration: None, - fix_all: Some( - false, - ), - organize_imports: Some( - true, - ), - lint: Some( - LintOptions { - enable: Some( - true, - ), - preview: Some( - true, - ), - select: Some( - [ - "F", - "I", - ], - ), - extend_select: None, - ignore: None, - }, - ), - format: Some( - FormatOptions { - preview: None, - }, - ), - code_action: Some( - CodeActionOptions { - disable_rule_comment: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - fix_violation: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - }, - ), - exclude: None, - line_length: None, - configuration_preference: None, - show_syntax_errors: Some( - true, - ), - tracing: TracingSettings { - log_level: None, - log_file: None, - }, - }, - workspace_settings: [ - WorkspaceSettings { - settings: ClientSettings { - configuration: None, - fix_all: Some( - true, - ), - organize_imports: Some( - true, - ), - lint: Some( - LintOptions { - enable: Some( - true, - ), - preview: None, - select: None, - extend_select: None, - ignore: None, - }, - ), - format: Some( - FormatOptions { - preview: None, - }, - ), - code_action: Some( - CodeActionOptions { - disable_rule_comment: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - fix_violation: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - }, - ), - exclude: None, - line_length: None, - configuration_preference: None, - show_syntax_errors: Some( - true, - ), - tracing: TracingSettings { - log_level: None, - log_file: None, - }, - }, - workspace: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/Users/test/projects/pandas", - query: None, - fragment: None, - }, - }, - WorkspaceSettings { - settings: ClientSettings { - configuration: None, - fix_all: Some( - true, - ), - organize_imports: Some( - true, - ), - lint: Some( - LintOptions { - enable: Some( - true, - ), - preview: Some( - false, - ), - select: None, - extend_select: None, - ignore: None, - }, - ), - format: Some( - FormatOptions { - preview: None, - }, - ), - code_action: Some( - CodeActionOptions { - disable_rule_comment: Some( - CodeActionParameters { - enable: Some( - true, - ), - }, - ), - fix_violation: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - }, - ), - exclude: None, - line_length: None, - configuration_preference: None, - show_syntax_errors: Some( - true, - ), - tracing: TracingSettings { - log_level: None, - log_file: None, - }, - }, - workspace: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/Users/test/projects/scipy", - query: None, - fragment: None, - }, - }, - ], - } - "###); - } - - #[cfg(not(windows))] - #[test] - fn test_vs_code_workspace_settings_resolve() { - let options = deserialize_fixture(VS_CODE_INIT_OPTIONS_FIXTURE); - let AllSettings { - global_settings, - workspace_settings, - } = AllSettings::from_init_options(options); - let path = - Url::from_str("file:///Users/test/projects/pandas").expect("path should be valid"); - let workspace_settings = workspace_settings.expect("workspace settings should exist"); - assert_eq!( - ResolvedClientSettings::with_workspace( - workspace_settings - .get(&path) - .expect("workspace setting should exist"), - &global_settings - ), - ResolvedClientSettings { - fix_all: true, - organize_imports: true, - lint_enable: true, - disable_rule_comment_enable: false, - fix_violation_enable: false, - show_syntax_errors: true, - editor_settings: ResolvedEditorSettings { - configuration: None, - lint_preview: Some(true), - format_preview: None, - select: Some(vec![ - RuleSelector::Linter(Linter::Pyflakes), - RuleSelector::Linter(Linter::Isort) - ]), - extend_select: None, - ignore: None, - exclude: None, - line_length: None, - configuration_preference: ConfigurationPreference::default(), - }, - } - ); - let path = - Url::from_str("file:///Users/test/projects/scipy").expect("path should be valid"); - assert_eq!( - ResolvedClientSettings::with_workspace( - workspace_settings - .get(&path) - .expect("workspace setting should exist"), - &global_settings - ), - ResolvedClientSettings { - fix_all: true, - organize_imports: true, - lint_enable: true, - disable_rule_comment_enable: true, - fix_violation_enable: false, - show_syntax_errors: true, - editor_settings: ResolvedEditorSettings { - configuration: None, - lint_preview: Some(false), - format_preview: None, - select: Some(vec![ - RuleSelector::Linter(Linter::Pyflakes), - RuleSelector::Linter(Linter::Isort) - ]), - extend_select: None, - ignore: None, - exclude: None, - line_length: None, - configuration_preference: ConfigurationPreference::EditorFirst, - }, - } - ); - } - - #[test] - fn test_global_only_init_options_deserialize() { - let options: InitializationOptions = deserialize_fixture(GLOBAL_ONLY_INIT_OPTIONS_FIXTURE); - - assert_debug_snapshot!(options, @r#" - GlobalOnly { - settings: ClientSettings { - configuration: None, - fix_all: Some( - false, - ), - organize_imports: None, - lint: Some( - LintOptions { - enable: None, - preview: None, - select: None, - extend_select: None, - ignore: Some( - [ - "RUF001", - ], - ), - }, - ), - format: None, - code_action: Some( - CodeActionOptions { - disable_rule_comment: Some( - CodeActionParameters { - enable: Some( - false, - ), - }, - ), - fix_violation: None, - }, - ), - exclude: Some( - [ - "third_party", - ], - ), - line_length: Some( - LineLength( - 80, - ), - ), - configuration_preference: None, - show_syntax_errors: None, - tracing: TracingSettings { - log_level: Some( - Warn, - ), - log_file: None, - }, - }, - } - "#); - } - - #[test] - fn test_global_only_resolves_correctly() { - let options = deserialize_fixture(GLOBAL_ONLY_INIT_OPTIONS_FIXTURE); - - let AllSettings { - global_settings, .. - } = AllSettings::from_init_options(options); - assert_eq!( - ResolvedClientSettings::global(&global_settings), - ResolvedClientSettings { - fix_all: false, - organize_imports: true, - lint_enable: true, - disable_rule_comment_enable: false, - fix_violation_enable: true, - show_syntax_errors: true, - editor_settings: ResolvedEditorSettings { - configuration: None, - lint_preview: None, - format_preview: None, - select: None, - extend_select: None, - ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), - exclude: Some(vec!["third_party".into()]), - line_length: Some(LineLength::try_from(80).unwrap()), - configuration_preference: ConfigurationPreference::EditorFirst, - }, - } - ); - } - - #[test] - fn test_empty_init_options_deserialize() { - let options: InitializationOptions = deserialize_fixture(EMPTY_INIT_OPTIONS_FIXTURE); - - assert_eq!(options, InitializationOptions::default()); - } - - fn assert_preview_client_settings(settings: &ClientSettings, preview: bool) { - assert_eq!(settings.lint.as_ref().unwrap().preview.unwrap(), preview); - assert_eq!(settings.format.as_ref().unwrap().preview.unwrap(), preview); - } - - fn assert_preview_all_settings(all_settings: &AllSettings, preview: bool) { - assert_preview_client_settings(&all_settings.global_settings, preview); - if let Some(workspace_settings) = all_settings.workspace_settings.as_ref() { - for settings in workspace_settings.values() { - assert_preview_client_settings(settings, preview); - } - } - } - - #[test] - fn test_preview_flag() { - let options = deserialize_fixture(EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE); - let mut all_settings = AllSettings::from_init_options(options); - - all_settings.set_preview(false); - assert_preview_all_settings(&all_settings, false); - - all_settings.set_preview(true); - assert_preview_all_settings(&all_settings, true); - } - - #[test] - fn inline_configuration() { - let options: InitializationOptions = deserialize_fixture(INLINE_CONFIGURATION_FIXTURE); - - let AllSettings { - global_settings, - workspace_settings: None, - } = AllSettings::from_init_options(options) - else { - panic!("Expected global settings only"); - }; - - assert_eq!( - ResolvedClientSettings::global(&global_settings), - ResolvedClientSettings { - fix_all: true, - organize_imports: true, - lint_enable: true, - disable_rule_comment_enable: true, - fix_violation_enable: true, - show_syntax_errors: true, - editor_settings: ResolvedEditorSettings { - configuration: Some(ResolvedConfiguration::Inline(Box::new(Options { - line_length: Some(LineLength::try_from(100).unwrap()), - lint: Some(LintOptions { - common: LintCommonOptions { - extend_select: Some(vec![RuleSelector::from_str("I001").unwrap()]), - ..Default::default() - }, - ..Default::default() - }), - format: Some(RuffFormatOptions { - quote_style: Some(QuoteStyle::Single), - ..Default::default() - }), - ..Default::default() - }))), - extend_select: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), - ..Default::default() - } - } - ); - } -} diff --git a/crates/ruff_server/src/workspace.rs b/crates/ruff_server/src/workspace.rs index d264d1e602e1f1..d8274e76696675 100644 --- a/crates/ruff_server/src/workspace.rs +++ b/crates/ruff_server/src/workspace.rs @@ -3,8 +3,7 @@ use std::ops::Deref; use lsp_types::{Url, WorkspaceFolder}; use thiserror::Error; -use crate::ClientSettings; -use crate::session::WorkspaceSettingsMap; +use crate::session::{ClientOptions, WorkspaceOptionsMap}; #[derive(Debug)] pub struct Workspaces(Vec); @@ -18,15 +17,15 @@ impl Workspaces { /// initialization. pub(crate) fn from_workspace_folders( workspace_folders: Option>, - mut workspace_settings: WorkspaceSettingsMap, + mut workspace_options: WorkspaceOptionsMap, ) -> std::result::Result { - let mut client_settings_for_url = |url: &Url| { - workspace_settings.remove(url).unwrap_or_else(|| { + let mut client_options_for_url = |url: &Url| { + workspace_options.remove(url).unwrap_or_else(|| { tracing::info!( - "No workspace settings found for {}, using default settings", + "No workspace options found for {}, using default options", url ); - ClientSettings::default() + ClientOptions::default() }) }; @@ -35,8 +34,8 @@ impl Workspaces { folders .into_iter() .map(|folder| { - let settings = client_settings_for_url(&folder.uri); - Workspace::new(folder.uri).with_settings(settings) + let options = client_options_for_url(&folder.uri); + Workspace::new(folder.uri).with_options(options) }) .collect() } else { @@ -48,8 +47,8 @@ impl Workspaces { ); let uri = Url::from_file_path(current_dir) .map_err(|()| WorkspacesError::InvalidCurrentDir)?; - let settings = client_settings_for_url(&uri); - vec![Workspace::default(uri).with_settings(settings)] + let options = client_options_for_url(&uri); + vec![Workspace::default(uri).with_options(options)] }; Ok(Workspaces(workspaces)) @@ -76,8 +75,8 @@ pub(crate) enum WorkspacesError { pub struct Workspace { /// The [`Url`] pointing to the root of the workspace. url: Url, - /// The client settings for this workspace. - settings: Option, + /// The client options for this workspace. + options: Option, /// Whether this is the default workspace as created by the server. This will be the case when /// no workspace folders were provided during initialization. is_default: bool, @@ -88,7 +87,7 @@ impl Workspace { pub fn new(url: Url) -> Self { Self { url, - settings: None, + options: None, is_default: false, } } @@ -97,15 +96,15 @@ impl Workspace { pub fn default(url: Url) -> Self { Self { url, - settings: None, + options: None, is_default: true, } } - /// Set the client settings for this workspace. + /// Set the client options for this workspace. #[must_use] - pub fn with_settings(mut self, settings: ClientSettings) -> Self { - self.settings = Some(settings); + pub fn with_options(mut self, options: ClientOptions) -> Self { + self.options = Some(options); self } @@ -114,9 +113,9 @@ impl Workspace { &self.url } - /// Returns the client settings for this workspace. - pub(crate) fn settings(&self) -> Option<&ClientSettings> { - self.settings.as_ref() + /// Returns the client options for this workspace. + pub(crate) fn options(&self) -> Option<&ClientOptions> { + self.options.as_ref() } /// Returns true if this is the default workspace. diff --git a/crates/ruff_server/tests/notebook.rs b/crates/ruff_server/tests/notebook.rs index 64222fd04be9a2..6d3e6be1db562b 100644 --- a/crates/ruff_server/tests/notebook.rs +++ b/crates/ruff_server/tests/notebook.rs @@ -8,7 +8,7 @@ use lsp_types::{ Position, Range, TextDocumentContentChangeEvent, VersionedTextDocumentIdentifier, }; use ruff_notebook::SourceValue; -use ruff_server::{ClientSettings, Workspace, Workspaces}; +use ruff_server::{ClientOptions, GlobalOptions, Workspace, Workspaces}; const SUPER_RESOLUTION_OVERVIEW_PATH: &str = "./resources/test/fixtures/tensorflow_test_notebook.ipynb"; @@ -28,13 +28,16 @@ fn super_resolution_overview() { insta::assert_snapshot!("initial_notebook", notebook_source(¬ebook)); + let options = GlobalOptions::default(); + let global = options.into_settings(); + let mut session = ruff_server::Session::new( &ClientCapabilities::default(), ruff_server::PositionEncoding::UTF16, - ClientSettings::default(), + global, &Workspaces::new(vec![ Workspace::new(lsp_types::Url::from_file_path(file_path.parent().unwrap()).unwrap()) - .with_settings(ClientSettings::default()), + .with_options(ClientOptions::default()), ]), ) .unwrap(); From 1f27d53fd576e92a7d2cd4abc1761d6ad836d85b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 12 Jun 2025 19:07:31 +0200 Subject: [PATCH 407/487] [ty] File inclusion and exclusion (#18498) --- Cargo.lock | 5 + Cargo.toml | 13 +- crates/ruff_db/src/diagnostic/mod.rs | 4 + crates/ruff_db/src/system/path.rs | 28 + crates/ty/docs/cli.md | 2 + crates/ty/docs/configuration.md | 61 ++ crates/ty/src/args.rs | 17 +- crates/ty/tests/cli/file_selection.rs | 726 +++++++++++++++++++++ crates/ty/tests/cli/main.rs | 1 + crates/ty/tests/cli/rule_selection.rs | 4 +- crates/ty_project/Cargo.toml | 5 + crates/ty_project/src/db.rs | 7 +- crates/ty_project/src/db/changes.rs | 36 +- crates/ty_project/src/glob.rs | 89 +++ crates/ty_project/src/glob/exclude.rs | 285 ++++++++ crates/ty_project/src/glob/include.rs | 399 +++++++++++ crates/ty_project/src/glob/portable.rs | 407 ++++++++++++ crates/ty_project/src/lib.rs | 70 +- crates/ty_project/src/metadata/options.rs | 398 ++++++++++- crates/ty_project/src/metadata/settings.rs | 37 +- crates/ty_project/src/metadata/value.rs | 100 +++ crates/ty_project/src/walk.rs | 165 +++-- ty.schema.json | 20 + 23 files changed, 2724 insertions(+), 155 deletions(-) create mode 100644 crates/ty/tests/cli/file_selection.rs create mode 100644 crates/ty_project/src/glob.rs create mode 100644 crates/ty_project/src/glob/exclude.rs create mode 100644 crates/ty_project/src/glob/include.rs create mode 100644 crates/ty_project/src/glob/portable.rs diff --git a/Cargo.lock b/Cargo.lock index 9c8c36681ec0db..ec1b10d2681641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3936,11 +3936,16 @@ name = "ty_project" version = "0.0.0" dependencies = [ "anyhow", + "camino", + "colored 3.0.0", "crossbeam", + "globset", "insta", "notify", "pep440_rs", "rayon", + "regex", + "regex-automata 0.4.9", "ruff_cache", "ruff_db", "ruff_macros", diff --git a/Cargo.toml b/Cargo.toml index d86984336d4cfd..4f55b171b9f3d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,7 @@ quote = { version = "1.0.23" } rand = { version = "0.9.0" } rayon = { version = "1.10.0" } regex = { version = "1.10.2" } +regex-automata = { version = "0.4.9" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` @@ -165,7 +166,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = "env-filter", "fmt", "ansi", - "smallvec", + "smallvec" ] } tryfn = { version = "0.2.1" } typed-arena = { version = "2.0.2" } @@ -175,7 +176,11 @@ unicode-width = { version = "0.2.0" } unicode_names2 = { version = "1.2.2" } unicode-normalization = { version = "0.1.23" } url = { version = "2.5.0" } -uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] } +uuid = { version = "1.6.1", features = [ + "v4", + "fast-rng", + "macro-diagnostics", +] } walkdir = { version = "2.3.2" } wasm-bindgen = { version = "0.2.92" } wasm-bindgen-test = { version = "0.3.42" } @@ -210,8 +215,8 @@ must_use_candidate = "allow" similar_names = "allow" single_match_else = "allow" too_many_lines = "allow" -needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. -unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often. +needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block. +unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often. # Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250 needless_raw_string_hashes = "allow" # Disallowed restriction lints diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 8c373ca1e4c621..07ad6371e5cb65 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -665,6 +665,9 @@ pub enum DiagnosticId { /// No rule with the given name exists. UnknownRule, + + /// A glob pattern doesn't follow the expected syntax. + InvalidGlob, } impl DiagnosticId { @@ -699,6 +702,7 @@ impl DiagnosticId { DiagnosticId::Lint(name) => name.as_str(), DiagnosticId::RevealedType => "revealed-type", DiagnosticId::UnknownRule => "unknown-rule", + DiagnosticId::InvalidGlob => "invalid-glob", } } diff --git a/crates/ruff_db/src/system/path.rs b/crates/ruff_db/src/system/path.rs index c20b2b19f5f8ea..07ea7d0b71dff3 100644 --- a/crates/ruff_db/src/system/path.rs +++ b/crates/ruff_db/src/system/path.rs @@ -45,6 +45,30 @@ impl SystemPath { SystemPath::from_std_path(dunce::simplified(self.as_std_path())).unwrap() } + /// Returns `true` if the `SystemPath` is absolute, i.e., if it is independent of + /// the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and [`has_root`] are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// + /// # Examples + /// + /// ``` + /// use ruff_db::system::SystemPath; + /// + /// assert!(!SystemPath::new("foo.txt").is_absolute()); + /// ``` + /// + /// [`has_root`]: Utf8Path::has_root + #[inline] + #[must_use] + pub fn is_absolute(&self) -> bool { + self.0.is_absolute() + } + /// Extracts the file extension, if possible. /// /// The extension is: @@ -538,6 +562,10 @@ impl SystemPathBuf { self.0.into_std_path_buf() } + pub fn into_string(self) -> String { + self.0.into_string() + } + #[inline] pub fn as_path(&self) -> &SystemPath { SystemPath::new(&self.0) diff --git a/crates/ty/docs/cli.md b/crates/ty/docs/cli.md index a68b04eb35459c..56a6ae32c661d1 100644 --- a/crates/ty/docs/cli.md +++ b/crates/ty/docs/cli.md @@ -51,6 +51,8 @@ over all configuration files.

      While ty configuration can be included in a pyproject.toml file, it is not allowed in this context.

      May also be set with the TY_CONFIG_FILE environment variable.

    --error rule

    Treat the given rule as having severity 'error'. Can be specified multiple times.

    --error-on-warning

    Use exit code 1 if there are any warning-level diagnostics

    +
    --exclude exclude

    Glob patterns for files to exclude from type checking.

    +

    Uses gitignore-style syntax to exclude files and directories from type checking. Supports patterns like tests/, *.tmp, **/__pycache__/**.

    --exit-zero

    Always use exit code 0, even when there are error-level diagnostics

    --extra-search-path path

    Additional path to use as a module-resolution source (can be passed multiple times)

    --help, -h

    Print help (see a summary with '-h')

    diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index a5333dc9314434..25cdb70a75b725 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -153,6 +153,67 @@ typeshed = "/path/to/custom/typeshed" ## `src` +#### `exclude` + +A list of file and directory patterns to exclude from type checking. + +Patterns follow a syntax similar to `.gitignore`: +- `./src/` matches only a directory +- `./src` matches both files and directories +- `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) +- `*` matches any (possibly empty) sequence of characters (except `/`). +- `**` matches zero or more path components. + This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. + A sequence of more than two consecutive `*` characters is also invalid. +- `?` matches any single character except `/` +- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, + so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid. +- `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded) + +By default, the following directories are excluded: + +- `.bzr` +- `.direnv` +- `.eggs` +- `.git` +- `.git-rewrite` +- `.hg` +- `.mypy_cache` +- `.nox` +- `.pants.d` +- `.pytype` +- `.ruff_cache` +- `.svn` +- `.tox` +- `.venv` +- `__pypackages__` +- `_build` +- `buck-out` +- `dist` +- `node_modules` +- `venv` + +You can override any default exclude by using a negated pattern. For example, +to re-include `dist` use `exclude = ["!dist"]` + +**Default value**: `null` + +**Type**: `list[str]` + +**Example usage** (`pyproject.toml`): + +```toml +[tool.ty.src] +exclude = [ + "generated", + "*.proto", + "tests/fixtures/**", + "!tests/fixtures/important.py" # Include this one file +] +``` + +--- + #### `respect-ignore-files` Whether to automatically exclude files that are ignored by `.ignore`, diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index 8d02b941d89eca..99f0428f0ce205 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -5,7 +5,9 @@ use clap::{ArgAction, ArgMatches, Error, Parser}; use ruff_db::system::SystemPathBuf; use ty_project::combine::Combine; use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions}; -use ty_project::metadata::value::{RangedValue, RelativePathBuf, ValueSource}; +use ty_project::metadata::value::{ + RangedValue, RelativeExcludePattern, RelativePathBuf, ValueSource, +}; use ty_python_semantic::lint; #[derive(Debug, Parser)] @@ -148,6 +150,13 @@ pub(crate) struct CheckCommand { respect_ignore_files: Option, #[clap(long, overrides_with("respect_ignore_files"), hide = true)] no_respect_ignore_files: bool, + + /// Glob patterns for files to exclude from type checking. + /// + /// Uses gitignore-style syntax to exclude files and directories from type checking. + /// Supports patterns like `tests/`, `*.tmp`, `**/__pycache__/**`. + #[arg(long, help_heading = "File selection")] + exclude: Option>, } impl CheckCommand { @@ -195,6 +204,12 @@ impl CheckCommand { }), src: Some(SrcOptions { respect_ignore_files, + exclude: self.exclude.map(|excludes| { + excludes + .iter() + .map(|exclude| RelativeExcludePattern::cli(exclude)) + .collect() + }), ..SrcOptions::default() }), rules, diff --git a/crates/ty/tests/cli/file_selection.rs b/crates/ty/tests/cli/file_selection.rs new file mode 100644 index 00000000000000..5e646a07cdfd2e --- /dev/null +++ b/crates/ty/tests/cli/file_selection.rs @@ -0,0 +1,726 @@ +use insta_cmd::assert_cmd_snapshot; + +use crate::CliTest; + +/// Test exclude CLI argument functionality +#[test] +fn exclude_argument() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "tests/test_main.py", + r#" + print(another_undefined_var) # error: unresolved-reference + "#, + ), + ( + "temp_file.py", + r#" + print(temp_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Test that exclude argument is recognized and works + assert_cmd_snapshot!(case.command().arg("--exclude").arg("tests/"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + error[unresolved-reference]: Name `temp_undefined_var` used when not defined + --> temp_file.py:2:7 + | + 2 | print(temp_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Test multiple exclude patterns + assert_cmd_snapshot!(case.command().arg("--exclude").arg("tests/").arg("--exclude").arg("temp_*.py"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test configuration file include functionality +#[test] +fn configuration_include() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "tests/test_main.py", + r#" + print(another_undefined_var) # error: unresolved-reference + "#, + ), + ( + "other.py", + r#" + print(other_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Test include via configuration - should only check included files + case.write_file( + "ty.toml", + r#" + [src] + include = ["src"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Test multiple include patterns via configuration + case.write_file( + "ty.toml", + r#" + [src] + include = ["src", "other.py"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `other_undefined_var` used when not defined + --> other.py:2:7 + | + 2 | print(other_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test configuration file exclude functionality +#[test] +fn configuration_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "tests/test_main.py", + r#" + print(another_undefined_var) # error: unresolved-reference + "#, + ), + ( + "temp_file.py", + r#" + print(temp_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Test exclude via configuration + case.write_file( + "ty.toml", + r#" + [src] + exclude = ["tests/"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + error[unresolved-reference]: Name `temp_undefined_var` used when not defined + --> temp_file.py:2:7 + | + 2 | print(temp_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Test multiple exclude patterns via configuration + case.write_file( + "ty.toml", + r#" + [src] + exclude = ["tests/", "temp_*.py"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test that exclude takes precedence over include in configuration +#[test] +fn exclude_precedence_over_include() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "src/test_helper.py", + r#" + print(helper_undefined_var) # error: unresolved-reference + "#, + ), + ( + "other.py", + r#" + print(other_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Include all src files but exclude test files - exclude should win + case.write_file( + "ty.toml", + r#" + [src] + include = ["src"] + exclude = ["test_*.py"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test that CLI exclude overrides configuration include +#[test] +fn exclude_argument_precedence_include_argument() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "tests/test_main.py", + r#" + print(another_undefined_var) # error: unresolved-reference + "#, + ), + ( + "other.py", + r#" + print(other_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Configuration includes all files, but CLI excludes tests + case.write_file( + "ty.toml", + r#" + [src] + include = ["src/", "tests/"] + "#, + )?; + + assert_cmd_snapshot!(case.command().arg("--exclude").arg("tests/"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "###); + + Ok(()) +} + +/// Test that default excludes can be removed using negated patterns +#[test] +fn remove_default_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "dist/generated.py", + r#" + print(another_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // By default, 'dist' directory should be excluded (see default excludes) + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Now override the default exclude by using a negated pattern to re-include 'dist' + case.write_file( + "ty.toml", + r#" + [src] + exclude = ["!dist"] + "#, + )?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `another_undefined_var` used when not defined + --> dist/generated.py:2:7 + | + 2 | print(another_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test that configuration excludes can be removed via CLI negation +#[test] +fn cli_removes_config_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "build/output.py", + r#" + print(build_undefined_var) # error: unresolved-reference + "#, + ), + ])?; + + // Configuration excludes the build directory + case.write_file( + "ty.toml", + r#" + [src] + exclude = ["build/"] + "#, + )?; + + // Verify that build/ is excluded by configuration + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Now remove the configuration exclude via CLI negation + assert_cmd_snapshot!(case.command().arg("--exclude").arg("!build/"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `build_undefined_var` used when not defined + --> build/output.py:2:7 + | + 2 | print(build_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Test behavior when explicitly checking a path that matches an exclude pattern +#[test] +fn explicit_path_overrides_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "tests/generated.py", + r#" + print(dist_undefined_var) # error: unresolved-reference + "#, + ), + ( + "dist/other.py", + r#" + print(other_undefined_var) # error: unresolved-reference + "#, + ), + ( + "ty.toml", + r#" + [src] + exclude = ["tests/generated.py"] + "#, + ), + ])?; + + // dist is excluded by default and `tests/generated` is excluded in the project, so only src/main.py should be checked + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `undefined_var` used when not defined + --> src/main.py:2:7 + | + 2 | print(undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Explicitly checking a file in an excluded directory should still check that file + assert_cmd_snapshot!(case.command().arg("tests/generated.py"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `dist_undefined_var` used when not defined + --> tests/generated.py:2:7 + | + 2 | print(dist_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // Explicitly checking the entire excluded directory should check all files in it + assert_cmd_snapshot!(case.command().arg("dist/"), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-reference]: Name `other_undefined_var` used when not defined + --> dist/other.py:2:7 + | + 2 | print(other_undefined_var) # error: unresolved-reference + | ^^^^^^^^^^^^^^^^^^^ + | + info: rule `unresolved-reference` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +#[test] +fn invalid_include_pattern() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "ty.toml", + r#" + [src] + include = [ + "src/**test/" + ] + "#, + ), + ])?; + + // By default, dist/ is excluded, so only src/main.py should be checked + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: error[invalid-glob]: Invalid include pattern + --> ty.toml:4:5 + | + 2 | [src] + 3 | include = [ + 4 | "src/**test/" + | ^^^^^^^^^^^^^ Too many stars at position 5 in glob: `src/**test/` + 5 | ] + | + "#); + + Ok(()) +} + +#[test] +fn invalid_include_pattern_concise_output() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "ty.toml", + r#" + [src] + include = [ + "src/**test/" + ] + "#, + ), + ])?; + + // By default, dist/ is excluded, so only src/main.py should be checked + assert_cmd_snapshot!(case.command().arg("--output-format").arg("concise"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: error[invalid-glob] ty.toml:4:5: Invalid include pattern: Too many stars at position 5 in glob: `src/**test/` + "); + + Ok(()) +} + +#[test] +fn invalid_exclude_pattern() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "src/main.py", + r#" + print(undefined_var) # error: unresolved-reference + "#, + ), + ( + "ty.toml", + r#" + [src] + exclude = [ + "../src" + ] + "#, + ), + ])?; + + // By default, dist/ is excluded, so only src/main.py should be checked + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: error[invalid-glob]: Invalid exclude pattern + --> ty.toml:4:5 + | + 2 | [src] + 3 | exclude = [ + 4 | "../src" + | ^^^^^^^^ The parent directory operator (`..`) at position 1 is not allowed in glob: `../src` + 5 | ] + | + "#); + + Ok(()) +} diff --git a/crates/ty/tests/cli/main.rs b/crates/ty/tests/cli/main.rs index 023e14cefc891c..d314b4731a79fc 100644 --- a/crates/ty/tests/cli/main.rs +++ b/crates/ty/tests/cli/main.rs @@ -1,5 +1,6 @@ mod config_option; mod exit_code; +mod file_selection; mod python_environment; mod rule_selection; diff --git a/crates/ty/tests/cli/rule_selection.rs b/crates/ty/tests/cli/rule_selection.rs index 9269790e3a6cea..d60c229263b2be 100644 --- a/crates/ty/tests/cli/rule_selection.rs +++ b/crates/ty/tests/cli/rule_selection.rs @@ -254,12 +254,12 @@ fn configuration_unknown_rules() -> anyhow::Result<()> { success: true exit_code: 0 ----- stdout ----- - warning[unknown-rule] + warning[unknown-rule]: Unknown lint rule `division-by-zer` --> pyproject.toml:3:1 | 2 | [tool.ty.rules] 3 | division-by-zer = "warn" # incorrect rule name - | ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer` + | ^^^^^^^^^^^^^^^ | Found 1 diagnostic diff --git a/crates/ty_project/Cargo.toml b/crates/ty_project/Cargo.toml index e8df92d5662797..707c9915ae5276 100644 --- a/crates/ty_project/Cargo.toml +++ b/crates/ty_project/Cargo.toml @@ -24,10 +24,15 @@ ty_python_semantic = { workspace = true, features = ["serde"] } ty_vendored = { workspace = true } anyhow = { workspace = true } +camino = { workspace = true } +colored = { workspace = true } crossbeam = { workspace = true } +globset = { workspace = true } notify = { workspace = true } pep440_rs = { workspace = true, features = ["version-ranges"] } rayon = { workspace = true } +regex = { workspace = true } +regex-automata = { workspace = true } rustc-hash = { workspace = true } salsa = { workspace = true } schemars = { workspace = true, optional = true } diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index 9af4e7e50e80f0..ddcf8b95ab4dfa 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -70,7 +70,10 @@ impl ProjectDatabase { let program_settings = project_metadata.to_program_settings(db.system()); Program::from_settings(&db, program_settings)?; - db.project = Some(Project::from_metadata(&db, project_metadata)); + db.project = Some( + Project::from_metadata(&db, project_metadata) + .map_err(|error| anyhow::anyhow!("{}", error.pretty(&db)))?, + ); Ok(db) } @@ -269,7 +272,7 @@ pub(crate) mod tests { project: None, }; - let project = Project::from_metadata(&db, project); + let project = Project::from_metadata(&db, project).unwrap(); db.project = Some(project); db } diff --git a/crates/ty_project/src/db/changes.rs b/crates/ty_project/src/db/changes.rs index de9dcd1b421e24..7a9c9898214624 100644 --- a/crates/ty_project/src/db/changes.rs +++ b/crates/ty_project/src/db/changes.rs @@ -9,6 +9,7 @@ use ruff_db::Db as _; use ruff_db::files::{File, Files}; use ruff_db::system::SystemPath; use rustc_hash::FxHashSet; +use salsa::Setter; use ty_python_semantic::Program; /// Represents the result of applying changes to the project database. @@ -113,14 +114,15 @@ impl ProjectDatabase { // should be included in the project. We can skip this check for // paths that aren't part of the project or shouldn't be included // when checking the project. - if project.is_path_included(self, &path) { - if self.system().is_file(&path) { + + if self.system().is_file(&path) { + if project.is_file_included(self, &path) { // Add the parent directory because `walkdir` always visits explicitly passed files // even if they match an exclude filter. added_paths.insert(path.parent().unwrap().to_path_buf()); - } else { - added_paths.insert(path); } + } else if project.is_directory_included(self, &path) { + added_paths.insert(path); } } @@ -153,7 +155,9 @@ impl ProjectDatabase { result.custom_stdlib_changed = true; } - if project.is_path_included(self, &path) || path == project_root { + let directory_included = project.is_directory_included(self, &path); + + if directory_included || path == project_root { // TODO: Shouldn't it be enough to simply traverse the project files and remove all // that start with the given path? tracing::debug!( @@ -165,6 +169,10 @@ impl ProjectDatabase { // indexed files and remove the once that start with the same path, unless // the deleted path is the project configuration. result.project_changed = true; + } else if !directory_included { + tracing::debug!( + "Skipping reload because directory '{path}' isn't included in the project" + ); } } } @@ -233,8 +241,22 @@ impl ProjectDatabase { tracing::debug!("Reloading project after structural change"); project.reload(self, metadata); } else { - tracing::debug!("Replace project after structural change"); - project = Project::from_metadata(self, metadata); + match Project::from_metadata(self, metadata) { + Ok(new_project) => { + tracing::debug!("Replace project after structural change"); + project = new_project; + } + Err(error) => { + tracing::error!( + "Keeping old project configuration because loading the new settings failed with: {error}" + ); + + project + .set_settings_diagnostics(self) + .to(vec![error.into_diagnostic()]); + } + } + self.project = Some(project); } } diff --git a/crates/ty_project/src/glob.rs b/crates/ty_project/src/glob.rs new file mode 100644 index 00000000000000..2c93fc47161d88 --- /dev/null +++ b/crates/ty_project/src/glob.rs @@ -0,0 +1,89 @@ +use ruff_db::system::SystemPath; + +pub(crate) use exclude::{ExcludeFilter, ExcludeFilterBuilder}; +pub(crate) use include::{IncludeFilter, IncludeFilterBuilder}; +pub(crate) use portable::{AbsolutePortableGlobPattern, PortableGlobError, PortableGlobPattern}; + +mod exclude; +mod include; +mod portable; + +/// Path filtering based on an an exclude and include glob pattern set. +/// +/// Exclude patterns take precedence over includes. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IncludeExcludeFilter { + include: IncludeFilter, + exclude: ExcludeFilter, +} + +impl IncludeExcludeFilter { + pub(crate) fn new(include: IncludeFilter, exclude: ExcludeFilter) -> Self { + Self { include, exclude } + } + + /// Returns whether this directory is included in this filter. + /// + /// Note, this function never returns [`IncludeResult::Included`] for a path that is not included or excluded. + /// However, it may return [`IncludeResult::Included`] for directories that are not excluded, but where + /// it requires traversal to decide if any of its subdirectories or files are included. This, for example, + /// is the case when using wildcard include-patterns like `**/test`. Prefix wildcards require to traverse `src` + /// because it can't be known ahead of time whether it contains a `test` directory or file. + pub(crate) fn is_directory_maybe_included( + &self, + path: &SystemPath, + mode: GlobFilterCheckMode, + ) -> IncludeResult { + if self.exclude.match_directory(path, mode) { + IncludeResult::Excluded + } else if self.include.match_directory(path) { + IncludeResult::Included + } else { + IncludeResult::NotIncluded + } + } + + pub(crate) fn is_file_included( + &self, + path: &SystemPath, + mode: GlobFilterCheckMode, + ) -> IncludeResult { + if self.exclude.match_file(path, mode) { + IncludeResult::Excluded + } else if self.include.match_file(path) { + IncludeResult::Included + } else { + IncludeResult::NotIncluded + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub(crate) enum GlobFilterCheckMode { + /// The paths are checked top-to-bottom and inclusion is determined + /// for each path during the traversal. + TopDown, + + /// An adhoc test if a single file or directory is included. + /// + /// This is more expensive than a [`Self::TopDown`] check + /// because it may require testing every ancestor path in addition to the + /// path itself to ensure no ancestor path matches an exclude rule. + Adhoc, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum IncludeResult { + /// The path matches or at least is a prefix of an include pattern. + /// + /// For directories: This isn't a guarantee that any file in this directory gets included + /// but we need to traverse it to make this decision. + Included, + + /// The path matches an exclude pattern. + Excluded, + + /// The path matches neither an include nor an exclude pattern and, therefore, + /// isn't included. + NotIncluded, +} diff --git a/crates/ty_project/src/glob/exclude.rs b/crates/ty_project/src/glob/exclude.rs new file mode 100644 index 00000000000000..1e6813a00c9cc7 --- /dev/null +++ b/crates/ty_project/src/glob/exclude.rs @@ -0,0 +1,285 @@ +//! Exclude filter supporting gitignore-like globs. +//! +//! * `src` excludes a file or directory named `src` anywhere in the path. +//! * `/src/` excludes a directory named `src` at the root of the path. +//! * `/src` excludes a directory or file named `src` at the root of the path. +//! * `/src/**` excludes all files and directories inside a directory named `src` but not `src` itself. +//! * `!src` allows a file or directory named `src` anywhere in the path + +use std::sync::Arc; + +use globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder}; +use regex_automata::util::pool::Pool; +use ruff_db::system::SystemPath; + +use crate::GlobFilterCheckMode; +use crate::glob::portable::AbsolutePortableGlobPattern; + +/// A filter for gitignore-like globs that excludes files and directories. +/// +/// # Equality +/// +/// Two filters are equal if they're constructed from the same patterns (including order). +/// Two filters that exclude the exact same files but were constructed from different patterns aren't considered +/// equal. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct ExcludeFilter { + ignore: Gitignore, +} + +impl ExcludeFilter { + /// Returns `true` if the path to a directory is definitely excluded and `false` otherwise. + pub(crate) fn match_directory(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool { + self.matches(path, mode, true) + } + + /// Returns `true` if the path to a file is definitely excluded and `false` otherwise. + pub(crate) fn match_file(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool { + self.matches(path, mode, false) + } + + fn matches(&self, path: &SystemPath, mode: GlobFilterCheckMode, directory: bool) -> bool { + match mode { + GlobFilterCheckMode::TopDown => { + match self.ignore.matched(path, directory) { + // No hit or an allow hit means the file or directory is not excluded. + Match::None | Match::Allow => false, + Match::Ignore => true, + } + } + GlobFilterCheckMode::Adhoc => { + for ancestor in path.ancestors() { + match self.ignore.matched(ancestor, directory) { + // If the path is allowlisted or there's no hit, try the parent to ensure we don't return false + // for a folder where there's an exclude for a parent. + Match::None | Match::Allow => {} + Match::Ignore => return true, + } + } + + false + } + } + } +} + +pub(crate) struct ExcludeFilterBuilder { + ignore: GitignoreBuilder, +} + +impl ExcludeFilterBuilder { + pub(crate) fn new() -> Self { + Self { + ignore: GitignoreBuilder::new(), + } + } + + pub(crate) fn add( + &mut self, + pattern: &AbsolutePortableGlobPattern, + ) -> Result<&mut Self, globset::Error> { + self.ignore.add(pattern)?; + + Ok(self) + } + + pub(crate) fn build(self) -> Result { + Ok(ExcludeFilter { + ignore: self.ignore.build()?, + }) + } +} + +/// Matcher for gitignore like globs. +/// +/// This code is our own vendored copy of the ignore's crate `Gitignore` type. +/// The main difference to `ignore`'s version is that it makes use +/// of the fact that all our globs are absolute. This simplifies the implementation a fair bit. +/// Making globs absolute is also because the globs can come from both the CLI and configuration files, +/// where the paths are anchored relative to the current working directory or the project root respectively. +/// +/// Vendoring our own copy has the added benefit that we don't need to deal with ignore's `Error` type. +/// Instead, we can exclusively use [`globset::Error`]. +/// +/// This implementation also removes supported for comments, because the patterns aren't read +/// from a `.gitignore` file. This removes the need to escape `#` for file names starting with `#`, +/// +/// You can find the original source on [GitHub](https://github.com/BurntSushi/ripgrep/blob/cbc598f245f3c157a872b69102653e2e349b6d92/crates/ignore/src/gitignore.rs#L81). +/// +/// # Equality +/// +/// Two ignore matches are only equal if they're constructed from the same patterns (including order). +/// Two matchers that were constructed from different patterns but result in +/// including the same files don't compare equal. +#[derive(Clone)] +struct Gitignore { + set: GlobSet, + globs: Vec, + matches: Option>>>, +} + +impl Gitignore { + /// Returns whether the given path (file or directory) matched a pattern in + /// this gitignore matcher. + /// + /// `is_dir` should be true if the path refers to a directory and false + /// otherwise. + /// + /// The path must be absolute or it will only match prefix-wildcard patterns. + fn matched(&self, path: &SystemPath, is_dir: bool) -> Match { + if self.globs.is_empty() { + return Match::None; + } + + let mut matches = self.matches.as_ref().unwrap().get(); + let candidate = Candidate::new(path); + self.set.matches_candidate_into(&candidate, &mut matches); + for &i in matches.iter().rev() { + let glob = &self.globs[i]; + if !glob.is_only_dir || is_dir { + return if glob.is_ignore() { + Match::Ignore + } else { + Match::Allow + }; + } + } + Match::None + } +} + +impl std::fmt::Debug for Gitignore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Gitignore") + .field("globs", &self.globs) + .finish_non_exhaustive() + } +} + +impl PartialEq for Gitignore { + fn eq(&self, other: &Self) -> bool { + self.globs == other.globs + } +} + +impl Eq for Gitignore {} + +#[derive(Copy, Clone, Debug)] +enum Match { + /// The path matches no pattern. + None, + + /// The path matches an ignore pattern (a positive pattern) + /// It should be ignored. + Ignore, + + /// The path matches an allow pattern (a negative pattern). + /// It should not be ignored. + Allow, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct IgnoreGlob { + /// The pattern that was originally parsed. + original: String, + + /// This is a pattern allowing a path (it starts with a `!`, possibly undoing a previous ignore) + is_allow: bool, + + /// Whether this pattern only matches directories. + is_only_dir: bool, +} + +impl IgnoreGlob { + const fn is_ignore(&self) -> bool { + !self.is_allow + } +} + +/// Builds a matcher for git-ignore like globs. +/// +/// All globs need to use absolute paths, unless they're unanchored (contain no `/`). +#[derive(Clone, Debug)] +struct GitignoreBuilder { + builder: GlobSetBuilder, + globs: Vec, +} + +impl GitignoreBuilder { + /// Create a new builder for a gitignore file. + fn new() -> GitignoreBuilder { + GitignoreBuilder { + builder: GlobSetBuilder::new(), + globs: vec![], + } + } + + /// Builds a new matcher from the globs added so far. + /// + /// Once a matcher is built, no new globs can be added to it. + fn build(&self) -> Result { + let set = self.builder.build()?; + + Ok(Gitignore { + set, + globs: self.globs.clone(), + matches: Some(Arc::new(Pool::new(Vec::new))), + }) + } + + /// Adds a gitignore like glob pattern to this builder. + /// + /// If the pattern could not be parsed as a glob, then an error is returned. + fn add(&mut self, mut pattern: &str) -> Result<&mut GitignoreBuilder, globset::Error> { + let mut glob = IgnoreGlob { + original: pattern.to_string(), + is_allow: false, + is_only_dir: false, + }; + + // File names starting with `!` are escaped with a backslash. Strip the backslash. + // This is not a negated pattern! + if pattern.starts_with("\\!") { + pattern = &pattern[1..]; + } else if let Some(after) = pattern.strip_prefix("!") { + glob.is_allow = true; + pattern = after; + } + + // If it ends with a slash, then this should only match directories, + // but the slash should otherwise not be used while globbing. + if let Some(before) = pattern.strip_suffix('/') { + glob.is_only_dir = true; + pattern = before; + } + + let mut actual = pattern.to_string(); + + // If there is a literal slash, then this is a glob that must match the + // entire path name. Otherwise, we should let it match anywhere, so use + // a **/ prefix. + if !pattern.chars().any(|c| c == '/') { + // ... but only if we don't already have a **/ prefix. + if !pattern.starts_with("**/") { + actual = format!("**/{actual}"); + } + } + // If the glob ends with `/**`, then we should only match everything + // inside a directory, but not the directory itself. Standard globs + // will match the directory. So we add `/*` to force the issue. + if actual.ends_with("/**") { + actual = format!("{actual}/*"); + } + + let parsed = GlobBuilder::new(&actual) + .literal_separator(true) + // No need to support Windows-style paths, so the backslash can be used an escape. + .backslash_escape(true) + .build()?; + + self.builder.add(parsed); + self.globs.push(glob); + + Ok(self) + } +} diff --git a/crates/ty_project/src/glob/include.rs b/crates/ty_project/src/glob/include.rs new file mode 100644 index 00000000000000..b6365cf62711b7 --- /dev/null +++ b/crates/ty_project/src/glob/include.rs @@ -0,0 +1,399 @@ +use globset::{Glob, GlobBuilder, GlobSet, GlobSetBuilder}; +use regex_automata::dfa; +use regex_automata::dfa::Automaton; +use ruff_db::system::SystemPath; +use std::path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}; +use tracing::warn; + +use crate::glob::portable::AbsolutePortableGlobPattern; + +/// Chosen at a whim -Konsti +const DFA_SIZE_LIMIT: usize = 1_000_000; + +/// Path filter based on a set of include globs. +/// +/// The patterns are similar to gitignore, but reversed: +/// +/// * `/src`: matches a file or directory with its content named `src` +/// * `/src/`: matches a directory with its content named `src` +/// * `/src/**` or `/src/*`: matches the content of `src`, but not a file named `src` +/// +/// Negated patterns are not supported. +/// +/// Internally, the globs are converted to a regex and then to a DFA, which unlike the globs and the +/// regex allows to check for prefix matches. +/// +/// ## Equality +/// Equality is based on the patterns from which a filter was constructed. +/// +/// Because of that, two filters that include the exact same files but were +/// constructed from different patterns (or even just order) compare unequal. +#[derive(Clone)] +pub(crate) struct IncludeFilter { + glob_set: GlobSet, + original_patterns: Box<[String]>, + dfa: Option>>, +} + +impl IncludeFilter { + /// Whether the file matches any of the globs. + pub(crate) fn match_file(&self, path: impl AsRef) -> bool { + let path = path.as_ref(); + + self.glob_set.is_match(path) + } + + /// Check whether a directory or any of its children can be matched by any of the globs. + /// + /// This never returns `false` if any child matches, but it may return `true` even if we + /// don't end up including any child. + pub(crate) fn match_directory(&self, path: impl AsRef) -> bool { + self.match_directory_impl(path.as_ref()) + } + + fn match_directory_impl(&self, path: &SystemPath) -> bool { + let Some(dfa) = &self.dfa else { + return true; + }; + + // Allow the root path + if path == SystemPath::new("") { + return true; + } + + let config_anchored = + regex_automata::util::start::Config::new().anchored(regex_automata::Anchored::Yes); + let mut state = dfa.start_state(&config_anchored).unwrap(); + + let byte_path = path + .as_str() + .strip_suffix('/') + .unwrap_or(path.as_str()) + .as_bytes(); + for b in byte_path { + state = dfa.next_state(state, *b); + } + // Say we're looking at a directory `foo/bar`. We want to continue if either `foo/bar` is + // a match, e.g., from `foo/*`, or a path below it can match, e.g., from `foo/bar/*`. + let eoi_state = dfa.next_eoi_state(state); + // We must not call `next_eoi_state` on the slash state, we want to only check if more + // characters (path components) are allowed, not if we're matching the `$` anchor at the + // end. + let slash_state = dfa.next_state(state, u8::try_from(MAIN_SEPARATOR).unwrap()); + + debug_assert!( + !dfa.is_quit_state(eoi_state) && !dfa.is_quit_state(slash_state), + "matcher is in quit state" + ); + + dfa.is_match_state(eoi_state) || !dfa.is_dead_state(slash_state) + } +} + +impl std::fmt::Debug for IncludeFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IncludeFilder") + .field("original_patterns", &self.original_patterns) + .finish_non_exhaustive() + } +} + +impl PartialEq for IncludeFilter { + fn eq(&self, other: &Self) -> bool { + self.original_patterns == other.original_patterns + } +} + +impl Eq for IncludeFilter {} + +#[derive(Debug)] +pub(crate) struct IncludeFilterBuilder { + set: GlobSetBuilder, + original_pattern: Vec, + regexes: Vec, +} + +impl IncludeFilterBuilder { + pub(crate) fn new() -> Self { + Self { + set: GlobSetBuilder::new(), + original_pattern: Vec::new(), + regexes: Vec::new(), + } + } + + /// Adds an include pattern to the filter. + pub(crate) fn add( + &mut self, + input: &AbsolutePortableGlobPattern, + ) -> Result<&mut Self, globset::Error> { + let mut glob = &**input; + + let mut only_directory = false; + + // A pattern ending with a `/` should only match directories. E.g. `src/` only matches directories + // whereas `src` matches both files and directories. + // We need to remove the `/` to ensure that a path missing the trailing `/` matches. + if let Some(after) = input.strip_suffix('/') { + // Escaped `/` or `\` aren't allowed. `portable_glob::parse` will error + only_directory = true; + glob = after; + } + + // If regex ends with `/**`, only push that one glob and regex + // Otherwise, push two regex, one for `/**` and one for without + let glob = GlobBuilder::new(glob) + .literal_separator(true) + // No need to support Windows-style paths, so the backslash can be used a escape. + .backslash_escape(true) + .build()?; + self.original_pattern.push(input.to_string()); + + // `lib` is the same as `lib/**` + // Add a glob that matches `lib` exactly, change the glob to `lib/**`. + if input.ends_with("**") { + self.push_prefix_regex(&glob); + self.set.add(glob); + } else { + let prefix_glob = GlobBuilder::new(&format!("{glob}/**")) + .literal_separator(true) + // No need to support Windows-style paths, so the backslash can be used a escape. + .backslash_escape(true) + .build()?; + + self.push_prefix_regex(&prefix_glob); + self.set.add(prefix_glob); + + // The reason we add the exact glob, e.g. `src` when the original pattern was `src/` is + // so that `match_file` returns true when matching against a file. However, we don't + // need to do this if this is a pattern that should only match a directory (specifically, its contents). + if !only_directory { + self.set.add(glob); + } + } + + Ok(self) + } + + fn push_prefix_regex(&mut self, glob: &Glob) { + let main_separator = regex::escape(MAIN_SEPARATOR_STR); + + let regex = glob + .regex() + // We are using a custom DFA builder + .strip_prefix("(?-u)") + .expect("a glob is a non-unicode byte regex") + // Match windows paths if applicable + .replace('/', &main_separator); + + self.regexes.push(regex); + } + + /// The filter matches if any of the globs matches. + /// + /// See for the error returned. + pub(crate) fn build(self) -> Result { + let glob_set = self.set.build()?; + + let dfa_builder = dfa::dense::Builder::new() + .syntax( + // The glob regex is a byte matcher + regex_automata::util::syntax::Config::new() + .unicode(false) + .utf8(false), + ) + .configure( + dfa::dense::Config::new() + .start_kind(dfa::StartKind::Anchored) + // DFA can grow exponentially, in which case we bail out + .dfa_size_limit(Some(DFA_SIZE_LIMIT)) + .determinize_size_limit(Some(DFA_SIZE_LIMIT)), + ) + .build_many(&self.regexes); + let dfa = if let Ok(dfa) = dfa_builder { + Some(dfa) + } else { + // TODO(konsti): `regex_automata::dfa::dense::BuildError` should allow asking whether + // is a size error + warn!( + "Glob expressions regex is larger than {DFA_SIZE_LIMIT} bytes, \ + falling back to full directory traversal!" + ); + None + }; + + Ok(IncludeFilter { + glob_set, + dfa, + original_patterns: self.original_pattern.into(), + }) + } +} + +#[cfg(test)] +mod tests { + use std::path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}; + + use crate::glob::PortableGlobPattern; + use crate::glob::include::{IncludeFilter, IncludeFilterBuilder}; + use ruff_db::system::{MemoryFileSystem, walk_directory::WalkState}; + + fn create_filter(patterns: impl IntoIterator) -> IncludeFilter { + let mut builder = IncludeFilterBuilder::new(); + for pattern in patterns { + builder + .add( + &PortableGlobPattern::parse(pattern, false) + .unwrap() + .into_absolute(""), + ) + .unwrap(); + } + + builder.build().unwrap() + } + + fn setup_files(files: impl IntoIterator) -> MemoryFileSystem { + let fs = MemoryFileSystem::new(); + + fs.write_files_all(files.into_iter().map(|name| (name, ""))) + .unwrap(); + fs + } + + #[track_caller] + fn assert_match_directory(filter: &IncludeFilter, path: &str) { + assert!(filter.match_directory(path.replace('/', MAIN_SEPARATOR_STR))); + } + + #[track_caller] + fn assert_not_match_directory(filter: &IncludeFilter, path: &str) { + assert!(!filter.match_directory(path.replace('/', MAIN_SEPARATOR_STR))); + } + + #[test] + fn match_directory() { + // `lib` is the same as `src/**`. It includes a file or directory (including its contents) + // `src/*`: The same as `src/**` + let filter = create_filter(["lib", "src/*", "tests/**", "a/test-*/b", "files/*.py"]); + + assert_match_directory(&filter, "lib"); + assert_match_directory(&filter, "lib/more/test"); + + assert_match_directory(&filter, "src"); + assert_match_directory(&filter, "src/more/test"); + + assert_match_directory(&filter, "tests"); + assert_match_directory(&filter, "tests/more/test"); + + assert_match_directory(&filter, "a"); + assert_match_directory(&filter, "a/test-b"); + + assert_not_match_directory(&filter, "a/test-b/x"); + assert_not_match_directory(&filter, "a/test"); + + assert_match_directory(&filter, "files/a.py"); + assert_match_directory(&filter, "files/a.py/bcd"); + + assert_not_match_directory(&filter, "not_included"); + assert_not_match_directory(&filter, "files/a.pi"); + } + + #[test] + fn match_file() { + // `lib` is the same as `src/**`. It includes a file or directory (including its contents) + // `src/*`: The same as `src/**` + let filter = create_filter([ + "lib", + "src/*", + "directory/", + "tests/**", + "a/test-*/b", + "files/*.py", + ]); + + assert!(filter.match_file("lib")); + assert!(filter.match_file("lib/more/test")); + + // Unlike `directory`, `directory/` only includes a directory with the given name and its contents + assert!(!filter.match_file("directory")); + assert!(filter.match_file("directory/more/test")); + + // Unlike `src`, `src/*` only includes a directory with the given name. + assert!(!filter.match_file("src")); + assert!(filter.match_file("src/more/test")); + + // Unlike `tests`, `tests/**` only includes files under `tests`, but not a file named tests + assert!(!filter.match_file("tests")); + assert!(filter.match_file("tests/more/test")); + + // Unlike `match_directory`, prefixes should not be included. + assert!(!filter.match_file("a")); + assert!(!filter.match_file("a/test-b")); + + assert!(!filter.match_file("a/test-b/x")); + assert!(!filter.match_file("a/test")); + + assert!(filter.match_file("files/a.py")); + assert!(filter.match_file("files/a.py/bcd")); + + assert!(!filter.match_file("not_included")); + assert!(!filter.match_file("files/a.pi")); + } + + /// Check that we skip directories that can never match. + #[test] + fn prefilter() { + let filter = create_filter(["/a/b/test-*/d", "/a/b/c/e", "/b/c"]); + let fs = setup_files([ + // Should visit + "/a/b/test-a/d", + "/a/b/c/e", + "/b/c", + // Can skip + "/d/e", + "/a/b/x/f", + ]); + + let visited = std::sync::Mutex::new(Vec::new()); + + // Test the prefix filtering + fs.walk_directory("/").run(|| { + Box::new(|entry| { + let entry = entry.unwrap(); + + if entry.file_type().is_directory() { + if !filter.match_directory(entry.path()) { + return WalkState::Skip; + } + } + + visited + .lock() + .unwrap() + .push(entry.path().as_str().replace(MAIN_SEPARATOR, "/")); + + WalkState::Continue + }) + }); + + let mut visited = visited.into_inner().unwrap(); + visited.sort(); + + // Assert that it didn't traverse into `/d` or `/a/b/x` + assert_eq!( + visited, + [ + "/", + "/a", + "/a/b", + "/a/b/c", + "/a/b/c/e", + "/a/b/test-a", + "/a/b/test-a/d", + "/b", + "/b/c" + ] + ); + } +} diff --git a/crates/ty_project/src/glob/portable.rs b/crates/ty_project/src/glob/portable.rs new file mode 100644 index 00000000000000..8de8d91188d041 --- /dev/null +++ b/crates/ty_project/src/glob/portable.rs @@ -0,0 +1,407 @@ +//! Cross-language glob syntax from +//! [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/). +//! +//! The glob syntax matches the `uv` variant of uv's `uv-globfilter` crate. +//! We intentionally use the same syntax to give users a consistent experience +//! across our tools. +//! +//! [Source](https://github.com/astral-sh/uv/blob/main/crates/uv-globfilter/src/portable_glob.rs) + +use ruff_db::system::SystemPath; +use std::ops::Deref; +use std::{fmt::Write, path::MAIN_SEPARATOR}; +use thiserror::Error; + +/// Pattern that only uses cross-language glob syntax based on [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/): +/// +/// - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) are matched verbatim. +/// - The special glob characters are: +/// - `*`: Matches any number of characters except path separators +/// - `?`: Matches a single character except the path separator +/// - `**`: Matches any number of characters including path separators +/// - `[]`, containing only the verbatim matched characters: Matches a single of the characters contained. Within +/// `[...]`, the hyphen indicates a locale-agnostic range (e.g. `a-z`, order based on Unicode code points). Hyphens at +/// the start or end are matched literally. +/// - `\`: It escapes the following character to be matched verbatim (extension to PEP 639). +/// - The path separator is the forward slash character (`/`). Patterns are relative to the given directory, a leading slash +/// character for absolute paths is not supported. +/// - Parent directory indicators (`..`) are not allowed. +/// +/// These rules mean that matching the backslash (`\`) is forbidden, which avoid collisions with the windows path separator. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub(crate) struct PortableGlobPattern<'a> { + pattern: &'a str, + is_exclude: bool, +} + +impl<'a> PortableGlobPattern<'a> { + /// Parses a portable glob pattern. Returns an error if the pattern isn't valid. + pub(crate) fn parse(glob: &'a str, is_exclude: bool) -> Result { + let mut chars = glob.chars().enumerate().peekable(); + + if is_exclude { + chars.next_if(|(_, c)| *c == '!'); + } + + // A `..` is on a parent directory indicator at the start of the string or after a directory + // separator. + let mut start_or_slash = true; + // The number of consecutive stars before the current character. + while let Some((offset, c)) = chars.next() { + let pos = offset + 1; + + // `***` or `**literals` can be correctly represented with less stars. They are banned by + // `glob`, they are allowed by `globset` and PEP 639 is ambiguous, so we're filtering them + // out. + if c == '*' { + let mut star_run = 1; + while let Some((_, c)) = chars.peek() { + if *c == '*' { + star_run += 1; + chars.next(); + } else { + break; + } + } + if star_run >= 3 { + return Err(PortableGlobError::TooManyStars { + glob: glob.to_string(), + // We don't update pos for the stars. + pos, + }); + } else if star_run == 2 { + if chars.peek().is_some_and(|(_, c)| *c != '/') { + return Err(PortableGlobError::TooManyStars { + glob: glob.to_string(), + // We don't update pos for the stars. + pos, + }); + } + } + start_or_slash = false; + } else if c.is_alphanumeric() || matches!(c, '_' | '-' | '?') { + start_or_slash = false; + } else if c == '.' { + if start_or_slash && matches!(chars.peek(), Some((_, '.'))) { + return Err(PortableGlobError::ParentDirectory { + pos, + glob: glob.to_string(), + }); + } + start_or_slash = false; + } else if c == '/' { + start_or_slash = true; + } else if c == '[' { + for (pos, c) in chars.by_ref() { + if c.is_alphanumeric() || matches!(c, '_' | '-' | '.') { + // Allowed. + } else if c == ']' { + break; + } else { + return Err(PortableGlobError::InvalidCharacterRange { + glob: glob.to_string(), + pos, + invalid: InvalidChar(c), + }); + } + } + start_or_slash = false; + } else if c == '\\' { + match chars.next() { + Some((pos, '/' | '\\')) => { + // For cross-platform compatibility, we don't allow forward slashes or + // backslashes to be escaped. + return Err(PortableGlobError::InvalidEscapee { + glob: glob.to_string(), + pos, + }); + } + Some(_) => { + // Escaped character + } + None => { + return Err(PortableGlobError::TrailingEscape { + glob: glob.to_string(), + pos, + }); + } + } + } else { + return Err(PortableGlobError::InvalidCharacter { + glob: glob.to_string(), + pos, + invalid: InvalidChar(c), + }); + } + } + Ok(PortableGlobPattern { + pattern: glob, + is_exclude, + }) + } + + /// Anchors pattern at `cwd`. + /// + /// `is_exclude` indicates whether this is a pattern in an exclude filter. + /// + /// This method similar to [`SystemPath::absolute`] but for a glob pattern. + /// The main difference is that this method always uses `/` as path separator. + pub(crate) fn into_absolute(self, cwd: impl AsRef) -> AbsolutePortableGlobPattern { + let mut pattern = self.pattern; + let mut negated = false; + + if self.is_exclude { + // If the pattern starts with `!`, we need to remove it and then anchor the rest. + if let Some(after) = self.pattern.strip_prefix('!') { + pattern = after; + negated = true; + } + + // Patterns that don't contain any `/`, e.g. `.venv` are unanchored patterns + // that match anywhere. + if !self.chars().any(|c| c == '/') { + return AbsolutePortableGlobPattern(self.to_string()); + } + } + + if pattern.starts_with('/') { + return AbsolutePortableGlobPattern(pattern.to_string()); + } + + let mut rest = pattern; + let mut prefix = cwd.as_ref().to_path_buf().into_utf8_path_buf(); + + loop { + if let Some(after) = rest.strip_prefix("./") { + rest = after; + } else if let Some(after) = rest.strip_prefix("../") { + prefix.pop(); + rest = after; + } else { + break; + } + } + + let mut output = String::with_capacity(prefix.as_str().len() + rest.len()); + + for component in prefix.components() { + match component { + camino::Utf8Component::Prefix(utf8_prefix_component) => { + output.push_str(&utf8_prefix_component.as_str().replace(MAIN_SEPARATOR, "/")); + } + + camino::Utf8Component::RootDir => { + output.push('/'); + continue; + } + camino::Utf8Component::CurDir => {} + camino::Utf8Component::ParentDir => output.push_str("../"), + camino::Utf8Component::Normal(component) => { + output.push_str(component); + output.push('/'); + } + } + } + + output.push_str(rest); + if negated { + // If the pattern is negated, we need to keep the leading `!`. + AbsolutePortableGlobPattern(format!("!{output}")) + } else { + AbsolutePortableGlobPattern(output) + } + } +} + +impl Deref for PortableGlobPattern<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.pattern + } +} + +/// A portable glob pattern that uses absolute paths. +/// +/// E.g., `./src/**` becomes `/root/src/**` when anchored to `/root`. +#[derive(Debug, Eq, PartialEq, Hash)] +pub(crate) struct AbsolutePortableGlobPattern(String); + +impl Deref for AbsolutePortableGlobPattern { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Error)] +pub(crate) enum PortableGlobError { + /// Shows the failing glob in the error message. + #[error(transparent)] + GlobError(#[from] globset::Error), + + #[error( + "The parent directory operator (`..`) at position {pos} is not allowed in glob: `{glob}`" + )] + ParentDirectory { glob: String, pos: usize }, + + #[error( + "Invalid character `{invalid}` at position {pos} in glob: `{glob}`. hint: Characters can be escaped with a backslash" + )] + InvalidCharacter { + glob: String, + pos: usize, + invalid: InvalidChar, + }, + + #[error( + "Path separators can't be escaped, invalid character at position {pos} in glob: `{glob}`" + )] + InvalidEscapee { glob: String, pos: usize }, + + #[error("Invalid character `{invalid}` in range at position {pos} in glob: `{glob}`")] + InvalidCharacterRange { + glob: String, + pos: usize, + invalid: InvalidChar, + }, + + #[error("Too many stars at position {pos} in glob: `{glob}`")] + TooManyStars { glob: String, pos: usize }, + + #[error("Trailing backslash at position {pos} in glob: `{glob}`")] + TrailingEscape { glob: String, pos: usize }, +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct InvalidChar(pub char); + +impl std::fmt::Display for InvalidChar { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + '\'' => f.write_char('\''), + c => c.escape_debug().fmt(f), + } + } +} + +#[cfg(test)] +mod tests { + + use crate::glob::PortableGlobPattern; + use insta::assert_snapshot; + use ruff_db::system::SystemPath; + + #[test] + fn test_error() { + #[track_caller] + fn parse_err(glob: &str) -> String { + let error = PortableGlobPattern::parse(glob, true).unwrap_err(); + error.to_string() + } + + assert_snapshot!( + parse_err(".."), + @"The parent directory operator (`..`) at position 1 is not allowed in glob: `..`" + ); + assert_snapshot!( + parse_err("licenses/.."), + @"The parent directory operator (`..`) at position 10 is not allowed in glob: `licenses/..`" + ); + assert_snapshot!( + parse_err("licenses/LICEN!E.txt"), + @"Invalid character `!` at position 15 in glob: `licenses/LICEN!E.txt`. hint: Characters can be escaped with a backslash" + ); + assert_snapshot!( + parse_err("licenses/LICEN[!C]E.txt"), + @"Invalid character `!` in range at position 15 in glob: `licenses/LICEN[!C]E.txt`" + ); + assert_snapshot!( + parse_err("licenses/LICEN[C?]E.txt"), + @"Invalid character `?` in range at position 16 in glob: `licenses/LICEN[C?]E.txt`" + ); + assert_snapshot!( + parse_err("******"), + @"Too many stars at position 1 in glob: `******`" + ); + assert_snapshot!( + parse_err("licenses/**license"), + @"Too many stars at position 10 in glob: `licenses/**license`" + ); + assert_snapshot!( + parse_err("licenses/***/licenses.csv"), + @"Too many stars at position 10 in glob: `licenses/***/licenses.csv`" + ); + assert_snapshot!( + parse_err(r"**/@test"), + @"Invalid character `@` at position 4 in glob: `**/@test`. hint: Characters can be escaped with a backslash" + ); + // Escapes are not allowed in strict PEP 639 mode + assert_snapshot!( + parse_err(r"public domain/Gulliver\\’s Travels.txt"), + @r"Invalid character ` ` at position 7 in glob: `public domain/Gulliver\\’s Travels.txt`. hint: Characters can be escaped with a backslash" + ); + assert_snapshot!( + parse_err(r"**/@test"), + @"Invalid character `@` at position 4 in glob: `**/@test`. hint: Characters can be escaped with a backslash" + ); + // Escaping slashes is not allowed. + assert_snapshot!( + parse_err(r"licenses\\MIT.txt"), + @r"Path separators can't be escaped, invalid character at position 9 in glob: `licenses\\MIT.txt`" + ); + assert_snapshot!( + parse_err(r"licenses\/MIT.txt"), + @r"Path separators can't be escaped, invalid character at position 9 in glob: `licenses\/MIT.txt`" + ); + } + + #[test] + fn test_valid() { + let cases = [ + r"licenses/*.txt", + r"licenses/**/*.txt", + r"LICEN[CS]E.txt", + r"LICEN?E.txt", + r"[a-z].txt", + r"[a-z._-].txt", + r"*/**", + r"LICENSE..txt", + r"LICENSE_file-1.txt", + // (google translate) + r"licenses/라이센스*.txt", + r"licenses/ライセンス*.txt", + r"licenses/执照*.txt", + r"src/**", + ]; + let cases_uv = [ + r"public-domain/Gulliver\’s\ Travels.txt", + // https://github.com/astral-sh/uv/issues/13280 + r"**/\@test", + ]; + for case in cases.iter().chain(cases_uv.iter()) { + PortableGlobPattern::parse(case, true).unwrap(); + } + } + + #[track_caller] + fn assert_absolute_path(pattern: &str, relative_to: impl AsRef, expected: &str) { + let pattern = PortableGlobPattern::parse(pattern, true).unwrap(); + let absolute = pattern.into_absolute(relative_to); + assert_eq!(&*absolute, expected); + } + + #[test] + fn absolute_pattern() { + assert_absolute_path("/src", "/root", "/src"); + assert_absolute_path("./src", "/root", "/root/src"); + } + + #[test] + #[cfg(windows)] + fn absolute_pattern_windows() { + assert_absolute_path("./src", r"C:\root", "C:/root/src"); + assert_absolute_path("./src", r"\\server\test", "//server/test/src"); + } +} diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index b57608d5f5f003..bc9daff46e29c6 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -1,6 +1,5 @@ -#![allow(clippy::ref_option)] - -use crate::metadata::options::OptionDiagnostic; +use crate::glob::{GlobFilterCheckMode, IncludeResult}; +use crate::metadata::options::{OptionDiagnostic, ToSettingsError}; use crate::walk::{ProjectFilesFilter, ProjectFilesWalker}; pub use db::{Db, ProjectDatabase}; use files::{Index, Indexed, IndexedFiles}; @@ -30,6 +29,7 @@ pub mod combine; mod db; mod files; +mod glob; pub mod metadata; mod walk; pub mod watch; @@ -70,12 +70,20 @@ pub struct Project { file_set: IndexedFiles, /// The metadata describing the project, including the unresolved options. - #[returns(ref)] - pub metadata: ProjectMetadata, + /// + /// We box the metadata here because it's a fairly large type and + /// reducing the size of `Project` helps reduce the size of the + /// salsa allocated table for `Project`. + #[returns(deref)] + pub metadata: Box, /// The resolved project settings. - #[returns(ref)] - pub settings: Settings, + /// + /// We box the metadata here because it's a fairly large type and + /// reducing the size of `Project` helps reduce the size of the + /// salsa allocated table for `Project`. + #[returns(deref)] + pub settings: Box, /// The paths that should be included when checking this project. /// @@ -126,14 +134,16 @@ impl Reporter for DummyReporter { #[salsa::tracked] impl Project { - pub fn from_metadata(db: &dyn Db, metadata: ProjectMetadata) -> Self { - let (settings, settings_diagnostics) = metadata.options().to_settings(db); + pub fn from_metadata(db: &dyn Db, metadata: ProjectMetadata) -> Result { + let (settings, diagnostics) = metadata.options().to_settings(db, metadata.root())?; - Project::builder(metadata, settings, settings_diagnostics) + let project = Project::builder(Box::new(metadata), Box::new(settings), diagnostics) .durability(Durability::MEDIUM) .open_fileset_durability(Durability::LOW) .file_set_durability(Durability::LOW) - .new(db) + .new(db); + + Ok(project) } pub fn root(self, db: &dyn Db) -> &SystemPath { @@ -160,8 +170,16 @@ impl Project { /// the project's include and exclude settings as well as the paths that were passed to `ty check `. /// This means, that this method is an over-approximation of `Self::files` and may return `true` for paths /// that won't be included when checking the project because they're ignored in a `.gitignore` file. - pub fn is_path_included(self, db: &dyn Db, path: &SystemPath) -> bool { - ProjectFilesFilter::from_project(db, self).is_included(path) + pub fn is_file_included(self, db: &dyn Db, path: &SystemPath) -> bool { + ProjectFilesFilter::from_project(db, self) + .is_file_included(path, GlobFilterCheckMode::Adhoc) + == IncludeResult::Included + } + + pub fn is_directory_included(self, db: &dyn Db, path: &SystemPath) -> bool { + ProjectFilesFilter::from_project(db, self) + .is_directory_included(path, GlobFilterCheckMode::Adhoc) + == IncludeResult::Included } pub fn reload(self, db: &mut dyn Db, metadata: ProjectMetadata) { @@ -169,17 +187,23 @@ impl Project { assert_eq!(self.root(db), metadata.root()); if &metadata != self.metadata(db) { - let (settings, settings_diagnostics) = metadata.options().to_settings(db); - - if self.settings(db) != &settings { - self.set_settings(db).to(settings); - } + match metadata.options().to_settings(db, metadata.root()) { + Ok((settings, settings_diagnostics)) => { + if self.settings(db) != &settings { + self.set_settings(db).to(Box::new(settings)); + } - if self.settings_diagnostics(db) != settings_diagnostics { - self.set_settings_diagnostics(db).to(settings_diagnostics); + if self.settings_diagnostics(db) != settings_diagnostics { + self.set_settings_diagnostics(db).to(settings_diagnostics); + } + } + Err(error) => { + self.set_settings_diagnostics(db) + .to(vec![error.into_diagnostic()]); + } } - self.set_metadata(db).to(metadata); + self.set_metadata(db).to(Box::new(metadata)); } self.reload_files(db); @@ -248,6 +272,10 @@ impl Project { } pub(crate) fn check_file(self, db: &dyn Db, file: File) -> Vec { + if !self.is_file_open(db, file) { + return Vec::new(); + } + let mut file_diagnostics: Vec<_> = self .settings_diagnostics(db) .iter() diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 50e07aad1d136b..6c7696278cb6da 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -1,13 +1,26 @@ use crate::Db; -use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSourceGuard}; -use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, Severity, Span}; +use crate::glob::{ + ExcludeFilterBuilder, IncludeExcludeFilter, IncludeFilterBuilder, PortableGlobPattern, +}; +use crate::metadata::settings::SrcSettings; +use crate::metadata::value::{ + RangedValue, RelativeExcludePattern, RelativeIncludePattern, RelativePathBuf, ValueSource, + ValueSourceGuard, +}; + +use ruff_db::diagnostic::{ + Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, Severity, + Span, SubDiagnostic, +}; use ruff_db::files::system_path_to_file; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_macros::{Combine, OptionsMetadata}; use ruff_python_ast::PythonVersion; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::borrow::Cow; +use std::fmt::{self, Debug, Display}; +use std::sync::Arc; use thiserror::Error; use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection}; use ty_python_semantic::{ @@ -210,24 +223,44 @@ impl Options { } } - #[must_use] - pub(crate) fn to_settings(&self, db: &dyn Db) -> (Settings, Vec) { + pub(crate) fn to_settings( + &self, + db: &dyn Db, + project_root: &SystemPath, + ) -> Result<(Settings, Vec), ToSettingsError> { let (rules, diagnostics) = self.to_rule_selection(db); - let mut settings = Settings::new(rules, self.src.as_ref()); + let terminal_options = self.terminal.clone().unwrap_or_default(); + let terminal = TerminalSettings { + output_format: terminal_options + .output_format + .as_deref() + .copied() + .unwrap_or_default(), + error_on_warning: terminal_options.error_on_warning.unwrap_or_default(), + }; - if let Some(terminal) = self.terminal.as_ref() { - settings.set_terminal(TerminalSettings { - output_format: terminal - .output_format - .as_deref() - .copied() - .unwrap_or_default(), - error_on_warning: terminal.error_on_warning.unwrap_or_default(), - }); - } + let src_options = if let Some(src) = self.src.as_ref() { + Cow::Borrowed(src) + } else { + Cow::Owned(SrcOptions::default()) + }; - (settings, diagnostics) + let src = src_options + .to_settings(db, project_root) + .map_err(|err| ToSettingsError { + diagnostic: err, + output_format: terminal.output_format, + color: colored::control::SHOULD_COLORIZE.should_colorize(), + })?; + + let settings = Settings { + rules: Arc::new(rules), + terminal, + src, + }; + + Ok((settings, diagnostics)) } #[must_use] @@ -290,14 +323,10 @@ impl Options { ), }; - let span = file.map(Span::from).map(|span| { - if let Some(range) = rule_name.range() { - span.with_range(range) - } else { - span - } + let annotation = file.map(Span::from).map(|span| { + Annotation::primary(span.with_optional_range(rule_name.range())) }); - diagnostics.push(diagnostic.with_span(span)); + diagnostics.push(diagnostic.with_annotation(annotation)); } } } @@ -442,6 +471,246 @@ pub struct SrcOptions { )] #[serde(skip_serializing_if = "Option::is_none")] pub respect_ignore_files: Option, + + /// A list of files and directories to check. The `include` option + /// follows a similar syntax to `.gitignore` but reversed: + /// Including a file or directory will make it so that it (and its contents) + /// are type checked. + /// + /// - `./src/` matches only a directory + /// - `./src` matches both files and directories + /// - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) + /// - `*` matches any (possibly empty) sequence of characters (except `/`). + /// - `**` matches zero or more path components. + /// This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. + /// A sequence of more than two consecutive `*` characters is also invalid. + /// - `?` matches any single character except `/` + /// - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, + /// so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid. + /// + /// Unlike `exclude`, all paths are anchored relative to the project root (`src` only + /// matches `/src` and not `/test/src`). + /// + /// `exclude` take precedence over `include`. + #[serde(skip_serializing_if = "Option::is_none")] + pub include: Option>, + + /// A list of file and directory patterns to exclude from type checking. + /// + /// Patterns follow a syntax similar to `.gitignore`: + /// - `./src/` matches only a directory + /// - `./src` matches both files and directories + /// - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) + /// - `*` matches any (possibly empty) sequence of characters (except `/`). + /// - `**` matches zero or more path components. + /// This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. + /// A sequence of more than two consecutive `*` characters is also invalid. + /// - `?` matches any single character except `/` + /// - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, + /// so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid. + /// - `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded) + /// + /// By default, the following directories are excluded: + /// + /// - `.bzr` + /// - `.direnv` + /// - `.eggs` + /// - `.git` + /// - `.git-rewrite` + /// - `.hg` + /// - `.mypy_cache` + /// - `.nox` + /// - `.pants.d` + /// - `.pytype` + /// - `.ruff_cache` + /// - `.svn` + /// - `.tox` + /// - `.venv` + /// - `__pypackages__` + /// - `_build` + /// - `buck-out` + /// - `dist` + /// - `node_modules` + /// - `venv` + /// + /// You can override any default exclude by using a negated pattern. For example, + /// to re-include `dist` use `exclude = ["!dist"]` + #[option( + default = r#"null"#, + value_type = r#"list[str]"#, + example = r#" + exclude = [ + "generated", + "*.proto", + "tests/fixtures/**", + "!tests/fixtures/important.py" # Include this one file + ] + "# + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude: Option>, +} + +impl SrcOptions { + fn to_settings( + &self, + db: &dyn Db, + project_root: &SystemPath, + ) -> Result> { + let mut includes = IncludeFilterBuilder::new(); + let system = db.system(); + + if let Some(include) = self.include.as_ref() { + for pattern in include { + // Check the relative pattern for better error messages. + pattern.absolute(project_root, system) + .and_then(|include| Ok(includes.add(&include)?)) + .map_err(|err| { + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + format!("Invalid include pattern: {err}"), + Severity::Error, + ); + + match pattern.source() { + ValueSource::File(file_path) => { + if let Ok(file) = system_path_to_file(db.upcast(), &**file_path) { + diagnostic + .with_message("Invalid include pattern") + .with_annotation(Some( + Annotation::primary( + Span::from(file) + .with_optional_range(pattern.range()), + ) + .message(err.to_string()), + )) + } else { + diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "The pattern is defined in the `src.include` option in your configuration file", + ))) + } + } + ValueSource::Cli => diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "The pattern was specified on the CLI using `--include`", + ))), + } + })?; + } + } else { + includes + .add( + &PortableGlobPattern::parse("**", false) + .unwrap() + .into_absolute(""), + ) + .unwrap(); + } + + let include = includes.build().map_err(|_| { + // https://github.com/BurntSushi/ripgrep/discussions/2927 + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + "The `src.include` patterns resulted in a regex that is too large".to_string(), + Severity::Error, + ); + diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "Please open an issue on the ty repository and share the pattern that caused the error.", + ))) + })?; + + let mut excludes = ExcludeFilterBuilder::new(); + + // Add the default excludes first, so that a user can override them with a negated exclude pattern. + for pattern in [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "dist", + "node_modules", + "venv", + ] { + PortableGlobPattern::parse(pattern, true) + .and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?)) + .unwrap_or_else(|err| { + panic!( + "Expected default exclude to be valid glob but adding it failed with: {err}" + ) + }); + } + + for exclude in self.exclude.as_deref().unwrap_or_default() { + // Check the relative path for better error messages. + exclude.absolute(project_root, system) + .and_then(|pattern| Ok(excludes.add(&pattern)?)) + .map_err(|err| { + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + format!("Invalid exclude pattern: {err}"), + Severity::Error, + ); + + match exclude.source() { + ValueSource::File(file_path) => { + if let Ok(file) = system_path_to_file(db.upcast(), &**file_path) { + diagnostic + .with_message("Invalid exclude pattern") + .with_annotation(Some( + Annotation::primary( + Span::from(file) + .with_optional_range(exclude.range()), + ) + .message(err.to_string()), + )) + } else { + diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "The pattern is defined in the `src.exclude` option in your configuration file", + ))) + } + } + ValueSource::Cli => diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "The pattern was specified on the CLI using `--exclude`", + ))), + } + })?; + } + + let exclude = excludes.build().map_err(|_| { + // https://github.com/BurntSushi/ripgrep/discussions/2927 + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + "The `src.exclude` patterns resulted in a regex that is too large".to_string(), + Severity::Error, + ); + diagnostic.sub(Some(SubDiagnostic::new( + Severity::Info, + "Please open an issue on the ty repository and share the pattern that caused the error.", + ))) + })?; + + Ok(SrcSettings { + respect_ignore_files: self.respect_ignore_files.unwrap_or(true), + files: IncludeExcludeFilter::new(include, exclude), + }) + } } #[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)] @@ -494,6 +763,54 @@ pub struct TerminalOptions { pub error_on_warning: Option, } +/// Error returned when the settings can't be resolved because of a hard error. +#[derive(Debug)] +pub struct ToSettingsError { + diagnostic: Box, + output_format: DiagnosticFormat, + color: bool, +} + +impl ToSettingsError { + pub fn pretty<'a>(&'a self, db: &'a dyn Db) -> impl fmt::Display + use<'a> { + struct DisplayPretty<'a> { + db: &'a dyn Db, + error: &'a ToSettingsError, + } + + impl fmt::Display for DisplayPretty<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_config = DisplayDiagnosticConfig::default() + .format(self.error.output_format) + .color(self.error.color); + + write!( + f, + "{}", + self.error + .diagnostic + .to_diagnostic() + .display(&self.db.upcast(), &display_config) + ) + } + } + + DisplayPretty { db, error: self } + } + + pub fn into_diagnostic(self) -> OptionDiagnostic { + *self.diagnostic + } +} + +impl Display for ToSettingsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.diagnostic.message) + } +} + +impl std::error::Error for ToSettingsError {} + #[cfg(feature = "schemars")] mod schema { use crate::DEFAULT_LINT_REGISTRY; @@ -568,7 +885,8 @@ pub struct OptionDiagnostic { id: DiagnosticId, message: String, severity: Severity, - span: Option, + annotation: Option, + sub: Option, } impl OptionDiagnostic { @@ -577,23 +895,35 @@ impl OptionDiagnostic { id, message, severity, - span: None, + annotation: None, + sub: None, + } + } + + #[must_use] + fn with_message(self, message: impl Display) -> Self { + OptionDiagnostic { + message: message.to_string(), + ..self } } #[must_use] - fn with_span(self, span: Option) -> Self { - OptionDiagnostic { span, ..self } + fn with_annotation(self, annotation: Option) -> Self { + OptionDiagnostic { annotation, ..self } + } + + #[must_use] + fn sub(self, sub: Option) -> Self { + OptionDiagnostic { sub, ..self } } pub(crate) fn to_diagnostic(&self) -> Diagnostic { - if let Some(ref span) = self.span { - let mut diag = Diagnostic::new(self.id, self.severity, ""); - diag.annotate(Annotation::primary(span.clone()).message(&self.message)); - diag - } else { - Diagnostic::new(self.id, self.severity, &self.message) + let mut diag = Diagnostic::new(self.id, self.severity, &self.message); + if let Some(annotation) = self.annotation.clone() { + diag.annotate(annotation); } + diag } } diff --git a/crates/ty_project/src/metadata/settings.rs b/crates/ty_project/src/metadata/settings.rs index 0a48fc559cd3ff..156f72632d92c9 100644 --- a/crates/ty_project/src/metadata/settings.rs +++ b/crates/ty_project/src/metadata/settings.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use crate::metadata::options::SrcOptions; use ruff_db::diagnostic::DiagnosticFormat; use ty_python_semantic::lint::RuleSelection; +use crate::glob::IncludeExcludeFilter; + /// The resolved [`super::Options`] for the project. /// /// Unlike [`super::Options`], the struct has default values filled in and @@ -19,32 +20,18 @@ use ty_python_semantic::lint::RuleSelection; /// Settings that are part of [`ty_python_semantic::ProgramSettings`] are not included here. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Settings { - rules: Arc, - - terminal: TerminalSettings, - - respect_ignore_files: bool, + pub(super) rules: Arc, + pub(super) terminal: TerminalSettings, + pub(super) src: SrcSettings, } impl Settings { - pub fn new(rules: RuleSelection, src_options: Option<&SrcOptions>) -> Self { - let respect_ignore_files = src_options - .and_then(|src| src.respect_ignore_files) - .unwrap_or(true); - - Self { - rules: Arc::new(rules), - terminal: TerminalSettings::default(), - respect_ignore_files, - } - } - pub fn rules(&self) -> &RuleSelection { &self.rules } - pub fn respect_ignore_files(&self) -> bool { - self.respect_ignore_files + pub fn src(&self) -> &SrcSettings { + &self.src } pub fn to_rules(&self) -> Arc { @@ -54,10 +41,6 @@ impl Settings { pub fn terminal(&self) -> &TerminalSettings { &self.terminal } - - pub fn set_terminal(&mut self, terminal: TerminalSettings) { - self.terminal = terminal; - } } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -65,3 +48,9 @@ pub struct TerminalSettings { pub output_format: DiagnosticFormat, pub error_on_warning: bool, } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SrcSettings { + pub respect_ignore_files: bool, + pub files: IncludeExcludeFilter, +} diff --git a/crates/ty_project/src/metadata/value.rs b/crates/ty_project/src/metadata/value.rs index f2f8db1e225226..8e19e6b402f3cc 100644 --- a/crates/ty_project/src/metadata/value.rs +++ b/crates/ty_project/src/metadata/value.rs @@ -1,5 +1,6 @@ use crate::Db; use crate::combine::Combine; +use crate::glob::{AbsolutePortableGlobPattern, PortableGlobError, PortableGlobPattern}; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_macros::Combine; use ruff_text_size::{TextRange, TextSize}; @@ -356,3 +357,102 @@ impl RelativePathBuf { SystemPath::absolute(&self.0, relative_to) } } + +#[derive( + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Combine, +)] +#[serde(transparent)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct RelativeIncludePattern(RangedValue); + +impl RelativeIncludePattern { + pub fn new(pattern: &str, source: ValueSource) -> Self { + Self(RangedValue::new(pattern.to_string(), source)) + } + + pub fn cli(pattern: &str) -> Self { + Self::new(pattern, ValueSource::Cli) + } + + pub(crate) fn source(&self) -> &ValueSource { + self.0.source() + } + + pub(crate) fn range(&self) -> Option { + self.0.range() + } + + /// Resolves the absolute pattern for `self` based on its origin. + pub(crate) fn absolute( + &self, + project_root: &SystemPath, + system: &dyn System, + ) -> Result { + let relative_to = match &self.0.source { + ValueSource::File(_) => project_root, + ValueSource::Cli => system.current_directory(), + }; + + let pattern = PortableGlobPattern::parse(&self.0, false)?; + Ok(pattern.into_absolute(relative_to)) + } +} + +#[derive( + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Combine, +)] +#[serde(transparent)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct RelativeExcludePattern(RangedValue); + +impl RelativeExcludePattern { + pub fn new(pattern: &str, source: ValueSource) -> Self { + Self(RangedValue::new(pattern.to_string(), source)) + } + + pub fn cli(pattern: &str) -> Self { + Self::new(pattern, ValueSource::Cli) + } + + pub(crate) fn source(&self) -> &ValueSource { + self.0.source() + } + + pub(crate) fn range(&self) -> Option { + self.0.range() + } + + /// Resolves the absolute pattern for `self` based on its origin. + pub(crate) fn absolute( + &self, + project_root: &SystemPath, + system: &dyn System, + ) -> Result { + let relative_to = match &self.0.source { + ValueSource::File(_) => project_root, + ValueSource::Cli => system.current_directory(), + }; + + let pattern = PortableGlobPattern::parse(&self.0, true)?; + + Ok(pattern.into_absolute(relative_to)) + } +} diff --git a/crates/ty_project/src/walk.rs b/crates/ty_project/src/walk.rs index e6d7f7fdc33c3b..761d1fd3e5e008 100644 --- a/crates/ty_project/src/walk.rs +++ b/crates/ty_project/src/walk.rs @@ -1,7 +1,8 @@ -use crate::{Db, IOErrorDiagnostic, IOErrorKind, Project}; +use crate::glob::IncludeExcludeFilter; +use crate::{Db, GlobFilterCheckMode, IOErrorDiagnostic, IOErrorKind, IncludeResult, Project}; use ruff_db::files::{File, system_path_to_file}; use ruff_db::system::walk_directory::{ErrorKind, WalkDirectoryBuilder, WalkState}; -use ruff_db::system::{FileType, SystemPath, SystemPathBuf}; +use ruff_db::system::{SystemPath, SystemPathBuf}; use ruff_python_ast::PySourceType; use rustc_hash::{FxBuildHasher, FxHashSet}; use std::path::PathBuf; @@ -13,22 +14,47 @@ use thiserror::Error; /// /// This struct mainly exists because `dyn Db` isn't `Send` or `Sync`, making it impossible /// to access fields from within the walker. -#[derive(Default, Debug)] +#[derive(Debug)] pub(crate) struct ProjectFilesFilter<'a> { /// The same as [`Project::included_paths_or_root`]. included_paths: &'a [SystemPathBuf], - /// The filter skips checking if the path is in `included_paths` if set to `true`. - /// - /// Skipping this check is useful when the walker only walks over `included_paths`. - skip_included_paths: bool, + /// The resolved `src.include` and `src.exclude` filter. + src_filter: &'a IncludeExcludeFilter, } impl<'a> ProjectFilesFilter<'a> { pub(crate) fn from_project(db: &'a dyn Db, project: Project) -> Self { Self { included_paths: project.included_paths_or_root(db), - skip_included_paths: false, + src_filter: &project.settings(db).src().files, + } + } + + fn match_included_paths( + &self, + path: &SystemPath, + mode: GlobFilterCheckMode, + ) -> Option { + match mode { + GlobFilterCheckMode::TopDown => Some(CheckPathMatch::Partial), + GlobFilterCheckMode::Adhoc => { + self.included_paths + .iter() + .filter_map(|included_path| { + if let Ok(relative_path) = path.strip_prefix(included_path) { + // Exact matches are always included + if relative_path.as_str().is_empty() { + Some(CheckPathMatch::Full) + } else { + Some(CheckPathMatch::Partial) + } + } else { + None + } + }) + .max() + } } } @@ -45,47 +71,42 @@ impl<'a> ProjectFilesFilter<'a> { /// This method may return `true` for files that don't end up being included when walking the /// project tree because it doesn't consider `.gitignore` and other ignore files when deciding /// if a file's included. - pub(crate) fn is_included(&self, path: &SystemPath) -> bool { - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] - enum CheckPathMatch { - /// The path is a partial match of the checked path (it's a sub path) - Partial, - - /// The path matches a check path exactly. - Full, + pub(crate) fn is_file_included( + &self, + path: &SystemPath, + mode: GlobFilterCheckMode, + ) -> IncludeResult { + match self.match_included_paths(path, mode) { + None => IncludeResult::NotIncluded, + Some(CheckPathMatch::Partial) => self.src_filter.is_file_included(path, mode), + Some(CheckPathMatch::Full) => IncludeResult::Included, } + } - let m = if self.skip_included_paths { - Some(CheckPathMatch::Partial) - } else { - self.included_paths - .iter() - .filter_map(|included_path| { - if let Ok(relative_path) = path.strip_prefix(included_path) { - // Exact matches are always included - if relative_path.as_str().is_empty() { - Some(CheckPathMatch::Full) - } else { - Some(CheckPathMatch::Partial) - } - } else { - None - } - }) - .max() - }; - - match m { - None => false, + pub(crate) fn is_directory_included( + &self, + path: &SystemPath, + mode: GlobFilterCheckMode, + ) -> IncludeResult { + match self.match_included_paths(path, mode) { + None => IncludeResult::NotIncluded, Some(CheckPathMatch::Partial) => { - // TODO: For partial matches, only include the file if it is included by the project's include/exclude settings. - true + self.src_filter.is_directory_maybe_included(path, mode) } - Some(CheckPathMatch::Full) => true, + Some(CheckPathMatch::Full) => IncludeResult::Included, } } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum CheckPathMatch { + /// The path is a partial match of the checked path (it's a sub path) + Partial, + + /// The path matches a check path exactly. + Full, +} + pub(crate) struct ProjectFilesWalker<'a> { walker: WalkDirectoryBuilder, @@ -96,9 +117,7 @@ impl<'a> ProjectFilesWalker<'a> { pub(crate) fn new(db: &'a dyn Db) -> Self { let project = db.project(); - let mut filter = ProjectFilesFilter::from_project(db, project); - // It's unnecessary to filter on included paths because it only iterates over those to start with. - filter.skip_included_paths = true; + let filter = ProjectFilesFilter::from_project(db, project); Self::from_paths(db, project.included_paths_or_root(db), filter) .expect("included_paths_or_root to never return an empty iterator") @@ -132,7 +151,7 @@ impl<'a> ProjectFilesWalker<'a> { let mut walker = db .system() .walk_directory(paths.next()?.as_ref()) - .standard_filters(db.project().settings(db).respect_ignore_files()) + .standard_filters(db.project().settings(db).src().respect_ignore_files) .ignore_hidden(false); for path in paths { @@ -152,25 +171,51 @@ impl<'a> ProjectFilesWalker<'a> { Box::new(|entry| { match entry { Ok(entry) => { - if !self.filter.is_included(entry.path()) { - tracing::debug!("Ignoring not-included path: {}", entry.path()); - return WalkState::Skip; + // Skip excluded directories unless they were explicitly passed to the walker + // (which is the case passed to `ty check `). + if entry.file_type().is_directory() && entry.depth() > 0 { + return match self.filter.is_directory_included(entry.path(), GlobFilterCheckMode::TopDown) { + IncludeResult::Included => WalkState::Continue, + IncludeResult::Excluded => { + tracing::debug!("Skipping directory '{path}' because it is excluded by a default or `src.exclude` pattern", path=entry.path()); + WalkState::Skip + }, + IncludeResult::NotIncluded => { + tracing::debug!("Skipping directory `{path}` because it doesn't match any `src.include` pattern or path specified on the CLI", path=entry.path()); + WalkState::Skip + }, + }; } - // Skip over any non python files to avoid creating too many entries in `Files`. - match entry.file_type() { - FileType::File => { - if entry - .path() - .extension() - .and_then(PySourceType::try_from_extension) - .is_some() - { - let mut paths = paths.lock().unwrap(); - paths.push(entry.into_path()); + if entry.file_type().is_file() { + // Ignore any non python files to avoid creating too many entries in `Files`. + if entry + .path() + .extension() + .and_then(PySourceType::try_from_extension) + .is_none() + { + return WalkState::Continue; + } + + // For all files, except the ones that were explicitly passed to the walker (CLI), + // check if they're included in the project. + if entry.depth() > 0 { + match self.filter.is_file_included(entry.path(), GlobFilterCheckMode::TopDown) { + IncludeResult::Included => {}, + IncludeResult::Excluded => { + tracing::debug!("Ignoring file `{path}` because it is excluded by a default or `src.exclude` pattern.", path=entry.path()); + return WalkState::Continue; + }, + IncludeResult::NotIncluded => { + tracing::debug!("Ignoring file `{path}` because it doesn't match any `src.include` pattern or path specified on the CLI.", path=entry.path()); + return WalkState::Continue; + }, } } - FileType::Directory | FileType::Symlink => {} + + let mut paths = paths.lock().unwrap(); + paths.push(entry.into_path()); } } Err(error) => match error.kind() { diff --git a/ty.schema.json b/ty.schema.json index fc6306d7ec224f..d3180fb4db0e33 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -851,6 +851,26 @@ "SrcOptions": { "type": "object", "properties": { + "exclude": { + "description": "A list of file and directory patterns to exclude from type checking.\n\nPatterns follow a syntax similar to `.gitignore`: - `./src/` matches only a directory - `./src` matches both files and directories - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) - `*` matches any (possibly empty) sequence of characters (except `/`). - `**` matches zero or more path components. This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. A sequence of more than two consecutive `*` characters is also invalid. - `?` matches any single character except `/` - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid. - `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded)\n\nBy default, the following directories are excluded:\n\n- `.bzr` - `.direnv` - `.eggs` - `.git` - `.git-rewrite` - `.hg` - `.mypy_cache` - `.nox` - `.pants.d` - `.pytype` - `.ruff_cache` - `.svn` - `.tox` - `.venv` - `__pypackages__` - `_build` - `buck-out` - `dist` - `node_modules` - `venv`\n\nYou can override any default exclude by using a negated pattern. For example, to re-include `dist` use `exclude = [\"!dist\"]`", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "include": { + "description": "A list of files and directories to check. The `include` option follows a similar syntax to `.gitignore` but reversed: Including a file or directory will make it so that it (and its contents) are type checked.\n\n- `./src/` matches only a directory - `./src` matches both files and directories - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) - `*` matches any (possibly empty) sequence of characters (except `/`). - `**` matches zero or more path components. This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. A sequence of more than two consecutive `*` characters is also invalid. - `?` matches any single character except `/` - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.\n\nUnlike `exclude`, all paths are anchored relative to the project root (`src` only matches `/src` and not `/test/src`).\n\n`exclude` take precedence over `include`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "respect-ignore-files": { "description": "Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. Enabled by default.", "type": [ From 015222900f16ae3100b3415e1b6be44aed867e4b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 12 Jun 2025 22:08:42 +0200 Subject: [PATCH 408/487] Support cancellation requests (#18627) --- crates/ruff/src/commands/server.rs | 13 +- crates/ruff/src/lib.rs | 9 +- crates/ruff_server/src/lib.rs | 42 ++- crates/ruff_server/src/message.rs | 54 ---- crates/ruff_server/src/server.rs | 265 ++++++--------- crates/ruff_server/src/server/api.rs | 304 +++++++++++++----- .../ruff_server/src/server/api/diagnostics.rs | 15 +- .../src/server/api/notifications.rs | 3 +- .../src/server/api/notifications/cancel.rs | 33 +- .../server/api/notifications/did_change.rs | 8 +- .../notifications/did_change_configuration.rs | 6 +- .../api/notifications/did_change_notebook.rs | 8 +- .../notifications/did_change_watched_files.rs | 21 +- .../api/notifications/did_change_workspace.rs | 8 +- .../src/server/api/notifications/did_close.rs | 8 +- .../api/notifications/did_close_notebook.rs | 6 +- .../src/server/api/notifications/did_open.rs | 8 +- .../api/notifications/did_open_notebook.rs | 8 +- crates/ruff_server/src/server/api/requests.rs | 2 + .../src/server/api/requests/code_action.rs | 6 +- .../api/requests/code_action_resolve.rs | 5 +- .../src/server/api/requests/diagnostic.rs | 4 +- .../server/api/requests/execute_command.rs | 24 +- .../src/server/api/requests/format.rs | 6 +- .../src/server/api/requests/format_range.rs | 6 +- .../src/server/api/requests/hover.rs | 6 +- .../src/server/api/requests/shutdown.rs | 17 + crates/ruff_server/src/server/api/traits.rs | 13 +- crates/ruff_server/src/server/client.rs | 169 ---------- crates/ruff_server/src/server/connection.rs | 129 +------- crates/ruff_server/src/server/main_loop.rs | 209 ++++++++++++ crates/ruff_server/src/server/schedule.rs | 56 +--- .../ruff_server/src/server/schedule/task.rs | 39 +-- .../src/server/schedule/thread/pool.rs | 22 +- crates/ruff_server/src/session.rs | 40 ++- crates/ruff_server/src/session/client.rs | 248 ++++++++++++++ crates/ruff_server/src/session/index.rs | 20 +- .../src/session/index/ruff_settings.rs | 10 +- crates/ruff_server/src/session/options.rs | 30 +- .../ruff_server/src/session/request_queue.rs | 198 ++++++++++++ crates/ruff_server/src/session/settings.rs | 9 +- crates/ruff_server/tests/notebook.rs | 13 +- crates/ty_server/src/server/api.rs | 2 +- crates/ty_server/src/server/main_loop.rs | 5 +- .../src/server/schedule/thread/pool.rs | 2 +- crates/ty_server/src/session/client.rs | 72 ++--- 46 files changed, 1324 insertions(+), 857 deletions(-) delete mode 100644 crates/ruff_server/src/message.rs create mode 100644 crates/ruff_server/src/server/api/requests/shutdown.rs delete mode 100644 crates/ruff_server/src/server/client.rs create mode 100644 crates/ruff_server/src/server/main_loop.rs create mode 100644 crates/ruff_server/src/session/client.rs create mode 100644 crates/ruff_server/src/session/request_queue.rs diff --git a/crates/ruff/src/commands/server.rs b/crates/ruff/src/commands/server.rs index 817269bc7e63ba..837662e7d3c211 100644 --- a/crates/ruff/src/commands/server.rs +++ b/crates/ruff/src/commands/server.rs @@ -1,14 +1,7 @@ -use std::num::NonZeroUsize; - use crate::ExitStatus; use anyhow::Result; -use ruff_server::Server; - -pub(crate) fn run_server( - worker_threads: NonZeroUsize, - preview: Option, -) -> Result { - let server = Server::new(worker_threads, preview)?; - server.run().map(|()| ExitStatus::Success) +pub(crate) fn run_server(preview: Option) -> Result { + ruff_server::run(preview)?; + Ok(ExitStatus::Success) } diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index ef8f6397468806..bb4d8e02ab23dc 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -2,7 +2,6 @@ use std::fs::File; use std::io::{self, BufWriter, Write, stdout}; -use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::process::ExitCode; use std::sync::mpsc::channel; @@ -223,13 +222,7 @@ fn analyze_graph( } fn server(args: ServerCommand) -> Result { - let four = NonZeroUsize::new(4).unwrap(); - - // by default, we set the number of worker threads to `num_cpus`, with a maximum of 4. - let worker_threads = std::thread::available_parallelism() - .unwrap_or(four) - .min(four); - commands::server::run_server(worker_threads, args.resolve_preview()) + commands::server::run_server(args.resolve_preview()) } pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result { diff --git a/crates/ruff_server/src/lib.rs b/crates/ruff_server/src/lib.rs index ca4ee50ab892f0..784538a23e8c01 100644 --- a/crates/ruff_server/src/lib.rs +++ b/crates/ruff_server/src/lib.rs @@ -1,13 +1,15 @@ //! ## The Ruff Language Server +use std::num::NonZeroUsize; + +use anyhow::Context as _; pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument}; use lsp_types::CodeActionKind; -pub use server::Server; -pub use session::{ClientOptions, DocumentQuery, DocumentSnapshot, GlobalOptions, Session}; +pub use server::{ConnectionSender, MainLoopSender, Server}; +pub use session::{Client, ClientOptions, DocumentQuery, DocumentSnapshot, GlobalOptions, Session}; pub use workspace::{Workspace, Workspaces}; -#[macro_use] -mod message; +use crate::server::ConnectionInitializer; mod edit; mod fix; @@ -37,3 +39,35 @@ pub(crate) type Result = anyhow::Result; pub(crate) fn version() -> &'static str { ruff_linter::VERSION } + +pub fn run(preview: Option) -> Result<()> { + let four = NonZeroUsize::new(4).unwrap(); + + // by default, we set the number of worker threads to `num_cpus`, with a maximum of 4. + let worker_threads = std::thread::available_parallelism() + .unwrap_or(four) + .min(four); + + let (connection, io_threads) = ConnectionInitializer::stdio(); + + let server_result = Server::new(worker_threads, connection, preview) + .context("Failed to start server")? + .run(); + + let io_result = io_threads.join(); + + let result = match (server_result, io_result) { + (Ok(()), Ok(())) => Ok(()), + (Err(server), Err(io)) => Err(server).context(format!("IO thread error: {io}")), + (Err(server), _) => Err(server), + (_, Err(io)) => Err(io).context("IO thread error"), + }; + + if let Err(err) = result.as_ref() { + tracing::warn!("Server shut down with an error: {err}"); + } else { + tracing::info!("Server shut down"); + } + + result +} diff --git a/crates/ruff_server/src/message.rs b/crates/ruff_server/src/message.rs deleted file mode 100644 index 1b007ea38c5c21..00000000000000 --- a/crates/ruff_server/src/message.rs +++ /dev/null @@ -1,54 +0,0 @@ -use anyhow::Context; -use lsp_types::notification::Notification; -use std::sync::OnceLock; - -use crate::server::ClientSender; - -static MESSENGER: OnceLock = OnceLock::new(); - -pub(crate) fn init_messenger(client_sender: ClientSender) { - MESSENGER - .set(client_sender) - .expect("messenger should only be initialized once"); -} - -pub(crate) fn show_message(message: String, message_type: lsp_types::MessageType) { - try_show_message(message, message_type).unwrap(); -} - -pub(super) fn try_show_message( - message: String, - message_type: lsp_types::MessageType, -) -> crate::Result<()> { - MESSENGER - .get() - .ok_or_else(|| anyhow::anyhow!("messenger not initialized"))? - .send(lsp_server::Message::Notification( - lsp_server::Notification { - method: lsp_types::notification::ShowMessage::METHOD.into(), - params: serde_json::to_value(lsp_types::ShowMessageParams { - typ: message_type, - message, - })?, - }, - )) - .context("Failed to send message")?; - - Ok(()) -} - -/// Sends a request to display an error to the client with a formatted message. The error is sent -/// in a `window/showMessage` notification. -macro_rules! show_err_msg { - ($msg:expr$(, $($arg:tt)*)?) => { - crate::message::show_message(::core::format_args!($msg$(, $($arg)*)?).to_string(), lsp_types::MessageType::ERROR) - }; -} - -/// Sends a request to display a warning to the client with a formatted message. The warning is -/// sent in a `window/showMessage` notification. -macro_rules! show_warn_msg { - ($msg:expr$(, $($arg:tt)*)?) => { - crate::message::show_message(::core::format_args!($msg$(, $($arg)*)?).to_string(), lsp_types::MessageType::WARNING) - }; -} diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 9e9c462f3fd75b..19e0d75a233a34 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -1,19 +1,17 @@ //! Scheduling, I/O, and API endpoints. -use lsp_server as lsp; +use lsp_server::Connection; use lsp_types as types; use lsp_types::InitializeParams; +use lsp_types::MessageType; use std::num::NonZeroUsize; -// The new PanicInfoHook name requires MSRV >= 1.82 -#[expect(deprecated)] -use std::panic::PanicInfo; +use std::panic::PanicHookInfo; use std::str::FromStr; +use std::sync::Arc; use types::ClientCapabilities; use types::CodeActionKind; use types::CodeActionOptions; use types::DiagnosticOptions; -use types::DidChangeWatchedFilesRegistrationOptions; -use types::FileSystemWatcher; use types::NotebookCellSelector; use types::NotebookDocumentSyncOptions; use types::NotebookSelector; @@ -24,37 +22,38 @@ use types::TextDocumentSyncOptions; use types::WorkDoneProgressOptions; use types::WorkspaceFoldersServerCapabilities; -use self::connection::Connection; -use self::connection::ConnectionInitializer; -use self::schedule::Scheduler; -use self::schedule::Task; -use self::schedule::event_loop_thread; +pub(crate) use self::connection::ConnectionInitializer; +pub use self::connection::ConnectionSender; +use self::schedule::spawn_main_loop; use crate::PositionEncoding; -use crate::session::AllOptions; -use crate::session::Session; +pub use crate::server::main_loop::MainLoopSender; +pub(crate) use crate::server::main_loop::{Event, MainLoopReceiver}; +use crate::session::{AllOptions, Client, Session}; use crate::workspace::Workspaces; +pub(crate) use api::Error; mod api; -mod client; mod connection; +mod main_loop; mod schedule; -use crate::message::try_show_message; -pub(crate) use connection::ClientSender; - pub(crate) type Result = std::result::Result; pub struct Server { connection: Connection, client_capabilities: ClientCapabilities, worker_threads: NonZeroUsize, + main_loop_receiver: MainLoopReceiver, + main_loop_sender: MainLoopSender, session: Session, } impl Server { - pub fn new(worker_threads: NonZeroUsize, preview: Option) -> crate::Result { - let connection = ConnectionInitializer::stdio(); - + pub(crate) fn new( + worker_threads: NonZeroUsize, + connection: ConnectionInitializer, + preview: Option, + ) -> crate::Result { let (id, init_params) = connection.initialize_start()?; let client_capabilities = init_params.capabilities; @@ -69,7 +68,7 @@ impl Server { crate::version(), )?; - crate::message::init_messenger(connection.make_sender()); + let (main_loop_sender, main_loop_receiver) = crossbeam::channel::bounded(32); let InitializeParams { initialization_options, @@ -77,13 +76,17 @@ impl Server { .. } = init_params; + let client = Client::new(main_loop_sender.clone(), connection.sender.clone()); let mut all_options = AllOptions::from_value( initialization_options .unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())), + &client, ); + if let Some(preview) = preview { all_options.set_preview(preview); } + let AllOptions { global: global_options, workspace: workspace_options, @@ -101,159 +104,33 @@ impl Server { tracing::debug!("Negotiated position encoding: {position_encoding:?}"); - let global = global_options.into_settings(); + let global = global_options.into_settings(client.clone()); Ok(Self { connection, worker_threads, - session: Session::new(&client_capabilities, position_encoding, global, &workspaces)?, + main_loop_sender, + main_loop_receiver, + session: Session::new( + &client_capabilities, + position_encoding, + global, + &workspaces, + &client, + )?, client_capabilities, }) } - pub fn run(self) -> crate::Result<()> { - // The new PanicInfoHook name requires MSRV >= 1.82 - #[expect(deprecated)] - type PanicHook = Box) + 'static + Sync + Send>; - struct RestorePanicHook { - hook: Option, - } - - impl Drop for RestorePanicHook { - fn drop(&mut self) { - if let Some(hook) = self.hook.take() { - std::panic::set_hook(hook); - } - } - } - - // unregister any previously registered panic hook - // The hook will be restored when this function exits. - let _ = RestorePanicHook { - hook: Some(std::panic::take_hook()), - }; - - // When we panic, try to notify the client. - std::panic::set_hook(Box::new(move |panic_info| { - use std::io::Write; - - let backtrace = std::backtrace::Backtrace::force_capture(); - tracing::error!("{panic_info}\n{backtrace}"); - - // we also need to print to stderr directly for when using `$logTrace` because - // the message won't be sent to the client. - // But don't use `eprintln` because `eprintln` itself may panic if the pipe is broken. - let mut stderr = std::io::stderr().lock(); - writeln!(stderr, "{panic_info}\n{backtrace}").ok(); - - try_show_message( - "The Ruff language server exited with a panic. See the logs for more details." - .to_string(), - lsp_types::MessageType::ERROR, - ) - .ok(); - })); - - event_loop_thread(move || { - Self::event_loop( - &self.connection, - &self.client_capabilities, - self.session, - self.worker_threads, - )?; - self.connection.close()?; - Ok(()) - })? - .join() - } - - fn event_loop( - connection: &Connection, - client_capabilities: &ClientCapabilities, - mut session: Session, - worker_threads: NonZeroUsize, - ) -> crate::Result<()> { - let mut scheduler = - schedule::Scheduler::new(&mut session, worker_threads, connection.make_sender()); - - Self::try_register_capabilities(client_capabilities, &mut scheduler); - for msg in connection.incoming() { - if connection.handle_shutdown(&msg)? { - break; - } - let task = match msg { - lsp::Message::Request(req) => api::request(req), - lsp::Message::Notification(notification) => api::notification(notification), - lsp::Message::Response(response) => scheduler.response(response), - }; - scheduler.dispatch(task); - } + pub fn run(mut self) -> crate::Result<()> { + let client = Client::new( + self.main_loop_sender.clone(), + self.connection.sender.clone(), + ); - Ok(()) - } + let _panic_hook = ServerPanicHookHandler::new(client); - fn try_register_capabilities( - client_capabilities: &ClientCapabilities, - scheduler: &mut Scheduler, - ) { - let dynamic_registration = client_capabilities - .workspace - .as_ref() - .and_then(|workspace| workspace.did_change_watched_files) - .and_then(|watched_files| watched_files.dynamic_registration) - .unwrap_or_default(); - if dynamic_registration { - // Register all dynamic capabilities here - - // `workspace/didChangeWatchedFiles` - // (this registers the configuration file watcher) - let params = lsp_types::RegistrationParams { - registrations: vec![lsp_types::Registration { - id: "ruff-server-watch".into(), - method: "workspace/didChangeWatchedFiles".into(), - register_options: Some( - serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { - watchers: vec![ - FileSystemWatcher { - glob_pattern: types::GlobPattern::String( - "**/.ruff.toml".into(), - ), - kind: None, - }, - FileSystemWatcher { - glob_pattern: types::GlobPattern::String("**/ruff.toml".into()), - kind: None, - }, - FileSystemWatcher { - glob_pattern: types::GlobPattern::String( - "**/pyproject.toml".into(), - ), - kind: None, - }, - ], - }) - .unwrap(), - ), - }], - }; - - let response_handler = |()| { - tracing::info!("Configuration file watcher successfully registered"); - Task::nothing() - }; - - if let Err(err) = scheduler - .request::(params, response_handler) - { - tracing::error!( - "An error occurred when trying to register the configuration file watcher: {err}" - ); - } - } else { - tracing::warn!( - "LSP client does not support dynamic capability registration - automatic configuration reloading will not be available." - ); - } + spawn_main_loop(move || self.main_loop())?.join() } fn find_best_position_encoding(client_capabilities: &ClientCapabilities) -> PositionEncoding { @@ -445,3 +322,63 @@ impl FromStr for SupportedCommand { }) } } + +type PanicHook = Box) + 'static + Sync + Send>; + +struct ServerPanicHookHandler { + hook: Option, + // Hold on to the strong reference for as long as the panic hook is set. + _client: Arc, +} + +impl ServerPanicHookHandler { + fn new(client: Client) -> Self { + let hook = std::panic::take_hook(); + let client = Arc::new(client); + + // Use a weak reference to the client because it must be dropped when exiting or the + // io-threads join hangs forever (because client has a reference to the connection sender). + let hook_client = Arc::downgrade(&client); + + // When we panic, try to notify the client. + std::panic::set_hook(Box::new(move |panic_info| { + use std::io::Write; + + let backtrace = std::backtrace::Backtrace::force_capture(); + tracing::error!("{panic_info}\n{backtrace}"); + + // we also need to print to stderr directly for when using `$logTrace` because + // the message won't be sent to the client. + // But don't use `eprintln` because `eprintln` itself may panic if the pipe is broken. + let mut stderr = std::io::stderr().lock(); + writeln!(stderr, "{panic_info}\n{backtrace}").ok(); + + if let Some(client) = hook_client.upgrade() { + client + .show_message( + "The Ruff language server exited with a panic. See the logs for more details.", + MessageType::ERROR, + ) + .ok(); + } + })); + + Self { + hook: Some(hook), + _client: client, + } + } +} + +impl Drop for ServerPanicHookHandler { + fn drop(&mut self) { + if std::thread::panicking() { + // Calling `std::panic::set_hook` while panicking results in a panic. + return; + } + + if let Some(hook) = self.hook.take() { + std::panic::set_hook(hook); + } + } +} diff --git a/crates/ruff_server/src/server/api.rs b/crates/ruff_server/src/server/api.rs index aa419c6348d815..42c73528cf9159 100644 --- a/crates/ruff_server/src/server/api.rs +++ b/crates/ruff_server/src/server/api.rs @@ -1,17 +1,30 @@ -use crate::{server::schedule::Task, session::Session}; -use lsp_server as server; +use std::panic::UnwindSafe; + +use anyhow::anyhow; +use lsp_server::{self as server, RequestId}; +use lsp_types::{notification::Notification, request::Request}; +use notifications as notification; +use requests as request; + +use crate::{ + server::{ + api::traits::{ + BackgroundDocumentNotificationHandler, BackgroundDocumentRequestHandler, + SyncNotificationHandler, + }, + schedule::Task, + }, + session::{Client, Session}, +}; mod diagnostics; mod notifications; mod requests; mod traits; -use notifications as notification; -use requests as request; - use self::traits::{NotificationHandler, RequestHandler}; -use super::{Result, client::Responder, schedule::BackgroundSchedule}; +use super::{Result, schedule::BackgroundSchedule}; /// Defines the `document_url` method for implementers of [`traits::Notification`] and [`traits::Request`], /// given the parameter type used by the implementer. @@ -25,7 +38,13 @@ macro_rules! define_document_url { use define_document_url; -pub(super) fn request<'a>(req: server::Request) -> Task<'a> { +/// Processes a request from the client to the server. +/// +/// The LSP specification requires that each request has exactly one response. Therefore, +/// it's crucial that all paths in this method call [`Client::respond`] exactly once. +/// The only exception to this is requests that were cancelled by the client. In this case, +/// the response was already sent by the [`notification::CancelNotificationHandler`]. +pub(super) fn request(req: server::Request) -> Task { let id = req.id.clone(); match req.method.as_str() { @@ -38,7 +57,7 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { request::DocumentDiagnostic::METHOD => { background_request_task::(req, BackgroundSchedule::Worker) } - request::ExecuteCommand::METHOD => local_request_task::(req), + request::ExecuteCommand::METHOD => sync_request_task::(req), request::Format::METHOD => { background_request_task::(req, BackgroundSchedule::Fmt) } @@ -48,46 +67,67 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { request::Hover::METHOD => { background_request_task::(req, BackgroundSchedule::Worker) } + lsp_types::request::Shutdown::METHOD => sync_request_task::(req), method => { tracing::warn!("Received request {method} which does not have a handler"); - return Task::nothing(); + let result: Result<()> = Err(Error::new( + anyhow!("Unknown request: {method}"), + server::ErrorCode::MethodNotFound, + )); + return Task::immediate(id, result); } } .unwrap_or_else(|err| { tracing::error!("Encountered error when routing request with ID {id}: {err}"); - show_err_msg!( - "Ruff failed to handle a request from the editor. Check the logs for more details." - ); - let result: Result<()> = Err(err); - Task::immediate(id, result) + + Task::sync(move |_session, client| { + client.show_error_message( + "Ruff failed to handle a request from the editor. Check the logs for more details.", + ); + respond_silent_error( + id, + client, + lsp_server::ResponseError { + code: err.code as i32, + message: err.to_string(), + data: None, + }, + ); + }) }) } -pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { +pub(super) fn notification(notif: server::Notification) -> Task { match notif.method.as_str() { - notification::Cancel::METHOD => local_notification_task::(notif), notification::DidChange::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notification::DidChangeConfiguration::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notification::DidChangeWatchedFiles::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notification::DidChangeWorkspace::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } - notification::DidClose::METHOD => local_notification_task::(notif), - notification::DidOpen::METHOD => local_notification_task::(notif), + notification::DidClose::METHOD => sync_notification_task::(notif), + notification::DidOpen::METHOD => sync_notification_task::(notif), notification::DidOpenNotebook::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notification::DidChangeNotebook::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) } notification::DidCloseNotebook::METHOD => { - local_notification_task::(notif) + sync_notification_task::(notif) + } + lsp_types::notification::Cancel::METHOD => { + sync_notification_task::(notif) + } + lsp_types::notification::SetTrace::METHOD => { + tracing::trace!("Ignoring `setTrace` notification"); + return Task::nothing(); } method => { tracing::warn!("Received notification {method} which does not have a handler."); @@ -96,71 +136,158 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> { } .unwrap_or_else(|err| { tracing::error!("Encountered error when routing notification: {err}"); - show_err_msg!( - "Ruff failed to handle a notification from the editor. Check the logs for more details." - ); - Task::nothing() + Task::sync(|_session, client| { + client.show_error_message( + "Ruff failed to handle a notification from the editor. Check the logs for more details." + ); + }) }) } -fn local_request_task<'a, R: traits::SyncRequestHandler>( - req: server::Request, -) -> super::Result> { +fn sync_request_task(req: server::Request) -> Result +where + <::RequestType as Request>::Params: UnwindSafe, +{ let (id, params) = cast_request::(req)?; - Ok(Task::local(|session, notifier, requester, responder| { - let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered(); - let result = R::run(session, notifier, requester, params); - respond::(id, result, &responder); + Ok(Task::sync(move |session, client: &Client| { + let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); + let result = R::run(session, client, params); + respond::(&id, result, client); })) } -fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( +fn background_request_task( req: server::Request, schedule: BackgroundSchedule, -) -> super::Result> { +) -> Result +where + <::RequestType as Request>::Params: UnwindSafe, +{ let (id, params) = cast_request::(req)?; + Ok(Task::background(schedule, move |session: &Session| { - // TODO(jane): we should log an error if we can't take a snapshot. + let cancellation_token = session + .request_queue() + .incoming() + .cancellation_token(&id) + .expect("request should have been tested for cancellation before scheduling"); + + let url = R::document_url(¶ms).into_owned(); + let Some(snapshot) = session.take_snapshot(R::document_url(¶ms).into_owned()) else { - return Box::new(|_, _| {}); + tracing::warn!("Ignoring request because snapshot for path `{url:?}` doesn't exist."); + return Box::new(|_| {}); }; - Box::new(move |notifier, responder| { - let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered(); - let result = R::run_with_snapshot(snapshot, notifier, params); - respond::(id, result, &responder); + + Box::new(move |client| { + let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered(); + + // Test again if the request was cancelled since it was scheduled on the background task + // and, if so, return early + if cancellation_token.is_cancelled() { + tracing::trace!( + "Ignoring request id={id} method={} because it was cancelled", + R::METHOD + ); + + // We don't need to send a response here because the `cancel` notification + // handler already responded with a message. + return; + } + + let result = + std::panic::catch_unwind(|| R::run_with_snapshot(snapshot, client, params)); + + let response = request_result_to_response::(result); + respond::(&id, response, client); }) })) } -fn local_notification_task<'a, N: traits::SyncNotificationHandler>( - notif: server::Notification, -) -> super::Result> { +fn request_result_to_response( + result: std::result::Result< + Result<<::RequestType as Request>::Result>, + Box, + >, +) -> Result<<::RequestType as Request>::Result> +where + R: BackgroundDocumentRequestHandler, +{ + match result { + Ok(response) => response, + + Err(error) => { + let message = if let Some(panic_message) = panic_message(&error) { + format!("Request handler failed with: {panic_message}") + } else { + "Request handler failed".into() + }; + + Err(Error { + code: lsp_server::ErrorCode::InternalError, + error: anyhow!(message), + }) + } + } +} + +fn sync_notification_task(notif: server::Notification) -> Result { let (id, params) = cast_notification::(notif)?; - Ok(Task::local(move |session, notifier, requester, _| { - let _span = tracing::trace_span!("notification", method = N::METHOD).entered(); - if let Err(err) = N::run(session, notifier, requester, params) { + Ok(Task::sync(move |session, client| { + let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); + if let Err(err) = N::run(session, client, params) { tracing::error!("An error occurred while running {id}: {err}"); - show_err_msg!("Ruff encountered a problem. Check the logs for more details."); + client + .show_error_message("Ruff encountered a problem. Check the logs for more details."); } })) } #[expect(dead_code)] -fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationHandler>( +fn background_notification_thread( req: server::Notification, schedule: BackgroundSchedule, -) -> super::Result> { +) -> Result +where + N: BackgroundDocumentNotificationHandler, + <::NotificationType as Notification>::Params: UnwindSafe, +{ let (id, params) = cast_notification::(req)?; Ok(Task::background(schedule, move |session: &Session| { - // TODO(jane): we should log an error if we can't take a snapshot. - let Some(snapshot) = session.take_snapshot(N::document_url(¶ms).into_owned()) else { - return Box::new(|_, _| {}); + let url = N::document_url(¶ms); + + let Some(snapshot) = session.take_snapshot((*url).clone()) else { + tracing::debug!( + "Ignoring notification because snapshot for url `{url}` doesn't exist." + ); + return Box::new(|_| {}); }; - Box::new(move |notifier, _| { - let _span = tracing::trace_span!("notification", method = N::METHOD).entered(); - if let Err(err) = N::run_with_snapshot(snapshot, notifier, params) { + Box::new(move |client| { + let _span = tracing::debug_span!("notification", method = N::METHOD).entered(); + + let result = + match std::panic::catch_unwind(|| N::run_with_snapshot(snapshot, client, params)) { + Ok(result) => result, + Err(panic) => { + let message = if let Some(panic_message) = panic_message(&panic) { + format!("notification handler for {id} failed with: {panic_message}") + } else { + format!("notification handler for {id} failed") + }; + + tracing::error!(message); + client.show_error_message( + "Ruff encountered a panic. Check the logs for more details.", + ); + return; + } + }; + + if let Err(err) = result { tracing::error!("An error occurred while running {id}: {err}"); - show_err_msg!("Ruff encountered a problem. Check the logs for more details."); + client.show_error_message( + "Ruff encountered a problem. Check the logs for more details.", + ); } }) })) @@ -172,12 +299,13 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH /// implementation. fn cast_request( request: server::Request, -) -> super::Result<( - server::RequestId, - <::RequestType as lsp_types::request::Request>::Params, +) -> Result<( + RequestId, + <::RequestType as Request>::Params, )> where - Req: traits::RequestHandler, + Req: RequestHandler, + <::RequestType as Request>::Params: UnwindSafe, { request .extract(Req::METHOD) @@ -193,21 +321,27 @@ where .with_failure_code(server::ErrorCode::InternalError) } -/// Sends back a response to the server using a [`Responder`]. +/// Sends back a response to the server, but only if the request wasn't cancelled. fn respond( - id: server::RequestId, - result: crate::server::Result< - <::RequestType as lsp_types::request::Request>::Result, - >, - responder: &Responder, + id: &RequestId, + result: Result<<::RequestType as Request>::Result>, + client: &Client, ) where - Req: traits::RequestHandler, + Req: RequestHandler, { if let Err(err) = &result { tracing::error!("An error occurred with request ID {id}: {err}"); - show_err_msg!("Ruff encountered a problem. Check the logs for more details."); + client.show_error_message("Ruff encountered a problem. Check the logs for more details."); } - if let Err(err) = responder.respond(id, result) { + if let Err(err) = client.respond(id, result) { + tracing::error!("Failed to send response: {err}"); + } +} + +/// Sends back an error response to the server using a [`Client`] without showing a warning +/// to the user. +fn respond_silent_error(id: RequestId, client: &Client, error: lsp_server::ResponseError) { + if let Err(err) = client.respond_err(id, error) { tracing::error!("Failed to send response: {err}"); } } @@ -216,11 +350,13 @@ fn respond( /// a parameter type for a specific request handler. fn cast_notification( notification: server::Notification, -) -> super::Result< - ( - &'static str, - <::NotificationType as lsp_types::notification::Notification>::Params, -)> where N: traits::NotificationHandler{ +) -> Result<( + &'static str, + <::NotificationType as Notification>::Params, +)> +where + N: NotificationHandler, +{ Ok(( N::METHOD, notification @@ -273,3 +409,15 @@ impl std::fmt::Display for Error { self.error.fmt(f) } } + +fn panic_message<'a>( + err: &'a Box, +) -> Option> { + if let Some(s) = err.downcast_ref::() { + Some(s.into()) + } else if let Some(&s) = err.downcast_ref::<&str>() { + Some(s.into()) + } else { + None + } +} diff --git a/crates/ruff_server/src/server/api/diagnostics.rs b/crates/ruff_server/src/server/api/diagnostics.rs index 5f0b9f468d6e36..6f8efe47e83b4e 100644 --- a/crates/ruff_server/src/server/api/diagnostics.rs +++ b/crates/ruff_server/src/server/api/diagnostics.rs @@ -1,7 +1,6 @@ use crate::{ lint::DiagnosticsMap, - server::client::Notifier, - session::{DocumentQuery, DocumentSnapshot}, + session::{Client, DocumentQuery, DocumentSnapshot}, }; use super::LSPResult; @@ -21,11 +20,11 @@ pub(super) fn generate_diagnostics(snapshot: &DocumentSnapshot) -> DiagnosticsMa pub(super) fn publish_diagnostics_for_document( snapshot: &DocumentSnapshot, - notifier: &Notifier, + client: &Client, ) -> crate::server::Result<()> { for (uri, diagnostics) in generate_diagnostics(snapshot) { - notifier - .notify::( + client + .send_notification::( lsp_types::PublishDiagnosticsParams { uri, diagnostics, @@ -40,10 +39,10 @@ pub(super) fn publish_diagnostics_for_document( pub(super) fn clear_diagnostics_for_document( query: &DocumentQuery, - notifier: &Notifier, + client: &Client, ) -> crate::server::Result<()> { - notifier - .notify::( + client + .send_notification::( lsp_types::PublishDiagnosticsParams { uri: query.make_key().into_url(), diagnostics: vec![], diff --git a/crates/ruff_server/src/server/api/notifications.rs b/crates/ruff_server/src/server/api/notifications.rs index ade0c2fbd510f0..d9a473d3fe9939 100644 --- a/crates/ruff_server/src/server/api/notifications.rs +++ b/crates/ruff_server/src/server/api/notifications.rs @@ -10,7 +10,8 @@ mod did_open; mod did_open_notebook; use super::traits::{NotificationHandler, SyncNotificationHandler}; -pub(super) use cancel::Cancel; + +pub(super) use cancel::CancelNotificationHandler; pub(super) use did_change::DidChange; pub(super) use did_change_configuration::DidChangeConfiguration; pub(super) use did_change_notebook::DidChangeNotebook; diff --git a/crates/ruff_server/src/server/api/notifications/cancel.rs b/crates/ruff_server/src/server/api/notifications/cancel.rs index a88fb30d1ad835..85553866783ded 100644 --- a/crates/ruff_server/src/server/api/notifications/cancel.rs +++ b/crates/ruff_server/src/server/api/notifications/cancel.rs @@ -1,23 +1,26 @@ +use lsp_server::RequestId; +use lsp_types::CancelParams; +use lsp_types::notification::Cancel; + use crate::server::Result; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; -use lsp_types as types; -use lsp_types::notification as notif; +use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler}; +use crate::session::{Client, Session}; -pub(crate) struct Cancel; +pub(crate) struct CancelNotificationHandler; -impl super::NotificationHandler for Cancel { - type NotificationType = notif::Cancel; +impl NotificationHandler for CancelNotificationHandler { + type NotificationType = Cancel; } -impl super::SyncNotificationHandler for Cancel { - fn run( - _session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, - _params: types::CancelParams, - ) -> Result<()> { - // TODO(jane): Handle this once we have task cancellation in the scheduler. +impl SyncNotificationHandler for CancelNotificationHandler { + fn run(session: &mut Session, client: &Client, params: CancelParams) -> Result<()> { + let id: RequestId = match params.id { + lsp_types::NumberOrString::Number(id) => id.into(), + lsp_types::NumberOrString::String(id) => id.into(), + }; + + let _ = client.cancel(session, id); + Ok(()) } } diff --git a/crates/ruff_server/src/server/api/notifications/did_change.rs b/crates/ruff_server/src/server/api/notifications/did_change.rs index d9b7d7092d9dc4..8e77cb593fadf5 100644 --- a/crates/ruff_server/src/server/api/notifications/did_change.rs +++ b/crates/ruff_server/src/server/api/notifications/did_change.rs @@ -1,8 +1,7 @@ use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_server::ErrorCode; use lsp_types as types; use lsp_types::notification as notif; @@ -16,8 +15,7 @@ impl super::NotificationHandler for DidChange { impl super::SyncNotificationHandler for DidChange { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, types::DidChangeTextDocumentParams { text_document: types::VersionedTextDocumentIdentifier { @@ -36,7 +34,7 @@ impl super::SyncNotificationHandler for DidChange { // Publish diagnostics if the client doesn't support pull diagnostics if !session.resolved_client_capabilities().pull_diagnostics { let snapshot = session.take_snapshot(key.into_url()).unwrap(); - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; } Ok(()) diff --git a/crates/ruff_server/src/server/api/notifications/did_change_configuration.rs b/crates/ruff_server/src/server/api/notifications/did_change_configuration.rs index ccfd30ddb07ec3..34982ec65659ec 100644 --- a/crates/ruff_server/src/server/api/notifications/did_change_configuration.rs +++ b/crates/ruff_server/src/server/api/notifications/did_change_configuration.rs @@ -1,6 +1,5 @@ use crate::server::Result; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types as types; use lsp_types::notification as notif; @@ -13,8 +12,7 @@ impl super::NotificationHandler for DidChangeConfiguration { impl super::SyncNotificationHandler for DidChangeConfiguration { fn run( _session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, + _client: &Client, _params: types::DidChangeConfigurationParams, ) -> Result<()> { // TODO(jane): get this wired up after the pre-release diff --git a/crates/ruff_server/src/server/api/notifications/did_change_notebook.rs b/crates/ruff_server/src/server/api/notifications/did_change_notebook.rs index d96b4ea97a4ce1..d092ccacb85289 100644 --- a/crates/ruff_server/src/server/api/notifications/did_change_notebook.rs +++ b/crates/ruff_server/src/server/api/notifications/did_change_notebook.rs @@ -1,8 +1,7 @@ use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_server::ErrorCode; use lsp_types as types; use lsp_types::notification as notif; @@ -16,8 +15,7 @@ impl super::NotificationHandler for DidChangeNotebook { impl super::SyncNotificationHandler for DidChangeNotebook { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, types::DidChangeNotebookDocumentParams { notebook_document: types::VersionedNotebookDocumentIdentifier { uri, version }, change: types::NotebookDocumentChangeEvent { cells, metadata }, @@ -32,7 +30,7 @@ impl super::SyncNotificationHandler for DidChangeNotebook { let snapshot = session .take_snapshot(key.into_url()) .expect("snapshot should be available"); - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; Ok(()) } diff --git a/crates/ruff_server/src/server/api/notifications/did_change_watched_files.rs b/crates/ruff_server/src/server/api/notifications/did_change_watched_files.rs index a05ee1f2728e4c..bc97231411dd1d 100644 --- a/crates/ruff_server/src/server/api/notifications/did_change_watched_files.rs +++ b/crates/ruff_server/src/server/api/notifications/did_change_watched_files.rs @@ -1,9 +1,7 @@ use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::server::schedule::Task; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types as types; use lsp_types::notification as notif; @@ -16,16 +14,19 @@ impl super::NotificationHandler for DidChangeWatchedFiles { impl super::SyncNotificationHandler for DidChangeWatchedFiles { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: types::DidChangeWatchedFilesParams, ) -> Result<()> { - session.reload_settings(¶ms.changes); + session.reload_settings(¶ms.changes, client); if !params.changes.is_empty() { if session.resolved_client_capabilities().workspace_refresh { - requester - .request::((), |()| Task::nothing()) + client + .send_request::( + session, + (), + |_, ()| (), + ) .with_failure_code(lsp_server::ErrorCode::InternalError)?; } else { // publish diagnostics for text documents @@ -33,7 +34,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles { let snapshot = session .take_snapshot(url.clone()) .expect("snapshot should be available"); - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; } } @@ -42,7 +43,7 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles { let snapshot = session .take_snapshot(url.clone()) .expect("snapshot should be available"); - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; } } diff --git a/crates/ruff_server/src/server/api/notifications/did_change_workspace.rs b/crates/ruff_server/src/server/api/notifications/did_change_workspace.rs index 2e9dd7cb1d043a..a121d1b2b4e962 100644 --- a/crates/ruff_server/src/server/api/notifications/did_change_workspace.rs +++ b/crates/ruff_server/src/server/api/notifications/did_change_workspace.rs @@ -1,7 +1,6 @@ use crate::server::Result; use crate::server::api::LSPResult; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types as types; use lsp_types::notification as notif; @@ -14,13 +13,12 @@ impl super::NotificationHandler for DidChangeWorkspace { impl super::SyncNotificationHandler for DidChangeWorkspace { fn run( session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, + client: &Client, params: types::DidChangeWorkspaceFoldersParams, ) -> Result<()> { for types::WorkspaceFolder { uri, .. } in params.event.added { session - .open_workspace_folder(uri) + .open_workspace_folder(uri, client) .with_failure_code(lsp_server::ErrorCode::InvalidParams)?; } for types::WorkspaceFolder { uri, .. } in params.event.removed { diff --git a/crates/ruff_server/src/server/api/notifications/did_close.rs b/crates/ruff_server/src/server/api/notifications/did_close.rs index 1448243f2b1353..a3075a4846b87c 100644 --- a/crates/ruff_server/src/server/api/notifications/did_close.rs +++ b/crates/ruff_server/src/server/api/notifications/did_close.rs @@ -1,8 +1,7 @@ use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::clear_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types as types; use lsp_types::notification as notif; @@ -15,8 +14,7 @@ impl super::NotificationHandler for DidClose { impl super::SyncNotificationHandler for DidClose { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, types::DidCloseTextDocumentParams { text_document: types::TextDocumentIdentifier { uri }, }: types::DidCloseTextDocumentParams, @@ -29,7 +27,7 @@ impl super::SyncNotificationHandler for DidClose { ); return Ok(()); }; - clear_diagnostics_for_document(snapshot.query(), ¬ifier)?; + clear_diagnostics_for_document(snapshot.query(), client)?; session .close_document(&key) diff --git a/crates/ruff_server/src/server/api/notifications/did_close_notebook.rs b/crates/ruff_server/src/server/api/notifications/did_close_notebook.rs index e675a4ddbe8758..b70993b5e1225b 100644 --- a/crates/ruff_server/src/server/api/notifications/did_close_notebook.rs +++ b/crates/ruff_server/src/server/api/notifications/did_close_notebook.rs @@ -1,7 +1,6 @@ use crate::server::Result; use crate::server::api::LSPResult; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types::notification as notif; use lsp_types::{self as types, NotebookDocumentIdentifier}; @@ -14,8 +13,7 @@ impl super::NotificationHandler for DidCloseNotebook { impl super::SyncNotificationHandler for DidCloseNotebook { fn run( session: &mut Session, - _notifier: Notifier, - _requester: &mut Requester, + _client: &Client, types::DidCloseNotebookDocumentParams { notebook_document: NotebookDocumentIdentifier { uri }, .. diff --git a/crates/ruff_server/src/server/api/notifications/did_open.rs b/crates/ruff_server/src/server/api/notifications/did_open.rs index 6d8e7b4b110eac..41a6fb6cf8d136 100644 --- a/crates/ruff_server/src/server/api/notifications/did_open.rs +++ b/crates/ruff_server/src/server/api/notifications/did_open.rs @@ -2,8 +2,7 @@ use crate::TextDocument; use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_types as types; use lsp_types::notification as notif; @@ -16,8 +15,7 @@ impl super::NotificationHandler for DidOpen { impl super::SyncNotificationHandler for DidOpen { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, types::DidOpenTextDocumentParams { text_document: types::TextDocumentItem { @@ -40,7 +38,7 @@ impl super::SyncNotificationHandler for DidOpen { anyhow::anyhow!("Unable to take snapshot for document with URL {uri}") }) .with_failure_code(lsp_server::ErrorCode::InternalError)?; - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; } Ok(()) diff --git a/crates/ruff_server/src/server/api/notifications/did_open_notebook.rs b/crates/ruff_server/src/server/api/notifications/did_open_notebook.rs index d8cda99c424998..a75e88ecc5398d 100644 --- a/crates/ruff_server/src/server/api/notifications/did_open_notebook.rs +++ b/crates/ruff_server/src/server/api/notifications/did_open_notebook.rs @@ -2,8 +2,7 @@ use crate::edit::NotebookDocument; use crate::server::Result; use crate::server::api::LSPResult; use crate::server::api::diagnostics::publish_diagnostics_for_document; -use crate::server::client::{Notifier, Requester}; -use crate::session::Session; +use crate::session::{Client, Session}; use lsp_server::ErrorCode; use lsp_types as types; use lsp_types::notification as notif; @@ -17,8 +16,7 @@ impl super::NotificationHandler for DidOpenNotebook { impl super::SyncNotificationHandler for DidOpenNotebook { fn run( session: &mut Session, - notifier: Notifier, - _requester: &mut Requester, + client: &Client, types::DidOpenNotebookDocumentParams { notebook_document: types::NotebookDocument { @@ -45,7 +43,7 @@ impl super::SyncNotificationHandler for DidOpenNotebook { let snapshot = session .take_snapshot(uri) .expect("snapshot should be available"); - publish_diagnostics_for_document(&snapshot, ¬ifier)?; + publish_diagnostics_for_document(&snapshot, client)?; Ok(()) } diff --git a/crates/ruff_server/src/server/api/requests.rs b/crates/ruff_server/src/server/api/requests.rs index 049f396f639f28..198ce4fe61e712 100644 --- a/crates/ruff_server/src/server/api/requests.rs +++ b/crates/ruff_server/src/server/api/requests.rs @@ -5,6 +5,7 @@ mod execute_command; mod format; mod format_range; mod hover; +mod shutdown; use super::{ define_document_url, @@ -17,5 +18,6 @@ pub(super) use execute_command::ExecuteCommand; pub(super) use format::Format; pub(super) use format_range::FormatRange; pub(super) use hover::Hover; +pub(super) use shutdown::ShutdownHandler; type FormatResponse = Option>; diff --git a/crates/ruff_server/src/server/api/requests/code_action.rs b/crates/ruff_server/src/server/api/requests/code_action.rs index 8edd92ba64c9eb..b39543a7730804 100644 --- a/crates/ruff_server/src/server/api/requests/code_action.rs +++ b/crates/ruff_server/src/server/api/requests/code_action.rs @@ -6,10 +6,10 @@ use types::{CodeActionKind, CodeActionOrCommand}; use crate::DIAGNOSTIC_NAME; use crate::edit::WorkspaceEditTracker; use crate::lint::{DiagnosticFix, fixes_for_diagnostics}; +use crate::server::Result; use crate::server::SupportedCodeAction; use crate::server::api::LSPResult; -use crate::server::{Result, client::Notifier}; -use crate::session::DocumentSnapshot; +use crate::session::{Client, DocumentSnapshot}; use super::code_action_resolve::{resolve_edit_for_fix_all, resolve_edit_for_organize_imports}; @@ -23,7 +23,7 @@ impl super::BackgroundDocumentRequestHandler for CodeActions { super::define_document_url!(params: &types::CodeActionParams); fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: types::CodeActionParams, ) -> Result> { let mut response: types::CodeActionResponse = types::CodeActionResponse::default(); diff --git a/crates/ruff_server/src/server/api/requests/code_action_resolve.rs b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs index e77ef8092e6050..ea5691c9d06693 100644 --- a/crates/ruff_server/src/server/api/requests/code_action_resolve.rs +++ b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs @@ -8,9 +8,10 @@ use ruff_linter::codes::Rule; use crate::PositionEncoding; use crate::edit::WorkspaceEditTracker; use crate::fix::Fixes; +use crate::server::Result; use crate::server::SupportedCodeAction; use crate::server::api::LSPResult; -use crate::server::{Result, client::Notifier}; +use crate::session::Client; use crate::session::{DocumentQuery, DocumentSnapshot, ResolvedClientCapabilities}; pub(crate) struct CodeActionResolve; @@ -27,7 +28,7 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve { } fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, mut action: types::CodeAction, ) -> Result { let query = snapshot.query(); diff --git a/crates/ruff_server/src/server/api/requests/diagnostic.rs b/crates/ruff_server/src/server/api/requests/diagnostic.rs index 48d5783b1e0ee6..5315ea931bc05b 100644 --- a/crates/ruff_server/src/server/api/requests/diagnostic.rs +++ b/crates/ruff_server/src/server/api/requests/diagnostic.rs @@ -1,6 +1,6 @@ use crate::server::api::diagnostics::generate_diagnostics; -use crate::server::{Result, client::Notifier}; use crate::session::DocumentSnapshot; +use crate::{server::Result, session::Client}; use lsp_types::{self as types, request as req}; use types::{ DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, @@ -17,7 +17,7 @@ impl super::BackgroundDocumentRequestHandler for DocumentDiagnostic { super::define_document_url!(params: &types::DocumentDiagnosticParams); fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, _params: types::DocumentDiagnosticParams, ) -> Result { Ok(DocumentDiagnosticReportResult::Report( diff --git a/crates/ruff_server/src/server/api/requests/execute_command.rs b/crates/ruff_server/src/server/api/requests/execute_command.rs index 600a65fb78abb2..1303e0ee1451bc 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -2,10 +2,9 @@ use std::fmt::Write; use std::str::FromStr; use crate::edit::WorkspaceEditTracker; +use crate::server::SupportedCommand; use crate::server::api::LSPResult; -use crate::server::schedule::Task; -use crate::server::{SupportedCommand, client}; -use crate::session::Session; +use crate::session::{Client, Session}; use crate::{DIAGNOSTIC_NAME, DocumentKey}; use crate::{edit::DocumentVersion, server}; use lsp_server::ErrorCode; @@ -38,8 +37,7 @@ impl super::RequestHandler for ExecuteCommand { impl super::SyncRequestHandler for ExecuteCommand { fn run( session: &mut Session, - _notifier: client::Notifier, - requester: &mut client::Requester, + client: &Client, params: types::ExecuteCommandParams, ) -> server::Result> { let command = SupportedCommand::from_str(¶ms.command) @@ -76,7 +74,7 @@ impl super::SyncRequestHandler for ExecuteCommand { for Argument { uri, version } in arguments { let Some(snapshot) = session.take_snapshot(uri.clone()) else { tracing::error!("Document at {uri} could not be opened"); - show_err_msg!("Ruff does not recognize this file"); + client.show_error_message("Ruff does not recognize this file"); return Ok(None); }; match command { @@ -114,7 +112,8 @@ impl super::SyncRequestHandler for ExecuteCommand { if !edit_tracker.is_empty() { apply_edit( - requester, + session, + client, command.label(), edit_tracker.into_workspace_edit(), ) @@ -126,24 +125,25 @@ impl super::SyncRequestHandler for ExecuteCommand { } fn apply_edit( - requester: &mut client::Requester, + session: &mut Session, + client: &Client, label: &str, edit: types::WorkspaceEdit, ) -> crate::Result<()> { - requester.request::( + client.send_request::( + session, types::ApplyWorkspaceEditParams { label: Some(format!("{DIAGNOSTIC_NAME}: {label}")), edit, }, - |response| { + move |client, response| { if !response.applied { let reason = response .failure_reason .unwrap_or_else(|| String::from("unspecified reason")); tracing::error!("Failed to apply workspace edit: {reason}"); - show_err_msg!("Ruff was unable to apply edits: {reason}"); + client.show_error_message(format_args!("Ruff was unable to apply edits: {reason}")); } - Task::nothing() }, ) } diff --git a/crates/ruff_server/src/server/api/requests/format.rs b/crates/ruff_server/src/server/api/requests/format.rs index e4fbc86ab1a561..2203d5381d7477 100644 --- a/crates/ruff_server/src/server/api/requests/format.rs +++ b/crates/ruff_server/src/server/api/requests/format.rs @@ -7,9 +7,9 @@ use ruff_source_file::LineIndex; use crate::edit::{Replacement, ToRangeExt}; use crate::fix::Fixes; use crate::resolve::is_document_excluded_for_formatting; +use crate::server::Result; use crate::server::api::LSPResult; -use crate::server::{Result, client::Notifier}; -use crate::session::{DocumentQuery, DocumentSnapshot}; +use crate::session::{Client, DocumentQuery, DocumentSnapshot}; use crate::{PositionEncoding, TextDocument}; pub(crate) struct Format; @@ -22,7 +22,7 @@ impl super::BackgroundDocumentRequestHandler for Format { super::define_document_url!(params: &types::DocumentFormattingParams); fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, _params: types::DocumentFormattingParams, ) -> Result { format_document(&snapshot) diff --git a/crates/ruff_server/src/server/api/requests/format_range.rs b/crates/ruff_server/src/server/api/requests/format_range.rs index 5f18af9737f5d1..e8cb4d56fc76c0 100644 --- a/crates/ruff_server/src/server/api/requests/format_range.rs +++ b/crates/ruff_server/src/server/api/requests/format_range.rs @@ -3,9 +3,9 @@ use lsp_types::{self as types, Range, request as req}; use crate::edit::{RangeExt, ToRangeExt}; use crate::resolve::is_document_excluded_for_formatting; +use crate::server::Result; use crate::server::api::LSPResult; -use crate::server::{Result, client::Notifier}; -use crate::session::{DocumentQuery, DocumentSnapshot}; +use crate::session::{Client, DocumentQuery, DocumentSnapshot}; use crate::{PositionEncoding, TextDocument}; pub(crate) struct FormatRange; @@ -18,7 +18,7 @@ impl super::BackgroundDocumentRequestHandler for FormatRange { super::define_document_url!(params: &types::DocumentRangeFormattingParams); fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: types::DocumentRangeFormattingParams, ) -> Result { format_document_range(&snapshot, params.range) diff --git a/crates/ruff_server/src/server/api/requests/hover.rs b/crates/ruff_server/src/server/api/requests/hover.rs index 846f3654c5cc5b..266234e868ed3f 100644 --- a/crates/ruff_server/src/server/api/requests/hover.rs +++ b/crates/ruff_server/src/server/api/requests/hover.rs @@ -1,5 +1,5 @@ -use crate::server::{Result, client::Notifier}; -use crate::session::DocumentSnapshot; +use crate::server::Result; +use crate::session::{Client, DocumentSnapshot}; use anyhow::Context; use lsp_types::{self as types, request as req}; use regex::Regex; @@ -20,7 +20,7 @@ impl super::BackgroundDocumentRequestHandler for Hover { } fn run_with_snapshot( snapshot: DocumentSnapshot, - _notifier: Notifier, + _client: &Client, params: types::HoverParams, ) -> Result> { Ok(hover(&snapshot, ¶ms.text_document_position_params)) diff --git a/crates/ruff_server/src/server/api/requests/shutdown.rs b/crates/ruff_server/src/server/api/requests/shutdown.rs new file mode 100644 index 00000000000000..5ec89c79329cd3 --- /dev/null +++ b/crates/ruff_server/src/server/api/requests/shutdown.rs @@ -0,0 +1,17 @@ +use crate::Session; +use crate::server::api::traits::{RequestHandler, SyncRequestHandler}; +use crate::session::Client; + +pub(crate) struct ShutdownHandler; + +impl RequestHandler for ShutdownHandler { + type RequestType = lsp_types::request::Shutdown; +} + +impl SyncRequestHandler for ShutdownHandler { + fn run(session: &mut Session, _client: &Client, _params: ()) -> crate::server::Result<()> { + tracing::debug!("Received shutdown request, waiting for shutdown notification"); + session.set_shutdown_requested(true); + Ok(()) + } +} diff --git a/crates/ruff_server/src/server/api/traits.rs b/crates/ruff_server/src/server/api/traits.rs index 3c8e56b7a2b064..8d70c5ff673102 100644 --- a/crates/ruff_server/src/server/api/traits.rs +++ b/crates/ruff_server/src/server/api/traits.rs @@ -1,7 +1,6 @@ //! A stateful LSP implementation that calls into the Ruff API. -use crate::server::client::{Notifier, Requester}; -use crate::session::{DocumentSnapshot, Session}; +use crate::session::{Client, DocumentSnapshot, Session}; use lsp_types::notification::Notification as LSPNotification; use lsp_types::request::Request; @@ -19,8 +18,7 @@ pub(super) trait RequestHandler { pub(super) trait SyncRequestHandler: RequestHandler { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: <::RequestType as Request>::Params, ) -> super::Result<<::RequestType as Request>::Result>; } @@ -36,7 +34,7 @@ pub(super) trait BackgroundDocumentRequestHandler: RequestHandler { fn run_with_snapshot( snapshot: DocumentSnapshot, - notifier: Notifier, + client: &Client, params: <::RequestType as Request>::Params, ) -> super::Result<<::RequestType as Request>::Result>; } @@ -55,8 +53,7 @@ pub(super) trait NotificationHandler { pub(super) trait SyncNotificationHandler: NotificationHandler { fn run( session: &mut Session, - notifier: Notifier, - requester: &mut Requester, + client: &Client, params: <::NotificationType as LSPNotification>::Params, ) -> super::Result<()>; } @@ -72,7 +69,7 @@ pub(super) trait BackgroundDocumentNotificationHandler: NotificationHandler { fn run_with_snapshot( snapshot: DocumentSnapshot, - notifier: Notifier, + client: &Client, params: <::NotificationType as LSPNotification>::Params, ) -> super::Result<()>; } diff --git a/crates/ruff_server/src/server/client.rs b/crates/ruff_server/src/server/client.rs deleted file mode 100644 index e136bc98d44b87..00000000000000 --- a/crates/ruff_server/src/server/client.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::any::TypeId; - -use lsp_server::{Notification, RequestId}; -use rustc_hash::FxHashMap; -use serde_json::Value; - -use super::{ClientSender, schedule::Task}; - -type ResponseBuilder<'s> = Box Task<'s>>; - -pub(crate) struct Client<'s> { - notifier: Notifier, - responder: Responder, - pub(super) requester: Requester<'s>, -} - -#[derive(Clone)] -pub(crate) struct Notifier(ClientSender); - -#[derive(Clone)] -pub(crate) struct Responder(ClientSender); - -pub(crate) struct Requester<'s> { - sender: ClientSender, - next_request_id: i32, - response_handlers: FxHashMap>, -} - -impl Client<'_> { - pub(super) fn new(sender: ClientSender) -> Self { - Self { - notifier: Notifier(sender.clone()), - responder: Responder(sender.clone()), - requester: Requester { - sender, - next_request_id: 1, - response_handlers: FxHashMap::default(), - }, - } - } - - pub(super) fn notifier(&self) -> Notifier { - self.notifier.clone() - } - - pub(super) fn responder(&self) -> Responder { - self.responder.clone() - } -} - -#[expect(dead_code)] // we'll need to use `Notifier` in the future -impl Notifier { - pub(crate) fn notify(&self, params: N::Params) -> crate::Result<()> - where - N: lsp_types::notification::Notification, - { - let method = N::METHOD.to_string(); - - let message = lsp_server::Message::Notification(Notification::new(method, params)); - - self.0.send(message) - } - - pub(crate) fn notify_method(&self, method: String) -> crate::Result<()> { - self.0 - .send(lsp_server::Message::Notification(Notification::new( - method, - Value::Null, - ))) - } -} - -impl Responder { - pub(crate) fn respond( - &self, - id: RequestId, - result: crate::server::Result, - ) -> crate::Result<()> - where - R: serde::Serialize, - { - self.0.send( - match result { - Ok(res) => lsp_server::Response::new_ok(id, res), - Err(crate::server::api::Error { code, error }) => { - lsp_server::Response::new_err(id, code as i32, format!("{error}")) - } - } - .into(), - ) - } -} - -impl<'s> Requester<'s> { - /// Sends a request of kind `R` to the client, with associated parameters. - /// The task provided by `response_handler` will be dispatched as soon as the response - /// comes back from the client. - pub(crate) fn request( - &mut self, - params: R::Params, - response_handler: impl Fn(R::Result) -> Task<'s> + 'static, - ) -> crate::Result<()> - where - R: lsp_types::request::Request, - { - let serialized_params = serde_json::to_value(params)?; - - self.response_handlers.insert( - self.next_request_id.into(), - Box::new(move |response: lsp_server::Response| { - match (response.error, response.result) { - (Some(err), _) => { - tracing::error!( - "Got an error from the client (code {}): {}", - err.code, - err.message - ); - Task::nothing() - } - (None, Some(response)) => match serde_json::from_value(response) { - Ok(response) => response_handler(response), - Err(error) => { - tracing::error!("Failed to deserialize response from server: {error}"); - Task::nothing() - } - }, - (None, None) => { - if TypeId::of::() == TypeId::of::<()>() { - // We can't call `response_handler(())` directly here, but - // since we _know_ the type expected is `()`, we can use - // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, - // so this branch works in the general case but we'll only - // hit it if the concrete type is `()`, so the `unwrap()` is safe here. - response_handler(serde_json::from_value(Value::Null).unwrap()); - } else { - tracing::error!( - "Server response was invalid: did not contain a result or error" - ); - } - Task::nothing() - } - } - }), - ); - - self.sender - .send(lsp_server::Message::Request(lsp_server::Request { - id: self.next_request_id.into(), - method: R::METHOD.into(), - params: serialized_params, - }))?; - - self.next_request_id += 1; - - Ok(()) - } - - pub(crate) fn pop_response_task(&mut self, response: lsp_server::Response) -> Task<'s> { - if let Some(handler) = self.response_handlers.remove(&response.id) { - handler(response) - } else { - tracing::error!( - "Received a response with ID {}, which was not expected", - response.id - ); - Task::nothing() - } - } -} diff --git a/crates/ruff_server/src/server/connection.rs b/crates/ruff_server/src/server/connection.rs index 029a34c931f59f..4993d2ba6cd6b0 100644 --- a/crates/ruff_server/src/server/connection.rs +++ b/crates/ruff_server/src/server/connection.rs @@ -1,31 +1,17 @@ use lsp_server as lsp; -use lsp_types::{notification::Notification, request::Request}; -use std::sync::{Arc, Weak}; -type ConnectionSender = crossbeam::channel::Sender; -type ConnectionReceiver = crossbeam::channel::Receiver; +pub type ConnectionSender = crossbeam::channel::Sender; /// A builder for `Connection` that handles LSP initialization. pub(crate) struct ConnectionInitializer { connection: lsp::Connection, - threads: lsp::IoThreads, -} - -/// Handles inbound and outbound messages with the client. -pub(crate) struct Connection { - sender: Arc, - receiver: ConnectionReceiver, - threads: lsp::IoThreads, } impl ConnectionInitializer { /// Create a new LSP server connection over stdin/stdout. - pub(super) fn stdio() -> Self { + pub(crate) fn stdio() -> (Self, lsp::IoThreads) { let (connection, threads) = lsp::Connection::stdio(); - Self { - connection, - threads, - } + (Self { connection }, threads) } /// Starts the initialization process with the client by listening for an initialization request. @@ -46,7 +32,7 @@ impl ConnectionInitializer { server_capabilities: &lsp_types::ServerCapabilities, name: &str, version: &str, - ) -> crate::Result { + ) -> crate::Result { self.connection.initialize_finish( id, serde_json::json!({ @@ -57,111 +43,6 @@ impl ConnectionInitializer { } }), )?; - let Self { - connection: lsp::Connection { sender, receiver }, - threads, - } = self; - Ok(Connection { - sender: Arc::new(sender), - receiver, - threads, - }) - } -} - -impl Connection { - /// Make a new `ClientSender` for sending messages to the client. - pub(super) fn make_sender(&self) -> ClientSender { - ClientSender { - weak_sender: Arc::downgrade(&self.sender), - } - } - - /// An iterator over incoming messages from the client. - pub(super) fn incoming(&self) -> crossbeam::channel::Iter { - self.receiver.iter() - } - - /// Check and respond to any incoming shutdown requests; returns`true` if the server should be shutdown. - pub(super) fn handle_shutdown(&self, message: &lsp::Message) -> crate::Result { - match message { - lsp::Message::Request(lsp::Request { id, method, .. }) - if method == lsp_types::request::Shutdown::METHOD => - { - self.sender - .send(lsp::Response::new_ok(id.clone(), ()).into())?; - tracing::info!("Shutdown request received. Waiting for an exit notification..."); - - loop { - match &self - .receiver - .recv_timeout(std::time::Duration::from_secs(30))? - { - lsp::Message::Notification(lsp::Notification { method, .. }) - if method == lsp_types::notification::Exit::METHOD => - { - tracing::info!("Exit notification received. Server shutting down..."); - return Ok(true); - } - lsp::Message::Request(lsp::Request { id, method, .. }) => { - tracing::warn!( - "Server received unexpected request {method} ({id}) while waiting for exit notification", - ); - self.sender.send(lsp::Message::Response(lsp::Response::new_err( - id.clone(), - lsp::ErrorCode::InvalidRequest as i32, - "Server received unexpected request while waiting for exit notification".to_string(), - )))?; - } - message => { - tracing::warn!( - "Server received unexpected message while waiting for exit notification: {message:?}" - ); - } - } - } - } - lsp::Message::Notification(lsp::Notification { method, .. }) - if method == lsp_types::notification::Exit::METHOD => - { - anyhow::bail!( - "Server received an exit notification before a shutdown request was sent. Exiting..." - ); - } - _ => Ok(false), - } - } - - /// Join the I/O threads that underpin this connection. - /// This is guaranteed to be nearly immediate since - /// we close the only active channels to these threads prior - /// to joining them. - pub(super) fn close(self) -> crate::Result<()> { - std::mem::drop( - Arc::into_inner(self.sender) - .expect("the client sender shouldn't have more than one strong reference"), - ); - std::mem::drop(self.receiver); - self.threads.join()?; - Ok(()) - } -} - -/// A weak reference to an underlying sender channel, used for communication with the client. -/// If the `Connection` that created this `ClientSender` is dropped, any `send` calls will throw -/// an error. -#[derive(Clone, Debug)] -pub(crate) struct ClientSender { - weak_sender: Weak, -} - -// note: additional wrapper functions for senders may be implemented as needed. -impl ClientSender { - pub(crate) fn send(&self, msg: lsp::Message) -> crate::Result<()> { - let Some(sender) = self.weak_sender.upgrade() else { - anyhow::bail!("The connection with the client has been closed"); - }; - - Ok(sender.send(msg)?) + Ok(self.connection) } } diff --git a/crates/ruff_server/src/server/main_loop.rs b/crates/ruff_server/src/server/main_loop.rs new file mode 100644 index 00000000000000..b5943ad3dbd944 --- /dev/null +++ b/crates/ruff_server/src/server/main_loop.rs @@ -0,0 +1,209 @@ +use anyhow::anyhow; +use crossbeam::select; +use lsp_server::Message; +use lsp_types::{ + self as types, DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher, + notification::Notification as _, +}; + +use crate::{ + Server, + server::{api, schedule}, + session::Client, +}; + +pub type MainLoopSender = crossbeam::channel::Sender; +pub(crate) type MainLoopReceiver = crossbeam::channel::Receiver; + +impl Server { + pub(super) fn main_loop(&mut self) -> crate::Result<()> { + self.initialize(&Client::new( + self.main_loop_sender.clone(), + self.connection.sender.clone(), + )); + + let mut scheduler = schedule::Scheduler::new(self.worker_threads); + + while let Ok(next_event) = self.next_event() { + let Some(next_event) = next_event else { + anyhow::bail!("client exited without proper shutdown sequence"); + }; + + match next_event { + Event::Message(msg) => { + let client = Client::new( + self.main_loop_sender.clone(), + self.connection.sender.clone(), + ); + + let task = match msg { + Message::Request(req) => { + self.session + .request_queue_mut() + .incoming_mut() + .register(req.id.clone(), req.method.clone()); + + if self.session.is_shutdown_requested() { + tracing::warn!( + "Received request after server shutdown was requested, discarding" + ); + client.respond_err( + req.id, + lsp_server::ResponseError { + code: lsp_server::ErrorCode::InvalidRequest as i32, + message: "Shutdown already requested".to_owned(), + data: None, + }, + )?; + continue; + } + + api::request(req) + } + Message::Notification(notification) => { + if notification.method == lsp_types::notification::Exit::METHOD { + if !self.session.is_shutdown_requested() { + return Err(anyhow!( + "Received exit notification before a shutdown request" + )); + } + + tracing::debug!("Received exit notification, exiting"); + return Ok(()); + } + + api::notification(notification) + } + + // Handle the response from the client to a server request + Message::Response(response) => { + if let Some(handler) = self + .session + .request_queue_mut() + .outgoing_mut() + .complete(&response.id) + { + handler(&client, response); + } else { + tracing::error!( + "Received a response with ID {}, which was not expected", + response.id + ); + } + + continue; + } + }; + + scheduler.dispatch(task, &mut self.session, client); + } + Event::SendResponse(response) => { + // Filter out responses for already canceled requests. + if let Some((start_time, method)) = self + .session + .request_queue_mut() + .incoming_mut() + .complete(&response.id) + { + let duration = start_time.elapsed(); + tracing::trace!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration)); + + self.connection.sender.send(Message::Response(response))?; + } else { + tracing::trace!( + "Ignoring response for canceled request id={}", + response.id + ); + } + } + } + } + + Ok(()) + } + + /// Waits for the next message from the client or action. + /// + /// Returns `Ok(None)` if the client connection is closed. + fn next_event(&self) -> Result, crossbeam::channel::RecvError> { + select!( + recv(self.connection.receiver) -> msg => { + // Ignore disconnect errors, they're handled by the main loop (it will exit). + Ok(msg.ok().map(Event::Message)) + }, + recv(self.main_loop_receiver) -> event => event.map(Some), + ) + } + + fn initialize(&mut self, client: &Client) { + let dynamic_registration = self + .client_capabilities + .workspace + .as_ref() + .and_then(|workspace| workspace.did_change_watched_files) + .and_then(|watched_files| watched_files.dynamic_registration) + .unwrap_or_default(); + if dynamic_registration { + // Register all dynamic capabilities here + + // `workspace/didChangeWatchedFiles` + // (this registers the configuration file watcher) + let params = lsp_types::RegistrationParams { + registrations: vec![lsp_types::Registration { + id: "ruff-server-watch".into(), + method: "workspace/didChangeWatchedFiles".into(), + register_options: Some( + serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { + watchers: vec![ + FileSystemWatcher { + glob_pattern: types::GlobPattern::String( + "**/.ruff.toml".into(), + ), + kind: None, + }, + FileSystemWatcher { + glob_pattern: types::GlobPattern::String("**/ruff.toml".into()), + kind: None, + }, + FileSystemWatcher { + glob_pattern: types::GlobPattern::String( + "**/pyproject.toml".into(), + ), + kind: None, + }, + ], + }) + .unwrap(), + ), + }], + }; + + let response_handler = |_: &Client, ()| { + tracing::info!("Configuration file watcher successfully registered"); + }; + + if let Err(err) = client.send_request::( + &self.session, + params, + response_handler, + ) { + tracing::error!( + "An error occurred when trying to register the configuration file watcher: {err}" + ); + } + } else { + tracing::warn!( + "LSP client does not support dynamic capability registration - automatic configuration reloading will not be available." + ); + } + } +} + +#[derive(Debug)] +pub enum Event { + /// An incoming message from the LSP client. + Message(lsp_server::Message), + + /// Send a response to the client + SendResponse(lsp_server::Response), +} diff --git a/crates/ruff_server/src/server/schedule.rs b/crates/ruff_server/src/server/schedule.rs index ec6204dd6225ee..e6ca9bd4386281 100644 --- a/crates/ruff_server/src/server/schedule.rs +++ b/crates/ruff_server/src/server/schedule.rs @@ -1,6 +1,6 @@ use std::num::NonZeroUsize; -use crate::session::Session; +use crate::session::{Client, Session}; mod task; mod thread; @@ -12,13 +12,11 @@ use self::{ thread::ThreadPriority, }; -use super::{ClientSender, client::Client}; - /// The event loop thread is actually a secondary thread that we spawn from the /// _actual_ main thread. This secondary thread has a larger stack size /// than some OS defaults (Windows, for example) and is also designated as /// high-priority. -pub(crate) fn event_loop_thread( +pub(crate) fn spawn_main_loop( func: impl FnOnce() -> crate::Result<()> + Send + 'static, ) -> crate::Result>> { // Override OS defaults to avoid stack overflows on platforms with low stack size defaults. @@ -32,69 +30,33 @@ pub(crate) fn event_loop_thread( ) } -pub(crate) struct Scheduler<'s> { - session: &'s mut Session, - client: Client<'s>, +pub(crate) struct Scheduler { fmt_pool: thread::Pool, background_pool: thread::Pool, } -impl<'s> Scheduler<'s> { - pub(super) fn new( - session: &'s mut Session, - worker_threads: NonZeroUsize, - sender: ClientSender, - ) -> Self { +impl Scheduler { + pub(super) fn new(worker_threads: NonZeroUsize) -> Self { const FMT_THREADS: usize = 1; Self { - session, fmt_pool: thread::Pool::new(NonZeroUsize::try_from(FMT_THREADS).unwrap()), background_pool: thread::Pool::new(worker_threads), - client: Client::new(sender), } } - /// Immediately sends a request of kind `R` to the client, with associated parameters. - /// The task provided by `response_handler` will be dispatched as soon as the response - /// comes back from the client. - pub(super) fn request( - &mut self, - params: R::Params, - response_handler: impl Fn(R::Result) -> Task<'s> + 'static, - ) -> crate::Result<()> - where - R: lsp_types::request::Request, - { - self.client.requester.request::(params, response_handler) - } - - /// Creates a task to handle a response from the client. - pub(super) fn response(&mut self, response: lsp_server::Response) -> Task<'s> { - self.client.requester.pop_response_task(response) - } - /// Dispatches a `task` by either running it as a blocking function or /// executing it on a background thread pool. - pub(super) fn dispatch(&mut self, task: task::Task<'s>) { + pub(super) fn dispatch(&mut self, task: Task, session: &mut Session, client: Client) { match task { Task::Sync(SyncTask { func }) => { - let notifier = self.client.notifier(); - let responder = self.client.responder(); - func( - self.session, - notifier, - &mut self.client.requester, - responder, - ); + func(session, &client); } Task::Background(BackgroundTaskBuilder { schedule, builder: func, }) => { - let static_func = func(self.session); - let notifier = self.client.notifier(); - let responder = self.client.responder(); - let task = move || static_func(notifier, responder); + let static_func = func(session); + let task = move || static_func(&client); match schedule { BackgroundSchedule::Worker => { self.background_pool.spawn(ThreadPriority::Worker, task); diff --git a/crates/ruff_server/src/server/schedule/task.rs b/crates/ruff_server/src/server/schedule/task.rs index e34b22ed6919f0..2e768c2b18fb6e 100644 --- a/crates/ruff_server/src/server/schedule/task.rs +++ b/crates/ruff_server/src/server/schedule/task.rs @@ -1,16 +1,13 @@ use lsp_server::RequestId; use serde::Serialize; -use crate::{ - server::client::{Notifier, Requester, Responder}, - session::Session, -}; +use crate::session::{Client, Session}; -type LocalFn<'s> = Box; +type LocalFn = Box; -type BackgroundFn = Box; +type BackgroundFn = Box; -type BackgroundFnBuilder<'s> = Box BackgroundFn + 's>; +type BackgroundFnBuilder = Box BackgroundFn>; /// Describes how the task should be run. #[derive(Clone, Copy, Debug, Default)] @@ -36,9 +33,9 @@ pub(in crate::server) enum BackgroundSchedule { /// while local tasks have exclusive access and can modify it as they please. Keep in mind that /// local tasks will **block** the main event loop, so only use local tasks if you **need** /// mutable state access or you need the absolute lowest latency possible. -pub(in crate::server) enum Task<'s> { - Background(BackgroundTaskBuilder<'s>), - Sync(SyncTask<'s>), +pub(in crate::server) enum Task { + Background(BackgroundTaskBuilder), + Sync(SyncTask), } // The reason why this isn't just a 'static background closure @@ -49,20 +46,20 @@ pub(in crate::server) enum Task<'s> { // that the inner closure can capture. This builder closure has a lifetime linked to the scheduler. // When the task is dispatched, the scheduler runs the synchronous builder, which takes the session // as a reference, to create the inner 'static closure. That closure is then moved to a background task pool. -pub(in crate::server) struct BackgroundTaskBuilder<'s> { +pub(in crate::server) struct BackgroundTaskBuilder { pub(super) schedule: BackgroundSchedule, - pub(super) builder: BackgroundFnBuilder<'s>, + pub(super) builder: BackgroundFnBuilder, } -pub(in crate::server) struct SyncTask<'s> { - pub(super) func: LocalFn<'s>, +pub(in crate::server) struct SyncTask { + pub(super) func: LocalFn, } -impl<'s> Task<'s> { +impl Task { /// Creates a new background task. pub(crate) fn background( schedule: BackgroundSchedule, - func: impl FnOnce(&Session) -> Box + 's, + func: impl FnOnce(&Session) -> Box + 'static, ) -> Self { Self::Background(BackgroundTaskBuilder { schedule, @@ -70,9 +67,7 @@ impl<'s> Task<'s> { }) } /// Creates a new local task. - pub(crate) fn local( - func: impl FnOnce(&mut Session, Notifier, &mut Requester, Responder) + 's, - ) -> Self { + pub(crate) fn sync(func: impl FnOnce(&mut Session, &Client) + 'static) -> Self { Self::Sync(SyncTask { func: Box::new(func), }) @@ -83,8 +78,8 @@ impl<'s> Task<'s> { where R: Serialize + Send + 'static, { - Self::local(move |_, _, _, responder| { - if let Err(err) = responder.respond(id, result) { + Self::sync(move |_, client| { + if let Err(err) = client.respond(&id, result) { tracing::error!("Unable to send immediate response: {err}"); } }) @@ -92,6 +87,6 @@ impl<'s> Task<'s> { /// Creates a local task that does nothing. pub(crate) fn nothing() -> Self { - Self::local(move |_, _, _, _| {}) + Self::sync(move |_, _| {}) } } diff --git a/crates/ruff_server/src/server/schedule/thread/pool.rs b/crates/ruff_server/src/server/schedule/thread/pool.rs index 98728bba8fa13c..ac3e072ab879eb 100644 --- a/crates/ruff_server/src/server/schedule/thread/pool.rs +++ b/crates/ruff_server/src/server/schedule/thread/pool.rs @@ -15,6 +15,7 @@ use std::{ num::NonZeroUsize, + panic::AssertUnwindSafe, sync::{ Arc, atomic::{AtomicUsize, Ordering}, @@ -71,7 +72,26 @@ impl Pool { current_priority = job.requested_priority; } extant_tasks.fetch_add(1, Ordering::SeqCst); - (job.f)(); + + // SAFETY: it's safe to assume that `job.f` is unwind safe because we always + // abort the process if it panics. + // Panicking here ensures that we don't swallow errors and is the same as + // what rayon does. + // Any recovery should be implemented outside the thread pool (e.g. when + // dispatching requests/notifications etc). + if let Err(error) = std::panic::catch_unwind(AssertUnwindSafe(job.f)) { + if let Some(msg) = error.downcast_ref::() { + tracing::error!("Worker thread panicked with: {msg}; aborting"); + } else if let Some(msg) = error.downcast_ref::<&str>() { + tracing::error!("Worker thread panicked with: {msg}; aborting"); + } else { + tracing::error!( + "Worker thread panicked with: {error:?}; aborting" + ); + } + + std::process::abort(); + } extant_tasks.fetch_sub(1, Ordering::SeqCst); } } diff --git a/crates/ruff_server/src/session.rs b/crates/ruff_server/src/session.rs index 79cc059b4c8d91..c87ba6d4eec62c 100644 --- a/crates/ruff_server/src/session.rs +++ b/crates/ruff_server/src/session.rs @@ -7,6 +7,7 @@ use lsp_types::{ClientCapabilities, FileEvent, NotebookDocumentCellChange, Url}; use settings::ClientSettings; use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument}; +use crate::session::request_queue::RequestQueue; use crate::session::settings::GlobalClientSettings; use crate::workspace::Workspaces; use crate::{PositionEncoding, TextDocument}; @@ -15,10 +16,13 @@ pub(crate) use self::capabilities::ResolvedClientCapabilities; pub use self::index::DocumentQuery; pub(crate) use self::options::{AllOptions, WorkspaceOptionsMap}; pub use self::options::{ClientOptions, GlobalOptions}; +pub use client::Client; mod capabilities; +mod client; mod index; mod options; +mod request_queue; mod settings; /// The global state for the LSP @@ -32,6 +36,12 @@ pub struct Session { /// Tracks what LSP features the client supports and doesn't support. resolved_client_capabilities: Arc, + + /// Tracks the pending requests between client and server. + request_queue: RequestQueue, + + /// Has the client requested the server to shutdown. + shutdown_requested: bool, } /// An immutable snapshot of `Session` that references @@ -49,17 +59,36 @@ impl Session { position_encoding: PositionEncoding, global: GlobalClientSettings, workspaces: &Workspaces, + client: &Client, ) -> crate::Result { Ok(Self { position_encoding, - index: index::Index::new(workspaces, &global)?, + index: index::Index::new(workspaces, &global, client)?, global_settings: global, resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new( client_capabilities, )), + request_queue: RequestQueue::new(), + shutdown_requested: false, }) } + pub(crate) fn request_queue(&self) -> &RequestQueue { + &self.request_queue + } + + pub(crate) fn request_queue_mut(&mut self) -> &mut RequestQueue { + &mut self.request_queue + } + + pub(crate) fn is_shutdown_requested(&self) -> bool { + self.shutdown_requested + } + + pub(crate) fn set_shutdown_requested(&mut self, requested: bool) { + self.shutdown_requested = requested; + } + pub fn key_from_url(&self, url: Url) -> DocumentKey { self.index.key_from_url(url) } @@ -140,13 +169,14 @@ impl Session { } /// Reloads the settings index based on the provided changes. - pub(crate) fn reload_settings(&mut self, changes: &[FileEvent]) { - self.index.reload_settings(changes); + pub(crate) fn reload_settings(&mut self, changes: &[FileEvent], client: &Client) { + self.index.reload_settings(changes, client); } /// Open a workspace folder at the given `url`. - pub(crate) fn open_workspace_folder(&mut self, url: Url) -> crate::Result<()> { - self.index.open_workspace_folder(url, &self.global_settings) + pub(crate) fn open_workspace_folder(&mut self, url: Url, client: &Client) -> crate::Result<()> { + self.index + .open_workspace_folder(url, &self.global_settings, client) } /// Close a workspace folder at the given `url`. diff --git a/crates/ruff_server/src/session/client.rs b/crates/ruff_server/src/session/client.rs new file mode 100644 index 00000000000000..0cbcd449d5a223 --- /dev/null +++ b/crates/ruff_server/src/session/client.rs @@ -0,0 +1,248 @@ +use crate::Session; +use crate::server::{ConnectionSender, Event, MainLoopSender}; +use anyhow::{Context, anyhow}; +use lsp_server::{ErrorCode, Message, Notification, RequestId, ResponseError}; +use serde_json::Value; +use std::any::TypeId; +use std::fmt::Display; + +pub(crate) type ClientResponseHandler = Box; + +#[derive(Clone, Debug)] +pub struct Client { + /// Channel to send messages back to the main loop. + main_loop_sender: MainLoopSender, + /// Channel to send messages directly to the LSP client without going through the main loop. + /// + /// This is generally preferred because it reduces pressure on the main loop but it may not always be + /// possible if access to data on [`Session`] is required, which background tasks don't have. + client_sender: ConnectionSender, +} + +impl Client { + pub fn new(main_loop_sender: MainLoopSender, client_sender: ConnectionSender) -> Self { + Self { + main_loop_sender, + client_sender, + } + } + + /// Sends a request of kind `R` to the client, with associated parameters. + /// + /// The request is sent immediately. + /// The `response_handler` will be dispatched as soon as the client response + /// is processed on the main-loop. The handler always runs on the main-loop thread. + /// + /// # Note + /// This method takes a `session` so that we can register the pending-request + /// and send the response directly to the client. If this ever becomes too limiting (because we + /// need to send a request from somewhere where we don't have access to session), consider introducing + /// a new `send_deferred_request` method that doesn't take a session and instead sends + /// an `Action` to the main loop to send the request (the main loop has always access to session). + pub(crate) fn send_request( + &self, + session: &Session, + params: R::Params, + response_handler: impl FnOnce(&Client, R::Result) + Send + 'static, + ) -> crate::Result<()> + where + R: lsp_types::request::Request, + { + let response_handler = Box::new(move |client: &Client, response: lsp_server::Response| { + let _span = + tracing::debug_span!("client_response", id=%response.id, method = R::METHOD) + .entered(); + + match (response.error, response.result) { + (Some(err), _) => { + tracing::error!( + "Got an error from the client (code {code}, method {method}): {message}", + code = err.code, + message = err.message, + method = R::METHOD + ); + } + (None, Some(response)) => match serde_json::from_value(response) { + Ok(response) => response_handler(client, response), + Err(error) => { + tracing::error!( + "Failed to deserialize client response (method={method}): {error}", + method = R::METHOD + ); + } + }, + (None, None) => { + if TypeId::of::() == TypeId::of::<()>() { + // We can't call `response_handler(())` directly here, but + // since we _know_ the type expected is `()`, we can use + // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, + // so this branch works in the general case but we'll only + // hit it if the concrete type is `()`, so the `unwrap()` is safe here. + response_handler(client, serde_json::from_value(Value::Null).unwrap()); + } else { + tracing::error!( + "Invalid client response: did not contain a result or error (method={method})", + method = R::METHOD + ); + } + } + } + }); + + let id = session + .request_queue() + .outgoing() + .register(response_handler); + + self.client_sender + .send(Message::Request(lsp_server::Request { + id, + method: R::METHOD.to_string(), + params: serde_json::to_value(params).context("Failed to serialize params")?, + })) + .with_context(|| { + format!("Failed to send request method={method}", method = R::METHOD) + })?; + + Ok(()) + } + + /// Sends a notification to the client. + pub(crate) fn send_notification(&self, params: N::Params) -> crate::Result<()> + where + N: lsp_types::notification::Notification, + { + let method = N::METHOD.to_string(); + + self.client_sender + .send(lsp_server::Message::Notification(Notification::new( + method, params, + ))) + .map_err(|error| { + anyhow!( + "Failed to send notification (method={method}): {error}", + method = N::METHOD + ) + }) + } + + /// Sends a notification without any parameters to the client. + /// + /// This is useful for notifications that don't require any data. + #[expect(dead_code)] + pub(crate) fn send_notification_no_params(&self, method: &str) -> crate::Result<()> { + self.client_sender + .send(lsp_server::Message::Notification(Notification::new( + method.to_string(), + Value::Null, + ))) + .map_err(|error| anyhow!("Failed to send notification (method={method}): {error}",)) + } + + /// Sends a response to the client for a given request ID. + /// + /// The response isn't sent immediately. Instead, it's queued up in the main loop + /// and checked for cancellation (each request must have exactly one response). + pub(crate) fn respond( + &self, + id: &RequestId, + result: crate::server::Result, + ) -> crate::Result<()> + where + R: serde::Serialize, + { + let response = match result { + Ok(res) => lsp_server::Response::new_ok(id.clone(), res), + Err(crate::server::Error { code, error }) => { + lsp_server::Response::new_err(id.clone(), code as i32, error.to_string()) + } + }; + + self.main_loop_sender + .send(Event::SendResponse(response)) + .map_err(|error| anyhow!("Failed to send response for request {id}: {error}")) + } + + /// Sends an error response to the client for a given request ID. + /// + /// The response isn't sent immediately. Instead, it's queued up in the main loop. + pub(crate) fn respond_err( + &self, + id: RequestId, + error: lsp_server::ResponseError, + ) -> crate::Result<()> { + let response = lsp_server::Response { + id, + result: None, + error: Some(error), + }; + + self.main_loop_sender + .send(Event::SendResponse(response)) + .map_err(|error| anyhow!("Failed to send response: {error}")) + } + + /// Shows a message to the user. + /// + /// This opens a pop up in VS Code showing `message`. + pub(crate) fn show_message( + &self, + message: impl Display, + message_type: lsp_types::MessageType, + ) -> crate::Result<()> { + self.send_notification::( + lsp_types::ShowMessageParams { + typ: message_type, + message: message.to_string(), + }, + ) + } + + /// Sends a request to display a warning to the client with a formatted message. The warning is + /// sent in a `window/showMessage` notification. + /// + /// Logs an error if the message could not be sent. + pub(crate) fn show_warning_message(&self, message: impl Display) { + let result = self.show_message(message, lsp_types::MessageType::WARNING); + + if let Err(err) = result { + tracing::error!("Failed to send warning message to the client: {err}"); + } + } + + /// Sends a request to display an error to the client with a formatted message. The error is + /// sent in a `window/showMessage` notification. + /// + /// Logs an error if the message could not be sent. + pub(crate) fn show_error_message(&self, message: impl Display) { + let result = self.show_message(message, lsp_types::MessageType::ERROR); + + if let Err(err) = result { + tracing::error!("Failed to send error message to the client: {err}"); + } + } + + pub(crate) fn cancel(&self, session: &mut Session, id: RequestId) -> crate::Result<()> { + let method_name = session.request_queue_mut().incoming_mut().cancel(&id); + + if let Some(method_name) = method_name { + tracing::debug!("Cancelled request id={id} method={method_name}"); + let error = ResponseError { + code: ErrorCode::RequestCanceled as i32, + message: "request was cancelled by client".to_owned(), + data: None, + }; + + // Use `client_sender` here instead of `respond_err` because + // `respond_err` filters out responses for canceled requests (which we just did!). + self.client_sender + .send(Message::Response(lsp_server::Response { + id, + result: None, + error: Some(error), + }))?; + } + + Ok(()) + } +} diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 2e56499eccadf6..209b5121c8f285 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -11,6 +11,7 @@ use thiserror::Error; pub(crate) use ruff_settings::RuffSettings; use crate::edit::LanguageId; +use crate::session::Client; use crate::session::options::Combine; use crate::session::settings::GlobalClientSettings; use crate::workspace::{Workspace, Workspaces}; @@ -73,10 +74,11 @@ impl Index { pub(super) fn new( workspaces: &Workspaces, global: &GlobalClientSettings, + client: &Client, ) -> crate::Result { let mut settings = WorkspaceSettingsIndex::default(); for workspace in &**workspaces { - settings.register_workspace(workspace, global)?; + settings.register_workspace(workspace, global, client)?; } Ok(Self { @@ -173,10 +175,11 @@ impl Index { &mut self, url: Url, global: &GlobalClientSettings, + client: &Client, ) -> crate::Result<()> { // TODO(jane): Find a way for workspace client settings to be added or changed dynamically. self.settings - .register_workspace(&Workspace::new(url), global) + .register_workspace(&Workspace::new(url), global, client) } pub(super) fn close_workspace_folder(&mut self, workspace_url: &Url) -> crate::Result<()> { @@ -259,7 +262,7 @@ impl Index { /// registered in [`try_register_capabilities`] method. /// /// [`try_register_capabilities`]: crate::server::Server::try_register_capabilities - pub(super) fn reload_settings(&mut self, changes: &[FileEvent]) { + pub(super) fn reload_settings(&mut self, changes: &[FileEvent], client: &Client) { let mut indexed = FxHashSet::default(); for change in changes { @@ -287,6 +290,7 @@ impl Index { indexed.insert(root.clone()); settings.ruff_settings = ruff_settings::RuffSettingsIndex::new( + client, root, settings.client_settings.editor_settings(), false, @@ -415,11 +419,14 @@ impl WorkspaceSettingsIndex { &mut self, workspace: &Workspace, global: &GlobalClientSettings, + client: &Client, ) -> crate::Result<()> { let workspace_url = workspace.url(); if workspace_url.scheme() != "file" { tracing::info!("Ignoring non-file workspace URL: {workspace_url}"); - show_warn_msg!("Ruff does not support non-file workspaces; Ignoring {workspace_url}"); + client.show_warning_message(format_args!( + "Ruff does not support non-file workspaces; Ignoring {workspace_url}" + )); return Ok(()); } let workspace_path = workspace_url.to_file_path().map_err(|()| { @@ -431,10 +438,10 @@ impl WorkspaceSettingsIndex { let settings = match options.into_settings() { Ok(settings) => settings, Err(settings) => { - show_err_msg!( + client.show_error_message(format_args!( "The settings for the workspace {workspace_path} are invalid. Refer to the logs for more information.", workspace_path = workspace_path.display() - ); + )); settings } }; @@ -444,6 +451,7 @@ impl WorkspaceSettingsIndex { }; let workspace_settings_index = ruff_settings::RuffSettingsIndex::new( + client, &workspace_path, client_settings.editor_settings(), workspace.is_default(), diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index 0f58ac2d941d60..658a2a08cc611e 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -18,6 +18,7 @@ use ruff_workspace::{ resolver::ConfigurationTransformer, }; +use crate::session::Client; use crate::session::options::ConfigurationPreference; use crate::session::settings::{EditorSettings, ResolvedConfiguration}; @@ -155,6 +156,7 @@ impl RuffSettingsIndex { /// server will be running in a single file mode, then only (1) and (2) will be resolved, /// skipping (3). pub(super) fn new( + client: &Client, root: &Path, editor_settings: &EditorSettings, is_default_workspace: bool, @@ -242,10 +244,10 @@ impl RuffSettingsIndex { // means for different editors. if is_default_workspace { if has_error { - show_err_msg!( + client.show_error_message(format!( "Error while resolving settings from workspace {}. Please refer to the logs for more details.", root.display() - ); + )); } return RuffSettingsIndex { index, fallback }; @@ -358,10 +360,10 @@ impl RuffSettingsIndex { }); if has_error.load(Ordering::Relaxed) { - show_err_msg!( + client.show_error_message(format!( "Error while resolving settings from workspace {}. Please refer to the logs for more details.", root.display() - ); + )); } RuffSettingsIndex { diff --git a/crates/ruff_server/src/session/options.rs b/crates/ruff_server/src/session/options.rs index 47ce2fa83e677e..0a23e712c70a59 100644 --- a/crates/ruff_server/src/session/options.rs +++ b/crates/ruff_server/src/session/options.rs @@ -7,8 +7,9 @@ use serde_json::{Map, Value}; use ruff_linter::{RuleSelector, line_width::LineLength, rule_selector::ParseError}; -use crate::session::settings::{ - ClientSettings, EditorSettings, GlobalClientSettings, ResolvedConfiguration, +use crate::session::{ + Client, + settings::{ClientSettings, EditorSettings, GlobalClientSettings, ResolvedConfiguration}, }; pub(crate) type WorkspaceOptionsMap = FxHashMap; @@ -62,10 +63,11 @@ impl GlobalOptions { &self.client } - pub fn into_settings(self) -> GlobalClientSettings { + pub fn into_settings(self, client: Client) -> GlobalClientSettings { GlobalClientSettings { options: self.client, settings: std::cell::OnceCell::default(), + client, } } } @@ -367,12 +369,12 @@ pub(crate) struct AllOptions { impl AllOptions { /// Initializes the controller from the serialized initialization options. /// This fails if `options` are not valid initialization options. - pub(crate) fn from_value(options: serde_json::Value) -> Self { + pub(crate) fn from_value(options: serde_json::Value, client: &Client) -> Self { Self::from_init_options( serde_json::from_value(options) .map_err(|err| { tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); - show_err_msg!("Ruff received invalid client settings - falling back to default client settings."); + client.show_error_message("Ruff received invalid client settings - falling back to default client settings."); }) .unwrap_or_default(), ) @@ -896,10 +898,14 @@ mod tests { #[test] fn test_global_only_resolves_correctly() { + let (main_loop_sender, main_loop_receiver) = crossbeam::channel::unbounded(); + let (client_sender, client_receiver) = crossbeam::channel::unbounded(); + let options = deserialize_fixture(GLOBAL_ONLY_INIT_OPTIONS_FIXTURE); let AllOptions { global, .. } = AllOptions::from_init_options(options); - let global = global.into_settings(); + let client = Client::new(main_loop_sender, client_sender); + let global = global.into_settings(client); assert_eq!( global.to_settings(), &ClientSettings { @@ -922,6 +928,9 @@ mod tests { }, } ); + + assert!(main_loop_receiver.is_empty()); + assert!(client_receiver.is_empty()); } #[test] @@ -959,6 +968,10 @@ mod tests { #[test] fn inline_configuration() { + let (main_loop_sender, main_loop_receiver) = crossbeam::channel::unbounded(); + let (client_sender, client_receiver) = crossbeam::channel::unbounded(); + let client = Client::new(main_loop_sender, client_sender); + let options: InitializationOptions = deserialize_fixture(INLINE_CONFIGURATION_FIXTURE); let AllOptions { @@ -969,7 +982,7 @@ mod tests { panic!("Expected global settings only"); }; - let global = global.into_settings(); + let global = global.into_settings(client); assert_eq!( global.to_settings(), @@ -1001,5 +1014,8 @@ mod tests { } } ); + + assert!(main_loop_receiver.is_empty()); + assert!(client_receiver.is_empty()); } } diff --git a/crates/ruff_server/src/session/request_queue.rs b/crates/ruff_server/src/session/request_queue.rs new file mode 100644 index 00000000000000..68696050bff876 --- /dev/null +++ b/crates/ruff_server/src/session/request_queue.rs @@ -0,0 +1,198 @@ +use crate::session::client::ClientResponseHandler; +use lsp_server::RequestId; +use rustc_hash::FxHashMap; +use std::cell::{Cell, OnceCell, RefCell}; +use std::fmt::Formatter; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::time::Instant; + +/// Tracks the pending requests between client and server. +pub(crate) struct RequestQueue { + incoming: Incoming, + outgoing: Outgoing, +} + +impl RequestQueue { + pub(super) fn new() -> Self { + Self { + incoming: Incoming::default(), + outgoing: Outgoing::default(), + } + } + + pub(crate) fn outgoing_mut(&mut self) -> &mut Outgoing { + &mut self.outgoing + } + + /// Returns the server to client request queue. + pub(crate) fn outgoing(&self) -> &Outgoing { + &self.outgoing + } + + /// Returns the client to server request queue. + pub(crate) fn incoming(&self) -> &Incoming { + &self.incoming + } + + pub(crate) fn incoming_mut(&mut self) -> &mut Incoming { + &mut self.incoming + } +} + +/// Requests from client -> server. +/// +/// Tracks which requests are pending. Requests that aren't registered are considered completed. +/// +/// A request is pending if: +/// +/// * it has been registered +/// * it hasn't been cancelled +/// * it hasn't been completed +/// +/// Tracking whether a request is pending is required to ensure that the server sends exactly +/// one response for every request as required by the LSP specification. +#[derive(Default, Debug)] +pub(crate) struct Incoming { + pending: FxHashMap, +} + +impl Incoming { + /// Registers a new pending request. + pub(crate) fn register(&mut self, request_id: RequestId, method: String) { + self.pending.insert(request_id, PendingRequest::new(method)); + } + + /// Cancels the pending request with the given id. + /// + /// Returns the method name if the request was still pending, `None` if it was already completed. + pub(super) fn cancel(&mut self, request_id: &RequestId) -> Option { + self.pending.remove(request_id).map(|mut pending| { + if let Some(cancellation_token) = pending.cancellation_token.take() { + cancellation_token.cancel(); + } + pending.method + }) + } + + /// Returns `true` if the request with the given id is still pending. + #[expect(dead_code)] + pub(crate) fn is_pending(&self, request_id: &RequestId) -> bool { + self.pending.contains_key(request_id) + } + + /// Returns the cancellation token for the given request id if the request is still pending. + pub(crate) fn cancellation_token( + &self, + request_id: &RequestId, + ) -> Option { + let pending = self.pending.get(request_id)?; + + Some(RequestCancellationToken::clone( + pending + .cancellation_token + .get_or_init(RequestCancellationToken::default), + )) + } + + /// Marks the request as completed. + /// + /// Returns the time when the request was registered and the request method name, or `None` if the request was not pending. + pub(crate) fn complete(&mut self, request_id: &RequestId) -> Option<(Instant, String)> { + self.pending + .remove(request_id) + .map(|pending| (pending.start_time, pending.method)) + } +} + +/// A request from the client to the server that hasn't been responded yet. +#[derive(Debug)] +struct PendingRequest { + /// The time when the request was registered. + /// + /// This does not include the time the request was queued in the main loop before it was registered. + start_time: Instant, + + /// The method name of the request. + method: String, + + /// A cancellation token to cancel this request. + /// + /// This is only initialized for background requests. Local tasks don't support cancellation (unless retried) + /// as they're processed immediately after receiving the request; Making it impossible for a + /// cancellation message to be processed before the task is completed. + cancellation_token: OnceCell, +} + +impl PendingRequest { + fn new(method: String) -> Self { + Self { + start_time: Instant::now(), + method, + cancellation_token: OnceCell::new(), + } + } +} + +/// Token to cancel a specific request. +/// +/// Can be shared between threads to check for cancellation *after* a request has been scheduled. +#[derive(Debug, Default)] +pub(crate) struct RequestCancellationToken(Arc); + +impl RequestCancellationToken { + /// Returns true if the request was cancelled. + pub(crate) fn is_cancelled(&self) -> bool { + self.0.load(std::sync::atomic::Ordering::Relaxed) + } + + /// Signals that the request should not be processed because it was cancelled. + fn cancel(&self) { + self.0.store(true, std::sync::atomic::Ordering::Relaxed); + } + + fn clone(this: &Self) -> Self { + RequestCancellationToken(this.0.clone()) + } +} + +/// Requests from server -> client. +#[derive(Default)] +pub(crate) struct Outgoing { + /// The id of the next request sent from the server to the client. + next_request_id: Cell, + + /// A map of request ids to the handlers that process the client-response. + response_handlers: RefCell>, +} + +impl Outgoing { + /// Registers a handler, returns the id for the request. + #[must_use] + pub(crate) fn register(&self, handler: ClientResponseHandler) -> RequestId { + let id = self.next_request_id.get(); + self.next_request_id.set(id + 1); + + self.response_handlers + .borrow_mut() + .insert(id.into(), handler); + id.into() + } + + /// Marks the request with the given id as complete and returns the handler to process the response. + /// + /// Returns `None` if the request was not found. + #[must_use] + pub(crate) fn complete(&mut self, request_id: &RequestId) -> Option { + self.response_handlers.get_mut().remove(request_id) + } +} + +impl std::fmt::Debug for Outgoing { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Outgoing") + .field("next_request_id", &self.next_request_id) + .field("response_handlers", &"") + .finish() + } +} diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs index 4015109e74df80..94cf41c827d196 100644 --- a/crates/ruff_server/src/session/settings.rs +++ b/crates/ruff_server/src/session/settings.rs @@ -8,7 +8,10 @@ use ruff_workspace::options::Options; use crate::{ ClientOptions, - session::options::{ClientConfiguration, ConfigurationPreference}, + session::{ + Client, + options::{ClientConfiguration, ConfigurationPreference}, + }, }; pub struct GlobalClientSettings { @@ -20,6 +23,8 @@ pub struct GlobalClientSettings { /// when the workspace settings e.g. select some rules that aren't available in a specific workspace /// and said workspace overrides the selected rules. pub(super) settings: std::cell::OnceCell>, + + pub(super) client: Client, } impl GlobalClientSettings { @@ -33,7 +38,7 @@ impl GlobalClientSettings { let settings = match settings { Ok(settings) => settings, Err(settings) => { - show_err_msg!( + self.client.show_error_message( "Ruff received invalid settings from the editor. Refer to the logs for more information." ); settings diff --git a/crates/ruff_server/tests/notebook.rs b/crates/ruff_server/tests/notebook.rs index 6d3e6be1db562b..504d299048163f 100644 --- a/crates/ruff_server/tests/notebook.rs +++ b/crates/ruff_server/tests/notebook.rs @@ -8,7 +8,7 @@ use lsp_types::{ Position, Range, TextDocumentContentChangeEvent, VersionedTextDocumentIdentifier, }; use ruff_notebook::SourceValue; -use ruff_server::{ClientOptions, GlobalOptions, Workspace, Workspaces}; +use ruff_server::{Client, ClientOptions, GlobalOptions, Workspace, Workspaces}; const SUPER_RESOLUTION_OVERVIEW_PATH: &str = "./resources/test/fixtures/tensorflow_test_notebook.ipynb"; @@ -28,8 +28,13 @@ fn super_resolution_overview() { insta::assert_snapshot!("initial_notebook", notebook_source(¬ebook)); + let (main_loop_sender, main_loop_receiver) = crossbeam::channel::unbounded(); + let (client_sender, client_receiver) = crossbeam::channel::unbounded(); + + let client = Client::new(main_loop_sender, client_sender); + let options = GlobalOptions::default(); - let global = options.into_settings(); + let global = options.into_settings(client.clone()); let mut session = ruff_server::Session::new( &ClientCapabilities::default(), @@ -39,6 +44,7 @@ fn super_resolution_overview() { Workspace::new(lsp_types::Url::from_file_path(file_path.parent().unwrap()).unwrap()) .with_options(ClientOptions::default()), ]), + &client, ) .unwrap(); @@ -307,6 +313,9 @@ fn super_resolution_overview() { "changed_notebook", notebook_source(snapshot.query().as_notebook().unwrap()) ); + + assert!(client_receiver.is_empty()); + assert!(main_loop_receiver.is_empty()); } fn notebook_source(notebook: &ruff_server::NotebookDocument) -> String { diff --git a/crates/ty_server/src/server/api.rs b/crates/ty_server/src/server/api.rs index f937adb2e4caba..cf1ffcdaad15eb 100644 --- a/crates/ty_server/src/server/api.rs +++ b/crates/ty_server/src/server/api.rs @@ -331,7 +331,7 @@ where .with_failure_code(server::ErrorCode::InternalError) } -/// Sends back a response to the server using a [`Responder`]. +/// Sends back a response to the server, but only if the request wasn't cancelled. fn respond( id: &RequestId, result: Result<<::RequestType as Request>::Result>, diff --git a/crates/ty_server/src/server/main_loop.rs b/crates/ty_server/src/server/main_loop.rs index 456e9d88b21354..6b05f39317e3d3 100644 --- a/crates/ty_server/src/server/main_loop.rs +++ b/crates/ty_server/src/server/main_loop.rs @@ -1,4 +1,3 @@ -use crate::Session; use crate::server::schedule::Scheduler; use crate::server::{Server, api}; use crate::session::client::Client; @@ -79,7 +78,7 @@ impl Server { .outgoing_mut() .complete(&response.id) { - handler(&self.session, response); + handler(&client, response); } else { tracing::error!( "Received a response with ID {}, which was not expected", @@ -203,7 +202,7 @@ impl Server { .unwrap(), ), }; - let response_handler = move |_session: &Session, ()| { + let response_handler = move |_: &Client, ()| { tracing::info!("File watcher successfully registered"); }; diff --git a/crates/ty_server/src/server/schedule/thread/pool.rs b/crates/ty_server/src/server/schedule/thread/pool.rs index 88abbd8e42e0bc..c49d642a772c79 100644 --- a/crates/ty_server/src/server/schedule/thread/pool.rs +++ b/crates/ty_server/src/server/schedule/thread/pool.rs @@ -51,7 +51,7 @@ impl Pool { let threads = usize::from(threads); - let (job_sender, job_receiver) = crossbeam::channel::bounded(std::cmp::max(threads * 2, 4)); + let (job_sender, job_receiver) = crossbeam::channel::bounded(std::cmp::min(threads * 2, 4)); let extant_tasks = Arc::new(AtomicUsize::new(0)); let mut handles = Vec::with_capacity(threads); diff --git a/crates/ty_server/src/session/client.rs b/crates/ty_server/src/session/client.rs index 4d54c0fd63769f..f9506e375b5e1a 100644 --- a/crates/ty_server/src/session/client.rs +++ b/crates/ty_server/src/session/client.rs @@ -7,7 +7,7 @@ use serde_json::Value; use std::any::TypeId; use std::fmt::Display; -pub(crate) type ClientResponseHandler = Box; +pub(crate) type ClientResponseHandler = Box; #[derive(Debug)] pub(crate) struct Client { @@ -44,53 +44,51 @@ impl Client { &self, session: &Session, params: R::Params, - response_handler: impl FnOnce(&Session, R::Result) + Send + 'static, + response_handler: impl FnOnce(&Client, R::Result) + Send + 'static, ) -> crate::Result<()> where R: lsp_types::request::Request, { - let response_handler = Box::new( - move |session: &Session, response: lsp_server::Response| { - let _span = - tracing::debug_span!("client_response", id=%response.id, method = R::METHOD) - .entered(); + let response_handler = Box::new(move |client: &Client, response: lsp_server::Response| { + let _span = + tracing::debug_span!("client_response", id=%response.id, method = R::METHOD) + .entered(); - match (response.error, response.result) { - (Some(err), _) => { + match (response.error, response.result) { + (Some(err), _) => { + tracing::error!( + "Got an error from the client (code {code}, method {method}): {message}", + code = err.code, + message = err.message, + method = R::METHOD + ); + } + (None, Some(response)) => match serde_json::from_value(response) { + Ok(response) => response_handler(client, response), + Err(error) => { tracing::error!( - "Got an error from the client (code {code}, method {method}): {message}", - code = err.code, - message = err.message, + "Failed to deserialize client response (method={method}): {error}", method = R::METHOD ); } - (None, Some(response)) => match serde_json::from_value(response) { - Ok(response) => response_handler(session, response), - Err(error) => { - tracing::error!( - "Failed to deserialize client response (method={method}): {error}", - method = R::METHOD - ); - } - }, - (None, None) => { - if TypeId::of::() == TypeId::of::<()>() { - // We can't call `response_handler(())` directly here, but - // since we _know_ the type expected is `()`, we can use - // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, - // so this branch works in the general case but we'll only - // hit it if the concrete type is `()`, so the `unwrap()` is safe here. - response_handler(session, serde_json::from_value(Value::Null).unwrap()); - } else { - tracing::error!( - "Invalid client response: did not contain a result or error (method={method})", - method = R::METHOD - ); - } + }, + (None, None) => { + if TypeId::of::() == TypeId::of::<()>() { + // We can't call `response_handler(())` directly here, but + // since we _know_ the type expected is `()`, we can use + // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, + // so this branch works in the general case but we'll only + // hit it if the concrete type is `()`, so the `unwrap()` is safe here. + response_handler(client, serde_json::from_value(Value::Null).unwrap()); + } else { + tracing::error!( + "Invalid client response: did not contain a result or error (method={method})", + method = R::METHOD + ); } } - }, - ); + } + }); let id = session .request_queue() From 76d9009a6ed257665e34d0412857e9c0bf609367 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Fri, 13 Jun 2025 03:44:15 -0300 Subject: [PATCH 409/487] [`pycodestyle`] Fix `E731` autofix creating a syntax error for expressions spanned across multiple lines (#18479) --- .../test/fixtures/pycodestyle/E731.py | 15 ++++- .../pycodestyle/rules/lambda_assignment.rs | 26 ++++++-- ...les__pycodestyle__tests__E731_E731.py.snap | 62 ++++++++++++++++++- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py index e28d613857469b..a5fab10bf48f7e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py @@ -176,4 +176,17 @@ class FilterDataclass: x = lambda: ( # comment y := 10 -) \ No newline at end of file +) + +# https://github.com/astral-sh/ruff/issues/18475 +foo_tooltip = ( + lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + if data["foo"] is not None + else "" +) + +foo_tooltip = ( + lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + + more + +) diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index ec00bf84f10e75..6b4ce91adec53d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -92,7 +92,7 @@ pub(crate) fn lambda_assignment( let first_line = checker.locator().line_str(stmt.start()); let indentation = leading_indentation(first_line); let mut indented = String::new(); - for (idx, line) in function(id, lambda, annotation, checker) + for (idx, line) in function(id, lambda, annotation, stmt, checker) .universal_newlines() .enumerate() { @@ -177,6 +177,7 @@ fn function( name: &str, lambda: &ExprLambda, annotation: Option<&Expr>, + stmt: &Stmt, checker: &Checker, ) -> String { // Use a dummy body. It gets replaced at the end with the actual body. @@ -236,7 +237,7 @@ fn function( }); let generated = checker.generator().stmt(&func); - return replace_trailing_ellipsis_with_original_expr(generated, lambda, checker); + return replace_trailing_ellipsis_with_original_expr(generated, lambda, stmt, checker); } } let function = Stmt::FunctionDef(ast::StmtFunctionDef { @@ -251,12 +252,13 @@ fn function( }); let generated = checker.generator().stmt(&function); - replace_trailing_ellipsis_with_original_expr(generated, lambda, checker) + replace_trailing_ellipsis_with_original_expr(generated, lambda, stmt, checker) } fn replace_trailing_ellipsis_with_original_expr( mut generated: String, lambda: &ExprLambda, + stmt: &Stmt, checker: &Checker, ) -> String { let original_expr_range = parenthesized_range( @@ -267,14 +269,28 @@ fn replace_trailing_ellipsis_with_original_expr( ) .unwrap_or(lambda.body.range()); - let original_expr_in_source = checker.locator().slice(original_expr_range); + // This prevents the autofix of introducing a syntax error if the lambda's body is an + // expression spanned across multiple lines. To avoid the syntax error we preserve + // the parenthesis around the body. + let original_expr_in_source = if parenthesized_range( + lambda.into(), + stmt.into(), + checker.comment_ranges(), + checker.source(), + ) + .is_some() + { + format!("({})", checker.locator().slice(original_expr_range)) + } else { + checker.locator().slice(original_expr_range).to_string() + }; let placeholder_ellipsis_start = generated.rfind("...").unwrap(); let placeholder_ellipsis_end = placeholder_ellipsis_start + "...".len(); generated.replace_range( placeholder_ellipsis_start..placeholder_ellipsis_end, - original_expr_in_source, + &original_expr_in_source, ); generated } diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap index 775ca06ecbaabd..d80f8fca9619d6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap @@ -318,7 +318,7 @@ E731.py:139:5: E731 Do not assign a `lambda` expression, use a `def` 138 138 | class TemperatureScales(Enum): 139 |- CELSIUS = (lambda deg_c: deg_c) 139 |+ def CELSIUS(deg_c): - 140 |+ return deg_c + 140 |+ return (deg_c) 140 141 | FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32) 141 142 | 142 143 | @@ -338,7 +338,7 @@ E731.py:140:5: E731 Do not assign a `lambda` expression, use a `def` 139 139 | CELSIUS = (lambda deg_c: deg_c) 140 |- FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32) 140 |+ def FAHRENHEIT(deg_c): - 141 |+ return deg_c * 9 / 5 + 32 + 141 |+ return (deg_c * 9 / 5 + 32) 141 142 | 142 143 | 143 144 | # Regression test for: https://github.com/astral-sh/ruff/issues/7141 @@ -449,6 +449,8 @@ E731.py:176:1: E731 [*] Do not assign a `lambda` expression, use a `def` 178 | | y := 10 179 | | ) | |_^ E731 +180 | +181 | # https://github.com/astral-sh/ruff/issues/18475 | = help: Rewrite `x` as a `def` @@ -462,3 +464,59 @@ E731.py:176:1: E731 [*] Do not assign a `lambda` expression, use a `def` 177 178 | # comment 178 179 | y := 10 179 180 | ) + +E731.py:182:1: E731 [*] Do not assign a `lambda` expression, use a `def` + | +181 | # https://github.com/astral-sh/ruff/issues/18475 +182 | / foo_tooltip = ( +183 | | lambda x, data: f"\nfoo: {data['foo'][int(x)]}" +184 | | if data["foo"] is not None +185 | | else "" +186 | | ) + | |_^ E731 +187 | +188 | foo_tooltip = ( + | + = help: Rewrite `foo_tooltip` as a `def` + +ℹ Unsafe fix +179 179 | ) +180 180 | +181 181 | # https://github.com/astral-sh/ruff/issues/18475 +182 |-foo_tooltip = ( +183 |- lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + 182 |+def foo_tooltip(x, data): + 183 |+ return (f"\nfoo: {data['foo'][int(x)]}" +184 184 | if data["foo"] is not None +185 |- else "" +186 |-) + 185 |+ else "") +187 186 | +188 187 | foo_tooltip = ( +189 188 | lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + + +E731.py:188:1: E731 [*] Do not assign a `lambda` expression, use a `def` + | +186 | ) +187 | +188 | / foo_tooltip = ( +189 | | lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + +190 | | more +191 | | +192 | | ) + | |_^ E731 + | + = help: Rewrite `foo_tooltip` as a `def` + +ℹ Unsafe fix +185 185 | else "" +186 186 | ) +187 187 | +188 |-foo_tooltip = ( +189 |- lambda x, data: f"\nfoo: {data['foo'][int(x)]}" + +190 |- more +191 |- +192 |-) + 188 |+def foo_tooltip(x, data): + 189 |+ return (f"\nfoo: {data['foo'][int(x)]}" + + 190 |+ more) From c9dff5c7d5bcab2a83f1c3b4a1a80af230ece541 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 13 Jun 2025 08:40:11 -0400 Subject: [PATCH 410/487] [ty] AST garbage collection (#18482) ## Summary Garbage collect ASTs once we are done checking a given file. Queries with a cross-file dependency on the AST will reparse the file on demand. This reduces ty's peak memory usage by ~20-30%. The primary change of this PR is adding a `node_index` field to every AST node, that is assigned by the parser. `ParsedModule` can use this to create a flat index of AST nodes any time the file is parsed (or reparsed). This allows `AstNodeRef` to simply index into the current instance of the `ParsedModule`, instead of storing a pointer directly. The indices are somewhat hackily (using an atomic integer) assigned by the `parsed_module` query instead of by the parser directly. Assigning the indices in source-order in the (recursive) parser turns out to be difficult, and collecting the nodes during semantic indexing is impossible as `SemanticIndex` does not hold onto a specific `ParsedModuleRef`, which the pointers in the flat AST are tied to. This means that we have to do an extra AST traversal to assign and collect the nodes into a flat index, but the small performance impact (~3% on cold runs) seems worth it for the memory savings. Part of https://github.com/astral-sh/ty/issues/214. --- Cargo.lock | 7 + Cargo.toml | 1 + crates/ruff_db/Cargo.toml | 1 + crates/ruff_db/src/parsed.rs | 319 +- crates/ruff_graph/src/collector.rs | 14 +- .../checkers/ast/analyze/except_handler.rs | 1 + .../src/checkers/ast/analyze/expression.rs | 27 +- .../src/checkers/ast/analyze/statement.rs | 42 +- crates/ruff_linter/src/checkers/ast/mod.rs | 74 +- crates/ruff_linter/src/doc_lines.rs | 1 + .../ruff_linter/src/docstrings/extraction.rs | 7 +- crates/ruff_linter/src/importer/mod.rs | 1 + .../src/rules/airflow/rules/removal_in_3.rs | 14 +- .../airflow/rules/suggested_to_update_3_0.rs | 1 + .../src/rules/flake8_2020/rules/subscript.rs | 1 + .../src/rules/flake8_annotations/helpers.rs | 2 + .../flake8_annotations/rules/definition.rs | 7 +- .../rules/bad_file_permissions.rs | 1 + .../rules/abstract_base_class.rs | 6 +- .../flake8_bugbear/rules/assert_false.rs | 4 + .../rules/assert_raises_exception.rs | 1 + .../rules/duplicate_exceptions.rs | 1 + .../rules/f_string_docstring.rs | 7 +- .../rules/function_uses_loop_variable.rs | 3 + .../rules/loop_iterator_mutation.rs | 10 +- .../rules/loop_variable_overrides_iterator.rs | 1 + .../rules/return_in_generator.rs | 1 + .../rules/reuse_of_groupby_generator.rs | 11 +- .../rules/setattr_with_constant.rs | 3 + ...cessary_dict_comprehension_for_iterable.rs | 3 + .../rules/unnecessary_list_call.rs | 2 + .../rules/unnecessary_subscript_reversal.rs | 2 + .../rules/multiple_starts_ends_with.rs | 11 + .../rules/reimplemented_container_builtin.rs | 1 + .../rules/duplicate_literal_member.rs | 2 + .../rules/duplicate_union_member.rs | 1 + .../flake8_pyi/rules/exit_annotations.rs | 1 + .../src/rules/flake8_pyi/rules/mod.rs | 3 + .../flake8_pyi/rules/non_empty_stub_body.rs | 7 +- .../rules/redundant_none_literal.rs | 3 + .../rules/redundant_numeric_union.rs | 1 + .../rules/flake8_pyi/rules/simple_defaults.rs | 11 +- .../rules/unnecessary_literal_union.rs | 4 + .../rules/unnecessary_type_union.rs | 9 + .../flake8_pytest_style/rules/assertion.rs | 2 + .../flake8_pytest_style/rules/fixture.rs | 13 +- .../rules/flake8_pytest_style/rules/marks.rs | 2 + .../flake8_pytest_style/rules/parametrize.rs | 7 + .../rules/flake8_pytest_style/rules/patch.rs | 1 + .../rules/unittest_assert.rs | 13 + .../rules/unnecessary_escaped_quote.rs | 2 + .../unnecessary_paren_on_raise_exception.rs | 1 + .../src/rules/flake8_return/visitor.rs | 12 +- .../flake8_simplify/rules/ast_bool_op.rs | 19 + .../rules/flake8_simplify/rules/ast_expr.rs | 2 + .../rules/flake8_simplify/rules/ast_ifexp.rs | 6 + .../flake8_simplify/rules/ast_unary_op.rs | 8 + .../if_else_block_instead_of_dict_get.rs | 9 + .../if_else_block_instead_of_dict_lookup.rs | 29 +- .../rules/if_else_block_instead_of_if_exp.rs | 7 + .../flake8_simplify/rules/key_in_dict.rs | 1 + .../flake8_simplify/rules/needless_bool.rs | 15 +- .../rules/open_file_with_context_handler.rs | 7 +- .../rules/reimplemented_builtin.rs | 15 + .../rules/split_static_string.rs | 2 + .../rules/suppressible_exception.rs | 8 +- .../flake8_simplify/rules/yoda_conditions.rs | 1 + .../rules/relative_imports.rs | 1 + .../src/rules/flake8_type_checking/helpers.rs | 1 + crates/ruff_linter/src/rules/flynt/helpers.rs | 17 +- .../flynt/rules/static_join_to_fstring.rs | 2 + .../ruff_linter/src/rules/isort/annotate.rs | 7 +- .../rules/isort/rules/add_required_imports.rs | 8 +- .../rules/nunique_constant_series_check.rs | 1 + .../rules/manual_dict_comprehension.rs | 1 + .../rules/manual_list_comprehension.rs | 2 + .../rules/perflint/rules/manual_list_copy.rs | 2 + .../perflint/rules/unnecessary_list_cast.rs | 2 + .../pycodestyle/rules/lambda_assignment.rs | 3 + .../rules/multiple_imports_on_one_line.rs | 2 + .../src/rules/pycodestyle/rules/not_tests.rs | 1 + .../rules/pydoclint/rules/check_docstring.rs | 14 +- .../rules/bad_string_format_character.rs | 1 + .../pylint/rules/bad_string_format_type.rs | 7 +- .../src/rules/pylint/rules/if_stmt_min_max.rs | 1 + .../rules/pylint/rules/manual_import_from.rs | 2 + .../src/rules/pylint/rules/nested_min_max.rs | 5 + .../rules/pylint/rules/redefined_loop_name.rs | 4 + .../rules/redefined_slots_in_subclass.rs | 20 +- .../rules/repeated_equality_comparison.rs | 6 +- .../src/rules/pylint/rules/return_in_init.rs | 7 +- .../rules/pylint/rules/unnecessary_lambda.rs | 1 + .../pylint/rules/unspecified_encoding.rs | 1 + .../src/rules/pylint/rules/useless_return.rs | 14 +- ...convert_named_tuple_functional_to_class.rs | 7 + .../convert_typed_dict_functional_to_class.rs | 18 +- .../rules/deprecated_c_element_tree.rs | 7 +- .../pyupgrade/rules/deprecated_mock_import.rs | 6 +- .../src/rules/pyupgrade/rules/f_strings.rs | 1 + .../rules/lru_cache_with_maxsize_none.rs | 3 + .../rules/lru_cache_without_parameters.rs | 1 + .../rules/pyupgrade/rules/native_literals.rs | 6 + .../rules/pyupgrade/rules/os_error_alias.rs | 2 + .../pyupgrade/rules/outdated_version_block.rs | 1 + .../src/rules/pyupgrade/rules/pep695/mod.rs | 7 + .../rules/printf_string_formatting.rs | 7 +- .../pyupgrade/rules/timeout_error_alias.rs | 2 + .../rules/unnecessary_default_type_args.rs | 3 + .../pyupgrade/rules/yield_in_for_loop.rs | 9 +- .../ruff_linter/src/rules/refurb/helpers.rs | 6 + .../refurb/rules/check_and_remove_from_set.rs | 4 + .../rules/refurb/rules/delete_full_slice.rs | 1 + .../rules/if_exp_instead_of_or_operator.rs | 1 + .../src/rules/refurb/rules/read_whole_file.rs | 3 + .../refurb/rules/reimplemented_starmap.rs | 6 + .../src/rules/refurb/rules/repeated_append.rs | 5 + .../rules/single_item_membership_test.rs | 1 + .../src/rules/refurb/rules/slice_copy.rs | 1 + .../rules/slice_to_remove_prefix_or_suffix.rs | 10 + .../refurb/rules/unnecessary_enumerate.rs | 8 + .../rules/refurb/rules/write_whole_file.rs | 3 + .../ruff/rules/assert_with_print_message.rs | 8 + .../rules/collection_literal_concatenation.rs | 4 + .../explicit_f_string_type_conversion.rs | 1 + .../src/rules/ruff/rules/implicit_optional.rs | 3 + .../rules/ruff/rules/in_empty_collection.rs | 1 + .../ruff/rules/map_int_version_parsing.rs | 9 +- .../ruff/rules/mutable_fromkeys_value.rs | 3 + .../src/rules/ruff/rules/never_union.rs | 4 + .../ruff/rules/quadratic_list_summation.rs | 1 + .../src/rules/ruff/rules/sort_dunder_slots.rs | 6 +- .../ruff/rules/unnecessary_nested_literal.rs | 2 + .../rules/unnecessary_regular_expression.rs | 5 + .../ruff/rules/zip_instead_of_pairwise.rs | 1 + .../tryceratops/rules/try_consider_else.rs | 7 +- crates/ruff_python_ast/generate.py | 211 + crates/ruff_python_ast/src/comparable.rs | 194 +- crates/ruff_python_ast/src/generated.rs | 5899 ++++++++++++----- crates/ruff_python_ast/src/helpers.rs | 170 +- crates/ruff_python_ast/src/lib.rs | 2 + crates/ruff_python_ast/src/node.rs | 97 +- crates/ruff_python_ast/src/node_index.rs | 98 + crates/ruff_python_ast/src/nodes.rs | 107 +- crates/ruff_python_ast/src/relocate.rs | 4 +- crates/ruff_python_ast/src/visitor.rs | 82 +- .../src/visitor/transformer.rs | 82 +- .../tests/visitor.rs | 12 +- crates/ruff_python_codegen/src/generator.rs | 113 +- crates/ruff_python_formatter/CONTRIBUTING.md | 2 +- .../src/comments/debug.rs | 4 +- .../src/comments/node_key.rs | 3 + .../src/comments/placement.rs | 2 + .../src/expression/expr_attribute.rs | 8 +- .../src/expression/expr_await.rs | 6 +- .../src/expression/expr_call.rs | 1 + .../src/expression/expr_dict.rs | 6 +- .../src/expression/expr_dict_comp.rs | 1 + .../src/expression/expr_generator.rs | 1 + .../src/expression/expr_if.rs | 1 + .../src/expression/expr_lambda.rs | 1 + .../src/expression/expr_list.rs | 1 + .../src/expression/expr_list_comp.rs | 1 + .../src/expression/expr_name.rs | 1 + .../src/expression/expr_named.rs | 1 + .../src/expression/expr_set.rs | 6 +- .../src/expression/expr_set_comp.rs | 1 + .../src/expression/expr_slice.rs | 2 + .../src/expression/expr_starred.rs | 1 + .../src/expression/expr_subscript.rs | 1 + .../src/expression/expr_tuple.rs | 1 + .../src/expression/expr_unary_op.rs | 1 + .../src/expression/mod.rs | 6 + .../src/module/mod_expression.rs | 6 +- .../src/module/mod_module.rs | 6 +- .../ruff_python_formatter/src/other/alias.rs | 1 + .../src/other/arguments.rs | 1 + .../src/other/comprehension.rs | 1 + .../src/other/decorator.rs | 1 + .../other/except_handler_except_handler.rs | 1 + .../src/other/keyword.rs | 1 + .../src/other/match_case.rs | 1 + .../src/other/parameter.rs | 1 + .../src/other/parameter_with_default.rs | 1 + .../src/other/parameters.rs | 1 + .../src/other/with_item.rs | 1 + .../src/pattern/pattern_keyword.rs | 1 + .../src/pattern/pattern_match_as.rs | 1 + .../src/pattern/pattern_match_class.rs | 1 + .../src/pattern/pattern_match_mapping.rs | 2 + .../src/pattern/pattern_match_or.rs | 6 +- .../src/pattern/pattern_match_sequence.rs | 6 +- .../src/pattern/pattern_match_value.rs | 6 +- crates/ruff_python_formatter/src/range.rs | 2 + .../src/statement/clause.rs | 10 + .../src/statement/stmt_ann_assign.rs | 1 + .../src/statement/stmt_assert.rs | 1 + .../src/statement/stmt_assign.rs | 1 + .../src/statement/stmt_aug_assign.rs | 1 + .../src/statement/stmt_class_def.rs | 1 + .../src/statement/stmt_delete.rs | 6 +- .../src/statement/stmt_for.rs | 1 + .../src/statement/stmt_function_def.rs | 1 + .../src/statement/stmt_if.rs | 2 + .../src/statement/stmt_import.rs | 6 +- .../src/statement/stmt_import_from.rs | 1 + .../src/statement/stmt_match.rs | 1 + .../src/statement/stmt_raise.rs | 1 + .../src/statement/stmt_return.rs | 6 +- .../src/statement/stmt_try.rs | 1 + .../src/statement/stmt_type_alias.rs | 1 + .../src/statement/stmt_while.rs | 1 + .../src/type_param/type_param_param_spec.rs | 1 + .../src/type_param/type_param_type_var.rs | 1 + .../type_param/type_param_type_var_tuple.rs | 1 + .../ruff_python_formatter/tests/normalizer.rs | 6 +- crates/ruff_python_parser/src/lib.rs | 3 +- .../src/parser/expression.rs | 86 +- crates/ruff_python_parser/src/parser/mod.rs | 4 +- .../ruff_python_parser/src/parser/pattern.rs | 62 +- .../ruff_python_parser/src/parser/recovery.rs | 68 +- ...parser__tests__expr_mode_valid_syntax.snap | 2 +- ...arser__tests__ipython_escape_commands.snap | 59 +- ...arser__parser__tests__unicode_aliases.snap | 5 +- .../src/parser/statement.rs | 53 +- .../ruff_python_parser/src/semantic_errors.rs | 7 +- ...arser__string__tests__backspace_alias.snap | 4 +- ...hon_parser__string__tests__bell_alias.snap | 4 +- ..._string__tests__carriage_return_alias.snap | 4 +- ...r_tabulation_with_justification_alias.snap | 4 +- ...n_parser__string__tests__delete_alias.snap | 4 +- ...ests__dont_panic_on_8_in_octal_escape.snap | 5 +- ...er__string__tests__double_quoted_byte.snap | 4 +- ...n_parser__string__tests__escape_alias.snap | 4 +- ...g__tests__escape_char_in_byte_literal.snap | 4 +- ...n_parser__string__tests__escape_octet.snap | 4 +- ...arser__string__tests__form_feed_alias.snap | 4 +- ...string__tests__fstring_constant_range.snap | 10 + ...ing__tests__fstring_escaped_character.snap | 6 + ...tring__tests__fstring_escaped_newline.snap | 6 + ...ing__tests__fstring_line_continuation.snap | 6 + ...__fstring_parse_self_documenting_base.snap | 5 + ...ring_parse_self_documenting_base_more.snap | 9 + ...fstring_parse_self_documenting_format.snap | 7 + ...ing__tests__fstring_unescaped_newline.snap | 6 + ...thon_parser__string__tests__hts_alias.snap | 4 +- ...r__string__tests__parse_empty_fstring.snap | 3 + ...r__string__tests__parse_empty_tstring.snap | 3 + ...tring__tests__parse_f_string_concat_1.snap | 5 + ...tring__tests__parse_f_string_concat_2.snap | 5 + ...tring__tests__parse_f_string_concat_3.snap | 8 + ...tring__tests__parse_f_string_concat_4.snap | 9 + ...ing__tests__parse_f_t_string_concat_1.snap | 6 + ...ing__tests__parse_f_t_string_concat_2.snap | 7 + ..._parser__string__tests__parse_fstring.snap | 8 + ...__string__tests__parse_fstring_equals.snap | 7 + ...ring_nested_concatenation_string_spec.snap | 10 + ...ing__tests__parse_fstring_nested_spec.snap | 8 + ...sts__parse_fstring_nested_string_spec.snap | 9 + ...ring__tests__parse_fstring_not_equals.snap | 7 + ..._tests__parse_fstring_not_nested_spec.snap | 7 + ...ts__parse_fstring_self_doc_prec_space.snap | 5 + ...parse_fstring_self_doc_trailing_space.snap | 5 + ...ring__tests__parse_fstring_yield_expr.snap | 5 + ...r__string__tests__parse_string_concat.snap | 5 +- ..._parse_string_triple_quotes_with_kind.snap | 4 +- ...tring__tests__parse_t_string_concat_1.snap | 5 + ...tring__tests__parse_t_string_concat_2.snap | 5 + ...tring__tests__parse_t_string_concat_3.snap | 8 + ...tring__tests__parse_t_string_concat_4.snap | 9 + ..._parser__string__tests__parse_tstring.snap | 8 + ...__string__tests__parse_tstring_equals.snap | 7 + ...ring_nested_concatenation_string_spec.snap | 10 + ...ing__tests__parse_tstring_nested_spec.snap | 8 + ...sts__parse_tstring_nested_string_spec.snap | 9 + ...ring__tests__parse_tstring_not_equals.snap | 7 + ..._tests__parse_tstring_not_nested_spec.snap | 7 + ...ts__parse_tstring_self_doc_prec_space.snap | 5 + ...parse_tstring_self_doc_trailing_space.snap | 5 + ...ring__tests__parse_tstring_yield_expr.snap | 5 + ...ing__tests__parse_u_f_string_concat_1.snap | 5 + ...ing__tests__parse_u_f_string_concat_2.snap | 6 + ...tring__tests__parse_u_string_concat_1.snap | 5 +- ...tring__tests__parse_u_string_concat_2.snap | 5 +- ...ing__tests__parse_u_t_string_concat_1.snap | 5 + ...ing__tests__parse_u_t_string_concat_2.snap | 6 + ...er__string__tests__raw_byte_literal_1.snap | 4 +- ...er__string__tests__raw_byte_literal_2.snap | 4 +- ...on_parser__string__tests__raw_fstring.snap | 5 + ...on_parser__string__tests__raw_tstring.snap | 5 + ...er__string__tests__single_quoted_byte.snap | 4 +- ..._tests__string_parser_escaped_mac_eol.snap | 4 +- ...tests__string_parser_escaped_unix_eol.snap | 4 +- ...ts__string_parser_escaped_windows_eol.snap | 4 +- ...ing__tests__triple_quoted_raw_fstring.snap | 5 + ...ing__tests__triple_quoted_raw_tstring.snap | 5 + ...string__tests__tstring_constant_range.snap | 10 + ...ing__tests__tstring_escaped_character.snap | 6 + ...tring__tests__tstring_escaped_newline.snap | 6 + ...ing__tests__tstring_line_continuation.snap | 6 + ...__tstring_parse_self_documenting_base.snap | 5 + ...ring_parse_self_documenting_base_more.snap | 9 + ...tstring_parse_self_documenting_format.snap | 7 + ...ing__tests__tstring_unescaped_newline.snap | 6 + crates/ruff_python_parser/src/string.rs | 10 +- ...ann_assign_stmt_invalid_annotation.py.snap | 22 + ...tax@ann_assign_stmt_invalid_target.py.snap | 56 +- ...ntax@ann_assign_stmt_invalid_value.py.snap | 28 +- ...syntax@ann_assign_stmt_missing_rhs.py.snap | 5 +- ..._assign_stmt_type_alias_annotation.py.snap | 14 +- ...tax@args_unparenthesized_generator.py.snap | 41 + .../invalid_syntax@assert_empty_msg.py.snap | 4 +- .../invalid_syntax@assert_empty_test.py.snap | 4 +- ...lid_syntax@assert_invalid_msg_expr.py.snap | 19 +- ...id_syntax@assert_invalid_test_expr.py.snap | 16 +- ..._syntax@assign_stmt_invalid_target.py.snap | 26 +- ...tax@assign_stmt_invalid_value_expr.py.snap | 39 + ..._syntax@assign_stmt_keyword_target.py.snap | 19 +- ...lid_syntax@assign_stmt_missing_rhs.py.snap | 25 +- ...tax@assign_stmt_starred_expr_value.py.snap | 23 + ...alid_syntax@async_unexpected_token.py.snap | 26 +- ...tax@aug_assign_stmt_invalid_target.py.snap | 25 +- ...ntax@aug_assign_stmt_invalid_value.py.snap | 34 +- ...syntax@aug_assign_stmt_missing_rhs.py.snap | 16 +- ..._syntax@case_expect_indented_block.py.snap | 12 +- ...nvalid_syntax@class_def_empty_body.py.snap | 10 +- ...alid_syntax@class_def_missing_name.py.snap | 19 +- ...class_def_unclosed_type_param_list.py.snap | 16 +- ...lid_syntax@class_type_params_py311.py.snap | 23 + ...yntax@clause_expect_indented_block.py.snap | 7 +- ...tax@clause_expect_single_statement.py.snap | 7 +- ...ntax@comma_separated_missing_comma.py.snap | 9 +- ...ted_missing_comma_between_elements.py.snap | 7 +- ...ted_missing_element_between_commas.py.snap | 7 +- ...ma_separated_missing_first_element.py.snap | 7 +- ...prehension_missing_for_after_async.py.snap | 10 +- .../invalid_syntax@debug_shadow_class.py.snap | 12 + ...valid_syntax@debug_shadow_function.py.snap | 28 + ...invalid_syntax@debug_shadow_import.py.snap | 17 + .../invalid_syntax@debug_shadow_match.py.snap | 8 + .../invalid_syntax@debug_shadow_try.py.snap | 9 + ...lid_syntax@debug_shadow_type_alias.py.snap | 12 + .../invalid_syntax@debug_shadow_with.py.snap | 11 + ...ax@decorator_await_expression_py38.py.snap | 16 + ...syntax@decorator_dict_literal_py38.py.snap | 12 + ...d_syntax@decorator_expression_py38.py.snap | 16 + ...yntax@decorator_float_literal_py38.py.snap | 10 + ...yntax@decorator_invalid_expression.py.snap | 24 +- ...yntax@decorator_missing_expression.py.snap | 27 +- ...d_syntax@decorator_missing_newline.py.snap | 26 +- ...ax@decorator_named_expression_py37.py.snap | 22 + ..._non_toplevel_call_expression_py38.py.snap | 16 + ..._syntax@decorator_unexpected_token.py.snap | 10 +- .../invalid_syntax@del_debug_py39.py.snap | 3 + ...valid_syntax@del_incomplete_target.py.snap | 15 + .../invalid_syntax@del_stmt_empty.py.snap | 3 +- ...d_syntax@dotted_name_multiple_dots.py.snap | 12 +- ..._syntax@duplicate_match_class_attr.py.snap | 110 + ...invalid_syntax@duplicate_match_key.py.snap | 190 + ...tax@duplicate_type_parameter_names.py.snap | 93 + .../invalid_syntax@except_star_py310.py.snap | 16 + ...tax@except_stmt_invalid_expression.py.snap | 14 +- ...syntax@except_stmt_missing_as_name.py.snap | 10 +- ...ntax@except_stmt_missing_exception.py.snap | 16 +- ...stmt_missing_exception_and_as_name.py.snap | 6 +- ...cept_stmt_unparenthesized_tuple_as.py.snap | 17 + ..._unparenthesized_tuple_no_as_py313.py.snap | 15 + ...essions__arguments__double_starred.py.snap | 29 + ...ments__duplicate_keyword_arguments.py.snap | 21 +- ...ons__arguments__invalid_expression.py.snap | 27 + ...uments__invalid_keyword_expression.py.snap | 34 +- ...ressions__arguments__invalid_order.py.snap | 43 +- ...sions__arguments__missing_argument.py.snap | 8 +- ...ressions__arguments__missing_comma.py.snap | 8 +- ...ons__arguments__missing_expression.py.snap | 22 + ...ax@expressions__arguments__starred.py.snap | 26 +- ...expressions__arguments__unclosed_0.py.snap | 11 + ...expressions__arguments__unclosed_1.py.snap | 12 + ...expressions__arguments__unclosed_2.py.snap | 12 + ...essions__attribute__invalid_member.py.snap | 18 +- ...ressions__attribute__multiple_dots.py.snap | 22 +- ...@expressions__attribute__no_member.py.snap | 11 + ...xpressions__await__no_expression_0.py.snap | 8 + ...xpressions__await__no_expression_1.py.snap | 10 + ...syntax@expressions__await__recover.py.snap | 43 + ...ns__bin_op__invalid_rhs_expression.py.snap | 17 + ...x@expressions__bin_op__missing_lhs.py.snap | 7 + ...expressions__bin_op__missing_rhs_0.py.snap | 9 + ...expressions__bin_op__missing_rhs_1.py.snap | 13 + ...@expressions__bin_op__multiple_ops.py.snap | 20 +- ...ressions__bin_op__named_expression.py.snap | 16 +- ...ssions__bin_op__starred_expression.py.snap | 12 +- ...s__bool_op__invalid_rhs_expression.py.snap | 17 + ...@expressions__bool_op__missing_lhs.py.snap | 4 +- ...@expressions__bool_op__missing_rhs.py.snap | 9 + ...essions__bool_op__named_expression.py.snap | 14 +- ...sions__bool_op__starred_expression.py.snap | 12 +- ...xpressions__compare__invalid_order.py.snap | 18 + ...s__compare__invalid_rhs_expression.py.snap | 17 + ...@expressions__compare__missing_lhs.py.snap | 7 + ...xpressions__compare__missing_rhs_0.py.snap | 9 + ...xpressions__compare__missing_rhs_1.py.snap | 10 + ...xpressions__compare__missing_rhs_2.py.snap | 9 + ...ressions__compare__multiple_equals.py.snap | 12 +- ...essions__compare__named_expression.py.snap | 16 +- ...sions__compare__starred_expression.py.snap | 21 + ...x@expressions__dict__comprehension.py.snap | 114 + ...tax@expressions__dict__double_star.py.snap | 63 + ...s__dict__double_star_comprehension.py.snap | 12 + ...ons__dict__missing_closing_brace_0.py.snap | 9 + ...ons__dict__missing_closing_brace_1.py.snap | 7 + ...ons__dict__missing_closing_brace_2.py.snap | 11 + ...ressions__dict__named_expression_0.py.snap | 15 + ...ressions__dict__named_expression_1.py.snap | 15 + ..._syntax@expressions__dict__recover.py.snap | 52 + ...tax@expressions__emoji_identifiers.py.snap | 10 + ...yntax@expressions__emoji_statement.py.snap | 2 +- ...essions__if__missing_orelse_expr_0.py.snap | 12 + ...essions__if__missing_orelse_expr_1.py.snap | 10 + ...pressions__if__missing_test_expr_0.py.snap | 12 + ...pressions__if__missing_test_expr_1.py.snap | 10 + ...id_syntax@expressions__if__recover.py.snap | 49 + ...essions__lambda_default_parameters.py.snap | 18 +- ...sions__lambda_duplicate_parameters.py.snap | 60 + ...x@expressions__list__comprehension.py.snap | 109 + ...s__list__missing_closing_bracket_0.py.snap | 4 + ...s__list__missing_closing_bracket_1.py.snap | 6 + ...s__list__missing_closing_bracket_2.py.snap | 7 + ...s__list__missing_closing_bracket_3.py.snap | 11 + ..._syntax@expressions__list__recover.py.snap | 33 + ...__list__star_expression_precedence.py.snap | 60 + ...expressions__named__invalid_target.py.snap | 26 + ...sions__named__missing_expression_0.py.snap | 3 + ...sions__named__missing_expression_1.py.snap | 5 + ...sions__named__missing_expression_2.py.snap | 10 + ...sions__named__missing_expression_3.py.snap | 7 + ...sions__named__missing_expression_4.py.snap | 9 + ...ressions__parenthesized__generator.py.snap | 16 + ...nthesized__missing_closing_paren_0.py.snap | 3 + ...nthesized__missing_closing_paren_1.py.snap | 5 + ...nthesized__missing_closing_paren_2.py.snap | 7 + ...nthesized__missing_closing_paren_3.py.snap | 11 + ...ions__parenthesized__parenthesized.py.snap | 8 + ...@expressions__parenthesized__tuple.py.snap | 34 + ..._parenthesized__tuple_starred_expr.py.snap | 174 + ...ax@expressions__set__comprehension.py.snap | 109 + ...set__missing_closing_curly_brace_0.py.snap | 4 + ...set__missing_closing_curly_brace_1.py.snap | 6 + ...set__missing_closing_curly_brace_2.py.snap | 7 + ...set__missing_closing_curly_brace_3.py.snap | 11 + ...d_syntax@expressions__set__recover.py.snap | 33 + ...s__set__star_expression_precedence.py.snap | 60 + ...__subscript__invalid_slice_element.py.snap | 37 + ...sions__subscript__unclosed_slice_0.py.snap | 8 + ...sions__subscript__unclosed_slice_1.py.snap | 11 + .../invalid_syntax@expressions__unary.py.snap | 7 +- ...pressions__unary__named_expression.py.snap | 12 +- ...xpressions__unary__no_expression_0.py.snap | 8 + ...xpressions__unary__no_expression_1.py.snap | 8 + ...pressions__yield__named_expression.py.snap | 13 + ...xpressions__yield__star_expression.py.snap | 13 + ...ns__yield_from__starred_expression.py.snap | 11 + ...sions__yield_from__unparenthesized.py.snap | 19 + ...d_syntax@f_string_empty_expression.py.snap | 11 + ...g_invalid_conversion_flag_name_tok.py.snap | 6 + ..._invalid_conversion_flag_other_tok.py.snap | 11 + ...ntax@f_string_invalid_starred_expr.py.snap | 22 + ..._string_lambda_without_parentheses.py.snap | 14 + ...id_syntax@f_string_unclosed_lbrace.py.snap | 24 + ...ing_unclosed_lbrace_in_format_spec.py.snap | 16 + ...nvalid_syntax@for_iter_unpack_py38.py.snap | 26 + ..._syntax@for_stmt_invalid_iter_expr.py.snap | 21 + ...lid_syntax@for_stmt_invalid_target.py.snap | 54 + ...or_stmt_invalid_target_binary_expr.py.snap | 42 +- ...for_stmt_invalid_target_in_keyword.py.snap | 55 +- ...syntax@for_stmt_missing_in_keyword.py.snap | 12 +- ...valid_syntax@for_stmt_missing_iter.py.snap | 8 +- ...lid_syntax@for_stmt_missing_target.py.snap | 7 +- ...id_syntax@from_import_dotted_names.py.snap | 28 +- ...lid_syntax@from_import_empty_names.py.snap | 8 +- ..._syntax@from_import_missing_module.py.snap | 6 +- ...id_syntax@from_import_missing_rpar.py.snap | 22 +- ...@from_import_star_with_other_names.py.snap | 31 +- ...ort_unparenthesized_trailing_comma.py.snap | 17 +- ...lid_syntax@function_def_empty_body.py.snap | 16 +- ...x@function_def_invalid_return_expr.py.snap | 28 + ...ax@function_def_missing_identifier.py.snap | 17 +- ...x@function_def_missing_return_type.py.snap | 9 +- ...nction_def_unclosed_parameter_list.py.snap | 37 + ...ction_def_unclosed_type_param_list.py.snap | 25 +- ...n_def_unparenthesized_return_types.py.snap | 21 +- ..._syntax@function_type_params_py311.py.snap | 19 + .../invalid_syntax@global_stmt_empty.py.snap | 3 +- ...alid_syntax@global_stmt_expression.py.snap | 7 +- ..._syntax@global_stmt_trailing_comma.py.snap | 8 +- ..._syntax@if_stmt_elif_missing_colon.py.snap | 10 +- .../invalid_syntax@if_stmt_empty_body.py.snap | 8 +- ...tax@if_stmt_invalid_elif_test_expr.py.snap | 13 +- ...d_syntax@if_stmt_invalid_test_expr.py.snap | 17 +- ...valid_syntax@if_stmt_missing_colon.py.snap | 10 +- ...nvalid_syntax@if_stmt_missing_test.py.snap | 6 +- ...lid_syntax@if_stmt_misspelled_elif.py.snap | 9 + ...y_concatenated_unterminated_string.py.snap | 19 + ...ated_unterminated_string_multiline.py.snap | 23 + ...syntax@import_alias_missing_asname.py.snap | 5 +- .../invalid_syntax@import_stmt_empty.py.snap | 3 +- ...ax@import_stmt_parenthesized_names.py.snap | 10 +- ...lid_syntax@import_stmt_star_import.py.snap | 14 +- ..._syntax@import_stmt_trailing_comma.py.snap | 8 +- ...id_syntax@invalid_annotation_class.py.snap | 70 + ...syntax@invalid_annotation_function.py.snap | 259 + ...@invalid_annotation_function_py314.py.snap | 97 + ...id_syntax@invalid_annotation_py314.py.snap | 28 + ...ntax@invalid_annotation_type_alias.py.snap | 54 + ...nvalid_syntax@invalid_byte_literal.py.snap | 11 +- .../invalid_syntax@invalid_del_target.py.snap | 25 +- ...ax@invalid_fstring_literal_element.py.snap | 9 + ...alid_syntax@invalid_string_literal.py.snap | 8 +- ...id_syntax@irrefutable_case_pattern.py.snap | 55 + ...lid_syntax@iter_unpack_return_py37.py.snap | 19 + ...alid_syntax@iter_unpack_yield_py37.py.snap | 35 + ...ntax@lambda_body_with_starred_expr.py.snap | 47 +- ...syntax@lambda_body_with_yield_expr.py.snap | 22 +- .../invalid_syntax@match_before_py310.py.snap | 7 + ...d_syntax@match_classify_as_keyword.py.snap | 9 +- ..._classify_as_keyword_or_identifier.py.snap | 9 +- ...nvalid_syntax@match_expected_colon.py.snap | 10 +- ...x@match_stmt_expect_indented_block.py.snap | 7 + ...tax@match_stmt_expected_case_block.py.snap | 14 + ...ntax@match_stmt_invalid_guard_expr.py.snap | 29 +- ...ax@match_stmt_invalid_subject_expr.py.snap | 27 +- ...ntax@match_stmt_missing_guard_expr.py.snap | 9 +- ..._syntax@match_stmt_missing_pattern.py.snap | 9 +- ...@match_stmt_no_newline_before_case.py.snap | 7 + ...@match_stmt_single_starred_subject.py.snap | 9 +- ...mixed_bytes_and_non_bytes_literals.py.snap | 16 + ...ultiple_assignment_in_case_pattern.py.snap | 104 + ...ntax@multiple_clauses_on_same_line.py.snap | 42 +- .../invalid_syntax@named_expr_slice.py.snap | 27 + ...yntax@named_expr_slice_parse_error.py.snap | 10 + ...x@nested_async_comprehension_py310.py.snap | 125 + ...nvalid_syntax@node_range_with_gaps.py.snap | 18 + ...nlocal_declaration_at_module_level.py.snap | 6 + ...invalid_syntax@nonlocal_stmt_empty.py.snap | 7 + ...id_syntax@nonlocal_stmt_expression.py.snap | 11 + ...yntax@nonlocal_stmt_trailing_comma.py.snap | 12 + ...id_syntax@param_missing_annotation.py.snap | 22 +- ...valid_syntax@param_missing_default.py.snap | 23 +- ...ntax@param_with_invalid_annotation.py.snap | 39 + ..._syntax@param_with_invalid_default.py.snap | 38 +- ...param_with_invalid_star_annotation.py.snap | 50 + ...x@param_with_star_annotation_py310.py.snap | 12 + ...alid_syntax@params_duplicate_names.py.snap | 26 + ...rams_expected_after_star_separator.py.snap | 45 +- ...@params_kwarg_after_star_separator.py.snap | 11 +- ...alid_syntax@params_multiple_kwargs.py.snap | 14 +- ...ax@params_multiple_slash_separator.py.snap | 31 +- ...tax@params_multiple_star_separator.py.snap | 31 +- ...lid_syntax@params_multiple_varargs.py.snap | 50 +- ..._syntax@params_no_arg_before_slash.py.snap | 19 +- ...x@params_non_default_after_default.py.snap | 20 +- ...lid_syntax@params_star_after_slash.py.snap | 58 +- ...ms_star_separator_after_star_param.py.snap | 35 +- ...ax@params_var_keyword_with_default.py.snap | 22 +- ...params_var_positional_with_default.py.snap | 18 +- ...parenthesized_context_manager_py38.py.snap | 26 + ...id_syntax@parenthesized_kwarg_py38.py.snap | 22 + ...valid_syntax@pep701_f_string_py311.py.snap | 95 + .../invalid_syntax@pos_only_py37.py.snap | 49 + ...syntax@raise_stmt_from_without_exc.py.snap | 5 + ...id_syntax@raise_stmt_invalid_cause.py.snap | 15 +- ...alid_syntax@raise_stmt_invalid_exc.py.snap | 12 +- ...e_stmt_unparenthesized_tuple_cause.py.snap | 11 +- ...ise_stmt_unparenthesized_tuple_exc.py.snap | 14 +- ...nvalid_syntax@re_lex_logical_token.py.snap | 114 + ...yntax@re_lex_logical_token_mac_eol.py.snap | 15 + ...x@re_lex_logical_token_windows_eol.py.snap | 15 + ...x@re_lexing__fstring_format_spec_1.py.snap | 39 + ...tax@re_lexing__line_continuation_1.py.snap | 13 + ...ing__line_continuation_windows_eol.py.snap | 13 + ...re_lexing__triple_quoted_fstring_1.py.snap | 7 + ...re_lexing__triple_quoted_fstring_2.py.snap | 8 + ...re_lexing__triple_quoted_fstring_3.py.snap | 12 + ...tax@rebound_comprehension_variable.py.snap | 125 + ...id_syntax@return_stmt_invalid_expr.py.snap | 19 + ...ple_and_compound_stmt_on_same_line.py.snap | 9 +- ...ompound_stmt_on_same_line_in_block.py.snap | 14 +- ...d_syntax@simple_stmts_on_same_line.py.snap | 19 +- ...simple_stmts_on_same_line_in_block.py.snap | 9 +- .../invalid_syntax@single_star_for.py.snap | 13 + .../invalid_syntax@single_star_return.py.snap | 9 + .../invalid_syntax@single_star_yield.py.snap | 10 + ...x@single_starred_assignment_target.py.snap | 6 + .../invalid_syntax@star_index_py310.py.snap | 51 + .../invalid_syntax@star_slices.py.snap | 9 + ...atements__function_type_parameters.py.snap | 60 + ...ents__if_extra_closing_parentheses.py.snap | 4 + ...syntax@statements__if_extra_indent.py.snap | 12 + ...ements__invalid_assignment_targets.py.snap | 190 + ...nvalid_augmented_assignment_target.py.snap | 180 + ...ax@statements__match__as_pattern_0.py.snap | 13 +- ...ax@statements__match__as_pattern_1.py.snap | 10 +- ...ax@statements__match__as_pattern_2.py.snap | 13 + ...ax@statements__match__as_pattern_3.py.snap | 14 + ...ax@statements__match__as_pattern_4.py.snap | 13 +- ...ents__match__invalid_class_pattern.py.snap | 58 +- ..._match__invalid_lhs_or_rhs_pattern.py.snap | 143 +- ...ts__match__invalid_mapping_pattern.py.snap | 70 +- ...tements__match__star_pattern_usage.py.snap | 70 +- ...statements__match__unary_add_usage.py.snap | 57 +- ...s__with__ambiguous_lpar_with_items.py.snap | 208 + ...statements__with__empty_with_items.py.snap | 8 + ...nts__with__unclosed_ambiguous_lpar.py.snap | 8 + ..._with__unclosed_ambiguous_lpar_eof.py.snap | 4 + ...__with__unparenthesized_with_items.py.snap | 44 + ...d_syntax@t_string_empty_expression.py.snap | 11 + ...g_invalid_conversion_flag_name_tok.py.snap | 6 + ..._invalid_conversion_flag_other_tok.py.snap | 11 + ...ntax@t_string_invalid_starred_expr.py.snap | 22 + ..._string_lambda_without_parentheses.py.snap | 14 + ...id_syntax@t_string_unclosed_lbrace.py.snap | 24 + ...ing_unclosed_lbrace_in_format_spec.py.snap | 16 + ...alid_syntax@try_stmt_invalid_order.py.snap | 5 + ...ax@try_stmt_missing_except_finally.py.snap | 6 + ..._syntax@try_stmt_misspelled_except.py.snap | 22 + ..._syntax@try_stmt_mixed_except_kind.py.snap | 27 + ..._syntax@tuple_context_manager_py38.py.snap | 32 + ..._syntax@type_alias_incomplete_stmt.py.snap | 11 +- ...ntax@type_alias_invalid_value_expr.py.snap | 18 + ...id_syntax@type_param_default_py312.py.snap | 42 + ...ntax@type_param_invalid_bound_expr.py.snap | 34 + ...id_syntax@type_param_missing_bound.py.snap | 16 +- ...syntax@type_param_param_spec_bound.py.snap | 12 +- ...am_param_spec_invalid_default_expr.py.snap | 42 + ...e_param_param_spec_missing_default.py.snap | 16 +- ...aram_type_var_invalid_default_expr.py.snap | 51 + ...ype_param_type_var_missing_default.py.snap | 23 +- ...ax@type_param_type_var_tuple_bound.py.snap | 12 +- ...ype_var_tuple_invalid_default_expr.py.snap | 44 + ...ram_type_var_tuple_missing_default.py.snap | 16 +- .../invalid_syntax@type_params_empty.py.snap | 15 +- .../invalid_syntax@type_stmt_py311.py.snap | 4 + ...arenthesized_named_expr_index_py38.py.snap | 7 + ...nthesized_named_expr_set_comp_py38.py.snap | 12 + ...esized_named_expr_set_literal_py38.py.snap | 22 + ...erminated_fstring_newline_recovery.py.snap | 39 + .../invalid_syntax@walrus_py37.py.snap | 5 + ...yntax@while_stmt_invalid_test_expr.py.snap | 24 +- ...id_syntax@while_stmt_missing_colon.py.snap | 7 +- ...lid_syntax@while_stmt_missing_test.py.snap | 11 +- ..._items_parenthesized_missing_colon.py.snap | 8 +- ..._items_parenthesized_missing_comma.py.snap | 47 +- ...invalid_syntax@write_to_debug_expr.py.snap | 22 + ...ntax@all_async_comprehension_py310.py.snap | 22 + ...iguous_lpar_with_items_binary_expr.py.snap | 56 +- ...@ambiguous_lpar_with_items_if_expr.py.snap | 44 +- ...ntax@ann_assign_stmt_simple_target.py.snap | 18 +- ...tax@args_unparenthesized_generator.py.snap | 45 + ...tax@assign_stmt_starred_expr_value.py.snap | 20 + ...d_syntax@assign_targets_terminator.py.snap | 20 +- .../valid_syntax@async_for_statement.py.snap | 7 +- ...d_syntax@async_function_definition.py.snap | 9 +- .../valid_syntax@async_with_statement.py.snap | 7 +- .../valid_syntax@class_def_arguments.py.snap | 11 +- ...ntax@class_keyword_in_case_pattern.py.snap | 13 + ...lid_syntax@class_type_params_py312.py.snap | 18 + ..._separated_regular_list_terminator.py.snap | 23 +- .../valid_syntax@debug_rename_import.py.snap | 14 + ...id_syntax@decorator_async_function.py.snap | 11 +- ...ax@decorator_await_expression_py39.py.snap | 16 + ...rator_expression_dotted_ident_py38.py.snap | 14 + ...ecorator_expression_eval_hack_py38.py.snap | 14 + ...ator_expression_identity_hack_py38.py.snap | 29 + ...d_syntax@decorator_expression_py39.py.snap | 37 + .../valid_syntax@del_debug_py38.py.snap | 3 + ...alid_syntax@del_targets_terminator.py.snap | 16 +- ...ntax@dotted_name_normalized_spaces.py.snap | 8 +- ...id_syntax@duplicate_match_key_attr.py.snap | 17 + .../valid_syntax@except_star_py311.py.snap | 8 + ...x@except_stmt_as_name_soft_keyword.py.snap | 20 +- ..._unparenthesized_tuple_no_as_py314.py.snap | 15 + ...alid_syntax@expressions__arguments.py.snap | 297 + ...alid_syntax@expressions__attribute.py.snap | 42 +- .../valid_syntax@expressions__await.py.snap | 83 +- .../valid_syntax@expressions__bin_op.py.snap | 152 + .../valid_syntax@expressions__bool_op.py.snap | 58 +- .../valid_syntax@expressions__call.py.snap | 94 +- .../valid_syntax@expressions__compare.py.snap | 96 +- ...lid_syntax@expressions__dictionary.py.snap | 164 +- ...ressions__dictionary_comprehension.py.snap | 153 +- ...valid_syntax@expressions__f_string.py.snap | 329 + ...alid_syntax@expressions__generator.py.snap | 102 +- .../valid_syntax@expressions__if.py.snap | 101 +- .../valid_syntax@expressions__lambda.py.snap | 330 +- .../valid_syntax@expressions__list.py.snap | 116 +- ...ax@expressions__list_comprehension.py.snap | 200 +- .../valid_syntax@expressions__name.py.snap | 20 +- .../valid_syntax@expressions__named.py.snap | 53 +- ...syntax@expressions__number_literal.py.snap | 154 +- ..._syntax@expressions__parenthesized.py.snap | 41 +- .../valid_syntax@expressions__set.py.snap | 89 +- ...tax@expressions__set_comprehension.py.snap | 116 +- .../valid_syntax@expressions__slice.py.snap | 93 +- .../valid_syntax@expressions__starred.py.snap | 52 +- .../valid_syntax@expressions__string.py.snap | 34 +- ...alid_syntax@expressions__subscript.py.snap | 111 +- ...valid_syntax@expressions__t_string.py.snap | 378 ++ .../valid_syntax@expressions__tuple.py.snap | 103 +- ...valid_syntax@expressions__unary_op.py.snap | 79 +- .../valid_syntax@expressions__yield.py.snap | 67 + ...lid_syntax@expressions__yield_from.py.snap | 58 +- ...id_syntax@for_in_target_valid_expr.py.snap | 29 +- .../valid_syntax@for_iter_unpack_py38.py.snap | 26 + .../valid_syntax@for_iter_unpack_py39.py.snap | 26 + .../valid_syntax@from_import_no_space.py.snap | 8 +- ...om_import_soft_keyword_module_name.py.snap | 18 +- ...syntax@from_import_stmt_terminator.py.snap | 38 +- ...tax@fstring_format_spec_terminator.py.snap | 18 + ...yntax@function_def_parameter_range.py.snap | 18 +- ...ion_def_parenthesized_return_types.py.snap | 21 +- ...tax@function_def_valid_return_expr.py.snap | 37 + ..._syntax@function_type_params_py312.py.snap | 11 + .../valid_syntax@global_stmt.py.snap | 8 +- ...syntax@import_as_name_soft_keyword.py.snap | 14 +- ...alid_syntax@import_stmt_terminator.py.snap | 21 +- ...ax@irrefutable_case_pattern_at_end.py.snap | 37 + ...lid_syntax@iter_unpack_return_py37.py.snap | 19 + ...lid_syntax@iter_unpack_return_py38.py.snap | 19 + ...alid_syntax@iter_unpack_yield_py37.py.snap | 20 + ...alid_syntax@iter_unpack_yield_py38.py.snap | 35 + ...d_syntax@lambda_with_no_parameters.py.snap | 5 +- ...alid_syntax@lambda_with_valid_body.py.snap | 73 +- .../valid_syntax@match_after_py310.py.snap | 7 + .../valid_syntax@match_as_pattern.py.snap | 14 + ...ntax@match_as_pattern_soft_keyword.py.snap | 22 + ...ax@match_attr_pattern_soft_keyword.py.snap | 42 +- ...tax@match_classify_as_identifier_1.py.snap | 6 +- ...tax@match_classify_as_identifier_2.py.snap | 48 +- ...syntax@match_classify_as_keyword_1.py.snap | 92 + ...syntax@match_classify_as_keyword_2.py.snap | 38 +- ..._classify_as_keyword_or_identifier.py.snap | 44 +- ...nce_pattern_parentheses_terminator.py.snap | 20 +- ...@match_sequence_pattern_terminator.py.snap | 31 + ...lid_syntax@match_stmt_subject_expr.py.snap | 36 +- ...syntax@match_stmt_valid_guard_expr.py.snap | 47 +- ...ultiple_assignment_in_case_pattern.py.snap | 18 +- ...x@nested_async_comprehension_py310.py.snap | 45 + ...x@nested_async_comprehension_py311.py.snap | 65 + ...non_duplicate_type_parameter_names.py.snap | 62 + ...non_rebound_comprehension_variable.py.snap | 12 + ...nlocal_declaration_at_module_level.py.snap | 8 + .../valid_syntax@nonlocal_stmt.py.snap | 12 + .../valid_syntax@other__atom.py.snap | 10 +- .../valid_syntax@other__decorator.py.snap | 128 +- ...valid_syntax@param_with_annotation.py.snap | 30 + .../valid_syntax@param_with_default.py.snap | 58 +- ..._syntax@param_with_star_annotation.py.snap | 28 +- ...x@param_with_star_annotation_py310.py.snap | 70 + ...x@param_with_star_annotation_py311.py.snap | 12 + ...ntax@params_non_default_after_star.py.snap | 46 +- ...seen_keyword_only_param_after_star.py.snap | 27 +- ...parenthesized_context_manager_py39.py.snap | 26 + ...id_syntax@parenthesized_kwarg_py37.py.snap | 8 + ...arenthesized_named_expr_index_py38.py.snap | 7 + ...ntax@parenthesized_named_expr_py38.py.snap | 19 + ...tax@parenthesized_star_index_py310.py.snap | 21 +- ...valid_syntax@pep701_f_string_py311.py.snap | 64 + ...valid_syntax@pep701_f_string_py312.py.snap | 70 + ...valid_syntax@pep750_t_string_py314.py.snap | 70 + .../valid_syntax@pos_only_py38.py.snap | 11 + .../valid_syntax@read_from_debug.py.snap | 8 + ...valid_syntax@simple_stmts_in_block.py.snap | 19 +- ...yntax@simple_stmts_with_semicolons.py.snap | 15 +- .../valid_syntax@single_star_in_tuple.py.snap | 34 + ...x@single_starred_assignment_target.py.snap | 19 + .../valid_syntax@star_index_py311.py.snap | 51 + ...atement__ambiguous_lpar_with_items.py.snap | 619 ++ ...ax@statement__annotated_assignment.py.snap | 41 +- .../valid_syntax@statement__assert.py.snap | 55 +- ...valid_syntax@statement__assignment.py.snap | 125 + ...ax@statement__augmented_assignment.py.snap | 69 +- .../valid_syntax@statement__class.py.snap | 209 +- .../valid_syntax@statement__delete.py.snap | 43 +- .../valid_syntax@statement__for.py.snap | 93 + ...alid_syntax@statement__from_import.py.snap | 44 +- .../valid_syntax@statement__function.py.snap | 674 +- .../valid_syntax@statement__if.py.snap | 111 +- .../valid_syntax@statement__import.py.snap | 26 +- .../valid_syntax@statement__match.py.snap | 1184 ++++ .../valid_syntax@statement__raise.py.snap | 72 +- .../valid_syntax@statement__return.py.snap | 50 + .../valid_syntax@statement__simple.py.snap | 39 +- .../valid_syntax@statement__try.py.snap | 229 + .../valid_syntax@statement__type.py.snap | 430 +- .../valid_syntax@statement__while.py.snap | 73 +- .../valid_syntax@statement__with.py.snap | 64 +- ..._syntax@tuple_context_manager_py38.py.snap | 10 + ...id_syntax@type_param_default_py313.py.snap | 28 + ...valid_syntax@type_param_param_spec.py.snap | 32 +- .../valid_syntax@type_param_type_var.py.snap | 47 +- ...d_syntax@type_param_type_var_tuple.py.snap | 40 +- .../valid_syntax@type_stmt_py312.py.snap | 4 + ...arenthesized_named_expr_index_py39.py.snap | 7 + ...ax@unparenthesized_named_expr_py39.py.snap | 19 + ...alid_syntax@valid_annotation_class.py.snap | 40 + ...ax@valid_annotation_function_py313.py.snap | 97 + ...alid_syntax@valid_annotation_py313.py.snap | 28 + .../valid_syntax@walrus_py38.py.snap | 5 + .../src/analyze/function_type.rs | 7 +- .../src/analyze/imports.rs | 13 +- .../src/analyze/typing.rs | 2 + crates/ruff_python_semantic/src/globals.rs | 6 +- crates/ruff_python_semantic/src/imports.rs | 7 +- crates/ruff_python_semantic/src/model/all.rs | 7 +- crates/ty_project/src/lib.rs | 117 +- crates/ty_python_semantic/src/ast_node_ref.rs | 119 +- crates/ty_python_semantic/src/module_name.rs | 1 + crates/ty_python_semantic/src/node_key.rs | 17 +- .../src/semantic_index/builder.rs | 57 +- .../src/semantic_index/definition.rs | 65 +- .../src/semantic_index/place.rs | 76 +- .../src/semantic_index/re_exports.rs | 22 +- crates/ty_python_semantic/src/types/infer.rs | 130 +- crates/ty_python_semantic/src/types/narrow.rs | 3 + .../src/types/signatures.rs | 1 + 824 files changed, 26966 insertions(+), 2527 deletions(-) create mode 100644 crates/ruff_python_ast/src/node_index.rs diff --git a/Cargo.lock b/Cargo.lock index ec1b10d2681641..03aa31a04bc2a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,12 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "argfile" version = "0.2.1" @@ -2612,6 +2618,7 @@ name = "ruff_db" version = "0.0.0" dependencies = [ "anstyle", + "arc-swap", "camino", "countme", "dashmap 6.1.0", diff --git a/Cargo.toml b/Cargo.toml index 4f55b171b9f3d0..1da4d9bdff9133 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ aho-corasick = { version = "1.1.3" } anstream = { version = "0.6.18" } anstyle = { version = "1.0.10" } anyhow = { version = "1.0.80" } +arc-swap = { version = "1.7.1" } assert_fs = { version = "1.1.0" } argfile = { version = "0.2.0" } bincode = { version = "2.0.0" } diff --git a/crates/ruff_db/Cargo.toml b/crates/ruff_db/Cargo.toml index f36a49810c9682..e694031591ab31 100644 --- a/crates/ruff_db/Cargo.toml +++ b/crates/ruff_db/Cargo.toml @@ -21,6 +21,7 @@ ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } anstyle = { workspace = true } +arc-swap = { workspace = true } camino = { workspace = true } countme = { workspace = true } dashmap = { workspace = true } diff --git a/crates/ruff_db/src/parsed.rs b/crates/ruff_db/src/parsed.rs index a47971392411e7..f28d280f7ed16d 100644 --- a/crates/ruff_db/src/parsed.rs +++ b/crates/ruff_db/src/parsed.rs @@ -1,7 +1,8 @@ use std::fmt::Formatter; use std::sync::Arc; -use ruff_python_ast::ModModule; +use arc_swap::ArcSwapOption; +use ruff_python_ast::{AnyRootNodeRef, ModModule, NodeIndex}; use ruff_python_parser::{ParseOptions, Parsed, parse_unchecked}; use crate::Db; @@ -23,16 +24,20 @@ use crate::source::source_text; pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { let _span = tracing::trace_span!("parsed_module", ?file).entered(); + let parsed = parsed_module_impl(db, file); + + ParsedModule::new(file, parsed) +} + +pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed { let source = source_text(db, file); let ty = file.source_type(db); let target_version = db.python_version(); let options = ParseOptions::from(ty).with_target_version(target_version); - let parsed = parse_unchecked(&source, options) + parse_unchecked(&source, options) .try_into_module() - .expect("PySourceType always parses into a module"); - - ParsedModule::new(parsed) + .expect("PySourceType always parses into a module") } /// A wrapper around a parsed module. @@ -41,22 +46,59 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { /// is represented with the [`ParsedModuleRef`] type. #[derive(Clone)] pub struct ParsedModule { - inner: Arc>, + file: File, + inner: Arc>, } impl ParsedModule { - pub fn new(parsed: Parsed) -> Self { + pub fn new(file: File, parsed: Parsed) -> Self { Self { - inner: Arc::new(parsed), + file, + inner: Arc::new(ArcSwapOption::new(Some(indexed::IndexedModule::new( + parsed, + )))), } } - /// Loads a reference to the parsed module. - pub fn load(&self, _db: &dyn Db) -> ParsedModuleRef { + /// + /// Note that holding on to the reference will prevent garbage collection + /// of the AST. This method will reparse the module if it has been collected. + pub fn load(&self, db: &dyn Db) -> ParsedModuleRef { + let parsed = match self.inner.load_full() { + Some(parsed) => parsed, + None => { + // Re-parse the file. + let parsed = indexed::IndexedModule::new(parsed_module_impl(db, self.file)); + tracing::debug!( + "File `{}` was reparsed after being collected in the current Salsa revision", + self.file.path(db) + ); + + self.inner.store(Some(parsed.clone())); + parsed + } + }; + ParsedModuleRef { - module_ref: self.inner.clone(), + module: self.clone(), + indexed: parsed, } } + + /// Clear the parsed module, dropping the AST once all references to it are dropped. + pub fn clear(&self) { + self.inner.store(None); + } + + /// Returns a pointer for this [`ParsedModule`]. + /// + /// The pointer uniquely identifies the module within the current Salsa revision, + /// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected. + pub fn as_ptr(&self) -> *const () { + // Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner + // `Arc` within the `ArcSwap` may change. + Arc::as_ptr(&self.inner).cast() + } } impl std::fmt::Debug for ParsedModule { @@ -76,16 +118,19 @@ impl Eq for ParsedModule {} /// Cheap cloneable wrapper around an instance of a module AST. #[derive(Clone)] pub struct ParsedModuleRef { - module_ref: Arc>, + module: ParsedModule, + indexed: Arc, } impl ParsedModuleRef { - pub fn as_arc(&self) -> &Arc> { - &self.module_ref + /// Returns a reference to the [`ParsedModule`] that this instance was loaded from. + pub fn module(&self) -> &ParsedModule { + &self.module } - pub fn into_arc(self) -> Arc> { - self.module_ref + /// Returns a reference to the AST node at the given index. + pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> { + self.indexed.get_by_index(index) } } @@ -93,7 +138,247 @@ impl std::ops::Deref for ParsedModuleRef { type Target = Parsed; fn deref(&self) -> &Self::Target { - &self.module_ref + &self.indexed.parsed + } +} + +mod indexed { + use std::sync::Arc; + + use ruff_python_ast::visitor::source_order::*; + use ruff_python_ast::*; + use ruff_python_parser::Parsed; + + /// A wrapper around the AST that allows access to AST nodes by index. + #[derive(Debug)] + pub struct IndexedModule { + index: Box<[AnyRootNodeRef<'static>]>, + pub parsed: Parsed, + } + + impl IndexedModule { + /// Create a new [`IndexedModule`] from the given AST. + #[allow(clippy::unnecessary_cast)] + pub fn new(parsed: Parsed) -> Arc { + let mut visitor = Visitor { + nodes: Vec::new(), + index: 0, + }; + + let mut inner = Arc::new(IndexedModule { + parsed, + index: Box::new([]), + }); + + AnyNodeRef::from(inner.parsed.syntax()).visit_source_order(&mut visitor); + + let index: Box<[AnyRootNodeRef<'_>]> = visitor.nodes.into_boxed_slice(); + + // SAFETY: We cast from `Box<[AnyRootNodeRef<'_>]>` to `Box<[AnyRootNodeRef<'static>]>`, + // faking the 'static lifetime to create the self-referential struct. The node references + // are into the `Arc>`, so are valid for as long as the `IndexedModule` + // is alive. We make sure to restore the correct lifetime in `get_by_index`. + // + // Note that we can never move the data within the `Arc` after this point. + Arc::get_mut(&mut inner).unwrap().index = + unsafe { Box::from_raw(Box::into_raw(index) as *mut [AnyRootNodeRef<'static>]) }; + + inner + } + + /// Returns the node at the given index. + pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> { + // Note that this method restores the correct lifetime: the nodes are valid for as + // long as the reference to `IndexedModule` is alive. + self.index[index.as_usize()] + } + } + + /// A visitor that collects nodes in source order. + pub struct Visitor<'a> { + pub index: u32, + pub nodes: Vec>, + } + + impl<'a> Visitor<'a> { + fn visit_node(&mut self, node: &'a T) + where + T: HasNodeIndex + std::fmt::Debug, + AnyRootNodeRef<'a>: From<&'a T>, + { + node.node_index().set(self.index); + self.nodes.push(AnyRootNodeRef::from(node)); + self.index += 1; + } + } + + impl<'a> SourceOrderVisitor<'a> for Visitor<'a> { + #[inline] + fn visit_mod(&mut self, module: &'a Mod) { + self.visit_node(module); + walk_module(self, module); + } + + #[inline] + fn visit_stmt(&mut self, stmt: &'a Stmt) { + self.visit_node(stmt); + walk_stmt(self, stmt); + } + + #[inline] + fn visit_annotation(&mut self, expr: &'a Expr) { + self.visit_node(expr); + walk_annotation(self, expr); + } + + #[inline] + fn visit_expr(&mut self, expr: &'a Expr) { + self.visit_node(expr); + walk_expr(self, expr); + } + + #[inline] + fn visit_decorator(&mut self, decorator: &'a Decorator) { + self.visit_node(decorator); + walk_decorator(self, decorator); + } + + #[inline] + fn visit_comprehension(&mut self, comprehension: &'a Comprehension) { + self.visit_node(comprehension); + walk_comprehension(self, comprehension); + } + + #[inline] + fn visit_except_handler(&mut self, except_handler: &'a ExceptHandler) { + self.visit_node(except_handler); + walk_except_handler(self, except_handler); + } + + #[inline] + fn visit_arguments(&mut self, arguments: &'a Arguments) { + self.visit_node(arguments); + walk_arguments(self, arguments); + } + + #[inline] + fn visit_parameters(&mut self, parameters: &'a Parameters) { + self.visit_node(parameters); + walk_parameters(self, parameters); + } + + #[inline] + fn visit_parameter(&mut self, arg: &'a Parameter) { + self.visit_node(arg); + walk_parameter(self, arg); + } + + fn visit_parameter_with_default( + &mut self, + parameter_with_default: &'a ParameterWithDefault, + ) { + self.visit_node(parameter_with_default); + walk_parameter_with_default(self, parameter_with_default); + } + + #[inline] + fn visit_keyword(&mut self, keyword: &'a Keyword) { + self.visit_node(keyword); + walk_keyword(self, keyword); + } + + #[inline] + fn visit_alias(&mut self, alias: &'a Alias) { + self.visit_node(alias); + walk_alias(self, alias); + } + + #[inline] + fn visit_with_item(&mut self, with_item: &'a WithItem) { + self.visit_node(with_item); + walk_with_item(self, with_item); + } + + #[inline] + fn visit_type_params(&mut self, type_params: &'a TypeParams) { + self.visit_node(type_params); + walk_type_params(self, type_params); + } + + #[inline] + fn visit_type_param(&mut self, type_param: &'a TypeParam) { + self.visit_node(type_param); + walk_type_param(self, type_param); + } + + #[inline] + fn visit_match_case(&mut self, match_case: &'a MatchCase) { + self.visit_node(match_case); + walk_match_case(self, match_case); + } + + #[inline] + fn visit_pattern(&mut self, pattern: &'a Pattern) { + self.visit_node(pattern); + walk_pattern(self, pattern); + } + + #[inline] + fn visit_pattern_arguments(&mut self, pattern_arguments: &'a PatternArguments) { + self.visit_node(pattern_arguments); + walk_pattern_arguments(self, pattern_arguments); + } + + #[inline] + fn visit_pattern_keyword(&mut self, pattern_keyword: &'a PatternKeyword) { + self.visit_node(pattern_keyword); + walk_pattern_keyword(self, pattern_keyword); + } + + #[inline] + fn visit_elif_else_clause(&mut self, elif_else_clause: &'a ElifElseClause) { + self.visit_node(elif_else_clause); + walk_elif_else_clause(self, elif_else_clause); + } + + #[inline] + fn visit_f_string(&mut self, f_string: &'a FString) { + self.visit_node(f_string); + walk_f_string(self, f_string); + } + + #[inline] + fn visit_interpolated_string_element( + &mut self, + interpolated_string_element: &'a InterpolatedStringElement, + ) { + self.visit_node(interpolated_string_element); + walk_interpolated_string_element(self, interpolated_string_element); + } + + #[inline] + fn visit_t_string(&mut self, t_string: &'a TString) { + self.visit_node(t_string); + walk_t_string(self, t_string); + } + + #[inline] + fn visit_string_literal(&mut self, string_literal: &'a StringLiteral) { + self.visit_node(string_literal); + walk_string_literal(self, string_literal); + } + + #[inline] + fn visit_bytes_literal(&mut self, bytes_literal: &'a BytesLiteral) { + self.visit_node(bytes_literal); + walk_bytes_literal(self, bytes_literal); + } + + #[inline] + fn visit_identifier(&mut self, identifier: &'a Identifier) { + self.visit_node(identifier); + walk_identifier(self, identifier); + } } } diff --git a/crates/ruff_graph/src/collector.rs b/crates/ruff_graph/src/collector.rs index 0aad63fbe63aef..73b37991deaab7 100644 --- a/crates/ruff_graph/src/collector.rs +++ b/crates/ruff_graph/src/collector.rs @@ -39,6 +39,7 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> { module, level, range: _, + node_index: _, }) => { let module = module.as_deref(); let level = *level; @@ -78,7 +79,11 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> { } } } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { for alias in names { if let Some(module_name) = ModuleName::new(alias.name.as_str()) { self.imports.push(CollectedImport::Import(module_name)); @@ -122,7 +127,12 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> { fn visit_expr(&mut self, expr: &'ast Expr) { if self.string_imports { - if let Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) = expr { + if let Expr::StringLiteral(ast::ExprStringLiteral { + value, + range: _, + node_index: _, + }) = expr + { // Determine whether the string literal "looks like" an import statement: contains // a dot, and consists solely of valid Python identifiers. let value = value.to_str(); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs b/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs index 7ef76d938274e8..3a7e3a4f5b9b90 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/except_handler.rs @@ -15,6 +15,7 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker) name, body, range: _, + node_index: _, }) => { if checker.enabled(Rule::BareExcept) { pycodestyle::rules::bare_except(checker, type_.as_deref(), body, except_handler); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index e29b4db602863f..f695c198ab482f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -185,12 +185,14 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { elts, ctx, range: _, + node_index: _, parenthesized: _, }) | Expr::List(ast::ExprList { elts, ctx, range: _, + node_index: _, }) => { if ctx.is_store() { let check_too_many_expressions = checker.enabled(Rule::ExpressionsInStarAssignment); @@ -205,7 +207,12 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { ); } } - Expr::Name(ast::ExprName { id, ctx, range }) => { + Expr::Name(ast::ExprName { + id, + ctx, + range, + node_index: _, + }) => { match ctx { ExprContext::Load => { if checker.enabled(Rule::TypingTextStrAlias) { @@ -472,8 +479,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }, ) => { if checker.any_enabled(&[ @@ -1261,6 +1270,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { op: Operator::Mod, right, range: _, + node_index: _, }, ) => { if let Expr::StringLiteral(format_string @ ast::ExprStringLiteral { value, .. }) = @@ -1426,6 +1436,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { op, operand, range: _, + node_index: _, }, ) => { if checker.any_enabled(&[Rule::NotInTest, Rule::NotIsTest]) { @@ -1452,6 +1463,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { ops, comparators, range: _, + node_index: _, }, ) => { if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) { @@ -1530,7 +1542,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { refurb::rules::math_constant(checker, number_literal); } } - Expr::StringLiteral(string_like @ ast::ExprStringLiteral { value, range: _ }) => { + Expr::StringLiteral( + string_like @ ast::ExprStringLiteral { + value, + range: _, + node_index: _, + }, + ) => { if checker.enabled(Rule::UnicodeKindPrefix) { for string_part in value { pyupgrade::rules::unicode_kind_prefix(checker, string_part); @@ -1551,6 +1569,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { body, orelse, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::IfElseBlockInsteadOfDictGet) { @@ -1585,6 +1604,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { elt, generators, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::UnnecessaryListIndexLookup) { @@ -1615,6 +1635,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { elt, generators, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::UnnecessaryListIndexLookup) { @@ -1646,6 +1667,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { value, generators, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::UnnecessaryListIndexLookup) { @@ -1684,6 +1706,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { generators, elt: _, range: _, + node_index: _, parenthesized: _, }, ) => { diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 69f7dfd8d8dc4f..4effbce61d88aa 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -18,7 +18,11 @@ use ruff_python_ast::PythonVersion; /// Run lint rules over a [`Stmt`] syntax node. pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { match stmt { - Stmt::Global(ast::StmtGlobal { names, range: _ }) => { + Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) => { if checker.enabled(Rule::GlobalAtModuleLevel) { pylint::rules::global_at_module_level(checker, stmt); } @@ -28,7 +32,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } } - Stmt::Nonlocal(nonlocal @ ast::StmtNonlocal { names, range: _ }) => { + Stmt::Nonlocal( + nonlocal @ ast::StmtNonlocal { + names, + range: _, + node_index: _, + }, + ) => { if checker.enabled(Rule::AmbiguousVariableName) { for name in names { pycodestyle::rules::ambiguous_variable_name(checker, name, name.range()); @@ -80,6 +90,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { body, type_params: _, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) { @@ -381,6 +392,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { decorator_list, body, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::NoClassmethodDecorator) { @@ -542,7 +554,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { ruff::rules::implicit_class_var_in_dataclass(checker, class_def); } } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { if checker.enabled(Rule::MultipleImportsOnOneLine) { pycodestyle::rules::multiple_imports_on_one_line(checker, stmt, names); } @@ -699,6 +715,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { module, level, range: _, + node_index: _, }, ) => { let level = *level; @@ -1142,6 +1159,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { test, msg, range: _, + node_index: _, }, ) => { if !checker.semantic.in_type_checking_block() { @@ -1243,6 +1261,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { orelse, is_async, range: _, + node_index: _, }, ) => { if checker.enabled(Rule::TooManyNestedBlocks) { @@ -1606,7 +1625,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { flake8_pyi::rules::t_suffixed_type_alias(checker, name); } } - Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => { + Stmt::Delete( + delete @ ast::StmtDelete { + targets, + range: _, + node_index: _, + }, + ) => { if checker.enabled(Rule::GlobalStatement) { for target in targets { if let Expr::Name(ast::ExprName { id, .. }) = target { @@ -1618,7 +1643,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { refurb::rules::delete_full_slice(checker, delete); } } - Stmt::Expr(expr @ ast::StmtExpr { value, range: _ }) => { + Stmt::Expr( + expr @ ast::StmtExpr { + value, + range: _, + node_index: _, + }, + ) => { if checker.enabled(Rule::UselessComparison) { flake8_bugbear::rules::useless_comparison(checker, value); } @@ -1645,6 +1676,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { subject: _, cases, range: _, + node_index: _, }) => { if checker.enabled(Rule::NanComparison) { pylint::rules::nan_comparison_match(checker, cases); diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 3ca0b6ed5bf091..7a3addd4da6f78 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -836,10 +836,15 @@ impl<'a> Visitor<'a> for Checker<'a> { op: _, value: _, range: _, + node_index: _, }) => { self.handle_node_load(target); } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { if self.semantic.at_top_level() { self.importer.visit_import(stmt); } @@ -893,6 +898,7 @@ impl<'a> Visitor<'a> for Checker<'a> { module, level, range: _, + node_index: _, }) => { if self.semantic.at_top_level() { self.importer.visit_import(stmt); @@ -954,7 +960,11 @@ impl<'a> Visitor<'a> for Checker<'a> { } } } - Stmt::Global(ast::StmtGlobal { names, range: _ }) => { + Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) => { if !self.semantic.scope_id.is_global() { for name in names { let binding_id = self.semantic.global_scope().get(name); @@ -976,7 +986,11 @@ impl<'a> Visitor<'a> for Checker<'a> { } } } - Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => { + Stmt::Nonlocal(ast::StmtNonlocal { + names, + range: _, + node_index: _, + }) => { if !self.semantic.scope_id.is_global() { for name in names { if let Some((scope_id, binding_id)) = self.semantic.nonlocal(name) { @@ -1186,6 +1200,7 @@ impl<'a> Visitor<'a> for Checker<'a> { } Stmt::TypeAlias(ast::StmtTypeAlias { range: _, + node_index: _, name, type_params, value, @@ -1280,6 +1295,7 @@ impl<'a> Visitor<'a> for Checker<'a> { test, msg, range: _, + node_index: _, }) => { let snapshot = self.semantic.flags; self.semantic.flags |= SemanticModelFlags::ASSERT_STATEMENT; @@ -1294,6 +1310,7 @@ impl<'a> Visitor<'a> for Checker<'a> { body, is_async: _, range: _, + node_index: _, }) => { for item in items { self.visit_with_item(item); @@ -1307,6 +1324,7 @@ impl<'a> Visitor<'a> for Checker<'a> { body, orelse, range: _, + node_index: _, }) => { self.visit_boolean_test(test); self.visit_body(body); @@ -1318,6 +1336,7 @@ impl<'a> Visitor<'a> for Checker<'a> { body, elif_else_clauses, range: _, + node_index: _, }, ) => { self.visit_boolean_test(test); @@ -1437,15 +1456,27 @@ impl<'a> Visitor<'a> for Checker<'a> { func, arguments: _, range: _, + node_index: _, }) => { - if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() { + if let Expr::Name(ast::ExprName { + id, + ctx, + range: _, + node_index: _, + }) = func.as_ref() + { if id == "locals" && ctx.is_load() { let scope = self.semantic.current_scope_mut(); scope.set_uses_locals(); } } } - Expr::Name(ast::ExprName { id, ctx, range: _ }) => match ctx { + Expr::Name(ast::ExprName { + id, + ctx, + range: _, + node_index: _, + }) => match ctx { ExprContext::Load => self.handle_node_load(expr), ExprContext::Store => self.handle_node_store(id, expr), ExprContext::Del => self.handle_node_delete(expr), @@ -1460,6 +1491,7 @@ impl<'a> Visitor<'a> for Checker<'a> { elt, generators, range: _, + node_index: _, }) => { self.visit_generators(GeneratorKind::ListComprehension, generators); self.visit_expr(elt); @@ -1468,6 +1500,7 @@ impl<'a> Visitor<'a> for Checker<'a> { elt, generators, range: _, + node_index: _, }) => { self.visit_generators(GeneratorKind::SetComprehension, generators); self.visit_expr(elt); @@ -1476,6 +1509,7 @@ impl<'a> Visitor<'a> for Checker<'a> { elt, generators, range: _, + node_index: _, parenthesized: _, }) => { self.visit_generators(GeneratorKind::Generator, generators); @@ -1486,6 +1520,7 @@ impl<'a> Visitor<'a> for Checker<'a> { value, generators, range: _, + node_index: _, }) => { self.visit_generators(GeneratorKind::DictComprehension, generators); self.visit_expr(key); @@ -1496,6 +1531,7 @@ impl<'a> Visitor<'a> for Checker<'a> { parameters, body: _, range: _, + node_index: _, }, ) => { // Visit the default arguments, but avoid the body, which will be deferred. @@ -1517,6 +1553,7 @@ impl<'a> Visitor<'a> for Checker<'a> { body, orelse, range: _, + node_index: _, }) => { self.visit_boolean_test(test); self.visit_expr(body); @@ -1526,6 +1563,7 @@ impl<'a> Visitor<'a> for Checker<'a> { op: UnaryOp::Not, operand, range: _, + node_index: _, }) => { self.visit_boolean_test(operand); } @@ -1533,6 +1571,7 @@ impl<'a> Visitor<'a> for Checker<'a> { func, arguments, range: _, + node_index: _, }) => { self.visit_expr(func); @@ -1647,6 +1686,7 @@ impl<'a> Visitor<'a> for Checker<'a> { arg, value, range: _, + node_index: _, } = keyword; if let Some(id) = arg { if matches!(&**id, "bound" | "default") { @@ -1738,7 +1778,12 @@ impl<'a> Visitor<'a> for Checker<'a> { self.visit_non_type_definition(arg); } for arg in args { - if let Expr::Dict(ast::ExprDict { items, range: _ }) = arg { + if let Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) = arg + { for ast::DictItem { key, value } in items { if let Some(key) = key { self.visit_non_type_definition(key); @@ -1776,6 +1821,7 @@ impl<'a> Visitor<'a> for Checker<'a> { value, arg, range: _, + node_index: _, } = keyword; if arg.as_ref().is_some_and(|arg| arg == "type") { self.visit_type_definition(value); @@ -1804,6 +1850,7 @@ impl<'a> Visitor<'a> for Checker<'a> { slice, ctx, range: _, + node_index: _, }) => { // Only allow annotations in `ExprContext::Load`. If we have, e.g., // `obj["foo"]["bar"]`, we need to avoid treating the `obj["foo"]` @@ -1843,6 +1890,7 @@ impl<'a> Visitor<'a> for Checker<'a> { elts, ctx, range: _, + node_index: _, parenthesized: _, }) = slice.as_ref() { @@ -1867,7 +1915,12 @@ impl<'a> Visitor<'a> for Checker<'a> { } } Some(typing::SubscriptKind::TypedDict) => { - if let Expr::Dict(ast::ExprDict { items, range: _ }) = slice.as_ref() { + if let Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) = slice.as_ref() + { for item in items { if let Some(key) = &item.key { self.visit_non_type_definition(key); @@ -1906,6 +1959,7 @@ impl<'a> Visitor<'a> for Checker<'a> { target, value, range: _, + node_index: _, }) => { self.visit_expr(value); @@ -1955,6 +2009,7 @@ impl<'a> Visitor<'a> for Checker<'a> { name, body: _, range: _, + node_index: _, }) => { if let Some(name) = name { // Store the existing binding, if any. @@ -2029,6 +2084,7 @@ impl<'a> Visitor<'a> for Checker<'a> { | Pattern::MatchStar(ast::PatternMatchStar { name: Some(name), range: _, + node_index: _, }) | Pattern::MatchMapping(ast::PatternMatchMapping { rest: Some(name), .. @@ -2088,6 +2144,7 @@ impl<'a> Visitor<'a> for Checker<'a> { default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = bound { self.visit @@ -2104,6 +2161,7 @@ impl<'a> Visitor<'a> for Checker<'a> { default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { self.visit @@ -2115,6 +2173,7 @@ impl<'a> Visitor<'a> for Checker<'a> { default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { self.visit @@ -2833,6 +2892,7 @@ impl<'a> Checker<'a> { parameters, body, range: _, + node_index: _, })) = self.semantic.current_expression() else { unreachable!("Expected Expr::Lambda"); diff --git a/crates/ruff_linter/src/doc_lines.rs b/crates/ruff_linter/src/doc_lines.rs index 94e1d16f65a1de..c45dc64b4da0ce 100644 --- a/crates/ruff_linter/src/doc_lines.rs +++ b/crates/ruff_linter/src/doc_lines.rs @@ -73,6 +73,7 @@ impl StatementVisitor<'_> for StringLinesVisitor<'_> { if let Stmt::Expr(ast::StmtExpr { value: expr, range: _, + node_index: _, }) = stmt { if expr.is_string_literal_expr() { diff --git a/crates/ruff_linter/src/docstrings/extraction.rs b/crates/ruff_linter/src/docstrings/extraction.rs index a0597b06269b65..2a7e70a0b5f6b7 100644 --- a/crates/ruff_linter/src/docstrings/extraction.rs +++ b/crates/ruff_linter/src/docstrings/extraction.rs @@ -7,7 +7,12 @@ use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, Member pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&ast::ExprStringLiteral> { let stmt = suite.first()?; // Require the docstring to be a standalone expression. - let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else { + let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = stmt + else { return None; }; // Only match strings. diff --git a/crates/ruff_linter/src/importer/mod.rs b/crates/ruff_linter/src/importer/mod.rs index 775943caf09cdd..31bf7262bced39 100644 --- a/crates/ruff_linter/src/importer/mod.rs +++ b/crates/ruff_linter/src/importer/mod.rs @@ -453,6 +453,7 @@ impl<'a> Importer<'a> { names, level, range: _, + node_index: _, }) = stmt { if *level == 0 diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index bafbf1fb4dd45f..5d367eb45b25e8 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -104,7 +104,12 @@ pub(crate) fn airflow_3_removal_expr(checker: &Checker, expr: &Expr) { check_name(checker, expr, *range); check_class_attribute(checker, attribute_expr); } - Expr::Name(ExprName { id, ctx, range }) => { + Expr::Name(ExprName { + id, + ctx, + range, + node_index: _, + }) => { check_name(checker, expr, *range); if matches!(ctx, ExprContext::Store) { if let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind { @@ -375,8 +380,11 @@ fn check_context_key_usage_in_call(checker: &Checker, call_expr: &ExprCall) { } for removed_key in REMOVED_CONTEXT_KEYS { - let Some(Expr::StringLiteral(ExprStringLiteral { value, range })) = - call_expr.arguments.find_positional(0) + let Some(Expr::StringLiteral(ExprStringLiteral { + value, + range, + node_index: _, + })) = call_expr.arguments.find_positional(0) else { continue; }; diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs index f7914999c12406..b5e26ea2aba1b7 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -102,6 +102,7 @@ pub(crate) fn airflow_3_0_suggested_update_expr(checker: &Checker, expr: &Expr) id: _, ctx: _, range, + node_index: _, }) => { check_name(checker, expr, *range); } diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs index 1036195ddd4be7..e594c4ffc03e2d 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/subscript.rs @@ -176,6 +176,7 @@ pub(crate) fn subscript(checker: &Checker, value: &Expr, slice: &Expr) { upper: Some(upper), step: None, range: _, + node_index: _, }) => { if let Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(i), diff --git a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs index e627e0f7c588a8..c0289225f174dc 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/helpers.rs @@ -137,6 +137,7 @@ impl AutoPythonType { let expr = Expr::Name(ast::ExprName { id: Name::from(binding), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }); Some((expr, vec![no_return_edit])) @@ -203,6 +204,7 @@ fn type_expr(python_type: PythonType) -> Option { Expr::Name(ast::ExprName { id: name.into(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }) } diff --git a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs index a83206cdc18e14..60886c00347ecf 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs @@ -562,7 +562,11 @@ fn is_stub_function(function_def: &ast::StmtFunctionDef, checker: &Checker) -> b fn is_empty_body(function_def: &ast::StmtFunctionDef) -> bool { function_def.body.iter().all(|stmt| match stmt { Stmt::Pass(_) => true, - Stmt::Expr(ast::StmtExpr { value, range: _ }) => { + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => { matches!( value.as_ref(), Expr::StringLiteral(_) | Expr::EllipsisLiteral(_) @@ -606,6 +610,7 @@ pub(crate) fn definition( let ast::StmtFunctionDef { range: _, + node_index: _, is_async: _, decorator_list, name, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs index b06f9104c28b10..dab899b43aa01d 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs @@ -166,6 +166,7 @@ fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result> { op, right, range: _, + node_index: _, }) => { let Some(left_value) = parse_mask(left, semantic)? else { return Ok(None); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs index 2e42008201a999..b84c909303f5f8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs @@ -131,7 +131,11 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel) fn is_empty_body(body: &[Stmt]) -> bool { body.iter().all(|stmt| match stmt { Stmt::Pass(_) => true, - Stmt::Expr(ast::StmtExpr { value, range: _ }) => { + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => { matches!( value.as_ref(), Expr::StringLiteral(_) | Expr::EllipsisLiteral(_) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs index 94847783b94220..8708e4c4879731 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs @@ -52,11 +52,13 @@ impl AlwaysFixableViolation for AssertFalse { fn assertion_error(msg: Option<&Expr>) -> Stmt { Stmt::Raise(ast::StmtRaise { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), exc: Some(Box::new(Expr::Call(ast::ExprCall { func: Box::new(Expr::Name(ast::ExprName { id: "AssertionError".into(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), arguments: Arguments { args: if let Some(msg) = msg { @@ -66,8 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt { }, keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }))), cause: None, }) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs index 2e9be0eef0abdb..9a74185ad3479a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs @@ -63,6 +63,7 @@ pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) { func, arguments, range: _, + node_index: _, }) = &item.context_expr else { continue; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index e3868cde2c60ed..6516a923905446 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -113,6 +113,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr { elts: elts.into_iter().cloned().collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, } .into() diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs index 14e4f73409b218..8b4d9527f9226b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/f_string_docstring.rs @@ -45,7 +45,12 @@ pub(crate) fn f_string_docstring(checker: &Checker, body: &[Stmt]) { let Some(stmt) = body.first() else { return; }; - let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else { + let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = stmt + else { return; }; if !value.is_f_string_expr() { diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs index fd7b5436efa566..a4cbba7cc7ffb7 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs @@ -111,6 +111,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { Stmt::Return(ast::StmtReturn { value: Some(value), range: _, + node_index: _, }) => { // Mark `return lambda: x` as safe. if value.is_lambda_expr() { @@ -128,6 +129,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { func, arguments, range: _, + node_index: _, }) => { match func.as_ref() { Expr::Name(ast::ExprName { id, .. }) => { @@ -167,6 +169,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { parameters, body, range: _, + node_index: _, }) => { if !self.safe_functions.contains(&expr) { // Collect all loaded variable names. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs index fe3ff0870045d9..83094305b845a1 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs @@ -60,6 +60,7 @@ pub(crate) fn loop_iterator_mutation(checker: &Checker, stmt_for: &StmtFor) { orelse: _, is_async: _, range: _, + node_index: _, } = stmt_for; let (index, target, iter) = match iter.as_ref() { @@ -170,6 +171,7 @@ impl<'a> LoopMutationsVisitor<'a> { for target in targets { if let Expr::Subscript(ExprSubscript { range: _, + node_index: _, value, slice: _, ctx: _, @@ -188,6 +190,7 @@ impl<'a> LoopMutationsVisitor<'a> { for target in targets { if let Expr::Subscript(ExprSubscript { range: _, + node_index: _, value, slice, ctx: _, @@ -217,6 +220,7 @@ impl<'a> LoopMutationsVisitor<'a> { fn handle_call(&mut self, func: &Expr) { if let Expr::Attribute(ExprAttribute { range, + node_index: _, value, attr, ctx: _, @@ -237,7 +241,11 @@ impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> { fn visit_stmt(&mut self, stmt: &'a Stmt) { match stmt { // Ex) `del items[0]` - Stmt::Delete(StmtDelete { range, targets }) => { + Stmt::Delete(StmtDelete { + range, + targets, + node_index: _, + }) => { self.handle_delete(*range, targets); visitor::walk_stmt(self, stmt); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs index dec0098e9a5fbb..f0a12e755a4f55 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_variable_overrides_iterator.rs @@ -97,6 +97,7 @@ impl<'a> Visitor<'a> for NameFinder<'a> { parameters, body, range: _, + node_index: _, }) => { visitor::walk_expr(self, body); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs index a24e49c2938c07..8b8f529df4e636 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/return_in_generator.rs @@ -126,6 +126,7 @@ impl StatementVisitor<'_> for ReturnInGeneratorVisitor { Stmt::Return(ast::StmtReturn { value: Some(_), range, + node_index: _, }) => { self.return_ = Some(*range); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs index 6cd3ddd478257a..7643b2846ea31c 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs @@ -153,6 +153,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> { body, elif_else_clauses, range: _, + node_index: _, }) => { // base if plus branches let mut if_stack = Vec::with_capacity(1 + elif_else_clauses.len()); @@ -179,6 +180,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> { subject, cases, range: _, + node_index: _, }) => { self.counter_stack.push(Vec::with_capacity(cases.len())); self.visit_expr(subject); @@ -210,7 +212,11 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> { Stmt::Continue(_) | Stmt::Break(_) => { self.reset_usage_count(); } - Stmt::Return(ast::StmtReturn { value, range: _ }) => { + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => { if let Some(expr) = value { self.visit_expr(expr); } @@ -250,11 +256,13 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> { elt, generators, range: _, + node_index: _, }) | Expr::SetComp(ast::ExprSetComp { elt, generators, range: _, + node_index: _, }) => { for comprehension in generators { self.visit_comprehension(comprehension); @@ -270,6 +278,7 @@ impl<'a> Visitor<'a> for GroupNameFinder<'a> { value, generators, range: _, + node_index: _, }) => { for comprehension in generators { self.visit_comprehension(comprehension); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index a83710800d8585..6244634ffa1fd2 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -53,9 +53,11 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str attr: Identifier::new(name.to_string(), TextRange::default()), ctx: ExprContext::Store, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })], value: Box::new(value.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); generator.stmt(&stmt) } @@ -92,6 +94,7 @@ pub(crate) fn setattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr, if let Stmt::Expr(ast::StmtExpr { value: child, range: _, + node_index: _, }) = checker.semantic().current_statement() { if expr == child.as_ref() { diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs index 7af4bfa6f1adc3..d7defe6c8bf92c 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -176,14 +176,17 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) - }, keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; Expr::Call(ExprCall { func: Box::new(Expr::Name(ExprName { id: "dict.fromkeys".into(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), arguments: args, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs index 1bf052e206632b..eea8374c604077 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs @@ -48,6 +48,7 @@ pub(crate) fn unnecessary_list_call(checker: &Checker, expr: &Expr, call: &ExprC func, arguments, range: _, + node_index: _, } = call; if !arguments.keywords.is_empty() { @@ -60,6 +61,7 @@ pub(crate) fn unnecessary_list_call(checker: &Checker, expr: &Expr, call: &ExprC let Arguments { range: _, + node_index: _, args, keywords: _, } = arguments; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs index 0e2768eb7a3bd6..5357daf99fd325 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs @@ -52,6 +52,7 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &Checker, call: &ast::Expr upper, step, range: _, + node_index: _, }) = slice.as_ref() else { return; @@ -66,6 +67,7 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &Checker, call: &ast::Expr op: UnaryOp::USub, operand, range: _, + node_index: _, }) = step.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs index 4ac91e9ca5b3f6..27c086833228da 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/multiple_starts_ends_with.rs @@ -72,6 +72,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { op: BoolOp::Or, values, range: _, + node_index: _, }) = expr else { return; @@ -86,8 +87,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }) = &call else { continue; @@ -145,8 +148,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { args, keywords: _, range: _, + node_index: _, }, range: _, + node_index: _, }) = expr else { unreachable!( @@ -173,18 +178,21 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }); let node1 = Expr::Name(ast::ExprName { id: arg_name.into(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let node2 = Expr::Attribute(ast::ExprAttribute { value: Box::new(node1), attr: Identifier::new(attr_name.to_string(), TextRange::default()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let node3 = Expr::Call(ast::ExprCall { func: Box::new(node2), @@ -192,8 +200,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { args: Box::from([node]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let call = node3; @@ -213,6 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) { }) .collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let bool_op = node; diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs index 77677672bd4952..ad817929c9e5f8 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs @@ -63,6 +63,7 @@ pub(crate) fn reimplemented_container_builtin(checker: &Checker, expr: &ExprLamb parameters, body, range: _, + node_index: _, } = expr; if parameters.is_some() { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs index 261a52a6192621..154a2bdc417fd2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs @@ -88,12 +88,14 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) { Expr::Tuple(ast::ExprTuple { elts: unique_nodes.into_iter().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: false, }) }), value: subscript.value.clone(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }); let fix = Fix::applicable_edit( diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 1064ca22b8d319..27c6d41c780767 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -165,6 +165,7 @@ fn generate_pep604_fix( op: Operator::BitOr, right: Box::new(right.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) } else { Some(right.clone()) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs index 7e7b67091383e5..3127f3d411f8bc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs @@ -347,6 +347,7 @@ fn check_positional_args_for_overloaded_method( // If any overloads have any variadic arguments, don't do any checking let Parameters { range: _, + node_index: _, posonlyargs, args, vararg: None, diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs index ce7b0700ac5e5c..77e902da12476f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs @@ -133,14 +133,17 @@ fn generate_union_fix( // Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]` let new_expr = Expr::Subscript(ExprSubscript { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), value: Box::new(Expr::Name(ExprName { id: Name::new(binding), ctx: ExprContext::Store, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), slice: Box::new(Expr::Tuple(ExprTuple { elts: nodes.into_iter().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: false, })), diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs index cfc005941ad618..3e3efd8c661fe1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_empty_stub_body.rs @@ -59,7 +59,12 @@ pub(crate) fn non_empty_stub_body(checker: &Checker, body: &[Stmt]) { } // Ignore `...` (the desired case). - if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt { + if let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = stmt + { if value.is_ellipsis_literal_expr() { return; } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs index 81ef4434fe191c..12c4b01fb954ed 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs @@ -205,11 +205,13 @@ fn create_fix( let new_literal_expr = Expr::Subscript(ast::ExprSubscript { value: Box::new(literal_subscript.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, slice: Box::new(if literal_elements.len() > 1 { Expr::Tuple(ast::ExprTuple { elts: literal_elements.into_iter().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: true, }) @@ -233,6 +235,7 @@ fn create_fix( UnionKind::BitOr => { let none_expr = Expr::NoneLiteral(ExprNoneLiteral { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let union_expr = pep_604_union(&[new_literal_expr, none_expr]); let content = checker.generator().expr(&union_expr); diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index c384433dddc804..8c22d3b0097ae1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -252,6 +252,7 @@ fn generate_pep604_fix( op: Operator::BitOr, right: Box::new(right.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) } else { Some(right.clone()) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index ad9d6137bd7040..93498e8b166de2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -300,7 +300,11 @@ fn is_valid_default_value_with_annotation( } Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) - | Expr::Set(ast::ExprSet { elts, range: _ }) => { + | Expr::Set(ast::ExprSet { + elts, + range: _, + node_index: _, + }) => { return allow_container && elts.len() <= 10 && elts @@ -320,6 +324,7 @@ fn is_valid_default_value_with_annotation( op: UnaryOp::USub, operand, range: _, + node_index: _, }) => { match operand.as_ref() { // Ex) `-1`, `-3.14`, `2j` @@ -342,6 +347,7 @@ fn is_valid_default_value_with_annotation( op: Operator::Add | Operator::Sub, right, range: _, + node_index: _, }) => { // Ex) `1 + 2j`, `1 - 2j`, `-1 - 2j`, `-1 + 2j` if let Expr::NumberLiteral(ast::ExprNumberLiteral { @@ -360,6 +366,7 @@ fn is_valid_default_value_with_annotation( op: UnaryOp::USub, operand, range: _, + node_index: _, }) = left.as_ref() { // Ex) `-1 + 2j`, `-1 - 2j` @@ -398,6 +405,7 @@ fn is_valid_pep_604_union(annotation: &Expr) -> bool { op: Operator::BitOr, right, range: _, + node_index: _, }) => is_valid_pep_604_union_member(left) && is_valid_pep_604_union_member(right), Expr::Name(_) | Expr::Subscript(_) | Expr::Attribute(_) | Expr::NoneLiteral(_) => true, _ => false, @@ -410,6 +418,7 @@ fn is_valid_pep_604_union(annotation: &Expr) -> bool { op: Operator::BitOr, right, range: _, + node_index: _, }) = annotation else { return false; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index 0062f070637409..0765eeb48d0ff9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -140,10 +140,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) { slice: Box::new(Expr::Tuple(ast::ExprTuple { elts: literal_exprs.into_iter().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: true, })), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }); @@ -162,10 +164,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) { slice: Box::new(Expr::Tuple(ast::ExprTuple { elts, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: true, })), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, })) } else { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs index c2b7628b2f7865..56d838b950af2a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs @@ -134,10 +134,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { id: Name::new_static("type"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), slice: Box::new(pep_604_union(&elts)), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); if other_exprs.is_empty() { @@ -157,6 +159,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { id: Name::new_static("type"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), slice: Box::new(Expr::Subscript(ast::ExprSubscript { value: subscript.value.clone(), @@ -168,18 +171,22 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { id: type_member, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) }) .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, })), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); if other_exprs.is_empty() { @@ -195,10 +202,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) { elts: exprs.into_iter().cloned().collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, })), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); checker.generator().expr(&union) diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs index 795c3c7e5c7adc..9e72a7e3584d41 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs @@ -619,11 +619,13 @@ fn is_composite_condition(test: &Expr) -> CompositionKind { op: UnaryOp::Not, operand, range: _, + node_index: _, }) => { if let Expr::BoolOp(ast::ExprBoolOp { op: BoolOp::Or, values, range: _, + node_index: _, }) = operand.as_ref() { // Only split cases without mixed `and` and `or`. diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index 634bf422e1e1df..433aca9834eac6 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -622,7 +622,11 @@ struct SkipFunctionsVisitor<'a> { impl<'a> Visitor<'a> for SkipFunctionsVisitor<'a> { fn visit_stmt(&mut self, stmt: &'a Stmt) { match stmt { - Stmt::Return(ast::StmtReturn { value, range: _ }) => { + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => { if value.is_some() { self.has_return_with_value = true; } @@ -637,7 +641,11 @@ impl<'a> Visitor<'a> for SkipFunctionsVisitor<'a> { Expr::YieldFrom(_) => { self.has_yield_from = true; } - Expr::Yield(ast::ExprYield { value, range: _ }) => { + Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => { self.yield_statements.push(expr); if value.is_some() { self.has_return_with_value = true; @@ -686,6 +694,7 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor func, arguments, range: _, + node_index: _, }) => { if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) { if !checker.settings.flake8_pytest_style.fixture_parentheses diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs index 979cb7dff6c48a..8b80c6b28943a3 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs @@ -146,8 +146,10 @@ fn check_mark_parentheses(checker: &Checker, decorator: &Decorator, marker: &str args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }) => { if !checker.settings.flake8_pytest_style.mark_parentheses && args.is_empty() diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs index 197802ed99ea52..66590cc684c172 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -301,6 +301,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) - }) .into_boxed_str(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags, }); Some(generator.expr(&node)) @@ -363,12 +364,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr Expr::from(ast::StringLiteral { value: Box::from(*name), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags: checker.default_string_flags(), }) }) .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( @@ -398,12 +401,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr Expr::from(ast::StringLiteral { value: Box::from(*name), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags: checker.default_string_flags(), }) }) .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node), @@ -432,6 +437,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr elts: elts.clone(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node), @@ -476,6 +482,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr elts: elts.clone(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs index 3936dbfff21723..fef3f695eb00c9 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs @@ -84,6 +84,7 @@ fn check_patch_call(checker: &Checker, call: &ast::ExprCall, index: usize) { parameters, body, range: _, + node_index: _, }) = call .arguments .find_argument_value("new", index) diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs index 1ff51ccbd25170..650452c6a0f867 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs @@ -166,6 +166,7 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt { test: Box::new(expr.clone()), msg: msg.map(|msg| Box::new(msg.clone())), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } @@ -175,6 +176,7 @@ fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr { ops: Box::from([cmp_op]), comparators: Box::from([right.clone()]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } @@ -294,6 +296,7 @@ impl UnittestAssert { op: UnaryOp::Not, operand: Box::new(expr.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }), msg, ) @@ -367,6 +370,7 @@ impl UnittestAssert { }; let node = Expr::NoneLiteral(ast::ExprNoneLiteral { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let expr = compare(expr, cmp_op, &node); Ok(assert(&expr, msg)) @@ -383,6 +387,7 @@ impl UnittestAssert { id: Name::new_static("isinstance"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node1 = ast::ExprCall { func: Box::new(node.into()), @@ -390,8 +395,10 @@ impl UnittestAssert { args: Box::from([(**obj).clone(), (**cls).clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let isinstance = node1.into(); if matches!(self, UnittestAssert::IsInstance) { @@ -401,6 +408,7 @@ impl UnittestAssert { op: UnaryOp::Not, operand: Box::new(isinstance), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let expr = node.into(); Ok(assert(&expr, msg)) @@ -421,12 +429,14 @@ impl UnittestAssert { id: Name::new_static("re"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node1 = ast::ExprAttribute { value: Box::new(node.into()), attr: Identifier::new("search".to_string(), TextRange::default()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node2 = ast::ExprCall { func: Box::new(node1.into()), @@ -434,8 +444,10 @@ impl UnittestAssert { args: Box::from([(**regex).clone(), (**text).clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let re_search = node2.into(); if matches!(self, UnittestAssert::Regex | UnittestAssert::RegexpMatches) { @@ -445,6 +457,7 @@ impl UnittestAssert { op: UnaryOp::Not, operand: Box::new(re_search), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; Ok(assert(&node.into(), msg)) } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs index 5c7d6c91d04f53..62ed604decbe3f 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs @@ -67,6 +67,7 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi ast::StringLikePart::FString(ast::FString { elements, range, + node_index: _, flags, }) => { check_interpolated_string(checker, AnyStringFlags::from(*flags), *range, elements); @@ -74,6 +75,7 @@ pub(crate) fn unnecessary_escaped_quote(checker: &Checker, string_like: StringLi ast::StringLikePart::TString(ast::TString { elements, range, + node_index: _, flags, }) => { check_interpolated_string(checker, AnyStringFlags::from(*flags), *range, elements); diff --git a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs index 37ae01b46b7fa7..d543b30dcddabc 100644 --- a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs @@ -58,6 +58,7 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &Checker, expr: &Exp func, arguments, range: _, + node_index: _, }) = expr else { return; diff --git a/crates/ruff_linter/src/rules/flake8_return/visitor.rs b/crates/ruff_linter/src/rules/flake8_return/visitor.rs index 38ddae2af4edee..e939209f536653 100644 --- a/crates/ruff_linter/src/rules/flake8_return/visitor.rs +++ b/crates/ruff_linter/src/rules/flake8_return/visitor.rs @@ -95,8 +95,16 @@ impl<'a> Visitor<'a> for ReturnVisitor<'_, 'a> { // But don't recurse into the body. return; } - Stmt::Global(ast::StmtGlobal { names, range: _ }) - | Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => { + Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) + | Stmt::Nonlocal(ast::StmtNonlocal { + names, + range: _, + node_index: _, + }) => { self.stack .non_locals .extend(names.iter().map(Identifier::as_str)); diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs index e2a35821373067..7576c71fc11baf 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs @@ -307,8 +307,10 @@ fn isinstance_target<'a>(call: &'a Expr, semantic: &'a SemanticModel) -> Option< args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, } = call.as_call_expr()?; if args.len() != 2 { return None; @@ -330,6 +332,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { op: BoolOp::Or, values, range: _, + node_index: _, }) = expr else { return; @@ -418,6 +421,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }; let isinstance_call = ast::ExprCall { @@ -426,6 +430,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { id: Name::new_static("isinstance"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -433,8 +438,10 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { args: Box::from([target.clone(), tuple.into()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(); @@ -451,6 +458,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) { .chain(after) .collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(); let fixed_source = checker.generator().expr(&bool_op); @@ -472,6 +480,7 @@ fn match_eq_target(expr: &Expr) -> Option<(&Name, &Expr)> { ops, comparators, range: _, + node_index: _, }) = expr else { return None; @@ -497,6 +506,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) { op: BoolOp::Or, values, range: _, + node_index: _, }) = expr else { return; @@ -542,18 +552,21 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) { elts: comparators.into_iter().cloned().collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }; let node1 = ast::ExprName { id: id.clone(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node2 = ast::ExprCompare { left: Box::new(node1.into()), ops: Box::from([CmpOp::In]), comparators: Box::from([node.into()]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let in_expr = node2.into(); let mut diagnostic = checker.report_diagnostic( @@ -576,6 +589,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) { op: BoolOp::Or, values: iter::once(in_expr).chain(unmatched).collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node.into() }; @@ -592,6 +606,7 @@ pub(crate) fn expr_and_not_expr(checker: &Checker, expr: &Expr) { op: BoolOp::And, values, range: _, + node_index: _, }) = expr else { return; @@ -608,6 +623,7 @@ pub(crate) fn expr_and_not_expr(checker: &Checker, expr: &Expr) { op: UnaryOp::Not, operand, range: _, + node_index: _, }) = expr { negated_expr.push(operand); @@ -648,6 +664,7 @@ pub(crate) fn expr_or_not_expr(checker: &Checker, expr: &Expr) { op: BoolOp::Or, values, range: _, + node_index: _, }) = expr else { return; @@ -664,6 +681,7 @@ pub(crate) fn expr_or_not_expr(checker: &Checker, expr: &Expr) { op: UnaryOp::Not, operand, range: _, + node_index: _, }) = expr { negated_expr.push(operand); @@ -733,6 +751,7 @@ fn is_short_circuit( op, values, range: _, + node_index: _, }) = expr else { return None; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index ff1b0e83ea3a78..d32f2cb1b0e6a5 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -232,6 +232,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) { } }), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let new_env_var = node.into(); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( @@ -246,6 +247,7 @@ pub(crate) fn dict_get_with_none_default(checker: &Checker, expr: &Expr) { func, arguments: Arguments { args, keywords, .. }, range: _, + node_index: _, }) = expr else { return; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs index abcd4b214a2714..7ba5d6410ac721 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_ifexp.rs @@ -188,6 +188,7 @@ pub(crate) fn if_expr_with_true_false( id: Name::new_static("bool"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -195,8 +196,10 @@ pub(crate) fn if_expr_with_true_false( args: Box::from([test.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -224,6 +227,7 @@ pub(crate) fn if_expr_with_false_true( op: UnaryOp::Not, operand: Box::new(test.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -243,6 +247,7 @@ pub(crate) fn twisted_arms_in_ifexpr( op, operand, range: _, + node_index: _, }) = &test else { return; @@ -277,6 +282,7 @@ pub(crate) fn twisted_arms_in_ifexpr( body: Box::new(node1), orelse: Box::new(node), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node3.into()), diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs index 4b7c249b2b82b7..37f3c16a8e8ded 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -153,6 +153,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp ops, comparators, range: _, + node_index: _, }) = operand else { return; @@ -185,6 +186,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp ops: Box::from([CmpOp::NotEq]), comparators: comparators.clone(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node.into()), @@ -207,6 +209,7 @@ pub(crate) fn negation_with_not_equal_op( ops, comparators, range: _, + node_index: _, }) = operand else { return; @@ -239,6 +242,7 @@ pub(crate) fn negation_with_not_equal_op( ops: Box::from([CmpOp::Eq]), comparators: comparators.clone(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&node.into()), @@ -255,6 +259,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera op: operand_op, operand, range: _, + node_index: _, }) = operand else { return; @@ -279,6 +284,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera id: Name::new_static("bool"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node1 = ast::ExprCall { func: Box::new(node.into()), @@ -286,8 +292,10 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera args: Box::from([*operand.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.generator().expr(&node1.into()), diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs index 12eb987bde0310..a4ab3ff311bf66 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs @@ -123,6 +123,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast ops, comparators: test_dict, range: _, + node_index: _, }) = &**test else { return; @@ -187,6 +188,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast attr: Identifier::new("get".to_string(), TextRange::default()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node3 = ast::ExprCall { func: Box::new(node2.into()), @@ -194,14 +196,17 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast args: Box::from([node1, node]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node4 = expected_var.clone(); let node5 = ast::StmtAssign { targets: vec![node4], value: Box::new(node3.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let contents = checker.generator().stmt(&node5.into()); @@ -246,6 +251,7 @@ pub(crate) fn if_exp_instead_of_dict_get( ops, comparators: test_dict, range: _, + node_index: _, }) = test else { return; @@ -291,6 +297,7 @@ pub(crate) fn if_exp_instead_of_dict_get( attr: Identifier::new("get".to_string(), TextRange::default()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let fixed_node = ast::ExprCall { func: Box::new(dict_get_node.into()), @@ -298,8 +305,10 @@ pub(crate) fn if_exp_instead_of_dict_get( args: Box::from([dict_key_node, default_value_node]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let contents = checker.generator().expr(&fixed_node.into()); diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs index 2d116658dbcb45..f9806190de5e68 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_lookup.rs @@ -57,6 +57,7 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & ops, comparators, range: _, + node_index: _, }) = test.as_ref() else { return; @@ -73,7 +74,14 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & let Some(literal_expr) = expr.as_literal_expr() else { return; }; - let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else { + let [ + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }), + ] = body.as_slice() + else { return; }; @@ -99,7 +107,14 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & for clause in elif_else_clauses { let ElifElseClause { test, body, .. } = clause; - let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else { + let [ + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }), + ] = body.as_slice() + else { return; }; @@ -107,7 +122,14 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & // `else` None => { // The else must also be a single effect-free return statement - let [Stmt::Return(ast::StmtReturn { value, range: _ })] = body.as_slice() else { + let [ + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }), + ] = body.as_slice() + else { return; }; if value.as_ref().is_some_and(|value| { @@ -122,6 +144,7 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &Checker, stmt_if: & ops, comparators, range: _, + node_index: _, })) => { let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index 7de2bb14d33ccc..44c89d37c3ba61 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -96,6 +96,7 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: body, elif_else_clauses, range: _, + node_index: _, } = stmt_if; // `test: None` to only match an `else` clause @@ -268,11 +269,13 @@ fn assignment_ternary( body: Box::new(body_value.clone()), orelse: Box::new(orelse_value.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node1 = ast::StmtAssign { targets: vec![target_var.clone()], value: Box::new(node.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node1.into() } @@ -282,11 +285,13 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp op: BoolOp::And, values: vec![left_value.clone(), right_value.clone()], range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let node1 = ast::StmtAssign { targets: vec![target_var.clone()], value: Box::new(node.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node1.into() } @@ -294,10 +299,12 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp fn assignment_binary_or(target_var: &Expr, left_value: &Expr, right_value: &Expr) -> Stmt { (ast::StmtAssign { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), targets: vec![target_var.clone()], value: Box::new( (ast::ExprBoolOp { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), op: BoolOp::Or, values: vec![left_value.clone(), right_value.clone()], }) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs index ff15fe659034a9..da647235e19de2 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs @@ -60,6 +60,7 @@ fn key_in_dict(checker: &Checker, left: &Expr, right: &Expr, operator: CmpOp, pa func, arguments: Arguments { args, keywords, .. }, range: _, + node_index: _, }) = &right else { return; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index f313430121e50a..679797a2583014 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -132,11 +132,13 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { body: elif_body, test: Some(elif_test), range: elif_range, + node_index: _, }, ElifElseClause { body: else_body, test: None, range: else_range, + node_index: _, }, ] => ( elif_test, @@ -254,6 +256,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { left: left.clone(), comparators: Box::new([right.clone()]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) } @@ -261,6 +264,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { op: ast::UnaryOp::Not, operand: Box::new(if_test.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), } } else if if_test.is_compare_expr() { @@ -273,6 +277,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { id: Name::new_static("bool"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let call_node = ast::ExprCall { func: Box::new(func_node.into()), @@ -280,8 +285,10 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { args: Box::from([if_test.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; Some(Expr::Call(call_node)) } else { @@ -294,6 +301,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) { Stmt::Return(ast::StmtReturn { value: Some(Box::new(expr.clone())), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) }); @@ -336,7 +344,12 @@ fn is_one_line_return_bool(stmts: &[Stmt]) -> Option { let [stmt] = stmts else { return None; }; - let Stmt::Return(ast::StmtReturn { value, range: _ }) = stmt else { + let Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) = stmt + else { return None; }; let Some(Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. })) = value.as_deref() else { diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 7005485d2b5971..33c789455fbd8b 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -49,7 +49,12 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool { let Some(expr) = semantic.current_expression_grandparent() else { return false; }; - let Expr::Await(ast::ExprAwait { value, range: _ }) = expr else { + let Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) = expr + else { return false; }; let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else { diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index f3737535edaec7..435874d0075665 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -133,6 +133,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { op: UnaryOp::Not, operand, range: _, + node_index: _, }) = &loop_.test { *operand.clone() @@ -141,6 +142,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { ops, comparators, range: _, + node_index: _, }) = &loop_.test { if let ([op], [comparator]) = (&**ops, &**comparators) { @@ -161,6 +163,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { ops: Box::from([op]), comparators: Box::from([comparator.clone()]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node.into() } else { @@ -168,6 +171,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { op: UnaryOp::Not, operand: Box::new(loop_.test.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node.into() } @@ -176,6 +180,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) { op: UnaryOp::Not, operand: Box::new(loop_.test.clone()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; node.into() } @@ -276,6 +281,7 @@ fn match_loop(stmt: &Stmt) -> Option { test: nested_test, elif_else_clauses: nested_elif_else_clauses, range: _, + node_index: _, }), ] = body.as_slice() else { @@ -288,6 +294,7 @@ fn match_loop(stmt: &Stmt) -> Option { Stmt::Return(ast::StmtReturn { value: Some(value), range: _, + node_index: _, }), ] = nested_body.as_slice() else { @@ -325,6 +332,7 @@ fn match_else_return(stmt: &Stmt) -> Option { Stmt::Return(ast::StmtReturn { value: Some(next_value), range: _, + node_index: _, }), ] = orelse.as_slice() else { @@ -368,6 +376,7 @@ fn match_sibling_return<'a>(stmt: &'a Stmt, sibling: &'a Stmt) -> Option Expr { Expr::from(StringLiteral { value: Box::from(*elt), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), // intentionally omit the triple quote flag, if set, to avoid strange // replacements like // @@ -140,6 +141,7 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr { .collect(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs index 3980e2692a01d6..1d01784e80d4ff 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs @@ -65,7 +65,13 @@ impl Violation for SuppressibleException { fn is_empty(body: &[Stmt]) -> bool { match body { [Stmt::Pass(_)] => true, - [Stmt::Expr(ast::StmtExpr { value, range: _ })] => value.is_ellipsis_literal_expr(), + [ + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }), + ] => value.is_ellipsis_literal_expr(), _ => false, } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs index 687af811c9cb23..a21b960af07d4f 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs @@ -116,6 +116,7 @@ impl From<&Expr> for ConstantLikelihood { op: UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert, operand, range: _, + node_index: _, }) => ConstantLikelihood::from(&**operand), _ => ConstantLikelihood::Unlikely, } diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs index 73dd4b82b997a9..1e6ac5e4ca8a52 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs @@ -101,6 +101,7 @@ fn fix_banned_relative_import( names: names.clone(), level: 0, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let content = generator.stmt(&node.into()); Some(Fix::unsafe_edit(Edit::range_replacement( diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs index 2b3d60c56e201a..21da4fb5145735 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs @@ -367,6 +367,7 @@ impl<'a> QuoteAnnotator<'a> { let annotation = subgenerator.expr(&expr_without_forward_references); generator.expr(&Expr::from(ast::StringLiteral { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), value: annotation.into_boxed_str(), flags: self.flags, })) diff --git a/crates/ruff_linter/src/rules/flynt/helpers.rs b/crates/ruff_linter/src/rules/flynt/helpers.rs index 4afb764c4ce062..58137a38165d84 100644 --- a/crates/ruff_linter/src/rules/flynt/helpers.rs +++ b/crates/ruff_linter/src/rules/flynt/helpers.rs @@ -9,6 +9,7 @@ fn to_interpolated_string_interpolation_element(inner: &Expr) -> ast::Interpolat conversion: ConversionFlag::None, format_spec: None, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } @@ -17,6 +18,7 @@ pub(super) fn to_interpolated_string_literal_element(s: &str) -> ast::Interpolat ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { value: Box::from(s), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } @@ -31,8 +33,10 @@ fn is_simple_call(expr: &Expr) -> bool { args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }) => args.is_empty() && keywords.is_empty() && is_simple_callee(func), _ => false, } @@ -53,12 +57,17 @@ pub(super) fn to_interpolated_string_element( expr: &Expr, ) -> Option { match expr { - Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => Some( - ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { + Expr::StringLiteral(ast::ExprStringLiteral { + value, + range, + node_index, + }) => Some(ast::InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { value: value.to_string().into_boxed_str(), range: *range, - }), - ), + node_index: node_index.clone(), + }, + )), // These should be pretty safe to wrap in a formatted value. Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) | Expr::Name(_) | Expr::Attribute(_) => { Some(to_interpolated_string_interpolation_element(expr)) diff --git a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs index aa07cb58af8615..f0e339977741f3 100644 --- a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs @@ -91,6 +91,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option< .into_boxed_str(), flags: flags?, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; return Some(node.into()); } @@ -113,6 +114,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option< let node = ast::FString { elements: f_string_elements.into(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags, }; Some(node.into()) diff --git a/crates/ruff_linter/src/rules/isort/annotate.rs b/crates/ruff_linter/src/rules/isort/annotate.rs index f3f1a247b2d4f8..ccbcc84b194f5a 100644 --- a/crates/ruff_linter/src/rules/isort/annotate.rs +++ b/crates/ruff_linter/src/rules/isort/annotate.rs @@ -23,7 +23,11 @@ pub(crate) fn annotate_imports<'a>( .iter() .map(|import| { match import { - Stmt::Import(ast::StmtImport { names, range }) => { + Stmt::Import(ast::StmtImport { + names, + range, + node_index: _, + }) => { // Find comments above. let mut atop = vec![]; while let Some(comment) = @@ -59,6 +63,7 @@ pub(crate) fn annotate_imports<'a>( names, level, range: _, + node_index: _, }) => { // Find comments above. let mut atop = vec![]; diff --git a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs index 3948202883dfbd..bff7e42137ee00 100644 --- a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs @@ -58,7 +58,12 @@ impl AlwaysFixableViolation for MissingRequiredImport { fn includes_import(stmt: &Stmt, target: &NameImport) -> bool { match target { NameImport::Import(target) => { - let Stmt::Import(ast::StmtImport { names, range: _ }) = &stmt else { + let Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) = &stmt + else { return false; }; names.iter().any(|alias| { @@ -72,6 +77,7 @@ fn includes_import(stmt: &Stmt, target: &NameImport) -> bool { names, level, range: _, + node_index: _, }) = &stmt else { return false; diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs index 5656e4489f3681..2d7ceba14bb336 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/nunique_constant_series_check.rs @@ -87,6 +87,7 @@ pub(crate) fn nunique_constant_series_check( Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(Int::ONE), range: _, + node_index: _, }) ) { return; diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs index be8c6feb0f5c10..07423b3a67f528 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs @@ -121,6 +121,7 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF targets, value, range, + node_index: _, }) = stmt else { return; diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs index da95d98ad350f2..0283c5a229e2b2 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs @@ -148,8 +148,10 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF args, keywords, range: _, + node_index: _, }, range, + node_index: _, }) = value.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs index 7e9c9b103e63b7..3322830aca515a 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_copy.rs @@ -69,8 +69,10 @@ pub(crate) fn manual_list_copy(checker: &Checker, for_stmt: &ast::StmtFor) { args, keywords, range: _, + node_index: _, }, range, + node_index: _, }) = value.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs index 5e9964e2922aae..08d3ccacce30af 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs @@ -58,8 +58,10 @@ pub(crate) fn unnecessary_list_cast(checker: &Checker, iter: &Expr, body: &[Stmt args, keywords: _, range: _, + node_index: _, }, range: list_range, + node_index: _, }) = iter else { return; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 6b4ce91adec53d..c8cbc21ce6aa72 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -187,6 +187,7 @@ fn function( ExprEllipsisLiteral::default(), ))), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let parameters = lambda.parameters.as_deref().cloned().unwrap_or_default(); if let Some(annotation) = annotation { @@ -234,6 +235,7 @@ fn function( returns: Some(Box::new(return_type)), type_params: None, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let generated = checker.generator().stmt(&func); @@ -249,6 +251,7 @@ fn function( returns: None, type_params: None, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let generated = checker.generator().stmt(&function); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs index 01a57c99c914af..dd7dc69b5b933e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs @@ -75,6 +75,7 @@ fn split_imports( .map(|alias| { let Alias { range: _, + node_index: _, name, asname, } = alias; @@ -99,6 +100,7 @@ fn split_imports( .map(|alias| { let Alias { range: _, + node_index: _, name, asname, } = alias; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs index d9f6b84d488472..30460c1b7cba6b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs @@ -88,6 +88,7 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) { ops, comparators, range: _, + node_index: _, }) = unary_op.operand.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs index e6c6a17b0d5e67..5c392ae93a831c 100644 --- a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs +++ b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs @@ -673,6 +673,7 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> { } Stmt::Return(ast::StmtReturn { range, + node_index: _, value: Some(value), }) => { self.returns.push(ReturnEntry { @@ -684,7 +685,11 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> { }, }); } - Stmt::Return(ast::StmtReturn { range, value: None }) => { + Stmt::Return(ast::StmtReturn { + range, + node_index: _, + value: None, + }) => { self.returns.push(ReturnEntry { range: *range, kind: ReturnEntryKind::ImplicitNone, @@ -701,6 +706,7 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> { match expr { Expr::Yield(ast::ExprYield { range, + node_index: _, value: Some(value), }) => { self.yields.push(YieldEntry { @@ -708,7 +714,11 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> { is_none_yield: value.is_none_literal_expr(), }); } - Expr::Yield(ast::ExprYield { range, value: None }) => { + Expr::Yield(ast::ExprYield { + range, + node_index: _, + value: None, + }) => { self.yields.push(YieldEntry { range: *range, is_none_yield: true, diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs index 300f937d155eae..a94e8290e93024 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_character.rs @@ -92,6 +92,7 @@ pub(crate) fn call(checker: &Checker, string: &str, range: TextRange) { pub(crate) fn percent(checker: &Checker, expr: &Expr, format_string: &ExprStringLiteral) { for StringLiteral { value: _, + node_index: _, range, flags, } in &format_string.value diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs index 2d9e165651d345..a747b9b12ae724 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs @@ -219,6 +219,7 @@ pub(crate) fn bad_string_format_type( let mut format_strings = vec![]; for StringLiteral { value: _, + node_index: _, range, flags, } in &format_string.value @@ -236,7 +237,11 @@ pub(crate) fn bad_string_format_type( // Parse the parameters. let is_valid = match &*bin_op.right { Expr::Tuple(ast::ExprTuple { elts, .. }) => is_valid_tuple(&format_strings, elts), - Expr::Dict(ast::ExprDict { items, range: _ }) => is_valid_dict(&format_strings, items), + Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => is_valid_dict(&format_strings, items), _ => is_valid_constant(&format_strings, &bin_op.right), }; if !is_valid { diff --git a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs index 733f809e6b6095..7a5e7ee9ce7b82 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/if_stmt_min_max.rs @@ -87,6 +87,7 @@ pub(crate) fn if_stmt_min_max(checker: &Checker, stmt_if: &ast::StmtIf) { body, elif_else_clauses, range: _, + node_index: _, } = stmt_if; if !elif_else_clauses.is_empty() { diff --git a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs index 89c9be036b339c..5f5cc9ccc9e179 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/manual_import_from.rs @@ -72,9 +72,11 @@ pub(crate) fn manual_from_import(checker: &Checker, stmt: &Stmt, alias: &Alias, name: asname.clone(), asname: None, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }], level: 0, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( checker.generator().stmt(&node.into()), diff --git a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index 154550c41fc651..9383906df9ced7 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs @@ -112,8 +112,10 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel) args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }) = arg { if MinMax::try_from_call(func, keywords, semantic) == Some(min_max) { @@ -123,6 +125,7 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel) value: Box::new(arg.clone()), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); new_args.push(new_arg); continue; @@ -181,8 +184,10 @@ pub(crate) fn nested_min_max( args: collect_nested_args(min_max, args, checker.semantic()).into_boxed_slice(), keywords: Box::from(keywords), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( checker.generator().expr(&flattened_expr), diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs index b7dd04e992cfec..1be0f21c5b5879 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs @@ -280,11 +280,13 @@ fn assignment_targets_from_expr<'a>( ctx: ExprContext::Store, value, range: _, + node_index: _, }) => Box::new(iter::once(value.as_ref())), Expr::Name(ast::ExprName { ctx: ExprContext::Store, id, range: _, + node_index: _, }) => { // Ignore dummy variables. if dummy_variable_rgx.is_match(id) { @@ -297,6 +299,7 @@ fn assignment_targets_from_expr<'a>( ctx: ExprContext::Store, elts, range: _, + node_index: _, }) => Box::new( elts.iter() .flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)), @@ -305,6 +308,7 @@ fn assignment_targets_from_expr<'a>( ctx: ExprContext::Store, elts, range: _, + node_index: _, parenthesized: _, }) => Box::new( elts.iter() diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs index a2eb232456bb4b..a449a3054db527 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_slots_in_subclass.rs @@ -167,7 +167,11 @@ fn slots_attributes(expr: &Expr) -> impl Iterator { Expr::Tuple(ast::ExprTuple { elts, .. }) | Expr::List(ast::ExprList { elts, .. }) | Expr::Set(ast::ExprSet { elts, .. }) => Some(elts.iter().filter_map(|elt| match elt { - Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => Some(Slot { + Expr::StringLiteral(ast::ExprStringLiteral { + value, + range, + node_index: _, + }) => Some(Slot { name: value.to_str(), range: *range, }), @@ -183,12 +187,14 @@ fn slots_attributes(expr: &Expr) -> impl Iterator { .unwrap() .iter_keys() .filter_map(|key| match key { - Some(Expr::StringLiteral(ast::ExprStringLiteral { value, range })) => { - Some(Slot { - name: value.to_str(), - range: *range, - }) - } + Some(Expr::StringLiteral(ast::ExprStringLiteral { + value, + range, + node_index: _, + })) => Some(Slot { + name: value.to_str(), + range: *range, + }), _ => None, }), ), diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index 4f279fa6b1e471..5b4bda45a906d3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -5,7 +5,7 @@ use ast::ExprContext; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{any_over_expr, contains_effect}; -use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr}; +use ruff_python_ast::{self as ast, AtomicNodeIndex, BoolOp, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; use ruff_text_size::{Ranged, TextRange}; @@ -164,11 +164,13 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp Expr::Set(ast::ExprSet { elts: comparators.iter().copied().cloned().collect(), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }) } else { Expr::Tuple(ast::ExprTuple { elts: comparators.iter().copied().cloned().collect(), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: true, }) @@ -186,10 +188,12 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp }, comparators: Box::from([comparator]), range: bool_op.range(), + node_index: AtomicNodeIndex::dummy(), }))) .chain(after) .collect(), range: bool_op.range(), + node_index: AtomicNodeIndex::dummy(), })), bool_op.range(), ))); diff --git a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs index cf14390acd7e79..ec80640c1d5d0f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/return_in_init.rs @@ -46,7 +46,12 @@ impl Violation for ReturnInInit { /// PLE0101 pub(crate) fn return_in_init(checker: &Checker, stmt: &Stmt) { - if let Stmt::Return(ast::StmtReturn { value, range: _ }) = stmt { + if let Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) = stmt + { if let Some(expr) = value { if expr.is_none_literal_expr() { // Explicit `return None`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs index b48e6ef8c91358..d1be080b4f823e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs @@ -63,6 +63,7 @@ pub(crate) fn unnecessary_lambda(checker: &Checker, lambda: &ExprLambda) { parameters, body, range: _, + node_index: _, } = lambda; // The lambda should consist of a single function call. diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 088041dcf189da..cc9ff1cb29c13a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -173,6 +173,7 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix { value: Box::from("utf-8"), flags: checker.default_string_flags(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) ), &call.arguments, diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs index cef3d35b66c92d..c33acea2d4720c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_return.rs @@ -61,7 +61,12 @@ pub(crate) fn useless_return( }; // Verify that the last statement is a return statement. - let Stmt::Return(ast::StmtReturn { value, range: _ }) = &last_stmt else { + let Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) = &last_stmt + else { return; }; @@ -72,7 +77,12 @@ pub(crate) fn useless_return( // Skip functions that consist of a docstring and a return statement. if body.len() == 2 { - if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = &body[0] { + if let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = &body[0] + { if value.is_string_literal_expr() { return; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index f88bfa38d4a6f2..92f163a57559e0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -87,6 +87,7 @@ pub(crate) fn convert_named_tuple_functional_to_class( // Ex) `NamedTuple("MyType")` ([_typename], []) => vec![Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })], // Ex) `NamedTuple("MyType", [("a", int), ("b", str)])` ([_typename, fields], []) => { @@ -145,6 +146,7 @@ fn match_named_tuple_assign<'a>( func, arguments: Arguments { args, keywords, .. }, range: _, + node_index: _, }) = value else { return None; @@ -163,6 +165,7 @@ fn create_field_assignment_stmt(field: Name, annotation: &Expr) -> Stmt { id: field, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -170,6 +173,7 @@ fn create_field_assignment_stmt(field: Name, annotation: &Expr) -> Stmt { value: None, simple: true, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into() } @@ -180,6 +184,7 @@ fn create_fields_from_fields_arg(fields: &Expr) -> Option> { if fields.is_empty() { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some(vec![node]) } else { @@ -231,11 +236,13 @@ fn create_class_def_stmt(typename: &str, body: Vec, base_class: &Expr) -> args: Box::from([base_class.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), body, type_params: None, decorator_list: vec![], range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into() } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index d45e4dd5cfc462..97f16190782ecb 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -131,6 +131,7 @@ fn match_typed_dict_assign<'a>( func, arguments, range: _, + node_index: _, }) = value else { return None; @@ -149,6 +150,7 @@ fn create_field_assignment_stmt(field: &str, annotation: &Expr) -> Stmt { id: field.into(), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -156,6 +158,7 @@ fn create_field_assignment_stmt(field: &str, annotation: &Expr) -> Stmt { value: None, simple: true, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into() } @@ -176,11 +179,13 @@ fn create_class_def_stmt( None => Box::from([]), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), body, type_params: None, decorator_list: vec![], range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into() } @@ -189,6 +194,7 @@ fn fields_from_dict_literal(items: &[ast::DictItem]) -> Option> { if items.is_empty() { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some(vec![node]) } else { @@ -222,6 +228,7 @@ fn fields_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Option> if keywords.is_empty() { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some(vec![node]) } else { @@ -234,6 +241,7 @@ fn fields_from_keywords(keywords: &[Keyword]) -> Option> { if keywords.is_empty() { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); return Some(vec![node]); } @@ -256,13 +264,16 @@ fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec, Option<&K ([_typename, fields], [..]) => { let total = arguments.find_keyword("total"); match fields { - Expr::Dict(ast::ExprDict { items, range: _ }) => { - Some((fields_from_dict_literal(items)?, total)) - } + Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => Some((fields_from_dict_literal(items)?, total)), Expr::Call(ast::ExprCall { func, arguments: Arguments { keywords, .. }, range: _, + node_index: _, }) => Some((fields_from_dict_call(func, keywords)?, total)), _ => None, } @@ -271,6 +282,7 @@ fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec, Option<&K ([_typename], []) => { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some((vec![node], None)) } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs index 7672680c72bd6b..ab1bd978f739a8 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_c_element_tree.rs @@ -53,7 +53,11 @@ where /// UP023 pub(crate) fn deprecated_c_element_tree(checker: &Checker, stmt: &Stmt) { match stmt { - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { // Ex) `import xml.etree.cElementTree as ET` for name in names { if &name.name == "xml.etree.cElementTree" && name.asname.is_some() { @@ -66,6 +70,7 @@ pub(crate) fn deprecated_c_element_tree(checker: &Checker, stmt: &Stmt) { names, level, range: _, + node_index: _, }) => { if *level > 0 { // Ex) `import .xml.etree.cElementTree as ET` diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index 8da16d71ba8d11..0ef84304fc41a2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -274,7 +274,11 @@ pub(crate) fn deprecated_mock_attribute(checker: &Checker, attribute: &ast::Expr /// UP026 pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) { match stmt { - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { // Find all `mock` imports. if names .iter() diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index 0edc9bb2e8389e..f34751ce9308fa 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -85,6 +85,7 @@ impl<'a> FormatSummaryValues<'a> { arg, value, range: _, + node_index: _, } = keyword; let key = arg.as_ref()?; if contains_quotes(locator.slice(value)) || locator.contains_line_break(value.range()) { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs index abdc427515607d..9b1fd268019b43 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs @@ -64,8 +64,10 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &Checker, decorator_list: &[D args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, }) = &decorator.expression else { continue; @@ -85,6 +87,7 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &Checker, decorator_list: &[D arg, value, range: _, + node_index: _, } = &keywords[0]; if arg.as_ref().is_some_and(|arg| arg == "maxsize") && value.is_none_literal_expr() { let mut diagnostic = checker.report_diagnostic( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index c4c84ddd90f41a..89416772198d66 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -59,6 +59,7 @@ pub(crate) fn lru_cache_without_parameters(checker: &Checker, decorator_list: &[ func, arguments, range: _, + node_index: _, }) = &decorator.expression else { continue; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs index 5b91362679f764..4b2bbb1068d2de 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs @@ -39,23 +39,27 @@ impl LiteralType { LiteralType::Str => ast::StringLiteral { value: Box::default(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags: checker.default_string_flags(), } .into(), LiteralType::Bytes => ast::BytesLiteral { value: Box::default(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), flags: checker.default_bytes_flags(), } .into(), LiteralType::Int => ast::ExprNumberLiteral { value: ast::Number::Int(Int::from(0u8)), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), LiteralType::Float => ast::ExprNumberLiteral { value: ast::Number::Float(0.0), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), LiteralType::Bool => ast::ExprBooleanLiteral::default().into(), @@ -160,8 +164,10 @@ pub(crate) fn native_literals( args, keywords, range: _, + node_index: _, }, range: call_range, + node_index: _, } = call; if !keywords.is_empty() || args.len() > 1 { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs index 286fb88d4e1118..c08a49462f1414 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs @@ -116,6 +116,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] id: Name::new_static("OSError"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; remaining.insert(0, node.into()); } @@ -127,6 +128,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] elts: remaining, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }; format!("({})", checker.generator().expr(&node.into())) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index 025d9be062896c..c8a44d8d5cc661 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -93,6 +93,7 @@ pub(crate) fn outdated_version_block(checker: &Checker, stmt_if: &StmtIf) { ops, comparators, range: _, + node_index: _, }) = &branch.test else { continue; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs index fc6a242c8584cf..e364056de37d67 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs @@ -140,12 +140,14 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam { TypeParamKind::TypeVar => { TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), name: Identifier::new(*name, TextRange::default()), bound: match restriction { Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())), Some(TypeVarRestriction::Constraint(constraints)) => { Some(Box::new(Expr::Tuple(ast::ExprTuple { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), elts: constraints.iter().map(|expr| (*expr).clone()).collect(), ctx: ast::ExprContext::Load, parenthesized: true, @@ -154,14 +156,17 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam { Some(TypeVarRestriction::AnyStr) => { Some(Box::new(Expr::Tuple(ast::ExprTuple { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), elts: vec![ Expr::Name(ExprName { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), id: Name::from("str"), ctx: ast::ExprContext::Load, }), Expr::Name(ExprName { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), id: Name::from("bytes"), ctx: ast::ExprContext::Load, }), @@ -179,11 +184,13 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam { } TypeParamKind::TypeVarTuple => TypeParam::TypeVarTuple(TypeParamTypeVarTuple { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), name: Identifier::new(*name, TextRange::default()), default: None, }), TypeParamKind::ParamSpec => TypeParam::ParamSpec(TypeParamParamSpec { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), name: Identifier::new(*name, TextRange::default()), default: None, }), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index bcb584df8a158b..1faffd5a716163 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -231,7 +231,12 @@ fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> { fn clean_params_dictionary(right: &Expr, locator: &Locator, stylist: &Stylist) -> Option { let is_multi_line = locator.contains_line_break(right.range()); let mut contents = String::new(); - if let Expr::Dict(ast::ExprDict { items, range: _ }) = &right { + if let Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) = &right + { let mut arguments: Vec = vec![]; let mut seen: Vec<&str> = vec![]; let mut indent = None; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index f1f8d921461f98..63dc4a363fff16 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -128,6 +128,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] id: Name::new_static("TimeoutError"), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; remaining.insert(0, node.into()); } @@ -139,6 +140,7 @@ fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr] elts: remaining, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }; format!("({})", checker.generator().expr(&node.into())) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs index 3c11602ffb6d79..ed1cba9f53df76 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs @@ -83,6 +83,7 @@ pub(crate) fn unnecessary_default_type_args(checker: &Checker, expr: &Expr) { elts, ctx: _, range: _, + node_index: _, parenthesized: _, }) = slice.as_ref() else { @@ -126,11 +127,13 @@ pub(crate) fn unnecessary_default_type_args(checker: &Checker, expr: &Expr) { elts: valid_elts, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }) }), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), expr.range(), ), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs index e616d4a5c95ef4..e7f88cd5535b00 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/yield_in_for_loop.rs @@ -78,6 +78,7 @@ pub(crate) fn yield_in_for_loop(checker: &Checker, stmt_for: &ast::StmtFor) { orelse, is_async: _, range: _, + node_index: _, } = stmt_for; // If there is an else statement, don't rewrite. @@ -91,12 +92,18 @@ pub(crate) fn yield_in_for_loop(checker: &Checker, stmt_for: &ast::StmtFor) { }; // If the body is not a yield, don't rewrite. - let Stmt::Expr(ast::StmtExpr { value, range: _ }) = &body else { + let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = &body + else { return; }; let Expr::Yield(ast::ExprYield { value: Some(value), range: _, + node_index: _, }) = value.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index 34ed1804ff9dcf..cd0ddd0b13f575 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -15,6 +15,7 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato id: name, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Construct `name.method`. let attr = ast::ExprAttribute { @@ -22,6 +23,7 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato attr: ast::Identifier::new(method.to_string(), TextRange::default()), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Make it into a call `name.method()` let call = ast::ExprCall { @@ -30,13 +32,16 @@ pub(super) fn generate_method_call(name: Name, method: &str, generator: Generato args: Box::from([]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // And finally, turn it into a statement. let stmt = ast::StmtExpr { value: Box::new(call.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; generator.stmt(&stmt.into()) } @@ -62,6 +67,7 @@ pub(super) fn replace_with_identity_check( ops: [op].into(), comparators: [ast::ExprNoneLiteral::default().into()].into(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let new_content = generator.expr(&new_expr); diff --git a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs index 00ea156590cffa..ed2494f24bfcdc 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/check_and_remove_from_set.rs @@ -185,6 +185,7 @@ fn make_suggestion(set: &ast::ExprName, element: &Expr, generator: Generator) -> attr: ast::Identifier::new("discard".to_string(), TextRange::default()), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Make the actual call `set.discard(element)` let call = ast::ExprCall { @@ -193,13 +194,16 @@ fn make_suggestion(set: &ast::ExprName, element: &Expr, generator: Generator) -> args: Box::from([element.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // And finally, turn it into a statement. let stmt = ast::StmtExpr { value: Box::new(call.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; generator.stmt(&stmt.into()) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs index 0d8ec381df1862..2fce83de338af2 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/delete_full_slice.rs @@ -93,6 +93,7 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a upper: None, step: None, range: _, + node_index: _, }) ) { return None; diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs index 7671a05b3aa660..51047f15bba73f 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs @@ -61,6 +61,7 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &Checker, if_expr: &ast::Ex body, orelse, range, + node_index: _, } = if_expr; if ComparableExpr::from(test) != ComparableExpr::from(body) { diff --git a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs index 0115f742d52aef..a5282a5bcebc3f 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs @@ -130,6 +130,7 @@ fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> SourceCodeSnipp id: open.mode.pathlib_method(), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let call = ast::ExprCall { func: Box::new(name.into()), @@ -137,8 +138,10 @@ fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> SourceCodeSnipp args: Box::from([]), keywords: open.keywords.iter().copied().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; SourceCodeSnippet::from_str(&generator.expr(&call.into())) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs index 3fcde5bc326c66..a45d768f5346ed 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_starmap.rs @@ -298,6 +298,7 @@ fn construct_starmap_call(starmap_binding: Name, iter: &Expr, func: &Expr) -> as id: starmap_binding, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; ast::ExprCall { func: Box::new(starmap.into()), @@ -305,8 +306,10 @@ fn construct_starmap_call(starmap_binding: Name, iter: &Expr, func: &Expr) -> as args: Box::from([func.clone(), iter.clone()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } } @@ -316,6 +319,7 @@ fn wrap_with_call_to(call: ast::ExprCall, func_name: Name) -> ast::ExprCall { id: func_name, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; ast::ExprCall { func: Box::new(name.into()), @@ -323,8 +327,10 @@ fn wrap_with_call_to(call: ast::ExprCall, func_name: Name) -> ast::ExprCall { args: Box::from([call.into()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs index 6674bf552dc87c..d3c8259ec9d96e 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/repeated_append.rs @@ -342,6 +342,7 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String { elts, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, }; // Make `var.extend`. @@ -351,6 +352,7 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String { attr: ast::Identifier::new("extend".to_string(), TextRange::default()), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Make the actual call `var.extend((elt1, elt2, ..., eltN))` let call = ast::ExprCall { @@ -359,13 +361,16 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String { args: Box::from([tuple.into()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // And finally, turn it into a statement. let stmt = ast::StmtExpr { value: Box::new(call.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; generator.stmt(&stmt.into()) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index f0b9930bf29b4e..e581bc7a1269d4 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs @@ -129,6 +129,7 @@ fn single_item<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a Ex func, arguments, range: _, + node_index: _, }) => { if arguments.len() != 1 || !is_set_method(func, semantic) { return None; diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs index 3b89ce2ebe474b..91b79038898336 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_copy.rs @@ -83,6 +83,7 @@ fn match_list_full_slice<'a>( upper: None, step: None, range: _, + node_index: _, }) ) { return None; diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs index 671cfb0fc9280f..427af68ccf93ce 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs @@ -145,6 +145,7 @@ fn affix_removal_data_expr(if_expr: &ast::ExprIf) -> Option { body, orelse, range: _, + node_index: _, } = if_expr; let ast::ExprSubscript { value, slice, .. } = body.as_subscript_expr()?; @@ -171,6 +172,7 @@ fn affix_removal_data_stmt(if_stmt: &ast::StmtIf) -> Option { body, elif_else_clauses, range: _, + node_index: _, } = if_stmt; // Cannot safely transform, e.g., @@ -203,6 +205,7 @@ fn affix_removal_data_stmt(if_stmt: &ast::StmtIf) -> Option { value, targets, range: _, + node_index: _, } = statement.as_assign_stmt()?; let [target] = targets.as_slice() else { return None; @@ -325,9 +328,11 @@ fn affix_matches_slice_bound(data: &RemoveAffixData, semantic: &SemanticModel) - ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, range: _, + node_index: _, }), ast::Expr::StringLiteral(ast::ExprStringLiteral { range: _, + node_index: _, value: string_val, }), ) => num @@ -339,6 +344,7 @@ fn affix_matches_slice_bound(data: &RemoveAffixData, semantic: &SemanticModel) - AffixKind::StartsWith, ast::Expr::Call(ast::ExprCall { range: _, + node_index: _, func, arguments, }), @@ -358,9 +364,11 @@ fn affix_matches_slice_bound(data: &RemoveAffixData, semantic: &SemanticModel) - op: ast::UnaryOp::USub, operand, range: _, + node_index: _, }), ast::Expr::StringLiteral(ast::ExprStringLiteral { range: _, + node_index: _, value: string_val, }), ) if operand.is_number_literal_expr() => operand.as_number_literal_expr().is_some_and( @@ -378,11 +386,13 @@ fn affix_matches_slice_bound(data: &RemoveAffixData, semantic: &SemanticModel) - op: ast::UnaryOp::USub, operand, range: _, + node_index: _, }), _, ) => operand.as_call_expr().is_some_and( |ast::ExprCall { range: _, + node_index: _, func, arguments, }| { diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs index 3313fc8c1805b0..db8e3fb1c3bc03 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_enumerate.rs @@ -232,6 +232,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String { id: name, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Construct `len(name)`. let len = ast::ExprCall { @@ -240,6 +241,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String { id: Name::new_static("len"), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -247,8 +249,10 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String { args: Box::from([var.into()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Construct `range(len(name))`. let range = ast::ExprCall { @@ -257,6 +261,7 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String { id: Name::new_static("range"), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), ), @@ -264,13 +269,16 @@ fn generate_range_len_call(name: Name, generator: Generator) -> String { args: Box::from([len.into()]), keywords: Box::from([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // And finally, turn it into a statement. let stmt = ast::StmtExpr { value: Box::new(range.into()), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; generator.stmt(&stmt.into()) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index 705cd25b8fffa4..4e7e9f20d786b5 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -148,6 +148,7 @@ fn make_suggestion(open: &FileOpen<'_>, arg: &Expr, generator: Generator) -> Sou id: open.mode.pathlib_method(), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let mut arg = arg.clone(); relocate_expr(&mut arg, TextRange::default()); @@ -157,8 +158,10 @@ fn make_suggestion(open: &FileOpen<'_>, arg: &Expr, generator: Generator) -> Sou args: Box::new([arg]), keywords: open.keywords.iter().copied().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; SourceCodeSnippet::from_str(&generator.expr(&call.into())) } diff --git a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs index 8b3fad38dcaa05..41f72bbdc5ea74 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/assert_with_print_message.rs @@ -68,6 +68,7 @@ pub(crate) fn assert_with_print_message(checker: &Checker, stmt: &ast::StmtAsser test: stmt.test.clone(), msg: print_arguments::to_expr(&call.arguments, checker).map(Box::new), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), // We have to replace the entire statement, // as the `print` could be empty and thus `call.range()` @@ -113,6 +114,7 @@ mod print_arguments { InterpolatedStringElement::Literal(InterpolatedStringLiteralElement { value: part.value.clone(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) }) .collect(), @@ -129,6 +131,7 @@ mod print_arguments { conversion: ConversionFlag::None, format_spec: None, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, )], } @@ -151,6 +154,7 @@ mod print_arguments { value: literal.value.clone(), flags, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some(acc) } else { @@ -208,6 +212,7 @@ mod print_arguments { value: combined_string.into(), flags, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) } @@ -241,8 +246,10 @@ mod print_arguments { elements: InterpolatedStringElements::from(fstring_elements), flags, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) } @@ -278,6 +285,7 @@ mod print_arguments { vec![InterpolatedStringElement::Literal( InterpolatedStringLiteralElement { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), value: " ".into(), }, )] diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index 566b9e822e889d..ed18e22a87c8f3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -79,6 +79,7 @@ fn make_splat_elts( value: Box::from(splat_element.clone()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; let splat = node.into(); if splat_at_left { @@ -102,6 +103,7 @@ fn concatenate_expressions(expr: &Expr, should_support_slices: bool) -> Option<( op: Operator::Add, right, range: _, + node_index: _, }) = expr else { return None; @@ -171,12 +173,14 @@ fn concatenate_expressions(expr: &Expr, should_support_slices: bool) -> Option<( elts: new_elts, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), } .into(), Type::Tuple => ast::ExprTuple { elts: new_elts, ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, } .into(), diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs index fa88d0b0836b02..0d396bdf5089ed 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs @@ -75,6 +75,7 @@ pub(crate) fn explicit_f_string_type_conversion(checker: &Checker, f_string: &as args, keywords, range: _, + node_index: _, }, .. }) = expression.as_ref() diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs index 7a259f92a0b02e..9cf78e1ddf378d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs @@ -133,6 +133,7 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr) op: Operator::BitOr, right: Box::new(Expr::NoneLiteral(ast::ExprNoneLiteral::default())), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); let content = checker.generator().expr(&new_expr); Ok(Fix::unsafe_edit(Edit::range_replacement( @@ -147,10 +148,12 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr) let (import_edit, binding) = importer.import(expr.start())?; let new_expr = Expr::Subscript(ast::ExprSubscript { range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), value: Box::new(Expr::Name(ast::ExprName { id: Name::new(binding), ctx: ast::ExprContext::Store, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })), slice: Box::new(expr.clone()), ctx: ast::ExprContext::Load, diff --git a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs index c19dc7bde459e0..30f4ff8bc71f9d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/in_empty_collection.rs @@ -83,6 +83,7 @@ fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool { func, arguments, range: _, + node_index: _, }) => { if arguments.is_empty() { collection_methods diff --git a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs index 590273f6713726..4b7fa7e69fcd53 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/map_int_version_parsing.rs @@ -68,8 +68,10 @@ fn map_call_with_two_arguments<'a>( args, keywords, range: _, + node_index: _, }, range: _, + node_index: _, } = call; if !keywords.is_empty() { @@ -100,8 +102,11 @@ fn is_dunder_version_split_dot(expr: &ast::Expr) -> bool { return false; } - let Some(ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ })) = - arguments.find_argument_value("sep", 0) + let Some(ast::Expr::StringLiteral(ast::ExprStringLiteral { + value, + range: _, + node_index: _, + })) = arguments.find_argument_value("sep", 0) else { return false; }; diff --git a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs index 7ec204fd61b1b0..5b60d38c1a7ff1 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mutable_fromkeys_value.rs @@ -102,6 +102,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator) id: Name::new_static("key"), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; // Construct `key in keys`. let comp = ast::Comprehension { @@ -109,6 +110,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator) iter: keys.clone(), ifs: vec![], range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), is_async: false, }; // Construct the dict comprehension. @@ -117,6 +119,7 @@ fn generate_dict_comprehension(keys: &Expr, value: &Expr, generator: Generator) value: Box::new(value.clone()), generators: vec![comp], range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }; generator.expr(&dict_comp.into()) } diff --git a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs index 34cf80384b5f80..8be1156a0a19c2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/never_union.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/never_union.rs @@ -74,6 +74,7 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { left, right, range: _, + node_index: _, }) => { // Analyze the left-hand side of the `|` operator. if let Some(never_like) = NeverLike::from_expr(left, checker.semantic()) { @@ -121,6 +122,7 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { slice, ctx: _, range: _, + node_index: _, }) if checker.semantic().match_typing_expr(value, "Union") => { let Expr::Tuple(tuple_slice) = &**slice else { return; @@ -162,10 +164,12 @@ pub(crate) fn never_union(checker: &Checker, expr: &Expr) { elts: rest, ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), parenthesized: true, })), ctx: ast::ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), })) }, expr.range(), diff --git a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs index fef61601fbfd55..4321e004c3b24c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/quadratic_list_summation.rs @@ -79,6 +79,7 @@ pub(crate) fn quadratic_list_summation(checker: &Checker, call: &ast::ExprCall) func, arguments, range, + node_index: _, } = call; let Some(iterable) = arguments.args.first() else { diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index 3ee20c168b7468..64b7f2b86eae50 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -223,7 +223,11 @@ impl<'a> StringLiteralDisplay<'a> { kind, } } - ast::Expr::Set(ast::ExprSet { elts, range }) => { + ast::Expr::Set(ast::ExprSet { + elts, + range, + node_index: _, + }) => { let kind = DisplayKind::Sequence(SequenceKind::Set); Self { elts: Cow::Borrowed(elts), diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs index 0dbdc44d86d419..5e06dd8ad1cd02 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs @@ -116,12 +116,14 @@ pub(crate) fn unnecessary_nested_literal<'a>(checker: &Checker, literal_expr: &' Expr::Tuple(ExprTuple { elts: nodes.into_iter().cloned().collect(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, parenthesized: false, }) }), value: subscript.value.clone(), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }); let fix = Fix::applicable_edit( diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 619ec3ac04ab86..7656a494fa738c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -277,6 +277,7 @@ impl<'a> ReFunc<'a> { op: UnaryOp::Not, operand: Box::new(expr), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Some(negated_expr) } @@ -300,6 +301,7 @@ impl<'a> ReFunc<'a> { ops: Box::new([op]), comparators: Box::new([right.clone()]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } @@ -311,6 +313,7 @@ impl<'a> ReFunc<'a> { attr: Identifier::new(method, TextRange::default()), ctx: ExprContext::Load, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }); Expr::Call(ExprCall { func: Box::new(method), @@ -318,8 +321,10 @@ impl<'a> ReFunc<'a> { args: args.into_boxed_slice(), keywords: Box::new([]), range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }, range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::dummy(), }) } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs index 261ee446c301ce..2c5396f8b5e322 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/zip_instead_of_pairwise.rs @@ -94,6 +94,7 @@ fn match_slice_info(expr: &Expr) -> Option { let Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(int), range: _, + node_index: _, }) = lower.as_ref() else { return None; diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs index c27be65d8a4a3d..feb5b7eb6c52e1 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/try_consider_else.rs @@ -67,7 +67,12 @@ pub(crate) fn try_consider_else( ) { if body.len() > 1 && orelse.is_empty() && !handler.is_empty() { if let Some(stmt) = body.last() { - if let Stmt::Return(ast::StmtReturn { value, range: _ }) = stmt { + if let Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) = stmt + { if let Some(value) = value { if contains_effect(value, |id| checker.semantic().has_builtin_binding(id)) { return; diff --git a/crates/ruff_python_ast/generate.py b/crates/ruff_python_ast/generate.py index 981e23fa095633..fd62df619e0774 100644 --- a/crates/ruff_python_ast/generate.py +++ b/crates/ruff_python_ast/generate.py @@ -300,9 +300,11 @@ def write_owned_enum(out: list[str], ast: Ast) -> None: Also creates: - `impl Ranged for TypeParam` + - `impl HasNodeIndex for TypeParam` - `TypeParam::visit_source_order` - `impl From for TypeParam` - `impl Ranged for TypeParamTypeVar` + - `impl HasNodeIndex for TypeParamTypeVar` - `fn TypeParam::is_type_var() -> bool` If the `add_suffix_to_is_methods` group option is true, then the @@ -341,6 +343,19 @@ def write_owned_enum(out: list[str], ast: Ast) -> None: } """) + out.append(f""" + impl crate::HasNodeIndex for {group.owned_enum_ty} {{ + fn node_index(&self) -> &crate::AtomicNodeIndex {{ + match self {{ + """) + for node in group.nodes: + out.append(f"Self::{node.variant}(node) => node.node_index(),") + out.append(""" + } + } + } + """) + out.append( "#[allow(dead_code, clippy::match_wildcard_for_single_variants)]" ) # Not all is_methods are used @@ -437,6 +452,15 @@ def write_owned_enum(out: list[str], ast: Ast) -> None: }} """) + for node in ast.all_nodes: + out.append(f""" + impl crate::HasNodeIndex for {node.ty} {{ + fn node_index(&self) -> &crate::AtomicNodeIndex {{ + &self.node_index + }} + }} + """) + for group in ast.groups: out.append(f""" impl {group.owned_enum_ty} {{ @@ -478,6 +502,7 @@ def write_ref_enum(out: list[str], ast: Ast) -> None: - `impl<'a> From<&'a TypeParam> for TypeParamRef<'a>` - `impl<'a> From<&'a TypeParamTypeVar> for TypeParamRef<'a>` - `impl Ranged for TypeParamRef<'_>` + - `impl HasNodeIndex for TypeParamRef<'_>` - `fn TypeParamRef::is_type_var() -> bool` The name of each variant can be customized via the `variant` node option. If @@ -535,6 +560,19 @@ def write_ref_enum(out: list[str], ast: Ast) -> None: } """) + out.append(f""" + impl crate::HasNodeIndex for {group.ref_enum_ty}<'_> {{ + fn node_index(&self) -> &crate::AtomicNodeIndex {{ + match self {{ + """) + for node in group.nodes: + out.append(f"Self::{node.variant}(node) => node.node_index(),") + out.append(""" + } + } + } + """) + # ------------------------------------------------------------------------------ # AnyNodeRef @@ -558,11 +596,13 @@ def write_anynoderef(out: list[str], ast: Ast) -> None: - `impl<'a> From> for AnyNodeRef<'a>` - `impl<'a> From<&'a TypeParamTypeVarTuple> for AnyNodeRef<'a>` - `impl Ranged for AnyNodeRef<'_>` + - `impl HasNodeIndex for AnyNodeRef<'_>` - `fn AnyNodeRef::as_ptr(&self) -> std::ptr::NonNull<()>` - `fn AnyNodeRef::visit_source_order(self, visitor &mut impl SourceOrderVisitor)` """ out.append(""" + /// A flattened enumeration of all AST nodes. #[derive(Copy, Clone, Debug, is_macro::Is, PartialEq)] pub enum AnyNodeRef<'a> { """) @@ -642,6 +682,19 @@ def write_anynoderef(out: list[str], ast: Ast) -> None: } """) + out.append(""" + impl crate::HasNodeIndex for AnyNodeRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + """) + for node in ast.all_nodes: + out.append(f"""AnyNodeRef::{node.name}(node) => node.node_index(),""") + out.append(""" + } + } + } + """) + out.append(""" impl AnyNodeRef<'_> { pub fn as_ptr(&self) -> std::ptr::NonNull<()> { @@ -693,6 +746,161 @@ def write_anynoderef(out: list[str], ast: Ast) -> None: """) +# ------------------------------------------------------------------------------ +# AnyRootNodeRef + + +def write_root_anynoderef(out: list[str], ast: Ast) -> None: + """ + Create the AnyRootNodeRef type. + + ```rust + pub enum AnyRootNodeRef<'a> { + ... + TypeParam(&'a TypeParam), + ... + } + ``` + + Also creates: + - `impl<'a> From<&'a TypeParam> for AnyRootNodeRef<'a>` + - `impl<'a> TryFrom> for &'a TypeParam` + - `impl<'a> TryFrom> for &'a TypeParamVarTuple` + - `impl Ranged for AnyRootNodeRef<'_>` + - `impl HasNodeIndex for AnyRootNodeRef<'_>` + - `fn AnyRootNodeRef::visit_source_order(self, visitor &mut impl SourceOrderVisitor)` + """ + + out.append(""" + /// An enumeration of all AST nodes. + /// + /// Unlike `AnyNodeRef`, this type does not flatten nested enums, so its variants only + /// consist of the "root" AST node types. This is useful as it exposes references to the + /// original enums, not just references to their inner values. + /// + /// For example, `AnyRootNodeRef::Mod` contains a reference to the `Mod` enum, while + /// `AnyNodeRef` has top-level `AnyNodeRef::ModModule` and `AnyNodeRef::ModExpression` + /// variants. + #[derive(Copy, Clone, Debug, PartialEq)] + pub enum AnyRootNodeRef<'a> { + """) + for group in ast.groups: + out.append(f"""{group.name}(&'a {group.owned_enum_ty}),""") + for node in ast.ungrouped_nodes: + out.append(f"""{node.name}(&'a {node.ty}),""") + out.append(""" + } + """) + + for group in ast.groups: + out.append(f""" + impl<'a> From<&'a {group.owned_enum_ty}> for AnyRootNodeRef<'a> {{ + fn from(node: &'a {group.owned_enum_ty}) -> AnyRootNodeRef<'a> {{ + AnyRootNodeRef::{group.name}(node) + }} + }} + """) + + out.append(f""" + impl<'a> TryFrom> for &'a {group.owned_enum_ty} {{ + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a {group.owned_enum_ty}, ()> {{ + match node {{ + AnyRootNodeRef::{group.name}(node) => Ok(node), + _ => Err(()) + }} + }} + }} + """) + + for node in group.nodes: + out.append(f""" + impl<'a> TryFrom> for &'a {node.ty} {{ + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a {node.ty}, ()> {{ + match node {{ + AnyRootNodeRef::{group.name}({group.owned_enum_ty}::{node.variant}(node)) => Ok(node), + _ => Err(()) + }} + }} + }} + """) + + for node in ast.ungrouped_nodes: + out.append(f""" + impl<'a> From<&'a {node.ty}> for AnyRootNodeRef<'a> {{ + fn from(node: &'a {node.ty}) -> AnyRootNodeRef<'a> {{ + AnyRootNodeRef::{node.name}(node) + }} + }} + """) + + out.append(f""" + impl<'a> TryFrom> for &'a {node.ty} {{ + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a {node.ty}, ()> {{ + match node {{ + AnyRootNodeRef::{node.name}(node) => Ok(node), + _ => Err(()) + }} + }} + }} + """) + + out.append(""" + impl ruff_text_size::Ranged for AnyRootNodeRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + """) + for group in ast.groups: + out.append(f"""AnyRootNodeRef::{group.name}(node) => node.range(),""") + for node in ast.ungrouped_nodes: + out.append(f"""AnyRootNodeRef::{node.name}(node) => node.range(),""") + out.append(""" + } + } + } + """) + + out.append(""" + impl crate::HasNodeIndex for AnyRootNodeRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + """) + for group in ast.groups: + out.append(f"""AnyRootNodeRef::{group.name}(node) => node.node_index(),""") + for node in ast.ungrouped_nodes: + out.append(f"""AnyRootNodeRef::{node.name}(node) => node.node_index(),""") + out.append(""" + } + } + } + """) + + out.append(""" + impl<'a> AnyRootNodeRef<'a> { + pub fn visit_source_order<'b, V>(self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'b> + ?Sized, + 'a: 'b, + { + match self { + """) + for group in ast.groups: + out.append( + f"""AnyRootNodeRef::{group.name}(node) => node.visit_source_order(visitor),""" + ) + for node in ast.ungrouped_nodes: + out.append( + f"""AnyRootNodeRef::{node.name}(node) => node.visit_source_order(visitor),""" + ) + out.append(""" + } + } + } + """) + + # ------------------------------------------------------------------------------ # NodeKind @@ -757,6 +965,7 @@ def write_node(out: list[str], ast: Ast) -> None: ) name = node.name out.append(f"pub struct {name} {{") + out.append("pub node_index: crate::AtomicNodeIndex,") out.append("pub range: ruff_text_size::TextRange,") for field in node.fields: field_str = f"pub {field.name}: " @@ -800,6 +1009,7 @@ def write_source_order(out: list[str], ast: Ast) -> None: else: fields_list += f"{field.name},\n" fields_list += "range: _,\n" + fields_list += "node_index: _,\n" for field in node.fields_in_source_order(): visitor_name = ( @@ -859,6 +1069,7 @@ def generate(ast: Ast) -> list[str]: write_owned_enum(out, ast) write_ref_enum(out, ast) write_anynoderef(out, ast) + write_root_anynoderef(out, ast) write_nodekind(out, ast) write_node(out, ast) write_source_order(out, ast) diff --git a/crates/ruff_python_ast/src/comparable.rs b/crates/ruff_python_ast/src/comparable.rs index a759ef208af2ea..170e20ebc75eac 100644 --- a/crates/ruff_python_ast/src/comparable.rs +++ b/crates/ruff_python_ast/src/comparable.rs @@ -547,6 +547,7 @@ impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> { conversion, format_spec, range: _, + node_index: _, } = interpolated_element; Self { @@ -576,6 +577,7 @@ impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> { fn from(elif_else_clause: &'a ast::ElifElseClause) -> Self { let ast::ElifElseClause { range: _, + node_index: _, test, body, } = elif_else_clause; @@ -1109,6 +1111,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { op, values, range: _, + node_index: _, }) => Self::BoolOp(ExprBoolOp { op: (*op).into(), values: values.iter().map(Into::into).collect(), @@ -1117,6 +1120,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { target, value, range: _, + node_index: _, }) => Self::NamedExpr(ExprNamed { target: target.into(), value: value.into(), @@ -1126,6 +1130,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { op, right, range: _, + node_index: _, }) => Self::BinOp(ExprBinOp { left: left.into(), op: (*op).into(), @@ -1135,6 +1140,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { op, operand, range: _, + node_index: _, }) => Self::UnaryOp(ExprUnaryOp { op: (*op).into(), operand: operand.into(), @@ -1143,6 +1149,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { parameters, body, range: _, + node_index: _, }) => Self::Lambda(ExprLambda { parameters: parameters.as_ref().map(Into::into), body: body.into(), @@ -1152,21 +1159,31 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { body, orelse, range: _, + node_index: _, }) => Self::IfExp(ExprIf { test: test.into(), body: body.into(), orelse: orelse.into(), }), - ast::Expr::Dict(ast::ExprDict { items, range: _ }) => Self::Dict(ExprDict { + ast::Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => Self::Dict(ExprDict { items: items.iter().map(ComparableDictItem::from).collect(), }), - ast::Expr::Set(ast::ExprSet { elts, range: _ }) => Self::Set(ExprSet { + ast::Expr::Set(ast::ExprSet { + elts, + range: _, + node_index: _, + }) => Self::Set(ExprSet { elts: elts.iter().map(Into::into).collect(), }), ast::Expr::ListComp(ast::ExprListComp { elt, generators, range: _, + node_index: _, }) => Self::ListComp(ExprListComp { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), @@ -1175,6 +1192,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { elt, generators, range: _, + node_index: _, }) => Self::SetComp(ExprSetComp { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), @@ -1184,6 +1202,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { value, generators, range: _, + node_index: _, }) => Self::DictComp(ExprDictComp { key: key.into(), value: value.into(), @@ -1193,27 +1212,39 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { elt, generators, range: _, + node_index: _, parenthesized: _, }) => Self::GeneratorExp(ExprGenerator { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), }), - ast::Expr::Await(ast::ExprAwait { value, range: _ }) => Self::Await(ExprAwait { + ast::Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) => Self::Await(ExprAwait { value: value.into(), }), - ast::Expr::Yield(ast::ExprYield { value, range: _ }) => Self::Yield(ExprYield { + ast::Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => Self::Yield(ExprYield { value: value.as_ref().map(Into::into), }), - ast::Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) => { - Self::YieldFrom(ExprYieldFrom { - value: value.into(), - }) - } + ast::Expr::YieldFrom(ast::ExprYieldFrom { + value, + range: _, + node_index: _, + }) => Self::YieldFrom(ExprYieldFrom { + value: value.into(), + }), ast::Expr::Compare(ast::ExprCompare { left, ops, comparators, range: _, + node_index: _, }) => Self::Compare(ExprCompare { left: left.into(), ops: ops.iter().copied().map(Into::into).collect(), @@ -1223,42 +1254,55 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { func, arguments, range: _, + node_index: _, }) => Self::Call(ExprCall { func: func.into(), arguments: arguments.into(), }), - ast::Expr::FString(ast::ExprFString { value, range: _ }) => { - Self::FString(ExprFString { - value: value.into(), - }) - } - ast::Expr::TString(ast::ExprTString { value, range: _ }) => { - Self::TString(ExprTString { - value: value.into(), - }) - } - ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) => { - Self::StringLiteral(ExprStringLiteral { - value: ComparableStringLiteral { - value: value.to_str(), - }, - }) - } - ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, range: _ }) => { - Self::BytesLiteral(ExprBytesLiteral { - value: ComparableBytesLiteral { - value: Cow::from(value), - }, - }) - } - ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, range: _ }) => { - Self::NumberLiteral(ExprNumberLiteral { - value: value.into(), - }) - } - ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, range: _ }) => { - Self::BoolLiteral(ExprBoolLiteral { value: *value }) - } + ast::Expr::FString(ast::ExprFString { + value, + range: _, + node_index: _, + }) => Self::FString(ExprFString { + value: value.into(), + }), + ast::Expr::TString(ast::ExprTString { + value, + range: _, + node_index: _, + }) => Self::TString(ExprTString { + value: value.into(), + }), + ast::Expr::StringLiteral(ast::ExprStringLiteral { + value, + range: _, + node_index: _, + }) => Self::StringLiteral(ExprStringLiteral { + value: ComparableStringLiteral { + value: value.to_str(), + }, + }), + ast::Expr::BytesLiteral(ast::ExprBytesLiteral { + value, + range: _, + node_index: _, + }) => Self::BytesLiteral(ExprBytesLiteral { + value: ComparableBytesLiteral { + value: Cow::from(value), + }, + }), + ast::Expr::NumberLiteral(ast::ExprNumberLiteral { + value, + range: _, + node_index: _, + }) => Self::NumberLiteral(ExprNumberLiteral { + value: value.into(), + }), + ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { + value, + range: _, + node_index: _, + }) => Self::BoolLiteral(ExprBoolLiteral { value: *value }), ast::Expr::NoneLiteral(_) => Self::NoneLiteral, ast::Expr::EllipsisLiteral(_) => Self::EllipsisLiteral, ast::Expr::Attribute(ast::ExprAttribute { @@ -1266,6 +1310,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { attr, ctx: _, range: _, + node_index: _, }) => Self::Attribute(ExprAttribute { value: value.into(), attr: attr.as_str(), @@ -1275,6 +1320,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { slice, ctx: _, range: _, + node_index: _, }) => Self::Subscript(ExprSubscript { value: value.into(), slice: slice.into(), @@ -1283,6 +1329,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { value, ctx: _, range: _, + node_index: _, }) => Self::Starred(ExprStarred { value: value.into(), }), @@ -1291,6 +1338,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { elts, ctx: _, range: _, + node_index: _, }) => Self::List(ExprList { elts: elts.iter().map(Into::into).collect(), }), @@ -1298,6 +1346,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { elts, ctx: _, range: _, + node_index: _, parenthesized: _, }) => Self::Tuple(ExprTuple { elts: elts.iter().map(Into::into).collect(), @@ -1307,6 +1356,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { upper, step, range: _, + node_index: _, }) => Self::Slice(ExprSlice { lower: lower.as_ref().map(Into::into), upper: upper.as_ref().map(Into::into), @@ -1316,6 +1366,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { kind, value, range: _, + node_index: _, }) => Self::IpyEscapeCommand(ExprIpyEscapeCommand { kind: *kind, value }), } } @@ -1400,6 +1451,7 @@ impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> { bound, default, range: _, + node_index: _, }) => Self::TypeVar(TypeParamTypeVar { name: name.as_str(), bound: bound.as_ref().map(Into::into), @@ -1409,6 +1461,7 @@ impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> { name, default, range: _, + node_index: _, }) => Self::TypeVarTuple(TypeParamTypeVarTuple { name: name.as_str(), default: default.as_ref().map(Into::into), @@ -1417,6 +1470,7 @@ impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> { name, default, range: _, + node_index: _, }) => Self::ParamSpec(TypeParamParamSpec { name: name.as_str(), default: default.as_ref().map(Into::into), @@ -1596,6 +1650,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { returns, type_params, range: _, + node_index: _, }) => Self::FunctionDef(StmtFunctionDef { is_async: *is_async, name: name.as_str(), @@ -1612,6 +1667,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { decorator_list, type_params, range: _, + node_index: _, }) => Self::ClassDef(StmtClassDef { name: name.as_str(), arguments: arguments.as_ref().map(Into::into).unwrap_or_default(), @@ -1619,14 +1675,23 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { decorator_list: decorator_list.iter().map(Into::into).collect(), type_params: type_params.as_ref().map(Into::into), }), - ast::Stmt::Return(ast::StmtReturn { value, range: _ }) => Self::Return(StmtReturn { + ast::Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => Self::Return(StmtReturn { value: value.as_ref().map(Into::into), }), - ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => Self::Delete(StmtDelete { + ast::Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => Self::Delete(StmtDelete { targets: targets.iter().map(Into::into).collect(), }), ast::Stmt::TypeAlias(ast::StmtTypeAlias { range: _, + node_index: _, name, type_params, value, @@ -1639,6 +1704,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { targets, value, range: _, + node_index: _, }) => Self::Assign(StmtAssign { targets: targets.iter().map(Into::into).collect(), value: value.into(), @@ -1648,6 +1714,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { op, value, range: _, + node_index: _, }) => Self::AugAssign(StmtAugAssign { target: target.into(), op: (*op).into(), @@ -1659,6 +1726,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { value, simple, range: _, + node_index: _, }) => Self::AnnAssign(StmtAnnAssign { target: target.into(), annotation: annotation.into(), @@ -1672,6 +1740,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { body, orelse, range: _, + node_index: _, }) => Self::For(StmtFor { is_async: *is_async, target: target.into(), @@ -1684,6 +1753,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { body, orelse, range: _, + node_index: _, }) => Self::While(StmtWhile { test: test.into(), body: body.iter().map(Into::into).collect(), @@ -1694,6 +1764,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { body, elif_else_clauses, range: _, + node_index: _, }) => Self::If(StmtIf { test: test.into(), body: body.iter().map(Into::into).collect(), @@ -1704,6 +1775,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { items, body, range: _, + node_index: _, }) => Self::With(StmtWith { is_async: *is_async, items: items.iter().map(Into::into).collect(), @@ -1713,6 +1785,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { subject, cases, range: _, + node_index: _, }) => Self::Match(StmtMatch { subject: subject.into(), cases: cases.iter().map(Into::into).collect(), @@ -1721,6 +1794,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { exc, cause, range: _, + node_index: _, }) => Self::Raise(StmtRaise { exc: exc.as_ref().map(Into::into), cause: cause.as_ref().map(Into::into), @@ -1732,6 +1806,7 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { finalbody, is_star, range: _, + node_index: _, }) => Self::Try(StmtTry { body: body.iter().map(Into::into).collect(), handlers: handlers.iter().map(Into::into).collect(), @@ -1743,11 +1818,16 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { test, msg, range: _, + node_index: _, }) => Self::Assert(StmtAssert { test: test.into(), msg: msg.as_ref().map(Into::into), }), - ast::Stmt::Import(ast::StmtImport { names, range: _ }) => Self::Import(StmtImport { + ast::Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => Self::Import(StmtImport { names: names.iter().map(Into::into).collect(), }), ast::Stmt::ImportFrom(ast::StmtImportFrom { @@ -1755,25 +1835,37 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { names, level, range: _, + node_index: _, }) => Self::ImportFrom(StmtImportFrom { module: module.as_deref(), names: names.iter().map(Into::into).collect(), level: *level, }), - ast::Stmt::Global(ast::StmtGlobal { names, range: _ }) => Self::Global(StmtGlobal { + ast::Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) => Self::Global(StmtGlobal { + names: names.iter().map(ast::Identifier::as_str).collect(), + }), + ast::Stmt::Nonlocal(ast::StmtNonlocal { + names, + range: _, + node_index: _, + }) => Self::Nonlocal(StmtNonlocal { names: names.iter().map(ast::Identifier::as_str).collect(), }), - ast::Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => { - Self::Nonlocal(StmtNonlocal { - names: names.iter().map(ast::Identifier::as_str).collect(), - }) - } ast::Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand { kind, value, range: _, + node_index: _, }) => Self::IpyEscapeCommand(StmtIpyEscapeCommand { kind: *kind, value }), - ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) => Self::Expr(StmtExpr { + ast::Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => Self::Expr(StmtExpr { value: value.into(), }), ast::Stmt::Pass(_) => Self::Pass, diff --git a/crates/ruff_python_ast/src/generated.rs b/crates/ruff_python_ast/src/generated.rs index 7fd7db50724a62..cedf608921d060 100644 --- a/crates/ruff_python_ast/src/generated.rs +++ b/crates/ruff_python_ast/src/generated.rs @@ -32,6 +32,15 @@ impl ruff_text_size::Ranged for Mod { } } +impl crate::HasNodeIndex for Mod { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::Module(node) => node.node_index(), + Self::Expression(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl Mod { #[inline] @@ -321,6 +330,38 @@ impl ruff_text_size::Ranged for Stmt { } } +impl crate::HasNodeIndex for Stmt { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::FunctionDef(node) => node.node_index(), + Self::ClassDef(node) => node.node_index(), + Self::Return(node) => node.node_index(), + Self::Delete(node) => node.node_index(), + Self::TypeAlias(node) => node.node_index(), + Self::Assign(node) => node.node_index(), + Self::AugAssign(node) => node.node_index(), + Self::AnnAssign(node) => node.node_index(), + Self::For(node) => node.node_index(), + Self::While(node) => node.node_index(), + Self::If(node) => node.node_index(), + Self::With(node) => node.node_index(), + Self::Match(node) => node.node_index(), + Self::Raise(node) => node.node_index(), + Self::Try(node) => node.node_index(), + Self::Assert(node) => node.node_index(), + Self::Import(node) => node.node_index(), + Self::ImportFrom(node) => node.node_index(), + Self::Global(node) => node.node_index(), + Self::Nonlocal(node) => node.node_index(), + Self::Expr(node) => node.node_index(), + Self::Pass(node) => node.node_index(), + Self::Break(node) => node.node_index(), + Self::Continue(node) => node.node_index(), + Self::IpyEscapeCommand(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl Stmt { #[inline] @@ -1525,6 +1566,46 @@ impl ruff_text_size::Ranged for Expr { } } +impl crate::HasNodeIndex for Expr { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::BoolOp(node) => node.node_index(), + Self::Named(node) => node.node_index(), + Self::BinOp(node) => node.node_index(), + Self::UnaryOp(node) => node.node_index(), + Self::Lambda(node) => node.node_index(), + Self::If(node) => node.node_index(), + Self::Dict(node) => node.node_index(), + Self::Set(node) => node.node_index(), + Self::ListComp(node) => node.node_index(), + Self::SetComp(node) => node.node_index(), + Self::DictComp(node) => node.node_index(), + Self::Generator(node) => node.node_index(), + Self::Await(node) => node.node_index(), + Self::Yield(node) => node.node_index(), + Self::YieldFrom(node) => node.node_index(), + Self::Compare(node) => node.node_index(), + Self::Call(node) => node.node_index(), + Self::FString(node) => node.node_index(), + Self::TString(node) => node.node_index(), + Self::StringLiteral(node) => node.node_index(), + Self::BytesLiteral(node) => node.node_index(), + Self::NumberLiteral(node) => node.node_index(), + Self::BooleanLiteral(node) => node.node_index(), + Self::NoneLiteral(node) => node.node_index(), + Self::EllipsisLiteral(node) => node.node_index(), + Self::Attribute(node) => node.node_index(), + Self::Subscript(node) => node.node_index(), + Self::Starred(node) => node.node_index(), + Self::Name(node) => node.node_index(), + Self::List(node) => node.node_index(), + Self::Tuple(node) => node.node_index(), + Self::Slice(node) => node.node_index(), + Self::IpyEscapeCommand(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl Expr { #[inline] @@ -2769,6 +2850,14 @@ impl ruff_text_size::Ranged for ExceptHandler { } } +impl crate::HasNodeIndex for ExceptHandler { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::ExceptHandler(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl ExceptHandler { #[inline] @@ -2832,6 +2921,15 @@ impl ruff_text_size::Ranged for InterpolatedStringElement { } } +impl crate::HasNodeIndex for InterpolatedStringElement { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::Interpolation(node) => node.node_index(), + Self::Literal(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl InterpolatedStringElement { #[inline] @@ -2985,6 +3083,21 @@ impl ruff_text_size::Ranged for Pattern { } } +impl crate::HasNodeIndex for Pattern { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::MatchValue(node) => node.node_index(), + Self::MatchSingleton(node) => node.node_index(), + Self::MatchSequence(node) => node.node_index(), + Self::MatchMapping(node) => node.node_index(), + Self::MatchClass(node) => node.node_index(), + Self::MatchStar(node) => node.node_index(), + Self::MatchAs(node) => node.node_index(), + Self::MatchOr(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl Pattern { #[inline] @@ -3320,6 +3433,16 @@ impl ruff_text_size::Ranged for TypeParam { } } +impl crate::HasNodeIndex for TypeParam { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::TypeVar(node) => node.node_index(), + Self::TypeVarTuple(node) => node.node_index(), + Self::ParamSpec(node) => node.node_index(), + } + } +} + #[allow(dead_code, clippy::match_wildcard_for_single_variants)] impl TypeParam { #[inline] @@ -3998,2283 +4121,3072 @@ impl ruff_text_size::Ranged for crate::Identifier { } } -impl Mod { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - Mod::Module(node) => node.visit_source_order(visitor), - Mod::Expression(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::ModModule { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl Stmt { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - Stmt::FunctionDef(node) => node.visit_source_order(visitor), - Stmt::ClassDef(node) => node.visit_source_order(visitor), - Stmt::Return(node) => node.visit_source_order(visitor), - Stmt::Delete(node) => node.visit_source_order(visitor), - Stmt::TypeAlias(node) => node.visit_source_order(visitor), - Stmt::Assign(node) => node.visit_source_order(visitor), - Stmt::AugAssign(node) => node.visit_source_order(visitor), - Stmt::AnnAssign(node) => node.visit_source_order(visitor), - Stmt::For(node) => node.visit_source_order(visitor), - Stmt::While(node) => node.visit_source_order(visitor), - Stmt::If(node) => node.visit_source_order(visitor), - Stmt::With(node) => node.visit_source_order(visitor), - Stmt::Match(node) => node.visit_source_order(visitor), - Stmt::Raise(node) => node.visit_source_order(visitor), - Stmt::Try(node) => node.visit_source_order(visitor), - Stmt::Assert(node) => node.visit_source_order(visitor), - Stmt::Import(node) => node.visit_source_order(visitor), - Stmt::ImportFrom(node) => node.visit_source_order(visitor), - Stmt::Global(node) => node.visit_source_order(visitor), - Stmt::Nonlocal(node) => node.visit_source_order(visitor), - Stmt::Expr(node) => node.visit_source_order(visitor), - Stmt::Pass(node) => node.visit_source_order(visitor), - Stmt::Break(node) => node.visit_source_order(visitor), - Stmt::Continue(node) => node.visit_source_order(visitor), - Stmt::IpyEscapeCommand(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::ModExpression { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl Expr { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - Expr::BoolOp(node) => node.visit_source_order(visitor), - Expr::Named(node) => node.visit_source_order(visitor), - Expr::BinOp(node) => node.visit_source_order(visitor), - Expr::UnaryOp(node) => node.visit_source_order(visitor), - Expr::Lambda(node) => node.visit_source_order(visitor), - Expr::If(node) => node.visit_source_order(visitor), - Expr::Dict(node) => node.visit_source_order(visitor), - Expr::Set(node) => node.visit_source_order(visitor), - Expr::ListComp(node) => node.visit_source_order(visitor), - Expr::SetComp(node) => node.visit_source_order(visitor), - Expr::DictComp(node) => node.visit_source_order(visitor), - Expr::Generator(node) => node.visit_source_order(visitor), - Expr::Await(node) => node.visit_source_order(visitor), - Expr::Yield(node) => node.visit_source_order(visitor), - Expr::YieldFrom(node) => node.visit_source_order(visitor), - Expr::Compare(node) => node.visit_source_order(visitor), - Expr::Call(node) => node.visit_source_order(visitor), - Expr::FString(node) => node.visit_source_order(visitor), - Expr::TString(node) => node.visit_source_order(visitor), - Expr::StringLiteral(node) => node.visit_source_order(visitor), - Expr::BytesLiteral(node) => node.visit_source_order(visitor), - Expr::NumberLiteral(node) => node.visit_source_order(visitor), - Expr::BooleanLiteral(node) => node.visit_source_order(visitor), - Expr::NoneLiteral(node) => node.visit_source_order(visitor), - Expr::EllipsisLiteral(node) => node.visit_source_order(visitor), - Expr::Attribute(node) => node.visit_source_order(visitor), - Expr::Subscript(node) => node.visit_source_order(visitor), - Expr::Starred(node) => node.visit_source_order(visitor), - Expr::Name(node) => node.visit_source_order(visitor), - Expr::List(node) => node.visit_source_order(visitor), - Expr::Tuple(node) => node.visit_source_order(visitor), - Expr::Slice(node) => node.visit_source_order(visitor), - Expr::IpyEscapeCommand(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::StmtFunctionDef { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ExceptHandler { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - ExceptHandler::ExceptHandler(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::StmtClassDef { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl InterpolatedStringElement { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - InterpolatedStringElement::Interpolation(node) => node.visit_source_order(visitor), - InterpolatedStringElement::Literal(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::StmtReturn { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl Pattern { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - Pattern::MatchValue(node) => node.visit_source_order(visitor), - Pattern::MatchSingleton(node) => node.visit_source_order(visitor), - Pattern::MatchSequence(node) => node.visit_source_order(visitor), - Pattern::MatchMapping(node) => node.visit_source_order(visitor), - Pattern::MatchClass(node) => node.visit_source_order(visitor), - Pattern::MatchStar(node) => node.visit_source_order(visitor), - Pattern::MatchAs(node) => node.visit_source_order(visitor), - Pattern::MatchOr(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::StmtDelete { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl TypeParam { - #[allow(unused)] - pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, - { - match self { - TypeParam::TypeVar(node) => node.visit_source_order(visitor), - TypeParam::TypeVarTuple(node) => node.visit_source_order(visitor), - TypeParam::ParamSpec(node) => node.visit_source_order(visitor), - } +impl crate::HasNodeIndex for crate::StmtTypeAlias { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum ModRef<'a> { - Module(&'a crate::ModModule), - Expression(&'a crate::ModExpression), +impl crate::HasNodeIndex for crate::StmtAssign { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } } -impl<'a> From<&'a Mod> for ModRef<'a> { - fn from(node: &'a Mod) -> Self { - match node { - Mod::Module(node) => ModRef::Module(node), - Mod::Expression(node) => ModRef::Expression(node), - } +impl crate::HasNodeIndex for crate::StmtAugAssign { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ModModule> for ModRef<'a> { - fn from(node: &'a crate::ModModule) -> Self { - Self::Module(node) +impl crate::HasNodeIndex for crate::StmtAnnAssign { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ModExpression> for ModRef<'a> { - fn from(node: &'a crate::ModExpression) -> Self { - Self::Expression(node) +impl crate::HasNodeIndex for crate::StmtFor { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for ModRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { - match self { - Self::Module(node) => node.range(), - Self::Expression(node) => node.range(), - } +impl crate::HasNodeIndex for crate::StmtWhile { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -/// See also [stmt](https://docs.python.org/3/library/ast.html#ast.stmt) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum StmtRef<'a> { - #[is(name = "function_def_stmt")] - FunctionDef(&'a crate::StmtFunctionDef), - #[is(name = "class_def_stmt")] - ClassDef(&'a crate::StmtClassDef), - #[is(name = "return_stmt")] - Return(&'a crate::StmtReturn), - #[is(name = "delete_stmt")] - Delete(&'a crate::StmtDelete), - #[is(name = "type_alias_stmt")] - TypeAlias(&'a crate::StmtTypeAlias), - #[is(name = "assign_stmt")] - Assign(&'a crate::StmtAssign), - #[is(name = "aug_assign_stmt")] - AugAssign(&'a crate::StmtAugAssign), - #[is(name = "ann_assign_stmt")] - AnnAssign(&'a crate::StmtAnnAssign), - #[is(name = "for_stmt")] - For(&'a crate::StmtFor), - #[is(name = "while_stmt")] - While(&'a crate::StmtWhile), - #[is(name = "if_stmt")] - If(&'a crate::StmtIf), - #[is(name = "with_stmt")] - With(&'a crate::StmtWith), - #[is(name = "match_stmt")] - Match(&'a crate::StmtMatch), - #[is(name = "raise_stmt")] - Raise(&'a crate::StmtRaise), - #[is(name = "try_stmt")] - Try(&'a crate::StmtTry), - #[is(name = "assert_stmt")] - Assert(&'a crate::StmtAssert), - #[is(name = "import_stmt")] - Import(&'a crate::StmtImport), - #[is(name = "import_from_stmt")] - ImportFrom(&'a crate::StmtImportFrom), - #[is(name = "global_stmt")] - Global(&'a crate::StmtGlobal), - #[is(name = "nonlocal_stmt")] - Nonlocal(&'a crate::StmtNonlocal), - #[is(name = "expr_stmt")] - Expr(&'a crate::StmtExpr), - #[is(name = "pass_stmt")] - Pass(&'a crate::StmtPass), - #[is(name = "break_stmt")] - Break(&'a crate::StmtBreak), - #[is(name = "continue_stmt")] - Continue(&'a crate::StmtContinue), - #[is(name = "ipy_escape_command_stmt")] - IpyEscapeCommand(&'a crate::StmtIpyEscapeCommand), +impl crate::HasNodeIndex for crate::StmtIf { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } } -impl<'a> From<&'a Stmt> for StmtRef<'a> { - fn from(node: &'a Stmt) -> Self { - match node { - Stmt::FunctionDef(node) => StmtRef::FunctionDef(node), - Stmt::ClassDef(node) => StmtRef::ClassDef(node), - Stmt::Return(node) => StmtRef::Return(node), - Stmt::Delete(node) => StmtRef::Delete(node), - Stmt::TypeAlias(node) => StmtRef::TypeAlias(node), - Stmt::Assign(node) => StmtRef::Assign(node), - Stmt::AugAssign(node) => StmtRef::AugAssign(node), - Stmt::AnnAssign(node) => StmtRef::AnnAssign(node), - Stmt::For(node) => StmtRef::For(node), - Stmt::While(node) => StmtRef::While(node), - Stmt::If(node) => StmtRef::If(node), - Stmt::With(node) => StmtRef::With(node), - Stmt::Match(node) => StmtRef::Match(node), - Stmt::Raise(node) => StmtRef::Raise(node), - Stmt::Try(node) => StmtRef::Try(node), - Stmt::Assert(node) => StmtRef::Assert(node), - Stmt::Import(node) => StmtRef::Import(node), - Stmt::ImportFrom(node) => StmtRef::ImportFrom(node), - Stmt::Global(node) => StmtRef::Global(node), - Stmt::Nonlocal(node) => StmtRef::Nonlocal(node), - Stmt::Expr(node) => StmtRef::Expr(node), - Stmt::Pass(node) => StmtRef::Pass(node), - Stmt::Break(node) => StmtRef::Break(node), - Stmt::Continue(node) => StmtRef::Continue(node), - Stmt::IpyEscapeCommand(node) => StmtRef::IpyEscapeCommand(node), - } +impl crate::HasNodeIndex for crate::StmtWith { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtFunctionDef> for StmtRef<'a> { - fn from(node: &'a crate::StmtFunctionDef) -> Self { - Self::FunctionDef(node) +impl crate::HasNodeIndex for crate::StmtMatch { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtClassDef> for StmtRef<'a> { - fn from(node: &'a crate::StmtClassDef) -> Self { - Self::ClassDef(node) +impl crate::HasNodeIndex for crate::StmtRaise { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtReturn> for StmtRef<'a> { - fn from(node: &'a crate::StmtReturn) -> Self { - Self::Return(node) +impl crate::HasNodeIndex for crate::StmtTry { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtDelete> for StmtRef<'a> { - fn from(node: &'a crate::StmtDelete) -> Self { - Self::Delete(node) +impl crate::HasNodeIndex for crate::StmtAssert { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtTypeAlias> for StmtRef<'a> { - fn from(node: &'a crate::StmtTypeAlias) -> Self { - Self::TypeAlias(node) +impl crate::HasNodeIndex for crate::StmtImport { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtAssign> for StmtRef<'a> { - fn from(node: &'a crate::StmtAssign) -> Self { - Self::Assign(node) +impl crate::HasNodeIndex for crate::StmtImportFrom { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtAugAssign> for StmtRef<'a> { - fn from(node: &'a crate::StmtAugAssign) -> Self { - Self::AugAssign(node) +impl crate::HasNodeIndex for crate::StmtGlobal { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtAnnAssign> for StmtRef<'a> { - fn from(node: &'a crate::StmtAnnAssign) -> Self { - Self::AnnAssign(node) +impl crate::HasNodeIndex for crate::StmtNonlocal { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtFor> for StmtRef<'a> { - fn from(node: &'a crate::StmtFor) -> Self { - Self::For(node) +impl crate::HasNodeIndex for crate::StmtExpr { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtWhile> for StmtRef<'a> { - fn from(node: &'a crate::StmtWhile) -> Self { - Self::While(node) +impl crate::HasNodeIndex for crate::StmtPass { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtIf> for StmtRef<'a> { - fn from(node: &'a crate::StmtIf) -> Self { - Self::If(node) +impl crate::HasNodeIndex for crate::StmtBreak { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtWith> for StmtRef<'a> { - fn from(node: &'a crate::StmtWith) -> Self { - Self::With(node) +impl crate::HasNodeIndex for crate::StmtContinue { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtMatch> for StmtRef<'a> { - fn from(node: &'a crate::StmtMatch) -> Self { - Self::Match(node) +impl crate::HasNodeIndex for crate::StmtIpyEscapeCommand { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtRaise> for StmtRef<'a> { - fn from(node: &'a crate::StmtRaise) -> Self { - Self::Raise(node) +impl crate::HasNodeIndex for crate::ExprBoolOp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtTry> for StmtRef<'a> { - fn from(node: &'a crate::StmtTry) -> Self { - Self::Try(node) +impl crate::HasNodeIndex for crate::ExprNamed { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtAssert> for StmtRef<'a> { - fn from(node: &'a crate::StmtAssert) -> Self { - Self::Assert(node) +impl crate::HasNodeIndex for crate::ExprBinOp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtImport> for StmtRef<'a> { - fn from(node: &'a crate::StmtImport) -> Self { - Self::Import(node) +impl crate::HasNodeIndex for crate::ExprUnaryOp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtImportFrom> for StmtRef<'a> { - fn from(node: &'a crate::StmtImportFrom) -> Self { - Self::ImportFrom(node) +impl crate::HasNodeIndex for crate::ExprLambda { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtGlobal> for StmtRef<'a> { - fn from(node: &'a crate::StmtGlobal) -> Self { - Self::Global(node) +impl crate::HasNodeIndex for crate::ExprIf { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtNonlocal> for StmtRef<'a> { - fn from(node: &'a crate::StmtNonlocal) -> Self { - Self::Nonlocal(node) +impl crate::HasNodeIndex for crate::ExprDict { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtExpr> for StmtRef<'a> { - fn from(node: &'a crate::StmtExpr) -> Self { - Self::Expr(node) +impl crate::HasNodeIndex for crate::ExprSet { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtPass> for StmtRef<'a> { - fn from(node: &'a crate::StmtPass) -> Self { - Self::Pass(node) +impl crate::HasNodeIndex for crate::ExprListComp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtBreak> for StmtRef<'a> { - fn from(node: &'a crate::StmtBreak) -> Self { - Self::Break(node) +impl crate::HasNodeIndex for crate::ExprSetComp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtContinue> for StmtRef<'a> { - fn from(node: &'a crate::StmtContinue) -> Self { - Self::Continue(node) +impl crate::HasNodeIndex for crate::ExprDictComp { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::StmtIpyEscapeCommand> for StmtRef<'a> { - fn from(node: &'a crate::StmtIpyEscapeCommand) -> Self { - Self::IpyEscapeCommand(node) +impl crate::HasNodeIndex for crate::ExprGenerator { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for StmtRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { - match self { - Self::FunctionDef(node) => node.range(), - Self::ClassDef(node) => node.range(), - Self::Return(node) => node.range(), - Self::Delete(node) => node.range(), - Self::TypeAlias(node) => node.range(), - Self::Assign(node) => node.range(), - Self::AugAssign(node) => node.range(), - Self::AnnAssign(node) => node.range(), - Self::For(node) => node.range(), - Self::While(node) => node.range(), - Self::If(node) => node.range(), - Self::With(node) => node.range(), - Self::Match(node) => node.range(), - Self::Raise(node) => node.range(), - Self::Try(node) => node.range(), - Self::Assert(node) => node.range(), - Self::Import(node) => node.range(), - Self::ImportFrom(node) => node.range(), - Self::Global(node) => node.range(), - Self::Nonlocal(node) => node.range(), - Self::Expr(node) => node.range(), - Self::Pass(node) => node.range(), - Self::Break(node) => node.range(), - Self::Continue(node) => node.range(), - Self::IpyEscapeCommand(node) => node.range(), - } +impl crate::HasNodeIndex for crate::ExprAwait { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -/// See also [expr](https://docs.python.org/3/library/ast.html#ast.expr) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum ExprRef<'a> { - #[is(name = "bool_op_expr")] - BoolOp(&'a crate::ExprBoolOp), - #[is(name = "named_expr")] - Named(&'a crate::ExprNamed), - #[is(name = "bin_op_expr")] - BinOp(&'a crate::ExprBinOp), - #[is(name = "unary_op_expr")] - UnaryOp(&'a crate::ExprUnaryOp), - #[is(name = "lambda_expr")] - Lambda(&'a crate::ExprLambda), - #[is(name = "if_expr")] - If(&'a crate::ExprIf), - #[is(name = "dict_expr")] - Dict(&'a crate::ExprDict), - #[is(name = "set_expr")] - Set(&'a crate::ExprSet), - #[is(name = "list_comp_expr")] - ListComp(&'a crate::ExprListComp), - #[is(name = "set_comp_expr")] - SetComp(&'a crate::ExprSetComp), - #[is(name = "dict_comp_expr")] - DictComp(&'a crate::ExprDictComp), - #[is(name = "generator_expr")] - Generator(&'a crate::ExprGenerator), - #[is(name = "await_expr")] - Await(&'a crate::ExprAwait), - #[is(name = "yield_expr")] - Yield(&'a crate::ExprYield), - #[is(name = "yield_from_expr")] - YieldFrom(&'a crate::ExprYieldFrom), - #[is(name = "compare_expr")] - Compare(&'a crate::ExprCompare), - #[is(name = "call_expr")] - Call(&'a crate::ExprCall), - #[is(name = "f_string_expr")] - FString(&'a crate::ExprFString), - #[is(name = "t_string_expr")] - TString(&'a crate::ExprTString), - #[is(name = "string_literal_expr")] - StringLiteral(&'a crate::ExprStringLiteral), - #[is(name = "bytes_literal_expr")] - BytesLiteral(&'a crate::ExprBytesLiteral), - #[is(name = "number_literal_expr")] - NumberLiteral(&'a crate::ExprNumberLiteral), - #[is(name = "boolean_literal_expr")] - BooleanLiteral(&'a crate::ExprBooleanLiteral), - #[is(name = "none_literal_expr")] - NoneLiteral(&'a crate::ExprNoneLiteral), - #[is(name = "ellipsis_literal_expr")] - EllipsisLiteral(&'a crate::ExprEllipsisLiteral), - #[is(name = "attribute_expr")] - Attribute(&'a crate::ExprAttribute), - #[is(name = "subscript_expr")] - Subscript(&'a crate::ExprSubscript), - #[is(name = "starred_expr")] - Starred(&'a crate::ExprStarred), - #[is(name = "name_expr")] - Name(&'a crate::ExprName), - #[is(name = "list_expr")] - List(&'a crate::ExprList), - #[is(name = "tuple_expr")] - Tuple(&'a crate::ExprTuple), - #[is(name = "slice_expr")] - Slice(&'a crate::ExprSlice), - #[is(name = "ipy_escape_command_expr")] - IpyEscapeCommand(&'a crate::ExprIpyEscapeCommand), +impl crate::HasNodeIndex for crate::ExprYield { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } } -impl<'a> From<&'a Expr> for ExprRef<'a> { - fn from(node: &'a Expr) -> Self { - match node { - Expr::BoolOp(node) => ExprRef::BoolOp(node), - Expr::Named(node) => ExprRef::Named(node), - Expr::BinOp(node) => ExprRef::BinOp(node), - Expr::UnaryOp(node) => ExprRef::UnaryOp(node), - Expr::Lambda(node) => ExprRef::Lambda(node), - Expr::If(node) => ExprRef::If(node), - Expr::Dict(node) => ExprRef::Dict(node), - Expr::Set(node) => ExprRef::Set(node), - Expr::ListComp(node) => ExprRef::ListComp(node), - Expr::SetComp(node) => ExprRef::SetComp(node), - Expr::DictComp(node) => ExprRef::DictComp(node), - Expr::Generator(node) => ExprRef::Generator(node), - Expr::Await(node) => ExprRef::Await(node), - Expr::Yield(node) => ExprRef::Yield(node), - Expr::YieldFrom(node) => ExprRef::YieldFrom(node), - Expr::Compare(node) => ExprRef::Compare(node), - Expr::Call(node) => ExprRef::Call(node), - Expr::FString(node) => ExprRef::FString(node), - Expr::TString(node) => ExprRef::TString(node), - Expr::StringLiteral(node) => ExprRef::StringLiteral(node), - Expr::BytesLiteral(node) => ExprRef::BytesLiteral(node), - Expr::NumberLiteral(node) => ExprRef::NumberLiteral(node), - Expr::BooleanLiteral(node) => ExprRef::BooleanLiteral(node), - Expr::NoneLiteral(node) => ExprRef::NoneLiteral(node), - Expr::EllipsisLiteral(node) => ExprRef::EllipsisLiteral(node), - Expr::Attribute(node) => ExprRef::Attribute(node), - Expr::Subscript(node) => ExprRef::Subscript(node), - Expr::Starred(node) => ExprRef::Starred(node), - Expr::Name(node) => ExprRef::Name(node), - Expr::List(node) => ExprRef::List(node), - Expr::Tuple(node) => ExprRef::Tuple(node), - Expr::Slice(node) => ExprRef::Slice(node), - Expr::IpyEscapeCommand(node) => ExprRef::IpyEscapeCommand(node), - } +impl crate::HasNodeIndex for crate::ExprYieldFrom { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprBoolOp> for ExprRef<'a> { - fn from(node: &'a crate::ExprBoolOp) -> Self { - Self::BoolOp(node) +impl crate::HasNodeIndex for crate::ExprCompare { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprNamed> for ExprRef<'a> { - fn from(node: &'a crate::ExprNamed) -> Self { - Self::Named(node) +impl crate::HasNodeIndex for crate::ExprCall { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprBinOp> for ExprRef<'a> { - fn from(node: &'a crate::ExprBinOp) -> Self { - Self::BinOp(node) +impl crate::HasNodeIndex for crate::ExprFString { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprUnaryOp> for ExprRef<'a> { - fn from(node: &'a crate::ExprUnaryOp) -> Self { - Self::UnaryOp(node) +impl crate::HasNodeIndex for crate::ExprTString { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprLambda> for ExprRef<'a> { - fn from(node: &'a crate::ExprLambda) -> Self { - Self::Lambda(node) +impl crate::HasNodeIndex for crate::ExprStringLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprIf> for ExprRef<'a> { - fn from(node: &'a crate::ExprIf) -> Self { - Self::If(node) +impl crate::HasNodeIndex for crate::ExprBytesLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprDict> for ExprRef<'a> { - fn from(node: &'a crate::ExprDict) -> Self { - Self::Dict(node) +impl crate::HasNodeIndex for crate::ExprNumberLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprSet> for ExprRef<'a> { - fn from(node: &'a crate::ExprSet) -> Self { - Self::Set(node) +impl crate::HasNodeIndex for crate::ExprBooleanLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprListComp> for ExprRef<'a> { - fn from(node: &'a crate::ExprListComp) -> Self { - Self::ListComp(node) +impl crate::HasNodeIndex for crate::ExprNoneLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprSetComp> for ExprRef<'a> { - fn from(node: &'a crate::ExprSetComp) -> Self { - Self::SetComp(node) +impl crate::HasNodeIndex for crate::ExprEllipsisLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprDictComp> for ExprRef<'a> { - fn from(node: &'a crate::ExprDictComp) -> Self { - Self::DictComp(node) +impl crate::HasNodeIndex for crate::ExprAttribute { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprGenerator> for ExprRef<'a> { - fn from(node: &'a crate::ExprGenerator) -> Self { - Self::Generator(node) +impl crate::HasNodeIndex for crate::ExprSubscript { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprAwait> for ExprRef<'a> { - fn from(node: &'a crate::ExprAwait) -> Self { - Self::Await(node) +impl crate::HasNodeIndex for crate::ExprStarred { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprYield> for ExprRef<'a> { - fn from(node: &'a crate::ExprYield) -> Self { - Self::Yield(node) +impl crate::HasNodeIndex for crate::ExprName { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprYieldFrom> for ExprRef<'a> { - fn from(node: &'a crate::ExprYieldFrom) -> Self { - Self::YieldFrom(node) +impl crate::HasNodeIndex for crate::ExprList { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprCompare> for ExprRef<'a> { - fn from(node: &'a crate::ExprCompare) -> Self { - Self::Compare(node) +impl crate::HasNodeIndex for crate::ExprTuple { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprCall> for ExprRef<'a> { - fn from(node: &'a crate::ExprCall) -> Self { - Self::Call(node) +impl crate::HasNodeIndex for crate::ExprSlice { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprFString> for ExprRef<'a> { - fn from(node: &'a crate::ExprFString) -> Self { - Self::FString(node) +impl crate::HasNodeIndex for crate::ExprIpyEscapeCommand { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprTString> for ExprRef<'a> { - fn from(node: &'a crate::ExprTString) -> Self { - Self::TString(node) +impl crate::HasNodeIndex for crate::ExceptHandlerExceptHandler { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprStringLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprStringLiteral) -> Self { - Self::StringLiteral(node) +impl crate::HasNodeIndex for crate::InterpolatedElement { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprBytesLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprBytesLiteral) -> Self { - Self::BytesLiteral(node) +impl crate::HasNodeIndex for crate::InterpolatedStringLiteralElement { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprNumberLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprNumberLiteral) -> Self { - Self::NumberLiteral(node) +impl crate::HasNodeIndex for crate::PatternMatchValue { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprBooleanLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprBooleanLiteral) -> Self { - Self::BooleanLiteral(node) +impl crate::HasNodeIndex for crate::PatternMatchSingleton { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprNoneLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprNoneLiteral) -> Self { - Self::NoneLiteral(node) +impl crate::HasNodeIndex for crate::PatternMatchSequence { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprEllipsisLiteral> for ExprRef<'a> { - fn from(node: &'a crate::ExprEllipsisLiteral) -> Self { - Self::EllipsisLiteral(node) +impl crate::HasNodeIndex for crate::PatternMatchMapping { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprAttribute> for ExprRef<'a> { - fn from(node: &'a crate::ExprAttribute) -> Self { - Self::Attribute(node) +impl crate::HasNodeIndex for crate::PatternMatchClass { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprSubscript> for ExprRef<'a> { - fn from(node: &'a crate::ExprSubscript) -> Self { - Self::Subscript(node) +impl crate::HasNodeIndex for crate::PatternMatchStar { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprStarred> for ExprRef<'a> { - fn from(node: &'a crate::ExprStarred) -> Self { - Self::Starred(node) +impl crate::HasNodeIndex for crate::PatternMatchAs { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprName> for ExprRef<'a> { - fn from(node: &'a crate::ExprName) -> Self { - Self::Name(node) +impl crate::HasNodeIndex for crate::PatternMatchOr { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprList> for ExprRef<'a> { - fn from(node: &'a crate::ExprList) -> Self { - Self::List(node) +impl crate::HasNodeIndex for crate::TypeParamTypeVar { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprTuple> for ExprRef<'a> { - fn from(node: &'a crate::ExprTuple) -> Self { - Self::Tuple(node) +impl crate::HasNodeIndex for crate::TypeParamTypeVarTuple { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprSlice> for ExprRef<'a> { - fn from(node: &'a crate::ExprSlice) -> Self { - Self::Slice(node) +impl crate::HasNodeIndex for crate::TypeParamParamSpec { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExprIpyEscapeCommand> for ExprRef<'a> { - fn from(node: &'a crate::ExprIpyEscapeCommand) -> Self { - Self::IpyEscapeCommand(node) +impl crate::HasNodeIndex for crate::InterpolatedStringFormatSpec { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for ExprRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { - match self { - Self::BoolOp(node) => node.range(), - Self::Named(node) => node.range(), - Self::BinOp(node) => node.range(), - Self::UnaryOp(node) => node.range(), - Self::Lambda(node) => node.range(), - Self::If(node) => node.range(), - Self::Dict(node) => node.range(), - Self::Set(node) => node.range(), - Self::ListComp(node) => node.range(), - Self::SetComp(node) => node.range(), - Self::DictComp(node) => node.range(), - Self::Generator(node) => node.range(), - Self::Await(node) => node.range(), - Self::Yield(node) => node.range(), - Self::YieldFrom(node) => node.range(), - Self::Compare(node) => node.range(), - Self::Call(node) => node.range(), - Self::FString(node) => node.range(), - Self::TString(node) => node.range(), - Self::StringLiteral(node) => node.range(), - Self::BytesLiteral(node) => node.range(), - Self::NumberLiteral(node) => node.range(), - Self::BooleanLiteral(node) => node.range(), - Self::NoneLiteral(node) => node.range(), - Self::EllipsisLiteral(node) => node.range(), - Self::Attribute(node) => node.range(), - Self::Subscript(node) => node.range(), - Self::Starred(node) => node.range(), - Self::Name(node) => node.range(), - Self::List(node) => node.range(), - Self::Tuple(node) => node.range(), - Self::Slice(node) => node.range(), - Self::IpyEscapeCommand(node) => node.range(), - } +impl crate::HasNodeIndex for crate::PatternArguments { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -/// See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.excepthandler) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum ExceptHandlerRef<'a> { - ExceptHandler(&'a crate::ExceptHandlerExceptHandler), -} - -impl<'a> From<&'a ExceptHandler> for ExceptHandlerRef<'a> { - fn from(node: &'a ExceptHandler) -> Self { - match node { - ExceptHandler::ExceptHandler(node) => ExceptHandlerRef::ExceptHandler(node), - } +impl crate::HasNodeIndex for crate::PatternKeyword { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for ExceptHandlerRef<'a> { - fn from(node: &'a crate::ExceptHandlerExceptHandler) -> Self { - Self::ExceptHandler(node) +impl crate::HasNodeIndex for crate::Comprehension { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for ExceptHandlerRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { - match self { - Self::ExceptHandler(node) => node.range(), - } +impl crate::HasNodeIndex for crate::Arguments { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum InterpolatedStringElementRef<'a> { - Interpolation(&'a crate::InterpolatedElement), - Literal(&'a crate::InterpolatedStringLiteralElement), +impl crate::HasNodeIndex for crate::Parameters { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } } -impl<'a> From<&'a InterpolatedStringElement> for InterpolatedStringElementRef<'a> { - fn from(node: &'a InterpolatedStringElement) -> Self { - match node { - InterpolatedStringElement::Interpolation(node) => { - InterpolatedStringElementRef::Interpolation(node) - } - InterpolatedStringElement::Literal(node) => InterpolatedStringElementRef::Literal(node), - } +impl crate::HasNodeIndex for crate::Parameter { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::InterpolatedElement> for InterpolatedStringElementRef<'a> { - fn from(node: &'a crate::InterpolatedElement) -> Self { - Self::Interpolation(node) +impl crate::HasNodeIndex for crate::ParameterWithDefault { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for InterpolatedStringElementRef<'a> { - fn from(node: &'a crate::InterpolatedStringLiteralElement) -> Self { - Self::Literal(node) +impl crate::HasNodeIndex for crate::Keyword { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for InterpolatedStringElementRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { - match self { - Self::Interpolation(node) => node.range(), - Self::Literal(node) => node.range(), - } +impl crate::HasNodeIndex for crate::Alias { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -/// See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum PatternRef<'a> { - MatchValue(&'a crate::PatternMatchValue), - MatchSingleton(&'a crate::PatternMatchSingleton), - MatchSequence(&'a crate::PatternMatchSequence), - MatchMapping(&'a crate::PatternMatchMapping), - MatchClass(&'a crate::PatternMatchClass), - MatchStar(&'a crate::PatternMatchStar), - MatchAs(&'a crate::PatternMatchAs), - MatchOr(&'a crate::PatternMatchOr), +impl crate::HasNodeIndex for crate::WithItem { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } } -impl<'a> From<&'a Pattern> for PatternRef<'a> { - fn from(node: &'a Pattern) -> Self { - match node { - Pattern::MatchValue(node) => PatternRef::MatchValue(node), - Pattern::MatchSingleton(node) => PatternRef::MatchSingleton(node), - Pattern::MatchSequence(node) => PatternRef::MatchSequence(node), - Pattern::MatchMapping(node) => PatternRef::MatchMapping(node), - Pattern::MatchClass(node) => PatternRef::MatchClass(node), - Pattern::MatchStar(node) => PatternRef::MatchStar(node), - Pattern::MatchAs(node) => PatternRef::MatchAs(node), - Pattern::MatchOr(node) => PatternRef::MatchOr(node), - } +impl crate::HasNodeIndex for crate::MatchCase { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchValue> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchValue) -> Self { - Self::MatchValue(node) +impl crate::HasNodeIndex for crate::Decorator { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchSingleton> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchSingleton) -> Self { - Self::MatchSingleton(node) +impl crate::HasNodeIndex for crate::ElifElseClause { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchSequence> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchSequence) -> Self { - Self::MatchSequence(node) +impl crate::HasNodeIndex for crate::TypeParams { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchMapping> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchMapping) -> Self { - Self::MatchMapping(node) +impl crate::HasNodeIndex for crate::FString { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchClass> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchClass) -> Self { - Self::MatchClass(node) +impl crate::HasNodeIndex for crate::TString { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchStar> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchStar) -> Self { - Self::MatchStar(node) +impl crate::HasNodeIndex for crate::StringLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchAs> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchAs) -> Self { - Self::MatchAs(node) +impl crate::HasNodeIndex for crate::BytesLiteral { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl<'a> From<&'a crate::PatternMatchOr> for PatternRef<'a> { - fn from(node: &'a crate::PatternMatchOr) -> Self { - Self::MatchOr(node) +impl crate::HasNodeIndex for crate::Identifier { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index } } -impl ruff_text_size::Ranged for PatternRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { +impl Mod { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { match self { - Self::MatchValue(node) => node.range(), - Self::MatchSingleton(node) => node.range(), - Self::MatchSequence(node) => node.range(), - Self::MatchMapping(node) => node.range(), - Self::MatchClass(node) => node.range(), - Self::MatchStar(node) => node.range(), - Self::MatchAs(node) => node.range(), - Self::MatchOr(node) => node.range(), + Mod::Module(node) => node.visit_source_order(visitor), + Mod::Expression(node) => node.visit_source_order(visitor), } } } -/// See also [type_param](https://docs.python.org/3/library/ast.html#ast.type_param) -#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] -pub enum TypeParamRef<'a> { - TypeVar(&'a crate::TypeParamTypeVar), - TypeVarTuple(&'a crate::TypeParamTypeVarTuple), - ParamSpec(&'a crate::TypeParamParamSpec), +impl Stmt { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { + match self { + Stmt::FunctionDef(node) => node.visit_source_order(visitor), + Stmt::ClassDef(node) => node.visit_source_order(visitor), + Stmt::Return(node) => node.visit_source_order(visitor), + Stmt::Delete(node) => node.visit_source_order(visitor), + Stmt::TypeAlias(node) => node.visit_source_order(visitor), + Stmt::Assign(node) => node.visit_source_order(visitor), + Stmt::AugAssign(node) => node.visit_source_order(visitor), + Stmt::AnnAssign(node) => node.visit_source_order(visitor), + Stmt::For(node) => node.visit_source_order(visitor), + Stmt::While(node) => node.visit_source_order(visitor), + Stmt::If(node) => node.visit_source_order(visitor), + Stmt::With(node) => node.visit_source_order(visitor), + Stmt::Match(node) => node.visit_source_order(visitor), + Stmt::Raise(node) => node.visit_source_order(visitor), + Stmt::Try(node) => node.visit_source_order(visitor), + Stmt::Assert(node) => node.visit_source_order(visitor), + Stmt::Import(node) => node.visit_source_order(visitor), + Stmt::ImportFrom(node) => node.visit_source_order(visitor), + Stmt::Global(node) => node.visit_source_order(visitor), + Stmt::Nonlocal(node) => node.visit_source_order(visitor), + Stmt::Expr(node) => node.visit_source_order(visitor), + Stmt::Pass(node) => node.visit_source_order(visitor), + Stmt::Break(node) => node.visit_source_order(visitor), + Stmt::Continue(node) => node.visit_source_order(visitor), + Stmt::IpyEscapeCommand(node) => node.visit_source_order(visitor), + } + } } -impl<'a> From<&'a TypeParam> for TypeParamRef<'a> { - fn from(node: &'a TypeParam) -> Self { - match node { - TypeParam::TypeVar(node) => TypeParamRef::TypeVar(node), - TypeParam::TypeVarTuple(node) => TypeParamRef::TypeVarTuple(node), - TypeParam::ParamSpec(node) => TypeParamRef::ParamSpec(node), +impl Expr { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { + match self { + Expr::BoolOp(node) => node.visit_source_order(visitor), + Expr::Named(node) => node.visit_source_order(visitor), + Expr::BinOp(node) => node.visit_source_order(visitor), + Expr::UnaryOp(node) => node.visit_source_order(visitor), + Expr::Lambda(node) => node.visit_source_order(visitor), + Expr::If(node) => node.visit_source_order(visitor), + Expr::Dict(node) => node.visit_source_order(visitor), + Expr::Set(node) => node.visit_source_order(visitor), + Expr::ListComp(node) => node.visit_source_order(visitor), + Expr::SetComp(node) => node.visit_source_order(visitor), + Expr::DictComp(node) => node.visit_source_order(visitor), + Expr::Generator(node) => node.visit_source_order(visitor), + Expr::Await(node) => node.visit_source_order(visitor), + Expr::Yield(node) => node.visit_source_order(visitor), + Expr::YieldFrom(node) => node.visit_source_order(visitor), + Expr::Compare(node) => node.visit_source_order(visitor), + Expr::Call(node) => node.visit_source_order(visitor), + Expr::FString(node) => node.visit_source_order(visitor), + Expr::TString(node) => node.visit_source_order(visitor), + Expr::StringLiteral(node) => node.visit_source_order(visitor), + Expr::BytesLiteral(node) => node.visit_source_order(visitor), + Expr::NumberLiteral(node) => node.visit_source_order(visitor), + Expr::BooleanLiteral(node) => node.visit_source_order(visitor), + Expr::NoneLiteral(node) => node.visit_source_order(visitor), + Expr::EllipsisLiteral(node) => node.visit_source_order(visitor), + Expr::Attribute(node) => node.visit_source_order(visitor), + Expr::Subscript(node) => node.visit_source_order(visitor), + Expr::Starred(node) => node.visit_source_order(visitor), + Expr::Name(node) => node.visit_source_order(visitor), + Expr::List(node) => node.visit_source_order(visitor), + Expr::Tuple(node) => node.visit_source_order(visitor), + Expr::Slice(node) => node.visit_source_order(visitor), + Expr::IpyEscapeCommand(node) => node.visit_source_order(visitor), } } } -impl<'a> From<&'a crate::TypeParamTypeVar> for TypeParamRef<'a> { - fn from(node: &'a crate::TypeParamTypeVar) -> Self { - Self::TypeVar(node) +impl ExceptHandler { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { + match self { + ExceptHandler::ExceptHandler(node) => node.visit_source_order(visitor), + } } } -impl<'a> From<&'a crate::TypeParamTypeVarTuple> for TypeParamRef<'a> { - fn from(node: &'a crate::TypeParamTypeVarTuple) -> Self { - Self::TypeVarTuple(node) +impl InterpolatedStringElement { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { + match self { + InterpolatedStringElement::Interpolation(node) => node.visit_source_order(visitor), + InterpolatedStringElement::Literal(node) => node.visit_source_order(visitor), + } } } -impl<'a> From<&'a crate::TypeParamParamSpec> for TypeParamRef<'a> { - fn from(node: &'a crate::TypeParamParamSpec) -> Self { - Self::ParamSpec(node) +impl Pattern { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { + match self { + Pattern::MatchValue(node) => node.visit_source_order(visitor), + Pattern::MatchSingleton(node) => node.visit_source_order(visitor), + Pattern::MatchSequence(node) => node.visit_source_order(visitor), + Pattern::MatchMapping(node) => node.visit_source_order(visitor), + Pattern::MatchClass(node) => node.visit_source_order(visitor), + Pattern::MatchStar(node) => node.visit_source_order(visitor), + Pattern::MatchAs(node) => node.visit_source_order(visitor), + Pattern::MatchOr(node) => node.visit_source_order(visitor), + } } } -impl ruff_text_size::Ranged for TypeParamRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { +impl TypeParam { + #[allow(unused)] + pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'a> + ?Sized, + { match self { - Self::TypeVar(node) => node.range(), - Self::TypeVarTuple(node) => node.range(), - Self::ParamSpec(node) => node.range(), + TypeParam::TypeVar(node) => node.visit_source_order(visitor), + TypeParam::TypeVarTuple(node) => node.visit_source_order(visitor), + TypeParam::ParamSpec(node) => node.visit_source_order(visitor), } } } -#[derive(Copy, Clone, Debug, is_macro::Is, PartialEq)] -pub enum AnyNodeRef<'a> { - ModModule(&'a crate::ModModule), - ModExpression(&'a crate::ModExpression), - StmtFunctionDef(&'a crate::StmtFunctionDef), - StmtClassDef(&'a crate::StmtClassDef), - StmtReturn(&'a crate::StmtReturn), - StmtDelete(&'a crate::StmtDelete), - StmtTypeAlias(&'a crate::StmtTypeAlias), - StmtAssign(&'a crate::StmtAssign), - StmtAugAssign(&'a crate::StmtAugAssign), - StmtAnnAssign(&'a crate::StmtAnnAssign), - StmtFor(&'a crate::StmtFor), - StmtWhile(&'a crate::StmtWhile), - StmtIf(&'a crate::StmtIf), - StmtWith(&'a crate::StmtWith), - StmtMatch(&'a crate::StmtMatch), - StmtRaise(&'a crate::StmtRaise), - StmtTry(&'a crate::StmtTry), - StmtAssert(&'a crate::StmtAssert), - StmtImport(&'a crate::StmtImport), - StmtImportFrom(&'a crate::StmtImportFrom), - StmtGlobal(&'a crate::StmtGlobal), - StmtNonlocal(&'a crate::StmtNonlocal), - StmtExpr(&'a crate::StmtExpr), - StmtPass(&'a crate::StmtPass), - StmtBreak(&'a crate::StmtBreak), - StmtContinue(&'a crate::StmtContinue), - StmtIpyEscapeCommand(&'a crate::StmtIpyEscapeCommand), - ExprBoolOp(&'a crate::ExprBoolOp), - ExprNamed(&'a crate::ExprNamed), - ExprBinOp(&'a crate::ExprBinOp), - ExprUnaryOp(&'a crate::ExprUnaryOp), - ExprLambda(&'a crate::ExprLambda), - ExprIf(&'a crate::ExprIf), - ExprDict(&'a crate::ExprDict), - ExprSet(&'a crate::ExprSet), - ExprListComp(&'a crate::ExprListComp), - ExprSetComp(&'a crate::ExprSetComp), - ExprDictComp(&'a crate::ExprDictComp), - ExprGenerator(&'a crate::ExprGenerator), - ExprAwait(&'a crate::ExprAwait), - ExprYield(&'a crate::ExprYield), - ExprYieldFrom(&'a crate::ExprYieldFrom), - ExprCompare(&'a crate::ExprCompare), - ExprCall(&'a crate::ExprCall), - ExprFString(&'a crate::ExprFString), - ExprTString(&'a crate::ExprTString), - ExprStringLiteral(&'a crate::ExprStringLiteral), - ExprBytesLiteral(&'a crate::ExprBytesLiteral), - ExprNumberLiteral(&'a crate::ExprNumberLiteral), - ExprBooleanLiteral(&'a crate::ExprBooleanLiteral), - ExprNoneLiteral(&'a crate::ExprNoneLiteral), - ExprEllipsisLiteral(&'a crate::ExprEllipsisLiteral), - ExprAttribute(&'a crate::ExprAttribute), - ExprSubscript(&'a crate::ExprSubscript), - ExprStarred(&'a crate::ExprStarred), - ExprName(&'a crate::ExprName), - ExprList(&'a crate::ExprList), - ExprTuple(&'a crate::ExprTuple), - ExprSlice(&'a crate::ExprSlice), - ExprIpyEscapeCommand(&'a crate::ExprIpyEscapeCommand), - ExceptHandlerExceptHandler(&'a crate::ExceptHandlerExceptHandler), - InterpolatedElement(&'a crate::InterpolatedElement), - InterpolatedStringLiteralElement(&'a crate::InterpolatedStringLiteralElement), - PatternMatchValue(&'a crate::PatternMatchValue), - PatternMatchSingleton(&'a crate::PatternMatchSingleton), - PatternMatchSequence(&'a crate::PatternMatchSequence), - PatternMatchMapping(&'a crate::PatternMatchMapping), - PatternMatchClass(&'a crate::PatternMatchClass), - PatternMatchStar(&'a crate::PatternMatchStar), - PatternMatchAs(&'a crate::PatternMatchAs), - PatternMatchOr(&'a crate::PatternMatchOr), - TypeParamTypeVar(&'a crate::TypeParamTypeVar), - TypeParamTypeVarTuple(&'a crate::TypeParamTypeVarTuple), - TypeParamParamSpec(&'a crate::TypeParamParamSpec), - InterpolatedStringFormatSpec(&'a crate::InterpolatedStringFormatSpec), - PatternArguments(&'a crate::PatternArguments), - PatternKeyword(&'a crate::PatternKeyword), - Comprehension(&'a crate::Comprehension), - Arguments(&'a crate::Arguments), - Parameters(&'a crate::Parameters), - Parameter(&'a crate::Parameter), - ParameterWithDefault(&'a crate::ParameterWithDefault), - Keyword(&'a crate::Keyword), - Alias(&'a crate::Alias), - WithItem(&'a crate::WithItem), - MatchCase(&'a crate::MatchCase), - Decorator(&'a crate::Decorator), - ElifElseClause(&'a crate::ElifElseClause), - TypeParams(&'a crate::TypeParams), - FString(&'a crate::FString), - TString(&'a crate::TString), - StringLiteral(&'a crate::StringLiteral), - BytesLiteral(&'a crate::BytesLiteral), - Identifier(&'a crate::Identifier), +/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum ModRef<'a> { + Module(&'a crate::ModModule), + Expression(&'a crate::ModExpression), } -impl<'a> From<&'a Mod> for AnyNodeRef<'a> { - fn from(node: &'a Mod) -> AnyNodeRef<'a> { +impl<'a> From<&'a Mod> for ModRef<'a> { + fn from(node: &'a Mod) -> Self { match node { - Mod::Module(node) => AnyNodeRef::ModModule(node), - Mod::Expression(node) => AnyNodeRef::ModExpression(node), + Mod::Module(node) => ModRef::Module(node), + Mod::Expression(node) => ModRef::Expression(node), } } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: ModRef<'a>) -> AnyNodeRef<'a> { - match node { - ModRef::Module(node) => AnyNodeRef::ModModule(node), - ModRef::Expression(node) => AnyNodeRef::ModExpression(node), - } +impl<'a> From<&'a crate::ModModule> for ModRef<'a> { + fn from(node: &'a crate::ModModule) -> Self { + Self::Module(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_mod_ref(self) -> Option> { +impl<'a> From<&'a crate::ModExpression> for ModRef<'a> { + fn from(node: &'a crate::ModExpression) -> Self { + Self::Expression(node) + } +} + +impl ruff_text_size::Ranged for ModRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { match self { - Self::ModModule(node) => Some(ModRef::Module(node)), - Self::ModExpression(node) => Some(ModRef::Expression(node)), + Self::Module(node) => node.range(), + Self::Expression(node) => node.range(), + } + } +} - _ => None, +impl crate::HasNodeIndex for ModRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::Module(node) => node.node_index(), + Self::Expression(node) => node.node_index(), } } } -impl<'a> From<&'a Stmt> for AnyNodeRef<'a> { - fn from(node: &'a Stmt) -> AnyNodeRef<'a> { - match node { - Stmt::FunctionDef(node) => AnyNodeRef::StmtFunctionDef(node), - Stmt::ClassDef(node) => AnyNodeRef::StmtClassDef(node), - Stmt::Return(node) => AnyNodeRef::StmtReturn(node), - Stmt::Delete(node) => AnyNodeRef::StmtDelete(node), - Stmt::TypeAlias(node) => AnyNodeRef::StmtTypeAlias(node), - Stmt::Assign(node) => AnyNodeRef::StmtAssign(node), - Stmt::AugAssign(node) => AnyNodeRef::StmtAugAssign(node), - Stmt::AnnAssign(node) => AnyNodeRef::StmtAnnAssign(node), - Stmt::For(node) => AnyNodeRef::StmtFor(node), - Stmt::While(node) => AnyNodeRef::StmtWhile(node), - Stmt::If(node) => AnyNodeRef::StmtIf(node), - Stmt::With(node) => AnyNodeRef::StmtWith(node), - Stmt::Match(node) => AnyNodeRef::StmtMatch(node), - Stmt::Raise(node) => AnyNodeRef::StmtRaise(node), - Stmt::Try(node) => AnyNodeRef::StmtTry(node), - Stmt::Assert(node) => AnyNodeRef::StmtAssert(node), - Stmt::Import(node) => AnyNodeRef::StmtImport(node), - Stmt::ImportFrom(node) => AnyNodeRef::StmtImportFrom(node), - Stmt::Global(node) => AnyNodeRef::StmtGlobal(node), - Stmt::Nonlocal(node) => AnyNodeRef::StmtNonlocal(node), - Stmt::Expr(node) => AnyNodeRef::StmtExpr(node), - Stmt::Pass(node) => AnyNodeRef::StmtPass(node), - Stmt::Break(node) => AnyNodeRef::StmtBreak(node), - Stmt::Continue(node) => AnyNodeRef::StmtContinue(node), - Stmt::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node), - } - } +/// See also [stmt](https://docs.python.org/3/library/ast.html#ast.stmt) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum StmtRef<'a> { + #[is(name = "function_def_stmt")] + FunctionDef(&'a crate::StmtFunctionDef), + #[is(name = "class_def_stmt")] + ClassDef(&'a crate::StmtClassDef), + #[is(name = "return_stmt")] + Return(&'a crate::StmtReturn), + #[is(name = "delete_stmt")] + Delete(&'a crate::StmtDelete), + #[is(name = "type_alias_stmt")] + TypeAlias(&'a crate::StmtTypeAlias), + #[is(name = "assign_stmt")] + Assign(&'a crate::StmtAssign), + #[is(name = "aug_assign_stmt")] + AugAssign(&'a crate::StmtAugAssign), + #[is(name = "ann_assign_stmt")] + AnnAssign(&'a crate::StmtAnnAssign), + #[is(name = "for_stmt")] + For(&'a crate::StmtFor), + #[is(name = "while_stmt")] + While(&'a crate::StmtWhile), + #[is(name = "if_stmt")] + If(&'a crate::StmtIf), + #[is(name = "with_stmt")] + With(&'a crate::StmtWith), + #[is(name = "match_stmt")] + Match(&'a crate::StmtMatch), + #[is(name = "raise_stmt")] + Raise(&'a crate::StmtRaise), + #[is(name = "try_stmt")] + Try(&'a crate::StmtTry), + #[is(name = "assert_stmt")] + Assert(&'a crate::StmtAssert), + #[is(name = "import_stmt")] + Import(&'a crate::StmtImport), + #[is(name = "import_from_stmt")] + ImportFrom(&'a crate::StmtImportFrom), + #[is(name = "global_stmt")] + Global(&'a crate::StmtGlobal), + #[is(name = "nonlocal_stmt")] + Nonlocal(&'a crate::StmtNonlocal), + #[is(name = "expr_stmt")] + Expr(&'a crate::StmtExpr), + #[is(name = "pass_stmt")] + Pass(&'a crate::StmtPass), + #[is(name = "break_stmt")] + Break(&'a crate::StmtBreak), + #[is(name = "continue_stmt")] + Continue(&'a crate::StmtContinue), + #[is(name = "ipy_escape_command_stmt")] + IpyEscapeCommand(&'a crate::StmtIpyEscapeCommand), } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: StmtRef<'a>) -> AnyNodeRef<'a> { +impl<'a> From<&'a Stmt> for StmtRef<'a> { + fn from(node: &'a Stmt) -> Self { match node { - StmtRef::FunctionDef(node) => AnyNodeRef::StmtFunctionDef(node), - StmtRef::ClassDef(node) => AnyNodeRef::StmtClassDef(node), - StmtRef::Return(node) => AnyNodeRef::StmtReturn(node), - StmtRef::Delete(node) => AnyNodeRef::StmtDelete(node), - StmtRef::TypeAlias(node) => AnyNodeRef::StmtTypeAlias(node), - StmtRef::Assign(node) => AnyNodeRef::StmtAssign(node), - StmtRef::AugAssign(node) => AnyNodeRef::StmtAugAssign(node), - StmtRef::AnnAssign(node) => AnyNodeRef::StmtAnnAssign(node), - StmtRef::For(node) => AnyNodeRef::StmtFor(node), - StmtRef::While(node) => AnyNodeRef::StmtWhile(node), - StmtRef::If(node) => AnyNodeRef::StmtIf(node), - StmtRef::With(node) => AnyNodeRef::StmtWith(node), - StmtRef::Match(node) => AnyNodeRef::StmtMatch(node), - StmtRef::Raise(node) => AnyNodeRef::StmtRaise(node), - StmtRef::Try(node) => AnyNodeRef::StmtTry(node), - StmtRef::Assert(node) => AnyNodeRef::StmtAssert(node), - StmtRef::Import(node) => AnyNodeRef::StmtImport(node), - StmtRef::ImportFrom(node) => AnyNodeRef::StmtImportFrom(node), - StmtRef::Global(node) => AnyNodeRef::StmtGlobal(node), - StmtRef::Nonlocal(node) => AnyNodeRef::StmtNonlocal(node), - StmtRef::Expr(node) => AnyNodeRef::StmtExpr(node), - StmtRef::Pass(node) => AnyNodeRef::StmtPass(node), - StmtRef::Break(node) => AnyNodeRef::StmtBreak(node), - StmtRef::Continue(node) => AnyNodeRef::StmtContinue(node), - StmtRef::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node), + Stmt::FunctionDef(node) => StmtRef::FunctionDef(node), + Stmt::ClassDef(node) => StmtRef::ClassDef(node), + Stmt::Return(node) => StmtRef::Return(node), + Stmt::Delete(node) => StmtRef::Delete(node), + Stmt::TypeAlias(node) => StmtRef::TypeAlias(node), + Stmt::Assign(node) => StmtRef::Assign(node), + Stmt::AugAssign(node) => StmtRef::AugAssign(node), + Stmt::AnnAssign(node) => StmtRef::AnnAssign(node), + Stmt::For(node) => StmtRef::For(node), + Stmt::While(node) => StmtRef::While(node), + Stmt::If(node) => StmtRef::If(node), + Stmt::With(node) => StmtRef::With(node), + Stmt::Match(node) => StmtRef::Match(node), + Stmt::Raise(node) => StmtRef::Raise(node), + Stmt::Try(node) => StmtRef::Try(node), + Stmt::Assert(node) => StmtRef::Assert(node), + Stmt::Import(node) => StmtRef::Import(node), + Stmt::ImportFrom(node) => StmtRef::ImportFrom(node), + Stmt::Global(node) => StmtRef::Global(node), + Stmt::Nonlocal(node) => StmtRef::Nonlocal(node), + Stmt::Expr(node) => StmtRef::Expr(node), + Stmt::Pass(node) => StmtRef::Pass(node), + Stmt::Break(node) => StmtRef::Break(node), + Stmt::Continue(node) => StmtRef::Continue(node), + Stmt::IpyEscapeCommand(node) => StmtRef::IpyEscapeCommand(node), } } } -impl<'a> AnyNodeRef<'a> { - pub fn as_stmt_ref(self) -> Option> { - match self { - Self::StmtFunctionDef(node) => Some(StmtRef::FunctionDef(node)), - Self::StmtClassDef(node) => Some(StmtRef::ClassDef(node)), - Self::StmtReturn(node) => Some(StmtRef::Return(node)), - Self::StmtDelete(node) => Some(StmtRef::Delete(node)), - Self::StmtTypeAlias(node) => Some(StmtRef::TypeAlias(node)), - Self::StmtAssign(node) => Some(StmtRef::Assign(node)), - Self::StmtAugAssign(node) => Some(StmtRef::AugAssign(node)), - Self::StmtAnnAssign(node) => Some(StmtRef::AnnAssign(node)), - Self::StmtFor(node) => Some(StmtRef::For(node)), - Self::StmtWhile(node) => Some(StmtRef::While(node)), - Self::StmtIf(node) => Some(StmtRef::If(node)), - Self::StmtWith(node) => Some(StmtRef::With(node)), - Self::StmtMatch(node) => Some(StmtRef::Match(node)), - Self::StmtRaise(node) => Some(StmtRef::Raise(node)), - Self::StmtTry(node) => Some(StmtRef::Try(node)), - Self::StmtAssert(node) => Some(StmtRef::Assert(node)), - Self::StmtImport(node) => Some(StmtRef::Import(node)), - Self::StmtImportFrom(node) => Some(StmtRef::ImportFrom(node)), - Self::StmtGlobal(node) => Some(StmtRef::Global(node)), - Self::StmtNonlocal(node) => Some(StmtRef::Nonlocal(node)), - Self::StmtExpr(node) => Some(StmtRef::Expr(node)), - Self::StmtPass(node) => Some(StmtRef::Pass(node)), - Self::StmtBreak(node) => Some(StmtRef::Break(node)), - Self::StmtContinue(node) => Some(StmtRef::Continue(node)), - Self::StmtIpyEscapeCommand(node) => Some(StmtRef::IpyEscapeCommand(node)), +impl<'a> From<&'a crate::StmtFunctionDef> for StmtRef<'a> { + fn from(node: &'a crate::StmtFunctionDef) -> Self { + Self::FunctionDef(node) + } +} - _ => None, - } +impl<'a> From<&'a crate::StmtClassDef> for StmtRef<'a> { + fn from(node: &'a crate::StmtClassDef) -> Self { + Self::ClassDef(node) } } -impl<'a> From<&'a Expr> for AnyNodeRef<'a> { - fn from(node: &'a Expr) -> AnyNodeRef<'a> { - match node { - Expr::BoolOp(node) => AnyNodeRef::ExprBoolOp(node), - Expr::Named(node) => AnyNodeRef::ExprNamed(node), - Expr::BinOp(node) => AnyNodeRef::ExprBinOp(node), - Expr::UnaryOp(node) => AnyNodeRef::ExprUnaryOp(node), - Expr::Lambda(node) => AnyNodeRef::ExprLambda(node), - Expr::If(node) => AnyNodeRef::ExprIf(node), - Expr::Dict(node) => AnyNodeRef::ExprDict(node), - Expr::Set(node) => AnyNodeRef::ExprSet(node), - Expr::ListComp(node) => AnyNodeRef::ExprListComp(node), - Expr::SetComp(node) => AnyNodeRef::ExprSetComp(node), - Expr::DictComp(node) => AnyNodeRef::ExprDictComp(node), - Expr::Generator(node) => AnyNodeRef::ExprGenerator(node), - Expr::Await(node) => AnyNodeRef::ExprAwait(node), - Expr::Yield(node) => AnyNodeRef::ExprYield(node), - Expr::YieldFrom(node) => AnyNodeRef::ExprYieldFrom(node), - Expr::Compare(node) => AnyNodeRef::ExprCompare(node), - Expr::Call(node) => AnyNodeRef::ExprCall(node), - Expr::FString(node) => AnyNodeRef::ExprFString(node), - Expr::TString(node) => AnyNodeRef::ExprTString(node), - Expr::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), - Expr::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), - Expr::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), - Expr::BooleanLiteral(node) => AnyNodeRef::ExprBooleanLiteral(node), - Expr::NoneLiteral(node) => AnyNodeRef::ExprNoneLiteral(node), - Expr::EllipsisLiteral(node) => AnyNodeRef::ExprEllipsisLiteral(node), - Expr::Attribute(node) => AnyNodeRef::ExprAttribute(node), - Expr::Subscript(node) => AnyNodeRef::ExprSubscript(node), - Expr::Starred(node) => AnyNodeRef::ExprStarred(node), - Expr::Name(node) => AnyNodeRef::ExprName(node), - Expr::List(node) => AnyNodeRef::ExprList(node), - Expr::Tuple(node) => AnyNodeRef::ExprTuple(node), - Expr::Slice(node) => AnyNodeRef::ExprSlice(node), - Expr::IpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node), - } +impl<'a> From<&'a crate::StmtReturn> for StmtRef<'a> { + fn from(node: &'a crate::StmtReturn) -> Self { + Self::Return(node) } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: ExprRef<'a>) -> AnyNodeRef<'a> { - match node { - ExprRef::BoolOp(node) => AnyNodeRef::ExprBoolOp(node), - ExprRef::Named(node) => AnyNodeRef::ExprNamed(node), - ExprRef::BinOp(node) => AnyNodeRef::ExprBinOp(node), - ExprRef::UnaryOp(node) => AnyNodeRef::ExprUnaryOp(node), - ExprRef::Lambda(node) => AnyNodeRef::ExprLambda(node), - ExprRef::If(node) => AnyNodeRef::ExprIf(node), - ExprRef::Dict(node) => AnyNodeRef::ExprDict(node), - ExprRef::Set(node) => AnyNodeRef::ExprSet(node), - ExprRef::ListComp(node) => AnyNodeRef::ExprListComp(node), - ExprRef::SetComp(node) => AnyNodeRef::ExprSetComp(node), - ExprRef::DictComp(node) => AnyNodeRef::ExprDictComp(node), - ExprRef::Generator(node) => AnyNodeRef::ExprGenerator(node), - ExprRef::Await(node) => AnyNodeRef::ExprAwait(node), - ExprRef::Yield(node) => AnyNodeRef::ExprYield(node), - ExprRef::YieldFrom(node) => AnyNodeRef::ExprYieldFrom(node), - ExprRef::Compare(node) => AnyNodeRef::ExprCompare(node), - ExprRef::Call(node) => AnyNodeRef::ExprCall(node), - ExprRef::FString(node) => AnyNodeRef::ExprFString(node), - ExprRef::TString(node) => AnyNodeRef::ExprTString(node), - ExprRef::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), - ExprRef::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), - ExprRef::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), - ExprRef::BooleanLiteral(node) => AnyNodeRef::ExprBooleanLiteral(node), - ExprRef::NoneLiteral(node) => AnyNodeRef::ExprNoneLiteral(node), - ExprRef::EllipsisLiteral(node) => AnyNodeRef::ExprEllipsisLiteral(node), - ExprRef::Attribute(node) => AnyNodeRef::ExprAttribute(node), - ExprRef::Subscript(node) => AnyNodeRef::ExprSubscript(node), - ExprRef::Starred(node) => AnyNodeRef::ExprStarred(node), - ExprRef::Name(node) => AnyNodeRef::ExprName(node), - ExprRef::List(node) => AnyNodeRef::ExprList(node), - ExprRef::Tuple(node) => AnyNodeRef::ExprTuple(node), - ExprRef::Slice(node) => AnyNodeRef::ExprSlice(node), - ExprRef::IpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node), - } +impl<'a> From<&'a crate::StmtDelete> for StmtRef<'a> { + fn from(node: &'a crate::StmtDelete) -> Self { + Self::Delete(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_expr_ref(self) -> Option> { - match self { - Self::ExprBoolOp(node) => Some(ExprRef::BoolOp(node)), - Self::ExprNamed(node) => Some(ExprRef::Named(node)), - Self::ExprBinOp(node) => Some(ExprRef::BinOp(node)), - Self::ExprUnaryOp(node) => Some(ExprRef::UnaryOp(node)), - Self::ExprLambda(node) => Some(ExprRef::Lambda(node)), - Self::ExprIf(node) => Some(ExprRef::If(node)), - Self::ExprDict(node) => Some(ExprRef::Dict(node)), - Self::ExprSet(node) => Some(ExprRef::Set(node)), - Self::ExprListComp(node) => Some(ExprRef::ListComp(node)), - Self::ExprSetComp(node) => Some(ExprRef::SetComp(node)), - Self::ExprDictComp(node) => Some(ExprRef::DictComp(node)), - Self::ExprGenerator(node) => Some(ExprRef::Generator(node)), - Self::ExprAwait(node) => Some(ExprRef::Await(node)), - Self::ExprYield(node) => Some(ExprRef::Yield(node)), - Self::ExprYieldFrom(node) => Some(ExprRef::YieldFrom(node)), - Self::ExprCompare(node) => Some(ExprRef::Compare(node)), - Self::ExprCall(node) => Some(ExprRef::Call(node)), - Self::ExprFString(node) => Some(ExprRef::FString(node)), - Self::ExprTString(node) => Some(ExprRef::TString(node)), - Self::ExprStringLiteral(node) => Some(ExprRef::StringLiteral(node)), - Self::ExprBytesLiteral(node) => Some(ExprRef::BytesLiteral(node)), - Self::ExprNumberLiteral(node) => Some(ExprRef::NumberLiteral(node)), - Self::ExprBooleanLiteral(node) => Some(ExprRef::BooleanLiteral(node)), - Self::ExprNoneLiteral(node) => Some(ExprRef::NoneLiteral(node)), - Self::ExprEllipsisLiteral(node) => Some(ExprRef::EllipsisLiteral(node)), - Self::ExprAttribute(node) => Some(ExprRef::Attribute(node)), - Self::ExprSubscript(node) => Some(ExprRef::Subscript(node)), - Self::ExprStarred(node) => Some(ExprRef::Starred(node)), - Self::ExprName(node) => Some(ExprRef::Name(node)), - Self::ExprList(node) => Some(ExprRef::List(node)), - Self::ExprTuple(node) => Some(ExprRef::Tuple(node)), - Self::ExprSlice(node) => Some(ExprRef::Slice(node)), - Self::ExprIpyEscapeCommand(node) => Some(ExprRef::IpyEscapeCommand(node)), - - _ => None, - } +impl<'a> From<&'a crate::StmtTypeAlias> for StmtRef<'a> { + fn from(node: &'a crate::StmtTypeAlias) -> Self { + Self::TypeAlias(node) } } -impl<'a> From<&'a ExceptHandler> for AnyNodeRef<'a> { - fn from(node: &'a ExceptHandler) -> AnyNodeRef<'a> { - match node { - ExceptHandler::ExceptHandler(node) => AnyNodeRef::ExceptHandlerExceptHandler(node), - } +impl<'a> From<&'a crate::StmtAssign> for StmtRef<'a> { + fn from(node: &'a crate::StmtAssign) -> Self { + Self::Assign(node) } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: ExceptHandlerRef<'a>) -> AnyNodeRef<'a> { - match node { - ExceptHandlerRef::ExceptHandler(node) => AnyNodeRef::ExceptHandlerExceptHandler(node), - } +impl<'a> From<&'a crate::StmtAugAssign> for StmtRef<'a> { + fn from(node: &'a crate::StmtAugAssign) -> Self { + Self::AugAssign(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_except_handler_ref(self) -> Option> { - match self { - Self::ExceptHandlerExceptHandler(node) => Some(ExceptHandlerRef::ExceptHandler(node)), - - _ => None, - } +impl<'a> From<&'a crate::StmtAnnAssign> for StmtRef<'a> { + fn from(node: &'a crate::StmtAnnAssign) -> Self { + Self::AnnAssign(node) } } -impl<'a> From<&'a InterpolatedStringElement> for AnyNodeRef<'a> { - fn from(node: &'a InterpolatedStringElement) -> AnyNodeRef<'a> { - match node { - InterpolatedStringElement::Interpolation(node) => AnyNodeRef::InterpolatedElement(node), - InterpolatedStringElement::Literal(node) => { - AnyNodeRef::InterpolatedStringLiteralElement(node) - } - } +impl<'a> From<&'a crate::StmtFor> for StmtRef<'a> { + fn from(node: &'a crate::StmtFor) -> Self { + Self::For(node) } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: InterpolatedStringElementRef<'a>) -> AnyNodeRef<'a> { - match node { - InterpolatedStringElementRef::Interpolation(node) => { - AnyNodeRef::InterpolatedElement(node) - } - InterpolatedStringElementRef::Literal(node) => { - AnyNodeRef::InterpolatedStringLiteralElement(node) - } - } +impl<'a> From<&'a crate::StmtWhile> for StmtRef<'a> { + fn from(node: &'a crate::StmtWhile) -> Self { + Self::While(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_interpolated_string_element_ref(self) -> Option> { - match self { - Self::InterpolatedElement(node) => { - Some(InterpolatedStringElementRef::Interpolation(node)) - } - Self::InterpolatedStringLiteralElement(node) => { - Some(InterpolatedStringElementRef::Literal(node)) - } - - _ => None, - } +impl<'a> From<&'a crate::StmtIf> for StmtRef<'a> { + fn from(node: &'a crate::StmtIf) -> Self { + Self::If(node) } } -impl<'a> From<&'a Pattern> for AnyNodeRef<'a> { - fn from(node: &'a Pattern) -> AnyNodeRef<'a> { - match node { - Pattern::MatchValue(node) => AnyNodeRef::PatternMatchValue(node), - Pattern::MatchSingleton(node) => AnyNodeRef::PatternMatchSingleton(node), - Pattern::MatchSequence(node) => AnyNodeRef::PatternMatchSequence(node), - Pattern::MatchMapping(node) => AnyNodeRef::PatternMatchMapping(node), - Pattern::MatchClass(node) => AnyNodeRef::PatternMatchClass(node), - Pattern::MatchStar(node) => AnyNodeRef::PatternMatchStar(node), - Pattern::MatchAs(node) => AnyNodeRef::PatternMatchAs(node), - Pattern::MatchOr(node) => AnyNodeRef::PatternMatchOr(node), - } +impl<'a> From<&'a crate::StmtWith> for StmtRef<'a> { + fn from(node: &'a crate::StmtWith) -> Self { + Self::With(node) } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: PatternRef<'a>) -> AnyNodeRef<'a> { - match node { - PatternRef::MatchValue(node) => AnyNodeRef::PatternMatchValue(node), - PatternRef::MatchSingleton(node) => AnyNodeRef::PatternMatchSingleton(node), - PatternRef::MatchSequence(node) => AnyNodeRef::PatternMatchSequence(node), - PatternRef::MatchMapping(node) => AnyNodeRef::PatternMatchMapping(node), - PatternRef::MatchClass(node) => AnyNodeRef::PatternMatchClass(node), - PatternRef::MatchStar(node) => AnyNodeRef::PatternMatchStar(node), - PatternRef::MatchAs(node) => AnyNodeRef::PatternMatchAs(node), - PatternRef::MatchOr(node) => AnyNodeRef::PatternMatchOr(node), - } +impl<'a> From<&'a crate::StmtMatch> for StmtRef<'a> { + fn from(node: &'a crate::StmtMatch) -> Self { + Self::Match(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_pattern_ref(self) -> Option> { - match self { - Self::PatternMatchValue(node) => Some(PatternRef::MatchValue(node)), - Self::PatternMatchSingleton(node) => Some(PatternRef::MatchSingleton(node)), - Self::PatternMatchSequence(node) => Some(PatternRef::MatchSequence(node)), - Self::PatternMatchMapping(node) => Some(PatternRef::MatchMapping(node)), - Self::PatternMatchClass(node) => Some(PatternRef::MatchClass(node)), - Self::PatternMatchStar(node) => Some(PatternRef::MatchStar(node)), - Self::PatternMatchAs(node) => Some(PatternRef::MatchAs(node)), - Self::PatternMatchOr(node) => Some(PatternRef::MatchOr(node)), - - _ => None, - } +impl<'a> From<&'a crate::StmtRaise> for StmtRef<'a> { + fn from(node: &'a crate::StmtRaise) -> Self { + Self::Raise(node) } } -impl<'a> From<&'a TypeParam> for AnyNodeRef<'a> { - fn from(node: &'a TypeParam) -> AnyNodeRef<'a> { - match node { - TypeParam::TypeVar(node) => AnyNodeRef::TypeParamTypeVar(node), - TypeParam::TypeVarTuple(node) => AnyNodeRef::TypeParamTypeVarTuple(node), - TypeParam::ParamSpec(node) => AnyNodeRef::TypeParamParamSpec(node), - } +impl<'a> From<&'a crate::StmtTry> for StmtRef<'a> { + fn from(node: &'a crate::StmtTry) -> Self { + Self::Try(node) } } -impl<'a> From> for AnyNodeRef<'a> { - fn from(node: TypeParamRef<'a>) -> AnyNodeRef<'a> { - match node { - TypeParamRef::TypeVar(node) => AnyNodeRef::TypeParamTypeVar(node), - TypeParamRef::TypeVarTuple(node) => AnyNodeRef::TypeParamTypeVarTuple(node), - TypeParamRef::ParamSpec(node) => AnyNodeRef::TypeParamParamSpec(node), - } +impl<'a> From<&'a crate::StmtAssert> for StmtRef<'a> { + fn from(node: &'a crate::StmtAssert) -> Self { + Self::Assert(node) } } -impl<'a> AnyNodeRef<'a> { - pub fn as_type_param_ref(self) -> Option> { - match self { - Self::TypeParamTypeVar(node) => Some(TypeParamRef::TypeVar(node)), - Self::TypeParamTypeVarTuple(node) => Some(TypeParamRef::TypeVarTuple(node)), - Self::TypeParamParamSpec(node) => Some(TypeParamRef::ParamSpec(node)), - - _ => None, - } +impl<'a> From<&'a crate::StmtImport> for StmtRef<'a> { + fn from(node: &'a crate::StmtImport) -> Self { + Self::Import(node) } } -impl<'a> From<&'a crate::ModModule> for AnyNodeRef<'a> { - fn from(node: &'a crate::ModModule) -> AnyNodeRef<'a> { - AnyNodeRef::ModModule(node) +impl<'a> From<&'a crate::StmtImportFrom> for StmtRef<'a> { + fn from(node: &'a crate::StmtImportFrom) -> Self { + Self::ImportFrom(node) } } -impl<'a> From<&'a crate::ModExpression> for AnyNodeRef<'a> { - fn from(node: &'a crate::ModExpression) -> AnyNodeRef<'a> { - AnyNodeRef::ModExpression(node) +impl<'a> From<&'a crate::StmtGlobal> for StmtRef<'a> { + fn from(node: &'a crate::StmtGlobal) -> Self { + Self::Global(node) } } -impl<'a> From<&'a crate::StmtFunctionDef> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtFunctionDef) -> AnyNodeRef<'a> { - AnyNodeRef::StmtFunctionDef(node) +impl<'a> From<&'a crate::StmtNonlocal> for StmtRef<'a> { + fn from(node: &'a crate::StmtNonlocal) -> Self { + Self::Nonlocal(node) } } -impl<'a> From<&'a crate::StmtClassDef> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtClassDef) -> AnyNodeRef<'a> { - AnyNodeRef::StmtClassDef(node) +impl<'a> From<&'a crate::StmtExpr> for StmtRef<'a> { + fn from(node: &'a crate::StmtExpr) -> Self { + Self::Expr(node) } } -impl<'a> From<&'a crate::StmtReturn> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtReturn) -> AnyNodeRef<'a> { - AnyNodeRef::StmtReturn(node) +impl<'a> From<&'a crate::StmtPass> for StmtRef<'a> { + fn from(node: &'a crate::StmtPass) -> Self { + Self::Pass(node) } } -impl<'a> From<&'a crate::StmtDelete> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtDelete) -> AnyNodeRef<'a> { - AnyNodeRef::StmtDelete(node) +impl<'a> From<&'a crate::StmtBreak> for StmtRef<'a> { + fn from(node: &'a crate::StmtBreak) -> Self { + Self::Break(node) } } -impl<'a> From<&'a crate::StmtTypeAlias> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtTypeAlias) -> AnyNodeRef<'a> { - AnyNodeRef::StmtTypeAlias(node) +impl<'a> From<&'a crate::StmtContinue> for StmtRef<'a> { + fn from(node: &'a crate::StmtContinue) -> Self { + Self::Continue(node) } } -impl<'a> From<&'a crate::StmtAssign> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtAssign) -> AnyNodeRef<'a> { - AnyNodeRef::StmtAssign(node) +impl<'a> From<&'a crate::StmtIpyEscapeCommand> for StmtRef<'a> { + fn from(node: &'a crate::StmtIpyEscapeCommand) -> Self { + Self::IpyEscapeCommand(node) } } -impl<'a> From<&'a crate::StmtAugAssign> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtAugAssign) -> AnyNodeRef<'a> { - AnyNodeRef::StmtAugAssign(node) +impl ruff_text_size::Ranged for StmtRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::FunctionDef(node) => node.range(), + Self::ClassDef(node) => node.range(), + Self::Return(node) => node.range(), + Self::Delete(node) => node.range(), + Self::TypeAlias(node) => node.range(), + Self::Assign(node) => node.range(), + Self::AugAssign(node) => node.range(), + Self::AnnAssign(node) => node.range(), + Self::For(node) => node.range(), + Self::While(node) => node.range(), + Self::If(node) => node.range(), + Self::With(node) => node.range(), + Self::Match(node) => node.range(), + Self::Raise(node) => node.range(), + Self::Try(node) => node.range(), + Self::Assert(node) => node.range(), + Self::Import(node) => node.range(), + Self::ImportFrom(node) => node.range(), + Self::Global(node) => node.range(), + Self::Nonlocal(node) => node.range(), + Self::Expr(node) => node.range(), + Self::Pass(node) => node.range(), + Self::Break(node) => node.range(), + Self::Continue(node) => node.range(), + Self::IpyEscapeCommand(node) => node.range(), + } } } -impl<'a> From<&'a crate::StmtAnnAssign> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtAnnAssign) -> AnyNodeRef<'a> { - AnyNodeRef::StmtAnnAssign(node) +impl crate::HasNodeIndex for StmtRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::FunctionDef(node) => node.node_index(), + Self::ClassDef(node) => node.node_index(), + Self::Return(node) => node.node_index(), + Self::Delete(node) => node.node_index(), + Self::TypeAlias(node) => node.node_index(), + Self::Assign(node) => node.node_index(), + Self::AugAssign(node) => node.node_index(), + Self::AnnAssign(node) => node.node_index(), + Self::For(node) => node.node_index(), + Self::While(node) => node.node_index(), + Self::If(node) => node.node_index(), + Self::With(node) => node.node_index(), + Self::Match(node) => node.node_index(), + Self::Raise(node) => node.node_index(), + Self::Try(node) => node.node_index(), + Self::Assert(node) => node.node_index(), + Self::Import(node) => node.node_index(), + Self::ImportFrom(node) => node.node_index(), + Self::Global(node) => node.node_index(), + Self::Nonlocal(node) => node.node_index(), + Self::Expr(node) => node.node_index(), + Self::Pass(node) => node.node_index(), + Self::Break(node) => node.node_index(), + Self::Continue(node) => node.node_index(), + Self::IpyEscapeCommand(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::StmtFor> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtFor) -> AnyNodeRef<'a> { - AnyNodeRef::StmtFor(node) - } +/// See also [expr](https://docs.python.org/3/library/ast.html#ast.expr) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum ExprRef<'a> { + #[is(name = "bool_op_expr")] + BoolOp(&'a crate::ExprBoolOp), + #[is(name = "named_expr")] + Named(&'a crate::ExprNamed), + #[is(name = "bin_op_expr")] + BinOp(&'a crate::ExprBinOp), + #[is(name = "unary_op_expr")] + UnaryOp(&'a crate::ExprUnaryOp), + #[is(name = "lambda_expr")] + Lambda(&'a crate::ExprLambda), + #[is(name = "if_expr")] + If(&'a crate::ExprIf), + #[is(name = "dict_expr")] + Dict(&'a crate::ExprDict), + #[is(name = "set_expr")] + Set(&'a crate::ExprSet), + #[is(name = "list_comp_expr")] + ListComp(&'a crate::ExprListComp), + #[is(name = "set_comp_expr")] + SetComp(&'a crate::ExprSetComp), + #[is(name = "dict_comp_expr")] + DictComp(&'a crate::ExprDictComp), + #[is(name = "generator_expr")] + Generator(&'a crate::ExprGenerator), + #[is(name = "await_expr")] + Await(&'a crate::ExprAwait), + #[is(name = "yield_expr")] + Yield(&'a crate::ExprYield), + #[is(name = "yield_from_expr")] + YieldFrom(&'a crate::ExprYieldFrom), + #[is(name = "compare_expr")] + Compare(&'a crate::ExprCompare), + #[is(name = "call_expr")] + Call(&'a crate::ExprCall), + #[is(name = "f_string_expr")] + FString(&'a crate::ExprFString), + #[is(name = "t_string_expr")] + TString(&'a crate::ExprTString), + #[is(name = "string_literal_expr")] + StringLiteral(&'a crate::ExprStringLiteral), + #[is(name = "bytes_literal_expr")] + BytesLiteral(&'a crate::ExprBytesLiteral), + #[is(name = "number_literal_expr")] + NumberLiteral(&'a crate::ExprNumberLiteral), + #[is(name = "boolean_literal_expr")] + BooleanLiteral(&'a crate::ExprBooleanLiteral), + #[is(name = "none_literal_expr")] + NoneLiteral(&'a crate::ExprNoneLiteral), + #[is(name = "ellipsis_literal_expr")] + EllipsisLiteral(&'a crate::ExprEllipsisLiteral), + #[is(name = "attribute_expr")] + Attribute(&'a crate::ExprAttribute), + #[is(name = "subscript_expr")] + Subscript(&'a crate::ExprSubscript), + #[is(name = "starred_expr")] + Starred(&'a crate::ExprStarred), + #[is(name = "name_expr")] + Name(&'a crate::ExprName), + #[is(name = "list_expr")] + List(&'a crate::ExprList), + #[is(name = "tuple_expr")] + Tuple(&'a crate::ExprTuple), + #[is(name = "slice_expr")] + Slice(&'a crate::ExprSlice), + #[is(name = "ipy_escape_command_expr")] + IpyEscapeCommand(&'a crate::ExprIpyEscapeCommand), } -impl<'a> From<&'a crate::StmtWhile> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtWhile) -> AnyNodeRef<'a> { - AnyNodeRef::StmtWhile(node) +impl<'a> From<&'a Expr> for ExprRef<'a> { + fn from(node: &'a Expr) -> Self { + match node { + Expr::BoolOp(node) => ExprRef::BoolOp(node), + Expr::Named(node) => ExprRef::Named(node), + Expr::BinOp(node) => ExprRef::BinOp(node), + Expr::UnaryOp(node) => ExprRef::UnaryOp(node), + Expr::Lambda(node) => ExprRef::Lambda(node), + Expr::If(node) => ExprRef::If(node), + Expr::Dict(node) => ExprRef::Dict(node), + Expr::Set(node) => ExprRef::Set(node), + Expr::ListComp(node) => ExprRef::ListComp(node), + Expr::SetComp(node) => ExprRef::SetComp(node), + Expr::DictComp(node) => ExprRef::DictComp(node), + Expr::Generator(node) => ExprRef::Generator(node), + Expr::Await(node) => ExprRef::Await(node), + Expr::Yield(node) => ExprRef::Yield(node), + Expr::YieldFrom(node) => ExprRef::YieldFrom(node), + Expr::Compare(node) => ExprRef::Compare(node), + Expr::Call(node) => ExprRef::Call(node), + Expr::FString(node) => ExprRef::FString(node), + Expr::TString(node) => ExprRef::TString(node), + Expr::StringLiteral(node) => ExprRef::StringLiteral(node), + Expr::BytesLiteral(node) => ExprRef::BytesLiteral(node), + Expr::NumberLiteral(node) => ExprRef::NumberLiteral(node), + Expr::BooleanLiteral(node) => ExprRef::BooleanLiteral(node), + Expr::NoneLiteral(node) => ExprRef::NoneLiteral(node), + Expr::EllipsisLiteral(node) => ExprRef::EllipsisLiteral(node), + Expr::Attribute(node) => ExprRef::Attribute(node), + Expr::Subscript(node) => ExprRef::Subscript(node), + Expr::Starred(node) => ExprRef::Starred(node), + Expr::Name(node) => ExprRef::Name(node), + Expr::List(node) => ExprRef::List(node), + Expr::Tuple(node) => ExprRef::Tuple(node), + Expr::Slice(node) => ExprRef::Slice(node), + Expr::IpyEscapeCommand(node) => ExprRef::IpyEscapeCommand(node), + } } } -impl<'a> From<&'a crate::StmtIf> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtIf) -> AnyNodeRef<'a> { - AnyNodeRef::StmtIf(node) +impl<'a> From<&'a crate::ExprBoolOp> for ExprRef<'a> { + fn from(node: &'a crate::ExprBoolOp) -> Self { + Self::BoolOp(node) } } -impl<'a> From<&'a crate::StmtWith> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtWith) -> AnyNodeRef<'a> { - AnyNodeRef::StmtWith(node) +impl<'a> From<&'a crate::ExprNamed> for ExprRef<'a> { + fn from(node: &'a crate::ExprNamed) -> Self { + Self::Named(node) } } -impl<'a> From<&'a crate::StmtMatch> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtMatch) -> AnyNodeRef<'a> { - AnyNodeRef::StmtMatch(node) +impl<'a> From<&'a crate::ExprBinOp> for ExprRef<'a> { + fn from(node: &'a crate::ExprBinOp) -> Self { + Self::BinOp(node) } } -impl<'a> From<&'a crate::StmtRaise> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtRaise) -> AnyNodeRef<'a> { - AnyNodeRef::StmtRaise(node) +impl<'a> From<&'a crate::ExprUnaryOp> for ExprRef<'a> { + fn from(node: &'a crate::ExprUnaryOp) -> Self { + Self::UnaryOp(node) } } -impl<'a> From<&'a crate::StmtTry> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtTry) -> AnyNodeRef<'a> { - AnyNodeRef::StmtTry(node) +impl<'a> From<&'a crate::ExprLambda> for ExprRef<'a> { + fn from(node: &'a crate::ExprLambda) -> Self { + Self::Lambda(node) } } -impl<'a> From<&'a crate::StmtAssert> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtAssert) -> AnyNodeRef<'a> { - AnyNodeRef::StmtAssert(node) +impl<'a> From<&'a crate::ExprIf> for ExprRef<'a> { + fn from(node: &'a crate::ExprIf) -> Self { + Self::If(node) } } -impl<'a> From<&'a crate::StmtImport> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtImport) -> AnyNodeRef<'a> { - AnyNodeRef::StmtImport(node) +impl<'a> From<&'a crate::ExprDict> for ExprRef<'a> { + fn from(node: &'a crate::ExprDict) -> Self { + Self::Dict(node) } } -impl<'a> From<&'a crate::StmtImportFrom> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtImportFrom) -> AnyNodeRef<'a> { - AnyNodeRef::StmtImportFrom(node) +impl<'a> From<&'a crate::ExprSet> for ExprRef<'a> { + fn from(node: &'a crate::ExprSet) -> Self { + Self::Set(node) } } -impl<'a> From<&'a crate::StmtGlobal> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtGlobal) -> AnyNodeRef<'a> { - AnyNodeRef::StmtGlobal(node) +impl<'a> From<&'a crate::ExprListComp> for ExprRef<'a> { + fn from(node: &'a crate::ExprListComp) -> Self { + Self::ListComp(node) } } -impl<'a> From<&'a crate::StmtNonlocal> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtNonlocal) -> AnyNodeRef<'a> { - AnyNodeRef::StmtNonlocal(node) +impl<'a> From<&'a crate::ExprSetComp> for ExprRef<'a> { + fn from(node: &'a crate::ExprSetComp) -> Self { + Self::SetComp(node) } } -impl<'a> From<&'a crate::StmtExpr> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtExpr) -> AnyNodeRef<'a> { - AnyNodeRef::StmtExpr(node) +impl<'a> From<&'a crate::ExprDictComp> for ExprRef<'a> { + fn from(node: &'a crate::ExprDictComp) -> Self { + Self::DictComp(node) } } -impl<'a> From<&'a crate::StmtPass> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtPass) -> AnyNodeRef<'a> { - AnyNodeRef::StmtPass(node) +impl<'a> From<&'a crate::ExprGenerator> for ExprRef<'a> { + fn from(node: &'a crate::ExprGenerator) -> Self { + Self::Generator(node) } } -impl<'a> From<&'a crate::StmtBreak> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtBreak) -> AnyNodeRef<'a> { - AnyNodeRef::StmtBreak(node) +impl<'a> From<&'a crate::ExprAwait> for ExprRef<'a> { + fn from(node: &'a crate::ExprAwait) -> Self { + Self::Await(node) } } -impl<'a> From<&'a crate::StmtContinue> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtContinue) -> AnyNodeRef<'a> { - AnyNodeRef::StmtContinue(node) +impl<'a> From<&'a crate::ExprYield> for ExprRef<'a> { + fn from(node: &'a crate::ExprYield) -> Self { + Self::Yield(node) } } -impl<'a> From<&'a crate::StmtIpyEscapeCommand> for AnyNodeRef<'a> { - fn from(node: &'a crate::StmtIpyEscapeCommand) -> AnyNodeRef<'a> { - AnyNodeRef::StmtIpyEscapeCommand(node) +impl<'a> From<&'a crate::ExprYieldFrom> for ExprRef<'a> { + fn from(node: &'a crate::ExprYieldFrom) -> Self { + Self::YieldFrom(node) } } -impl<'a> From<&'a crate::ExprBoolOp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprBoolOp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprBoolOp(node) +impl<'a> From<&'a crate::ExprCompare> for ExprRef<'a> { + fn from(node: &'a crate::ExprCompare) -> Self { + Self::Compare(node) } } -impl<'a> From<&'a crate::ExprNamed> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprNamed) -> AnyNodeRef<'a> { - AnyNodeRef::ExprNamed(node) +impl<'a> From<&'a crate::ExprCall> for ExprRef<'a> { + fn from(node: &'a crate::ExprCall) -> Self { + Self::Call(node) } } -impl<'a> From<&'a crate::ExprBinOp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprBinOp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprBinOp(node) +impl<'a> From<&'a crate::ExprFString> for ExprRef<'a> { + fn from(node: &'a crate::ExprFString) -> Self { + Self::FString(node) } } -impl<'a> From<&'a crate::ExprUnaryOp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprUnaryOp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprUnaryOp(node) +impl<'a> From<&'a crate::ExprTString> for ExprRef<'a> { + fn from(node: &'a crate::ExprTString) -> Self { + Self::TString(node) } } -impl<'a> From<&'a crate::ExprLambda> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprLambda) -> AnyNodeRef<'a> { - AnyNodeRef::ExprLambda(node) +impl<'a> From<&'a crate::ExprStringLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprStringLiteral) -> Self { + Self::StringLiteral(node) } } -impl<'a> From<&'a crate::ExprIf> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprIf) -> AnyNodeRef<'a> { - AnyNodeRef::ExprIf(node) +impl<'a> From<&'a crate::ExprBytesLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprBytesLiteral) -> Self { + Self::BytesLiteral(node) } } -impl<'a> From<&'a crate::ExprDict> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprDict) -> AnyNodeRef<'a> { - AnyNodeRef::ExprDict(node) +impl<'a> From<&'a crate::ExprNumberLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprNumberLiteral) -> Self { + Self::NumberLiteral(node) } } -impl<'a> From<&'a crate::ExprSet> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprSet) -> AnyNodeRef<'a> { - AnyNodeRef::ExprSet(node) +impl<'a> From<&'a crate::ExprBooleanLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprBooleanLiteral) -> Self { + Self::BooleanLiteral(node) } } -impl<'a> From<&'a crate::ExprListComp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprListComp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprListComp(node) +impl<'a> From<&'a crate::ExprNoneLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprNoneLiteral) -> Self { + Self::NoneLiteral(node) } } -impl<'a> From<&'a crate::ExprSetComp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprSetComp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprSetComp(node) +impl<'a> From<&'a crate::ExprEllipsisLiteral> for ExprRef<'a> { + fn from(node: &'a crate::ExprEllipsisLiteral) -> Self { + Self::EllipsisLiteral(node) } } -impl<'a> From<&'a crate::ExprDictComp> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprDictComp) -> AnyNodeRef<'a> { - AnyNodeRef::ExprDictComp(node) +impl<'a> From<&'a crate::ExprAttribute> for ExprRef<'a> { + fn from(node: &'a crate::ExprAttribute) -> Self { + Self::Attribute(node) } } -impl<'a> From<&'a crate::ExprGenerator> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprGenerator) -> AnyNodeRef<'a> { - AnyNodeRef::ExprGenerator(node) +impl<'a> From<&'a crate::ExprSubscript> for ExprRef<'a> { + fn from(node: &'a crate::ExprSubscript) -> Self { + Self::Subscript(node) } } -impl<'a> From<&'a crate::ExprAwait> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprAwait) -> AnyNodeRef<'a> { - AnyNodeRef::ExprAwait(node) +impl<'a> From<&'a crate::ExprStarred> for ExprRef<'a> { + fn from(node: &'a crate::ExprStarred) -> Self { + Self::Starred(node) } } -impl<'a> From<&'a crate::ExprYield> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprYield) -> AnyNodeRef<'a> { - AnyNodeRef::ExprYield(node) +impl<'a> From<&'a crate::ExprName> for ExprRef<'a> { + fn from(node: &'a crate::ExprName) -> Self { + Self::Name(node) } } -impl<'a> From<&'a crate::ExprYieldFrom> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprYieldFrom) -> AnyNodeRef<'a> { - AnyNodeRef::ExprYieldFrom(node) +impl<'a> From<&'a crate::ExprList> for ExprRef<'a> { + fn from(node: &'a crate::ExprList) -> Self { + Self::List(node) } } -impl<'a> From<&'a crate::ExprCompare> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprCompare) -> AnyNodeRef<'a> { - AnyNodeRef::ExprCompare(node) +impl<'a> From<&'a crate::ExprTuple> for ExprRef<'a> { + fn from(node: &'a crate::ExprTuple) -> Self { + Self::Tuple(node) } } -impl<'a> From<&'a crate::ExprCall> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprCall) -> AnyNodeRef<'a> { - AnyNodeRef::ExprCall(node) +impl<'a> From<&'a crate::ExprSlice> for ExprRef<'a> { + fn from(node: &'a crate::ExprSlice) -> Self { + Self::Slice(node) } } -impl<'a> From<&'a crate::ExprFString> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprFString) -> AnyNodeRef<'a> { - AnyNodeRef::ExprFString(node) +impl<'a> From<&'a crate::ExprIpyEscapeCommand> for ExprRef<'a> { + fn from(node: &'a crate::ExprIpyEscapeCommand) -> Self { + Self::IpyEscapeCommand(node) } } -impl<'a> From<&'a crate::ExprTString> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprTString) -> AnyNodeRef<'a> { - AnyNodeRef::ExprTString(node) +impl ruff_text_size::Ranged for ExprRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::BoolOp(node) => node.range(), + Self::Named(node) => node.range(), + Self::BinOp(node) => node.range(), + Self::UnaryOp(node) => node.range(), + Self::Lambda(node) => node.range(), + Self::If(node) => node.range(), + Self::Dict(node) => node.range(), + Self::Set(node) => node.range(), + Self::ListComp(node) => node.range(), + Self::SetComp(node) => node.range(), + Self::DictComp(node) => node.range(), + Self::Generator(node) => node.range(), + Self::Await(node) => node.range(), + Self::Yield(node) => node.range(), + Self::YieldFrom(node) => node.range(), + Self::Compare(node) => node.range(), + Self::Call(node) => node.range(), + Self::FString(node) => node.range(), + Self::TString(node) => node.range(), + Self::StringLiteral(node) => node.range(), + Self::BytesLiteral(node) => node.range(), + Self::NumberLiteral(node) => node.range(), + Self::BooleanLiteral(node) => node.range(), + Self::NoneLiteral(node) => node.range(), + Self::EllipsisLiteral(node) => node.range(), + Self::Attribute(node) => node.range(), + Self::Subscript(node) => node.range(), + Self::Starred(node) => node.range(), + Self::Name(node) => node.range(), + Self::List(node) => node.range(), + Self::Tuple(node) => node.range(), + Self::Slice(node) => node.range(), + Self::IpyEscapeCommand(node) => node.range(), + } } } -impl<'a> From<&'a crate::ExprStringLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprStringLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprStringLiteral(node) +impl crate::HasNodeIndex for ExprRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::BoolOp(node) => node.node_index(), + Self::Named(node) => node.node_index(), + Self::BinOp(node) => node.node_index(), + Self::UnaryOp(node) => node.node_index(), + Self::Lambda(node) => node.node_index(), + Self::If(node) => node.node_index(), + Self::Dict(node) => node.node_index(), + Self::Set(node) => node.node_index(), + Self::ListComp(node) => node.node_index(), + Self::SetComp(node) => node.node_index(), + Self::DictComp(node) => node.node_index(), + Self::Generator(node) => node.node_index(), + Self::Await(node) => node.node_index(), + Self::Yield(node) => node.node_index(), + Self::YieldFrom(node) => node.node_index(), + Self::Compare(node) => node.node_index(), + Self::Call(node) => node.node_index(), + Self::FString(node) => node.node_index(), + Self::TString(node) => node.node_index(), + Self::StringLiteral(node) => node.node_index(), + Self::BytesLiteral(node) => node.node_index(), + Self::NumberLiteral(node) => node.node_index(), + Self::BooleanLiteral(node) => node.node_index(), + Self::NoneLiteral(node) => node.node_index(), + Self::EllipsisLiteral(node) => node.node_index(), + Self::Attribute(node) => node.node_index(), + Self::Subscript(node) => node.node_index(), + Self::Starred(node) => node.node_index(), + Self::Name(node) => node.node_index(), + Self::List(node) => node.node_index(), + Self::Tuple(node) => node.node_index(), + Self::Slice(node) => node.node_index(), + Self::IpyEscapeCommand(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::ExprBytesLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprBytesLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprBytesLiteral(node) - } +/// See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.excepthandler) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum ExceptHandlerRef<'a> { + ExceptHandler(&'a crate::ExceptHandlerExceptHandler), } -impl<'a> From<&'a crate::ExprNumberLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprNumberLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprNumberLiteral(node) +impl<'a> From<&'a ExceptHandler> for ExceptHandlerRef<'a> { + fn from(node: &'a ExceptHandler) -> Self { + match node { + ExceptHandler::ExceptHandler(node) => ExceptHandlerRef::ExceptHandler(node), + } } } -impl<'a> From<&'a crate::ExprBooleanLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprBooleanLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprBooleanLiteral(node) +impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for ExceptHandlerRef<'a> { + fn from(node: &'a crate::ExceptHandlerExceptHandler) -> Self { + Self::ExceptHandler(node) } } -impl<'a> From<&'a crate::ExprNoneLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprNoneLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprNoneLiteral(node) +impl ruff_text_size::Ranged for ExceptHandlerRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::ExceptHandler(node) => node.range(), + } } } -impl<'a> From<&'a crate::ExprEllipsisLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprEllipsisLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::ExprEllipsisLiteral(node) +impl crate::HasNodeIndex for ExceptHandlerRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::ExceptHandler(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::ExprAttribute> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprAttribute) -> AnyNodeRef<'a> { - AnyNodeRef::ExprAttribute(node) - } +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum InterpolatedStringElementRef<'a> { + Interpolation(&'a crate::InterpolatedElement), + Literal(&'a crate::InterpolatedStringLiteralElement), } -impl<'a> From<&'a crate::ExprSubscript> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprSubscript) -> AnyNodeRef<'a> { - AnyNodeRef::ExprSubscript(node) +impl<'a> From<&'a InterpolatedStringElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a InterpolatedStringElement) -> Self { + match node { + InterpolatedStringElement::Interpolation(node) => { + InterpolatedStringElementRef::Interpolation(node) + } + InterpolatedStringElement::Literal(node) => InterpolatedStringElementRef::Literal(node), + } } } -impl<'a> From<&'a crate::ExprStarred> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprStarred) -> AnyNodeRef<'a> { - AnyNodeRef::ExprStarred(node) +impl<'a> From<&'a crate::InterpolatedElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a crate::InterpolatedElement) -> Self { + Self::Interpolation(node) } } -impl<'a> From<&'a crate::ExprName> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprName) -> AnyNodeRef<'a> { - AnyNodeRef::ExprName(node) +impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for InterpolatedStringElementRef<'a> { + fn from(node: &'a crate::InterpolatedStringLiteralElement) -> Self { + Self::Literal(node) } } -impl<'a> From<&'a crate::ExprList> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprList) -> AnyNodeRef<'a> { - AnyNodeRef::ExprList(node) +impl ruff_text_size::Ranged for InterpolatedStringElementRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::Interpolation(node) => node.range(), + Self::Literal(node) => node.range(), + } } } -impl<'a> From<&'a crate::ExprTuple> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprTuple) -> AnyNodeRef<'a> { - AnyNodeRef::ExprTuple(node) +impl crate::HasNodeIndex for InterpolatedStringElementRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::Interpolation(node) => node.node_index(), + Self::Literal(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::ExprSlice> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprSlice) -> AnyNodeRef<'a> { - AnyNodeRef::ExprSlice(node) - } +/// See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum PatternRef<'a> { + MatchValue(&'a crate::PatternMatchValue), + MatchSingleton(&'a crate::PatternMatchSingleton), + MatchSequence(&'a crate::PatternMatchSequence), + MatchMapping(&'a crate::PatternMatchMapping), + MatchClass(&'a crate::PatternMatchClass), + MatchStar(&'a crate::PatternMatchStar), + MatchAs(&'a crate::PatternMatchAs), + MatchOr(&'a crate::PatternMatchOr), } -impl<'a> From<&'a crate::ExprIpyEscapeCommand> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExprIpyEscapeCommand) -> AnyNodeRef<'a> { - AnyNodeRef::ExprIpyEscapeCommand(node) +impl<'a> From<&'a Pattern> for PatternRef<'a> { + fn from(node: &'a Pattern) -> Self { + match node { + Pattern::MatchValue(node) => PatternRef::MatchValue(node), + Pattern::MatchSingleton(node) => PatternRef::MatchSingleton(node), + Pattern::MatchSequence(node) => PatternRef::MatchSequence(node), + Pattern::MatchMapping(node) => PatternRef::MatchMapping(node), + Pattern::MatchClass(node) => PatternRef::MatchClass(node), + Pattern::MatchStar(node) => PatternRef::MatchStar(node), + Pattern::MatchAs(node) => PatternRef::MatchAs(node), + Pattern::MatchOr(node) => PatternRef::MatchOr(node), + } } } -impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for AnyNodeRef<'a> { - fn from(node: &'a crate::ExceptHandlerExceptHandler) -> AnyNodeRef<'a> { - AnyNodeRef::ExceptHandlerExceptHandler(node) +impl<'a> From<&'a crate::PatternMatchValue> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchValue) -> Self { + Self::MatchValue(node) } } -impl<'a> From<&'a crate::InterpolatedElement> for AnyNodeRef<'a> { - fn from(node: &'a crate::InterpolatedElement) -> AnyNodeRef<'a> { - AnyNodeRef::InterpolatedElement(node) +impl<'a> From<&'a crate::PatternMatchSingleton> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchSingleton) -> Self { + Self::MatchSingleton(node) } } -impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for AnyNodeRef<'a> { - fn from(node: &'a crate::InterpolatedStringLiteralElement) -> AnyNodeRef<'a> { - AnyNodeRef::InterpolatedStringLiteralElement(node) +impl<'a> From<&'a crate::PatternMatchSequence> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchSequence) -> Self { + Self::MatchSequence(node) } } -impl<'a> From<&'a crate::PatternMatchValue> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchValue) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchValue(node) +impl<'a> From<&'a crate::PatternMatchMapping> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchMapping) -> Self { + Self::MatchMapping(node) } } -impl<'a> From<&'a crate::PatternMatchSingleton> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchSingleton) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchSingleton(node) +impl<'a> From<&'a crate::PatternMatchClass> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchClass) -> Self { + Self::MatchClass(node) } } -impl<'a> From<&'a crate::PatternMatchSequence> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchSequence) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchSequence(node) +impl<'a> From<&'a crate::PatternMatchStar> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchStar) -> Self { + Self::MatchStar(node) } } -impl<'a> From<&'a crate::PatternMatchMapping> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchMapping) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchMapping(node) +impl<'a> From<&'a crate::PatternMatchAs> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchAs) -> Self { + Self::MatchAs(node) } } -impl<'a> From<&'a crate::PatternMatchClass> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchClass) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchClass(node) +impl<'a> From<&'a crate::PatternMatchOr> for PatternRef<'a> { + fn from(node: &'a crate::PatternMatchOr) -> Self { + Self::MatchOr(node) } } -impl<'a> From<&'a crate::PatternMatchStar> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchStar) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchStar(node) - } -} - -impl<'a> From<&'a crate::PatternMatchAs> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchAs) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchAs(node) - } -} - -impl<'a> From<&'a crate::PatternMatchOr> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternMatchOr) -> AnyNodeRef<'a> { - AnyNodeRef::PatternMatchOr(node) - } -} - -impl<'a> From<&'a crate::TypeParamTypeVar> for AnyNodeRef<'a> { - fn from(node: &'a crate::TypeParamTypeVar) -> AnyNodeRef<'a> { - AnyNodeRef::TypeParamTypeVar(node) +impl ruff_text_size::Ranged for PatternRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::MatchValue(node) => node.range(), + Self::MatchSingleton(node) => node.range(), + Self::MatchSequence(node) => node.range(), + Self::MatchMapping(node) => node.range(), + Self::MatchClass(node) => node.range(), + Self::MatchStar(node) => node.range(), + Self::MatchAs(node) => node.range(), + Self::MatchOr(node) => node.range(), + } } } -impl<'a> From<&'a crate::TypeParamTypeVarTuple> for AnyNodeRef<'a> { - fn from(node: &'a crate::TypeParamTypeVarTuple) -> AnyNodeRef<'a> { - AnyNodeRef::TypeParamTypeVarTuple(node) +impl crate::HasNodeIndex for PatternRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::MatchValue(node) => node.node_index(), + Self::MatchSingleton(node) => node.node_index(), + Self::MatchSequence(node) => node.node_index(), + Self::MatchMapping(node) => node.node_index(), + Self::MatchClass(node) => node.node_index(), + Self::MatchStar(node) => node.node_index(), + Self::MatchAs(node) => node.node_index(), + Self::MatchOr(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::TypeParamParamSpec> for AnyNodeRef<'a> { - fn from(node: &'a crate::TypeParamParamSpec) -> AnyNodeRef<'a> { - AnyNodeRef::TypeParamParamSpec(node) - } +/// See also [type_param](https://docs.python.org/3/library/ast.html#ast.type_param) +#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)] +pub enum TypeParamRef<'a> { + TypeVar(&'a crate::TypeParamTypeVar), + TypeVarTuple(&'a crate::TypeParamTypeVarTuple), + ParamSpec(&'a crate::TypeParamParamSpec), } -impl<'a> From<&'a crate::InterpolatedStringFormatSpec> for AnyNodeRef<'a> { - fn from(node: &'a crate::InterpolatedStringFormatSpec) -> AnyNodeRef<'a> { - AnyNodeRef::InterpolatedStringFormatSpec(node) +impl<'a> From<&'a TypeParam> for TypeParamRef<'a> { + fn from(node: &'a TypeParam) -> Self { + match node { + TypeParam::TypeVar(node) => TypeParamRef::TypeVar(node), + TypeParam::TypeVarTuple(node) => TypeParamRef::TypeVarTuple(node), + TypeParam::ParamSpec(node) => TypeParamRef::ParamSpec(node), + } } } -impl<'a> From<&'a crate::PatternArguments> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternArguments) -> AnyNodeRef<'a> { - AnyNodeRef::PatternArguments(node) +impl<'a> From<&'a crate::TypeParamTypeVar> for TypeParamRef<'a> { + fn from(node: &'a crate::TypeParamTypeVar) -> Self { + Self::TypeVar(node) } } -impl<'a> From<&'a crate::PatternKeyword> for AnyNodeRef<'a> { - fn from(node: &'a crate::PatternKeyword) -> AnyNodeRef<'a> { - AnyNodeRef::PatternKeyword(node) +impl<'a> From<&'a crate::TypeParamTypeVarTuple> for TypeParamRef<'a> { + fn from(node: &'a crate::TypeParamTypeVarTuple) -> Self { + Self::TypeVarTuple(node) } } -impl<'a> From<&'a crate::Comprehension> for AnyNodeRef<'a> { - fn from(node: &'a crate::Comprehension) -> AnyNodeRef<'a> { - AnyNodeRef::Comprehension(node) +impl<'a> From<&'a crate::TypeParamParamSpec> for TypeParamRef<'a> { + fn from(node: &'a crate::TypeParamParamSpec) -> Self { + Self::ParamSpec(node) } } -impl<'a> From<&'a crate::Arguments> for AnyNodeRef<'a> { - fn from(node: &'a crate::Arguments) -> AnyNodeRef<'a> { - AnyNodeRef::Arguments(node) +impl ruff_text_size::Ranged for TypeParamRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + Self::TypeVar(node) => node.range(), + Self::TypeVarTuple(node) => node.range(), + Self::ParamSpec(node) => node.range(), + } } } -impl<'a> From<&'a crate::Parameters> for AnyNodeRef<'a> { - fn from(node: &'a crate::Parameters) -> AnyNodeRef<'a> { - AnyNodeRef::Parameters(node) +impl crate::HasNodeIndex for TypeParamRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + Self::TypeVar(node) => node.node_index(), + Self::TypeVarTuple(node) => node.node_index(), + Self::ParamSpec(node) => node.node_index(), + } } } -impl<'a> From<&'a crate::Parameter> for AnyNodeRef<'a> { - fn from(node: &'a crate::Parameter) -> AnyNodeRef<'a> { - AnyNodeRef::Parameter(node) - } +/// A flattened enumeration of all AST nodes. +#[derive(Copy, Clone, Debug, is_macro::Is, PartialEq)] +pub enum AnyNodeRef<'a> { + ModModule(&'a crate::ModModule), + ModExpression(&'a crate::ModExpression), + StmtFunctionDef(&'a crate::StmtFunctionDef), + StmtClassDef(&'a crate::StmtClassDef), + StmtReturn(&'a crate::StmtReturn), + StmtDelete(&'a crate::StmtDelete), + StmtTypeAlias(&'a crate::StmtTypeAlias), + StmtAssign(&'a crate::StmtAssign), + StmtAugAssign(&'a crate::StmtAugAssign), + StmtAnnAssign(&'a crate::StmtAnnAssign), + StmtFor(&'a crate::StmtFor), + StmtWhile(&'a crate::StmtWhile), + StmtIf(&'a crate::StmtIf), + StmtWith(&'a crate::StmtWith), + StmtMatch(&'a crate::StmtMatch), + StmtRaise(&'a crate::StmtRaise), + StmtTry(&'a crate::StmtTry), + StmtAssert(&'a crate::StmtAssert), + StmtImport(&'a crate::StmtImport), + StmtImportFrom(&'a crate::StmtImportFrom), + StmtGlobal(&'a crate::StmtGlobal), + StmtNonlocal(&'a crate::StmtNonlocal), + StmtExpr(&'a crate::StmtExpr), + StmtPass(&'a crate::StmtPass), + StmtBreak(&'a crate::StmtBreak), + StmtContinue(&'a crate::StmtContinue), + StmtIpyEscapeCommand(&'a crate::StmtIpyEscapeCommand), + ExprBoolOp(&'a crate::ExprBoolOp), + ExprNamed(&'a crate::ExprNamed), + ExprBinOp(&'a crate::ExprBinOp), + ExprUnaryOp(&'a crate::ExprUnaryOp), + ExprLambda(&'a crate::ExprLambda), + ExprIf(&'a crate::ExprIf), + ExprDict(&'a crate::ExprDict), + ExprSet(&'a crate::ExprSet), + ExprListComp(&'a crate::ExprListComp), + ExprSetComp(&'a crate::ExprSetComp), + ExprDictComp(&'a crate::ExprDictComp), + ExprGenerator(&'a crate::ExprGenerator), + ExprAwait(&'a crate::ExprAwait), + ExprYield(&'a crate::ExprYield), + ExprYieldFrom(&'a crate::ExprYieldFrom), + ExprCompare(&'a crate::ExprCompare), + ExprCall(&'a crate::ExprCall), + ExprFString(&'a crate::ExprFString), + ExprTString(&'a crate::ExprTString), + ExprStringLiteral(&'a crate::ExprStringLiteral), + ExprBytesLiteral(&'a crate::ExprBytesLiteral), + ExprNumberLiteral(&'a crate::ExprNumberLiteral), + ExprBooleanLiteral(&'a crate::ExprBooleanLiteral), + ExprNoneLiteral(&'a crate::ExprNoneLiteral), + ExprEllipsisLiteral(&'a crate::ExprEllipsisLiteral), + ExprAttribute(&'a crate::ExprAttribute), + ExprSubscript(&'a crate::ExprSubscript), + ExprStarred(&'a crate::ExprStarred), + ExprName(&'a crate::ExprName), + ExprList(&'a crate::ExprList), + ExprTuple(&'a crate::ExprTuple), + ExprSlice(&'a crate::ExprSlice), + ExprIpyEscapeCommand(&'a crate::ExprIpyEscapeCommand), + ExceptHandlerExceptHandler(&'a crate::ExceptHandlerExceptHandler), + InterpolatedElement(&'a crate::InterpolatedElement), + InterpolatedStringLiteralElement(&'a crate::InterpolatedStringLiteralElement), + PatternMatchValue(&'a crate::PatternMatchValue), + PatternMatchSingleton(&'a crate::PatternMatchSingleton), + PatternMatchSequence(&'a crate::PatternMatchSequence), + PatternMatchMapping(&'a crate::PatternMatchMapping), + PatternMatchClass(&'a crate::PatternMatchClass), + PatternMatchStar(&'a crate::PatternMatchStar), + PatternMatchAs(&'a crate::PatternMatchAs), + PatternMatchOr(&'a crate::PatternMatchOr), + TypeParamTypeVar(&'a crate::TypeParamTypeVar), + TypeParamTypeVarTuple(&'a crate::TypeParamTypeVarTuple), + TypeParamParamSpec(&'a crate::TypeParamParamSpec), + InterpolatedStringFormatSpec(&'a crate::InterpolatedStringFormatSpec), + PatternArguments(&'a crate::PatternArguments), + PatternKeyword(&'a crate::PatternKeyword), + Comprehension(&'a crate::Comprehension), + Arguments(&'a crate::Arguments), + Parameters(&'a crate::Parameters), + Parameter(&'a crate::Parameter), + ParameterWithDefault(&'a crate::ParameterWithDefault), + Keyword(&'a crate::Keyword), + Alias(&'a crate::Alias), + WithItem(&'a crate::WithItem), + MatchCase(&'a crate::MatchCase), + Decorator(&'a crate::Decorator), + ElifElseClause(&'a crate::ElifElseClause), + TypeParams(&'a crate::TypeParams), + FString(&'a crate::FString), + TString(&'a crate::TString), + StringLiteral(&'a crate::StringLiteral), + BytesLiteral(&'a crate::BytesLiteral), + Identifier(&'a crate::Identifier), } -impl<'a> From<&'a crate::ParameterWithDefault> for AnyNodeRef<'a> { - fn from(node: &'a crate::ParameterWithDefault) -> AnyNodeRef<'a> { - AnyNodeRef::ParameterWithDefault(node) +impl<'a> From<&'a Mod> for AnyNodeRef<'a> { + fn from(node: &'a Mod) -> AnyNodeRef<'a> { + match node { + Mod::Module(node) => AnyNodeRef::ModModule(node), + Mod::Expression(node) => AnyNodeRef::ModExpression(node), + } } } -impl<'a> From<&'a crate::Keyword> for AnyNodeRef<'a> { - fn from(node: &'a crate::Keyword) -> AnyNodeRef<'a> { - AnyNodeRef::Keyword(node) +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: ModRef<'a>) -> AnyNodeRef<'a> { + match node { + ModRef::Module(node) => AnyNodeRef::ModModule(node), + ModRef::Expression(node) => AnyNodeRef::ModExpression(node), + } } } -impl<'a> From<&'a crate::Alias> for AnyNodeRef<'a> { - fn from(node: &'a crate::Alias) -> AnyNodeRef<'a> { - AnyNodeRef::Alias(node) - } -} +impl<'a> AnyNodeRef<'a> { + pub fn as_mod_ref(self) -> Option> { + match self { + Self::ModModule(node) => Some(ModRef::Module(node)), + Self::ModExpression(node) => Some(ModRef::Expression(node)), -impl<'a> From<&'a crate::WithItem> for AnyNodeRef<'a> { - fn from(node: &'a crate::WithItem) -> AnyNodeRef<'a> { - AnyNodeRef::WithItem(node) + _ => None, + } } } -impl<'a> From<&'a crate::MatchCase> for AnyNodeRef<'a> { - fn from(node: &'a crate::MatchCase) -> AnyNodeRef<'a> { - AnyNodeRef::MatchCase(node) +impl<'a> From<&'a Stmt> for AnyNodeRef<'a> { + fn from(node: &'a Stmt) -> AnyNodeRef<'a> { + match node { + Stmt::FunctionDef(node) => AnyNodeRef::StmtFunctionDef(node), + Stmt::ClassDef(node) => AnyNodeRef::StmtClassDef(node), + Stmt::Return(node) => AnyNodeRef::StmtReturn(node), + Stmt::Delete(node) => AnyNodeRef::StmtDelete(node), + Stmt::TypeAlias(node) => AnyNodeRef::StmtTypeAlias(node), + Stmt::Assign(node) => AnyNodeRef::StmtAssign(node), + Stmt::AugAssign(node) => AnyNodeRef::StmtAugAssign(node), + Stmt::AnnAssign(node) => AnyNodeRef::StmtAnnAssign(node), + Stmt::For(node) => AnyNodeRef::StmtFor(node), + Stmt::While(node) => AnyNodeRef::StmtWhile(node), + Stmt::If(node) => AnyNodeRef::StmtIf(node), + Stmt::With(node) => AnyNodeRef::StmtWith(node), + Stmt::Match(node) => AnyNodeRef::StmtMatch(node), + Stmt::Raise(node) => AnyNodeRef::StmtRaise(node), + Stmt::Try(node) => AnyNodeRef::StmtTry(node), + Stmt::Assert(node) => AnyNodeRef::StmtAssert(node), + Stmt::Import(node) => AnyNodeRef::StmtImport(node), + Stmt::ImportFrom(node) => AnyNodeRef::StmtImportFrom(node), + Stmt::Global(node) => AnyNodeRef::StmtGlobal(node), + Stmt::Nonlocal(node) => AnyNodeRef::StmtNonlocal(node), + Stmt::Expr(node) => AnyNodeRef::StmtExpr(node), + Stmt::Pass(node) => AnyNodeRef::StmtPass(node), + Stmt::Break(node) => AnyNodeRef::StmtBreak(node), + Stmt::Continue(node) => AnyNodeRef::StmtContinue(node), + Stmt::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node), + } } } -impl<'a> From<&'a crate::Decorator> for AnyNodeRef<'a> { - fn from(node: &'a crate::Decorator) -> AnyNodeRef<'a> { - AnyNodeRef::Decorator(node) +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: StmtRef<'a>) -> AnyNodeRef<'a> { + match node { + StmtRef::FunctionDef(node) => AnyNodeRef::StmtFunctionDef(node), + StmtRef::ClassDef(node) => AnyNodeRef::StmtClassDef(node), + StmtRef::Return(node) => AnyNodeRef::StmtReturn(node), + StmtRef::Delete(node) => AnyNodeRef::StmtDelete(node), + StmtRef::TypeAlias(node) => AnyNodeRef::StmtTypeAlias(node), + StmtRef::Assign(node) => AnyNodeRef::StmtAssign(node), + StmtRef::AugAssign(node) => AnyNodeRef::StmtAugAssign(node), + StmtRef::AnnAssign(node) => AnyNodeRef::StmtAnnAssign(node), + StmtRef::For(node) => AnyNodeRef::StmtFor(node), + StmtRef::While(node) => AnyNodeRef::StmtWhile(node), + StmtRef::If(node) => AnyNodeRef::StmtIf(node), + StmtRef::With(node) => AnyNodeRef::StmtWith(node), + StmtRef::Match(node) => AnyNodeRef::StmtMatch(node), + StmtRef::Raise(node) => AnyNodeRef::StmtRaise(node), + StmtRef::Try(node) => AnyNodeRef::StmtTry(node), + StmtRef::Assert(node) => AnyNodeRef::StmtAssert(node), + StmtRef::Import(node) => AnyNodeRef::StmtImport(node), + StmtRef::ImportFrom(node) => AnyNodeRef::StmtImportFrom(node), + StmtRef::Global(node) => AnyNodeRef::StmtGlobal(node), + StmtRef::Nonlocal(node) => AnyNodeRef::StmtNonlocal(node), + StmtRef::Expr(node) => AnyNodeRef::StmtExpr(node), + StmtRef::Pass(node) => AnyNodeRef::StmtPass(node), + StmtRef::Break(node) => AnyNodeRef::StmtBreak(node), + StmtRef::Continue(node) => AnyNodeRef::StmtContinue(node), + StmtRef::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node), + } } } -impl<'a> From<&'a crate::ElifElseClause> for AnyNodeRef<'a> { - fn from(node: &'a crate::ElifElseClause) -> AnyNodeRef<'a> { - AnyNodeRef::ElifElseClause(node) +impl<'a> AnyNodeRef<'a> { + pub fn as_stmt_ref(self) -> Option> { + match self { + Self::StmtFunctionDef(node) => Some(StmtRef::FunctionDef(node)), + Self::StmtClassDef(node) => Some(StmtRef::ClassDef(node)), + Self::StmtReturn(node) => Some(StmtRef::Return(node)), + Self::StmtDelete(node) => Some(StmtRef::Delete(node)), + Self::StmtTypeAlias(node) => Some(StmtRef::TypeAlias(node)), + Self::StmtAssign(node) => Some(StmtRef::Assign(node)), + Self::StmtAugAssign(node) => Some(StmtRef::AugAssign(node)), + Self::StmtAnnAssign(node) => Some(StmtRef::AnnAssign(node)), + Self::StmtFor(node) => Some(StmtRef::For(node)), + Self::StmtWhile(node) => Some(StmtRef::While(node)), + Self::StmtIf(node) => Some(StmtRef::If(node)), + Self::StmtWith(node) => Some(StmtRef::With(node)), + Self::StmtMatch(node) => Some(StmtRef::Match(node)), + Self::StmtRaise(node) => Some(StmtRef::Raise(node)), + Self::StmtTry(node) => Some(StmtRef::Try(node)), + Self::StmtAssert(node) => Some(StmtRef::Assert(node)), + Self::StmtImport(node) => Some(StmtRef::Import(node)), + Self::StmtImportFrom(node) => Some(StmtRef::ImportFrom(node)), + Self::StmtGlobal(node) => Some(StmtRef::Global(node)), + Self::StmtNonlocal(node) => Some(StmtRef::Nonlocal(node)), + Self::StmtExpr(node) => Some(StmtRef::Expr(node)), + Self::StmtPass(node) => Some(StmtRef::Pass(node)), + Self::StmtBreak(node) => Some(StmtRef::Break(node)), + Self::StmtContinue(node) => Some(StmtRef::Continue(node)), + Self::StmtIpyEscapeCommand(node) => Some(StmtRef::IpyEscapeCommand(node)), + + _ => None, + } } } -impl<'a> From<&'a crate::TypeParams> for AnyNodeRef<'a> { - fn from(node: &'a crate::TypeParams) -> AnyNodeRef<'a> { - AnyNodeRef::TypeParams(node) +impl<'a> From<&'a Expr> for AnyNodeRef<'a> { + fn from(node: &'a Expr) -> AnyNodeRef<'a> { + match node { + Expr::BoolOp(node) => AnyNodeRef::ExprBoolOp(node), + Expr::Named(node) => AnyNodeRef::ExprNamed(node), + Expr::BinOp(node) => AnyNodeRef::ExprBinOp(node), + Expr::UnaryOp(node) => AnyNodeRef::ExprUnaryOp(node), + Expr::Lambda(node) => AnyNodeRef::ExprLambda(node), + Expr::If(node) => AnyNodeRef::ExprIf(node), + Expr::Dict(node) => AnyNodeRef::ExprDict(node), + Expr::Set(node) => AnyNodeRef::ExprSet(node), + Expr::ListComp(node) => AnyNodeRef::ExprListComp(node), + Expr::SetComp(node) => AnyNodeRef::ExprSetComp(node), + Expr::DictComp(node) => AnyNodeRef::ExprDictComp(node), + Expr::Generator(node) => AnyNodeRef::ExprGenerator(node), + Expr::Await(node) => AnyNodeRef::ExprAwait(node), + Expr::Yield(node) => AnyNodeRef::ExprYield(node), + Expr::YieldFrom(node) => AnyNodeRef::ExprYieldFrom(node), + Expr::Compare(node) => AnyNodeRef::ExprCompare(node), + Expr::Call(node) => AnyNodeRef::ExprCall(node), + Expr::FString(node) => AnyNodeRef::ExprFString(node), + Expr::TString(node) => AnyNodeRef::ExprTString(node), + Expr::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), + Expr::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), + Expr::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), + Expr::BooleanLiteral(node) => AnyNodeRef::ExprBooleanLiteral(node), + Expr::NoneLiteral(node) => AnyNodeRef::ExprNoneLiteral(node), + Expr::EllipsisLiteral(node) => AnyNodeRef::ExprEllipsisLiteral(node), + Expr::Attribute(node) => AnyNodeRef::ExprAttribute(node), + Expr::Subscript(node) => AnyNodeRef::ExprSubscript(node), + Expr::Starred(node) => AnyNodeRef::ExprStarred(node), + Expr::Name(node) => AnyNodeRef::ExprName(node), + Expr::List(node) => AnyNodeRef::ExprList(node), + Expr::Tuple(node) => AnyNodeRef::ExprTuple(node), + Expr::Slice(node) => AnyNodeRef::ExprSlice(node), + Expr::IpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node), + } } } -impl<'a> From<&'a crate::FString> for AnyNodeRef<'a> { - fn from(node: &'a crate::FString) -> AnyNodeRef<'a> { - AnyNodeRef::FString(node) +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: ExprRef<'a>) -> AnyNodeRef<'a> { + match node { + ExprRef::BoolOp(node) => AnyNodeRef::ExprBoolOp(node), + ExprRef::Named(node) => AnyNodeRef::ExprNamed(node), + ExprRef::BinOp(node) => AnyNodeRef::ExprBinOp(node), + ExprRef::UnaryOp(node) => AnyNodeRef::ExprUnaryOp(node), + ExprRef::Lambda(node) => AnyNodeRef::ExprLambda(node), + ExprRef::If(node) => AnyNodeRef::ExprIf(node), + ExprRef::Dict(node) => AnyNodeRef::ExprDict(node), + ExprRef::Set(node) => AnyNodeRef::ExprSet(node), + ExprRef::ListComp(node) => AnyNodeRef::ExprListComp(node), + ExprRef::SetComp(node) => AnyNodeRef::ExprSetComp(node), + ExprRef::DictComp(node) => AnyNodeRef::ExprDictComp(node), + ExprRef::Generator(node) => AnyNodeRef::ExprGenerator(node), + ExprRef::Await(node) => AnyNodeRef::ExprAwait(node), + ExprRef::Yield(node) => AnyNodeRef::ExprYield(node), + ExprRef::YieldFrom(node) => AnyNodeRef::ExprYieldFrom(node), + ExprRef::Compare(node) => AnyNodeRef::ExprCompare(node), + ExprRef::Call(node) => AnyNodeRef::ExprCall(node), + ExprRef::FString(node) => AnyNodeRef::ExprFString(node), + ExprRef::TString(node) => AnyNodeRef::ExprTString(node), + ExprRef::StringLiteral(node) => AnyNodeRef::ExprStringLiteral(node), + ExprRef::BytesLiteral(node) => AnyNodeRef::ExprBytesLiteral(node), + ExprRef::NumberLiteral(node) => AnyNodeRef::ExprNumberLiteral(node), + ExprRef::BooleanLiteral(node) => AnyNodeRef::ExprBooleanLiteral(node), + ExprRef::NoneLiteral(node) => AnyNodeRef::ExprNoneLiteral(node), + ExprRef::EllipsisLiteral(node) => AnyNodeRef::ExprEllipsisLiteral(node), + ExprRef::Attribute(node) => AnyNodeRef::ExprAttribute(node), + ExprRef::Subscript(node) => AnyNodeRef::ExprSubscript(node), + ExprRef::Starred(node) => AnyNodeRef::ExprStarred(node), + ExprRef::Name(node) => AnyNodeRef::ExprName(node), + ExprRef::List(node) => AnyNodeRef::ExprList(node), + ExprRef::Tuple(node) => AnyNodeRef::ExprTuple(node), + ExprRef::Slice(node) => AnyNodeRef::ExprSlice(node), + ExprRef::IpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node), + } } } -impl<'a> From<&'a crate::TString> for AnyNodeRef<'a> { - fn from(node: &'a crate::TString) -> AnyNodeRef<'a> { - AnyNodeRef::TString(node) +impl<'a> AnyNodeRef<'a> { + pub fn as_expr_ref(self) -> Option> { + match self { + Self::ExprBoolOp(node) => Some(ExprRef::BoolOp(node)), + Self::ExprNamed(node) => Some(ExprRef::Named(node)), + Self::ExprBinOp(node) => Some(ExprRef::BinOp(node)), + Self::ExprUnaryOp(node) => Some(ExprRef::UnaryOp(node)), + Self::ExprLambda(node) => Some(ExprRef::Lambda(node)), + Self::ExprIf(node) => Some(ExprRef::If(node)), + Self::ExprDict(node) => Some(ExprRef::Dict(node)), + Self::ExprSet(node) => Some(ExprRef::Set(node)), + Self::ExprListComp(node) => Some(ExprRef::ListComp(node)), + Self::ExprSetComp(node) => Some(ExprRef::SetComp(node)), + Self::ExprDictComp(node) => Some(ExprRef::DictComp(node)), + Self::ExprGenerator(node) => Some(ExprRef::Generator(node)), + Self::ExprAwait(node) => Some(ExprRef::Await(node)), + Self::ExprYield(node) => Some(ExprRef::Yield(node)), + Self::ExprYieldFrom(node) => Some(ExprRef::YieldFrom(node)), + Self::ExprCompare(node) => Some(ExprRef::Compare(node)), + Self::ExprCall(node) => Some(ExprRef::Call(node)), + Self::ExprFString(node) => Some(ExprRef::FString(node)), + Self::ExprTString(node) => Some(ExprRef::TString(node)), + Self::ExprStringLiteral(node) => Some(ExprRef::StringLiteral(node)), + Self::ExprBytesLiteral(node) => Some(ExprRef::BytesLiteral(node)), + Self::ExprNumberLiteral(node) => Some(ExprRef::NumberLiteral(node)), + Self::ExprBooleanLiteral(node) => Some(ExprRef::BooleanLiteral(node)), + Self::ExprNoneLiteral(node) => Some(ExprRef::NoneLiteral(node)), + Self::ExprEllipsisLiteral(node) => Some(ExprRef::EllipsisLiteral(node)), + Self::ExprAttribute(node) => Some(ExprRef::Attribute(node)), + Self::ExprSubscript(node) => Some(ExprRef::Subscript(node)), + Self::ExprStarred(node) => Some(ExprRef::Starred(node)), + Self::ExprName(node) => Some(ExprRef::Name(node)), + Self::ExprList(node) => Some(ExprRef::List(node)), + Self::ExprTuple(node) => Some(ExprRef::Tuple(node)), + Self::ExprSlice(node) => Some(ExprRef::Slice(node)), + Self::ExprIpyEscapeCommand(node) => Some(ExprRef::IpyEscapeCommand(node)), + + _ => None, + } } } -impl<'a> From<&'a crate::StringLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::StringLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::StringLiteral(node) +impl<'a> From<&'a ExceptHandler> for AnyNodeRef<'a> { + fn from(node: &'a ExceptHandler) -> AnyNodeRef<'a> { + match node { + ExceptHandler::ExceptHandler(node) => AnyNodeRef::ExceptHandlerExceptHandler(node), + } } } -impl<'a> From<&'a crate::BytesLiteral> for AnyNodeRef<'a> { - fn from(node: &'a crate::BytesLiteral) -> AnyNodeRef<'a> { - AnyNodeRef::BytesLiteral(node) +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: ExceptHandlerRef<'a>) -> AnyNodeRef<'a> { + match node { + ExceptHandlerRef::ExceptHandler(node) => AnyNodeRef::ExceptHandlerExceptHandler(node), + } } } -impl<'a> From<&'a crate::Identifier> for AnyNodeRef<'a> { - fn from(node: &'a crate::Identifier) -> AnyNodeRef<'a> { - AnyNodeRef::Identifier(node) +impl<'a> AnyNodeRef<'a> { + pub fn as_except_handler_ref(self) -> Option> { + match self { + Self::ExceptHandlerExceptHandler(node) => Some(ExceptHandlerRef::ExceptHandler(node)), + + _ => None, + } } } -impl ruff_text_size::Ranged for AnyNodeRef<'_> { - fn range(&self) -> ruff_text_size::TextRange { +impl<'a> From<&'a InterpolatedStringElement> for AnyNodeRef<'a> { + fn from(node: &'a InterpolatedStringElement) -> AnyNodeRef<'a> { + match node { + InterpolatedStringElement::Interpolation(node) => AnyNodeRef::InterpolatedElement(node), + InterpolatedStringElement::Literal(node) => { + AnyNodeRef::InterpolatedStringLiteralElement(node) + } + } + } +} + +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: InterpolatedStringElementRef<'a>) -> AnyNodeRef<'a> { + match node { + InterpolatedStringElementRef::Interpolation(node) => { + AnyNodeRef::InterpolatedElement(node) + } + InterpolatedStringElementRef::Literal(node) => { + AnyNodeRef::InterpolatedStringLiteralElement(node) + } + } + } +} + +impl<'a> AnyNodeRef<'a> { + pub fn as_interpolated_string_element_ref(self) -> Option> { match self { - AnyNodeRef::ModModule(node) => node.range(), - AnyNodeRef::ModExpression(node) => node.range(), - AnyNodeRef::StmtFunctionDef(node) => node.range(), - AnyNodeRef::StmtClassDef(node) => node.range(), - AnyNodeRef::StmtReturn(node) => node.range(), - AnyNodeRef::StmtDelete(node) => node.range(), - AnyNodeRef::StmtTypeAlias(node) => node.range(), - AnyNodeRef::StmtAssign(node) => node.range(), - AnyNodeRef::StmtAugAssign(node) => node.range(), - AnyNodeRef::StmtAnnAssign(node) => node.range(), - AnyNodeRef::StmtFor(node) => node.range(), - AnyNodeRef::StmtWhile(node) => node.range(), - AnyNodeRef::StmtIf(node) => node.range(), - AnyNodeRef::StmtWith(node) => node.range(), - AnyNodeRef::StmtMatch(node) => node.range(), - AnyNodeRef::StmtRaise(node) => node.range(), - AnyNodeRef::StmtTry(node) => node.range(), - AnyNodeRef::StmtAssert(node) => node.range(), - AnyNodeRef::StmtImport(node) => node.range(), - AnyNodeRef::StmtImportFrom(node) => node.range(), - AnyNodeRef::StmtGlobal(node) => node.range(), - AnyNodeRef::StmtNonlocal(node) => node.range(), - AnyNodeRef::StmtExpr(node) => node.range(), - AnyNodeRef::StmtPass(node) => node.range(), - AnyNodeRef::StmtBreak(node) => node.range(), - AnyNodeRef::StmtContinue(node) => node.range(), - AnyNodeRef::StmtIpyEscapeCommand(node) => node.range(), - AnyNodeRef::ExprBoolOp(node) => node.range(), - AnyNodeRef::ExprNamed(node) => node.range(), - AnyNodeRef::ExprBinOp(node) => node.range(), - AnyNodeRef::ExprUnaryOp(node) => node.range(), - AnyNodeRef::ExprLambda(node) => node.range(), - AnyNodeRef::ExprIf(node) => node.range(), - AnyNodeRef::ExprDict(node) => node.range(), - AnyNodeRef::ExprSet(node) => node.range(), - AnyNodeRef::ExprListComp(node) => node.range(), - AnyNodeRef::ExprSetComp(node) => node.range(), - AnyNodeRef::ExprDictComp(node) => node.range(), - AnyNodeRef::ExprGenerator(node) => node.range(), - AnyNodeRef::ExprAwait(node) => node.range(), - AnyNodeRef::ExprYield(node) => node.range(), - AnyNodeRef::ExprYieldFrom(node) => node.range(), - AnyNodeRef::ExprCompare(node) => node.range(), - AnyNodeRef::ExprCall(node) => node.range(), - AnyNodeRef::ExprFString(node) => node.range(), - AnyNodeRef::ExprTString(node) => node.range(), - AnyNodeRef::ExprStringLiteral(node) => node.range(), - AnyNodeRef::ExprBytesLiteral(node) => node.range(), - AnyNodeRef::ExprNumberLiteral(node) => node.range(), - AnyNodeRef::ExprBooleanLiteral(node) => node.range(), - AnyNodeRef::ExprNoneLiteral(node) => node.range(), - AnyNodeRef::ExprEllipsisLiteral(node) => node.range(), - AnyNodeRef::ExprAttribute(node) => node.range(), - AnyNodeRef::ExprSubscript(node) => node.range(), - AnyNodeRef::ExprStarred(node) => node.range(), - AnyNodeRef::ExprName(node) => node.range(), - AnyNodeRef::ExprList(node) => node.range(), - AnyNodeRef::ExprTuple(node) => node.range(), - AnyNodeRef::ExprSlice(node) => node.range(), - AnyNodeRef::ExprIpyEscapeCommand(node) => node.range(), - AnyNodeRef::ExceptHandlerExceptHandler(node) => node.range(), - AnyNodeRef::InterpolatedElement(node) => node.range(), - AnyNodeRef::InterpolatedStringLiteralElement(node) => node.range(), - AnyNodeRef::PatternMatchValue(node) => node.range(), - AnyNodeRef::PatternMatchSingleton(node) => node.range(), - AnyNodeRef::PatternMatchSequence(node) => node.range(), - AnyNodeRef::PatternMatchMapping(node) => node.range(), - AnyNodeRef::PatternMatchClass(node) => node.range(), - AnyNodeRef::PatternMatchStar(node) => node.range(), - AnyNodeRef::PatternMatchAs(node) => node.range(), - AnyNodeRef::PatternMatchOr(node) => node.range(), - AnyNodeRef::TypeParamTypeVar(node) => node.range(), - AnyNodeRef::TypeParamTypeVarTuple(node) => node.range(), - AnyNodeRef::TypeParamParamSpec(node) => node.range(), - AnyNodeRef::InterpolatedStringFormatSpec(node) => node.range(), - AnyNodeRef::PatternArguments(node) => node.range(), - AnyNodeRef::PatternKeyword(node) => node.range(), - AnyNodeRef::Comprehension(node) => node.range(), - AnyNodeRef::Arguments(node) => node.range(), - AnyNodeRef::Parameters(node) => node.range(), - AnyNodeRef::Parameter(node) => node.range(), - AnyNodeRef::ParameterWithDefault(node) => node.range(), - AnyNodeRef::Keyword(node) => node.range(), - AnyNodeRef::Alias(node) => node.range(), - AnyNodeRef::WithItem(node) => node.range(), - AnyNodeRef::MatchCase(node) => node.range(), - AnyNodeRef::Decorator(node) => node.range(), - AnyNodeRef::ElifElseClause(node) => node.range(), - AnyNodeRef::TypeParams(node) => node.range(), - AnyNodeRef::FString(node) => node.range(), - AnyNodeRef::TString(node) => node.range(), - AnyNodeRef::StringLiteral(node) => node.range(), - AnyNodeRef::BytesLiteral(node) => node.range(), - AnyNodeRef::Identifier(node) => node.range(), + Self::InterpolatedElement(node) => { + Some(InterpolatedStringElementRef::Interpolation(node)) + } + Self::InterpolatedStringLiteralElement(node) => { + Some(InterpolatedStringElementRef::Literal(node)) + } + + _ => None, } } } -impl AnyNodeRef<'_> { - pub fn as_ptr(&self) -> std::ptr::NonNull<()> { +impl<'a> From<&'a Pattern> for AnyNodeRef<'a> { + fn from(node: &'a Pattern) -> AnyNodeRef<'a> { + match node { + Pattern::MatchValue(node) => AnyNodeRef::PatternMatchValue(node), + Pattern::MatchSingleton(node) => AnyNodeRef::PatternMatchSingleton(node), + Pattern::MatchSequence(node) => AnyNodeRef::PatternMatchSequence(node), + Pattern::MatchMapping(node) => AnyNodeRef::PatternMatchMapping(node), + Pattern::MatchClass(node) => AnyNodeRef::PatternMatchClass(node), + Pattern::MatchStar(node) => AnyNodeRef::PatternMatchStar(node), + Pattern::MatchAs(node) => AnyNodeRef::PatternMatchAs(node), + Pattern::MatchOr(node) => AnyNodeRef::PatternMatchOr(node), + } + } +} + +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: PatternRef<'a>) -> AnyNodeRef<'a> { + match node { + PatternRef::MatchValue(node) => AnyNodeRef::PatternMatchValue(node), + PatternRef::MatchSingleton(node) => AnyNodeRef::PatternMatchSingleton(node), + PatternRef::MatchSequence(node) => AnyNodeRef::PatternMatchSequence(node), + PatternRef::MatchMapping(node) => AnyNodeRef::PatternMatchMapping(node), + PatternRef::MatchClass(node) => AnyNodeRef::PatternMatchClass(node), + PatternRef::MatchStar(node) => AnyNodeRef::PatternMatchStar(node), + PatternRef::MatchAs(node) => AnyNodeRef::PatternMatchAs(node), + PatternRef::MatchOr(node) => AnyNodeRef::PatternMatchOr(node), + } + } +} + +impl<'a> AnyNodeRef<'a> { + pub fn as_pattern_ref(self) -> Option> { match self { - AnyNodeRef::ModModule(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ModExpression(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtFunctionDef(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtClassDef(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtReturn(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtDelete(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtTypeAlias(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtAssign(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtAugAssign(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtAnnAssign(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtFor(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtWhile(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtIf(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtWith(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtMatch(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtRaise(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtTry(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtAssert(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtImport(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtImportFrom(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtGlobal(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtNonlocal(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtExpr(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtPass(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtBreak(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtContinue(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StmtIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprBoolOp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprNamed(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprBinOp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprUnaryOp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprLambda(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprIf(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprDict(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprSet(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprListComp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprSetComp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprDictComp(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprGenerator(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprAwait(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprYield(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprYieldFrom(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprCompare(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprCall(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprFString(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprTString(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprStringLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprBytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprNumberLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprBooleanLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprNoneLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprEllipsisLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprAttribute(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprSubscript(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprStarred(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprName(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprList(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprTuple(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprSlice(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExprIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ExceptHandlerExceptHandler(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::InterpolatedElement(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::InterpolatedStringLiteralElement(node) => { - std::ptr::NonNull::from(*node).cast() - } - AnyNodeRef::PatternMatchValue(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchSingleton(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchSequence(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchMapping(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchClass(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchStar(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchAs(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternMatchOr(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::TypeParamTypeVar(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::TypeParamTypeVarTuple(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::TypeParamParamSpec(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::InterpolatedStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternArguments(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::PatternKeyword(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Comprehension(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Arguments(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Parameters(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Parameter(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ParameterWithDefault(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Keyword(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Alias(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::WithItem(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::MatchCase(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Decorator(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::ElifElseClause(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::TypeParams(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::FString(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::TString(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::StringLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::BytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Identifier(node) => std::ptr::NonNull::from(*node).cast(), + Self::PatternMatchValue(node) => Some(PatternRef::MatchValue(node)), + Self::PatternMatchSingleton(node) => Some(PatternRef::MatchSingleton(node)), + Self::PatternMatchSequence(node) => Some(PatternRef::MatchSequence(node)), + Self::PatternMatchMapping(node) => Some(PatternRef::MatchMapping(node)), + Self::PatternMatchClass(node) => Some(PatternRef::MatchClass(node)), + Self::PatternMatchStar(node) => Some(PatternRef::MatchStar(node)), + Self::PatternMatchAs(node) => Some(PatternRef::MatchAs(node)), + Self::PatternMatchOr(node) => Some(PatternRef::MatchOr(node)), + + _ => None, + } + } +} + +impl<'a> From<&'a TypeParam> for AnyNodeRef<'a> { + fn from(node: &'a TypeParam) -> AnyNodeRef<'a> { + match node { + TypeParam::TypeVar(node) => AnyNodeRef::TypeParamTypeVar(node), + TypeParam::TypeVarTuple(node) => AnyNodeRef::TypeParamTypeVarTuple(node), + TypeParam::ParamSpec(node) => AnyNodeRef::TypeParamParamSpec(node), + } + } +} + +impl<'a> From> for AnyNodeRef<'a> { + fn from(node: TypeParamRef<'a>) -> AnyNodeRef<'a> { + match node { + TypeParamRef::TypeVar(node) => AnyNodeRef::TypeParamTypeVar(node), + TypeParamRef::TypeVarTuple(node) => AnyNodeRef::TypeParamTypeVarTuple(node), + TypeParamRef::ParamSpec(node) => AnyNodeRef::TypeParamParamSpec(node), } } } impl<'a> AnyNodeRef<'a> { - pub fn visit_source_order<'b, V>(self, visitor: &mut V) - where - V: crate::visitor::source_order::SourceOrderVisitor<'b> + ?Sized, - 'a: 'b, - { + pub fn as_type_param_ref(self) -> Option> { match self { - AnyNodeRef::ModModule(node) => node.visit_source_order(visitor), - AnyNodeRef::ModExpression(node) => node.visit_source_order(visitor), + Self::TypeParamTypeVar(node) => Some(TypeParamRef::TypeVar(node)), + Self::TypeParamTypeVarTuple(node) => Some(TypeParamRef::TypeVarTuple(node)), + Self::TypeParamParamSpec(node) => Some(TypeParamRef::ParamSpec(node)), + + _ => None, + } + } +} + +impl<'a> From<&'a crate::ModModule> for AnyNodeRef<'a> { + fn from(node: &'a crate::ModModule) -> AnyNodeRef<'a> { + AnyNodeRef::ModModule(node) + } +} + +impl<'a> From<&'a crate::ModExpression> for AnyNodeRef<'a> { + fn from(node: &'a crate::ModExpression) -> AnyNodeRef<'a> { + AnyNodeRef::ModExpression(node) + } +} + +impl<'a> From<&'a crate::StmtFunctionDef> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtFunctionDef) -> AnyNodeRef<'a> { + AnyNodeRef::StmtFunctionDef(node) + } +} + +impl<'a> From<&'a crate::StmtClassDef> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtClassDef) -> AnyNodeRef<'a> { + AnyNodeRef::StmtClassDef(node) + } +} + +impl<'a> From<&'a crate::StmtReturn> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtReturn) -> AnyNodeRef<'a> { + AnyNodeRef::StmtReturn(node) + } +} + +impl<'a> From<&'a crate::StmtDelete> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtDelete) -> AnyNodeRef<'a> { + AnyNodeRef::StmtDelete(node) + } +} + +impl<'a> From<&'a crate::StmtTypeAlias> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtTypeAlias) -> AnyNodeRef<'a> { + AnyNodeRef::StmtTypeAlias(node) + } +} + +impl<'a> From<&'a crate::StmtAssign> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtAssign) -> AnyNodeRef<'a> { + AnyNodeRef::StmtAssign(node) + } +} + +impl<'a> From<&'a crate::StmtAugAssign> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtAugAssign) -> AnyNodeRef<'a> { + AnyNodeRef::StmtAugAssign(node) + } +} + +impl<'a> From<&'a crate::StmtAnnAssign> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtAnnAssign) -> AnyNodeRef<'a> { + AnyNodeRef::StmtAnnAssign(node) + } +} + +impl<'a> From<&'a crate::StmtFor> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtFor) -> AnyNodeRef<'a> { + AnyNodeRef::StmtFor(node) + } +} + +impl<'a> From<&'a crate::StmtWhile> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtWhile) -> AnyNodeRef<'a> { + AnyNodeRef::StmtWhile(node) + } +} + +impl<'a> From<&'a crate::StmtIf> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtIf) -> AnyNodeRef<'a> { + AnyNodeRef::StmtIf(node) + } +} + +impl<'a> From<&'a crate::StmtWith> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtWith) -> AnyNodeRef<'a> { + AnyNodeRef::StmtWith(node) + } +} + +impl<'a> From<&'a crate::StmtMatch> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtMatch) -> AnyNodeRef<'a> { + AnyNodeRef::StmtMatch(node) + } +} + +impl<'a> From<&'a crate::StmtRaise> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtRaise) -> AnyNodeRef<'a> { + AnyNodeRef::StmtRaise(node) + } +} + +impl<'a> From<&'a crate::StmtTry> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtTry) -> AnyNodeRef<'a> { + AnyNodeRef::StmtTry(node) + } +} + +impl<'a> From<&'a crate::StmtAssert> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtAssert) -> AnyNodeRef<'a> { + AnyNodeRef::StmtAssert(node) + } +} + +impl<'a> From<&'a crate::StmtImport> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtImport) -> AnyNodeRef<'a> { + AnyNodeRef::StmtImport(node) + } +} + +impl<'a> From<&'a crate::StmtImportFrom> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtImportFrom) -> AnyNodeRef<'a> { + AnyNodeRef::StmtImportFrom(node) + } +} + +impl<'a> From<&'a crate::StmtGlobal> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtGlobal) -> AnyNodeRef<'a> { + AnyNodeRef::StmtGlobal(node) + } +} + +impl<'a> From<&'a crate::StmtNonlocal> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtNonlocal) -> AnyNodeRef<'a> { + AnyNodeRef::StmtNonlocal(node) + } +} + +impl<'a> From<&'a crate::StmtExpr> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtExpr) -> AnyNodeRef<'a> { + AnyNodeRef::StmtExpr(node) + } +} + +impl<'a> From<&'a crate::StmtPass> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtPass) -> AnyNodeRef<'a> { + AnyNodeRef::StmtPass(node) + } +} + +impl<'a> From<&'a crate::StmtBreak> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtBreak) -> AnyNodeRef<'a> { + AnyNodeRef::StmtBreak(node) + } +} + +impl<'a> From<&'a crate::StmtContinue> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtContinue) -> AnyNodeRef<'a> { + AnyNodeRef::StmtContinue(node) + } +} + +impl<'a> From<&'a crate::StmtIpyEscapeCommand> for AnyNodeRef<'a> { + fn from(node: &'a crate::StmtIpyEscapeCommand) -> AnyNodeRef<'a> { + AnyNodeRef::StmtIpyEscapeCommand(node) + } +} + +impl<'a> From<&'a crate::ExprBoolOp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprBoolOp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprBoolOp(node) + } +} + +impl<'a> From<&'a crate::ExprNamed> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprNamed) -> AnyNodeRef<'a> { + AnyNodeRef::ExprNamed(node) + } +} + +impl<'a> From<&'a crate::ExprBinOp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprBinOp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprBinOp(node) + } +} + +impl<'a> From<&'a crate::ExprUnaryOp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprUnaryOp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprUnaryOp(node) + } +} + +impl<'a> From<&'a crate::ExprLambda> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprLambda) -> AnyNodeRef<'a> { + AnyNodeRef::ExprLambda(node) + } +} + +impl<'a> From<&'a crate::ExprIf> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprIf) -> AnyNodeRef<'a> { + AnyNodeRef::ExprIf(node) + } +} + +impl<'a> From<&'a crate::ExprDict> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprDict) -> AnyNodeRef<'a> { + AnyNodeRef::ExprDict(node) + } +} + +impl<'a> From<&'a crate::ExprSet> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprSet) -> AnyNodeRef<'a> { + AnyNodeRef::ExprSet(node) + } +} + +impl<'a> From<&'a crate::ExprListComp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprListComp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprListComp(node) + } +} + +impl<'a> From<&'a crate::ExprSetComp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprSetComp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprSetComp(node) + } +} + +impl<'a> From<&'a crate::ExprDictComp> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprDictComp) -> AnyNodeRef<'a> { + AnyNodeRef::ExprDictComp(node) + } +} + +impl<'a> From<&'a crate::ExprGenerator> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprGenerator) -> AnyNodeRef<'a> { + AnyNodeRef::ExprGenerator(node) + } +} + +impl<'a> From<&'a crate::ExprAwait> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprAwait) -> AnyNodeRef<'a> { + AnyNodeRef::ExprAwait(node) + } +} + +impl<'a> From<&'a crate::ExprYield> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprYield) -> AnyNodeRef<'a> { + AnyNodeRef::ExprYield(node) + } +} + +impl<'a> From<&'a crate::ExprYieldFrom> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprYieldFrom) -> AnyNodeRef<'a> { + AnyNodeRef::ExprYieldFrom(node) + } +} + +impl<'a> From<&'a crate::ExprCompare> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprCompare) -> AnyNodeRef<'a> { + AnyNodeRef::ExprCompare(node) + } +} + +impl<'a> From<&'a crate::ExprCall> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprCall) -> AnyNodeRef<'a> { + AnyNodeRef::ExprCall(node) + } +} + +impl<'a> From<&'a crate::ExprFString> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprFString) -> AnyNodeRef<'a> { + AnyNodeRef::ExprFString(node) + } +} + +impl<'a> From<&'a crate::ExprTString> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprTString) -> AnyNodeRef<'a> { + AnyNodeRef::ExprTString(node) + } +} + +impl<'a> From<&'a crate::ExprStringLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprStringLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprStringLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprBytesLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprBytesLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprBytesLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprNumberLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprNumberLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprNumberLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprBooleanLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprBooleanLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprBooleanLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprNoneLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprNoneLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprNoneLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprEllipsisLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprEllipsisLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::ExprEllipsisLiteral(node) + } +} + +impl<'a> From<&'a crate::ExprAttribute> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprAttribute) -> AnyNodeRef<'a> { + AnyNodeRef::ExprAttribute(node) + } +} + +impl<'a> From<&'a crate::ExprSubscript> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprSubscript) -> AnyNodeRef<'a> { + AnyNodeRef::ExprSubscript(node) + } +} + +impl<'a> From<&'a crate::ExprStarred> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprStarred) -> AnyNodeRef<'a> { + AnyNodeRef::ExprStarred(node) + } +} + +impl<'a> From<&'a crate::ExprName> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprName) -> AnyNodeRef<'a> { + AnyNodeRef::ExprName(node) + } +} + +impl<'a> From<&'a crate::ExprList> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprList) -> AnyNodeRef<'a> { + AnyNodeRef::ExprList(node) + } +} + +impl<'a> From<&'a crate::ExprTuple> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprTuple) -> AnyNodeRef<'a> { + AnyNodeRef::ExprTuple(node) + } +} + +impl<'a> From<&'a crate::ExprSlice> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprSlice) -> AnyNodeRef<'a> { + AnyNodeRef::ExprSlice(node) + } +} + +impl<'a> From<&'a crate::ExprIpyEscapeCommand> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExprIpyEscapeCommand) -> AnyNodeRef<'a> { + AnyNodeRef::ExprIpyEscapeCommand(node) + } +} + +impl<'a> From<&'a crate::ExceptHandlerExceptHandler> for AnyNodeRef<'a> { + fn from(node: &'a crate::ExceptHandlerExceptHandler) -> AnyNodeRef<'a> { + AnyNodeRef::ExceptHandlerExceptHandler(node) + } +} + +impl<'a> From<&'a crate::InterpolatedElement> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedElement) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedElement(node) + } +} + +impl<'a> From<&'a crate::InterpolatedStringLiteralElement> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedStringLiteralElement) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedStringLiteralElement(node) + } +} + +impl<'a> From<&'a crate::PatternMatchValue> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchValue) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchValue(node) + } +} + +impl<'a> From<&'a crate::PatternMatchSingleton> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchSingleton) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchSingleton(node) + } +} + +impl<'a> From<&'a crate::PatternMatchSequence> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchSequence) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchSequence(node) + } +} + +impl<'a> From<&'a crate::PatternMatchMapping> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchMapping) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchMapping(node) + } +} + +impl<'a> From<&'a crate::PatternMatchClass> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchClass) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchClass(node) + } +} + +impl<'a> From<&'a crate::PatternMatchStar> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchStar) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchStar(node) + } +} + +impl<'a> From<&'a crate::PatternMatchAs> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchAs) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchAs(node) + } +} + +impl<'a> From<&'a crate::PatternMatchOr> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternMatchOr) -> AnyNodeRef<'a> { + AnyNodeRef::PatternMatchOr(node) + } +} + +impl<'a> From<&'a crate::TypeParamTypeVar> for AnyNodeRef<'a> { + fn from(node: &'a crate::TypeParamTypeVar) -> AnyNodeRef<'a> { + AnyNodeRef::TypeParamTypeVar(node) + } +} + +impl<'a> From<&'a crate::TypeParamTypeVarTuple> for AnyNodeRef<'a> { + fn from(node: &'a crate::TypeParamTypeVarTuple) -> AnyNodeRef<'a> { + AnyNodeRef::TypeParamTypeVarTuple(node) + } +} + +impl<'a> From<&'a crate::TypeParamParamSpec> for AnyNodeRef<'a> { + fn from(node: &'a crate::TypeParamParamSpec) -> AnyNodeRef<'a> { + AnyNodeRef::TypeParamParamSpec(node) + } +} + +impl<'a> From<&'a crate::InterpolatedStringFormatSpec> for AnyNodeRef<'a> { + fn from(node: &'a crate::InterpolatedStringFormatSpec) -> AnyNodeRef<'a> { + AnyNodeRef::InterpolatedStringFormatSpec(node) + } +} + +impl<'a> From<&'a crate::PatternArguments> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternArguments) -> AnyNodeRef<'a> { + AnyNodeRef::PatternArguments(node) + } +} + +impl<'a> From<&'a crate::PatternKeyword> for AnyNodeRef<'a> { + fn from(node: &'a crate::PatternKeyword) -> AnyNodeRef<'a> { + AnyNodeRef::PatternKeyword(node) + } +} + +impl<'a> From<&'a crate::Comprehension> for AnyNodeRef<'a> { + fn from(node: &'a crate::Comprehension) -> AnyNodeRef<'a> { + AnyNodeRef::Comprehension(node) + } +} + +impl<'a> From<&'a crate::Arguments> for AnyNodeRef<'a> { + fn from(node: &'a crate::Arguments) -> AnyNodeRef<'a> { + AnyNodeRef::Arguments(node) + } +} + +impl<'a> From<&'a crate::Parameters> for AnyNodeRef<'a> { + fn from(node: &'a crate::Parameters) -> AnyNodeRef<'a> { + AnyNodeRef::Parameters(node) + } +} + +impl<'a> From<&'a crate::Parameter> for AnyNodeRef<'a> { + fn from(node: &'a crate::Parameter) -> AnyNodeRef<'a> { + AnyNodeRef::Parameter(node) + } +} + +impl<'a> From<&'a crate::ParameterWithDefault> for AnyNodeRef<'a> { + fn from(node: &'a crate::ParameterWithDefault) -> AnyNodeRef<'a> { + AnyNodeRef::ParameterWithDefault(node) + } +} + +impl<'a> From<&'a crate::Keyword> for AnyNodeRef<'a> { + fn from(node: &'a crate::Keyword) -> AnyNodeRef<'a> { + AnyNodeRef::Keyword(node) + } +} + +impl<'a> From<&'a crate::Alias> for AnyNodeRef<'a> { + fn from(node: &'a crate::Alias) -> AnyNodeRef<'a> { + AnyNodeRef::Alias(node) + } +} + +impl<'a> From<&'a crate::WithItem> for AnyNodeRef<'a> { + fn from(node: &'a crate::WithItem) -> AnyNodeRef<'a> { + AnyNodeRef::WithItem(node) + } +} + +impl<'a> From<&'a crate::MatchCase> for AnyNodeRef<'a> { + fn from(node: &'a crate::MatchCase) -> AnyNodeRef<'a> { + AnyNodeRef::MatchCase(node) + } +} + +impl<'a> From<&'a crate::Decorator> for AnyNodeRef<'a> { + fn from(node: &'a crate::Decorator) -> AnyNodeRef<'a> { + AnyNodeRef::Decorator(node) + } +} + +impl<'a> From<&'a crate::ElifElseClause> for AnyNodeRef<'a> { + fn from(node: &'a crate::ElifElseClause) -> AnyNodeRef<'a> { + AnyNodeRef::ElifElseClause(node) + } +} + +impl<'a> From<&'a crate::TypeParams> for AnyNodeRef<'a> { + fn from(node: &'a crate::TypeParams) -> AnyNodeRef<'a> { + AnyNodeRef::TypeParams(node) + } +} + +impl<'a> From<&'a crate::FString> for AnyNodeRef<'a> { + fn from(node: &'a crate::FString) -> AnyNodeRef<'a> { + AnyNodeRef::FString(node) + } +} + +impl<'a> From<&'a crate::TString> for AnyNodeRef<'a> { + fn from(node: &'a crate::TString) -> AnyNodeRef<'a> { + AnyNodeRef::TString(node) + } +} + +impl<'a> From<&'a crate::StringLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::StringLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::StringLiteral(node) + } +} + +impl<'a> From<&'a crate::BytesLiteral> for AnyNodeRef<'a> { + fn from(node: &'a crate::BytesLiteral) -> AnyNodeRef<'a> { + AnyNodeRef::BytesLiteral(node) + } +} + +impl<'a> From<&'a crate::Identifier> for AnyNodeRef<'a> { + fn from(node: &'a crate::Identifier) -> AnyNodeRef<'a> { + AnyNodeRef::Identifier(node) + } +} + +impl ruff_text_size::Ranged for AnyNodeRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + AnyNodeRef::ModModule(node) => node.range(), + AnyNodeRef::ModExpression(node) => node.range(), + AnyNodeRef::StmtFunctionDef(node) => node.range(), + AnyNodeRef::StmtClassDef(node) => node.range(), + AnyNodeRef::StmtReturn(node) => node.range(), + AnyNodeRef::StmtDelete(node) => node.range(), + AnyNodeRef::StmtTypeAlias(node) => node.range(), + AnyNodeRef::StmtAssign(node) => node.range(), + AnyNodeRef::StmtAugAssign(node) => node.range(), + AnyNodeRef::StmtAnnAssign(node) => node.range(), + AnyNodeRef::StmtFor(node) => node.range(), + AnyNodeRef::StmtWhile(node) => node.range(), + AnyNodeRef::StmtIf(node) => node.range(), + AnyNodeRef::StmtWith(node) => node.range(), + AnyNodeRef::StmtMatch(node) => node.range(), + AnyNodeRef::StmtRaise(node) => node.range(), + AnyNodeRef::StmtTry(node) => node.range(), + AnyNodeRef::StmtAssert(node) => node.range(), + AnyNodeRef::StmtImport(node) => node.range(), + AnyNodeRef::StmtImportFrom(node) => node.range(), + AnyNodeRef::StmtGlobal(node) => node.range(), + AnyNodeRef::StmtNonlocal(node) => node.range(), + AnyNodeRef::StmtExpr(node) => node.range(), + AnyNodeRef::StmtPass(node) => node.range(), + AnyNodeRef::StmtBreak(node) => node.range(), + AnyNodeRef::StmtContinue(node) => node.range(), + AnyNodeRef::StmtIpyEscapeCommand(node) => node.range(), + AnyNodeRef::ExprBoolOp(node) => node.range(), + AnyNodeRef::ExprNamed(node) => node.range(), + AnyNodeRef::ExprBinOp(node) => node.range(), + AnyNodeRef::ExprUnaryOp(node) => node.range(), + AnyNodeRef::ExprLambda(node) => node.range(), + AnyNodeRef::ExprIf(node) => node.range(), + AnyNodeRef::ExprDict(node) => node.range(), + AnyNodeRef::ExprSet(node) => node.range(), + AnyNodeRef::ExprListComp(node) => node.range(), + AnyNodeRef::ExprSetComp(node) => node.range(), + AnyNodeRef::ExprDictComp(node) => node.range(), + AnyNodeRef::ExprGenerator(node) => node.range(), + AnyNodeRef::ExprAwait(node) => node.range(), + AnyNodeRef::ExprYield(node) => node.range(), + AnyNodeRef::ExprYieldFrom(node) => node.range(), + AnyNodeRef::ExprCompare(node) => node.range(), + AnyNodeRef::ExprCall(node) => node.range(), + AnyNodeRef::ExprFString(node) => node.range(), + AnyNodeRef::ExprTString(node) => node.range(), + AnyNodeRef::ExprStringLiteral(node) => node.range(), + AnyNodeRef::ExprBytesLiteral(node) => node.range(), + AnyNodeRef::ExprNumberLiteral(node) => node.range(), + AnyNodeRef::ExprBooleanLiteral(node) => node.range(), + AnyNodeRef::ExprNoneLiteral(node) => node.range(), + AnyNodeRef::ExprEllipsisLiteral(node) => node.range(), + AnyNodeRef::ExprAttribute(node) => node.range(), + AnyNodeRef::ExprSubscript(node) => node.range(), + AnyNodeRef::ExprStarred(node) => node.range(), + AnyNodeRef::ExprName(node) => node.range(), + AnyNodeRef::ExprList(node) => node.range(), + AnyNodeRef::ExprTuple(node) => node.range(), + AnyNodeRef::ExprSlice(node) => node.range(), + AnyNodeRef::ExprIpyEscapeCommand(node) => node.range(), + AnyNodeRef::ExceptHandlerExceptHandler(node) => node.range(), + AnyNodeRef::InterpolatedElement(node) => node.range(), + AnyNodeRef::InterpolatedStringLiteralElement(node) => node.range(), + AnyNodeRef::PatternMatchValue(node) => node.range(), + AnyNodeRef::PatternMatchSingleton(node) => node.range(), + AnyNodeRef::PatternMatchSequence(node) => node.range(), + AnyNodeRef::PatternMatchMapping(node) => node.range(), + AnyNodeRef::PatternMatchClass(node) => node.range(), + AnyNodeRef::PatternMatchStar(node) => node.range(), + AnyNodeRef::PatternMatchAs(node) => node.range(), + AnyNodeRef::PatternMatchOr(node) => node.range(), + AnyNodeRef::TypeParamTypeVar(node) => node.range(), + AnyNodeRef::TypeParamTypeVarTuple(node) => node.range(), + AnyNodeRef::TypeParamParamSpec(node) => node.range(), + AnyNodeRef::InterpolatedStringFormatSpec(node) => node.range(), + AnyNodeRef::PatternArguments(node) => node.range(), + AnyNodeRef::PatternKeyword(node) => node.range(), + AnyNodeRef::Comprehension(node) => node.range(), + AnyNodeRef::Arguments(node) => node.range(), + AnyNodeRef::Parameters(node) => node.range(), + AnyNodeRef::Parameter(node) => node.range(), + AnyNodeRef::ParameterWithDefault(node) => node.range(), + AnyNodeRef::Keyword(node) => node.range(), + AnyNodeRef::Alias(node) => node.range(), + AnyNodeRef::WithItem(node) => node.range(), + AnyNodeRef::MatchCase(node) => node.range(), + AnyNodeRef::Decorator(node) => node.range(), + AnyNodeRef::ElifElseClause(node) => node.range(), + AnyNodeRef::TypeParams(node) => node.range(), + AnyNodeRef::FString(node) => node.range(), + AnyNodeRef::TString(node) => node.range(), + AnyNodeRef::StringLiteral(node) => node.range(), + AnyNodeRef::BytesLiteral(node) => node.range(), + AnyNodeRef::Identifier(node) => node.range(), + } + } +} + +impl crate::HasNodeIndex for AnyNodeRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + AnyNodeRef::ModModule(node) => node.node_index(), + AnyNodeRef::ModExpression(node) => node.node_index(), + AnyNodeRef::StmtFunctionDef(node) => node.node_index(), + AnyNodeRef::StmtClassDef(node) => node.node_index(), + AnyNodeRef::StmtReturn(node) => node.node_index(), + AnyNodeRef::StmtDelete(node) => node.node_index(), + AnyNodeRef::StmtTypeAlias(node) => node.node_index(), + AnyNodeRef::StmtAssign(node) => node.node_index(), + AnyNodeRef::StmtAugAssign(node) => node.node_index(), + AnyNodeRef::StmtAnnAssign(node) => node.node_index(), + AnyNodeRef::StmtFor(node) => node.node_index(), + AnyNodeRef::StmtWhile(node) => node.node_index(), + AnyNodeRef::StmtIf(node) => node.node_index(), + AnyNodeRef::StmtWith(node) => node.node_index(), + AnyNodeRef::StmtMatch(node) => node.node_index(), + AnyNodeRef::StmtRaise(node) => node.node_index(), + AnyNodeRef::StmtTry(node) => node.node_index(), + AnyNodeRef::StmtAssert(node) => node.node_index(), + AnyNodeRef::StmtImport(node) => node.node_index(), + AnyNodeRef::StmtImportFrom(node) => node.node_index(), + AnyNodeRef::StmtGlobal(node) => node.node_index(), + AnyNodeRef::StmtNonlocal(node) => node.node_index(), + AnyNodeRef::StmtExpr(node) => node.node_index(), + AnyNodeRef::StmtPass(node) => node.node_index(), + AnyNodeRef::StmtBreak(node) => node.node_index(), + AnyNodeRef::StmtContinue(node) => node.node_index(), + AnyNodeRef::StmtIpyEscapeCommand(node) => node.node_index(), + AnyNodeRef::ExprBoolOp(node) => node.node_index(), + AnyNodeRef::ExprNamed(node) => node.node_index(), + AnyNodeRef::ExprBinOp(node) => node.node_index(), + AnyNodeRef::ExprUnaryOp(node) => node.node_index(), + AnyNodeRef::ExprLambda(node) => node.node_index(), + AnyNodeRef::ExprIf(node) => node.node_index(), + AnyNodeRef::ExprDict(node) => node.node_index(), + AnyNodeRef::ExprSet(node) => node.node_index(), + AnyNodeRef::ExprListComp(node) => node.node_index(), + AnyNodeRef::ExprSetComp(node) => node.node_index(), + AnyNodeRef::ExprDictComp(node) => node.node_index(), + AnyNodeRef::ExprGenerator(node) => node.node_index(), + AnyNodeRef::ExprAwait(node) => node.node_index(), + AnyNodeRef::ExprYield(node) => node.node_index(), + AnyNodeRef::ExprYieldFrom(node) => node.node_index(), + AnyNodeRef::ExprCompare(node) => node.node_index(), + AnyNodeRef::ExprCall(node) => node.node_index(), + AnyNodeRef::ExprFString(node) => node.node_index(), + AnyNodeRef::ExprTString(node) => node.node_index(), + AnyNodeRef::ExprStringLiteral(node) => node.node_index(), + AnyNodeRef::ExprBytesLiteral(node) => node.node_index(), + AnyNodeRef::ExprNumberLiteral(node) => node.node_index(), + AnyNodeRef::ExprBooleanLiteral(node) => node.node_index(), + AnyNodeRef::ExprNoneLiteral(node) => node.node_index(), + AnyNodeRef::ExprEllipsisLiteral(node) => node.node_index(), + AnyNodeRef::ExprAttribute(node) => node.node_index(), + AnyNodeRef::ExprSubscript(node) => node.node_index(), + AnyNodeRef::ExprStarred(node) => node.node_index(), + AnyNodeRef::ExprName(node) => node.node_index(), + AnyNodeRef::ExprList(node) => node.node_index(), + AnyNodeRef::ExprTuple(node) => node.node_index(), + AnyNodeRef::ExprSlice(node) => node.node_index(), + AnyNodeRef::ExprIpyEscapeCommand(node) => node.node_index(), + AnyNodeRef::ExceptHandlerExceptHandler(node) => node.node_index(), + AnyNodeRef::InterpolatedElement(node) => node.node_index(), + AnyNodeRef::InterpolatedStringLiteralElement(node) => node.node_index(), + AnyNodeRef::PatternMatchValue(node) => node.node_index(), + AnyNodeRef::PatternMatchSingleton(node) => node.node_index(), + AnyNodeRef::PatternMatchSequence(node) => node.node_index(), + AnyNodeRef::PatternMatchMapping(node) => node.node_index(), + AnyNodeRef::PatternMatchClass(node) => node.node_index(), + AnyNodeRef::PatternMatchStar(node) => node.node_index(), + AnyNodeRef::PatternMatchAs(node) => node.node_index(), + AnyNodeRef::PatternMatchOr(node) => node.node_index(), + AnyNodeRef::TypeParamTypeVar(node) => node.node_index(), + AnyNodeRef::TypeParamTypeVarTuple(node) => node.node_index(), + AnyNodeRef::TypeParamParamSpec(node) => node.node_index(), + AnyNodeRef::InterpolatedStringFormatSpec(node) => node.node_index(), + AnyNodeRef::PatternArguments(node) => node.node_index(), + AnyNodeRef::PatternKeyword(node) => node.node_index(), + AnyNodeRef::Comprehension(node) => node.node_index(), + AnyNodeRef::Arguments(node) => node.node_index(), + AnyNodeRef::Parameters(node) => node.node_index(), + AnyNodeRef::Parameter(node) => node.node_index(), + AnyNodeRef::ParameterWithDefault(node) => node.node_index(), + AnyNodeRef::Keyword(node) => node.node_index(), + AnyNodeRef::Alias(node) => node.node_index(), + AnyNodeRef::WithItem(node) => node.node_index(), + AnyNodeRef::MatchCase(node) => node.node_index(), + AnyNodeRef::Decorator(node) => node.node_index(), + AnyNodeRef::ElifElseClause(node) => node.node_index(), + AnyNodeRef::TypeParams(node) => node.node_index(), + AnyNodeRef::FString(node) => node.node_index(), + AnyNodeRef::TString(node) => node.node_index(), + AnyNodeRef::StringLiteral(node) => node.node_index(), + AnyNodeRef::BytesLiteral(node) => node.node_index(), + AnyNodeRef::Identifier(node) => node.node_index(), + } + } +} + +impl AnyNodeRef<'_> { + pub fn as_ptr(&self) -> std::ptr::NonNull<()> { + match self { + AnyNodeRef::ModModule(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ModExpression(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtFunctionDef(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtClassDef(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtReturn(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtDelete(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtTypeAlias(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtAssign(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtAugAssign(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtAnnAssign(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtFor(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtWhile(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtIf(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtWith(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtMatch(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtRaise(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtTry(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtAssert(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtImport(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtImportFrom(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtGlobal(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtNonlocal(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtExpr(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtPass(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtBreak(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtContinue(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StmtIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprBoolOp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprNamed(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprBinOp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprUnaryOp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprLambda(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprIf(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprDict(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprSet(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprListComp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprSetComp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprDictComp(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprGenerator(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprAwait(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprYield(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprYieldFrom(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprCompare(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprCall(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprFString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprTString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprStringLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprBytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprNumberLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprBooleanLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprNoneLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprEllipsisLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprAttribute(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprSubscript(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprStarred(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprName(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprList(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprTuple(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprSlice(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExprIpyEscapeCommand(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ExceptHandlerExceptHandler(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedElement(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedStringLiteralElement(node) => { + std::ptr::NonNull::from(*node).cast() + } + AnyNodeRef::PatternMatchValue(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchSingleton(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchSequence(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchMapping(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchClass(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchStar(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchAs(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternMatchOr(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TypeParamTypeVar(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TypeParamTypeVarTuple(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TypeParamParamSpec(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::InterpolatedStringFormatSpec(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternArguments(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::PatternKeyword(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Comprehension(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Arguments(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Parameters(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Parameter(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ParameterWithDefault(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Keyword(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Alias(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::WithItem(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::MatchCase(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Decorator(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::ElifElseClause(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TypeParams(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::FString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::TString(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::StringLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::BytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Identifier(node) => std::ptr::NonNull::from(*node).cast(), + } + } +} + +impl<'a> AnyNodeRef<'a> { + pub fn visit_source_order<'b, V>(self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'b> + ?Sized, + 'a: 'b, + { + match self { + AnyNodeRef::ModModule(node) => node.visit_source_order(visitor), + AnyNodeRef::ModExpression(node) => node.visit_source_order(visitor), AnyNodeRef::StmtFunctionDef(node) => node.visit_source_order(visitor), AnyNodeRef::StmtClassDef(node) => node.visit_source_order(visitor), AnyNodeRef::StmtReturn(node) => node.visit_source_order(visitor), @@ -6371,128 +7283,1452 @@ impl<'a> AnyNodeRef<'a> { } } -impl AnyNodeRef<'_> { - pub const fn is_module(self) -> bool { - matches!( - self, - AnyNodeRef::ModModule(_) | AnyNodeRef::ModExpression(_) - ) +impl AnyNodeRef<'_> { + pub const fn is_module(self) -> bool { + matches!( + self, + AnyNodeRef::ModModule(_) | AnyNodeRef::ModExpression(_) + ) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_statement(self) -> bool { + matches!( + self, + AnyNodeRef::StmtFunctionDef(_) + | AnyNodeRef::StmtClassDef(_) + | AnyNodeRef::StmtReturn(_) + | AnyNodeRef::StmtDelete(_) + | AnyNodeRef::StmtTypeAlias(_) + | AnyNodeRef::StmtAssign(_) + | AnyNodeRef::StmtAugAssign(_) + | AnyNodeRef::StmtAnnAssign(_) + | AnyNodeRef::StmtFor(_) + | AnyNodeRef::StmtWhile(_) + | AnyNodeRef::StmtIf(_) + | AnyNodeRef::StmtWith(_) + | AnyNodeRef::StmtMatch(_) + | AnyNodeRef::StmtRaise(_) + | AnyNodeRef::StmtTry(_) + | AnyNodeRef::StmtAssert(_) + | AnyNodeRef::StmtImport(_) + | AnyNodeRef::StmtImportFrom(_) + | AnyNodeRef::StmtGlobal(_) + | AnyNodeRef::StmtNonlocal(_) + | AnyNodeRef::StmtExpr(_) + | AnyNodeRef::StmtPass(_) + | AnyNodeRef::StmtBreak(_) + | AnyNodeRef::StmtContinue(_) + | AnyNodeRef::StmtIpyEscapeCommand(_) + ) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_expression(self) -> bool { + matches!( + self, + AnyNodeRef::ExprBoolOp(_) + | AnyNodeRef::ExprNamed(_) + | AnyNodeRef::ExprBinOp(_) + | AnyNodeRef::ExprUnaryOp(_) + | AnyNodeRef::ExprLambda(_) + | AnyNodeRef::ExprIf(_) + | AnyNodeRef::ExprDict(_) + | AnyNodeRef::ExprSet(_) + | AnyNodeRef::ExprListComp(_) + | AnyNodeRef::ExprSetComp(_) + | AnyNodeRef::ExprDictComp(_) + | AnyNodeRef::ExprGenerator(_) + | AnyNodeRef::ExprAwait(_) + | AnyNodeRef::ExprYield(_) + | AnyNodeRef::ExprYieldFrom(_) + | AnyNodeRef::ExprCompare(_) + | AnyNodeRef::ExprCall(_) + | AnyNodeRef::ExprFString(_) + | AnyNodeRef::ExprTString(_) + | AnyNodeRef::ExprStringLiteral(_) + | AnyNodeRef::ExprBytesLiteral(_) + | AnyNodeRef::ExprNumberLiteral(_) + | AnyNodeRef::ExprBooleanLiteral(_) + | AnyNodeRef::ExprNoneLiteral(_) + | AnyNodeRef::ExprEllipsisLiteral(_) + | AnyNodeRef::ExprAttribute(_) + | AnyNodeRef::ExprSubscript(_) + | AnyNodeRef::ExprStarred(_) + | AnyNodeRef::ExprName(_) + | AnyNodeRef::ExprList(_) + | AnyNodeRef::ExprTuple(_) + | AnyNodeRef::ExprSlice(_) + | AnyNodeRef::ExprIpyEscapeCommand(_) + ) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_except_handler(self) -> bool { + matches!(self, AnyNodeRef::ExceptHandlerExceptHandler(_)) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_interpolated_string_element(self) -> bool { + matches!( + self, + AnyNodeRef::InterpolatedElement(_) | AnyNodeRef::InterpolatedStringLiteralElement(_) + ) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_pattern(self) -> bool { + matches!( + self, + AnyNodeRef::PatternMatchValue(_) + | AnyNodeRef::PatternMatchSingleton(_) + | AnyNodeRef::PatternMatchSequence(_) + | AnyNodeRef::PatternMatchMapping(_) + | AnyNodeRef::PatternMatchClass(_) + | AnyNodeRef::PatternMatchStar(_) + | AnyNodeRef::PatternMatchAs(_) + | AnyNodeRef::PatternMatchOr(_) + ) + } +} + +impl AnyNodeRef<'_> { + pub const fn is_type_param(self) -> bool { + matches!( + self, + AnyNodeRef::TypeParamTypeVar(_) + | AnyNodeRef::TypeParamTypeVarTuple(_) + | AnyNodeRef::TypeParamParamSpec(_) + ) + } +} + +/// An enumeration of all AST nodes. +/// +/// Unlike `AnyNodeRef`, this type does not flatten nested enums, so its variants only +/// consist of the "root" AST node types. This is useful as it exposes references to the +/// original enums, not just references to their inner values. +/// +/// For example, `AnyRootNodeRef::Mod` contains a reference to the `Mod` enum, while +/// `AnyNodeRef` has top-level `AnyNodeRef::ModModule` and `AnyNodeRef::ModExpression` +/// variants. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum AnyRootNodeRef<'a> { + Mod(&'a Mod), + Stmt(&'a Stmt), + Expr(&'a Expr), + ExceptHandler(&'a ExceptHandler), + InterpolatedStringElement(&'a InterpolatedStringElement), + Pattern(&'a Pattern), + TypeParam(&'a TypeParam), + InterpolatedStringFormatSpec(&'a crate::InterpolatedStringFormatSpec), + PatternArguments(&'a crate::PatternArguments), + PatternKeyword(&'a crate::PatternKeyword), + Comprehension(&'a crate::Comprehension), + Arguments(&'a crate::Arguments), + Parameters(&'a crate::Parameters), + Parameter(&'a crate::Parameter), + ParameterWithDefault(&'a crate::ParameterWithDefault), + Keyword(&'a crate::Keyword), + Alias(&'a crate::Alias), + WithItem(&'a crate::WithItem), + MatchCase(&'a crate::MatchCase), + Decorator(&'a crate::Decorator), + ElifElseClause(&'a crate::ElifElseClause), + TypeParams(&'a crate::TypeParams), + FString(&'a crate::FString), + TString(&'a crate::TString), + StringLiteral(&'a crate::StringLiteral), + BytesLiteral(&'a crate::BytesLiteral), + Identifier(&'a crate::Identifier), +} + +impl<'a> From<&'a Mod> for AnyRootNodeRef<'a> { + fn from(node: &'a Mod) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Mod(node) + } +} + +impl<'a> TryFrom> for &'a Mod { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a Mod, ()> { + match node { + AnyRootNodeRef::Mod(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ModModule { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ModModule, ()> { + match node { + AnyRootNodeRef::Mod(Mod::Module(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ModExpression { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ModExpression, ()> { + match node { + AnyRootNodeRef::Mod(Mod::Expression(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a Stmt> for AnyRootNodeRef<'a> { + fn from(node: &'a Stmt) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Stmt(node) + } +} + +impl<'a> TryFrom> for &'a Stmt { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a Stmt, ()> { + match node { + AnyRootNodeRef::Stmt(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtFunctionDef { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtFunctionDef, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::FunctionDef(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtClassDef { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtClassDef, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::ClassDef(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtReturn { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtReturn, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Return(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtDelete { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtDelete, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Delete(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtTypeAlias { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtTypeAlias, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::TypeAlias(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtAssign { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtAssign, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Assign(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtAugAssign { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtAugAssign, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::AugAssign(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtAnnAssign { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtAnnAssign, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::AnnAssign(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtFor { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtFor, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::For(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtWhile { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtWhile, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::While(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtIf { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtIf, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::If(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtWith { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtWith, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::With(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtMatch { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtMatch, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Match(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtRaise { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtRaise, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Raise(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtTry { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtTry, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Try(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtAssert { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtAssert, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Assert(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtImport { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtImport, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Import(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtImportFrom { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtImportFrom, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::ImportFrom(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtGlobal { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtGlobal, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Global(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtNonlocal { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtNonlocal, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Nonlocal(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtExpr { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtExpr, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Expr(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtPass { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtPass, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Pass(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtBreak { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtBreak, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Break(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtContinue { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtContinue, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::Continue(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::StmtIpyEscapeCommand { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StmtIpyEscapeCommand, ()> { + match node { + AnyRootNodeRef::Stmt(Stmt::IpyEscapeCommand(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a Expr> for AnyRootNodeRef<'a> { + fn from(node: &'a Expr) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Expr(node) + } +} + +impl<'a> TryFrom> for &'a Expr { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a Expr, ()> { + match node { + AnyRootNodeRef::Expr(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprBoolOp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprBoolOp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::BoolOp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprNamed { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprNamed, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Named(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprBinOp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprBinOp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::BinOp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprUnaryOp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprUnaryOp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::UnaryOp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprLambda { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprLambda, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Lambda(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprIf { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprIf, ()> { + match node { + AnyRootNodeRef::Expr(Expr::If(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprDict { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprDict, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Dict(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprSet { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprSet, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Set(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprListComp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprListComp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::ListComp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprSetComp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprSetComp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::SetComp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprDictComp { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprDictComp, ()> { + match node { + AnyRootNodeRef::Expr(Expr::DictComp(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprGenerator { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprGenerator, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Generator(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprAwait { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprAwait, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Await(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprYield { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprYield, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Yield(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprYieldFrom { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprYieldFrom, ()> { + match node { + AnyRootNodeRef::Expr(Expr::YieldFrom(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprCompare { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprCompare, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Compare(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprCall { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprCall, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Call(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprFString { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprFString, ()> { + match node { + AnyRootNodeRef::Expr(Expr::FString(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprTString { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprTString, ()> { + match node { + AnyRootNodeRef::Expr(Expr::TString(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprStringLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprStringLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::StringLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprBytesLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprBytesLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::BytesLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprNumberLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprNumberLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::NumberLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprBooleanLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprBooleanLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::BooleanLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprNoneLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprNoneLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::NoneLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprEllipsisLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprEllipsisLiteral, ()> { + match node { + AnyRootNodeRef::Expr(Expr::EllipsisLiteral(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprAttribute { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprAttribute, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Attribute(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprSubscript { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprSubscript, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Subscript(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprStarred { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprStarred, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Starred(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprName { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprName, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Name(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprList { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprList, ()> { + match node { + AnyRootNodeRef::Expr(Expr::List(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprTuple { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprTuple, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Tuple(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprSlice { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprSlice, ()> { + match node { + AnyRootNodeRef::Expr(Expr::Slice(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExprIpyEscapeCommand { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExprIpyEscapeCommand, ()> { + match node { + AnyRootNodeRef::Expr(Expr::IpyEscapeCommand(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a ExceptHandler> for AnyRootNodeRef<'a> { + fn from(node: &'a ExceptHandler) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::ExceptHandler(node) + } +} + +impl<'a> TryFrom> for &'a ExceptHandler { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a ExceptHandler, ()> { + match node { + AnyRootNodeRef::ExceptHandler(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::ExceptHandlerExceptHandler { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ExceptHandlerExceptHandler, ()> { + match node { + AnyRootNodeRef::ExceptHandler(ExceptHandler::ExceptHandler(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a InterpolatedStringElement> for AnyRootNodeRef<'a> { + fn from(node: &'a InterpolatedStringElement) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::InterpolatedStringElement(node) + } +} + +impl<'a> TryFrom> for &'a InterpolatedStringElement { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a InterpolatedStringElement, ()> { + match node { + AnyRootNodeRef::InterpolatedStringElement(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::InterpolatedElement { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::InterpolatedElement, ()> { + match node { + AnyRootNodeRef::InterpolatedStringElement( + InterpolatedStringElement::Interpolation(node), + ) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::InterpolatedStringLiteralElement { + type Error = (); + fn try_from( + node: AnyRootNodeRef<'a>, + ) -> Result<&'a crate::InterpolatedStringLiteralElement, ()> { + match node { + AnyRootNodeRef::InterpolatedStringElement(InterpolatedStringElement::Literal(node)) => { + Ok(node) + } + _ => Err(()), + } + } +} + +impl<'a> From<&'a Pattern> for AnyRootNodeRef<'a> { + fn from(node: &'a Pattern) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Pattern(node) + } +} + +impl<'a> TryFrom> for &'a Pattern { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a Pattern, ()> { + match node { + AnyRootNodeRef::Pattern(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchValue { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchValue, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchValue(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchSingleton { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchSingleton, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchSingleton(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchSequence { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchSequence, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchSequence(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchMapping { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchMapping, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchMapping(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchClass { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchClass, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchClass(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchStar { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchStar, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchStar(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchAs { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchAs, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchAs(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::PatternMatchOr { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternMatchOr, ()> { + match node { + AnyRootNodeRef::Pattern(Pattern::MatchOr(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a TypeParam> for AnyRootNodeRef<'a> { + fn from(node: &'a TypeParam) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::TypeParam(node) + } +} + +impl<'a> TryFrom> for &'a TypeParam { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a TypeParam, ()> { + match node { + AnyRootNodeRef::TypeParam(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::TypeParamTypeVar { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::TypeParamTypeVar, ()> { + match node { + AnyRootNodeRef::TypeParam(TypeParam::TypeVar(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::TypeParamTypeVarTuple { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::TypeParamTypeVarTuple, ()> { + match node { + AnyRootNodeRef::TypeParam(TypeParam::TypeVarTuple(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> TryFrom> for &'a crate::TypeParamParamSpec { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::TypeParamParamSpec, ()> { + match node { + AnyRootNodeRef::TypeParam(TypeParam::ParamSpec(node)) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::InterpolatedStringFormatSpec> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::InterpolatedStringFormatSpec) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::InterpolatedStringFormatSpec(node) + } +} + +impl<'a> TryFrom> for &'a crate::InterpolatedStringFormatSpec { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::InterpolatedStringFormatSpec, ()> { + match node { + AnyRootNodeRef::InterpolatedStringFormatSpec(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::PatternArguments> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::PatternArguments) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::PatternArguments(node) + } +} + +impl<'a> TryFrom> for &'a crate::PatternArguments { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternArguments, ()> { + match node { + AnyRootNodeRef::PatternArguments(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::PatternKeyword> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::PatternKeyword) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::PatternKeyword(node) + } +} + +impl<'a> TryFrom> for &'a crate::PatternKeyword { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::PatternKeyword, ()> { + match node { + AnyRootNodeRef::PatternKeyword(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Comprehension> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Comprehension) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Comprehension(node) + } +} + +impl<'a> TryFrom> for &'a crate::Comprehension { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Comprehension, ()> { + match node { + AnyRootNodeRef::Comprehension(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Arguments> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Arguments) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Arguments(node) + } +} + +impl<'a> TryFrom> for &'a crate::Arguments { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Arguments, ()> { + match node { + AnyRootNodeRef::Arguments(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Parameters> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Parameters) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Parameters(node) + } +} + +impl<'a> TryFrom> for &'a crate::Parameters { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Parameters, ()> { + match node { + AnyRootNodeRef::Parameters(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Parameter> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Parameter) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Parameter(node) + } +} + +impl<'a> TryFrom> for &'a crate::Parameter { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Parameter, ()> { + match node { + AnyRootNodeRef::Parameter(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::ParameterWithDefault> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::ParameterWithDefault) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::ParameterWithDefault(node) } } -impl AnyNodeRef<'_> { - pub const fn is_statement(self) -> bool { - matches!( - self, - AnyNodeRef::StmtFunctionDef(_) - | AnyNodeRef::StmtClassDef(_) - | AnyNodeRef::StmtReturn(_) - | AnyNodeRef::StmtDelete(_) - | AnyNodeRef::StmtTypeAlias(_) - | AnyNodeRef::StmtAssign(_) - | AnyNodeRef::StmtAugAssign(_) - | AnyNodeRef::StmtAnnAssign(_) - | AnyNodeRef::StmtFor(_) - | AnyNodeRef::StmtWhile(_) - | AnyNodeRef::StmtIf(_) - | AnyNodeRef::StmtWith(_) - | AnyNodeRef::StmtMatch(_) - | AnyNodeRef::StmtRaise(_) - | AnyNodeRef::StmtTry(_) - | AnyNodeRef::StmtAssert(_) - | AnyNodeRef::StmtImport(_) - | AnyNodeRef::StmtImportFrom(_) - | AnyNodeRef::StmtGlobal(_) - | AnyNodeRef::StmtNonlocal(_) - | AnyNodeRef::StmtExpr(_) - | AnyNodeRef::StmtPass(_) - | AnyNodeRef::StmtBreak(_) - | AnyNodeRef::StmtContinue(_) - | AnyNodeRef::StmtIpyEscapeCommand(_) - ) +impl<'a> TryFrom> for &'a crate::ParameterWithDefault { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ParameterWithDefault, ()> { + match node { + AnyRootNodeRef::ParameterWithDefault(node) => Ok(node), + _ => Err(()), + } } } -impl AnyNodeRef<'_> { - pub const fn is_expression(self) -> bool { - matches!( - self, - AnyNodeRef::ExprBoolOp(_) - | AnyNodeRef::ExprNamed(_) - | AnyNodeRef::ExprBinOp(_) - | AnyNodeRef::ExprUnaryOp(_) - | AnyNodeRef::ExprLambda(_) - | AnyNodeRef::ExprIf(_) - | AnyNodeRef::ExprDict(_) - | AnyNodeRef::ExprSet(_) - | AnyNodeRef::ExprListComp(_) - | AnyNodeRef::ExprSetComp(_) - | AnyNodeRef::ExprDictComp(_) - | AnyNodeRef::ExprGenerator(_) - | AnyNodeRef::ExprAwait(_) - | AnyNodeRef::ExprYield(_) - | AnyNodeRef::ExprYieldFrom(_) - | AnyNodeRef::ExprCompare(_) - | AnyNodeRef::ExprCall(_) - | AnyNodeRef::ExprFString(_) - | AnyNodeRef::ExprTString(_) - | AnyNodeRef::ExprStringLiteral(_) - | AnyNodeRef::ExprBytesLiteral(_) - | AnyNodeRef::ExprNumberLiteral(_) - | AnyNodeRef::ExprBooleanLiteral(_) - | AnyNodeRef::ExprNoneLiteral(_) - | AnyNodeRef::ExprEllipsisLiteral(_) - | AnyNodeRef::ExprAttribute(_) - | AnyNodeRef::ExprSubscript(_) - | AnyNodeRef::ExprStarred(_) - | AnyNodeRef::ExprName(_) - | AnyNodeRef::ExprList(_) - | AnyNodeRef::ExprTuple(_) - | AnyNodeRef::ExprSlice(_) - | AnyNodeRef::ExprIpyEscapeCommand(_) - ) +impl<'a> From<&'a crate::Keyword> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Keyword) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Keyword(node) } } -impl AnyNodeRef<'_> { - pub const fn is_except_handler(self) -> bool { - matches!(self, AnyNodeRef::ExceptHandlerExceptHandler(_)) +impl<'a> TryFrom> for &'a crate::Keyword { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Keyword, ()> { + match node { + AnyRootNodeRef::Keyword(node) => Ok(node), + _ => Err(()), + } } } -impl AnyNodeRef<'_> { - pub const fn is_interpolated_string_element(self) -> bool { - matches!( - self, - AnyNodeRef::InterpolatedElement(_) | AnyNodeRef::InterpolatedStringLiteralElement(_) - ) +impl<'a> From<&'a crate::Alias> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Alias) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Alias(node) } } -impl AnyNodeRef<'_> { - pub const fn is_pattern(self) -> bool { - matches!( - self, - AnyNodeRef::PatternMatchValue(_) - | AnyNodeRef::PatternMatchSingleton(_) - | AnyNodeRef::PatternMatchSequence(_) - | AnyNodeRef::PatternMatchMapping(_) - | AnyNodeRef::PatternMatchClass(_) - | AnyNodeRef::PatternMatchStar(_) - | AnyNodeRef::PatternMatchAs(_) - | AnyNodeRef::PatternMatchOr(_) - ) +impl<'a> TryFrom> for &'a crate::Alias { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Alias, ()> { + match node { + AnyRootNodeRef::Alias(node) => Ok(node), + _ => Err(()), + } } } -impl AnyNodeRef<'_> { - pub const fn is_type_param(self) -> bool { - matches!( - self, - AnyNodeRef::TypeParamTypeVar(_) - | AnyNodeRef::TypeParamTypeVarTuple(_) - | AnyNodeRef::TypeParamParamSpec(_) - ) +impl<'a> From<&'a crate::WithItem> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::WithItem) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::WithItem(node) + } +} + +impl<'a> TryFrom> for &'a crate::WithItem { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::WithItem, ()> { + match node { + AnyRootNodeRef::WithItem(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::MatchCase> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::MatchCase) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::MatchCase(node) + } +} + +impl<'a> TryFrom> for &'a crate::MatchCase { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::MatchCase, ()> { + match node { + AnyRootNodeRef::MatchCase(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Decorator> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Decorator) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Decorator(node) + } +} + +impl<'a> TryFrom> for &'a crate::Decorator { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Decorator, ()> { + match node { + AnyRootNodeRef::Decorator(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::ElifElseClause> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::ElifElseClause) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::ElifElseClause(node) + } +} + +impl<'a> TryFrom> for &'a crate::ElifElseClause { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::ElifElseClause, ()> { + match node { + AnyRootNodeRef::ElifElseClause(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::TypeParams> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::TypeParams) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::TypeParams(node) + } +} + +impl<'a> TryFrom> for &'a crate::TypeParams { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::TypeParams, ()> { + match node { + AnyRootNodeRef::TypeParams(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::FString> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::FString) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::FString(node) + } +} + +impl<'a> TryFrom> for &'a crate::FString { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::FString, ()> { + match node { + AnyRootNodeRef::FString(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::TString> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::TString) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::TString(node) + } +} + +impl<'a> TryFrom> for &'a crate::TString { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::TString, ()> { + match node { + AnyRootNodeRef::TString(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::StringLiteral> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::StringLiteral) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::StringLiteral(node) + } +} + +impl<'a> TryFrom> for &'a crate::StringLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::StringLiteral, ()> { + match node { + AnyRootNodeRef::StringLiteral(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::BytesLiteral> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::BytesLiteral) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::BytesLiteral(node) + } +} + +impl<'a> TryFrom> for &'a crate::BytesLiteral { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::BytesLiteral, ()> { + match node { + AnyRootNodeRef::BytesLiteral(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Identifier> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Identifier) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Identifier(node) + } +} + +impl<'a> TryFrom> for &'a crate::Identifier { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Identifier, ()> { + match node { + AnyRootNodeRef::Identifier(node) => Ok(node), + _ => Err(()), + } + } +} + +impl ruff_text_size::Ranged for AnyRootNodeRef<'_> { + fn range(&self) -> ruff_text_size::TextRange { + match self { + AnyRootNodeRef::Mod(node) => node.range(), + AnyRootNodeRef::Stmt(node) => node.range(), + AnyRootNodeRef::Expr(node) => node.range(), + AnyRootNodeRef::ExceptHandler(node) => node.range(), + AnyRootNodeRef::InterpolatedStringElement(node) => node.range(), + AnyRootNodeRef::Pattern(node) => node.range(), + AnyRootNodeRef::TypeParam(node) => node.range(), + AnyRootNodeRef::InterpolatedStringFormatSpec(node) => node.range(), + AnyRootNodeRef::PatternArguments(node) => node.range(), + AnyRootNodeRef::PatternKeyword(node) => node.range(), + AnyRootNodeRef::Comprehension(node) => node.range(), + AnyRootNodeRef::Arguments(node) => node.range(), + AnyRootNodeRef::Parameters(node) => node.range(), + AnyRootNodeRef::Parameter(node) => node.range(), + AnyRootNodeRef::ParameterWithDefault(node) => node.range(), + AnyRootNodeRef::Keyword(node) => node.range(), + AnyRootNodeRef::Alias(node) => node.range(), + AnyRootNodeRef::WithItem(node) => node.range(), + AnyRootNodeRef::MatchCase(node) => node.range(), + AnyRootNodeRef::Decorator(node) => node.range(), + AnyRootNodeRef::ElifElseClause(node) => node.range(), + AnyRootNodeRef::TypeParams(node) => node.range(), + AnyRootNodeRef::FString(node) => node.range(), + AnyRootNodeRef::TString(node) => node.range(), + AnyRootNodeRef::StringLiteral(node) => node.range(), + AnyRootNodeRef::BytesLiteral(node) => node.range(), + AnyRootNodeRef::Identifier(node) => node.range(), + } + } +} + +impl crate::HasNodeIndex for AnyRootNodeRef<'_> { + fn node_index(&self) -> &crate::AtomicNodeIndex { + match self { + AnyRootNodeRef::Mod(node) => node.node_index(), + AnyRootNodeRef::Stmt(node) => node.node_index(), + AnyRootNodeRef::Expr(node) => node.node_index(), + AnyRootNodeRef::ExceptHandler(node) => node.node_index(), + AnyRootNodeRef::InterpolatedStringElement(node) => node.node_index(), + AnyRootNodeRef::Pattern(node) => node.node_index(), + AnyRootNodeRef::TypeParam(node) => node.node_index(), + AnyRootNodeRef::InterpolatedStringFormatSpec(node) => node.node_index(), + AnyRootNodeRef::PatternArguments(node) => node.node_index(), + AnyRootNodeRef::PatternKeyword(node) => node.node_index(), + AnyRootNodeRef::Comprehension(node) => node.node_index(), + AnyRootNodeRef::Arguments(node) => node.node_index(), + AnyRootNodeRef::Parameters(node) => node.node_index(), + AnyRootNodeRef::Parameter(node) => node.node_index(), + AnyRootNodeRef::ParameterWithDefault(node) => node.node_index(), + AnyRootNodeRef::Keyword(node) => node.node_index(), + AnyRootNodeRef::Alias(node) => node.node_index(), + AnyRootNodeRef::WithItem(node) => node.node_index(), + AnyRootNodeRef::MatchCase(node) => node.node_index(), + AnyRootNodeRef::Decorator(node) => node.node_index(), + AnyRootNodeRef::ElifElseClause(node) => node.node_index(), + AnyRootNodeRef::TypeParams(node) => node.node_index(), + AnyRootNodeRef::FString(node) => node.node_index(), + AnyRootNodeRef::TString(node) => node.node_index(), + AnyRootNodeRef::StringLiteral(node) => node.node_index(), + AnyRootNodeRef::BytesLiteral(node) => node.node_index(), + AnyRootNodeRef::Identifier(node) => node.node_index(), + } + } +} + +impl<'a> AnyRootNodeRef<'a> { + pub fn visit_source_order<'b, V>(self, visitor: &mut V) + where + V: crate::visitor::source_order::SourceOrderVisitor<'b> + ?Sized, + 'a: 'b, + { + match self { + AnyRootNodeRef::Mod(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Stmt(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Expr(node) => node.visit_source_order(visitor), + AnyRootNodeRef::ExceptHandler(node) => node.visit_source_order(visitor), + AnyRootNodeRef::InterpolatedStringElement(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Pattern(node) => node.visit_source_order(visitor), + AnyRootNodeRef::TypeParam(node) => node.visit_source_order(visitor), + AnyRootNodeRef::InterpolatedStringFormatSpec(node) => node.visit_source_order(visitor), + AnyRootNodeRef::PatternArguments(node) => node.visit_source_order(visitor), + AnyRootNodeRef::PatternKeyword(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Comprehension(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Arguments(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Parameters(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Parameter(node) => node.visit_source_order(visitor), + AnyRootNodeRef::ParameterWithDefault(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Keyword(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Alias(node) => node.visit_source_order(visitor), + AnyRootNodeRef::WithItem(node) => node.visit_source_order(visitor), + AnyRootNodeRef::MatchCase(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Decorator(node) => node.visit_source_order(visitor), + AnyRootNodeRef::ElifElseClause(node) => node.visit_source_order(visitor), + AnyRootNodeRef::TypeParams(node) => node.visit_source_order(visitor), + AnyRootNodeRef::FString(node) => node.visit_source_order(visitor), + AnyRootNodeRef::TString(node) => node.visit_source_order(visitor), + AnyRootNodeRef::StringLiteral(node) => node.visit_source_order(visitor), + AnyRootNodeRef::BytesLiteral(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Identifier(node) => node.visit_source_order(visitor), + } } } @@ -6700,6 +8936,7 @@ impl AnyNodeRef<'_> { /// See also [Module](https://docs.python.org/3/library/ast.html#ast.Module) #[derive(Clone, Debug, PartialEq)] pub struct ModModule { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub body: Vec, } @@ -6707,6 +8944,7 @@ pub struct ModModule { /// See also [Module](https://docs.python.org/3/library/ast.html#ast.Module) #[derive(Clone, Debug, PartialEq)] pub struct ModExpression { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub body: Box, } @@ -6717,6 +8955,7 @@ pub struct ModExpression { /// This type differs from the original Python AST, as it collapses the synchronous and asynchronous variants into a single type. #[derive(Clone, Debug, PartialEq)] pub struct StmtFunctionDef { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub is_async: bool, pub decorator_list: Vec, @@ -6730,6 +8969,7 @@ pub struct StmtFunctionDef { /// See also [ClassDef](https://docs.python.org/3/library/ast.html#ast.ClassDef) #[derive(Clone, Debug, PartialEq)] pub struct StmtClassDef { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub decorator_list: Vec, pub name: crate::Identifier, @@ -6741,6 +8981,7 @@ pub struct StmtClassDef { /// See also [Return](https://docs.python.org/3/library/ast.html#ast.Return) #[derive(Clone, Debug, PartialEq)] pub struct StmtReturn { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Option>, } @@ -6748,6 +8989,7 @@ pub struct StmtReturn { /// See also [Delete](https://docs.python.org/3/library/ast.html#ast.Delete) #[derive(Clone, Debug, PartialEq)] pub struct StmtDelete { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub targets: Vec, } @@ -6755,6 +8997,7 @@ pub struct StmtDelete { /// See also [TypeAlias](https://docs.python.org/3/library/ast.html#ast.TypeAlias) #[derive(Clone, Debug, PartialEq)] pub struct StmtTypeAlias { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub name: Box, pub type_params: Option>, @@ -6764,6 +9007,7 @@ pub struct StmtTypeAlias { /// See also [Assign](https://docs.python.org/3/library/ast.html#ast.Assign) #[derive(Clone, Debug, PartialEq)] pub struct StmtAssign { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub targets: Vec, pub value: Box, @@ -6772,6 +9016,7 @@ pub struct StmtAssign { /// See also [AugAssign](https://docs.python.org/3/library/ast.html#ast.AugAssign) #[derive(Clone, Debug, PartialEq)] pub struct StmtAugAssign { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub target: Box, pub op: crate::Operator, @@ -6781,6 +9026,7 @@ pub struct StmtAugAssign { /// See also [AnnAssign](https://docs.python.org/3/library/ast.html#ast.AnnAssign) #[derive(Clone, Debug, PartialEq)] pub struct StmtAnnAssign { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub target: Box, pub annotation: Box, @@ -6794,6 +9040,7 @@ pub struct StmtAnnAssign { /// This type differs from the original Python AST, as it collapses the synchronous and asynchronous variants into a single type. #[derive(Clone, Debug, PartialEq)] pub struct StmtFor { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub is_async: bool, pub target: Box, @@ -6806,6 +9053,7 @@ pub struct StmtFor { /// and [AsyncWhile](https://docs.python.org/3/library/ast.html#ast.AsyncWhile). #[derive(Clone, Debug, PartialEq)] pub struct StmtWhile { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub test: Box, pub body: Vec, @@ -6815,6 +9063,7 @@ pub struct StmtWhile { /// See also [If](https://docs.python.org/3/library/ast.html#ast.If) #[derive(Clone, Debug, PartialEq)] pub struct StmtIf { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub test: Box, pub body: Vec, @@ -6827,6 +9076,7 @@ pub struct StmtIf { /// This type differs from the original Python AST, as it collapses the synchronous and asynchronous variants into a single type. #[derive(Clone, Debug, PartialEq)] pub struct StmtWith { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub is_async: bool, pub items: Vec, @@ -6836,6 +9086,7 @@ pub struct StmtWith { /// See also [Match](https://docs.python.org/3/library/ast.html#ast.Match) #[derive(Clone, Debug, PartialEq)] pub struct StmtMatch { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub subject: Box, pub cases: Vec, @@ -6844,6 +9095,7 @@ pub struct StmtMatch { /// See also [Raise](https://docs.python.org/3/library/ast.html#ast.Raise) #[derive(Clone, Debug, PartialEq)] pub struct StmtRaise { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub exc: Option>, pub cause: Option>, @@ -6853,6 +9105,7 @@ pub struct StmtRaise { /// and [TryStar](https://docs.python.org/3/library/ast.html#ast.TryStar) #[derive(Clone, Debug, PartialEq)] pub struct StmtTry { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub body: Vec, pub handlers: Vec, @@ -6864,6 +9117,7 @@ pub struct StmtTry { /// See also [Assert](https://docs.python.org/3/library/ast.html#ast.Assert) #[derive(Clone, Debug, PartialEq)] pub struct StmtAssert { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub test: Box, pub msg: Option>, @@ -6872,6 +9126,7 @@ pub struct StmtAssert { /// See also [Import](https://docs.python.org/3/library/ast.html#ast.Import) #[derive(Clone, Debug, PartialEq)] pub struct StmtImport { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub names: Vec, } @@ -6879,6 +9134,7 @@ pub struct StmtImport { /// See also [ImportFrom](https://docs.python.org/3/library/ast.html#ast.ImportFrom) #[derive(Clone, Debug, PartialEq)] pub struct StmtImportFrom { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub module: Option, pub names: Vec, @@ -6888,6 +9144,7 @@ pub struct StmtImportFrom { /// See also [Global](https://docs.python.org/3/library/ast.html#ast.Global) #[derive(Clone, Debug, PartialEq)] pub struct StmtGlobal { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub names: Vec, } @@ -6895,6 +9152,7 @@ pub struct StmtGlobal { /// See also [Nonlocal](https://docs.python.org/3/library/ast.html#ast.Nonlocal) #[derive(Clone, Debug, PartialEq)] pub struct StmtNonlocal { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub names: Vec, } @@ -6902,6 +9160,7 @@ pub struct StmtNonlocal { /// See also [Expr](https://docs.python.org/3/library/ast.html#ast.Expr) #[derive(Clone, Debug, PartialEq)] pub struct StmtExpr { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, } @@ -6909,18 +9168,21 @@ pub struct StmtExpr { /// See also [Pass](https://docs.python.org/3/library/ast.html#ast.Pass) #[derive(Clone, Debug, PartialEq)] pub struct StmtPass { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, } /// See also [Break](https://docs.python.org/3/library/ast.html#ast.Break) #[derive(Clone, Debug, PartialEq)] pub struct StmtBreak { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, } /// See also [Continue](https://docs.python.org/3/library/ast.html#ast.Continue) #[derive(Clone, Debug, PartialEq)] pub struct StmtContinue { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, } @@ -6980,6 +9242,7 @@ pub struct StmtContinue { /// #[derive(Clone, Debug, PartialEq)] pub struct StmtIpyEscapeCommand { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub kind: crate::IpyEscapeKind, pub value: Box, @@ -6988,6 +9251,7 @@ pub struct StmtIpyEscapeCommand { /// See also [BoolOp](https://docs.python.org/3/library/ast.html#ast.BoolOp) #[derive(Clone, Debug, PartialEq)] pub struct ExprBoolOp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub op: crate::BoolOp, pub values: Vec, @@ -6996,6 +9260,7 @@ pub struct ExprBoolOp { /// See also [NamedExpr](https://docs.python.org/3/library/ast.html#ast.NamedExpr) #[derive(Clone, Debug, PartialEq)] pub struct ExprNamed { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub target: Box, pub value: Box, @@ -7004,6 +9269,7 @@ pub struct ExprNamed { /// See also [BinOp](https://docs.python.org/3/library/ast.html#ast.BinOp) #[derive(Clone, Debug, PartialEq)] pub struct ExprBinOp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub left: Box, pub op: crate::Operator, @@ -7013,6 +9279,7 @@ pub struct ExprBinOp { /// See also [UnaryOp](https://docs.python.org/3/library/ast.html#ast.UnaryOp) #[derive(Clone, Debug, PartialEq)] pub struct ExprUnaryOp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub op: crate::UnaryOp, pub operand: Box, @@ -7021,6 +9288,7 @@ pub struct ExprUnaryOp { /// See also [Lambda](https://docs.python.org/3/library/ast.html#ast.Lambda) #[derive(Clone, Debug, PartialEq)] pub struct ExprLambda { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub parameters: Option>, pub body: Box, @@ -7029,6 +9297,7 @@ pub struct ExprLambda { /// See also [IfExp](https://docs.python.org/3/library/ast.html#ast.IfExp) #[derive(Clone, Debug, PartialEq)] pub struct ExprIf { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub test: Box, pub body: Box, @@ -7038,6 +9307,7 @@ pub struct ExprIf { /// See also [Dict](https://docs.python.org/3/library/ast.html#ast.Dict) #[derive(Clone, Debug, PartialEq)] pub struct ExprDict { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub items: Vec, } @@ -7045,6 +9315,7 @@ pub struct ExprDict { /// See also [Set](https://docs.python.org/3/library/ast.html#ast.Set) #[derive(Clone, Debug, PartialEq)] pub struct ExprSet { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elts: Vec, } @@ -7052,6 +9323,7 @@ pub struct ExprSet { /// See also [ListComp](https://docs.python.org/3/library/ast.html#ast.ListComp) #[derive(Clone, Debug, PartialEq)] pub struct ExprListComp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elt: Box, pub generators: Vec, @@ -7060,6 +9332,7 @@ pub struct ExprListComp { /// See also [SetComp](https://docs.python.org/3/library/ast.html#ast.SetComp) #[derive(Clone, Debug, PartialEq)] pub struct ExprSetComp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elt: Box, pub generators: Vec, @@ -7068,6 +9341,7 @@ pub struct ExprSetComp { /// See also [DictComp](https://docs.python.org/3/library/ast.html#ast.DictComp) #[derive(Clone, Debug, PartialEq)] pub struct ExprDictComp { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub key: Box, pub value: Box, @@ -7077,6 +9351,7 @@ pub struct ExprDictComp { /// See also [GeneratorExp](https://docs.python.org/3/library/ast.html#ast.GeneratorExp) #[derive(Clone, Debug, PartialEq)] pub struct ExprGenerator { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elt: Box, pub generators: Vec, @@ -7086,6 +9361,7 @@ pub struct ExprGenerator { /// See also [Await](https://docs.python.org/3/library/ast.html#ast.Await) #[derive(Clone, Debug, PartialEq)] pub struct ExprAwait { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, } @@ -7093,6 +9369,7 @@ pub struct ExprAwait { /// See also [Yield](https://docs.python.org/3/library/ast.html#ast.Yield) #[derive(Clone, Debug, PartialEq)] pub struct ExprYield { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Option>, } @@ -7100,6 +9377,7 @@ pub struct ExprYield { /// See also [YieldFrom](https://docs.python.org/3/library/ast.html#ast.YieldFrom) #[derive(Clone, Debug, PartialEq)] pub struct ExprYieldFrom { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, } @@ -7107,6 +9385,7 @@ pub struct ExprYieldFrom { /// See also [Compare](https://docs.python.org/3/library/ast.html#ast.Compare) #[derive(Clone, Debug, PartialEq)] pub struct ExprCompare { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub left: Box, pub ops: Box<[crate::CmpOp]>, @@ -7116,6 +9395,7 @@ pub struct ExprCompare { /// See also [Call](https://docs.python.org/3/library/ast.html#ast.Call) #[derive(Clone, Debug, PartialEq)] pub struct ExprCall { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub func: Box, pub arguments: crate::Arguments, @@ -7131,6 +9411,7 @@ pub struct ExprCall { /// See also [JoinedStr](https://docs.python.org/3/library/ast.html#ast.JoinedStr) #[derive(Clone, Debug, PartialEq)] pub struct ExprFString { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: crate::FStringValue, } @@ -7145,6 +9426,7 @@ pub struct ExprFString { /// See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateStr) #[derive(Clone, Debug, PartialEq)] pub struct ExprTString { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: crate::TStringValue, } @@ -7153,6 +9435,7 @@ pub struct ExprTString { /// or an implicitly concatenated string literal. #[derive(Clone, Debug, PartialEq)] pub struct ExprStringLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: crate::StringLiteralValue, } @@ -7161,35 +9444,41 @@ pub struct ExprStringLiteral { /// or an implicitly concatenated bytestring literal. #[derive(Clone, Debug, PartialEq)] pub struct ExprBytesLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: crate::BytesLiteralValue, } #[derive(Clone, Debug, PartialEq)] pub struct ExprNumberLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: crate::Number, } #[derive(Clone, Debug, PartialEq, Default)] pub struct ExprBooleanLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: bool, } #[derive(Clone, Debug, PartialEq, Default)] pub struct ExprNoneLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, } #[derive(Clone, Debug, PartialEq, Default)] pub struct ExprEllipsisLiteral { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, } /// See also [Attribute](https://docs.python.org/3/library/ast.html#ast.Attribute) #[derive(Clone, Debug, PartialEq)] pub struct ExprAttribute { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, pub attr: crate::Identifier, @@ -7199,6 +9488,7 @@ pub struct ExprAttribute { /// See also [Subscript](https://docs.python.org/3/library/ast.html#ast.Subscript) #[derive(Clone, Debug, PartialEq)] pub struct ExprSubscript { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, pub slice: Box, @@ -7208,6 +9498,7 @@ pub struct ExprSubscript { /// See also [Starred](https://docs.python.org/3/library/ast.html#ast.Starred) #[derive(Clone, Debug, PartialEq)] pub struct ExprStarred { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub value: Box, pub ctx: crate::ExprContext, @@ -7216,6 +9507,7 @@ pub struct ExprStarred { /// See also [Name](https://docs.python.org/3/library/ast.html#ast.Name) #[derive(Clone, Debug, PartialEq)] pub struct ExprName { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub id: Name, pub ctx: crate::ExprContext, @@ -7224,6 +9516,7 @@ pub struct ExprName { /// See also [List](https://docs.python.org/3/library/ast.html#ast.List) #[derive(Clone, Debug, PartialEq)] pub struct ExprList { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elts: Vec, pub ctx: crate::ExprContext, @@ -7232,6 +9525,7 @@ pub struct ExprList { /// See also [Tuple](https://docs.python.org/3/library/ast.html#ast.Tuple) #[derive(Clone, Debug, PartialEq)] pub struct ExprTuple { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub elts: Vec, pub ctx: crate::ExprContext, @@ -7241,6 +9535,7 @@ pub struct ExprTuple { /// See also [Slice](https://docs.python.org/3/library/ast.html#ast.Slice) #[derive(Clone, Debug, PartialEq)] pub struct ExprSlice { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub lower: Option>, pub upper: Option>, @@ -7260,6 +9555,7 @@ pub struct ExprSlice { /// see [`StmtIpyEscapeCommand`]. #[derive(Clone, Debug, PartialEq)] pub struct ExprIpyEscapeCommand { + pub node_index: crate::AtomicNodeIndex, pub range: ruff_text_size::TextRange, pub kind: crate::IpyEscapeKind, pub value: Box, @@ -7270,7 +9566,11 @@ impl ModModule { where V: SourceOrderVisitor<'a> + ?Sized, { - let ModModule { body, range: _ } = self; + let ModModule { + body, + range: _, + node_index: _, + } = self; visitor.visit_body(body); } } @@ -7280,7 +9580,11 @@ impl ModExpression { where V: SourceOrderVisitor<'a> + ?Sized, { - let ModExpression { body, range: _ } = self; + let ModExpression { + body, + range: _, + node_index: _, + } = self; visitor.visit_expr(body); } } @@ -7299,6 +9603,7 @@ impl StmtFunctionDef { returns, body, range: _, + node_index: _, } = self; for elm in decorator_list { @@ -7332,6 +9637,7 @@ impl StmtClassDef { arguments, body, range: _, + node_index: _, } = self; for elm in decorator_list { @@ -7356,7 +9662,11 @@ impl StmtReturn { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtReturn { value, range: _ } = self; + let StmtReturn { + value, + range: _, + node_index: _, + } = self; if let Some(value) = value { visitor.visit_expr(value); @@ -7369,7 +9679,11 @@ impl StmtDelete { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtDelete { targets, range: _ } = self; + let StmtDelete { + targets, + range: _, + node_index: _, + } = self; for elm in targets { visitor.visit_expr(elm); @@ -7387,6 +9701,7 @@ impl StmtTypeAlias { type_params, value, range: _, + node_index: _, } = self; visitor.visit_expr(name); @@ -7407,6 +9722,7 @@ impl StmtAssign { targets, value, range: _, + node_index: _, } = self; for elm in targets { @@ -7426,6 +9742,7 @@ impl StmtAugAssign { op, value, range: _, + node_index: _, } = self; visitor.visit_expr(target); visitor.visit_operator(op); @@ -7444,6 +9761,7 @@ impl StmtAnnAssign { value, simple: _, range: _, + node_index: _, } = self; visitor.visit_expr(target); visitor.visit_annotation(annotation); @@ -7466,6 +9784,7 @@ impl StmtFor { body, orelse, range: _, + node_index: _, } = self; visitor.visit_expr(target); visitor.visit_expr(iter); @@ -7484,6 +9803,7 @@ impl StmtWhile { body, orelse, range: _, + node_index: _, } = self; visitor.visit_expr(test); visitor.visit_body(body); @@ -7501,6 +9821,7 @@ impl StmtIf { body, elif_else_clauses, range: _, + node_index: _, } = self; visitor.visit_expr(test); visitor.visit_body(body); @@ -7521,6 +9842,7 @@ impl StmtWith { items, body, range: _, + node_index: _, } = self; for elm in items { @@ -7539,6 +9861,7 @@ impl StmtMatch { subject, cases, range: _, + node_index: _, } = self; visitor.visit_expr(subject); @@ -7557,6 +9880,7 @@ impl StmtRaise { exc, cause, range: _, + node_index: _, } = self; if let Some(exc) = exc { @@ -7581,6 +9905,7 @@ impl StmtTry { finalbody, is_star: _, range: _, + node_index: _, } = self; visitor.visit_body(body); @@ -7601,6 +9926,7 @@ impl StmtAssert { test, msg, range: _, + node_index: _, } = self; visitor.visit_expr(test); @@ -7615,7 +9941,11 @@ impl StmtImport { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtImport { names, range: _ } = self; + let StmtImport { + names, + range: _, + node_index: _, + } = self; for elm in names { visitor.visit_alias(elm); @@ -7633,6 +9963,7 @@ impl StmtImportFrom { names, level: _, range: _, + node_index: _, } = self; if let Some(module) = module { @@ -7650,7 +9981,11 @@ impl StmtGlobal { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtGlobal { names, range: _ } = self; + let StmtGlobal { + names, + range: _, + node_index: _, + } = self; for elm in names { visitor.visit_identifier(elm); @@ -7663,7 +9998,11 @@ impl StmtNonlocal { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtNonlocal { names, range: _ } = self; + let StmtNonlocal { + names, + range: _, + node_index: _, + } = self; for elm in names { visitor.visit_identifier(elm); @@ -7676,7 +10015,11 @@ impl StmtExpr { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtExpr { value, range: _ } = self; + let StmtExpr { + value, + range: _, + node_index: _, + } = self; visitor.visit_expr(value); } } @@ -7686,7 +10029,10 @@ impl StmtPass { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtPass { range: _ } = self; + let StmtPass { + range: _, + node_index: _, + } = self; } } @@ -7695,7 +10041,10 @@ impl StmtBreak { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtBreak { range: _ } = self; + let StmtBreak { + range: _, + node_index: _, + } = self; } } @@ -7704,7 +10053,10 @@ impl StmtContinue { where V: SourceOrderVisitor<'a> + ?Sized, { - let StmtContinue { range: _ } = self; + let StmtContinue { + range: _, + node_index: _, + } = self; } } @@ -7717,6 +10069,7 @@ impl StmtIpyEscapeCommand { kind: _, value: _, range: _, + node_index: _, } = self; } } @@ -7730,6 +10083,7 @@ impl ExprNamed { target, value, range: _, + node_index: _, } = self; visitor.visit_expr(target); visitor.visit_expr(value); @@ -7746,6 +10100,7 @@ impl ExprBinOp { op, right, range: _, + node_index: _, } = self; visitor.visit_expr(left); visitor.visit_operator(op); @@ -7762,6 +10117,7 @@ impl ExprUnaryOp { op, operand, range: _, + node_index: _, } = self; visitor.visit_unary_op(op); visitor.visit_expr(operand); @@ -7777,6 +10133,7 @@ impl ExprLambda { parameters, body, range: _, + node_index: _, } = self; if let Some(parameters) = parameters { @@ -7797,6 +10154,7 @@ impl ExprIf { body, orelse, range: _, + node_index: _, } = self; visitor.visit_expr(body); visitor.visit_expr(test); @@ -7809,7 +10167,11 @@ impl ExprSet { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprSet { elts, range: _ } = self; + let ExprSet { + elts, + range: _, + node_index: _, + } = self; for elm in elts { visitor.visit_expr(elm); @@ -7826,6 +10188,7 @@ impl ExprListComp { elt, generators, range: _, + node_index: _, } = self; visitor.visit_expr(elt); @@ -7844,6 +10207,7 @@ impl ExprSetComp { elt, generators, range: _, + node_index: _, } = self; visitor.visit_expr(elt); @@ -7863,6 +10227,7 @@ impl ExprDictComp { value, generators, range: _, + node_index: _, } = self; visitor.visit_expr(key); visitor.visit_expr(value); @@ -7883,6 +10248,7 @@ impl ExprGenerator { generators, parenthesized: _, range: _, + node_index: _, } = self; visitor.visit_expr(elt); @@ -7897,7 +10263,11 @@ impl ExprAwait { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprAwait { value, range: _ } = self; + let ExprAwait { + value, + range: _, + node_index: _, + } = self; visitor.visit_expr(value); } } @@ -7907,7 +10277,11 @@ impl ExprYield { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprYield { value, range: _ } = self; + let ExprYield { + value, + range: _, + node_index: _, + } = self; if let Some(value) = value { visitor.visit_expr(value); @@ -7920,7 +10294,11 @@ impl ExprYieldFrom { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprYieldFrom { value, range: _ } = self; + let ExprYieldFrom { + value, + range: _, + node_index: _, + } = self; visitor.visit_expr(value); } } @@ -7934,6 +10312,7 @@ impl ExprCall { func, arguments, range: _, + node_index: _, } = self; visitor.visit_expr(func); visitor.visit_arguments(arguments); @@ -7945,7 +10324,11 @@ impl ExprNumberLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprNumberLiteral { value: _, range: _ } = self; + let ExprNumberLiteral { + value: _, + range: _, + node_index: _, + } = self; } } @@ -7954,7 +10337,11 @@ impl ExprBooleanLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprBooleanLiteral { value: _, range: _ } = self; + let ExprBooleanLiteral { + value: _, + range: _, + node_index: _, + } = self; } } @@ -7963,7 +10350,10 @@ impl ExprNoneLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprNoneLiteral { range: _ } = self; + let ExprNoneLiteral { + range: _, + node_index: _, + } = self; } } @@ -7972,7 +10362,10 @@ impl ExprEllipsisLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ExprEllipsisLiteral { range: _ } = self; + let ExprEllipsisLiteral { + range: _, + node_index: _, + } = self; } } @@ -7986,6 +10379,7 @@ impl ExprAttribute { attr, ctx: _, range: _, + node_index: _, } = self; visitor.visit_expr(value); visitor.visit_identifier(attr); @@ -8002,6 +10396,7 @@ impl ExprSubscript { slice, ctx: _, range: _, + node_index: _, } = self; visitor.visit_expr(value); visitor.visit_expr(slice); @@ -8017,6 +10412,7 @@ impl ExprStarred { value, ctx: _, range: _, + node_index: _, } = self; visitor.visit_expr(value); } @@ -8031,6 +10427,7 @@ impl ExprName { id: _, ctx: _, range: _, + node_index: _, } = self; } } @@ -8044,6 +10441,7 @@ impl ExprList { elts, ctx: _, range: _, + node_index: _, } = self; for elm in elts { @@ -8062,6 +10460,7 @@ impl ExprTuple { ctx: _, parenthesized: _, range: _, + node_index: _, } = self; for elm in elts { @@ -8080,6 +10479,7 @@ impl ExprSlice { upper, step, range: _, + node_index: _, } = self; if let Some(lower) = lower { @@ -8105,6 +10505,7 @@ impl ExprIpyEscapeCommand { kind: _, value: _, range: _, + node_index: _, } = self; } } diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index ba0b08bfe680df..de17f508691302 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -12,8 +12,8 @@ use crate::parenthesize::parenthesized_range; use crate::statement_visitor::StatementVisitor; use crate::visitor::Visitor; use crate::{ - self as ast, Arguments, CmpOp, DictItem, ExceptHandler, Expr, InterpolatedStringElement, - MatchCase, Operator, Pattern, Stmt, TypeParam, + self as ast, Arguments, AtomicNodeIndex, CmpOp, DictItem, ExceptHandler, Expr, + InterpolatedStringElement, MatchCase, Operator, Pattern, Stmt, TypeParam, }; use crate::{AnyNodeRef, ExprContext}; @@ -53,6 +53,7 @@ where func, arguments, range: _, + node_index: _, }) = expr { // Ex) `list()` @@ -146,6 +147,7 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { target, value, range: _, + node_index: _, }) => any_over_expr(target, func) || any_over_expr(value, func), Expr::BinOp(ast::ExprBinOp { left, right, .. }) => { any_over_expr(left, func) || any_over_expr(right, func) @@ -157,32 +159,49 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { body, orelse, range: _, + node_index: _, }) => any_over_expr(test, func) || any_over_expr(body, func) || any_over_expr(orelse, func), - Expr::Dict(ast::ExprDict { items, range: _ }) => { - items.iter().any(|ast::DictItem { key, value }| { - any_over_expr(value, func) - || key.as_ref().is_some_and(|key| any_over_expr(key, func)) - }) - } - Expr::Set(ast::ExprSet { elts, range: _ }) - | Expr::List(ast::ExprList { elts, range: _, .. }) - | Expr::Tuple(ast::ExprTuple { elts, range: _, .. }) => { - elts.iter().any(|expr| any_over_expr(expr, func)) - } + Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => items.iter().any(|ast::DictItem { key, value }| { + any_over_expr(value, func) || key.as_ref().is_some_and(|key| any_over_expr(key, func)) + }), + Expr::Set(ast::ExprSet { + elts, + range: _, + node_index: _, + }) + | Expr::List(ast::ExprList { + elts, + range: _, + node_index: _, + .. + }) + | Expr::Tuple(ast::ExprTuple { + elts, + range: _, + node_index: _, + .. + }) => elts.iter().any(|expr| any_over_expr(expr, func)), Expr::ListComp(ast::ExprListComp { elt, generators, range: _, + node_index: _, }) | Expr::SetComp(ast::ExprSetComp { elt, generators, range: _, + node_index: _, }) | Expr::Generator(ast::ExprGenerator { elt, generators, range: _, + node_index: _, parenthesized: _, }) => { any_over_expr(elt, func) @@ -197,6 +216,7 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { value, generators, range: _, + node_index: _, }) => { any_over_expr(key, func) || any_over_expr(value, func) @@ -206,15 +226,33 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { || generator.ifs.iter().any(|expr| any_over_expr(expr, func)) }) } - Expr::Await(ast::ExprAwait { value, range: _ }) - | Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) + Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) + | Expr::YieldFrom(ast::ExprYieldFrom { + value, + range: _, + node_index: _, + }) | Expr::Attribute(ast::ExprAttribute { - value, range: _, .. + value, + range: _, + node_index: _, + .. }) | Expr::Starred(ast::ExprStarred { - value, range: _, .. + value, + range: _, + node_index: _, + .. }) => any_over_expr(value, func), - Expr::Yield(ast::ExprYield { value, range: _ }) => value + Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => value .as_ref() .is_some_and(|value| any_over_expr(value, func)), Expr::Compare(ast::ExprCompare { @@ -224,6 +262,7 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { func: call_func, arguments, range: _, + node_index: _, }) => { any_over_expr(call_func, func) // Note that this is the evaluation order but not necessarily the declaration order @@ -241,6 +280,7 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { upper, step, range: _, + node_index: _, }) => { lower .as_ref() @@ -284,11 +324,17 @@ pub fn any_over_type_param(type_param: &TypeParam, func: &dyn Fn(&Expr) -> bool) pub fn any_over_pattern(pattern: &Pattern, func: &dyn Fn(&Expr) -> bool) -> bool { match pattern { - Pattern::MatchValue(ast::PatternMatchValue { value, range: _ }) => { - any_over_expr(value, func) - } + Pattern::MatchValue(ast::PatternMatchValue { + value, + range: _, + node_index: _, + }) => any_over_expr(value, func), Pattern::MatchSingleton(_) => false, - Pattern::MatchSequence(ast::PatternMatchSequence { patterns, range: _ }) => patterns + Pattern::MatchSequence(ast::PatternMatchSequence { + patterns, + range: _, + node_index: _, + }) => patterns .iter() .any(|pattern| any_over_pattern(pattern, func)), Pattern::MatchMapping(ast::PatternMatchMapping { keys, patterns, .. }) => { @@ -312,7 +358,11 @@ pub fn any_over_pattern(pattern: &Pattern, func: &dyn Fn(&Expr) -> bool) -> bool Pattern::MatchAs(ast::PatternMatchAs { pattern, .. }) => pattern .as_ref() .is_some_and(|pattern| any_over_pattern(pattern, func)), - Pattern::MatchOr(ast::PatternMatchOr { patterns, range: _ }) => patterns + Pattern::MatchOr(ast::PatternMatchOr { + patterns, + range: _, + node_index: _, + }) => patterns .iter() .any(|pattern| any_over_pattern(pattern, func)), } @@ -395,12 +445,18 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { .iter() .any(|decorator| any_over_expr(&decorator.expression, func)) } - Stmt::Return(ast::StmtReturn { value, range: _ }) => value + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => value .as_ref() .is_some_and(|value| any_over_expr(value, func)), - Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { - targets.iter().any(|expr| any_over_expr(expr, func)) - } + Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => targets.iter().any(|expr| any_over_expr(expr, func)), Stmt::TypeAlias(ast::StmtTypeAlias { name, type_params, @@ -450,12 +506,14 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { body, orelse, range: _, + node_index: _, }) => any_over_expr(test, func) || any_over_body(body, func) || any_over_body(orelse, func), Stmt::If(ast::StmtIf { test, body, elif_else_clauses, range: _, + node_index: _, }) => { any_over_expr(test, func) || any_over_body(body, func) @@ -480,6 +538,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { exc, cause, range: _, + node_index: _, }) => { exc.as_ref().is_some_and(|value| any_over_expr(value, func)) || cause @@ -493,6 +552,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { finalbody, is_star: _, range: _, + node_index: _, }) => { any_over_body(body, func) || handlers.iter().any(|handler| { @@ -511,6 +571,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { test, msg, range: _, + node_index: _, }) => { any_over_expr(test, func) || msg.as_ref().is_some_and(|value| any_over_expr(value, func)) @@ -519,6 +580,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { subject, cases, range: _, + node_index: _, }) => { any_over_expr(subject, func) || cases.iter().any(|case| { @@ -527,6 +589,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { guard, body, range: _, + node_index: _, } = case; any_over_pattern(pattern, func) || guard.as_ref().is_some_and(|expr| any_over_expr(expr, func)) @@ -537,7 +600,11 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool { Stmt::ImportFrom(_) => false, Stmt::Global(_) => false, Stmt::Nonlocal(_) => false, - Stmt::Expr(ast::StmtExpr { value, range: _ }) => any_over_expr(value, func), + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => any_over_expr(value, func), Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => false, Stmt::IpyEscapeCommand(_) => false, } @@ -957,6 +1024,7 @@ impl<'a> StatementVisitor<'a> for RaiseStatementVisitor<'a> { exc, cause, range: _, + node_index: _, }) => { self.raises .push((stmt.range(), exc.as_deref(), cause.as_deref())); @@ -1026,7 +1094,12 @@ impl Visitor<'_> for AwaitVisitor { /// Return `true` if a `Stmt` is a docstring. pub fn is_docstring_stmt(stmt: &Stmt) -> bool { - if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt { + if let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = stmt + { value.is_string_literal_expr() } else { false @@ -1039,7 +1112,12 @@ pub fn on_conditional_branch<'a>(parents: &mut impl Iterator) - if matches!(parent, Stmt::If(_) | Stmt::While(_) | Stmt::Match(_)) { return true; } - if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = parent { + if let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = parent + { if value.is_if_expr() { return true; } @@ -1480,6 +1558,7 @@ pub fn pep_604_optional(expr: &Expr) -> Expr { op: Operator::BitOr, right: Box::new(Expr::NoneLiteral(ast::ExprNoneLiteral::default())), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), } .into() } @@ -1491,6 +1570,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr { elts: vec![], ctx: ExprContext::Load, range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), parenthesized: true, }), [Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts), @@ -1500,6 +1580,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr { op: Operator::BitOr, right: Box::new(pep_604_union(&[elt.clone()])), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }), } } @@ -1510,11 +1591,13 @@ pub fn typing_optional(elt: Expr, binding: Name) -> Expr { value: Box::new(Expr::Name(ast::ExprName { id: binding, range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), ctx: ExprContext::Load, })), slice: Box::new(elt), ctx: ExprContext::Load, range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }) } @@ -1527,16 +1610,19 @@ pub fn typing_union(elts: &[Expr], binding: Name) -> Expr { value: Box::new(Expr::Name(ast::ExprName { id: binding, range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), ctx: ExprContext::Load, })), slice: Box::new(Expr::Tuple(ast::ExprTuple { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), elts: elts.to_vec(), ctx: ExprContext::Load, parenthesized: false, })), ctx: ExprContext::Load, range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }) } @@ -1624,9 +1710,9 @@ mod tests { use crate::helpers::{any_over_stmt, any_over_type_param, resolve_imported_module_path}; use crate::{ - Expr, ExprContext, ExprName, ExprNumberLiteral, Identifier, Int, Number, Stmt, - StmtTypeAlias, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, - TypeParams, + AtomicNodeIndex, Expr, ExprContext, ExprName, ExprNumberLiteral, Identifier, Int, Number, + Stmt, StmtTypeAlias, TypeParam, TypeParamParamSpec, TypeParamTypeVar, + TypeParamTypeVarTuple, TypeParams, }; #[test] @@ -1669,28 +1755,34 @@ mod tests { let name = Expr::Name(ExprName { id: "x".into(), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), ctx: ExprContext::Load, }); let constant_one = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::from(1u8)), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let constant_two = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::from(2u8)), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let constant_three = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::from(3u8)), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let type_var_one = TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), bound: Some(Box::new(constant_one.clone())), default: None, name: Identifier::new("x", TextRange::default()), }); let type_var_two = TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), bound: None, default: Some(Box::new(constant_two.clone())), name: Identifier::new("x", TextRange::default()), @@ -1700,9 +1792,11 @@ mod tests { type_params: Some(Box::new(TypeParams { type_params: vec![type_var_one, type_var_two], range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), })), value: Box::new(constant_three.clone()), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); assert!(!any_over_stmt(&type_alias, &|expr| { seen.borrow_mut().push(expr.clone()); @@ -1718,6 +1812,7 @@ mod tests { fn any_over_type_param_type_var() { let type_var_no_bound = TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), bound: None, default: None, name: Identifier::new("x", TextRange::default()), @@ -1727,10 +1822,12 @@ mod tests { let constant = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::ONE), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let type_var_with_bound = TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), bound: Some(Box::new(constant.clone())), default: None, name: Identifier::new("x", TextRange::default()), @@ -1748,6 +1845,7 @@ mod tests { let type_var_with_default = TypeParam::TypeVar(TypeParamTypeVar { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), default: Some(Box::new(constant.clone())), bound: None, name: Identifier::new("x", TextRange::default()), @@ -1768,6 +1866,7 @@ mod tests { fn any_over_type_param_type_var_tuple() { let type_var_tuple = TypeParam::TypeVarTuple(TypeParamTypeVarTuple { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), name: Identifier::new("x", TextRange::default()), default: None, }); @@ -1779,10 +1878,12 @@ mod tests { let constant = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::ONE), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let type_var_tuple_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), default: Some(Box::new(constant.clone())), name: Identifier::new("x", TextRange::default()), }); @@ -1802,6 +1903,7 @@ mod tests { fn any_over_type_param_param_spec() { let type_param_spec = TypeParam::ParamSpec(TypeParamParamSpec { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), name: Identifier::new("x", TextRange::default()), default: None, }); @@ -1813,10 +1915,12 @@ mod tests { let constant = Expr::NumberLiteral(ExprNumberLiteral { value: Number::Int(Int::ONE), range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }); let param_spec_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), default: Some(Box::new(constant.clone())), name: Identifier::new("x", TextRange::default()), }); diff --git a/crates/ruff_python_ast/src/lib.rs b/crates/ruff_python_ast/src/lib.rs index 7983ccee821905..eddcb953cec14d 100644 --- a/crates/ruff_python_ast/src/lib.rs +++ b/crates/ruff_python_ast/src/lib.rs @@ -4,6 +4,7 @@ use std::path::Path; pub use expression::*; pub use generated::*; pub use int::*; +pub use node_index::*; pub use nodes::*; pub use operator_precedence::*; pub use python_version::*; @@ -17,6 +18,7 @@ pub mod identifier; mod int; pub mod name; mod node; +mod node_index; mod nodes; pub mod operator_precedence; pub mod parenthesize; diff --git a/crates/ruff_python_ast/src/node.rs b/crates/ruff_python_ast/src/node.rs index 52912c6d66c64f..b9b637b2b4b4d8 100644 --- a/crates/ruff_python_ast/src/node.rs +++ b/crates/ruff_python_ast/src/node.rs @@ -13,6 +13,7 @@ impl ast::ElifElseClause { { let ast::ElifElseClause { range: _, + node_index: _, test, body, } = self; @@ -28,7 +29,11 @@ impl ast::ExprDict { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::ExprDict { items, range: _ } = self; + let ast::ExprDict { + items, + range: _, + node_index: _, + } = self; for ast::DictItem { key, value } in items { if let Some(key) = key { @@ -48,6 +53,7 @@ impl ast::ExprBoolOp { op, values, range: _, + node_index: _, } = self; match values.as_slice() { [left, rest @ ..] => { @@ -74,6 +80,7 @@ impl ast::ExprCompare { ops, comparators, range: _, + node_index: _, } = self; visitor.visit_expr(left); @@ -121,7 +128,11 @@ impl ast::InterpolatedStringLiteralElement { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::InterpolatedStringLiteralElement { range: _, value: _ } = self; + let ast::InterpolatedStringLiteralElement { + range: _, + node_index: _, + value: _, + } = self; } } @@ -130,7 +141,11 @@ impl ast::ExprFString { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::ExprFString { value, range: _ } = self; + let ast::ExprFString { + value, + range: _, + node_index: _, + } = self; for f_string_part in value { match f_string_part { @@ -150,7 +165,11 @@ impl ast::ExprTString { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::ExprTString { value, range: _ } = self; + let ast::ExprTString { + value, + range: _, + node_index: _, + } = self; for t_string_part in value { match t_string_part { @@ -173,7 +192,11 @@ impl ast::ExprStringLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::ExprStringLiteral { value, range: _ } = self; + let ast::ExprStringLiteral { + value, + range: _, + node_index: _, + } = self; for string_literal in value { visitor.visit_string_literal(string_literal); @@ -186,7 +209,11 @@ impl ast::ExprBytesLiteral { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::ExprBytesLiteral { value, range: _ } = self; + let ast::ExprBytesLiteral { + value, + range: _, + node_index: _, + } = self; for bytes_literal in value { visitor.visit_bytes_literal(bytes_literal); @@ -201,6 +228,7 @@ impl ast::ExceptHandlerExceptHandler { { let ast::ExceptHandlerExceptHandler { range: _, + node_index: _, type_, name, body, @@ -222,7 +250,11 @@ impl ast::PatternMatchValue { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::PatternMatchValue { value, range: _ } = self; + let ast::PatternMatchValue { + value, + range: _, + node_index: _, + } = self; visitor.visit_expr(value); } } @@ -232,7 +264,11 @@ impl ast::PatternMatchSingleton { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::PatternMatchSingleton { value, range: _ } = self; + let ast::PatternMatchSingleton { + value, + range: _, + node_index: _, + } = self; visitor.visit_singleton(value); } } @@ -242,7 +278,11 @@ impl ast::PatternMatchSequence { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::PatternMatchSequence { patterns, range: _ } = self; + let ast::PatternMatchSequence { + patterns, + range: _, + node_index: _, + } = self; for pattern in patterns { visitor.visit_pattern(pattern); } @@ -259,6 +299,7 @@ impl ast::PatternMatchMapping { patterns, rest, range: _, + node_index: _, } = self; let mut rest = rest.as_ref(); @@ -289,6 +330,7 @@ impl ast::PatternMatchClass { cls, arguments: parameters, range: _, + node_index: _, } = self; visitor.visit_expr(cls); visitor.visit_pattern_arguments(parameters); @@ -300,7 +342,11 @@ impl ast::PatternMatchStar { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::PatternMatchStar { range: _, name } = self; + let ast::PatternMatchStar { + range: _, + node_index: _, + name, + } = self; if let Some(name) = name { visitor.visit_identifier(name); @@ -316,6 +362,7 @@ impl ast::PatternMatchAs { let ast::PatternMatchAs { pattern, range: _, + node_index: _, name, } = self; if let Some(pattern) = pattern { @@ -333,7 +380,11 @@ impl ast::PatternMatchOr { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::PatternMatchOr { patterns, range: _ } = self; + let ast::PatternMatchOr { + patterns, + range: _, + node_index: _, + } = self; for pattern in patterns { visitor.visit_pattern(pattern); } @@ -347,6 +398,7 @@ impl ast::PatternArguments { { let PatternArguments { range: _, + node_index: _, patterns, keywords, } = self; @@ -368,6 +420,7 @@ impl ast::PatternKeyword { { let PatternKeyword { range: _, + node_index: _, attr, pattern, } = self; @@ -384,6 +437,7 @@ impl ast::Comprehension { { let ast::Comprehension { range: _, + node_index: _, target, iter, ifs, @@ -435,6 +489,7 @@ impl ast::Parameter { { let ast::Parameter { range: _, + node_index: _, name, annotation, } = self; @@ -453,6 +508,7 @@ impl ast::ParameterWithDefault { { let ast::ParameterWithDefault { range: _, + node_index: _, parameter, default, } = self; @@ -470,6 +526,7 @@ impl ast::Keyword { { let ast::Keyword { range: _, + node_index: _, arg, value, } = self; @@ -488,6 +545,7 @@ impl Alias { { let ast::Alias { range: _, + node_index: _, name, asname, } = self; @@ -506,6 +564,7 @@ impl ast::WithItem { { let ast::WithItem { range: _, + node_index: _, context_expr, optional_vars, } = self; @@ -525,6 +584,7 @@ impl ast::MatchCase { { let ast::MatchCase { range: _, + node_index: _, pattern, guard, body, @@ -545,6 +605,7 @@ impl ast::Decorator { { let ast::Decorator { range: _, + node_index: _, expression, } = self; @@ -559,6 +620,7 @@ impl ast::TypeParams { { let ast::TypeParams { range: _, + node_index: _, type_params, } = self; @@ -578,6 +640,7 @@ impl ast::TypeParamTypeVar { default, name, range: _, + node_index: _, } = self; visitor.visit_identifier(name); @@ -598,6 +661,7 @@ impl ast::TypeParamTypeVarTuple { { let ast::TypeParamTypeVarTuple { range: _, + node_index: _, name, default, } = self; @@ -616,6 +680,7 @@ impl ast::TypeParamParamSpec { { let ast::TypeParamParamSpec { range: _, + node_index: _, name, default, } = self; @@ -634,6 +699,7 @@ impl ast::FString { let ast::FString { elements, range: _, + node_index: _, flags: _, } = self; @@ -651,6 +717,7 @@ impl ast::TString { let ast::TString { elements, range: _, + node_index: _, flags: _, } = self; @@ -668,6 +735,7 @@ impl ast::StringLiteral { { let ast::StringLiteral { range: _, + node_index: _, value: _, flags: _, } = self; @@ -682,6 +750,7 @@ impl ast::BytesLiteral { { let ast::BytesLiteral { range: _, + node_index: _, value: _, flags: _, } = self; @@ -694,7 +763,11 @@ impl ast::Identifier { where V: SourceOrderVisitor<'a> + ?Sized, { - let ast::Identifier { range: _, id: _ } = self; + let ast::Identifier { + range: _, + node_index: _, + id: _, + } = self; } } diff --git a/crates/ruff_python_ast/src/node_index.rs b/crates/ruff_python_ast/src/node_index.rs new file mode 100644 index 00000000000000..d7d4e8614c06ed --- /dev/null +++ b/crates/ruff_python_ast/src/node_index.rs @@ -0,0 +1,98 @@ +use std::sync::atomic::{AtomicU32, Ordering}; + +/// An AST node that has an index. +pub trait HasNodeIndex { + /// Returns the [`AtomicNodeIndex`] for this node. + fn node_index(&self) -> &AtomicNodeIndex; +} + +impl HasNodeIndex for &T +where + T: HasNodeIndex, +{ + fn node_index(&self) -> &AtomicNodeIndex { + T::node_index(*self) + } +} + +/// A unique index for a node within an AST. +/// +/// This type is interiorly mutable to allow assigning node indices +/// on-demand after parsing. +#[derive(Default)] +pub struct AtomicNodeIndex(AtomicU32); + +impl AtomicNodeIndex { + /// Returns a placeholder `AtomicNodeIndex`. + pub fn dummy() -> AtomicNodeIndex { + AtomicNodeIndex(AtomicU32::from(u32::MAX)) + } + + /// Load the current value of the `AtomicNodeIndex`. + pub fn load(&self) -> NodeIndex { + NodeIndex(self.0.load(Ordering::Relaxed)) + } + + /// Set the value of the `AtomicNodeIndex`. + pub fn set(&self, value: u32) { + self.0.store(value, Ordering::Relaxed); + } +} + +/// A unique index for a node within an AST. +#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct NodeIndex(u32); + +impl NodeIndex { + pub fn as_usize(self) -> usize { + self.0 as _ + } +} + +impl From for AtomicNodeIndex { + fn from(value: u32) -> Self { + AtomicNodeIndex(AtomicU32::from(value)) + } +} + +impl std::fmt::Debug for AtomicNodeIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if *self == AtomicNodeIndex::dummy() { + f.debug_tuple("AtomicNodeIndex").finish_non_exhaustive() + } else { + f.debug_tuple("AtomicNodeIndex").field(&self.0).finish() + } + } +} + +impl std::hash::Hash for AtomicNodeIndex { + fn hash(&self, state: &mut H) { + self.load().hash(state); + } +} + +impl PartialOrd for AtomicNodeIndex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AtomicNodeIndex { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.load().cmp(&other.load()) + } +} + +impl Eq for AtomicNodeIndex {} + +impl PartialEq for AtomicNodeIndex { + fn eq(&self, other: &Self) -> bool { + self.load() == other.load() + } +} + +impl Clone for AtomicNodeIndex { + fn clone(&self) -> Self { + Self(AtomicU32::from(self.0.load(Ordering::Relaxed))) + } +} diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 54235b2bbe91a3..9520765197c7fa 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -1,5 +1,6 @@ #![allow(clippy::derive_partial_eq_without_eq)] +use crate::AtomicNodeIndex; use crate::generated::{ ExprBytesLiteral, ExprDict, ExprFString, ExprList, ExprName, ExprSet, ExprStringLiteral, ExprTString, ExprTuple, StmtClassDef, @@ -48,6 +49,7 @@ impl StmtClassDef { #[derive(Clone, Debug, PartialEq)] pub struct ElifElseClause { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub test: Option, pub body: Vec, } @@ -316,6 +318,7 @@ impl<'a> IntoIterator for &'a ExprSet { #[derive(Clone, Debug, PartialEq)] pub struct InterpolatedStringFormatSpec { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub elements: InterpolatedStringElements, } @@ -323,6 +326,7 @@ pub struct InterpolatedStringFormatSpec { #[derive(Clone, Debug, PartialEq)] pub struct InterpolatedElement { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub expression: Box, pub debug_text: Option, pub conversion: ConversionFlag, @@ -333,6 +337,7 @@ pub struct InterpolatedElement { #[derive(Clone, Debug, PartialEq)] pub struct InterpolatedStringLiteralElement { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub value: Box, } @@ -1105,6 +1110,7 @@ impl fmt::Debug for TStringFlags { #[derive(Clone, Debug, PartialEq)] pub struct FString { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub elements: InterpolatedStringElements, pub flags: FStringFlags, } @@ -1112,6 +1118,7 @@ pub struct FString { impl From for Expr { fn from(payload: FString) -> Self { ExprFString { + node_index: payload.node_index.clone(), range: payload.range, value: FStringValue::single(payload), } @@ -1183,6 +1190,7 @@ impl fmt::Debug for InterpolatedStringElements { #[derive(Clone, Debug, PartialEq)] pub struct TString { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub elements: InterpolatedStringElements, pub flags: TStringFlags, } @@ -1190,6 +1198,7 @@ pub struct TString { impl From for Expr { fn from(payload: TString) -> Self { ExprTString { + node_index: payload.node_index.clone(), range: payload.range, value: TStringValue::single(payload), } @@ -1553,6 +1562,7 @@ impl fmt::Debug for StringLiteralFlags { #[derive(Clone, Debug, PartialEq)] pub struct StringLiteral { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub value: Box, pub flags: StringLiteralFlags, } @@ -1576,6 +1586,7 @@ impl StringLiteral { Self { range, value: "".into(), + node_index: AtomicNodeIndex::dummy(), flags: StringLiteralFlags::empty().with_invalid(), } } @@ -1595,6 +1606,7 @@ impl From for Expr { fn from(payload: StringLiteral) -> Self { ExprStringLiteral { range: payload.range, + node_index: AtomicNodeIndex::dummy(), value: StringLiteralValue::single(payload), } .into() @@ -1941,6 +1953,7 @@ impl fmt::Debug for BytesLiteralFlags { #[derive(Clone, Debug, PartialEq)] pub struct BytesLiteral { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub value: Box<[u8]>, pub flags: BytesLiteralFlags, } @@ -1964,6 +1977,7 @@ impl BytesLiteral { Self { range, value: Box::new([]), + node_index: AtomicNodeIndex::dummy(), flags: BytesLiteralFlags::empty().with_invalid(), } } @@ -1973,6 +1987,7 @@ impl From for Expr { fn from(payload: BytesLiteral) -> Self { ExprBytesLiteral { range: payload.range, + node_index: AtomicNodeIndex::dummy(), value: BytesLiteralValue::single(payload), } .into() @@ -2573,6 +2588,7 @@ impl fmt::Display for CmpOp { #[derive(Clone, Debug, PartialEq)] pub struct Comprehension { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub target: Expr, pub iter: Expr, pub ifs: Vec, @@ -2583,6 +2599,7 @@ pub struct Comprehension { #[derive(Clone, Debug, PartialEq)] pub struct ExceptHandlerExceptHandler { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub type_: Option>, pub name: Option, pub body: Vec, @@ -2592,6 +2609,7 @@ pub struct ExceptHandlerExceptHandler { #[derive(Clone, Debug, PartialEq)] pub struct Parameter { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Identifier, pub annotation: Option>, } @@ -2610,6 +2628,7 @@ impl Parameter { #[derive(Clone, Debug, PartialEq)] pub struct Keyword { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub arg: Option, pub value: Expr, } @@ -2618,6 +2637,7 @@ pub struct Keyword { #[derive(Clone, Debug, PartialEq)] pub struct Alias { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Identifier, pub asname: Option, } @@ -2626,6 +2646,7 @@ pub struct Alias { #[derive(Clone, Debug, PartialEq)] pub struct WithItem { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub context_expr: Expr, pub optional_vars: Option>, } @@ -2634,6 +2655,7 @@ pub struct WithItem { #[derive(Clone, Debug, PartialEq)] pub struct MatchCase { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub pattern: Pattern, pub guard: Option>, pub body: Vec, @@ -2654,16 +2676,19 @@ impl Pattern { pattern, name, range, + node_index, }) => match pattern { Some(pattern) => pattern.irrefutable_pattern(), None => match name { Some(name) => Some(IrrefutablePattern { kind: IrrefutablePatternKind::Name(name.id.clone()), range: *range, + node_index: node_index.clone(), }), None => Some(IrrefutablePattern { kind: IrrefutablePatternKind::Wildcard, range: *range, + node_index: node_index.clone(), }), }, }, @@ -2701,6 +2726,7 @@ impl Pattern { pub struct IrrefutablePattern { pub kind: IrrefutablePatternKind, pub range: TextRange, + pub node_index: AtomicNodeIndex, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -2713,6 +2739,7 @@ pub enum IrrefutablePatternKind { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchValue { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub value: Box, } @@ -2720,6 +2747,7 @@ pub struct PatternMatchValue { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchSingleton { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub value: Singleton, } @@ -2727,6 +2755,7 @@ pub struct PatternMatchSingleton { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchSequence { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub patterns: Vec, } @@ -2734,6 +2763,7 @@ pub struct PatternMatchSequence { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchMapping { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub keys: Vec, pub patterns: Vec, pub rest: Option, @@ -2743,6 +2773,7 @@ pub struct PatternMatchMapping { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchClass { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub cls: Box, pub arguments: PatternArguments, } @@ -2754,6 +2785,7 @@ pub struct PatternMatchClass { #[derive(Clone, Debug, PartialEq)] pub struct PatternArguments { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub patterns: Vec, pub keywords: Vec, } @@ -2765,6 +2797,7 @@ pub struct PatternArguments { #[derive(Clone, Debug, PartialEq)] pub struct PatternKeyword { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub attr: Identifier, pub pattern: Pattern, } @@ -2773,6 +2806,7 @@ pub struct PatternKeyword { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchStar { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Option, } @@ -2780,6 +2814,7 @@ pub struct PatternMatchStar { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchAs { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub pattern: Option>, pub name: Option, } @@ -2788,6 +2823,7 @@ pub struct PatternMatchAs { #[derive(Clone, Debug, PartialEq)] pub struct PatternMatchOr { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub patterns: Vec, } @@ -2813,6 +2849,7 @@ impl TypeParam { #[derive(Clone, Debug, PartialEq)] pub struct TypeParamTypeVar { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Identifier, pub bound: Option>, pub default: Option>, @@ -2822,6 +2859,7 @@ pub struct TypeParamTypeVar { #[derive(Clone, Debug, PartialEq)] pub struct TypeParamParamSpec { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Identifier, pub default: Option>, } @@ -2830,6 +2868,7 @@ pub struct TypeParamParamSpec { #[derive(Clone, Debug, PartialEq)] pub struct TypeParamTypeVarTuple { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub name: Identifier, pub default: Option>, } @@ -2838,6 +2877,7 @@ pub struct TypeParamTypeVarTuple { #[derive(Clone, Debug, PartialEq)] pub struct Decorator { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub expression: Expr, } @@ -2911,6 +2951,7 @@ impl Ranged for AnyParameterRef<'_> { #[derive(Clone, Debug, PartialEq, Default)] pub struct Parameters { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub posonlyargs: Vec, pub args: Vec, pub vararg: Option>, @@ -2945,6 +2986,7 @@ impl Parameters { pub fn len(&self) -> usize { let Parameters { range: _, + node_index: _, posonlyargs, args, vararg, @@ -2993,6 +3035,7 @@ impl<'a> ParametersIterator<'a> { fn new(parameters: &'a Parameters) -> Self { let Parameters { range: _, + node_index: _, posonlyargs, args, vararg, @@ -3127,6 +3170,7 @@ impl<'a> IntoIterator for &'a Box { #[derive(Clone, Debug, PartialEq)] pub struct ParameterWithDefault { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub parameter: Parameter, pub default: Option>, } @@ -3170,6 +3214,7 @@ impl ParameterWithDefault { #[derive(Clone, Debug, PartialEq)] pub struct Arguments { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub args: Box<[Expr]>, pub keywords: Box<[Keyword]>, } @@ -3319,6 +3364,7 @@ impl Arguments { #[derive(Clone, Debug, PartialEq)] pub struct TypeParams { pub range: TextRange, + pub node_index: AtomicNodeIndex, pub type_params: Vec, } @@ -3433,6 +3479,7 @@ impl IpyEscapeKind { pub struct Identifier { pub id: Name, pub range: TextRange, + pub node_index: AtomicNodeIndex, } impl Identifier { @@ -3440,6 +3487,7 @@ impl Identifier { pub fn new(id: impl Into, range: TextRange) -> Self { Self { id: id.into(), + node_index: AtomicNodeIndex::dummy(), range, } } @@ -3527,45 +3575,44 @@ mod tests { #[test] #[cfg(target_pointer_width = "64")] fn size() { - assert!(std::mem::size_of::() <= 120); - assert!(std::mem::size_of::() <= 120); - assert!(std::mem::size_of::() <= 104); - assert!(std::mem::size_of::() <= 112); - assert!(std::mem::size_of::() <= 32); - assert!(matches!(std::mem::size_of::(), 88)); - - assert_eq!(std::mem::size_of::(), 64); - assert_eq!(std::mem::size_of::(), 56); - assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 128); + assert_eq!(std::mem::size_of::(), 128); + assert_eq!(std::mem::size_of::(), 120); + assert_eq!(std::mem::size_of::(), 112); + assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 104); + assert_eq!(std::mem::size_of::(), 80); + assert_eq!(std::mem::size_of::(), 64); + assert_eq!(std::mem::size_of::(), 24); assert_eq!(std::mem::size_of::(), 32); assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 12); - assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 56); - assert_eq!(std::mem::size_of::(), 48); - assert_eq!(std::mem::size_of::(), 32); - assert_eq!(std::mem::size_of::(), 48); - assert_eq!(std::mem::size_of::(), 8); - assert!(matches!(std::mem::size_of::(), 48)); + assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 48); + assert_eq!(std::mem::size_of::(), 72); + assert_eq!(std::mem::size_of::(), 56); + assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 56); + assert_eq!(std::mem::size_of::(), 12); + assert_eq!(std::mem::size_of::(), 56); assert_eq!(std::mem::size_of::(), 48); - assert_eq!(std::mem::size_of::(), 32); + assert_eq!(std::mem::size_of::(), 40); assert_eq!(std::mem::size_of::(), 32); - assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::(), 32); assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 48); assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 24); - assert_eq!(std::mem::size_of::(), 8); - assert_eq!(std::mem::size_of::(), 32); - assert_eq!(std::mem::size_of::(), 32); - assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 32); + assert_eq!(std::mem::size_of::(), 32); + assert_eq!(std::mem::size_of::(), 12); + assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 48); + assert_eq!(std::mem::size_of::(), 40); assert_eq!(std::mem::size_of::(), 24); - assert_eq!(std::mem::size_of::(), 56); + assert_eq!(std::mem::size_of::(), 64); assert_eq!(std::mem::size_of::(), 32); assert_eq!(std::mem::size_of::(), 40); assert_eq!(std::mem::size_of::(), 24); - assert_eq!(std::mem::size_of::(), 16); - assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::(), 24); } } diff --git a/crates/ruff_python_ast/src/relocate.rs b/crates/ruff_python_ast/src/relocate.rs index c985003069bd38..eea26d7a37c6cb 100644 --- a/crates/ruff_python_ast/src/relocate.rs +++ b/crates/ruff_python_ast/src/relocate.rs @@ -87,10 +87,10 @@ impl Transformer for Relocator { Expr::BooleanLiteral(ast::ExprBooleanLiteral { range, .. }) => { *range = self.range; } - Expr::NoneLiteral(ast::ExprNoneLiteral { range }) => { + Expr::NoneLiteral(ast::ExprNoneLiteral { range, .. }) => { *range = self.range; } - Expr::EllipsisLiteral(ast::ExprEllipsisLiteral { range }) => { + Expr::EllipsisLiteral(ast::ExprEllipsisLiteral { range, .. }) => { *range = self.range; } Expr::Attribute(ast::ExprAttribute { range, .. }) => { diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index 1e8c5ebf36c387..786a7c643f61cb 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -172,18 +172,27 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { } visitor.visit_body(body); } - Stmt::Return(ast::StmtReturn { value, range: _ }) => { + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => { if let Some(expr) = value { visitor.visit_expr(expr); } } - Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { + Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => { for expr in targets { visitor.visit_expr(expr); } } Stmt::TypeAlias(ast::StmtTypeAlias { range: _, + node_index: _, name, type_params, value, @@ -205,6 +214,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { op, value, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_operator(op); @@ -239,6 +249,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { body, orelse, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_body(body); @@ -249,6 +260,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { body, elif_else_clauses, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_body(body); @@ -269,6 +281,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { subject, cases, range: _, + node_index: _, }) => { visitor.visit_expr(subject); for match_case in cases { @@ -279,6 +292,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { exc, cause, range: _, + node_index: _, }) => { if let Some(expr) = exc { visitor.visit_expr(expr); @@ -294,6 +308,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { finalbody, is_star: _, range: _, + node_index: _, }) => { visitor.visit_body(body); for except_handler in handlers { @@ -306,13 +321,18 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { test, msg, range: _, + node_index: _, }) => { visitor.visit_expr(test); if let Some(expr) = msg { visitor.visit_expr(expr); } } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { for alias in names { visitor.visit_alias(alias); } @@ -324,7 +344,11 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { } Stmt::Global(_) => {} Stmt::Nonlocal(_) => {} - Stmt::Expr(ast::StmtExpr { value, range: _ }) => visitor.visit_expr(value), + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::IpyEscapeCommand(_) => {} } } @@ -343,6 +367,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { op, values, range: _, + node_index: _, }) => { visitor.visit_bool_op(op); for expr in values { @@ -353,6 +378,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { target, value, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr(target); @@ -362,6 +388,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { op, right, range: _, + node_index: _, }) => { visitor.visit_expr(left); visitor.visit_operator(op); @@ -371,6 +398,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { op, operand, range: _, + node_index: _, }) => { visitor.visit_unary_op(op); visitor.visit_expr(operand); @@ -379,6 +407,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { parameters, body, range: _, + node_index: _, }) => { if let Some(parameters) = parameters { visitor.visit_parameters(parameters); @@ -390,12 +419,17 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { body, orelse, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_expr(body); visitor.visit_expr(orelse); } - Expr::Dict(ast::ExprDict { items, range: _ }) => { + Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => { for ast::DictItem { key, value } in items { if let Some(key) = key { visitor.visit_expr(key); @@ -403,7 +437,11 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { visitor.visit_expr(value); } } - Expr::Set(ast::ExprSet { elts, range: _ }) => { + Expr::Set(ast::ExprSet { + elts, + range: _, + node_index: _, + }) => { for expr in elts { visitor.visit_expr(expr); } @@ -412,6 +450,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { elt, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -422,6 +461,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { elt, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -433,6 +473,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { value, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -444,6 +485,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { elt, generators, range: _, + node_index: _, parenthesized: _, }) => { for comprehension in generators { @@ -451,18 +493,31 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { } visitor.visit_expr(elt); } - Expr::Await(ast::ExprAwait { value, range: _ }) => visitor.visit_expr(value), - Expr::Yield(ast::ExprYield { value, range: _ }) => { + Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), + Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => { if let Some(expr) = value { visitor.visit_expr(expr); } } - Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) => visitor.visit_expr(value), + Expr::YieldFrom(ast::ExprYieldFrom { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), Expr::Compare(ast::ExprCompare { left, ops, comparators, range: _, + node_index: _, }) => { visitor.visit_expr(left); for cmp_op in ops { @@ -476,6 +531,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { func, arguments, range: _, + node_index: _, }) => { visitor.visit_expr(func); visitor.visit_arguments(arguments); @@ -524,6 +580,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { slice, ctx, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr(slice); @@ -533,6 +590,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { value, ctx, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr_context(ctx); @@ -544,6 +602,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { elts, ctx, range: _, + node_index: _, }) => { for expr in elts { visitor.visit_expr(expr); @@ -554,6 +613,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { elts, ctx, range: _, + node_index: _, parenthesized: _, }) => { for expr in elts { @@ -566,6 +626,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { upper, step, range: _, + node_index: _, }) => { if let Some(expr) = lower { visitor.visit_expr(expr); @@ -662,6 +723,7 @@ pub fn walk_type_param<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, type_param: default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = bound { visitor.visit_expr(expr); @@ -674,6 +736,7 @@ pub fn walk_type_param<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, type_param: default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { visitor.visit_expr(expr); @@ -683,6 +746,7 @@ pub fn walk_type_param<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, type_param: default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { visitor.visit_expr(expr); diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index 07b098eb43f2c1..4ceebf619a43a9 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -159,18 +159,27 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { } visitor.visit_body(body); } - Stmt::Return(ast::StmtReturn { value, range: _ }) => { + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => { if let Some(expr) = value { visitor.visit_expr(expr); } } - Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { + Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => { for expr in targets { visitor.visit_expr(expr); } } Stmt::TypeAlias(ast::StmtTypeAlias { range: _, + node_index: _, name, type_params, value, @@ -192,6 +201,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { op, value, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_operator(op); @@ -226,6 +236,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { body, orelse, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_body(body); @@ -236,6 +247,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { body, elif_else_clauses, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_body(body); @@ -253,6 +265,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { subject, cases, range: _, + node_index: _, }) => { visitor.visit_expr(subject); for match_case in cases { @@ -263,6 +276,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { exc, cause, range: _, + node_index: _, }) => { if let Some(expr) = exc { visitor.visit_expr(expr); @@ -278,6 +292,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { finalbody, is_star: _, range: _, + node_index: _, }) => { visitor.visit_body(body); for except_handler in handlers { @@ -290,13 +305,18 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { test, msg, range: _, + node_index: _, }) => { visitor.visit_expr(test); if let Some(expr) = msg { visitor.visit_expr(expr); } } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { for alias in names { visitor.visit_alias(alias); } @@ -308,7 +328,11 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { } Stmt::Global(_) => {} Stmt::Nonlocal(_) => {} - Stmt::Expr(ast::StmtExpr { value, range: _ }) => visitor.visit_expr(value), + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::IpyEscapeCommand(_) => {} } } @@ -327,6 +351,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { op, values, range: _, + node_index: _, }) => { visitor.visit_bool_op(op); for expr in values { @@ -337,6 +362,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { target, value, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr(target); @@ -346,6 +372,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { op, right, range: _, + node_index: _, }) => { visitor.visit_expr(left); visitor.visit_operator(op); @@ -355,6 +382,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { op, operand, range: _, + node_index: _, }) => { visitor.visit_unary_op(op); visitor.visit_expr(operand); @@ -363,6 +391,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { parameters, body, range: _, + node_index: _, }) => { if let Some(parameters) = parameters { visitor.visit_parameters(parameters); @@ -374,12 +403,17 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { body, orelse, range: _, + node_index: _, }) => { visitor.visit_expr(test); visitor.visit_expr(body); visitor.visit_expr(orelse); } - Expr::Dict(ast::ExprDict { items, range: _ }) => { + Expr::Dict(ast::ExprDict { + items, + range: _, + node_index: _, + }) => { for ast::DictItem { key, value } in items { if let Some(key) = key { visitor.visit_expr(key); @@ -387,7 +421,11 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { visitor.visit_expr(value); } } - Expr::Set(ast::ExprSet { elts, range: _ }) => { + Expr::Set(ast::ExprSet { + elts, + range: _, + node_index: _, + }) => { for expr in elts { visitor.visit_expr(expr); } @@ -396,6 +434,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { elt, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -406,6 +445,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { elt, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -417,6 +457,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { value, generators, range: _, + node_index: _, }) => { for comprehension in generators { visitor.visit_comprehension(comprehension); @@ -428,6 +469,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { elt, generators, range: _, + node_index: _, parenthesized: _, }) => { for comprehension in generators { @@ -435,18 +477,31 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { } visitor.visit_expr(elt); } - Expr::Await(ast::ExprAwait { value, range: _ }) => visitor.visit_expr(value), - Expr::Yield(ast::ExprYield { value, range: _ }) => { + Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), + Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => { if let Some(expr) = value { visitor.visit_expr(expr); } } - Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) => visitor.visit_expr(value), + Expr::YieldFrom(ast::ExprYieldFrom { + value, + range: _, + node_index: _, + }) => visitor.visit_expr(value), Expr::Compare(ast::ExprCompare { left, ops, comparators, range: _, + node_index: _, }) => { visitor.visit_expr(left); for cmp_op in &mut **ops { @@ -460,6 +515,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { func, arguments, range: _, + node_index: _, }) => { visitor.visit_expr(func); visitor.visit_arguments(arguments); @@ -514,6 +570,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { slice, ctx, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr(slice); @@ -523,6 +580,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { value, ctx, range: _, + node_index: _, }) => { visitor.visit_expr(value); visitor.visit_expr_context(ctx); @@ -534,6 +592,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { elts, ctx, range: _, + node_index: _, }) => { for expr in elts { visitor.visit_expr(expr); @@ -544,6 +603,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { elts, ctx, range: _, + node_index: _, parenthesized: _, }) => { for expr in elts { @@ -556,6 +616,7 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { upper, step, range: _, + node_index: _, }) => { if let Some(expr) = lower { visitor.visit_expr(expr); @@ -670,6 +731,7 @@ pub fn walk_type_param(visitor: &V, type_param: &mut Ty default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = bound { visitor.visit_expr(expr); @@ -682,6 +744,7 @@ pub fn walk_type_param(visitor: &V, type_param: &mut Ty default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { visitor.visit_expr(expr); @@ -691,6 +754,7 @@ pub fn walk_type_param(visitor: &V, type_param: &mut Ty default, name: _, range: _, + node_index: _, }) => { if let Some(expr) = default { visitor.visit_expr(expr); diff --git a/crates/ruff_python_ast_integration_tests/tests/visitor.rs b/crates/ruff_python_ast_integration_tests/tests/visitor.rs index 9cdc7d998a505e..b05655d9ba477e 100644 --- a/crates/ruff_python_ast_integration_tests/tests/visitor.rs +++ b/crates/ruff_python_ast_integration_tests/tests/visitor.rs @@ -178,10 +178,18 @@ where V: Visitor<'a> + ?Sized, { match module { - ast::Mod::Module(ast::ModModule { body, range: _ }) => { + ast::Mod::Module(ast::ModModule { + body, + range: _, + node_index: _, + }) => { visitor.visit_body(body); } - ast::Mod::Expression(ast::ModExpression { body, range: _ }) => visitor.visit_expr(body), + ast::Mod::Expression(ast::ModExpression { + body, + range: _, + node_index: _, + }) => visitor.visit_expr(body), } } diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index 89332057312860..bf6e5b9a81a26b 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -264,6 +264,7 @@ impl<'a> Generator<'a> { decorator_list, type_params, range: _, + node_index: _, }) => { self.newlines(if self.indent_depth == 0 { 2 } else { 1 }); for decorator in decorator_list { @@ -308,7 +309,11 @@ impl<'a> Generator<'a> { self.newlines(2); } } - Stmt::Return(ast::StmtReturn { value, range: _ }) => { + Stmt::Return(ast::StmtReturn { + value, + range: _, + node_index: _, + }) => { statement!({ if let Some(expr) = value { self.p("return "); @@ -318,7 +323,11 @@ impl<'a> Generator<'a> { } }); } - Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { + Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => { statement!({ self.p("del "); let mut first = true; @@ -342,6 +351,7 @@ impl<'a> Generator<'a> { op, value, range: _, + node_index: _, }) => { statement!({ self.unparse_expr(target, precedence::AUG_ASSIGN); @@ -371,6 +381,7 @@ impl<'a> Generator<'a> { value, simple, range: _, + node_index: _, }) => { statement!({ let need_parens = matches!(target.as_ref(), Expr::Name(_)) && !simple; @@ -416,6 +427,7 @@ impl<'a> Generator<'a> { body, orelse, range: _, + node_index: _, }) => { statement!({ self.p("while "); @@ -435,6 +447,7 @@ impl<'a> Generator<'a> { body, elif_else_clauses, range: _, + node_index: _, }) => { statement!({ self.p("if "); @@ -482,6 +495,7 @@ impl<'a> Generator<'a> { subject, cases, range: _, + node_index: _, }) => { statement!({ self.p("match "); @@ -499,6 +513,7 @@ impl<'a> Generator<'a> { Stmt::TypeAlias(ast::StmtTypeAlias { name, range: _, + node_index: _, type_params, value, }) => { @@ -516,6 +531,7 @@ impl<'a> Generator<'a> { exc, cause, range: _, + node_index: _, }) => { statement!({ self.p("raise"); @@ -536,6 +552,7 @@ impl<'a> Generator<'a> { finalbody, is_star, range: _, + node_index: _, }) => { statement!({ self.p("try:"); @@ -565,6 +582,7 @@ impl<'a> Generator<'a> { test, msg, range: _, + node_index: _, }) => { statement!({ self.p("assert "); @@ -575,7 +593,11 @@ impl<'a> Generator<'a> { } }); } - Stmt::Import(ast::StmtImport { names, range: _ }) => { + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => { statement!({ self.p("import "); let mut first = true; @@ -590,6 +612,7 @@ impl<'a> Generator<'a> { names, level, range: _, + node_index: _, }) => { statement!({ self.p("from "); @@ -609,7 +632,11 @@ impl<'a> Generator<'a> { } }); } - Stmt::Global(ast::StmtGlobal { names, range: _ }) => { + Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) => { statement!({ self.p("global "); let mut first = true; @@ -619,7 +646,11 @@ impl<'a> Generator<'a> { } }); } - Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => { + Stmt::Nonlocal(ast::StmtNonlocal { + names, + range: _, + node_index: _, + }) => { statement!({ self.p("nonlocal "); let mut first = true; @@ -629,7 +660,11 @@ impl<'a> Generator<'a> { } }); } - Stmt::Expr(ast::StmtExpr { value, range: _ }) => { + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => { statement!({ self.unparse_expr(value, precedence::EXPR); }); @@ -664,6 +699,7 @@ impl<'a> Generator<'a> { name, body, range: _, + node_index: _, }) => { self.p("except"); if star { @@ -685,13 +721,25 @@ impl<'a> Generator<'a> { fn unparse_pattern(&mut self, ast: &Pattern) { match ast { - Pattern::MatchValue(ast::PatternMatchValue { value, range: _ }) => { + Pattern::MatchValue(ast::PatternMatchValue { + value, + range: _, + node_index: _, + }) => { self.unparse_expr(value, precedence::MAX); } - Pattern::MatchSingleton(ast::PatternMatchSingleton { value, range: _ }) => { + Pattern::MatchSingleton(ast::PatternMatchSingleton { + value, + range: _, + node_index: _, + }) => { self.unparse_singleton(*value); } - Pattern::MatchSequence(ast::PatternMatchSequence { patterns, range: _ }) => { + Pattern::MatchSequence(ast::PatternMatchSequence { + patterns, + range: _, + node_index: _, + }) => { self.p("["); let mut first = true; for pattern in patterns { @@ -705,6 +753,7 @@ impl<'a> Generator<'a> { patterns, rest, range: _, + node_index: _, }) => { self.p("{"); let mut first = true; @@ -722,7 +771,11 @@ impl<'a> Generator<'a> { self.p("}"); } Pattern::MatchClass(_) => {} - Pattern::MatchStar(ast::PatternMatchStar { name, range: _ }) => { + Pattern::MatchStar(ast::PatternMatchStar { + name, + range: _, + node_index: _, + }) => { self.p("*"); if let Some(name) = name { self.p_id(name); @@ -734,6 +787,7 @@ impl<'a> Generator<'a> { pattern, name, range: _, + node_index: _, }) => { if let Some(pattern) = pattern { self.unparse_pattern(pattern); @@ -745,7 +799,11 @@ impl<'a> Generator<'a> { self.p("_"); } } - Pattern::MatchOr(ast::PatternMatchOr { patterns, range: _ }) => { + Pattern::MatchOr(ast::PatternMatchOr { + patterns, + range: _, + node_index: _, + }) => { let mut first = true; for pattern in patterns { self.p_delim(&mut first, " | "); @@ -841,6 +899,7 @@ impl<'a> Generator<'a> { op, values, range: _, + node_index: _, }) => { let (op, prec) = opprec!(bin, op, BoolOp, And("and", AND), Or("or", OR)); group_if!(prec, { @@ -855,6 +914,7 @@ impl<'a> Generator<'a> { target, value, range: _, + node_index: _, }) => { group_if!(precedence::NAMED_EXPR, { self.unparse_expr(target, precedence::NAMED_EXPR); @@ -867,6 +927,7 @@ impl<'a> Generator<'a> { op, right, range: _, + node_index: _, }) => { let rassoc = matches!(op, Operator::Pow); let (op, prec) = opprec!( @@ -897,6 +958,7 @@ impl<'a> Generator<'a> { op, operand, range: _, + node_index: _, }) => { let (op, prec) = opprec!( un, @@ -916,6 +978,7 @@ impl<'a> Generator<'a> { parameters, body, range: _, + node_index: _, }) => { group_if!(precedence::LAMBDA, { self.p("lambda"); @@ -932,6 +995,7 @@ impl<'a> Generator<'a> { body, orelse, range: _, + node_index: _, }) => { group_if!(precedence::IF_EXP, { self.unparse_expr(body, precedence::IF_EXP + 1); @@ -974,6 +1038,7 @@ impl<'a> Generator<'a> { elt, generators, range: _, + node_index: _, }) => { self.p("["); self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT); @@ -984,6 +1049,7 @@ impl<'a> Generator<'a> { elt, generators, range: _, + node_index: _, }) => { self.p("{"); self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT); @@ -995,6 +1061,7 @@ impl<'a> Generator<'a> { value, generators, range: _, + node_index: _, }) => { self.p("{"); self.unparse_expr(key, precedence::COMPREHENSION_ELEMENT); @@ -1008,19 +1075,28 @@ impl<'a> Generator<'a> { generators, parenthesized: _, range: _, + node_index: _, }) => { self.p("("); self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT); self.unparse_comp(generators); self.p(")"); } - Expr::Await(ast::ExprAwait { value, range: _ }) => { + Expr::Await(ast::ExprAwait { + value, + range: _, + node_index: _, + }) => { group_if!(precedence::AWAIT, { self.p("await "); self.unparse_expr(value, precedence::MAX); }); } - Expr::Yield(ast::ExprYield { value, range: _ }) => { + Expr::Yield(ast::ExprYield { + value, + range: _, + node_index: _, + }) => { group_if!(precedence::YIELD, { self.p("yield"); if let Some(value) = value { @@ -1029,7 +1105,11 @@ impl<'a> Generator<'a> { } }); } - Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) => { + Expr::YieldFrom(ast::ExprYieldFrom { + value, + range: _, + node_index: _, + }) => { group_if!(precedence::YIELD_FROM, { self.p("yield from "); self.unparse_expr(value, precedence::MAX); @@ -1040,6 +1120,7 @@ impl<'a> Generator<'a> { ops, comparators, range: _, + node_index: _, }) => { group_if!(precedence::CMP, { let new_lvl = precedence::CMP + 1; @@ -1066,6 +1147,7 @@ impl<'a> Generator<'a> { func, arguments, range: _, + node_index: _, }) => { self.unparse_expr(func, precedence::MAX); self.p("("); @@ -1075,6 +1157,7 @@ impl<'a> Generator<'a> { elt, generators, range: _, + node_index: _, parenthesized: _, }), ], @@ -1217,6 +1300,7 @@ impl<'a> Generator<'a> { upper, step, range: _, + node_index: _, }) => { if let Some(lower) = lower { self.unparse_expr(lower, precedence::SLICE); @@ -1396,6 +1480,7 @@ impl<'a> Generator<'a> { conversion, format_spec, range: _, + node_index: _, }) => self.unparse_interpolated_element( expression, debug_text.as_ref(), diff --git a/crates/ruff_python_formatter/CONTRIBUTING.md b/crates/ruff_python_formatter/CONTRIBUTING.md index 3084ec3607dccd..f0e60974a49c13 100644 --- a/crates/ruff_python_formatter/CONTRIBUTING.md +++ b/crates/ruff_python_formatter/CONTRIBUTING.md @@ -204,7 +204,7 @@ impl FormatNodeRule for FormatStmtReturn { fn fmt_fields(&self, item: &StmtReturn, f: &mut PyFormatter) -> FormatResult<()> { // Here we destructure item and make sure each field is listed. // We generally don't need range if it's underscore-ignored - let StmtReturn { range: _, value } = item; + let StmtReturn { range: _, node_index: _, value } = item; // Implement some formatting logic, in this case no space (and no value) after a return with // no value if let Some(value) = value { diff --git a/crates/ruff_python_formatter/src/comments/debug.rs b/crates/ruff_python_formatter/src/comments/debug.rs index a2038669b42822..8cd374464f4a88 100644 --- a/crates/ruff_python_formatter/src/comments/debug.rs +++ b/crates/ruff_python_formatter/src/comments/debug.rs @@ -184,7 +184,7 @@ mod tests { use insta::assert_debug_snapshot; use ruff_formatter::SourceCode; - use ruff_python_ast::AnyNodeRef; + use ruff_python_ast::{AnyNodeRef, AtomicNodeIndex}; use ruff_python_ast::{StmtBreak, StmtContinue}; use ruff_python_trivia::{CommentLinePosition, CommentRanges}; use ruff_text_size::{TextRange, TextSize}; @@ -196,10 +196,12 @@ mod tests { fn debug() { let continue_statement = StmtContinue { range: TextRange::new(TextSize::new(18), TextSize::new(26)), + node_index: AtomicNodeIndex::dummy(), }; let break_statement = StmtBreak { range: TextRange::new(TextSize::new(55), TextSize::new(60)), + node_index: AtomicNodeIndex::dummy(), }; let source = r"# leading comment diff --git a/crates/ruff_python_formatter/src/comments/node_key.rs b/crates/ruff_python_formatter/src/comments/node_key.rs index 5c38ac10e5f9fe..a0c28ea2866068 100644 --- a/crates/ruff_python_formatter/src/comments/node_key.rs +++ b/crates/ruff_python_formatter/src/comments/node_key.rs @@ -53,6 +53,7 @@ impl<'a> From> for NodeRefEqualityKey<'a> { mod tests { use crate::comments::node_key::NodeRefEqualityKey; use ruff_python_ast::AnyNodeRef; + use ruff_python_ast::AtomicNodeIndex; use ruff_python_ast::StmtContinue; use ruff_text_size::TextRange; use std::collections::hash_map::DefaultHasher; @@ -68,6 +69,7 @@ mod tests { fn equality() { let continue_statement = StmtContinue { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }; let ref_a = NodeRefEqualityKey::from_ref(AnyNodeRef::from(&continue_statement)); @@ -81,6 +83,7 @@ mod tests { fn inequality() { let continue_statement = StmtContinue { range: TextRange::default(), + node_index: AtomicNodeIndex::dummy(), }; let boxed = Box::new(continue_statement.clone()); diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 23cc3ee9966489..63075a7a0a7b1f 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1067,6 +1067,7 @@ fn handle_slice_comments<'a>( ) -> CommentPlacement<'a> { let ast::ExprSlice { range: _, + node_index: _, lower, upper, step, @@ -1450,6 +1451,7 @@ fn handle_expr_if_comment<'a>( ) -> CommentPlacement<'a> { let ast::ExprIf { range: _, + node_index: _, test, body, orelse, diff --git a/crates/ruff_python_formatter/src/expression/expr_attribute.rs b/crates/ruff_python_formatter/src/expression/expr_attribute.rs index 3d1d6ddb786edb..ebb0093ccb28ea 100644 --- a/crates/ruff_python_formatter/src/expression/expr_attribute.rs +++ b/crates/ruff_python_formatter/src/expression/expr_attribute.rs @@ -30,6 +30,7 @@ impl FormatNodeRule for FormatExprAttribute { let ExprAttribute { value, range: _, + node_index: _, attr, ctx: _, } = item; @@ -188,7 +189,12 @@ impl NeedsParentheses for ExprAttribute { // Non Hex, octal or binary number literals need parentheses to disambiguate the attribute `.` from // a decimal point. Floating point numbers don't strictly need parentheses but it reads better (rather than 0.0.test()). fn is_base_ten_number_literal(expr: &Expr, source: &str) -> bool { - if let Some(ExprNumberLiteral { value, range }) = expr.as_number_literal_expr() { + if let Some(ExprNumberLiteral { + value, + range, + node_index: _, + }) = expr.as_number_literal_expr() + { match value { Number::Float(_) => true, Number::Int(_) => { diff --git a/crates/ruff_python_formatter/src/expression/expr_await.rs b/crates/ruff_python_formatter/src/expression/expr_await.rs index 3985e87d67c809..cd862f2f9afe6a 100644 --- a/crates/ruff_python_formatter/src/expression/expr_await.rs +++ b/crates/ruff_python_formatter/src/expression/expr_await.rs @@ -13,7 +13,11 @@ pub struct FormatExprAwait; impl FormatNodeRule for FormatExprAwait { fn fmt_fields(&self, item: &ExprAwait, f: &mut PyFormatter) -> FormatResult<()> { - let ExprAwait { range: _, value } = item; + let ExprAwait { + range: _, + node_index: _, + value, + } = item; write!( f, diff --git a/crates/ruff_python_formatter/src/expression/expr_call.rs b/crates/ruff_python_formatter/src/expression/expr_call.rs index 3fc156331f969b..a5c3227a7d3ba8 100644 --- a/crates/ruff_python_formatter/src/expression/expr_call.rs +++ b/crates/ruff_python_formatter/src/expression/expr_call.rs @@ -27,6 +27,7 @@ impl FormatNodeRule for FormatExprCall { fn fmt_fields(&self, item: &ExprCall, f: &mut PyFormatter) -> FormatResult<()> { let ExprCall { range: _, + node_index: _, func, arguments, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_dict.rs b/crates/ruff_python_formatter/src/expression/expr_dict.rs index 41f9804a4f4a8e..9e5b4f111225bc 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict.rs @@ -13,7 +13,11 @@ pub struct FormatExprDict; impl FormatNodeRule for FormatExprDict { fn fmt_fields(&self, item: &ExprDict, f: &mut PyFormatter) -> FormatResult<()> { - let ExprDict { range: _, items } = item; + let ExprDict { + range: _, + node_index: _, + items, + } = item; let comments = f.context().comments().clone(); let dangling = comments.dangling(item); diff --git a/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs b/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs index 793579b1d091dc..91eb3183633abf 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatExprDictComp { fn fmt_fields(&self, item: &ExprDictComp, f: &mut PyFormatter) -> FormatResult<()> { let ExprDictComp { range: _, + node_index: _, key, value, generators, diff --git a/crates/ruff_python_formatter/src/expression/expr_generator.rs b/crates/ruff_python_formatter/src/expression/expr_generator.rs index c6c92dfa724f9d..db0cd28ef20cad 100644 --- a/crates/ruff_python_formatter/src/expression/expr_generator.rs +++ b/crates/ruff_python_formatter/src/expression/expr_generator.rs @@ -37,6 +37,7 @@ impl FormatNodeRule for FormatExprGenerator { fn fmt_fields(&self, item: &ExprGenerator, f: &mut PyFormatter) -> FormatResult<()> { let ExprGenerator { range: _, + node_index: _, elt, generators, parenthesized: is_parenthesized, diff --git a/crates/ruff_python_formatter/src/expression/expr_if.rs b/crates/ruff_python_formatter/src/expression/expr_if.rs index 15be436c6ac9f0..8dfa92d885115f 100644 --- a/crates/ruff_python_formatter/src/expression/expr_if.rs +++ b/crates/ruff_python_formatter/src/expression/expr_if.rs @@ -46,6 +46,7 @@ impl FormatNodeRule for FormatExprIf { fn fmt_fields(&self, item: &ExprIf, f: &mut PyFormatter) -> FormatResult<()> { let ExprIf { range: _, + node_index: _, test, body, orelse, diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 6d4c684585a71c..c5890fba248c0c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatExprLambda { fn fmt_fields(&self, item: &ExprLambda, f: &mut PyFormatter) -> FormatResult<()> { let ExprLambda { range: _, + node_index: _, parameters, body, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_list.rs b/crates/ruff_python_formatter/src/expression/expr_list.rs index 23586bf1a95318..4e295001d3865b 100644 --- a/crates/ruff_python_formatter/src/expression/expr_list.rs +++ b/crates/ruff_python_formatter/src/expression/expr_list.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatExprList { fn fmt_fields(&self, item: &ExprList, f: &mut PyFormatter) -> FormatResult<()> { let ExprList { range: _, + node_index: _, elts, ctx: _, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_list_comp.rs b/crates/ruff_python_formatter/src/expression/expr_list_comp.rs index b896c0a6a36200..ba36b4b66eb22e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_list_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_list_comp.rs @@ -12,6 +12,7 @@ impl FormatNodeRule for FormatExprListComp { fn fmt_fields(&self, item: &ExprListComp, f: &mut PyFormatter) -> FormatResult<()> { let ExprListComp { range: _, + node_index: _, elt, generators, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_name.rs b/crates/ruff_python_formatter/src/expression/expr_name.rs index 5a8b6b2665089e..f9c4534722ce5b 100644 --- a/crates/ruff_python_formatter/src/expression/expr_name.rs +++ b/crates/ruff_python_formatter/src/expression/expr_name.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatExprName { id: _, range, ctx: _, + node_index: _, } = item; write!(f, [source_text_slice(*range)]) } diff --git a/crates/ruff_python_formatter/src/expression/expr_named.rs b/crates/ruff_python_formatter/src/expression/expr_named.rs index 5a5fa23ca32202..117e42825ebb5d 100644 --- a/crates/ruff_python_formatter/src/expression/expr_named.rs +++ b/crates/ruff_python_formatter/src/expression/expr_named.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatExprNamed { target, value, range: _, + node_index: _, } = item; // This context, a dangling comment is a comment between the `:=` and the value. diff --git a/crates/ruff_python_formatter/src/expression/expr_set.rs b/crates/ruff_python_formatter/src/expression/expr_set.rs index d034e0c4fd66ad..45d7f2f1701875 100644 --- a/crates/ruff_python_formatter/src/expression/expr_set.rs +++ b/crates/ruff_python_formatter/src/expression/expr_set.rs @@ -10,7 +10,11 @@ pub struct FormatExprSet; impl FormatNodeRule for FormatExprSet { fn fmt_fields(&self, item: &ExprSet, f: &mut PyFormatter) -> FormatResult<()> { - let ExprSet { range: _, elts } = item; + let ExprSet { + range: _, + node_index: _, + elts, + } = item; // That would be a dict expression assert!(!elts.is_empty()); // Avoid second mutable borrow of f diff --git a/crates/ruff_python_formatter/src/expression/expr_set_comp.rs b/crates/ruff_python_formatter/src/expression/expr_set_comp.rs index 4d37f07928a9a1..adbe2d9671f2c3 100644 --- a/crates/ruff_python_formatter/src/expression/expr_set_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_set_comp.rs @@ -12,6 +12,7 @@ impl FormatNodeRule for FormatExprSetComp { fn fmt_fields(&self, item: &ExprSetComp, f: &mut PyFormatter) -> FormatResult<()> { let ExprSetComp { range: _, + node_index: _, elt, generators, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_slice.rs b/crates/ruff_python_formatter/src/expression/expr_slice.rs index 061874642befcf..4f4b0f001dc413 100644 --- a/crates/ruff_python_formatter/src/expression/expr_slice.rs +++ b/crates/ruff_python_formatter/src/expression/expr_slice.rs @@ -21,6 +21,7 @@ impl FormatNodeRule for FormatExprSlice { upper, step, range, + node_index: _, } = item; let (first_colon, second_colon) = find_colons( @@ -232,6 +233,7 @@ pub(crate) fn assign_comment_in_slice( upper, step: _, range, + node_index: _, } = expr_slice; let (first_colon, second_colon) = diff --git a/crates/ruff_python_formatter/src/expression/expr_starred.rs b/crates/ruff_python_formatter/src/expression/expr_starred.rs index 07fa030260572a..57dd3c7affb8ee 100644 --- a/crates/ruff_python_formatter/src/expression/expr_starred.rs +++ b/crates/ruff_python_formatter/src/expression/expr_starred.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatExprStarred { fn fmt_fields(&self, item: &ExprStarred, f: &mut PyFormatter) -> FormatResult<()> { let ExprStarred { range: _, + node_index: _, value, ctx: _, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_subscript.rs b/crates/ruff_python_formatter/src/expression/expr_subscript.rs index 0c058d7326ba40..ce3aaf1f69c904 100644 --- a/crates/ruff_python_formatter/src/expression/expr_subscript.rs +++ b/crates/ruff_python_formatter/src/expression/expr_subscript.rs @@ -27,6 +27,7 @@ impl FormatNodeRule for FormatExprSubscript { fn fmt_fields(&self, item: &ExprSubscript, f: &mut PyFormatter) -> FormatResult<()> { let ExprSubscript { range: _, + node_index: _, value, slice, ctx: _, diff --git a/crates/ruff_python_formatter/src/expression/expr_tuple.rs b/crates/ruff_python_formatter/src/expression/expr_tuple.rs index 3372ec219d1a83..6d9504867329d4 100644 --- a/crates/ruff_python_formatter/src/expression/expr_tuple.rs +++ b/crates/ruff_python_formatter/src/expression/expr_tuple.rs @@ -116,6 +116,7 @@ impl FormatNodeRule for FormatExprTuple { elts, ctx: _, range: _, + node_index: _, parenthesized: is_parenthesized, } = item; diff --git a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs index e28bfd4a95adc8..a8454cda1eb41c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs +++ b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatExprUnaryOp { fn fmt_fields(&self, item: &ExprUnaryOp, f: &mut PyFormatter) -> FormatResult<()> { let ExprUnaryOp { range: _, + node_index: _, op, operand, } = item; diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 025c4e75139fe7..a320a1edf54b96 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -685,6 +685,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { #[expect(clippy::cast_possible_truncation)] Expr::BoolOp(ast::ExprBoolOp { range: _, + node_index: _, op: _, values, }) => self.update_max_precedence_with_count( @@ -696,6 +697,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { left: _, right: _, range: _, + node_index: _, }) => self.update_max_precedence(OperatorPrecedence::from(*op)), Expr::If(_) => { @@ -708,6 +710,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { #[expect(clippy::cast_possible_truncation)] Expr::Compare(ast::ExprCompare { range: _, + node_index: _, left: _, ops, comparators: _, @@ -719,6 +722,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { } Expr::Call(ast::ExprCall { range: _, + node_index: _, func, arguments: _, }) => { @@ -740,6 +744,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { // `[a, b].test.test[300].dot` Expr::Attribute(ast::ExprAttribute { range: _, + node_index: _, value, attr: _, ctx: _, @@ -760,6 +765,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { // Visit the sub-expressions because the sub expressions may be the end of the entire expression. Expr::UnaryOp(ast::ExprUnaryOp { range: _, + node_index: _, op, operand: _, }) => { diff --git a/crates/ruff_python_formatter/src/module/mod_expression.rs b/crates/ruff_python_formatter/src/module/mod_expression.rs index 79822a2dbb0173..7a0664f4f8cfa9 100644 --- a/crates/ruff_python_formatter/src/module/mod_expression.rs +++ b/crates/ruff_python_formatter/src/module/mod_expression.rs @@ -7,7 +7,11 @@ pub struct FormatModExpression; impl FormatNodeRule for FormatModExpression { fn fmt_fields(&self, item: &ModExpression, f: &mut PyFormatter) -> FormatResult<()> { - let ModExpression { body, range: _ } = item; + let ModExpression { + body, + range: _, + node_index: _, + } = item; body.format().fmt(f) } } diff --git a/crates/ruff_python_formatter/src/module/mod_module.rs b/crates/ruff_python_formatter/src/module/mod_module.rs index 5b3436c6d1aee0..f24eb904291596 100644 --- a/crates/ruff_python_formatter/src/module/mod_module.rs +++ b/crates/ruff_python_formatter/src/module/mod_module.rs @@ -11,7 +11,11 @@ pub struct FormatModModule; impl FormatNodeRule for FormatModModule { fn fmt_fields(&self, item: &ModModule, f: &mut PyFormatter) -> FormatResult<()> { - let ModModule { range, body } = item; + let ModModule { + range, + body, + node_index: _, + } = item; if body.is_empty() { // Only preserve an empty line if the source contains an empty line too. diff --git a/crates/ruff_python_formatter/src/other/alias.rs b/crates/ruff_python_formatter/src/other/alias.rs index f8532d22d4b209..bce6485e124926 100644 --- a/crates/ruff_python_formatter/src/other/alias.rs +++ b/crates/ruff_python_formatter/src/other/alias.rs @@ -11,6 +11,7 @@ impl FormatNodeRule for FormatAlias { fn fmt_fields(&self, item: &Alias, f: &mut PyFormatter) -> FormatResult<()> { let Alias { range: _, + node_index: _, name, asname, } = item; diff --git a/crates/ruff_python_formatter/src/other/arguments.rs b/crates/ruff_python_formatter/src/other/arguments.rs index 0ee77b790b0f54..758deaeeb7d910 100644 --- a/crates/ruff_python_formatter/src/other/arguments.rs +++ b/crates/ruff_python_formatter/src/other/arguments.rs @@ -19,6 +19,7 @@ impl FormatNodeRule for FormatArguments { range, args, keywords, + node_index: _, } = item; // We have a case with `f()` without any argument, which is a special case because we can // have a comment with no node attachment inside: diff --git a/crates/ruff_python_formatter/src/other/comprehension.rs b/crates/ruff_python_formatter/src/other/comprehension.rs index 75910cdc553414..cfa75b3e881a5f 100644 --- a/crates/ruff_python_formatter/src/other/comprehension.rs +++ b/crates/ruff_python_formatter/src/other/comprehension.rs @@ -52,6 +52,7 @@ impl FormatNodeRule for FormatComprehension { let Comprehension { range: _, + node_index: _, target, iter, ifs, diff --git a/crates/ruff_python_formatter/src/other/decorator.rs b/crates/ruff_python_formatter/src/other/decorator.rs index 38e534d9048bb6..0c1719b99166fe 100644 --- a/crates/ruff_python_formatter/src/other/decorator.rs +++ b/crates/ruff_python_formatter/src/other/decorator.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatDecorator { let Decorator { expression, range: _, + node_index: _, } = item; write!( diff --git a/crates/ruff_python_formatter/src/other/except_handler_except_handler.rs b/crates/ruff_python_formatter/src/other/except_handler_except_handler.rs index e638dc4b2a4df6..4f2f93a3e934a4 100644 --- a/crates/ruff_python_formatter/src/other/except_handler_except_handler.rs +++ b/crates/ruff_python_formatter/src/other/except_handler_except_handler.rs @@ -42,6 +42,7 @@ impl FormatNodeRule for FormatExceptHandlerExceptHan let except_handler_kind = self.except_handler_kind; let ExceptHandlerExceptHandler { range: _, + node_index: _, type_, name, body, diff --git a/crates/ruff_python_formatter/src/other/keyword.rs b/crates/ruff_python_formatter/src/other/keyword.rs index 6c568832423fb1..df00ed94ccb585 100644 --- a/crates/ruff_python_formatter/src/other/keyword.rs +++ b/crates/ruff_python_formatter/src/other/keyword.rs @@ -10,6 +10,7 @@ impl FormatNodeRule for FormatKeyword { fn fmt_fields(&self, item: &Keyword, f: &mut PyFormatter) -> FormatResult<()> { let Keyword { range: _, + node_index: _, arg, value, } = item; diff --git a/crates/ruff_python_formatter/src/other/match_case.rs b/crates/ruff_python_formatter/src/other/match_case.rs index 2c96a1546c55e0..130efd01cdc538 100644 --- a/crates/ruff_python_formatter/src/other/match_case.rs +++ b/crates/ruff_python_formatter/src/other/match_case.rs @@ -26,6 +26,7 @@ impl FormatNodeRule for FormatMatchCase { fn fmt_fields(&self, item: &MatchCase, f: &mut PyFormatter) -> FormatResult<()> { let MatchCase { range: _, + node_index: _, pattern, guard, body, diff --git a/crates/ruff_python_formatter/src/other/parameter.rs b/crates/ruff_python_formatter/src/other/parameter.rs index 8a634928fcf6da..483280082eb6c7 100644 --- a/crates/ruff_python_formatter/src/other/parameter.rs +++ b/crates/ruff_python_formatter/src/other/parameter.rs @@ -9,6 +9,7 @@ impl FormatNodeRule for FormatParameter { fn fmt_fields(&self, item: &Parameter, f: &mut PyFormatter) -> FormatResult<()> { let Parameter { range: _, + node_index: _, name, annotation, } = item; diff --git a/crates/ruff_python_formatter/src/other/parameter_with_default.rs b/crates/ruff_python_formatter/src/other/parameter_with_default.rs index 3248d7381f850a..a78c082a00dd2b 100644 --- a/crates/ruff_python_formatter/src/other/parameter_with_default.rs +++ b/crates/ruff_python_formatter/src/other/parameter_with_default.rs @@ -12,6 +12,7 @@ impl FormatNodeRule for FormatParameterWithDefault { fn fmt_fields(&self, item: &ParameterWithDefault, f: &mut PyFormatter) -> FormatResult<()> { let ParameterWithDefault { range: _, + node_index: _, parameter, default, } = item; diff --git a/crates/ruff_python_formatter/src/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index c677d7651e96ac..1c6682bab15cf0 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -49,6 +49,7 @@ impl FormatNodeRule for FormatParameters { fn fmt_fields(&self, item: &Parameters, f: &mut PyFormatter) -> FormatResult<()> { let Parameters { range: _, + node_index: _, posonlyargs, args, vararg, diff --git a/crates/ruff_python_formatter/src/other/with_item.rs b/crates/ruff_python_formatter/src/other/with_item.rs index 6a03b299a48bcd..a309edd6e16418 100644 --- a/crates/ruff_python_formatter/src/other/with_item.rs +++ b/crates/ruff_python_formatter/src/other/with_item.rs @@ -94,6 +94,7 @@ impl FormatNodeRule for FormatWithItem { fn fmt_fields(&self, item: &WithItem, f: &mut PyFormatter) -> FormatResult<()> { let WithItem { range: _, + node_index: _, context_expr, optional_vars, } = item; diff --git a/crates/ruff_python_formatter/src/pattern/pattern_keyword.rs b/crates/ruff_python_formatter/src/pattern/pattern_keyword.rs index 974e6534743cde..3f3aa6efa9c507 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_keyword.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_keyword.rs @@ -9,6 +9,7 @@ impl FormatNodeRule for FormatPatternKeyword { fn fmt_fields(&self, item: &PatternKeyword, f: &mut PyFormatter) -> FormatResult<()> { let PatternKeyword { range: _, + node_index: _, attr, pattern, } = item; diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs index 68958cf02dbd7a..f313bf0309afcb 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs @@ -13,6 +13,7 @@ impl FormatNodeRule for FormatPatternMatchAs { fn fmt_fields(&self, item: &PatternMatchAs, f: &mut PyFormatter) -> FormatResult<()> { let PatternMatchAs { range: _, + node_index: _, pattern, name, } = item; diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs index d516940302435c..ce1583049406b8 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs @@ -13,6 +13,7 @@ impl FormatNodeRule for FormatPatternMatchClass { fn fmt_fields(&self, item: &PatternMatchClass, f: &mut PyFormatter) -> FormatResult<()> { let PatternMatchClass { range: _, + node_index: _, cls, arguments, } = item; diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs index 9ce8f7911ecd5c..cb5568f707035f 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs @@ -21,6 +21,7 @@ impl FormatNodeRule for FormatPatternMatchMapping { patterns, rest, range: _, + node_index: _, } = item; debug_assert_eq!(keys.len(), patterns.len()); @@ -163,6 +164,7 @@ fn find_double_star(pattern: &PatternMatchMapping, source: &str) -> Option<(Text patterns, rest, range: _, + node_index: _, } = pattern; // If there's no `rest` element, there's no `**`. diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs index d9653a81d42892..b0b21f49e902a8 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs @@ -14,7 +14,11 @@ pub struct FormatPatternMatchOr; impl FormatNodeRule for FormatPatternMatchOr { fn fmt_fields(&self, item: &PatternMatchOr, f: &mut PyFormatter) -> FormatResult<()> { - let PatternMatchOr { range: _, patterns } = item; + let PatternMatchOr { + range: _, + node_index: _, + patterns, + } = item; let inner = format_with(|f: &mut PyFormatter| { let mut patterns = patterns.iter(); let comments = f.context().comments().clone(); diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs index 24f07f050d9e46..0625f78a5e64ef 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs @@ -14,7 +14,11 @@ pub struct FormatPatternMatchSequence; impl FormatNodeRule for FormatPatternMatchSequence { fn fmt_fields(&self, item: &PatternMatchSequence, f: &mut PyFormatter) -> FormatResult<()> { - let PatternMatchSequence { patterns, range } = item; + let PatternMatchSequence { + patterns, + range, + node_index: _, + } = item; let comments = f.context().comments().clone(); let dangling = comments.dangling(item); diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs index 5f8efadd9e960b..848b7c4a5d5336 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs @@ -9,7 +9,11 @@ pub struct FormatPatternMatchValue; impl FormatNodeRule for FormatPatternMatchValue { fn fmt_fields(&self, item: &PatternMatchValue, f: &mut PyFormatter) -> FormatResult<()> { - let PatternMatchValue { value, range: _ } = item; + let PatternMatchValue { + value, + range: _, + node_index: _, + } = item; value.format().with_options(Parentheses::Never).fmt(f) } } diff --git a/crates/ruff_python_formatter/src/range.rs b/crates/ruff_python_formatter/src/range.rs index 61043061b5dd4f..436c1a12c2f946 100644 --- a/crates/ruff_python_formatter/src/range.rs +++ b/crates/ruff_python_formatter/src/range.rs @@ -369,6 +369,7 @@ impl SourceOrderVisitor<'_> for NarrowRange<'_> { subject: _, cases, range: _, + node_index: _, }) => { if let Some(saved_state) = self.enter_level(cases.first().map(AnyNodeRef::from)) { for match_case in cases { @@ -387,6 +388,7 @@ impl SourceOrderVisitor<'_> for NarrowRange<'_> { finalbody, is_star: _, range: _, + node_index: _, }) => { self.visit_body(body); if let Some(except_handler_saved) = diff --git a/crates/ruff_python_formatter/src/statement/clause.rs b/crates/ruff_python_formatter/src/statement/clause.rs index 39b70254497fdc..7cc82ca923b08f 100644 --- a/crates/ruff_python_formatter/src/statement/clause.rs +++ b/crates/ruff_python_formatter/src/statement/clause.rs @@ -87,6 +87,7 @@ impl ClauseHeader<'_> { type_params, arguments, range: _, + node_index: _, decorator_list: _, name: _, body: _, @@ -103,6 +104,7 @@ impl ClauseHeader<'_> { type_params, parameters, range: _, + node_index: _, is_async: _, decorator_list: _, name: _, @@ -121,6 +123,7 @@ impl ClauseHeader<'_> { ClauseHeader::If(StmtIf { test, range: _, + node_index: _, body: _, elif_else_clauses: _, }) => { @@ -129,6 +132,7 @@ impl ClauseHeader<'_> { ClauseHeader::ElifElse(ElifElseClause { test, range: _, + node_index: _, body: _, }) => { if let Some(test) = test.as_ref() { @@ -139,6 +143,7 @@ impl ClauseHeader<'_> { ClauseHeader::ExceptHandler(ExceptHandlerExceptHandler { type_: type_expr, range: _, + node_index: _, name: _, body: _, }) => { @@ -149,6 +154,7 @@ impl ClauseHeader<'_> { ClauseHeader::Match(StmtMatch { subject, range: _, + node_index: _, cases: _, }) => { visit(subject.as_ref(), visitor); @@ -157,6 +163,7 @@ impl ClauseHeader<'_> { guard, pattern, range: _, + node_index: _, body: _, }) => { visit(pattern, visitor); @@ -169,6 +176,7 @@ impl ClauseHeader<'_> { target, iter, range: _, + node_index: _, is_async: _, body: _, orelse: _, @@ -179,6 +187,7 @@ impl ClauseHeader<'_> { ClauseHeader::While(StmtWhile { test, range: _, + node_index: _, body: _, orelse: _, }) => { @@ -187,6 +196,7 @@ impl ClauseHeader<'_> { ClauseHeader::With(StmtWith { items, range: _, + node_index: _, is_async: _, body: _, }) => { diff --git a/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs index a51e668f3e7d87..1de79a6b07eab6 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatStmtAnnAssign { fn fmt_fields(&self, item: &StmtAnnAssign, f: &mut PyFormatter) -> FormatResult<()> { let StmtAnnAssign { range: _, + node_index: _, target, annotation, value, diff --git a/crates/ruff_python_formatter/src/statement/stmt_assert.rs b/crates/ruff_python_formatter/src/statement/stmt_assert.rs index 099ce3c24d391d..fc2aba97015f3e 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assert.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assert.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatStmtAssert { fn fmt_fields(&self, item: &StmtAssert, f: &mut PyFormatter) -> FormatResult<()> { let StmtAssert { range: _, + node_index: _, test, msg, } = item; diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index 54a2eec8bb71c7..b9fbe6b7a3e859 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -33,6 +33,7 @@ impl FormatNodeRule for FormatStmtAssign { fn fmt_fields(&self, item: &StmtAssign, f: &mut PyFormatter) -> FormatResult<()> { let StmtAssign { range: _, + node_index: _, targets, value, } = item; diff --git a/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs index e69045d4e1c8b8..58ac4b34e50680 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_aug_assign.rs @@ -21,6 +21,7 @@ impl FormatNodeRule for FormatStmtAugAssign { op, value, range: _, + node_index: _, } = item; if has_target_own_parentheses(target, f.context()) diff --git a/crates/ruff_python_formatter/src/statement/stmt_class_def.rs b/crates/ruff_python_formatter/src/statement/stmt_class_def.rs index f24409704b7766..9e84c76580e404 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_class_def.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_class_def.rs @@ -18,6 +18,7 @@ impl FormatNodeRule for FormatStmtClassDef { fn fmt_fields(&self, item: &StmtClassDef, f: &mut PyFormatter) -> FormatResult<()> { let StmtClassDef { range: _, + node_index: _, name, arguments, body, diff --git a/crates/ruff_python_formatter/src/statement/stmt_delete.rs b/crates/ruff_python_formatter/src/statement/stmt_delete.rs index e5b301e40f099e..fa3a27bb6f4d13 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_delete.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_delete.rs @@ -13,7 +13,11 @@ pub struct FormatStmtDelete; impl FormatNodeRule for FormatStmtDelete { fn fmt_fields(&self, item: &StmtDelete, f: &mut PyFormatter) -> FormatResult<()> { - let StmtDelete { range: _, targets } = item; + let StmtDelete { + range: _, + node_index: _, + targets, + } = item; write!(f, [token("del"), space()])?; diff --git a/crates/ruff_python_formatter/src/statement/stmt_for.rs b/crates/ruff_python_formatter/src/statement/stmt_for.rs index a4758b6b6c03f0..17517fc20ae7a0 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_for.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_for.rs @@ -36,6 +36,7 @@ impl FormatNodeRule for FormatStmtFor { body, orelse, range: _, + node_index: _, } = item; let comments = f.context().comments().clone(); diff --git a/crates/ruff_python_formatter/src/statement/stmt_function_def.rs b/crates/ruff_python_formatter/src/statement/stmt_function_def.rs index 61b1655fa59eed..86e2003efdc266 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_function_def.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_function_def.rs @@ -93,6 +93,7 @@ impl FormatNodeRule for FormatStmtFunctionDef { fn format_function_header(f: &mut PyFormatter, item: &StmtFunctionDef) -> FormatResult<()> { let StmtFunctionDef { range: _, + node_index: _, is_async, decorator_list: _, name, diff --git a/crates/ruff_python_formatter/src/statement/stmt_if.rs b/crates/ruff_python_formatter/src/statement/stmt_if.rs index 895c1476754e91..9b080ddc6e87a9 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_if.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_if.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatStmtIf { fn fmt_fields(&self, item: &StmtIf, f: &mut PyFormatter) -> FormatResult<()> { let StmtIf { range: _, + node_index: _, test, body, elif_else_clauses, @@ -68,6 +69,7 @@ pub(crate) fn format_elif_else_clause( ) -> FormatResult<()> { let ElifElseClause { range: _, + node_index: _, test, body, } = item; diff --git a/crates/ruff_python_formatter/src/statement/stmt_import.rs b/crates/ruff_python_formatter/src/statement/stmt_import.rs index 8045ae7864db5a..c44b8ffd494d57 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_import.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_import.rs @@ -9,7 +9,11 @@ pub struct FormatStmtImport; impl FormatNodeRule for FormatStmtImport { fn fmt_fields(&self, item: &StmtImport, f: &mut PyFormatter) -> FormatResult<()> { - let StmtImport { names, range: _ } = item; + let StmtImport { + names, + range: _, + node_index: _, + } = item; let names = format_with(|f| { f.join_with(&format_args![token(","), space()]) .entries(names.iter().formatted()) diff --git a/crates/ruff_python_formatter/src/statement/stmt_import_from.rs b/crates/ruff_python_formatter/src/statement/stmt_import_from.rs index 6699013593ff71..341adc64d227e9 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_import_from.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_import_from.rs @@ -19,6 +19,7 @@ impl FormatNodeRule for FormatStmtImportFrom { names, level, range: _, + node_index: _, } = item; write!( diff --git a/crates/ruff_python_formatter/src/statement/stmt_match.rs b/crates/ruff_python_formatter/src/statement/stmt_match.rs index 9ed641ba5541ae..45e0f9238659ae 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_match.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_match.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatStmtMatch { fn fmt_fields(&self, item: &StmtMatch, f: &mut PyFormatter) -> FormatResult<()> { let StmtMatch { range: _, + node_index: _, subject, cases, } = item; diff --git a/crates/ruff_python_formatter/src/statement/stmt_raise.rs b/crates/ruff_python_formatter/src/statement/stmt_raise.rs index 6a855e44185864..bfb73d00afaae3 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_raise.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_raise.rs @@ -13,6 +13,7 @@ impl FormatNodeRule for FormatStmtRaise { fn fmt_fields(&self, item: &StmtRaise, f: &mut PyFormatter) -> FormatResult<()> { let StmtRaise { range: _, + node_index: _, exc, cause, } = item; diff --git a/crates/ruff_python_formatter/src/statement/stmt_return.rs b/crates/ruff_python_formatter/src/statement/stmt_return.rs index ab782c02edbf25..fbeb9488e441ef 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_return.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_return.rs @@ -11,7 +11,11 @@ pub struct FormatStmtReturn; impl FormatNodeRule for FormatStmtReturn { fn fmt_fields(&self, item: &StmtReturn, f: &mut PyFormatter) -> FormatResult<()> { - let StmtReturn { range: _, value } = item; + let StmtReturn { + range: _, + node_index: _, + value, + } = item; token("return").fmt(f)?; diff --git a/crates/ruff_python_formatter/src/statement/stmt_try.rs b/crates/ruff_python_formatter/src/statement/stmt_try.rs index 97ef1512e46cf4..411be5b33984a3 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_try.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_try.rs @@ -66,6 +66,7 @@ impl FormatNodeRule for FormatStmtTry { finalbody, is_star, range: _, + node_index: _, } = item; let comments_info = f.context().comments().clone(); diff --git a/crates/ruff_python_formatter/src/statement/stmt_type_alias.rs b/crates/ruff_python_formatter/src/statement/stmt_type_alias.rs index e503d413668b21..a66b4f64be9c1a 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_type_alias.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_type_alias.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatStmtTypeAlias { type_params, value, range: _, + node_index: _, } = item; write!(f, [token("type"), space(), name.as_ref().format()])?; diff --git a/crates/ruff_python_formatter/src/statement/stmt_while.rs b/crates/ruff_python_formatter/src/statement/stmt_while.rs index c471078620bcd5..dc5593ded0265e 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_while.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_while.rs @@ -15,6 +15,7 @@ impl FormatNodeRule for FormatStmtWhile { fn fmt_fields(&self, item: &StmtWhile, f: &mut PyFormatter) -> FormatResult<()> { let StmtWhile { range: _, + node_index: _, test, body, orelse, diff --git a/crates/ruff_python_formatter/src/type_param/type_param_param_spec.rs b/crates/ruff_python_formatter/src/type_param/type_param_param_spec.rs index 6f9f5e5a720053..4a33ed0684056a 100644 --- a/crates/ruff_python_formatter/src/type_param/type_param_param_spec.rs +++ b/crates/ruff_python_formatter/src/type_param/type_param_param_spec.rs @@ -10,6 +10,7 @@ impl FormatNodeRule for FormatTypeParamParamSpec { fn fmt_fields(&self, item: &TypeParamParamSpec, f: &mut PyFormatter) -> FormatResult<()> { let TypeParamParamSpec { range: _, + node_index: _, name, default, } = item; diff --git a/crates/ruff_python_formatter/src/type_param/type_param_type_var.rs b/crates/ruff_python_formatter/src/type_param/type_param_type_var.rs index 294498326714c6..c18032b7959337 100644 --- a/crates/ruff_python_formatter/src/type_param/type_param_type_var.rs +++ b/crates/ruff_python_formatter/src/type_param/type_param_type_var.rs @@ -10,6 +10,7 @@ impl FormatNodeRule for FormatTypeParamTypeVar { fn fmt_fields(&self, item: &TypeParamTypeVar, f: &mut PyFormatter) -> FormatResult<()> { let TypeParamTypeVar { range: _, + node_index: _, name, bound, default, diff --git a/crates/ruff_python_formatter/src/type_param/type_param_type_var_tuple.rs b/crates/ruff_python_formatter/src/type_param/type_param_type_var_tuple.rs index 30ab85e9001ff6..4b50da7704eb0e 100644 --- a/crates/ruff_python_formatter/src/type_param/type_param_type_var_tuple.rs +++ b/crates/ruff_python_formatter/src/type_param/type_param_type_var_tuple.rs @@ -10,6 +10,7 @@ impl FormatNodeRule for FormatTypeParamTypeVarTuple { fn fmt_fields(&self, item: &TypeParamTypeVarTuple, f: &mut PyFormatter) -> FormatResult<()> { let TypeParamTypeVarTuple { range: _, + node_index: _, name, default, } = item; diff --git a/crates/ruff_python_formatter/tests/normalizer.rs b/crates/ruff_python_formatter/tests/normalizer.rs index 8bed90221c56b9..b89ec5130b1ca0 100644 --- a/crates/ruff_python_formatter/tests/normalizer.rs +++ b/crates/ruff_python_formatter/tests/normalizer.rs @@ -4,11 +4,11 @@ use { regex::Regex, }; -use ruff_python_ast::visitor::transformer::Transformer; use ruff_python_ast::{ self as ast, BytesLiteralFlags, Expr, FStringFlags, FStringPart, InterpolatedStringElement, InterpolatedStringLiteralElement, Stmt, StringFlags, }; +use ruff_python_ast::{AtomicNodeIndex, visitor::transformer::Transformer}; use ruff_python_ast::{StringLiteralFlags, visitor::transformer}; use ruff_text_size::{Ranged, TextRange}; @@ -82,6 +82,7 @@ impl Transformer for Normalizer { value: Box::from(string.value.to_str()), range: string.range, flags: StringLiteralFlags::empty(), + node_index: AtomicNodeIndex::dummy(), }); } } @@ -98,6 +99,7 @@ impl Transformer for Normalizer { value: bytes.value.bytes().collect(), range: bytes.range, flags: BytesLiteralFlags::empty(), + node_index: AtomicNodeIndex::dummy(), }); } } @@ -141,6 +143,7 @@ impl Transformer for Normalizer { InterpolatedStringLiteralElement { range, value: literal.into(), + node_index: AtomicNodeIndex::dummy(), }, )); } @@ -182,6 +185,7 @@ impl Transformer for Normalizer { elements: collector.elements.into(), range: fstring.range, flags: FStringFlags::empty(), + node_index: AtomicNodeIndex::dummy(), }); } } diff --git a/crates/ruff_python_parser/src/lib.rs b/crates/ruff_python_parser/src/lib.rs index 346bd89aa81b90..4825c2adce6c81 100644 --- a/crates/ruff_python_parser/src/lib.rs +++ b/crates/ruff_python_parser/src/lib.rs @@ -208,13 +208,14 @@ pub fn parse_parenthesized_expression_range( /// /// ``` /// use ruff_python_parser::parse_string_annotation; -/// use ruff_python_ast::{StringLiteral, StringLiteralFlags}; +/// use ruff_python_ast::{StringLiteral, StringLiteralFlags, AtomicNodeIndex}; /// use ruff_text_size::{TextRange, TextSize}; /// /// let string = StringLiteral { /// value: "'''\n int | str'''".to_string().into_boxed_str(), /// flags: StringLiteralFlags::empty(), /// range: TextRange::new(TextSize::new(0), TextSize::new(16)), +/// node_index: AtomicNodeIndex::dummy() /// }; /// let parsed = parse_string_annotation("'''\n int | str'''", &string); /// assert!(!parsed.is_ok()); diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 73ce20666472b6..ffe6a56d8a6b36 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -6,9 +6,9 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; use ruff_python_ast::name::Name; use ruff_python_ast::{ - self as ast, AnyStringFlags, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FString, - InterpolatedStringElement, InterpolatedStringElements, IpyEscapeKind, Number, Operator, - OperatorPrecedence, StringFlags, TString, UnaryOp, + self as ast, AnyStringFlags, AtomicNodeIndex, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, + FString, InterpolatedStringElement, InterpolatedStringElements, IpyEscapeKind, Number, + Operator, OperatorPrecedence, StringFlags, TString, UnaryOp, }; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -305,6 +305,7 @@ impl<'src> Parser<'src> { op: bin_op, right: Box::new(right.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } }; @@ -472,6 +473,7 @@ impl<'src> Parser<'src> { range: identifier.range, id: identifier.id, ctx, + node_index: AtomicNodeIndex::dummy(), } } @@ -487,13 +489,21 @@ impl<'src> Parser<'src> { let TokenValue::Name(name) = self.bump_value(TokenKind::Name) else { unreachable!(); }; - return ast::Identifier { id: name, range }; + return ast::Identifier { + id: name, + range, + node_index: AtomicNodeIndex::dummy(), + }; } if self.current_token_kind().is_soft_keyword() { let id = Name::new(self.src_text(range)); self.bump_soft_keyword_as_name(); - return ast::Identifier { id, range }; + return ast::Identifier { + id, + range, + node_index: AtomicNodeIndex::dummy(), + }; } if self.current_token_kind().is_keyword() { @@ -508,7 +518,11 @@ impl<'src> Parser<'src> { let id = Name::new(self.src_text(range)); self.bump_any(); - ast::Identifier { id, range } + ast::Identifier { + id, + range, + node_index: AtomicNodeIndex::dummy(), + } } else { self.add_error( ParseErrorType::OtherError("Expected an identifier".into()), @@ -518,6 +532,7 @@ impl<'src> Parser<'src> { ast::Identifier { id: Name::empty(), range: self.missing_node_range(), + node_index: AtomicNodeIndex::dummy(), } } } @@ -537,6 +552,7 @@ impl<'src> Parser<'src> { Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Float(value), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Complex => { @@ -546,6 +562,7 @@ impl<'src> Parser<'src> { Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Complex { real, imag }, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Int => { @@ -555,6 +572,7 @@ impl<'src> Parser<'src> { Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Int(value), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::True => { @@ -562,6 +580,7 @@ impl<'src> Parser<'src> { Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::False => { @@ -569,18 +588,21 @@ impl<'src> Parser<'src> { Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: false, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::None => { self.bump(TokenKind::None); Expr::NoneLiteral(ast::ExprNoneLiteral { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Ellipsis => { self.bump(TokenKind::Ellipsis); Expr::EllipsisLiteral(ast::ExprEllipsisLiteral { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Name => Expr::Name(self.parse_name()), @@ -608,6 +630,7 @@ impl<'src> Parser<'src> { range: self.missing_node_range(), id: Name::empty(), ctx: ExprContext::Invalid, + node_index: AtomicNodeIndex::dummy(), }) } } @@ -650,6 +673,7 @@ impl<'src> Parser<'src> { func: Box::new(func), arguments, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -679,6 +703,7 @@ impl<'src> Parser<'src> { arg: None, value: value.expr, range: parser.node_range(argument_start), + node_index: AtomicNodeIndex::dummy(), }); seen_keyword_unpacking = true; @@ -743,6 +768,7 @@ impl<'src> Parser<'src> { ast::Identifier { id: ident_expr.id, range: ident_expr.range, + node_index: AtomicNodeIndex::dummy(), } } else { // TODO(dhruvmanila): Parser shouldn't drop the `parsed_expr` if it's @@ -755,6 +781,7 @@ impl<'src> Parser<'src> { ast::Identifier { id: Name::empty(), range: parsed_expr.range(), + node_index: AtomicNodeIndex::dummy(), } }; @@ -764,6 +791,7 @@ impl<'src> Parser<'src> { arg: Some(arg), value: value.expr, range: parser.node_range(argument_start), + node_index: AtomicNodeIndex::dummy(), }); } else { if !parsed_expr.is_unparenthesized_starred_expr() { @@ -788,6 +816,7 @@ impl<'src> Parser<'src> { let arguments = ast::Arguments { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), args: args.into_boxed_slice(), keywords: keywords.into_boxed_slice(), }; @@ -829,9 +858,11 @@ impl<'src> Parser<'src> { range: slice_range, id: Name::empty(), ctx: ExprContext::Invalid, + node_index: AtomicNodeIndex::dummy(), })), ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }; } @@ -851,6 +882,7 @@ impl<'src> Parser<'src> { ctx: ExprContext::Load, range: self.node_range(slice_start), parenthesized: false, + node_index: AtomicNodeIndex::dummy(), }); } else if slice.is_starred_expr() { // If the only slice element is a starred expression, that is represented @@ -861,6 +893,7 @@ impl<'src> Parser<'src> { ctx: ExprContext::Load, range: self.node_range(slice_start), parenthesized: false, + node_index: AtomicNodeIndex::dummy(), }); } @@ -909,6 +942,7 @@ impl<'src> Parser<'src> { slice: Box::new(slice), ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1002,6 +1036,7 @@ impl<'src> Parser<'src> { Expr::Slice(ast::ExprSlice { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), lower, upper, step, @@ -1032,6 +1067,7 @@ impl<'src> Parser<'src> { op, operand: Box::new(operand.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1056,6 +1092,7 @@ impl<'src> Parser<'src> { attr, ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1099,6 +1136,7 @@ impl<'src> Parser<'src> { values, op, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1178,6 +1216,7 @@ impl<'src> Parser<'src> { ops: operators.into_boxed_slice(), comparators: comparators.into_boxed_slice(), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1229,18 +1268,22 @@ impl<'src> Parser<'src> { StringType::Str(string) => Expr::StringLiteral(ast::ExprStringLiteral { value: ast::StringLiteralValue::single(string), range, + node_index: AtomicNodeIndex::dummy(), }), StringType::Bytes(bytes) => Expr::BytesLiteral(ast::ExprBytesLiteral { value: ast::BytesLiteralValue::single(bytes), range, + node_index: AtomicNodeIndex::dummy(), }), StringType::FString(fstring) => Expr::FString(ast::ExprFString { value: ast::FStringValue::single(fstring), range, + node_index: AtomicNodeIndex::dummy(), }), StringType::TString(tstring) => Expr::TString(ast::ExprTString { value: ast::TStringValue::single(tstring), range, + node_index: AtomicNodeIndex::dummy(), }), }, _ => self.handle_implicitly_concatenated_strings(strings, range), @@ -1307,6 +1350,7 @@ impl<'src> Parser<'src> { return Expr::from(ast::ExprBytesLiteral { value: ast::BytesLiteralValue::concatenated(values), range, + node_index: AtomicNodeIndex::dummy(), }); } Ordering::Greater => unreachable!(), @@ -1346,6 +1390,7 @@ impl<'src> Parser<'src> { return Expr::from(ast::ExprStringLiteral { value: ast::StringLiteralValue::concatenated(values), range, + node_index: AtomicNodeIndex::dummy(), }); } @@ -1367,6 +1412,7 @@ impl<'src> Parser<'src> { return Expr::from(ast::ExprTString { value: ast::TStringValue::concatenated(parts), range, + node_index: AtomicNodeIndex::dummy(), }); } @@ -1387,6 +1433,7 @@ impl<'src> Parser<'src> { Expr::from(ast::ExprFString { value: ast::FStringValue::concatenated(parts), range, + node_index: AtomicNodeIndex::dummy(), }) } @@ -1422,6 +1469,7 @@ impl<'src> Parser<'src> { value: Box::new([]), range, flags: ast::BytesLiteralFlags::from(flags).with_invalid(), + node_index: AtomicNodeIndex::dummy(), }) } else { // test_err invalid_string_literal @@ -1431,6 +1479,7 @@ impl<'src> Parser<'src> { value: "".into(), range, flags: ast::StringLiteralFlags::from(flags).with_invalid(), + node_index: AtomicNodeIndex::dummy(), }) } } @@ -1604,6 +1653,7 @@ impl<'src> Parser<'src> { ast::InterpolatedStringLiteralElement { value: "".into(), range, + node_index: AtomicNodeIndex::dummy(), } }), ) @@ -1759,6 +1809,7 @@ impl<'src> Parser<'src> { Some(Box::new(ast::InterpolatedStringFormatSpec { range: self.node_range(spec_start), elements, + node_index: AtomicNodeIndex::dummy(), })) } else { None @@ -1810,6 +1861,7 @@ impl<'src> Parser<'src> { conversion, format_spec, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1839,6 +1891,7 @@ impl<'src> Parser<'src> { elts: vec![], ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }); } @@ -1890,6 +1943,7 @@ impl<'src> Parser<'src> { return Expr::Dict(ast::ExprDict { items: vec![], range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }); } @@ -2000,6 +2054,7 @@ impl<'src> Parser<'src> { elts: vec![], ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), parenthesized: true, }) .into(); @@ -2088,6 +2143,7 @@ impl<'src> Parser<'src> { elts, ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), parenthesized: parenthesized.is_yes(), } } @@ -2116,6 +2172,7 @@ impl<'src> Parser<'src> { elts, ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2164,6 +2221,7 @@ impl<'src> Parser<'src> { ast::ExprSet { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), elts, } } @@ -2206,6 +2264,7 @@ impl<'src> Parser<'src> { ast::ExprDict { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), items, } } @@ -2273,6 +2332,7 @@ impl<'src> Parser<'src> { ast::Comprehension { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), target: target.expr, iter: iter.expr, ifs, @@ -2302,6 +2362,7 @@ impl<'src> Parser<'src> { elt: Box::new(element), generators, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), parenthesized: parenthesized.is_yes(), } } @@ -2322,6 +2383,7 @@ impl<'src> Parser<'src> { elt: Box::new(element), generators, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2343,6 +2405,7 @@ impl<'src> Parser<'src> { value: Box::new(value), generators, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2362,6 +2425,7 @@ impl<'src> Parser<'src> { elt: Box::new(element), generators, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2398,6 +2462,7 @@ impl<'src> Parser<'src> { value: Box::new(parsed_expr.expr), ctx: ExprContext::Load, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2420,6 +2485,7 @@ impl<'src> Parser<'src> { ast::ExprAwait { value: Box::new(parsed_expr.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2468,6 +2534,7 @@ impl<'src> Parser<'src> { Expr::Yield(ast::ExprYield { value, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } @@ -2507,6 +2574,7 @@ impl<'src> Parser<'src> { Expr::YieldFrom(ast::ExprYieldFrom { value: Box::new(expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } @@ -2547,6 +2615,7 @@ impl<'src> Parser<'src> { target: Box::new(target), value: Box::new(value.expr), range, + node_index: AtomicNodeIndex::dummy(), } } @@ -2594,6 +2663,7 @@ impl<'src> Parser<'src> { body: Box::new(body.expr), parameters, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2618,6 +2688,7 @@ impl<'src> Parser<'src> { test: Box::new(test.expr), orelse: Box::new(orelse.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2643,6 +2714,7 @@ impl<'src> Parser<'src> { let command = ast::ExprIpyEscapeCommand { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), kind, value, }; @@ -2901,6 +2973,7 @@ impl From for FString { elements: value.elements, range: value.range, flags: value.flags.into(), + node_index: AtomicNodeIndex::dummy(), } } } @@ -2911,6 +2984,7 @@ impl From for TString { elements: value.elements, range: value.range, flags: value.flags.into(), + node_index: AtomicNodeIndex::dummy(), } } } diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs index 0668f18b2954ee..904e92df95b8c3 100644 --- a/crates/ruff_python_parser/src/parser/mod.rs +++ b/crates/ruff_python_parser/src/parser/mod.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use bitflags::bitflags; -use ruff_python_ast::{Mod, ModExpression, ModModule}; +use ruff_python_ast::{AtomicNodeIndex, Mod, ModExpression, ModModule}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::error::UnsupportedSyntaxError; @@ -132,6 +132,7 @@ impl<'src> Parser<'src> { ModExpression { body: Box::new(parsed_expr.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -149,6 +150,7 @@ impl<'src> Parser<'src> { ModModule { body, range: TextRange::new(self.start_offset, self.current_token_range().end()), + node_index: AtomicNodeIndex::dummy(), } } diff --git a/crates/ruff_python_parser/src/parser/pattern.rs b/crates/ruff_python_parser/src/parser/pattern.rs index 461b859c78ad36..dd552410db6fa1 100644 --- a/crates/ruff_python_parser/src/parser/pattern.rs +++ b/crates/ruff_python_parser/src/parser/pattern.rs @@ -1,5 +1,7 @@ use ruff_python_ast::name::Name; -use ruff_python_ast::{self as ast, Expr, ExprContext, Number, Operator, Pattern, Singleton}; +use ruff_python_ast::{ + self as ast, AtomicNodeIndex, Expr, ExprContext, Number, Operator, Pattern, Singleton, +}; use ruff_text_size::{Ranged, TextSize}; use crate::ParseErrorType; @@ -110,6 +112,7 @@ impl Parser<'_> { lhs = Pattern::MatchOr(ast::PatternMatchOr { range: self.node_range(start), patterns, + node_index: AtomicNodeIndex::dummy(), }); } @@ -125,6 +128,7 @@ impl Parser<'_> { range: self.node_range(start), name: Some(ident), pattern: Some(Box::new(lhs)), + node_index: AtomicNodeIndex::dummy(), }); } @@ -200,18 +204,25 @@ impl Parser<'_> { } else { let key = match parser.parse_match_pattern_lhs(AllowStarPattern::No) { Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value, - Pattern::MatchSingleton(ast::PatternMatchSingleton { value, range }) => { - match value { - Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range }), - Singleton::True => { - Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, range }) - } - Singleton::False => Expr::BooleanLiteral(ast::ExprBooleanLiteral { - value: false, - range, - }), + Pattern::MatchSingleton(ast::PatternMatchSingleton { + value, + range, + node_index, + }) => match value { + Singleton::None => { + Expr::NoneLiteral(ast::ExprNoneLiteral { range, node_index }) } - } + Singleton::True => Expr::BooleanLiteral(ast::ExprBooleanLiteral { + value: true, + range, + node_index, + }), + Singleton::False => Expr::BooleanLiteral(ast::ExprBooleanLiteral { + value: false, + range, + node_index, + }), + }, pattern => { parser.add_error( ParseErrorType::OtherError("Invalid mapping pattern key".to_string()), @@ -244,6 +255,7 @@ impl Parser<'_> { keys, patterns, rest, + node_index: AtomicNodeIndex::dummy(), } } @@ -267,6 +279,7 @@ impl Parser<'_> { } else { Some(ident) }, + node_index: AtomicNodeIndex::dummy(), } } @@ -306,6 +319,7 @@ impl Parser<'_> { return Pattern::MatchSequence(ast::PatternMatchSequence { patterns: vec![], range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }); } @@ -360,6 +374,7 @@ impl Parser<'_> { ast::PatternMatchSequence { range: self.node_range(start), patterns, + node_index: AtomicNodeIndex::dummy(), } } @@ -374,6 +389,7 @@ impl Parser<'_> { Pattern::MatchSingleton(ast::PatternMatchSingleton { value: Singleton::None, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::True => { @@ -381,6 +397,7 @@ impl Parser<'_> { Pattern::MatchSingleton(ast::PatternMatchSingleton { value: Singleton::True, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::False => { @@ -388,6 +405,7 @@ impl Parser<'_> { Pattern::MatchSingleton(ast::PatternMatchSingleton { value: Singleton::False, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::String | TokenKind::FStringStart | TokenKind::TStringStart => { @@ -396,6 +414,7 @@ impl Parser<'_> { Pattern::MatchValue(ast::PatternMatchValue { value: Box::new(str), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Complex => { @@ -408,8 +427,10 @@ impl Parser<'_> { value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Complex { real, imag }, range, + node_index: AtomicNodeIndex::dummy(), })), range, + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Int => { @@ -422,8 +443,10 @@ impl Parser<'_> { value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Int(value), range, + node_index: AtomicNodeIndex::dummy(), })), range, + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Float => { @@ -436,8 +459,10 @@ impl Parser<'_> { value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral { value: Number::Float(value), range, + node_index: AtomicNodeIndex::dummy(), })), range, + node_index: AtomicNodeIndex::dummy(), }) } kind => { @@ -464,6 +489,7 @@ impl Parser<'_> { return Pattern::MatchValue(ast::PatternMatchValue { value: Box::new(Expr::UnaryOp(unary_expr)), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }); } } @@ -483,6 +509,7 @@ impl Parser<'_> { Pattern::MatchValue(ast::PatternMatchValue { value: Box::new(attribute), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } else { // test_ok match_as_pattern_soft_keyword @@ -503,6 +530,7 @@ impl Parser<'_> { range: ident.range, pattern: None, name: if &ident == "_" { None } else { Some(ident) }, + node_index: AtomicNodeIndex::dummy(), }) } } else { @@ -516,10 +544,12 @@ impl Parser<'_> { range: self.missing_node_range(), id: Name::empty(), ctx: ExprContext::Invalid, + node_index: AtomicNodeIndex::dummy(), }); Pattern::MatchValue(ast::PatternMatchValue { range: invalid_node.range(), value: Box::new(invalid_node), + node_index: AtomicNodeIndex::dummy(), }) } } @@ -575,8 +605,10 @@ impl Parser<'_> { op: operator, right: rhs_value, range, + node_index: AtomicNodeIndex::dummy(), })), range, + node_index: AtomicNodeIndex::dummy(), } } @@ -616,12 +648,14 @@ impl Parser<'_> { range: ident.range(), id: ident.id, ctx: ExprContext::Load, + node_index: AtomicNodeIndex::dummy(), })) } else { Box::new(Expr::Name(ast::ExprName { range: ident.range(), id: Name::empty(), ctx: ExprContext::Invalid, + node_index: AtomicNodeIndex::dummy(), })) } } @@ -673,6 +707,7 @@ impl Parser<'_> { ast::Identifier { id: Name::empty(), range: parser.missing_node_range(), + node_index: AtomicNodeIndex::dummy(), } }; @@ -682,6 +717,7 @@ impl Parser<'_> { attr: key, pattern: value_pattern, range: parser.node_range(pattern_start), + node_index: AtomicNodeIndex::dummy(), }); } else { has_seen_pattern = true; @@ -707,8 +743,10 @@ impl Parser<'_> { patterns, keywords, range: self.node_range(arguments_start), + node_index: AtomicNodeIndex::dummy(), }, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } } diff --git a/crates/ruff_python_parser/src/parser/recovery.rs b/crates/ruff_python_parser/src/parser/recovery.rs index 1dd4489a8c085d..f66f7e6dd130de 100644 --- a/crates/ruff_python_parser/src/parser/recovery.rs +++ b/crates/ruff_python_parser/src/parser/recovery.rs @@ -27,27 +27,38 @@ use ruff_text_size::{Ranged, TextLen, TextRange}; /// without dropping one of them as there's no way to represent `x as y` as a valid expression. pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { match pattern { - Pattern::MatchSingleton(ast::PatternMatchSingleton { range, value }) => match value { - ast::Singleton::True => { - Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, range }) - } + Pattern::MatchSingleton(ast::PatternMatchSingleton { + range, + node_index, + value, + }) => match value { + ast::Singleton::True => Expr::BooleanLiteral(ast::ExprBooleanLiteral { + value: true, + range, + node_index, + }), ast::Singleton::False => Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: false, range, + node_index, }), - ast::Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range }), + ast::Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range, node_index }), }, Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value, // We don't know which kind of sequence this is: `case [1, 2]:` or `case (1, 2):`. - Pattern::MatchSequence(ast::PatternMatchSequence { range, patterns }) => { - Expr::List(ast::ExprList { - elts: patterns.into_iter().map(pattern_to_expr).collect(), - ctx: ExprContext::Store, - range, - }) - } + Pattern::MatchSequence(ast::PatternMatchSequence { + range, + node_index, + patterns, + }) => Expr::List(ast::ExprList { + elts: patterns.into_iter().map(pattern_to_expr).collect(), + ctx: ExprContext::Store, + range, + node_index, + }), Pattern::MatchMapping(ast::PatternMatchMapping { range, + node_index, keys, patterns, rest, @@ -63,22 +74,30 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { if let Some(rest) = rest { let value = Expr::Name(ast::ExprName { range: rest.range, + node_index: node_index.clone(), id: rest.id, ctx: ExprContext::Store, }); items.push(ast::DictItem { key: None, value }); } - Expr::Dict(ast::ExprDict { range, items }) + Expr::Dict(ast::ExprDict { + range, + node_index, + items, + }) } Pattern::MatchClass(ast::PatternMatchClass { range, + node_index, cls, arguments, }) => Expr::Call(ast::ExprCall { range, + node_index: node_index.clone(), func: cls, arguments: ast::Arguments { range: arguments.range, + node_index: node_index.clone(), args: arguments .patterns .into_iter() @@ -89,18 +108,25 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { .into_iter() .map(|keyword_pattern| ast::Keyword { range: keyword_pattern.range, + node_index: node_index.clone(), arg: Some(keyword_pattern.attr), value: pattern_to_expr(keyword_pattern.pattern), }) .collect(), }, }), - Pattern::MatchStar(ast::PatternMatchStar { range, name }) => { + Pattern::MatchStar(ast::PatternMatchStar { + range, + node_index, + name, + }) => { if let Some(name) = name { Expr::Starred(ast::ExprStarred { range, + node_index: node_index.clone(), value: Box::new(Expr::Name(ast::ExprName { range: name.range, + node_index, id: name.id, ctx: ExprContext::Store, })), @@ -109,10 +135,12 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { } else { Expr::Starred(ast::ExprStarred { range, + node_index: node_index.clone(), value: Box::new(Expr::Name(ast::ExprName { range: TextRange::new(range.end() - "_".text_len(), range.end()), id: Name::new_static("_"), ctx: ExprContext::Store, + node_index, })), ctx: ExprContext::Store, }) @@ -120,32 +148,41 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { } Pattern::MatchAs(ast::PatternMatchAs { range, + node_index, pattern, name, }) => match (pattern, name) { (Some(_), Some(_)) => Expr::Name(ast::ExprName { range, + node_index, id: Name::empty(), ctx: ExprContext::Invalid, }), (Some(pattern), None) => pattern_to_expr(*pattern), (None, Some(name)) => Expr::Name(ast::ExprName { range: name.range, + node_index, id: name.id, ctx: ExprContext::Store, }), (None, None) => Expr::Name(ast::ExprName { range, + node_index, id: Name::new_static("_"), ctx: ExprContext::Store, }), }, - Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => { + Pattern::MatchOr(ast::PatternMatchOr { + patterns, + node_index, + .. + }) => { let to_bin_expr = |left: Pattern, right: Pattern| ast::ExprBinOp { range: TextRange::new(left.start(), right.end()), left: Box::new(pattern_to_expr(left)), op: ast::Operator::BitOr, right: Box::new(pattern_to_expr(right)), + node_index: node_index.clone(), }; let mut iter = patterns.into_iter(); @@ -158,6 +195,7 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { left: Box::new(Expr::BinOp(expr_bin_op)), op: ast::Operator::BitOr, right: Box::new(pattern_to_expr(pattern)), + node_index: node_index.clone(), } })) } diff --git a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__expr_mode_valid_syntax.snap b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__expr_mode_valid_syntax.snap index 29cb09b85afc3e..ab002c583afa95 100644 --- a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__expr_mode_valid_syntax.snap +++ b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__expr_mode_valid_syntax.snap @@ -1,10 +1,10 @@ --- source: crates/ruff_python_parser/src/parser/tests.rs expression: parsed.expr() -snapshot_kind: text --- Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("first"), ctx: Load, diff --git a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__ipython_escape_commands.snap b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__ipython_escape_commands.snap index e5566ded219930..8d6fbc000ce9ab 100644 --- a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__ipython_escape_commands.snap +++ b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__ipython_escape_commands.snap @@ -1,20 +1,23 @@ --- source: crates/ruff_python_parser/src/parser/tests.rs expression: parsed.syntax() -snapshot_kind: text --- Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..929, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..42, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 27..40, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("a"), ctx: Load, @@ -23,6 +26,7 @@ Module( op: Mod, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("b"), ctx: Load, @@ -34,6 +38,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 66..73, kind: Help2, value: "a.foo", @@ -41,6 +46,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 74..80, kind: Help, value: "a.foo", @@ -48,6 +54,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 81..88, kind: Help, value: "a.foo", @@ -55,6 +62,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 89..100, kind: Help2, value: "a.foo()", @@ -62,6 +70,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 115..128, kind: Magic, value: "timeit a = b", @@ -69,6 +78,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 129..147, kind: Magic, value: "timeit foo(b) % 3", @@ -76,6 +86,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 148..176, kind: Magic, value: "alias showPath pwd && ls -a", @@ -83,6 +94,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 177..205, kind: Magic, value: "timeit a = foo(b); b = 2", @@ -90,6 +102,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 206..226, kind: Magic, value: "matplotlib --inline", @@ -97,6 +110,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 227..253, kind: Magic, value: "matplotlib --inline", @@ -104,6 +118,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 277..309, kind: Shell, value: "pwd && ls -a | sed 's/^/\\ /'", @@ -111,6 +126,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 310..347, kind: Shell, value: "pwd && ls -a | sed 's/^/\\\\ /'", @@ -118,6 +134,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 348..393, kind: ShCap, value: "cd /Users/foo/Library/Application\\ Support/", @@ -125,16 +142,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 566..626, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 570..573, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 573..575, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -145,13 +167,16 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 581..626, value: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 598..620, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 598..599, id: Name("a"), ctx: Load, @@ -163,6 +188,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 619..620, id: Name("b"), ctx: Load, @@ -179,6 +205,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 656..664, kind: Paren, value: "foo 1 2", @@ -186,6 +213,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 665..673, kind: Quote2, value: "foo 1 2", @@ -193,6 +221,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 674..682, kind: Quote, value: "foo 1 2", @@ -200,10 +229,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 711..737, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 715..716, id: Name("a"), ctx: Store, @@ -211,9 +242,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 720..728, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 720..725, id: Name("range"), ctx: Load, @@ -221,9 +254,11 @@ Module( ), arguments: Arguments { range: 725..728, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 726..727, value: Int( 5, @@ -238,6 +273,7 @@ Module( body: [ IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 734..737, kind: Shell, value: "ls", @@ -249,10 +285,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 739..748, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 739..741, id: Name("p1"), ctx: Store, @@ -261,6 +299,7 @@ Module( ], value: IpyEscapeCommand( ExprIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 744..748, kind: Shell, value: "pwd", @@ -270,9 +309,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 749..763, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 749..751, id: Name("p2"), ctx: Store, @@ -280,6 +321,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 753..756, id: Name("str"), ctx: Load, @@ -288,6 +330,7 @@ Module( value: Some( IpyEscapeCommand( ExprIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 759..763, kind: Shell, value: "pwd", @@ -299,10 +342,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 764..784, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 764..767, id: Name("foo"), ctx: Store, @@ -311,6 +356,7 @@ Module( ], value: IpyEscapeCommand( ExprIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 770..784, kind: Magic, value: "foo bar", @@ -320,6 +366,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 786..791, kind: Magic, value: " foo", @@ -327,10 +374,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 792..813, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 792..795, id: Name("foo"), ctx: Store, @@ -339,6 +388,7 @@ Module( ], value: IpyEscapeCommand( ExprIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 798..813, kind: Magic, value: "foo # comment", @@ -348,6 +398,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 838..842, kind: Help, value: "foo", @@ -355,6 +406,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 843..852, kind: Help2, value: "foo.bar", @@ -362,6 +414,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 853..865, kind: Help, value: "foo.bar.baz", @@ -369,6 +422,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 866..874, kind: Help2, value: "foo[0]", @@ -376,6 +430,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 875..885, kind: Help, value: "foo[0][1]", @@ -383,6 +438,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 886..905, kind: Help2, value: "foo.bar[0].baz[1]", @@ -390,6 +446,7 @@ Module( ), IpyEscapeCommand( StmtIpyEscapeCommand { + node_index: AtomicNodeIndex(..), range: 906..929, kind: Help2, value: "foo.bar[0].baz[2].egg", diff --git a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__unicode_aliases.snap b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__unicode_aliases.snap index fe8e440c175822..6ee807fc8c9859 100644 --- a/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__unicode_aliases.snap +++ b/crates/ruff_python_parser/src/parser/snapshots/ruff_python_parser__parser__tests__unicode_aliases.snap @@ -1,15 +1,16 @@ --- source: crates/ruff_python_parser/src/parser/tests.rs expression: suite -snapshot_kind: text --- [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..37, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -18,11 +19,13 @@ snapshot_kind: text ], value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 4..37, value: StringLiteralValue { inner: Single( StringLiteral { range: 4..37, + node_index: AtomicNodeIndex(..), value: "\u{8}another cool trick", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 6a44192746a34e..d738fc78d6592a 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3,8 +3,8 @@ use std::fmt::{Display, Write}; use ruff_python_ast::name::Name; use ruff_python_ast::{ - self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, PythonVersion, Stmt, - WithItem, + self as ast, AtomicNodeIndex, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, + PythonVersion, Stmt, WithItem, }; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -312,6 +312,7 @@ impl<'src> Parser<'src> { Stmt::Expr(ast::StmtExpr { range: self.node_range(start), value: Box::new(parsed_expr.expr), + node_index: AtomicNodeIndex::dummy(), }) } } @@ -367,6 +368,7 @@ impl<'src> Parser<'src> { ast::StmtDelete { targets, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -415,6 +417,7 @@ impl<'src> Parser<'src> { ast::StmtReturn { range: self.node_range(start), value, + node_index: AtomicNodeIndex::dummy(), } } @@ -520,6 +523,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), exc, cause, + node_index: AtomicNodeIndex::dummy(), } } @@ -560,6 +564,7 @@ impl<'src> Parser<'src> { ast::StmtImport { range: self.node_range(start), names, + node_index: AtomicNodeIndex::dummy(), } } @@ -671,6 +676,7 @@ impl<'src> Parser<'src> { names, level: leading_dots, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -687,9 +693,11 @@ impl<'src> Parser<'src> { name: ast::Identifier { id: Name::new_static("*"), range, + node_index: AtomicNodeIndex::dummy(), }, asname: None, range, + node_index: AtomicNodeIndex::dummy(), }; } @@ -722,6 +730,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), name, asname, + node_index: AtomicNodeIndex::dummy(), } } @@ -750,6 +759,7 @@ impl<'src> Parser<'src> { ast::Identifier { id: Name::from(dotted_name), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -765,6 +775,7 @@ impl<'src> Parser<'src> { self.bump(TokenKind::Pass); ast::StmtPass { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -780,6 +791,7 @@ impl<'src> Parser<'src> { self.bump(TokenKind::Continue); ast::StmtContinue { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -795,6 +807,7 @@ impl<'src> Parser<'src> { self.bump(TokenKind::Break); ast::StmtBreak { range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -844,6 +857,7 @@ impl<'src> Parser<'src> { test: Box::new(test.expr), msg, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -882,6 +896,7 @@ impl<'src> Parser<'src> { ast::StmtGlobal { range: self.node_range(start), names, + node_index: AtomicNodeIndex::dummy(), } } @@ -927,6 +942,7 @@ impl<'src> Parser<'src> { ast::StmtNonlocal { range: self.node_range(start), names, + node_index: AtomicNodeIndex::dummy(), } } @@ -979,6 +995,7 @@ impl<'src> Parser<'src> { type_params: type_params.map(Box::new), value: Box::new(value.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1001,7 +1018,12 @@ impl<'src> Parser<'src> { self.add_error(ParseErrorType::UnexpectedIpythonEscapeCommand, range); } - ast::StmtIpyEscapeCommand { range, kind, value } + ast::StmtIpyEscapeCommand { + range, + kind, + value, + node_index: AtomicNodeIndex::dummy(), + } } /// Parses an IPython help end escape command at the statement level. @@ -1097,6 +1119,7 @@ impl<'src> Parser<'src> { value: value.into_boxed_str(), kind, range: self.node_range(parsed_expr.start()), + node_index: AtomicNodeIndex::dummy(), } } @@ -1164,6 +1187,7 @@ impl<'src> Parser<'src> { targets, value: Box::new(value.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1243,6 +1267,7 @@ impl<'src> Parser<'src> { value, simple, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1297,6 +1322,7 @@ impl<'src> Parser<'src> { op, value: Box::new(value.expr), range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1352,6 +1378,7 @@ impl<'src> Parser<'src> { body, elif_else_clauses, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1395,6 +1422,7 @@ impl<'src> Parser<'src> { test, body, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1544,6 +1572,7 @@ impl<'src> Parser<'src> { finalbody, is_star, range: self.node_range(try_start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1693,6 +1722,7 @@ impl<'src> Parser<'src> { name, body: except_body, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }), block_kind, ) @@ -1804,6 +1834,7 @@ impl<'src> Parser<'src> { body, orelse, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1851,6 +1882,7 @@ impl<'src> Parser<'src> { body, orelse, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -1980,6 +2012,7 @@ impl<'src> Parser<'src> { is_async: false, returns, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2049,6 +2082,7 @@ impl<'src> Parser<'src> { type_params: type_params.map(Box::new), arguments, body, + node_index: AtomicNodeIndex::dummy(), } } @@ -2075,6 +2109,7 @@ impl<'src> Parser<'src> { body, is_async: false, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2343,6 +2378,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), context_expr: context_expr.expr, optional_vars, + node_index: AtomicNodeIndex::dummy(), }, } } @@ -2411,6 +2447,7 @@ impl<'src> Parser<'src> { subject: Box::new(subject), cases, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } TokenKind::Newline if matches!(self.peek2(), (TokenKind::Indent, TokenKind::Case)) => { @@ -2433,6 +2470,7 @@ impl<'src> Parser<'src> { subject: Box::new(subject), cases, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), }) } _ => { @@ -2480,6 +2518,7 @@ impl<'src> Parser<'src> { subject: Box::new(subject), cases, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2658,6 +2697,7 @@ impl<'src> Parser<'src> { guard, body, range: self.node_range(start), + node_index: AtomicNodeIndex::dummy(), } } @@ -2826,6 +2866,7 @@ impl<'src> Parser<'src> { decorators.push(ast::Decorator { expression: parsed_expr.expr, range: self.node_range(decorator_start), + node_index: AtomicNodeIndex::dummy(), }); // test_err decorator_missing_newline @@ -3039,6 +3080,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), name, annotation, + node_index: AtomicNodeIndex::dummy(), } } @@ -3088,6 +3130,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), parameter, default, + node_index: AtomicNodeIndex::dummy(), } } @@ -3405,6 +3448,7 @@ impl<'src> Parser<'src> { ast::TypeParams { range: self.node_range(start), type_params, + node_index: AtomicNodeIndex::dummy(), } } @@ -3467,6 +3511,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), name, default, + node_index: AtomicNodeIndex::dummy(), }) // test_ok type_param_param_spec @@ -3506,6 +3551,7 @@ impl<'src> Parser<'src> { range: self.node_range(start), name, default, + node_index: AtomicNodeIndex::dummy(), }) // test_ok type_param_type_var // type X[T] = int @@ -3589,6 +3635,7 @@ impl<'src> Parser<'src> { name, bound, default, + node_index: AtomicNodeIndex::dummy(), }) } } diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index 0996ea4644bc1b..c8d7fb458fbee3 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -118,7 +118,11 @@ impl SemanticSyntaxChecker { // _ = *(p + q) Self::invalid_star_expression(value, ctx); } - Stmt::Return(ast::StmtReturn { value, range }) => { + Stmt::Return(ast::StmtReturn { + value, + range, + node_index: _, + }) => { if let Some(value) = value { // test_err single_star_return // def f(): return *x @@ -638,6 +642,7 @@ impl SemanticSyntaxChecker { range, id, ctx: expr_ctx, + node_index: _, }) => { // test_err write_to_debug_expr // del __debug__ diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap index 84fbc3cae0ecd6..04e5596e74227a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..15, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..15, + node_index: AtomicNodeIndex(..), value: "\u{8}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap index 4cfaed6c05d264..8f28967afe49cd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..9, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "\u{7}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap index 9fce7955def23c..258440c8d44d38 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..21, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..21, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..21, + node_index: AtomicNodeIndex(..), value: "\r", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap index 9f6bf06e282489..447e6a7e416cdd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..45, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..45, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..45, + node_index: AtomicNodeIndex(..), value: "\u{89}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap index 9a8831f2dbaf53..ac810427d1e7cc 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..12, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..12, + node_index: AtomicNodeIndex(..), value: "\u{7f}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__dont_panic_on_8_in_octal_escape.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__dont_panic_on_8_in_octal_escape.snap index 3fd84081eb8d91..ccac176830a4de 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__dont_panic_on_8_in_octal_escape.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__dont_panic_on_8_in_octal_escape.snap @@ -1,15 +1,16 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..16, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("bold"), ctx: Store, @@ -18,11 +19,13 @@ snapshot_kind: text ], value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 7..16, value: StringLiteralValue { inner: Single( StringLiteral { range: 7..16, + node_index: AtomicNodeIndex(..), value: "\u{3}8[1m", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap index fd003b6a4af61d..59f9b5213b1de3 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..738, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..738, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..738, + node_index: AtomicNodeIndex(..), value: [ 0, 1, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap index d924ebd21a712b..4f706e1fde15d8 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..12, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..12, + node_index: AtomicNodeIndex(..), value: "\u{1b}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap index e830dd89ea42c4..aad2af989480b2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..13, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..13, + node_index: AtomicNodeIndex(..), value: [ 111, 109, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap index cf13defcb6a26a..cfc7e3ee098153 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..14, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..14, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..14, + node_index: AtomicNodeIndex(..), value: [ 35, 97, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap index bd6d96055975e2..21c8747c01ec18 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..15, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..15, + node_index: AtomicNodeIndex(..), value: "\u{c}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap index 78e450c4e2cfd3..b418df379e0424 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..22, value: FStringValue { inner: Single( FString( FString { range: 0..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..5, + node_index: AtomicNodeIndex(..), value: "aaa", }, ), Interpolation( InterpolatedElement { range: 5..10, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("bbb"), ctx: Load, @@ -39,14 +45,17 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 10..13, + node_index: AtomicNodeIndex(..), value: "ccc", }, ), Interpolation( InterpolatedElement { range: 13..18, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("ddd"), ctx: Load, @@ -60,6 +69,7 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 18..21, + node_index: AtomicNodeIndex(..), value: "eee", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap index 02db608ab1c030..8cb891a551b446 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..8, value: FStringValue { inner: Single( FString( FString { range: 0..8, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..4, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 4..7, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap index 9482530d4afc78..b1df67c78f8f56 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..8, value: FStringValue { inner: Single( FString( FString { range: 0..8, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..4, + node_index: AtomicNodeIndex(..), value: "\n", }, ), Interpolation( InterpolatedElement { range: 4..7, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap index 6ccd6f466dcac6..c3fd51c096ba4f 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..9, value: FStringValue { inner: Single( FString( FString { range: 0..9, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 3..5, + node_index: AtomicNodeIndex(..), value: "\\\n", }, ), Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap index 840873127a2a60..35e090916ce479 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..10, value: FStringValue { inner: Single( FString( FString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("user"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap index 3d5f1e79c0d831..e05de72bc4655a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..38, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..38, value: FStringValue { inner: Single( FString( FString { range: 0..38, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..6, + node_index: AtomicNodeIndex(..), value: "mix ", }, ), Interpolation( InterpolatedElement { range: 6..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..11, id: Name("user"), ctx: Load, @@ -44,14 +50,17 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 13..28, + node_index: AtomicNodeIndex(..), value: " with text and ", }, ), Interpolation( InterpolatedElement { range: 28..37, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..35, id: Name("second"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap index d2063a00eceee1..932339c3590d45 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..14, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..14, value: FStringValue { inner: Single( FString( FString { range: 0..14, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("user"), ctx: Load, @@ -35,10 +40,12 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 9..12, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 9..12, + node_index: AtomicNodeIndex(..), value: ">10", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap index 98be2ba27a7a9f..f651b99dff099d 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..11, value: FStringValue { inner: Single( FString( FString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 4..5, + node_index: AtomicNodeIndex(..), value: "\n", }, ), Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap index c65231cc33bdff..4d694d9dcaf9c5 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..9, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "\u{88}", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap index 1a57e2606a5874..52bb0728d36b3e 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_fstring.snap @@ -5,15 +5,18 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..3, value: FStringValue { inner: Single( FString( FString { range: 0..3, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap index 3ccfef44042661..3e32e6364a7852 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_empty_tstring.snap @@ -5,15 +5,18 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..3, value: TStringValue { inner: Single( TString( TString { range: 0..3, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap index e43a1b109c4be8..291eb7e8f4f5e6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..17, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite FString( FString { range: 9..17, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap index e43a1b109c4be8..291eb7e8f4f5e6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..17, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite FString( FString { range: 9..17, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap index a9f5a2c31fbe70..66e0a2cbd93ccb 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..22, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,23 +29,28 @@ expression: suite FString( FString { range: 9..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), Interpolation( InterpolatedElement { range: 16..21, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 17..20, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap index 6b348cab6d4961..d7830a63d0ecfa 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..31, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..31, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,23 +29,28 @@ expression: suite FString( FString { range: 9..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), Interpolation( InterpolatedElement { range: 16..21, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 17..20, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Double, @@ -70,6 +78,7 @@ expression: suite Literal( StringLiteral { range: 23..31, + node_index: AtomicNodeIndex(..), value: "again!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap index c1432f9b0335e2..5e211ea8ca933c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_1.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..18, value: TStringValue { inner: Concatenated( @@ -15,10 +17,12 @@ expression: suite FString( FString { range: 0..9, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..8, + node_index: AtomicNodeIndex(..), value: "Hello ", }, ), @@ -33,10 +37,12 @@ expression: suite TString( TString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap index b3a9d72180d295..67c09862be6b13 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_t_string_concat_2.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..22, value: TStringValue { inner: Concatenated( @@ -15,10 +17,12 @@ expression: suite FString( FString { range: 0..9, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..8, + node_index: AtomicNodeIndex(..), value: "Hello ", }, ), @@ -33,10 +37,12 @@ expression: suite TString( TString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), @@ -51,6 +57,7 @@ expression: suite Literal( StringLiteral { range: 19..22, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap index b4fbc87730c141..d4d4e01a4afe38 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..18, value: FStringValue { inner: Single( FString( FString { range: 0..18, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..5, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("a"), ctx: Load, @@ -33,8 +38,10 @@ expression: suite Interpolation( InterpolatedElement { range: 5..10, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("b"), ctx: Load, @@ -48,6 +55,7 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 10..17, + node_index: AtomicNodeIndex(..), value: "{foo}", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap index 57e5d8296c8121..93333df2aade43 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_equals.snap @@ -5,24 +5,30 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..13, value: FStringValue { inner: Single( FString( FString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 3..11, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3..5, value: Int( 42, @@ -35,6 +41,7 @@ expression: suite comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..11, value: Int( 42, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap index f0f88bc994cd2a..2e0f5c6474b437 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..16, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..16, value: FStringValue { inner: Single( FString( FString { range: 0..16, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..15, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,12 +35,15 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..14, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..14, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 8..13, value: StringLiteralValue { inner: Concatenated( @@ -43,6 +51,7 @@ expression: suite strings: [ StringLiteral { range: 8..10, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -52,6 +61,7 @@ expression: suite }, StringLiteral { range: 11..13, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap index 5b4aebba0663c1..479ea844b6436a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..15, value: FStringValue { inner: Single( FString( FString { range: 0..15, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..14, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,12 +35,15 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..12, id: Name("spec"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap index c9ab599e07ce3c..c9ef305e708b56 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..13, value: FStringValue { inner: Single( FString( FString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,17 +35,21 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..11, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 8..10, value: StringLiteralValue { inner: Single( StringLiteral { range: 8..10, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap index b631befca65b95..42ef942c167875 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_equals.snap @@ -5,24 +5,30 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..11, value: FStringValue { inner: Single( FString( FString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..10, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 3..9, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3..4, value: Int( 1, @@ -35,6 +41,7 @@ expression: suite comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 2, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap index 93308bd0ca7fd2..3b428b414876ae 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..13, value: FStringValue { inner: Single( FString( FString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,10 +35,12 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..11, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 7..11, + node_index: AtomicNodeIndex(..), value: "spec", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap index 2d72726265ed3c..764c3768a40123 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_prec_space.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..10, value: FStringValue { inner: Single( FString( FString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap index e11b44be8ab883..869d7ff2d3984e 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_self_doc_trailing_space.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..10, value: FStringValue { inner: Single( FString( FString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap index bcd14124f194c1..1989651e7ff014 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_yield_expr.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..10, value: FStringValue { inner: Single( FString( FString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 3..8, value: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap index 11f5a0bd1f674c..c5e7745a97c27b 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap @@ -1,14 +1,15 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..16, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..16, value: StringLiteralValue { inner: Concatenated( @@ -16,6 +17,7 @@ snapshot_kind: text strings: [ StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -25,6 +27,7 @@ snapshot_kind: text }, StringLiteral { range: 9..16, + node_index: AtomicNodeIndex(..), value: "world", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap index cdf62c658ad5be..295d3fd63490f9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..20, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..20, + node_index: AtomicNodeIndex(..), value: "Hello, world!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap index cc79ec38df9696..e09433deb3d689 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_1.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..17, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite TString( TString { range: 9..17, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap index cc79ec38df9696..e09433deb3d689 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_2.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..17, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite TString( TString { range: 9..17, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap index 080c41beec2151..3497f4897dde2a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_3.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..22, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,23 +29,28 @@ expression: suite TString( TString { range: 9..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), Interpolation( InterpolatedElement { range: 16..21, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 17..20, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap index 7d178f95699f8a..94159277818063 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_t_string_concat_4.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..31, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..31, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,23 +29,28 @@ expression: suite TString( TString { range: 9..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 11..16, + node_index: AtomicNodeIndex(..), value: "world", }, ), Interpolation( InterpolatedElement { range: 16..21, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 17..20, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Double, @@ -70,6 +78,7 @@ expression: suite Literal( StringLiteral { range: 23..31, + node_index: AtomicNodeIndex(..), value: "again!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap index 999de0e0e86ccc..343661c2b0f6d4 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..18, value: TStringValue { inner: Single( TString( TString { range: 0..18, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..5, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("a"), ctx: Load, @@ -33,8 +38,10 @@ expression: suite Interpolation( InterpolatedElement { range: 5..10, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("b"), ctx: Load, @@ -48,6 +55,7 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 10..17, + node_index: AtomicNodeIndex(..), value: "{foo}", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap index 2618c0abf757c8..676e05c163590b 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_equals.snap @@ -5,24 +5,30 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..13, value: TStringValue { inner: Single( TString( TString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 3..11, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3..5, value: Int( 42, @@ -35,6 +41,7 @@ expression: suite comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..11, value: Int( 42, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap index 26719dcb0a0e75..7154789c31cbec 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_concatenation_string_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..16, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..16, value: TStringValue { inner: Single( TString( TString { range: 0..16, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..15, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,12 +35,15 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..14, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..14, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 8..13, value: StringLiteralValue { inner: Concatenated( @@ -43,6 +51,7 @@ expression: suite strings: [ StringLiteral { range: 8..10, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -52,6 +61,7 @@ expression: suite }, StringLiteral { range: 11..13, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap index 0bd592171b7184..68bb64bd871add 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..15, value: TStringValue { inner: Single( TString( TString { range: 0..15, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..14, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,12 +35,15 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..12, id: Name("spec"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap index cfa89174c8c6b2..e860374bd05fd9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_nested_string_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..13, value: TStringValue { inner: Single( TString( TString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,17 +35,21 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..11, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 8..10, value: StringLiteralValue { inner: Single( StringLiteral { range: 8..10, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap index 96bc26e6fc47ae..4747eef4cd3cf1 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_equals.snap @@ -5,24 +5,30 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..11, value: TStringValue { inner: Single( TString( TString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..10, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 3..9, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3..4, value: Int( 1, @@ -35,6 +41,7 @@ expression: suite comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 2, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap index b77e560ea214d1..360789335eff9c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_not_nested_spec.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..13, value: TStringValue { inner: Single( TString( TString { range: 0..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("foo"), ctx: Load, @@ -30,10 +35,12 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 7..11, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 7..11, + node_index: AtomicNodeIndex(..), value: "spec", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap index dc1558c8d23bfd..cd25297e73a19f 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_prec_space.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..10, value: TStringValue { inner: Single( TString( TString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap index a6c8b8849b3493..ab568959489941 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_self_doc_trailing_space.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..10, value: TStringValue { inner: Single( TString( TString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap index 7693375a34292f..bb265f1a94cc55 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_tstring_yield_expr.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..10, value: TStringValue { inner: Single( TString( TString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 3..8, value: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap index 31a9098fde576a..0b9244c7303621 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..18, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite FString( FString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap index 04c02e6462f910..76de944babaf55 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..22, value: FStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite FString( FString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), @@ -44,6 +49,7 @@ expression: suite Literal( StringLiteral { range: 19..22, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap index ea637bffc33849..b6c48ef5d8ba45 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap @@ -1,14 +1,15 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteralValue { inner: Concatenated( @@ -16,6 +17,7 @@ snapshot_kind: text strings: [ StringLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -25,6 +27,7 @@ snapshot_kind: text }, StringLiteral { range: 9..17, + node_index: AtomicNodeIndex(..), value: "world", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap index 7748193e5befae..7ad459df3f5a8d 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap @@ -1,14 +1,15 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteralValue { inner: Concatenated( @@ -16,6 +17,7 @@ snapshot_kind: text strings: [ StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -25,6 +27,7 @@ snapshot_kind: text }, StringLiteral { range: 10..17, + node_index: AtomicNodeIndex(..), value: "world", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap index 0fd3e39703c8d2..0a72333dda37c0 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_1.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..18, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite TString( TString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap index ae5721a93181b2..ac4d44cd3ff4e9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_t_string_concat_2.snap @@ -5,9 +5,11 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..22, value: TStringValue { inner: Concatenated( @@ -15,6 +17,7 @@ expression: suite Literal( StringLiteral { range: 0..9, + node_index: AtomicNodeIndex(..), value: "Hello ", flags: StringLiteralFlags { quote_style: Single, @@ -26,10 +29,12 @@ expression: suite TString( TString { range: 10..18, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 12..17, + node_index: AtomicNodeIndex(..), value: "world", }, ), @@ -44,6 +49,7 @@ expression: suite Literal( StringLiteral { range: 19..22, + node_index: AtomicNodeIndex(..), value: "!", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap index 68da7f5aaa72e1..8c3cd6bc04657b 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..8, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..8, + node_index: AtomicNodeIndex(..), value: [ 92, 120, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap index 6cf0a69a020748..f3a05cb29727f3 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..6, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..6, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..6, + node_index: AtomicNodeIndex(..), value: [ 92, 92, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap index d835921a9ae08e..7156cd907306f3 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..7, value: FStringValue { inner: Single( FString( FString { range: 0..7, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 3..6, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap index 5f16d7cc6bdf9c..50449bdc140401 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_tstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..7, value: TStringValue { inner: Single( TString( TString { range: 0..7, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 3..6, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap index 921213a707749b..b85749f0dc0f53 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..738, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..738, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..738, + node_index: AtomicNodeIndex(..), value: [ 0, 1, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_mac_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_mac_eol.snap index 67456edf4fa4a5..432c43fcf4195c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_mac_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_mac_eol.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..18, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..18, + node_index: AtomicNodeIndex(..), value: "text more text", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_unix_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_unix_eol.snap index 67456edf4fa4a5..432c43fcf4195c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_unix_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_unix_eol.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..18, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..18, + node_index: AtomicNodeIndex(..), value: "text more text", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_windows_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_windows_eol.snap index 46afa1625beb9b..dfaefc86f8ac24 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_windows_eol.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__string_parser_escaped_windows_eol.snap @@ -1,19 +1,21 @@ --- source: crates/ruff_python_parser/src/string.rs expression: suite -snapshot_kind: text --- [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..19, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..19, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..19, + node_index: AtomicNodeIndex(..), value: "text more text", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap index 2c0adae7d11126..a65a7d4b3945f6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..11, value: FStringValue { inner: Single( FString( FString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap index 7a383de39c53e1..afbf6c6688efa4 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_tstring.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..11, value: TStringValue { inner: Single( TString( TString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap index 6227dea0fee749..8e099c0f668bc7 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_constant_range.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..22, value: TStringValue { inner: Single( TString( TString { range: 0..22, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..5, + node_index: AtomicNodeIndex(..), value: "aaa", }, ), Interpolation( InterpolatedElement { range: 5..10, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("bbb"), ctx: Load, @@ -39,14 +45,17 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 10..13, + node_index: AtomicNodeIndex(..), value: "ccc", }, ), Interpolation( InterpolatedElement { range: 13..18, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("ddd"), ctx: Load, @@ -60,6 +69,7 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 18..21, + node_index: AtomicNodeIndex(..), value: "eee", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap index b72088efc89763..d65d8f0a38515a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_character.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..8, value: TStringValue { inner: Single( TString( TString { range: 0..8, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..4, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 4..7, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap index d34b25231ff518..7b3fa584575c0c 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_escaped_newline.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..8, value: TStringValue { inner: Single( TString( TString { range: 0..8, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..4, + node_index: AtomicNodeIndex(..), value: "\n", }, ), Interpolation( InterpolatedElement { range: 4..7, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap index 396755f9857ef5..d285f2ff882b88 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_line_continuation.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..9, value: TStringValue { inner: Single( TString( TString { range: 0..9, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 3..5, + node_index: AtomicNodeIndex(..), value: "\\\n", }, ), Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap index 99398a48b1fc11..2540e8745ba15e 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..10, value: TStringValue { inner: Single( TString( TString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("user"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap index a745f0f639bd90..2bb2303078ef49 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_base_more.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..38, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..38, value: TStringValue { inner: Single( TString( TString { range: 0..38, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..6, + node_index: AtomicNodeIndex(..), value: "mix ", }, ), Interpolation( InterpolatedElement { range: 6..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..11, id: Name("user"), ctx: Load, @@ -44,14 +50,17 @@ expression: suite Literal( InterpolatedStringLiteralElement { range: 13..28, + node_index: AtomicNodeIndex(..), value: " with text and ", }, ), Interpolation( InterpolatedElement { range: 28..37, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..35, id: Name("second"), ctx: Load, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap index a401f11e744023..43e424c4bc7e59 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_parse_self_documenting_format.snap @@ -5,21 +5,26 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..14, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..14, value: TStringValue { inner: Single( TString( TString { range: 0..14, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..13, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("user"), ctx: Load, @@ -35,10 +40,12 @@ expression: suite format_spec: Some( InterpolatedStringFormatSpec { range: 9..12, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 9..12, + node_index: AtomicNodeIndex(..), value: ">10", }, ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap index b14e366fb44086..188b88a0d3ef76 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__tstring_unescaped_newline.snap @@ -5,27 +5,33 @@ expression: suite [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 0..11, value: TStringValue { inner: Single( TString( TString { range: 0..11, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 4..5, + node_index: AtomicNodeIndex(..), value: "\n", }, ), Interpolation( InterpolatedElement { range: 5..8, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/src/string.rs b/crates/ruff_python_parser/src/string.rs index 6642fbc25a5506..8dd9190b90057f 100644 --- a/crates/ruff_python_parser/src/string.rs +++ b/crates/ruff_python_parser/src/string.rs @@ -3,7 +3,7 @@ use bstr::ByteSlice; use std::fmt; -use ruff_python_ast::{self as ast, AnyStringFlags, Expr, StringFlags}; +use ruff_python_ast::{self as ast, AnyStringFlags, AtomicNodeIndex, Expr, StringFlags}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::{ @@ -287,6 +287,7 @@ impl StringParser { return Ok(ast::InterpolatedStringLiteralElement { value: self.source, range: self.range, + node_index: AtomicNodeIndex::dummy(), }); }; @@ -364,6 +365,7 @@ impl StringParser { Ok(ast::InterpolatedStringLiteralElement { value: value.into_boxed_str(), range: self.range, + node_index: AtomicNodeIndex::dummy(), }) } @@ -385,6 +387,7 @@ impl StringParser { value: self.source.into_boxed_bytes(), range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })); } @@ -394,6 +397,7 @@ impl StringParser { value: self.source.into_boxed_bytes(), range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })); }; @@ -431,6 +435,7 @@ impl StringParser { value: value.into_boxed_slice(), range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })) } @@ -441,6 +446,7 @@ impl StringParser { value: self.source, range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })); } @@ -450,6 +456,7 @@ impl StringParser { value: self.source, range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })); }; @@ -487,6 +494,7 @@ impl StringParser { value: value.into_boxed_str(), range: self.range, flags: self.flags.into(), + node_index: AtomicNodeIndex::dummy(), })) } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap index b758b7438b649c..172e4daa89c020 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_annotation.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_inval ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..63, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..11, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -21,9 +24,11 @@ Module( ), annotation: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3..7, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..7, id: Name("int"), ctx: Load, @@ -35,6 +40,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 1, @@ -47,9 +53,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 12..26, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("x"), ctx: Store, @@ -57,10 +65,12 @@ Module( ), annotation: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 15..22, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("a"), ctx: Load, @@ -72,6 +82,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 1, @@ -84,9 +95,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 27..46, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Store, @@ -94,9 +107,11 @@ Module( ), annotation: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 30..42, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("b"), ctx: Load, @@ -107,6 +122,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 1, @@ -119,9 +135,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 47..51, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -129,6 +147,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..51, id: Name("y"), ctx: Load, @@ -140,10 +159,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 55..62, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..58, id: Name("int"), ctx: Store, @@ -152,6 +173,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_target.py.snap index 9bd4841679c938..4a80fdc2f52b3b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_target.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_invalid_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..170, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..18, target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..5, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..5, + node_index: AtomicNodeIndex(..), value: "abc", flags: StringLiteralFlags { quote_style: Double, @@ -33,6 +36,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..10, id: Name("str"), ctx: Load, @@ -41,11 +45,13 @@ Module( value: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 13..18, value: StringLiteralValue { inner: Single( StringLiteral { range: 13..18, + node_index: AtomicNodeIndex(..), value: "def", flags: StringLiteralFlags { quote_style: Double, @@ -63,12 +69,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 19..37, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 19..25, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..23, id: Name("call"), ctx: Load, @@ -76,6 +85,7 @@ Module( ), arguments: Arguments { range: 23..25, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -83,6 +93,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..30, id: Name("str"), ctx: Load, @@ -91,11 +102,13 @@ Module( value: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 33..37, value: StringLiteralValue { inner: Single( StringLiteral { range: 33..37, + node_index: AtomicNodeIndex(..), value: "no", flags: StringLiteralFlags { quote_style: Double, @@ -113,12 +126,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 38..52, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 38..40, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Store, @@ -129,6 +145,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..45, id: Name("int"), ctx: Load, @@ -137,10 +154,12 @@ Module( value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 48..52, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 48..49, value: Int( 1, @@ -149,6 +168,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 2, @@ -166,13 +186,16 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 72..83, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 72..74, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("x"), ctx: Store, @@ -185,6 +208,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("int"), ctx: Load, @@ -193,6 +217,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 1, @@ -205,13 +230,16 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 84..100, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 84..88, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..85, id: Name("x"), ctx: Store, @@ -219,6 +247,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("y"), ctx: Store, @@ -231,6 +260,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..93, id: Name("int"), ctx: Load, @@ -239,10 +269,12 @@ Module( value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 96..100, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 96..97, value: Int( 1, @@ -251,6 +283,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 99..100, value: Int( 2, @@ -268,13 +301,16 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 101..119, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 101..107, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("x"), ctx: Store, @@ -282,6 +318,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("y"), ctx: Store, @@ -294,6 +331,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..112, id: Name("int"), ctx: Load, @@ -302,10 +340,12 @@ Module( value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 115..119, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 1, @@ -314,6 +354,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 2, @@ -331,13 +372,16 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 138..150, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 138..141, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("x"), ctx: Store, @@ -349,6 +393,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..146, id: Name("int"), ctx: Load, @@ -357,6 +402,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 1, @@ -369,13 +415,16 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 151..169, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 151..157, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..153, id: Name("x"), ctx: Store, @@ -383,6 +432,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("y"), ctx: Store, @@ -394,6 +444,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..162, id: Name("int"), ctx: Load, @@ -402,10 +453,12 @@ Module( value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 165..169, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 165..166, value: Int( 1, @@ -414,6 +467,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 168..169, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_value.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_value.py.snap index f37b524ee3717e..1d6945367089ef 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_value.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_invalid_value.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_invalid_value.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..65, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..17, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -22,6 +24,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("Any"), ctx: Load, @@ -30,14 +33,17 @@ Module( value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 9..17, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 10..17, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("a"), ctx: Load, @@ -45,6 +51,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("b"), ctx: Load, @@ -62,9 +69,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 18..28, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("x"), ctx: Store, @@ -72,6 +81,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..24, id: Name("Any"), ctx: Load, @@ -80,6 +90,7 @@ Module( value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Load, @@ -91,9 +102,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..33, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, @@ -104,9 +117,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 34..64, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Store, @@ -114,6 +129,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..41, id: Name("list"), ctx: Load, @@ -122,10 +138,12 @@ Module( value: Some( List( ExprList { + node_index: AtomicNodeIndex(..), range: 44..64, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("x"), ctx: Load, @@ -133,12 +151,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 48..54, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 49..54, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("a"), ctx: Load, @@ -147,6 +168,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("b"), ctx: Load, @@ -159,14 +181,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 56..63, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 57..63, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("a"), ctx: Load, @@ -174,6 +199,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_missing_rhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_missing_rhs.py.snap index dfd20d1d11e076..b1a670039436f4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_missing_rhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_missing_rhs.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_missing_rhs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..9, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..8, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -22,6 +24,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_type_alias_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_type_alias_annotation.py.snap index 304d630374a0ae..957104e6d291b5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_type_alias_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@ann_assign_stmt_type_alias_annotation.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/ann_assign_stmt_type_alias_annotation.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..37, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..7, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Store, @@ -22,6 +24,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("type"), ctx: Load, @@ -33,10 +36,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 8..15, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("X"), ctx: Store, @@ -45,6 +50,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..15, id: Name("int"), ctx: Load, @@ -54,13 +60,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..28, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 16..28, parameters: None, body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..28, id: Name("type"), ctx: Load, @@ -72,10 +81,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 29..36, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("X"), ctx: Store, @@ -84,6 +95,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..36, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap index b4050ccf1348f7..745c8c8c402f84 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/args_unparenthesized_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..28, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..28, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..3, id: Name("sum"), ctx: Load, @@ -24,12 +28,15 @@ Module( ), arguments: Arguments { range: 3..28, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 4..24, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -38,8 +45,10 @@ Module( generators: [ Comprehension { range: 6..24, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Store, @@ -47,9 +56,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 15..24, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..20, id: Name("range"), ctx: Load, @@ -57,9 +68,11 @@ Module( ), arguments: Arguments { range: 20..24, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..23, value: Int( 10, @@ -80,6 +93,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..27, value: Int( 5, @@ -95,12 +109,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..64, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 29..64, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..34, id: Name("total"), ctx: Load, @@ -108,9 +125,11 @@ Module( ), arguments: Arguments { range: 34..64, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 35..36, value: Int( 1, @@ -119,6 +138,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..39, value: Int( 2, @@ -127,9 +147,11 @@ Module( ), Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 41..60, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Load, @@ -138,8 +160,10 @@ Module( generators: [ Comprehension { range: 43..60, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -147,9 +171,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 52..60, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..57, id: Name("range"), ctx: Load, @@ -157,9 +183,11 @@ Module( ), arguments: Arguments { range: 57..60, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 58..59, value: Int( 5, @@ -180,6 +208,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 62..63, value: Int( 6, @@ -195,12 +224,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..91, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 65..91, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..68, id: Name("sum"), ctx: Load, @@ -208,12 +240,15 @@ Module( ), arguments: Arguments { range: 68..91, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 69..89, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..70, id: Name("x"), ctx: Load, @@ -222,8 +257,10 @@ Module( generators: [ Comprehension { range: 71..89, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Store, @@ -231,9 +268,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 80..89, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..85, id: Name("range"), ctx: Load, @@ -241,9 +280,11 @@ Module( ), arguments: Arguments { range: 85..89, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..88, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_msg.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_msg.py.snap index 0de6bbd3f82f91..e4f9844f739ee5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_msg.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_msg.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assert_empty_msg.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 0..9, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_test.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_test.py.snap index 24dfb690d2c7d3..8722a03c2e652d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_test.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_empty_test.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assert_empty_test.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..7, body: [ Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 0..6, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..6, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_msg_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_msg_expr.py.snap index 557cbe6bf3f37d..bfef69718472ff 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_msg_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_msg_expr.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assert_invalid_msg_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..83, body: [ Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 0..16, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 7..12, value: false, }, @@ -22,9 +24,11 @@ Module( msg: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 14..16, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("x"), ctx: Load, @@ -38,9 +42,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 17..30, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 24..29, value: false, }, @@ -50,9 +56,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 31..39, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("x"), ctx: Load, @@ -63,9 +71,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 40..61, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 47..52, value: false, }, @@ -73,10 +83,12 @@ Module( msg: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 54..61, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("x"), ctx: Load, @@ -90,9 +102,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 62..77, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 69..74, value: false, }, @@ -100,6 +114,7 @@ Module( msg: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..77, id: Name("x"), ctx: Load, @@ -110,9 +125,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..82, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_test_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_test_expr.py.snap index afe4aca2ba14c4..1f87e7e7b68577 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_test_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assert_invalid_test_expr.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assert_invalid_test_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..55, body: [ Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 0..9, test: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 7..9, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("x"), ctx: Load, @@ -31,9 +34,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 10..23, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..23, id: Name("assert"), ctx: Load, @@ -44,9 +49,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..25, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -56,13 +63,16 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 26..40, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 33..40, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Load, @@ -76,9 +86,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 41..49, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("x"), ctx: Load, @@ -89,9 +101,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..54, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_target.py.snap index 8f47b9c0422d7b..e317e4b49c9e57 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_target.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_invalid_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..58, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..5, targets: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 0..1, value: Int( 1, @@ -25,6 +27,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -35,10 +38,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 6..15, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Store, @@ -46,6 +51,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 1, @@ -55,6 +61,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 14..15, value: Int( 2, @@ -65,10 +72,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 16..33, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Store, @@ -76,6 +85,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 1, @@ -84,6 +94,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("y"), ctx: Store, @@ -91,6 +102,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 2, @@ -100,6 +112,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("z"), ctx: Load, @@ -109,19 +122,23 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 34..57, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 34..44, elts: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 35..38, value: StringLiteralValue { inner: Single( StringLiteral { range: 35..38, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -135,11 +152,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 40..43, value: StringLiteralValue { inner: Single( StringLiteral { range: 40..43, + node_index: AtomicNodeIndex(..), value: "b", flags: StringLiteralFlags { quote_style: Double, @@ -158,15 +177,18 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 47..57, elts: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 48..51, value: StringLiteralValue { inner: Single( StringLiteral { range: 48..51, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -180,11 +202,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 53..56, value: StringLiteralValue { inner: Single( StringLiteral { range: 53..56, + node_index: AtomicNodeIndex(..), value: "b", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_value_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_value_expr.py.snap index 78ea01ad7e616f..65ac6baf43b08e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_value_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_invalid_value_expr.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_invalid_v ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..90, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..15, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,18 +26,22 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 4..15, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 5..13, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 6..13, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("a"), ctx: Load, @@ -42,6 +49,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("b"), ctx: Load, @@ -62,10 +70,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 16..34, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Store, @@ -74,10 +84,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 20..34, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..23, value: Int( 42, @@ -86,13 +98,16 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 25..33, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 26..33, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("x"), ctx: Load, @@ -113,10 +128,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 35..58, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("x"), ctx: Store, @@ -125,10 +142,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 39..58, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 40..42, value: Int( 42, @@ -137,12 +156,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 44..57, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 45..57, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -162,10 +184,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 59..78, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("x"), ctx: Store, @@ -174,26 +198,35 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 63..78, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 64..76, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 65..76, parameters: Some( Parameters { range: 72..73, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 72..73, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 72..73, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 72..73, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -207,6 +240,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Load, @@ -226,10 +260,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 79..84, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("x"), ctx: Store, @@ -238,6 +274,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -247,9 +284,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 88..89, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_keyword_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_keyword_target.py.snap index 0ed03205762572..0f58eff527dfa2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_keyword_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_keyword_target.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_keyword_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..42, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..12, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..8, id: Name("pass"), ctx: Store, @@ -31,6 +34,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("c"), ctx: Load, @@ -40,12 +44,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..18, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 13..18, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("a"), ctx: Load, @@ -54,6 +61,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("b"), ctx: Load, @@ -65,10 +73,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 19..35, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("a"), ctx: Store, @@ -76,6 +86,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("b"), ctx: Store, @@ -83,6 +94,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..31, id: Name("pass"), ctx: Store, @@ -91,6 +103,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("c"), ctx: Load, @@ -100,12 +113,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..41, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 36..41, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("a"), ctx: Load, @@ -114,6 +130,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_missing_rhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_missing_rhs.py.snap index 35e19216452624..6ef39d80456340 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_missing_rhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_missing_rhs.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_missing_rhs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..3, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -24,6 +26,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -33,12 +36,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..9, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4..9, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -48,6 +54,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 1, @@ -60,10 +67,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 10..17, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Store, @@ -71,6 +80,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("y"), ctx: Store, @@ -79,6 +89,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..17, id: Name(""), ctx: Invalid, @@ -88,12 +99,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..23, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 18..23, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 2, @@ -103,6 +117,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 22..23, value: Int( 2, @@ -115,10 +130,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 24..31, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Store, @@ -126,6 +143,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..27, id: Name(""), ctx: Store, @@ -134,6 +152,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..31, id: Name("y"), ctx: Load, @@ -143,12 +162,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..37, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 32..37, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 3, @@ -158,6 +180,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..37, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_starred_expr_value.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_starred_expr_value.py.snap index 220f6e607436a2..5af1853e866612 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_starred_expr_value.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@assign_stmt_starred_expr_value.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_starred_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..45, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..9, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("_"), ctx: Store, @@ -23,13 +26,16 @@ Module( ], value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 4..9, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 5..9, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..8, value: Int( 42, @@ -47,10 +53,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 10..19, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("_"), ctx: Store, @@ -59,13 +67,16 @@ Module( ], value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 14..19, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 15..19, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..18, value: Int( 42, @@ -82,10 +93,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 20..31, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("_"), ctx: Store, @@ -94,12 +107,15 @@ Module( ], value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 24..31, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 25..31, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..29, id: Name("list"), ctx: Load, @@ -107,6 +123,7 @@ Module( ), arguments: Arguments { range: 29..31, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -119,10 +136,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 32..44, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("_"), ctx: Store, @@ -131,12 +150,15 @@ Module( ], value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 36..44, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 38..43, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("p"), ctx: Load, @@ -145,6 +167,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("q"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@async_unexpected_token.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@async_unexpected_token.py.snap index 678e667d880d7a..7ebaaad4b78a1c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@async_unexpected_token.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@async_unexpected_token.py.snap @@ -1,31 +1,35 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/async_unexpected_token.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..116, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 6..20, decorator_list: [], name: Identifier { id: Name("Foo"), range: 12..15, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), @@ -36,9 +40,11 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 27..42, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..37, id: Name("test"), ctx: Load, @@ -47,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..42, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 39..42, }, ), @@ -61,10 +69,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 49..54, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("x"), ctx: Store, @@ -73,6 +83,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 1, @@ -83,16 +94,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 61..81, is_async: true, decorator_list: [], name: Identifier { id: Name("foo"), range: 71..74, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 74..76, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -103,9 +119,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..81, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 78..81, }, ), @@ -116,9 +134,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 88..115, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..98, id: Name("test"), ctx: Load, @@ -127,9 +147,11 @@ Module( cases: [ MatchCase { range: 104..115, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 109..110, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -138,9 +160,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 112..115, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 112..115, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_target.py.snap index 17b749427a5497..78da56cda60cef 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_target.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/aug_assign_stmt_invalid_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..59, body: [ AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 0..6, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 0..1, value: Int( 1, @@ -24,6 +26,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -34,14 +37,17 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 7..17, target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 7..10, value: StringLiteralValue { inner: Single( StringLiteral { range: 7..10, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -56,11 +62,13 @@ Module( op: Add, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 14..17, value: StringLiteralValue { inner: Single( StringLiteral { range: 14..17, + node_index: AtomicNodeIndex(..), value: "b", flags: StringLiteralFlags { quote_style: Double, @@ -76,12 +84,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 18..25, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 18..20, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("x"), ctx: Store, @@ -93,6 +104,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -103,14 +115,17 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 26..30, }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..35, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 34..35, value: Int( 1, @@ -121,9 +136,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 36..45, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("x"), ctx: Store, @@ -132,6 +149,7 @@ Module( op: Add, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..45, id: Name("pass"), ctx: Load, @@ -141,12 +159,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 46..58, target: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 47..52, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, @@ -155,6 +176,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("y"), ctx: Load, @@ -165,6 +187,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_value.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_value.py.snap index cd6e0380f95668..cfc583f1b86b60 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_value.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_invalid_value.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/aug_assign_stmt_invalid_value.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..77, body: [ AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 0..13, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,14 +25,17 @@ Module( op: Add, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 5..13, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 6..13, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("a"), ctx: Load, @@ -38,6 +43,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("b"), ctx: Load, @@ -53,9 +59,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 14..27, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Store, @@ -64,13 +72,16 @@ Module( op: Add, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 19..27, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 20..27, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("x"), ctx: Load, @@ -86,9 +97,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 28..46, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("x"), ctx: Store, @@ -97,12 +110,15 @@ Module( op: Add, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 33..46, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 34..46, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("x"), ctx: Load, @@ -117,9 +133,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 47..64, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -128,22 +146,30 @@ Module( op: Add, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 52..64, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 53..64, parameters: Some( Parameters { range: 60..61, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 60..61, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 60..61, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 60..61, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -157,6 +183,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("x"), ctx: Load, @@ -171,9 +198,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 65..71, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Store, @@ -182,6 +211,7 @@ Module( op: Add, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("y"), ctx: Load, @@ -191,9 +221,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..76, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_missing_rhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_missing_rhs.py.snap index d2ba825987081b..50e27a1c5e9a7c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_missing_rhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@aug_assign_stmt_missing_rhs.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/aug_assign_stmt_missing_rhs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..27, body: [ AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 0..4, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,6 +25,7 @@ Module( op: Add, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..4, id: Name(""), ctx: Invalid, @@ -32,12 +35,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -47,6 +53,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, @@ -59,9 +66,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 11..17, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("x"), ctx: Store, @@ -70,6 +79,7 @@ Module( op: Add, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("y"), ctx: Load, @@ -79,12 +89,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..26, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 21..26, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 2, @@ -94,6 +107,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@case_expect_indented_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@case_expect_indented_block.py.snap index 48c0bba70c1fe4..0e0305e26b406f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@case_expect_indented_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@case_expect_indented_block.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/case_expect_indented_block.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..42, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -23,11 +25,14 @@ Module( cases: [ MatchCase { range: 19..26, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 24..25, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -41,11 +46,14 @@ Module( }, MatchCase { range: 31..42, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 36..37, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..37, value: Int( 2, @@ -58,9 +66,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..42, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 39..42, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_empty_body.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_empty_body.py.snap index 29be135fec38d7..48a479382f0af8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_empty_body.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_empty_body.py.snap @@ -1,22 +1,24 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/class_def_empty_body.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..10, decorator_list: [], name: Identifier { id: Name("Foo"), range: 6..9, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, @@ -25,16 +27,19 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 11..23, decorator_list: [], name: Identifier { id: Name("Foo"), range: 17..20, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 20..22, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -44,10 +49,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 24..30, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Store, @@ -56,6 +63,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..30, value: Int( 42, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_missing_name.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_missing_name.py.snap index 05c4ed0abcc3b5..6d68771f17f6f8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_missing_name.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_missing_name.py.snap @@ -1,31 +1,35 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/class_def_missing_name.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..11, decorator_list: [], name: Identifier { id: Name(""), range: 5..5, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..11, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 8..11, }, ), @@ -36,16 +40,19 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 12..25, decorator_list: [], name: Identifier { id: Name(""), range: 17..17, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 18..20, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -53,9 +60,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..25, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 22..25, }, ), @@ -66,28 +75,34 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 26..52, decorator_list: [], name: Identifier { id: Name(""), range: 31..31, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 32..47, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 33..46, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("metaclass"), range: 33..42, + node_index: AtomicNodeIndex(..), }, ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("ABC"), ctx: Load, @@ -100,9 +115,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_unclosed_type_param_list.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_unclosed_type_param_list.py.snap index bcbd8a0224d82b..17f5d5b1061406 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_unclosed_type_param_list.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_def_unclosed_type_param_list.py.snap @@ -1,33 +1,38 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/class_def_unclosed_type_param_list.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..33, decorator_list: [], name: Identifier { id: Name("Foo"), range: 6..9, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 9..17, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 10..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T1"), range: 10..12, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -36,9 +41,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 14..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 15..17, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -49,9 +56,11 @@ Module( arguments: Some( Arguments { range: 17..23, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("a"), ctx: Load, @@ -59,6 +68,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("b"), ctx: Load, @@ -71,6 +81,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 29..33, }, ), @@ -79,10 +90,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 34..40, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Store, @@ -91,6 +104,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..40, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap index 60c7fd49741e27..9d91dbf072625b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap @@ -7,34 +7,42 @@ input_file: crates/ruff_python_parser/resources/inline/err/class_type_params_py3 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..113, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 44..95, decorator_list: [], name: Identifier { id: Name("Foo"), range: 50..53, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 53..90, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 54..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("S"), range: 54..55, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 57..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..61, id: Name("str"), ctx: Load, @@ -42,6 +50,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..68, id: Name("bytes"), ctx: Load, @@ -59,13 +68,16 @@ Module( TypeVar( TypeParamTypeVar { range: 71..79, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 71..72, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..79, id: Name("float"), ctx: Load, @@ -78,9 +90,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 81..84, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 82..84, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -88,9 +102,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 86..89, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 88..89, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -102,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..95, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 92..95, }, ), @@ -115,15 +133,18 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 96..112, decorator_list: [], name: Identifier { id: Name("Foo"), range: 102..105, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 105..107, + node_index: AtomicNodeIndex(..), type_params: [], }, ), @@ -131,9 +152,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..112, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_indented_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_indented_block.py.snap index 44bc4b2b510a63..52dcef4c1806c5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_indented_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_indented_block.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/clause_expect_indented_block.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..171, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 53..61, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 56..60, value: true, }, @@ -25,14 +27,17 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 62..66, }, ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 162..170, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 165..169, value: true, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_single_statement.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_single_statement.py.snap index 6ed8d3277491b2..0cec1c5726cf58 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_single_statement.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@clause_expect_single_statement.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/clause_expect_single_statement.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..23, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..8, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -25,9 +27,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 9..22, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 12..16, value: true, }, @@ -35,6 +39,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 18..22, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma.py.snap index fee330ecc28b22..749a5fa1ae447e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/comma_separated_missing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..15, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..14, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..14, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..14, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 12..13, value: Int( 1, @@ -38,9 +43,11 @@ Module( keywords: [ Keyword { range: 5..8, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma_between_elements.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma_between_elements.py.snap index 39ffd3982ff4cc..3afc0336c3391b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma_between_elements.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_comma_between_elements.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/comma_separated_missing_comma_between_elements.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..91, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 83..91, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 84..85, value: Int( 0, @@ -27,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 87..88, value: Int( 1, @@ -35,6 +39,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_element_between_commas.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_element_between_commas.py.snap index 799432dc3f042c..0e1f29a3c074e4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_element_between_commas.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_element_between_commas.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/comma_separated_missing_element_between_commas.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..12, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 0..11, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1..2, value: Int( 0, @@ -27,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -35,6 +39,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_first_element.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_first_element.py.snap index 59fb82a1e5c4da..02c89d0035bd8a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_first_element.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comma_separated_missing_first_element.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/comma_separated_missing_first_element.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..9, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..9, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comprehension_missing_for_after_async.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comprehension_missing_for_after_async.py.snap index 8713ae5f80d9e8..636c0bdbac66da 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comprehension_missing_for_after_async.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@comprehension_missing_for_after_async.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/comprehension_missing_for_after_async.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..6, id: Name("async"), ctx: Load, @@ -24,12 +26,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..27, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 8..27, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -38,8 +43,10 @@ Module( generators: [ Comprehension { range: 11..26, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Store, @@ -47,6 +54,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..26, id: Name("iter"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_class.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_class.py.snap index 251a2c6c281192..91477355a39eef 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_class.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_class.py.snap @@ -7,24 +7,29 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_class.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..82, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..20, decorator_list: [], name: Identifier { id: Name("__debug__"), range: 6..15, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), @@ -35,22 +40,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 35..58, decorator_list: [], name: Identifier { id: Name("C"), range: 41..42, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 42..53, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 43..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 43..52, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -63,9 +73,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..58, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 55..58, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_function.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_function.py.snap index 517c64d7fb58be..f31edee08ac344 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_function.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_function.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_function ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..125, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..20, is_async: false, decorator_list: [], name: Identifier { id: Name("__debug__"), range: 4..13, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 13..15, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), @@ -44,23 +52,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 38..61, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 42..43, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 43..54, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 44..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 44..53, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -71,6 +84,9 @@ Module( ), parameters: Parameters { range: 54..56, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -81,9 +97,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -94,25 +112,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 85..106, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 89..90, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 90..101, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 91..100, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 91..100, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 91..100, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -127,9 +153,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..106, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 103..106, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_import.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_import.py.snap index 9cbb40853132a7..380240dd334de8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_import.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_import.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_import.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..100, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..16, names: [ Alias { range: 7..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 7..16, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -26,18 +30,22 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 17..42, names: [ Alias { range: 24..42, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("debug"), range: 24..29, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("__debug__"), range: 33..42, + node_index: AtomicNodeIndex(..), }, ), }, @@ -46,19 +54,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 43..66, module: Some( Identifier { id: Name("x"), range: 48..49, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 57..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 57..66, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -68,24 +80,29 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 67..99, module: Some( Identifier { id: Name("x"), range: 72..73, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 81..99, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("debug"), range: 81..86, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("__debug__"), range: 90..99, + node_index: AtomicNodeIndex(..), }, ), }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_match.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_match.py.snap index 2b23fee0c414a6..acb6dfcc11b03f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_match.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_match.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_match.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..33, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..32, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,14 +25,17 @@ Module( cases: [ MatchCase { range: 13..32, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 18..27, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("__debug__"), range: 18..27, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_try.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_try.py.snap index d965f5306a693a..d638e10acc3e06 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_try.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_try.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_try.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..44, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..8, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5..8, }, ), @@ -28,9 +32,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 9..43, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..25, id: Name("Exception"), ctx: Load, @@ -41,14 +47,17 @@ Module( Identifier { id: Name("__debug__"), range: 29..38, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..43, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 40..43, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_type_alias.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_type_alias.py.snap index 2817caa8c4c1ee..06bce5209a0cb1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_type_alias.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_type_alias.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_type_ali ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..95, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..26, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..14, id: Name("__debug__"), ctx: Store, @@ -22,9 +25,11 @@ Module( type_params: None, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 17..26, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..21, id: Name("list"), ctx: Load, @@ -32,6 +37,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..25, id: Name("int"), ctx: Load, @@ -44,9 +50,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 67..94, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..77, id: Name("Debug"), ctx: Store, @@ -55,13 +63,16 @@ Module( type_params: Some( TypeParams { range: 77..88, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 78..87, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 78..87, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -72,6 +83,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..94, id: Name("str"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_with.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_with.py.snap index 51a3a7cc6446ea..3d0560bf59c366 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_with.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@debug_shadow_with.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/inline/err/debug_shadow_with.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..39, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..38, is_async: false, items: [ WithItem { range: 5..33, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5..20, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..9, id: Name("open"), ctx: Load, @@ -28,14 +33,17 @@ Module( ), arguments: Arguments { range: 9..20, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 10..19, value: StringLiteralValue { inner: Single( StringLiteral { range: 10..19, + node_index: AtomicNodeIndex(..), value: "foo.txt", flags: StringLiteralFlags { quote_style: Double, @@ -55,6 +63,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..33, id: Name("__debug__"), ctx: Store, @@ -66,9 +75,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..38, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 35..38, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_await_expression_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_await_expression_py38.py.snap index e86f1ea1f1d865..9b42c0f68442d6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_await_expression_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_await_expression_py38.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_await_expre ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..96, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..95, is_async: true, decorator_list: [], name: Identifier { id: Name("foo"), range: 55..58, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 58..60, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 66..95, is_async: false, decorator_list: [ Decorator { range: 66..76, + node_index: AtomicNodeIndex(..), expression: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 67..76, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..76, id: Name("bar"), ctx: Load, @@ -53,10 +63,14 @@ Module( name: Identifier { id: Name("baz"), range: 85..88, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 88..90, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -67,9 +81,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..95, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 92..95, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_dict_literal_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_dict_literal_py38.py.snap index e42d100e1f14fe..0262ed63aae0f6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_dict_literal_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_dict_literal_py38.py.snap @@ -7,23 +7,28 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_dict_litera ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..68, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..67, is_async: false, decorator_list: [ Decorator { range: 45..52, + node_index: AtomicNodeIndex(..), expression: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 46..52, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 47..48, value: Int( 3, @@ -33,6 +38,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 50..51, value: Int( 3, @@ -48,10 +54,14 @@ Module( name: Identifier { id: Name("bar"), range: 57..60, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 60..62, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -62,9 +72,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..67, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 64..67, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_expression_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_expression_py38.py.snap index d530df39de0cfe..5d24f31dd4e870 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_expression_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_expression_py38.py.snap @@ -7,26 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_expression_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..89, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..88, is_async: false, decorator_list: [ Decorator { range: 45..72, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..72, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..64, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 46..56, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..53, id: Name("buttons"), ctx: Load, @@ -34,6 +41,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 0, @@ -46,6 +54,7 @@ Module( attr: Identifier { id: Name("clicked"), range: 57..64, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -53,6 +62,7 @@ Module( attr: Identifier { id: Name("connect"), range: 65..72, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -62,10 +72,14 @@ Module( name: Identifier { id: Name("spam"), range: 77..81, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 81..83, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -76,9 +90,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..88, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 85..88, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_float_literal_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_float_literal_py38.py.snap index 8f2af04e275915..666aa7e2baf001 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_float_literal_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_float_literal_py38.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_float_liter ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..66, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..65, is_async: false, decorator_list: [ Decorator { range: 45..50, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 46..50, value: Float( 3.14, @@ -29,10 +33,14 @@ Module( name: Identifier { id: Name("bar"), range: 55..58, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 58..60, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -43,9 +51,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 62..65, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 62..65, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_invalid_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_invalid_expression.py.snap index b89a8c6132395e..a55f5868c20070 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_invalid_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_invalid_expression.py.snap @@ -1,27 +1,31 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/decorator_invalid_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..56, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..55, is_async: false, decorator_list: [ Decorator { range: 0..3, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("x"), ctx: Load, @@ -33,11 +37,14 @@ Module( }, Decorator { range: 4..9, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -49,11 +56,14 @@ Module( }, Decorator { range: 10..17, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Load, @@ -65,12 +75,15 @@ Module( }, Decorator { range: 18..26, + node_index: AtomicNodeIndex(..), expression: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 19..26, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Load, @@ -82,11 +95,14 @@ Module( }, Decorator { range: 27..40, + node_index: AtomicNodeIndex(..), expression: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 28..40, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Load, @@ -99,10 +115,14 @@ Module( name: Identifier { id: Name("foo"), range: 45..48, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 48..50, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -113,9 +133,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..55, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 52..55, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_expression.py.snap index 38f833d0402da8..61a1b2b2202fc5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_expression.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/decorator_missing_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..51, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 5..15, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5..10, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..8, id: Name("foo"), ctx: Load, @@ -25,6 +28,7 @@ Module( ), arguments: Arguments { range: 8..10, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -32,6 +36,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 12..15, }, ), @@ -41,13 +46,16 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 16..32, is_async: false, decorator_list: [ Decorator { range: 16..17, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..17, id: Name(""), ctx: Invalid, @@ -58,10 +66,14 @@ Module( name: Identifier { id: Name("foo"), range: 22..25, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 25..27, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -72,9 +84,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -85,16 +99,20 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 33..50, is_async: false, decorator_list: [ Decorator { range: 33..35, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 34..35, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..34, id: Name(""), ctx: Invalid, @@ -103,6 +121,7 @@ Module( op: MatMult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..35, id: Name(""), ctx: Invalid, @@ -115,10 +134,14 @@ Module( name: Identifier { id: Name("foo"), range: 40..43, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 43..45, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -129,9 +152,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..50, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 47..50, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_newline.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_newline.py.snap index 98cda543d8d290..88a78f186a2f91 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_newline.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_missing_newline.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/decorator_missing_newline.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..60, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..17, is_async: false, decorator_list: [ Decorator { range: 0..2, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -29,10 +32,14 @@ Module( name: Identifier { id: Name("foo"), range: 7..10, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 10..12, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -43,9 +50,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..17, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 14..17, }, ), @@ -56,13 +65,16 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 18..41, is_async: true, decorator_list: [ Decorator { range: 18..20, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("x"), ctx: Load, @@ -73,10 +85,14 @@ Module( name: Identifier { id: Name("foo"), range: 31..34, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 34..36, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -87,9 +103,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..41, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 38..41, }, ), @@ -100,12 +118,15 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 42..59, decorator_list: [ Decorator { range: 42..44, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("x"), ctx: Load, @@ -116,15 +137,18 @@ Module( name: Identifier { id: Name("Foo"), range: 51..54, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..59, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 56..59, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_named_expression_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_named_expression_py37.py.snap index fbbef254954567..0cbedb39195d64 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_named_expression_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_named_expression_py37.py.snap @@ -7,23 +7,29 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_named_expre ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..85, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..84, is_async: false, decorator_list: [ Decorator { range: 45..69, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 46..69, func: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 47..63, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -31,19 +37,26 @@ Module( ), value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 52..63, parameters: Some( Parameters { range: 59..60, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 59..60, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 59..60, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 59..60, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -57,6 +70,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -68,9 +82,11 @@ Module( ), arguments: Arguments { range: 64..69, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..68, id: Name("foo"), ctx: Load, @@ -86,10 +102,14 @@ Module( name: Identifier { id: Name("bar"), range: 74..77, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 77..79, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -100,9 +120,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_non_toplevel_call_expression_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_non_toplevel_call_expression_py38.py.snap index 2374561e1678d5..4172d616562808 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_non_toplevel_call_expression_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_non_toplevel_call_expression_py38.py.snap @@ -7,26 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/err/decorator_non_topleve ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..72, is_async: false, decorator_list: [ Decorator { range: 45..57, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 46..57, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..55, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 46..51, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..49, id: Name("foo"), ctx: Load, @@ -34,6 +41,7 @@ Module( ), arguments: Arguments { range: 49..51, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -42,12 +50,14 @@ Module( attr: Identifier { id: Name("bar"), range: 52..55, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 55..57, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -58,10 +68,14 @@ Module( name: Identifier { id: Name("baz"), range: 62..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -72,9 +86,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..72, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 69..72, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_unexpected_token.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_unexpected_token.py.snap index 8e3126c2e33c59..d76a5c7bff80be 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_unexpected_token.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@decorator_unexpected_token.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/decorator_unexpected_token.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..34, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 5..22, is_async: true, items: [ WithItem { range: 16..17, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Load, @@ -30,9 +33,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -43,10 +48,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 28..33, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("x"), ctx: Store, @@ -55,6 +62,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_debug_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_debug_py39.py.snap index 6909999bfed48f..40a5517f37278b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_debug_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_debug_py39.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/del_debug_py39.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 43..56, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..56, id: Name("__debug__"), ctx: Del, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_incomplete_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_incomplete_target.py.snap index b17df25feb8d72..2e3f2be0afb4e9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_incomplete_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_incomplete_target.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/del_incomplete_target ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..9, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Del, @@ -22,9 +25,11 @@ Module( ), Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 7..9, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("y"), ctx: Load, @@ -33,6 +38,7 @@ Module( attr: Identifier { id: Name(""), range: 9..9, + node_index: AtomicNodeIndex(..), }, ctx: Del, }, @@ -42,9 +48,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..11, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("z"), ctx: Load, @@ -54,10 +62,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 12..24, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Del, @@ -65,9 +75,11 @@ Module( ), Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 19..23, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("y"), ctx: Load, @@ -75,10 +87,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 22..23, lower: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("z"), ctx: Load, @@ -88,6 +102,7 @@ Module( upper: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..23, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_stmt_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_stmt_empty.py.snap index da814920189fda..412384af7c374b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_stmt_empty.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@del_stmt_empty.py.snap @@ -1,17 +1,18 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/del_stmt_empty.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..4, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..3, targets: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@dotted_name_multiple_dots.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@dotted_name_multiple_dots.py.snap index 281c71c151fe7f..3614aa08aa440e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@dotted_name_multiple_dots.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@dotted_name_multiple_dots.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/dotted_name_multiple_dots.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..25, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..11, names: [ Alias { range: 7..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a..b"), range: 7..11, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -27,13 +30,16 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 12..20, names: [ Alias { range: 19..20, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 19..20, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -42,9 +48,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), @@ -52,9 +60,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..24, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap index f4d1b42821b528..950b30f63de6e9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/duplicate_match_class ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..231, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..230, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,11 +25,14 @@ Module( cases: [ MatchCase { range: 13..38, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 18..33, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..23, id: Name("Class"), ctx: Load, @@ -34,19 +40,24 @@ Module( ), arguments: PatternArguments { range: 23..33, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 24..27, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 24..25, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 26..27, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..27, value: Int( 1, @@ -58,15 +69,19 @@ Module( }, PatternKeyword { range: 29..32, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 29..30, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 31..32, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 31..32, value: Int( 2, @@ -84,9 +99,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..38, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 35..38, }, ), @@ -96,15 +113,19 @@ Module( }, MatchCase { range: 43..70, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 48..65, + node_index: AtomicNodeIndex(..), patterns: [ MatchClass( PatternMatchClass { range: 49..64, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..54, id: Name("Class"), ctx: Load, @@ -112,19 +133,24 @@ Module( ), arguments: PatternArguments { range: 54..64, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 55..58, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 55..56, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 57..58, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 1, @@ -136,15 +162,19 @@ Module( }, PatternKeyword { range: 60..63, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 60..61, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 62..63, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 62..63, value: Int( 2, @@ -165,9 +195,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..70, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 67..70, }, ), @@ -177,17 +209,21 @@ Module( }, MatchCase { range: 75..113, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 80..108, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, value: StringLiteralValue { inner: Single( StringLiteral { range: 81..84, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -201,11 +237,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 89..92, value: StringLiteralValue { inner: Single( StringLiteral { range: 89..92, + node_index: AtomicNodeIndex(..), value: "y", flags: StringLiteralFlags { quote_style: Double, @@ -222,11 +260,13 @@ Module( MatchAs( PatternMatchAs { range: 86..87, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 86..87, + node_index: AtomicNodeIndex(..), }, ), }, @@ -234,8 +274,10 @@ Module( MatchClass( PatternMatchClass { range: 94..107, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..97, id: Name("Foo"), ctx: Load, @@ -243,19 +285,24 @@ Module( ), arguments: PatternArguments { range: 97..107, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 98..101, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 98..99, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 100..101, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..101, value: Int( 1, @@ -267,15 +314,19 @@ Module( }, PatternKeyword { range: 103..106, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 103..104, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 105..106, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 2, @@ -297,9 +348,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 110..113, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 110..113, }, ), @@ -309,13 +362,16 @@ Module( }, MatchCase { range: 118..162, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 123..157, + node_index: AtomicNodeIndex(..), patterns: [ MatchMapping( PatternMatchMapping { range: 124..126, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -324,14 +380,17 @@ Module( MatchMapping( PatternMatchMapping { range: 128..156, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 129..132, value: StringLiteralValue { inner: Single( StringLiteral { range: 129..132, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -345,11 +404,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 137..140, value: StringLiteralValue { inner: Single( StringLiteral { range: 137..140, + node_index: AtomicNodeIndex(..), value: "y", flags: StringLiteralFlags { quote_style: Double, @@ -366,11 +427,13 @@ Module( MatchAs( PatternMatchAs { range: 134..135, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 134..135, + node_index: AtomicNodeIndex(..), }, ), }, @@ -378,8 +441,10 @@ Module( MatchClass( PatternMatchClass { range: 142..155, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..145, id: Name("Foo"), ctx: Load, @@ -387,19 +452,24 @@ Module( ), arguments: PatternArguments { range: 145..155, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 146..149, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 146..147, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 148..149, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 148..149, value: Int( 1, @@ -411,15 +481,19 @@ Module( }, PatternKeyword { range: 151..154, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 151..152, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 153..154, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 153..154, value: Int( 2, @@ -444,9 +518,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 159..162, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 159..162, }, ), @@ -456,11 +532,14 @@ Module( }, MatchCase { range: 167..230, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 172..225, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 172..177, id: Name("Class"), ctx: Load, @@ -468,19 +547,24 @@ Module( ), arguments: PatternArguments { range: 177..225, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 178..181, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 178..179, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 180..181, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 180..181, value: Int( 1, @@ -492,21 +576,26 @@ Module( }, PatternKeyword { range: 183..201, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("d"), range: 183..184, + node_index: AtomicNodeIndex(..), }, pattern: MatchMapping( PatternMatchMapping { range: 185..201, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 186..189, value: StringLiteralValue { inner: Single( StringLiteral { range: 186..189, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -520,11 +609,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 194..197, value: StringLiteralValue { inner: Single( StringLiteral { range: 194..197, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -541,8 +632,10 @@ Module( MatchValue( PatternMatchValue { range: 191..192, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 191..192, value: Int( 1, @@ -554,8 +647,10 @@ Module( MatchValue( PatternMatchValue { range: 199..200, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 199..200, value: Int( 2, @@ -571,15 +666,19 @@ Module( }, PatternKeyword { range: 203..224, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("other"), range: 203..208, + node_index: AtomicNodeIndex(..), }, pattern: MatchClass( PatternMatchClass { range: 209..224, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 209..214, id: Name("Class"), ctx: Load, @@ -587,19 +686,24 @@ Module( ), arguments: PatternArguments { range: 214..224, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 215..218, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 215..216, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 217..218, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 217..218, value: Int( 1, @@ -611,15 +715,19 @@ Module( }, PatternKeyword { range: 220..223, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 220..221, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 222..223, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 222..223, value: Int( 2, @@ -642,9 +750,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 227..230, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 227..230, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap index 4d4dbc2eca418d..4cfbb7011e9545 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/duplicate_match_key.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..533, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..532, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,17 +25,21 @@ Module( cases: [ MatchCase { range: 13..39, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 18..34, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, value: StringLiteralValue { inner: Single( StringLiteral { range: 19..22, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -46,11 +53,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 27..30, value: StringLiteralValue { inner: Single( StringLiteral { range: 27..30, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -67,8 +76,10 @@ Module( MatchValue( PatternMatchValue { range: 24..25, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -80,8 +91,10 @@ Module( MatchValue( PatternMatchValue { range: 32..33, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 2, @@ -98,9 +111,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..39, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 36..39, }, ), @@ -110,17 +125,21 @@ Module( }, MatchCase { range: 44..72, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 49..67, + node_index: AtomicNodeIndex(..), keys: [ BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 50..54, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 50..54, + node_index: AtomicNodeIndex(..), value: [ 120, ], @@ -136,11 +155,13 @@ Module( ), BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 59..63, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 59..63, + node_index: AtomicNodeIndex(..), value: [ 120, ], @@ -159,8 +180,10 @@ Module( MatchValue( PatternMatchValue { range: 56..57, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 56..57, value: Int( 1, @@ -172,8 +195,10 @@ Module( MatchValue( PatternMatchValue { range: 65..66, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 2, @@ -190,9 +215,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..72, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 69..72, }, ), @@ -202,12 +229,15 @@ Module( }, MatchCase { range: 77..99, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 82..94, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 0, @@ -216,6 +246,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 0, @@ -227,8 +258,10 @@ Module( MatchValue( PatternMatchValue { range: 86..87, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..87, value: Int( 1, @@ -240,8 +273,10 @@ Module( MatchValue( PatternMatchValue { range: 92..93, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 92..93, value: Int( 2, @@ -258,9 +293,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), @@ -270,12 +307,15 @@ Module( }, MatchCase { range: 104..130, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 109..125, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 110..113, value: Float( 1.0, @@ -284,6 +324,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..121, value: Float( 1.0, @@ -295,8 +336,10 @@ Module( MatchValue( PatternMatchValue { range: 115..116, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 1, @@ -308,8 +351,10 @@ Module( MatchValue( PatternMatchValue { range: 123..124, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 123..124, value: Int( 2, @@ -326,9 +371,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 127..130, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 127..130, }, ), @@ -338,15 +385,19 @@ Module( }, MatchCase { range: 135..171, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 140..166, + node_index: AtomicNodeIndex(..), keys: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 141..149, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 141..144, value: Float( 1.0, @@ -356,6 +407,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 147..149, value: Complex { real: 0.0, @@ -367,9 +419,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 154..162, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 154..157, value: Float( 1.0, @@ -379,6 +433,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..162, value: Complex { real: 0.0, @@ -393,8 +448,10 @@ Module( MatchValue( PatternMatchValue { range: 151..152, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 151..152, value: Int( 1, @@ -406,8 +463,10 @@ Module( MatchValue( PatternMatchValue { range: 164..165, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 2, @@ -424,9 +483,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..171, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 168..171, }, ), @@ -436,18 +497,22 @@ Module( }, MatchCase { range: 176..204, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 181..199, + node_index: AtomicNodeIndex(..), keys: [ BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 182..186, value: true, }, ), BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 191..195, value: true, }, @@ -457,8 +522,10 @@ Module( MatchValue( PatternMatchValue { range: 188..189, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 188..189, value: Int( 1, @@ -470,8 +537,10 @@ Module( MatchValue( PatternMatchValue { range: 197..198, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..198, value: Int( 2, @@ -488,9 +557,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 201..204, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 201..204, }, ), @@ -500,17 +571,21 @@ Module( }, MatchCase { range: 209..237, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 214..232, + node_index: AtomicNodeIndex(..), keys: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 215..219, }, ), NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 224..228, }, ), @@ -519,8 +594,10 @@ Module( MatchValue( PatternMatchValue { range: 221..222, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 221..222, value: Int( 1, @@ -532,8 +609,10 @@ Module( MatchValue( PatternMatchValue { range: 230..231, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 230..231, value: Int( 2, @@ -550,9 +629,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 234..237, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 234..237, }, ), @@ -562,17 +643,21 @@ Module( }, MatchCase { range: 242..319, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 247..314, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 253..277, value: StringLiteralValue { inner: Single( StringLiteral { range: 253..277, + node_index: AtomicNodeIndex(..), value: "x\n y\n z\n ", flags: StringLiteralFlags { quote_style: Double, @@ -586,11 +671,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 286..310, value: StringLiteralValue { inner: Single( StringLiteral { range: 286..310, + node_index: AtomicNodeIndex(..), value: "x\n y\n z\n ", flags: StringLiteralFlags { quote_style: Double, @@ -607,8 +694,10 @@ Module( MatchValue( PatternMatchValue { range: 279..280, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 279..280, value: Int( 1, @@ -620,8 +709,10 @@ Module( MatchValue( PatternMatchValue { range: 312..313, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 312..313, value: Int( 2, @@ -638,9 +729,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 316..319, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 316..319, }, ), @@ -650,17 +743,21 @@ Module( }, MatchCase { range: 324..358, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 329..353, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 330..333, value: StringLiteralValue { inner: Single( StringLiteral { range: 330..333, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -674,11 +771,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 338..341, value: StringLiteralValue { inner: Single( StringLiteral { range: 338..341, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -692,11 +791,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 346..349, value: StringLiteralValue { inner: Single( StringLiteral { range: 346..349, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -713,8 +814,10 @@ Module( MatchValue( PatternMatchValue { range: 335..336, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 335..336, value: Int( 1, @@ -726,8 +829,10 @@ Module( MatchValue( PatternMatchValue { range: 343..344, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 343..344, value: Int( 2, @@ -739,8 +844,10 @@ Module( MatchValue( PatternMatchValue { range: 351..352, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 351..352, value: Int( 3, @@ -757,9 +864,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 355..358, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 355..358, }, ), @@ -769,12 +878,15 @@ Module( }, MatchCase { range: 363..401, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 368..396, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 369..370, value: Int( 0, @@ -783,11 +895,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 375..378, value: StringLiteralValue { inner: Single( StringLiteral { range: 375..378, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -801,6 +915,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 383..384, value: Int( 0, @@ -809,11 +924,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 389..392, value: StringLiteralValue { inner: Single( StringLiteral { range: 389..392, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -830,8 +947,10 @@ Module( MatchValue( PatternMatchValue { range: 372..373, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 372..373, value: Int( 1, @@ -843,8 +962,10 @@ Module( MatchValue( PatternMatchValue { range: 380..381, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 380..381, value: Int( 1, @@ -856,8 +977,10 @@ Module( MatchValue( PatternMatchValue { range: 386..387, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 386..387, value: Int( 2, @@ -869,8 +992,10 @@ Module( MatchValue( PatternMatchValue { range: 394..395, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 394..395, value: Int( 2, @@ -887,9 +1012,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 398..401, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 398..401, }, ), @@ -899,21 +1026,26 @@ Module( }, MatchCase { range: 406..434, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 411..429, + node_index: AtomicNodeIndex(..), patterns: [ MatchMapping( PatternMatchMapping { range: 412..428, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 413..416, value: StringLiteralValue { inner: Single( StringLiteral { range: 413..416, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -927,11 +1059,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 421..424, value: StringLiteralValue { inner: Single( StringLiteral { range: 421..424, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -948,8 +1082,10 @@ Module( MatchValue( PatternMatchValue { range: 418..419, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 418..419, value: Int( 1, @@ -961,8 +1097,10 @@ Module( MatchValue( PatternMatchValue { range: 426..427, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 426..427, value: Int( 2, @@ -982,9 +1120,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 431..434, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 431..434, }, ), @@ -994,11 +1134,14 @@ Module( }, MatchCase { range: 439..477, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 444..472, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 444..447, id: Name("Foo"), ctx: Load, @@ -1006,19 +1149,24 @@ Module( ), arguments: PatternArguments { range: 447..472, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 448..451, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 448..449, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 450..451, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 450..451, value: Int( 1, @@ -1030,21 +1178,26 @@ Module( }, PatternKeyword { range: 453..471, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 453..454, + node_index: AtomicNodeIndex(..), }, pattern: MatchMapping( PatternMatchMapping { range: 455..471, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 456..459, value: StringLiteralValue { inner: Single( StringLiteral { range: 456..459, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -1058,11 +1211,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 464..467, value: StringLiteralValue { inner: Single( StringLiteral { range: 464..467, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -1079,8 +1234,10 @@ Module( MatchValue( PatternMatchValue { range: 461..462, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 461..462, value: Int( 1, @@ -1092,8 +1249,10 @@ Module( MatchValue( PatternMatchValue { range: 469..470, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 469..470, value: Int( 2, @@ -1115,9 +1274,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 474..477, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 474..477, }, ), @@ -1127,15 +1288,19 @@ Module( }, MatchCase { range: 482..532, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 487..527, + node_index: AtomicNodeIndex(..), patterns: [ MatchClass( PatternMatchClass { range: 488..496, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 488..491, id: Name("Foo"), ctx: Load, @@ -1143,19 +1308,24 @@ Module( ), arguments: PatternArguments { range: 491..496, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 492..495, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 492..493, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 494..495, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 494..495, value: Int( 1, @@ -1172,8 +1342,10 @@ Module( MatchClass( PatternMatchClass { range: 498..526, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 498..501, id: Name("Foo"), ctx: Load, @@ -1181,19 +1353,24 @@ Module( ), arguments: PatternArguments { range: 501..526, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 502..505, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 502..503, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 504..505, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 504..505, value: Int( 1, @@ -1205,21 +1382,26 @@ Module( }, PatternKeyword { range: 507..525, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 507..508, + node_index: AtomicNodeIndex(..), }, pattern: MatchMapping( PatternMatchMapping { range: 509..525, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 510..513, value: StringLiteralValue { inner: Single( StringLiteral { range: 510..513, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -1233,11 +1415,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 518..521, value: StringLiteralValue { inner: Single( StringLiteral { range: 518..521, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Double, @@ -1254,8 +1438,10 @@ Module( MatchValue( PatternMatchValue { range: 515..516, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 515..516, value: Int( 1, @@ -1267,8 +1453,10 @@ Module( MatchValue( PatternMatchValue { range: 523..524, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 523..524, value: Int( 2, @@ -1293,9 +1481,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 529..532, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 529..532, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_type_parameter_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_type_parameter_names.py.snap index 88b7f1ffb791ab..55e020ad84b15f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_type_parameter_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_type_parameter_names.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/duplicate_type_parame ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..261, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..22, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..10, id: Name("Alias"), ctx: Store, @@ -22,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 10..16, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 11..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 11..12, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -37,9 +43,11 @@ Module( TypeVar( TypeParamTypeVar { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 14..15, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -50,6 +58,7 @@ Module( ), value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -57,23 +66,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..45, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 27..28, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 28..34, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 29..30, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 29..30, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -82,9 +96,11 @@ Module( TypeVar( TypeParamTypeVar { range: 32..33, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 32..33, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -95,19 +111,26 @@ Module( ), parameters: Parameters { range: 34..40, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 35..39, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 35..39, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("t"), range: 35..36, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("T"), ctx: Load, @@ -126,9 +149,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 42..45, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 42..45, }, ), @@ -139,22 +164,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 46..64, decorator_list: [], name: Identifier { id: Name("C"), range: 52..53, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 53..59, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 54..55, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 54..55, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -163,9 +193,11 @@ Module( TypeVar( TypeParamTypeVar { range: 57..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 57..58, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -178,9 +210,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..64, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 61..64, }, ), @@ -191,9 +225,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 65..132, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..75, id: Name("Alias"), ctx: Store, @@ -202,13 +238,16 @@ Module( type_params: Some( TypeParams { range: 75..126, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 76..77, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 76..77, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -217,13 +256,16 @@ Module( TypeVar( TypeParamTypeVar { range: 79..85, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 79..80, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..85, id: Name("str"), ctx: Load, @@ -236,17 +278,21 @@ Module( TypeVar( TypeParamTypeVar { range: 87..102, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("V"), range: 87..88, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 90..102, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..94, id: Name("str"), ctx: Load, @@ -254,6 +300,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..101, id: Name("bytes"), ctx: Load, @@ -271,9 +318,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 104..107, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 105..107, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -281,9 +330,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 109..112, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 111..112, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -291,14 +342,17 @@ Module( TypeVar( TypeParamTypeVar { range: 114..125, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 114..115, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 118..125, id: Name("default"), ctx: Load, @@ -312,6 +366,7 @@ Module( ), value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 129..132, }, ), @@ -319,23 +374,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 133..154, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 137..138, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 138..147, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 139..140, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 139..140, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -344,9 +404,11 @@ Module( TypeVar( TypeParamTypeVar { range: 142..143, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 142..143, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -355,9 +417,11 @@ Module( TypeVar( TypeParamTypeVar { range: 145..146, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 145..146, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -368,6 +432,9 @@ Module( ), parameters: Parameters { range: 147..149, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -378,9 +445,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 151..154, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 151..154, }, ), @@ -391,23 +460,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 169..188, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 173..174, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 174..181, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 175..176, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 175..176, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -416,9 +490,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 178..180, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 179..180, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -428,6 +504,9 @@ Module( ), parameters: Parameters { range: 181..183, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -438,9 +517,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..188, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 185..188, }, ), @@ -451,23 +532,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 218..238, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 222..223, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 223..231, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 224..225, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 224..225, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -476,9 +562,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 227..230, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 229..230, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -488,6 +576,9 @@ Module( ), parameters: Parameters { range: 231..233, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -498,9 +589,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 235..238, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 235..238, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap index 070d99e74df7f4..0530c751f8c98c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/except_star_py310.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..126, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 44..125, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), @@ -28,9 +32,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 53..76, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..71, id: Name("ValueError"), ctx: Load, @@ -41,9 +47,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 73..76, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 73..76, }, ), @@ -55,9 +63,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 77..98, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..93, id: Name("KeyError"), ctx: Load, @@ -68,9 +78,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..98, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 95..98, }, ), @@ -82,9 +94,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 99..125, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..120, id: Name("Error"), ctx: Load, @@ -95,9 +109,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..125, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 122..125, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_invalid_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_invalid_expression.py.snap index 3409ecda78583e..fb1f2b6181124e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_invalid_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_invalid_expression.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_invalid_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..74, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -24,13 +26,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..38, + node_index: AtomicNodeIndex(..), type_: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 21..28, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Load, @@ -44,6 +49,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 34..38, }, ), @@ -58,10 +64,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 39..73, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 48..52, }, ), @@ -70,12 +78,15 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 53..73, + node_index: AtomicNodeIndex(..), type_: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 61..63, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -89,6 +100,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 69..73, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_as_name.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_as_name.py.snap index 309e29e699f143..b9b370d139a818 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_as_name.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_as_name.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_missing_as_name.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..72, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -24,9 +26,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..43, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..30, id: Name("Exception"), ctx: Load, @@ -37,6 +41,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 39..43, }, ), @@ -46,9 +51,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 44..72, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..60, id: Name("Exception"), ctx: Load, @@ -59,6 +66,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 68..72, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception.py.snap index 16e649cc7d26c3..fa14993f7942b7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_missing_exception.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..166, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..37, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -24,16 +26,19 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..37, + node_index: AtomicNodeIndex(..), type_: None, name: Some( Identifier { id: Name("exc"), range: 24..27, + node_index: AtomicNodeIndex(..), }, ), body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 33..37, }, ), @@ -48,10 +53,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 92..165, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 101..105, }, ), @@ -60,11 +67,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 106..123, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 119..123, }, ), @@ -74,11 +83,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 124..140, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 136..140, }, ), @@ -88,16 +99,19 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 141..165, + node_index: AtomicNodeIndex(..), type_: None, name: Some( Identifier { id: Name("exc"), range: 152..155, + node_index: AtomicNodeIndex(..), }, ), body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 161..165, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception_and_as_name.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception_and_as_name.py.snap index ad463555ea040f..fc9b78dde0110e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception_and_as_name.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_missing_exception_and_as_name.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_missing_exception_and_as_name.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..34, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..33, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -24,11 +26,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..33, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 29..33, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_as.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_as.py.snap index 928495cb4dde98..ddc722c8922ea9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_as.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_as.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_unparenth ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..86, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..42, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -23,13 +26,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..42, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 21..25, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("y"), ctx: Load, @@ -52,11 +59,13 @@ Module( Identifier { id: Name("exc"), range: 29..32, + node_index: AtomicNodeIndex(..), }, ), body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 38..42, }, ), @@ -71,10 +80,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 43..85, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 52..56, }, ), @@ -83,13 +94,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 57..85, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 65..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Load, @@ -97,6 +111,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("y"), ctx: Load, @@ -112,11 +127,13 @@ Module( Identifier { id: Name("eg"), range: 73..75, + node_index: AtomicNodeIndex(..), }, ), body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 81..85, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_no_as_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_no_as_py313.py.snap index 1a74da4a6767cb..848e2c129955f0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_no_as_py313.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_stmt_unparenthesized_tuple_no_as_py313.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/except_stmt_unparenth ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..117, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 44..79, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 53..57, }, ), @@ -23,13 +26,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 58..79, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 65..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("y"), ctx: Load, @@ -52,6 +59,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 75..79, }, ), @@ -66,10 +74,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 80..116, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 89..93, }, ), @@ -78,13 +88,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 94..116, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 102..106, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("x"), ctx: Load, @@ -92,6 +105,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("y"), ctx: Load, @@ -107,6 +121,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 112..116, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__double_starred.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__double_starred.py.snap index 4ef425440b982f..0637b78f081a1b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__double_starred.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__double_starred.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/do ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..55, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..15, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,17 +28,21 @@ Module( ), arguments: Arguments { range: 4..15, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 5..14, + node_index: AtomicNodeIndex(..), arg: None, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 7..14, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("x"), ctx: Load, @@ -52,12 +60,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..27, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..27, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..20, id: Name("call"), ctx: Load, @@ -65,16 +76,20 @@ Module( ), arguments: Arguments { range: 20..27, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 21..26, + node_index: AtomicNodeIndex(..), arg: None, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 24..26, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Load, @@ -92,12 +107,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..38, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 28..38, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..32, id: Name("call"), ctx: Load, @@ -105,16 +123,20 @@ Module( ), arguments: Arguments { range: 32..38, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 33..37, + node_index: AtomicNodeIndex(..), arg: None, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 35..37, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("x"), ctx: Load, @@ -132,12 +154,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..54, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 40..54, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..44, id: Name("call"), ctx: Load, @@ -145,9 +170,11 @@ Module( ), arguments: Arguments { range: 44..54, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 1, @@ -158,9 +185,11 @@ Module( keywords: [ Keyword { range: 45..48, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__duplicate_keyword_arguments.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__duplicate_keyword_arguments.py.snap index d31cfdfc0b694f..6e6e7699e23bcc 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__duplicate_keyword_arguments.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__duplicate_keyword_arguments.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/duplicate_keyword_arguments.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..28, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..28, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..3, id: Name("foo"), ctx: Load, @@ -25,18 +28,22 @@ Module( ), arguments: Arguments { range: 3..28, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 4..7, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 4..5, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 1, @@ -46,14 +53,17 @@ Module( }, Keyword { range: 9..12, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("b"), range: 9..10, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 2, @@ -63,14 +73,17 @@ Module( }, Keyword { range: 14..17, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("c"), range: 14..15, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 3, @@ -80,14 +93,17 @@ Module( }, Keyword { range: 19..22, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("b"), range: 19..20, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 4, @@ -97,14 +113,17 @@ Module( }, Keyword { range: 24..27, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 24..25, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..27, value: Int( 5, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_expression.py.snap index 46cb028b2a6198..e517103c145d9b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_expression.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/in ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..15, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,18 +28,22 @@ Module( ), arguments: Arguments { range: 4..15, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 5..14, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name(""), range: 5..10, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, @@ -51,12 +59,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..32, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..32, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..20, id: Name("call"), ctx: Load, @@ -64,18 +75,22 @@ Module( ), arguments: Arguments { range: 20..32, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 21..31, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name(""), range: 21..27, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 30..31, value: Int( 1, @@ -91,12 +106,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..47, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 34..47, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..38, id: Name("call"), ctx: Load, @@ -104,13 +122,16 @@ Module( ), arguments: Arguments { range: 38..47, + node_index: AtomicNodeIndex(..), args: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 39..46, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("x"), ctx: Load, @@ -128,12 +149,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 48..66, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 48..66, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..52, id: Name("call"), ctx: Load, @@ -141,12 +165,15 @@ Module( ), arguments: Arguments { range: 52..66, + node_index: AtomicNodeIndex(..), args: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 53..65, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..65, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_keyword_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_keyword_expression.py.snap index 1f990885a4eb7e..99643af80e05f9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_keyword_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_keyword_expression.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/invalid_keyword_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..69, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..17, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,22 +28,27 @@ Module( ), arguments: Arguments { range: 4..17, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 5..16, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 9..16, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("y"), ctx: Load, @@ -58,12 +66,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..40, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 18..40, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..22, id: Name("call"), ctx: Load, @@ -71,21 +82,26 @@ Module( ), arguments: Arguments { range: 22..40, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 23..39, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 23..24, + node_index: AtomicNodeIndex(..), }, ), value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 27..39, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("y"), ctx: Load, @@ -102,12 +118,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 41..53, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 41..53, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..45, id: Name("call"), ctx: Load, @@ -115,21 +134,26 @@ Module( ), arguments: Arguments { range: 45..53, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 46..52, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 46..47, + node_index: AtomicNodeIndex(..), }, ), value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 50..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("y"), ctx: Load, @@ -147,12 +171,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..68, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 54..68, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..58, id: Name("call"), ctx: Load, @@ -160,21 +187,26 @@ Module( ), arguments: Arguments { range: 58..68, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 59..67, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 59..60, + node_index: AtomicNodeIndex(..), }, ), value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 64..66, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_order.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_order.py.snap index b4a999a44874eb..2b60be9235fb7f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_order.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__invalid_order.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/invalid_order.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..100, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..17, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..17, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("x"), ctx: Load, @@ -37,9 +42,11 @@ Module( keywords: [ Keyword { range: 5..13, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..13, id: Name("kwargs"), ctx: Load, @@ -54,12 +61,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..30, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 18..30, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..22, id: Name("call"), ctx: Load, @@ -67,9 +77,11 @@ Module( ), arguments: Arguments { range: 22..30, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("y"), ctx: Load, @@ -79,14 +91,17 @@ Module( keywords: [ Keyword { range: 23..26, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 23..24, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 1, @@ -102,12 +117,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..53, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 31..53, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..35, id: Name("call"), ctx: Load, @@ -115,9 +133,11 @@ Module( ), arguments: Arguments { range: 35..53, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("y"), ctx: Load, @@ -127,14 +147,17 @@ Module( keywords: [ Keyword { range: 36..39, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 36..37, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..39, value: Int( 1, @@ -144,9 +167,11 @@ Module( }, Keyword { range: 41..49, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..49, id: Name("kwargs"), ctx: Load, @@ -161,12 +186,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..75, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 54..75, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..58, id: Name("call"), ctx: Load, @@ -174,12 +202,15 @@ Module( ), arguments: Arguments { range: 58..75, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 69..74, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..74, id: Name("args"), ctx: Load, @@ -192,9 +223,11 @@ Module( keywords: [ Keyword { range: 59..67, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..67, id: Name("kwargs"), ctx: Load, @@ -209,12 +242,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 76..99, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 76..99, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..80, id: Name("call"), ctx: Load, @@ -222,12 +258,15 @@ Module( ), arguments: Arguments { range: 80..99, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 92..97, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..97, id: Name("args"), ctx: Load, @@ -240,9 +279,11 @@ Module( keywords: [ Keyword { range: 81..89, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..89, id: Name("kwargs"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_argument.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_argument.py.snap index 2fd99bd6cb3f08..bade10dafee529 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_argument.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_argument.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/missing_argument.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..10, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..10, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -35,6 +40,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_comma.py.snap index 43abd82cae46cc..a17128ef2c7f44 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_comma.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/missing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..9, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..9, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..9, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -35,6 +40,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_expression.py.snap index 1c86ec46e36216..1b515e8f95efab 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__missing_expression.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/mi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..10, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..10, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 1, @@ -42,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..21, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 11..21, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..15, id: Name("call"), ctx: Load, @@ -55,18 +64,22 @@ Module( ), arguments: Arguments { range: 15..21, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 16..19, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 16..17, + node_index: AtomicNodeIndex(..), }, ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..19, id: Name(""), ctx: Invalid, @@ -81,12 +94,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..32, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 22..32, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..26, id: Name("call"), ctx: Load, @@ -94,12 +110,15 @@ Module( ), arguments: Arguments { range: 26..32, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 27..28, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..28, id: Name(""), ctx: Invalid, @@ -110,6 +129,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..31, id: Name("y"), ctx: Load, @@ -124,9 +144,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..37, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..37, id: Name("foo"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__starred.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__starred.py.snap index 3c1e2f02bbb3c4..d9a820dcd8fbd4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__starred.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__starred.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/starred.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..64, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..28, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..28, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -25,15 +28,19 @@ Module( ), arguments: Arguments { range: 4..28, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 5..27, elt: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 5..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..10, id: Name("data"), ctx: Load, @@ -45,8 +52,10 @@ Module( generators: [ Comprehension { range: 11..27, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..19, id: Name("data"), ctx: Store, @@ -54,6 +63,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..27, id: Name("iter"), ctx: Load, @@ -75,12 +85,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..43, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 29..43, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..33, id: Name("call"), ctx: Load, @@ -88,16 +101,20 @@ Module( ), arguments: Arguments { range: 33..43, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 34..42, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 35..42, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Load, @@ -118,12 +135,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..63, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 44..63, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..48, id: Name("call"), ctx: Load, @@ -131,15 +151,19 @@ Module( ), arguments: Arguments { range: 48..63, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 49..62, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 50..62, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..62, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_0.py.snap index 0ea89395000552..6eab95404964d8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/un ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..26, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..5, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,6 +28,7 @@ Module( ), arguments: Arguments { range: 4..5, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -33,16 +38,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 7..26, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 11..14, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 14..16, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -53,6 +63,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 22..26, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_1.py.snap index af8a140c618343..d60f2bf8710a47 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/un ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..27, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..6, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..6, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..6, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -41,16 +47,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 8..27, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 12..15, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 15..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -61,6 +72,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 23..27, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_2.py.snap index d41c5727a7bab6..ee3456db28520b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__arguments__unclosed_2.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/arguments/un ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..7, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..7, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -41,16 +47,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 9..28, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 13..16, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 16..18, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -61,6 +72,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 24..28, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__invalid_member.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__invalid_member.py.snap index 0ab3721b7a554b..f2eec9b2cb9126 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__invalid_member.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__invalid_member.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/attribute/invalid_member.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..16, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..1, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -24,9 +26,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1..3, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1..3, value: Float( 0.1, @@ -37,9 +41,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..5, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -49,9 +55,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..7, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..7, value: Float( 0.1, @@ -62,9 +70,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..9, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..9, value: Float( 0.0, @@ -75,15 +85,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..15, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 10..15, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 10..12, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Load, @@ -92,12 +106,14 @@ Module( attr: Identifier { id: Name(""), range: 12..12, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 0, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__multiple_dots.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__multiple_dots.py.snap index a6c1f3f614e500..efa04a319e7bf4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__multiple_dots.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__multiple_dots.py.snap @@ -1,26 +1,30 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/attribute/multiple_dots.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..46, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 0..10, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 0..6, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("extra"), ctx: Load, @@ -29,6 +33,7 @@ Module( attr: Identifier { id: Name(""), range: 6..6, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -36,6 +41,7 @@ Module( attr: Identifier { id: Name("dot"), range: 7..10, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -44,9 +50,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..19, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..19, id: Name("multiple"), ctx: Load, @@ -56,18 +64,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..27, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 19..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), attr: Identifier { id: Name("dots"), range: 23..27, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -76,9 +88,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..36, id: Name("multiple"), ctx: Load, @@ -88,21 +102,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..45, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 36..45, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 36..40, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 36..39, }, ), attr: Identifier { id: Name(""), range: 40..40, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -110,6 +129,7 @@ Module( attr: Identifier { id: Name("dots"), range: 41..45, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__no_member.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__no_member.py.snap index 440c42423fefeb..69af9f8041b7d2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__no_member.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__attribute__no_member.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/attribute/no ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..141, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..93, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 87..93, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..92, id: Name("first"), ctx: Load, @@ -25,6 +29,7 @@ Module( attr: Identifier { id: Name(""), range: 93..93, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -33,9 +38,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..100, id: Name("second"), ctx: Load, @@ -45,12 +52,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..141, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 136..141, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..140, id: Name("last"), ctx: Load, @@ -59,6 +69,7 @@ Module( attr: Identifier { id: Name(""), range: 141..141, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_0.py.snap index 747e1c7a795397..f1e8020d1a8cb4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/await/no_exp ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..66, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 61..66, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..66, id: Name(""), ctx: Invalid, @@ -28,12 +32,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..73, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 68..73, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -42,6 +49,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_1.py.snap index 7d67f0e7008b63..cf7d31d11b5e7e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__no_expression_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/await/no_exp ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..85, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..64, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 59..64, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..64, id: Name(""), ctx: Invalid, @@ -28,16 +32,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 66..85, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 70..73, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 73..75, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -48,6 +57,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 81..85, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__recover.py.snap index 87c433552a4a59..029e1723d9b726 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__await__recover.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/await/recove ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..284, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..130, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 117..130, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 123..130, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("x"), ctx: Load, @@ -33,15 +38,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 154..162, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 154..162, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 160..162, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("x"), ctx: Load, @@ -56,15 +65,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..173, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 163..173, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 170..172, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("x"), ctx: Load, @@ -79,16 +92,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 214..227, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 214..227, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 220..227, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 226..227, id: Name("x"), ctx: Load, @@ -103,25 +120,34 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 228..245, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 228..245, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 234..245, parameters: Some( Parameters { range: 241..242, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 241..242, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 241..242, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 241..242, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -135,6 +161,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 244..245, id: Name("x"), ctx: Load, @@ -148,16 +175,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 246..254, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 246..254, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 252..254, op: UAdd, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 253..254, id: Name("x"), ctx: Load, @@ -171,16 +202,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 255..263, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 255..263, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 261..263, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 262..263, id: Name("x"), ctx: Load, @@ -194,16 +229,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 264..272, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 264..272, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 270..272, op: Invert, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 271..272, id: Name("x"), ctx: Load, @@ -217,16 +256,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 273..284, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 273..284, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 279..284, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 283..284, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__invalid_rhs_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__invalid_rhs_expression.py.snap index cb65ebee143648..fa628602c101ca 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__invalid_rhs_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__invalid_rhs_expression.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/inval ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..15, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -25,19 +29,26 @@ Module( op: Add, right: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 4..15, parameters: Some( Parameters { range: 11..12, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 11..12, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 11..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 11..12, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -51,6 +62,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("y"), ctx: Load, @@ -64,12 +76,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..28, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 17..28, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Load, @@ -78,10 +93,12 @@ Module( op: Sub, right: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 21..28, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_lhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_lhs.py.snap index 7e9d24594d17e8..b9bd494cd7f6bc 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_lhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_lhs.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/missi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("y"), ctx: Load, @@ -23,12 +26,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -38,6 +44,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_0.py.snap index 9f84e4249e7857..ab67484feb3a63 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/missi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..3, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 0..1, value: Int( 0, @@ -26,6 +30,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -37,12 +42,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -52,6 +60,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_1.py.snap index 2951642fe3f0b5..9357c59dc3aa49 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__missing_rhs_1.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/missi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..18, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..11, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..5, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 0..1, value: Int( 1, @@ -29,6 +34,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 2, @@ -40,9 +46,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 8..11, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 3, @@ -52,6 +60,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..11, id: Name(""), ctx: Invalid, @@ -65,12 +74,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..18, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 13..18, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 4, @@ -80,6 +92,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..18, value: Int( 5, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__multiple_ops.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__multiple_ops.py.snap index 2bcbe82a7e502b..41adb471bfeb46 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__multiple_ops.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__multiple_ops.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/multiple_ops.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..19, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..3, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -26,10 +29,12 @@ Module( op: Add, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 2..3, op: UAdd, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -43,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..9, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4..9, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -58,6 +66,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 2, @@ -70,12 +79,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..13, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 10..13, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Load, @@ -84,10 +96,12 @@ Module( op: Sub, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 12..13, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..13, id: Name(""), ctx: Invalid, @@ -101,12 +115,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..19, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 14..19, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 14..15, value: Int( 1, @@ -116,6 +133,7 @@ Module( op: Sub, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__named_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__named_expression.py.snap index f38974105d4b6c..475add5dc262cb 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__named_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__named_expression.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/named_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..26, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..5, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -26,6 +29,7 @@ Module( op: Sub, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("y"), ctx: Load, @@ -37,13 +41,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..15, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 9..15, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 1, @@ -52,6 +59,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 2, @@ -67,12 +75,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..21, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 16..21, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Load, @@ -81,6 +92,7 @@ Module( op: Div, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("y"), ctx: Load, @@ -92,9 +104,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..26, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__starred_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__starred_expression.py.snap index cc1cc268ad3dd1..3fc18497c4ce55 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__starred_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bin_op__starred_expression.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bin_op/starred_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..14, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..6, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 0..6, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -26,9 +29,11 @@ Module( op: Add, right: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 4..6, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("y"), ctx: Load, @@ -43,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..14, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 7..14, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -57,9 +65,11 @@ Module( op: Pow, right: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 12..14, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__invalid_rhs_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__invalid_rhs_expression.py.snap index 9b4a04616ca2ac..a3515f3a7fa6d8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__invalid_rhs_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__invalid_rhs_expression.py.snap @@ -7,18 +7,22 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bool_op/inva ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 0..17, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -26,19 +30,26 @@ Module( ), Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 6..17, parameters: Some( Parameters { range: 13..14, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 13..14, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 13..14, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 13..14, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -52,6 +63,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("y"), ctx: Load, @@ -66,14 +78,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..31, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 19..31, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("x"), ctx: Load, @@ -81,10 +96,12 @@ Module( ), Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 24..31, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..31, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_lhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_lhs.py.snap index 6818a4a9bf6889..68ff71e827417a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_lhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_lhs.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bool_op/missing_lhs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..5, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..5, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_rhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_rhs.py.snap index 34a82c7f26085b..85ad8a317f91da 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_rhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__missing_rhs.py.snap @@ -7,18 +7,22 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/bool_op/miss ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..12, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 0..5, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -26,6 +30,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..5, id: Name(""), ctx: Invalid, @@ -38,12 +43,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..12, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 7..12, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, @@ -53,6 +61,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__named_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__named_expression.py.snap index c7af1705281fa1..e06f08171688b3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__named_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__named_expression.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bool_op/named_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 0..7, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -27,6 +30,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("a"), ctx: Load, @@ -39,9 +43,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..12, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("b"), ctx: Load, @@ -51,14 +57,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..19, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 13..19, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("x"), ctx: Load, @@ -66,6 +75,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("a"), ctx: Load, @@ -78,9 +88,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..24, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__starred_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__starred_expression.py.snap index 76db7d093a2117..5369e4e69bd796 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__starred_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__bool_op__starred_expression.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/bool_op/starred_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..16, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 0..8, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -27,9 +30,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("y"), ctx: Load, @@ -45,14 +50,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..16, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 9..16, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -60,9 +68,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 14..16, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_order.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_order.py.snap index efd6a63b4df801..a64df1f7b321fa 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_order.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_order.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/inva ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..131, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..10, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -28,10 +32,12 @@ Module( comparators: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 5..10, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("y"), ctx: Load, @@ -46,10 +52,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 35..41, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("x"), ctx: Store, @@ -58,9 +66,11 @@ Module( ], value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 38..41, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..38, id: Name(""), ctx: Invalid, @@ -72,6 +82,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("y"), ctx: Load, @@ -84,9 +95,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 120..121, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("x"), ctx: Load, @@ -96,13 +109,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..128, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 122..128, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..128, id: Name("is"), ctx: Load, @@ -114,9 +130,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 129..130, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_rhs_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_rhs_expression.py.snap index 7fac7231c751ef..a118b221db5f8e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_rhs_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__invalid_rhs_expression.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/inva ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..34, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..20, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..20, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -28,19 +32,26 @@ Module( comparators: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 9..20, parameters: Some( Parameters { range: 16..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 16..17, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 16..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 16..17, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -54,6 +65,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("y"), ctx: Load, @@ -68,12 +80,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..34, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 22..34, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -85,10 +100,12 @@ Module( comparators: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 27..34, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..34, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_lhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_lhs.py.snap index 755eb042a59798..a38a989312cb15 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_lhs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_lhs.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/miss ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("y"), ctx: Load, @@ -23,12 +26,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -38,6 +44,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_0.py.snap index 135f76de359383..b41171207cb07a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/miss ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..3, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -28,6 +32,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -40,12 +45,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -55,6 +63,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_1.py.snap index f61bcb3b891293..e58c0025af0d0e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_1.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/miss ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..71, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..60, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("x"), ctx: Load, @@ -23,13 +26,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..64, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 61..64, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..64, id: Name(""), ctx: Invalid, @@ -41,12 +47,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..71, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 66..71, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 66..67, value: Int( 1, @@ -56,6 +65,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 70..71, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_2.py.snap index e530d30468f2e4..f02148c9589400 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__missing_rhs_2.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/miss ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..15, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..8, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -28,6 +32,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..8, id: Name(""), ctx: Invalid, @@ -40,12 +45,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..15, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 10..15, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 1, @@ -55,6 +63,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 14..15, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__multiple_equals.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__multiple_equals.py.snap index d3879e17a80c70..023e710c958e25 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__multiple_equals.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__multiple_equals.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/multiple_equals.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 25..32, targets: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 25..29, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Load, @@ -30,6 +33,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..29, id: Name(""), ctx: Invalid, @@ -41,6 +45,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..32, id: Name("y"), ctx: Load, @@ -50,13 +55,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 33..40, targets: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 33..37, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..34, id: Name("x"), ctx: Load, @@ -68,6 +76,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..37, id: Name(""), ctx: Invalid, @@ -79,6 +88,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__named_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__named_expression.py.snap index 27be524dd397c3..fdeafe1b4d71f9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__named_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__named_expression.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/named_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..10, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -29,6 +32,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("y"), ctx: Load, @@ -41,13 +45,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..20, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 14..20, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 15..16, value: Int( 1, @@ -56,6 +63,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 2, @@ -71,12 +79,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..26, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 21..26, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("x"), ctx: Load, @@ -88,6 +99,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("y"), ctx: Load, @@ -100,9 +112,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..31, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 30..31, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__starred_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__starred_expression.py.snap index 61e69b69f969f2..90dd6b20562a66 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__starred_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__compare__starred_expression.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/compare/star ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..39, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..7, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -28,9 +32,11 @@ Module( comparators: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 5..7, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("y"), ctx: Load, @@ -46,12 +52,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..19, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 8..19, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("x"), ctx: Load, @@ -63,9 +72,11 @@ Module( comparators: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 17..19, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("y"), ctx: Load, @@ -81,15 +92,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..27, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 21..27, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 22..27, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -101,6 +116,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("y"), ctx: Load, @@ -116,15 +132,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..39, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 28..39, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 29..39, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -136,6 +156,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__comprehension.py.snap index 6d06ef0c123f98..7a000174912f13 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__comprehension.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/compreh ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..362, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..34, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 17..34, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("x"), ctx: Load, @@ -24,6 +28,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("y"), ctx: Load, @@ -32,8 +37,10 @@ Module( generators: [ Comprehension { range: 23..33, + node_index: AtomicNodeIndex(..), target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 27..28, value: Int( 1, @@ -42,6 +49,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("y"), ctx: Load, @@ -57,12 +65,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..54, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 35..54, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("x"), ctx: Load, @@ -70,6 +81,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Load, @@ -78,13 +90,16 @@ Module( generators: [ Comprehension { range: 41..53, + node_index: AtomicNodeIndex(..), target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 45..48, value: StringLiteralValue { inner: Single( StringLiteral { range: 45..48, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Single, @@ -98,6 +113,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("y"), ctx: Load, @@ -113,12 +129,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..77, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 55..77, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -126,6 +145,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("y"), ctx: Load, @@ -134,11 +154,14 @@ Module( generators: [ Comprehension { range: 61..76, + node_index: AtomicNodeIndex(..), target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 65..71, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..69, id: Name("call"), ctx: Load, @@ -146,6 +169,7 @@ Module( ), arguments: Arguments { range: 69..71, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -153,6 +177,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("y"), ctx: Load, @@ -168,12 +193,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..100, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 78..100, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("x"), ctx: Load, @@ -181,6 +209,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("y"), ctx: Load, @@ -189,12 +218,15 @@ Module( generators: [ Comprehension { range: 84..99, + node_index: AtomicNodeIndex(..), target: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 88..94, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("a"), ctx: Load, @@ -202,6 +234,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("b"), ctx: Load, @@ -212,6 +245,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("y"), ctx: Load, @@ -227,12 +261,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..135, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 117..135, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 118..119, id: Name("x"), ctx: Load, @@ -240,6 +277,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..122, id: Name("y"), ctx: Load, @@ -248,8 +286,10 @@ Module( generators: [ Comprehension { range: 123..134, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("x"), ctx: Store, @@ -257,9 +297,11 @@ Module( ), iter: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 132..134, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..134, id: Name("y"), ctx: Load, @@ -278,12 +320,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..159, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 136..159, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("x"), ctx: Load, @@ -291,6 +336,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("y"), ctx: Load, @@ -299,8 +345,10 @@ Module( generators: [ Comprehension { range: 142..158, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Store, @@ -308,10 +356,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 151..158, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..158, id: Name("y"), ctx: Load, @@ -330,12 +380,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 160..188, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 160..188, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("x"), ctx: Load, @@ -343,6 +396,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 164..165, id: Name("y"), ctx: Load, @@ -351,8 +405,10 @@ Module( generators: [ Comprehension { range: 166..187, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("x"), ctx: Store, @@ -360,9 +416,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 175..187, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 186..187, id: Name("y"), ctx: Load, @@ -380,12 +438,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 189..216, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 189..216, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 190..191, id: Name("x"), ctx: Load, @@ -393,6 +454,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..194, id: Name("y"), ctx: Load, @@ -401,8 +463,10 @@ Module( generators: [ Comprehension { range: 195..215, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("x"), ctx: Store, @@ -410,19 +474,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 204..215, parameters: Some( Parameters { range: 211..212, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 211..212, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 211..212, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 211..212, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -436,6 +507,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("y"), ctx: Load, @@ -453,12 +525,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 231..257, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 231..257, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 232..233, id: Name("x"), ctx: Load, @@ -466,6 +541,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 235..236, id: Name("y"), ctx: Load, @@ -474,8 +550,10 @@ Module( generators: [ Comprehension { range: 237..256, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..242, id: Name("x"), ctx: Store, @@ -483,6 +561,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 246..250, id: Name("data"), ctx: Load, @@ -491,9 +570,11 @@ Module( ifs: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 254..256, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..256, id: Name("y"), ctx: Load, @@ -512,12 +593,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 258..289, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 258..289, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("x"), ctx: Load, @@ -525,6 +609,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 262..263, id: Name("y"), ctx: Load, @@ -533,8 +618,10 @@ Module( generators: [ Comprehension { range: 264..288, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 268..269, id: Name("x"), ctx: Store, @@ -542,6 +629,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 273..277, id: Name("data"), ctx: Load, @@ -550,10 +638,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 281..288, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 287..288, id: Name("y"), ctx: Load, @@ -572,12 +662,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 290..326, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 290..326, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 291..292, id: Name("x"), ctx: Load, @@ -585,6 +678,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 294..295, id: Name("y"), ctx: Load, @@ -593,8 +687,10 @@ Module( generators: [ Comprehension { range: 296..325, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 300..301, id: Name("x"), ctx: Store, @@ -602,6 +698,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 305..309, id: Name("data"), ctx: Load, @@ -610,9 +707,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 313..325, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 324..325, id: Name("y"), ctx: Load, @@ -630,12 +729,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 327..362, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 327..362, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 328..329, id: Name("x"), ctx: Load, @@ -643,6 +745,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 331..332, id: Name("y"), ctx: Load, @@ -651,8 +754,10 @@ Module( generators: [ Comprehension { range: 333..361, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 337..338, id: Name("x"), ctx: Store, @@ -660,6 +765,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 342..346, id: Name("data"), ctx: Load, @@ -668,19 +774,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 350..361, parameters: Some( Parameters { range: 357..358, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 357..358, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 357..358, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 357..358, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -694,6 +807,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 360..361, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap index 18f34772fa4e09..2375e262a6783c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/double_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..278, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..135, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 125..135, items: [ DictItem { key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("x"), ctx: Load, @@ -30,6 +34,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 133..134, value: Int( 1, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..134, id: Name(""), ctx: Invalid, @@ -52,15 +58,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..162, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 136..162, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("a"), ctx: Load, @@ -69,6 +78,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 140..141, value: Int( 1, @@ -80,15 +90,18 @@ Module( key: None, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 145..161, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 150..154, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..146, id: Name("x"), ctx: Load, @@ -96,6 +109,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("y"), ctx: Load, @@ -111,28 +125,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..184, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 163..184, items: [ DictItem { key: None, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 166..177, parameters: Some( Parameters { range: 173..174, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 173..174, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 173..174, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 173..174, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -146,6 +169,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..177, id: Name("x"), ctx: Load, @@ -158,6 +182,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 179..180, id: Name("b"), ctx: Load, @@ -166,6 +191,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 182..183, value: Int( 2, @@ -180,15 +206,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..201, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 185..201, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 186..187, id: Name("a"), ctx: Load, @@ -197,6 +226,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 189..190, value: Int( 1, @@ -208,11 +238,13 @@ Module( key: None, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 194..200, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 194..195, id: Name("x"), ctx: Load, @@ -220,6 +252,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("y"), ctx: Load, @@ -236,20 +269,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 202..219, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 202..219, items: [ DictItem { key: None, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 205..212, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 205..206, id: Name("x"), ctx: Load, @@ -257,6 +294,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 211..212, id: Name("y"), ctx: Load, @@ -270,6 +308,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("b"), ctx: Load, @@ -278,6 +317,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 217..218, value: Int( 2, @@ -292,15 +332,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 220..241, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 220..241, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 221..222, id: Name("a"), ctx: Load, @@ -309,6 +352,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 224..225, value: Int( 1, @@ -320,10 +364,12 @@ Module( key: None, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 229..234, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..234, id: Name("x"), ctx: Load, @@ -336,6 +382,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("b"), ctx: Load, @@ -344,6 +391,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..240, value: Int( 2, @@ -358,18 +406,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 242..252, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 242..252, items: [ DictItem { key: None, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 245..251, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("x"), ctx: Load, @@ -381,6 +433,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..251, id: Name("y"), ctx: Load, @@ -397,18 +450,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 253..267, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 253..267, items: [ DictItem { key: None, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 256..266, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 256..257, id: Name("x"), ctx: Load, @@ -420,6 +477,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("y"), ctx: Load, @@ -436,18 +494,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 268..277, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 268..277, items: [ DictItem { key: None, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 271..276, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 271..272, id: Name("x"), ctx: Load, @@ -459,6 +521,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 275..276, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap index 2cdfca37149d4b..22ca3a3d362a79 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/double_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..358, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..147, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 122..147, items: [ DictItem { key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("x"), ctx: Load, @@ -30,6 +34,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("y"), ctx: Load, @@ -38,6 +43,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..133, id: Name("for"), ctx: Load, @@ -48,6 +54,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..135, id: Name("x"), ctx: Load, @@ -56,6 +63,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..135, id: Name(""), ctx: Invalid, @@ -66,9 +74,11 @@ Module( key: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 137..146, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("y"), ctx: Load, @@ -80,6 +90,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..146, id: Name("data"), ctx: Load, @@ -91,6 +102,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..146, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap index 45b456fe189ec0..3b205a80f1463c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..24, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 0..24, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -28,6 +32,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..8, id: Name("def"), ctx: Load, @@ -38,9 +43,11 @@ Module( key: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 9..14, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..12, id: Name("foo"), ctx: Load, @@ -48,6 +55,7 @@ Module( ), arguments: Arguments { range: 12..14, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -56,6 +64,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..24, id: Name("pass"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap index 67b4fca986ec0e..487654f51db0e8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 0..10, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -28,9 +32,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -40,6 +46,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap index 99ceea07ed510d..474f5b303833a4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..27, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..6, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 0..6, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -28,6 +32,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -42,16 +47,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 8..27, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 12..15, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 15..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -62,6 +72,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 23..27, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap index b3123ce9b03173..a6bcfaa62163d9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap @@ -7,22 +7,27 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/named_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..84, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..77, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 55..77, items: [ DictItem { key: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 56..62, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Store, @@ -30,6 +35,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 1, @@ -41,6 +47,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..65, id: Name("y"), ctx: Load, @@ -51,6 +58,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("z"), ctx: Load, @@ -59,6 +67,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..68, id: Name(""), ctx: Invalid, @@ -69,6 +78,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 72..73, value: Int( 2, @@ -78,6 +88,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("a"), ctx: Load, @@ -91,12 +102,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 79..84, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 79..84, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("x"), ctx: Load, @@ -105,6 +119,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap index dd7855f8b47919..47c76d0afcc717 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/named_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..86, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..79, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 57..79, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("x"), ctx: Load, @@ -28,6 +32,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..62, id: Name("y"), ctx: Load, @@ -38,6 +43,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 66..67, value: Int( 1, @@ -47,6 +53,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..67, id: Name(""), ctx: Invalid, @@ -57,6 +64,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..70, id: Name("z"), ctx: Load, @@ -65,6 +73,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("a"), ctx: Load, @@ -75,6 +84,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 77..78, value: Int( 2, @@ -84,6 +94,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..78, id: Name(""), ctx: Invalid, @@ -97,12 +108,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..86, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 81..86, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("x"), ctx: Load, @@ -111,6 +125,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..86, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap index 71d01983722cc0..ecfb8d7b9a15da 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/dict/recover ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..346, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 88..91, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 88..91, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..89, id: Name(""), ctx: Invalid, @@ -30,15 +34,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..105, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 93..105, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 94..95, value: Int( 1, @@ -48,6 +55,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 97..98, value: Int( 2, @@ -59,6 +67,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..101, value: Int( 3, @@ -68,6 +77,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 103..104, value: Int( 4, @@ -82,15 +92,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..115, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 107..115, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 108..109, value: Int( 1, @@ -100,6 +113,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 111..112, value: Int( 2, @@ -114,15 +128,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..144, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 133..144, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 134..135, value: Int( 1, @@ -132,6 +149,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 137..138, value: Int( 2, @@ -143,6 +161,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 139..140, value: Int( 3, @@ -152,6 +171,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 142..143, value: Int( 4, @@ -166,15 +186,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 157..162, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 157..162, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 1, @@ -184,6 +207,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..160, id: Name(""), ctx: Invalid, @@ -197,15 +221,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 201..205, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 201..205, items: [ DictItem { key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 204..204, id: Name(""), ctx: Invalid, @@ -219,15 +246,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 206..222, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 206..222, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 207..208, id: Name("x"), ctx: Load, @@ -236,6 +266,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 210..211, id: Name("y"), ctx: Load, @@ -246,6 +277,7 @@ Module( key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 215..215, id: Name(""), ctx: Invalid, @@ -256,6 +288,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 217..218, id: Name("a"), ctx: Load, @@ -264,6 +297,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 220..221, id: Name("b"), ctx: Load, @@ -277,18 +311,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 310..330, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 310..330, items: [ DictItem { key: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 311..313, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 312..313, id: Name("x"), ctx: Load, @@ -300,6 +338,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 315..316, id: Name("y"), ctx: Load, @@ -310,6 +349,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 318..319, id: Name("z"), ctx: Load, @@ -318,6 +358,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 321..322, id: Name("a"), ctx: Load, @@ -328,9 +369,11 @@ Module( key: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 324..326, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 325..326, id: Name("b"), ctx: Load, @@ -342,6 +385,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 328..329, id: Name("c"), ctx: Load, @@ -355,15 +399,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 331..345, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 331..345, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 332..333, id: Name("x"), ctx: Load, @@ -372,9 +419,11 @@ Module( ), value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 335..337, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 336..337, id: Name("y"), ctx: Load, @@ -388,6 +437,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 339..340, id: Name("z"), ctx: Load, @@ -396,9 +446,11 @@ Module( ), value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 342..344, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 343..344, id: Name("a"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_identifiers.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_identifiers.py.snap index 7a4f5b3f5d6437..bf215d18b6919d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_identifiers.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_identifiers.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/emoji_identi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..64, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..5, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Store, @@ -23,6 +26,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..5, id: Name(""), ctx: Invalid, @@ -32,10 +36,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 32..37, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("a"), ctx: Store, @@ -44,6 +50,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..37, id: Name(""), ctx: Invalid, @@ -53,13 +60,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 42..43, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 42..43, op: UAdd, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..43, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_statement.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_statement.py.snap index 8738dedd6d3e4f..d4c139b3cc46e0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_statement.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__emoji_statement.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/emoji_statement.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..5, body: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_0.py.snap index 380694740073e9..b480e99948aa3b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/if/missing_o ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..88, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..67, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 53..67, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..62, id: Name("expr"), ctx: Load, @@ -24,6 +28,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..67, id: Name(""), ctx: Invalid, @@ -42,16 +48,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 69..88, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 73..76, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 76..78, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -62,6 +73,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 84..88, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_1.py.snap index 21c4bce330eef0..beec6bcb149944 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_orelse_expr_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/if/missing_o ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..76, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..69, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 55..69, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..64, id: Name("expr"), ctx: Load, @@ -24,6 +28,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..69, id: Name(""), ctx: Invalid, @@ -42,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..76, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 71..76, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 1, @@ -57,6 +66,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_0.py.snap index e621d76610c15e..d3c32935800bd6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/if/missing_t ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..76, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 51..55, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 51..55, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..55, id: Name(""), ctx: Invalid, @@ -24,6 +28,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..55, id: Name(""), ctx: Invalid, @@ -42,16 +48,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 57..76, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 61..64, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 64..66, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -62,6 +73,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 72..76, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_1.py.snap index 1289e838434a5d..39ddb5266add89 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__missing_test_expr_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/if/missing_t ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..64, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..57, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 53..57, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..57, id: Name(""), ctx: Invalid, @@ -24,6 +28,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..57, id: Name(""), ctx: Invalid, @@ -42,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..64, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 59..64, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 59..60, value: Int( 1, @@ -57,6 +66,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__recover.py.snap index dd35e9a27d7102..e62779397c09c5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__if__recover.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/if/recover.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..215, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..43, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 26..43, test: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 31..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..36, id: Name("expr"), ctx: Load, @@ -30,6 +35,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("y"), ctx: Load, @@ -48,25 +55,34 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..67, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 44..67, test: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 49..60, parameters: Some( Parameters { range: 56..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 56..57, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 56..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 56..57, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -80,6 +96,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("x"), ctx: Load, @@ -89,6 +106,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Load, @@ -96,6 +114,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("y"), ctx: Load, @@ -107,16 +126,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..87, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 68..87, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 73..80, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("x"), ctx: Load, @@ -127,6 +150,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -134,6 +158,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("y"), ctx: Load, @@ -145,15 +170,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 88..112, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 88..112, test: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 93..105, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("x"), ctx: Load, @@ -163,6 +192,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("x"), ctx: Load, @@ -170,6 +200,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("y"), ctx: Load, @@ -181,12 +212,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 142..164, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 142..164, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 147..151, id: Name("expr"), ctx: Load, @@ -194,6 +228,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..143, id: Name("x"), ctx: Load, @@ -201,9 +236,11 @@ Module( ), orelse: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 157..164, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 158..164, id: Name("orelse"), ctx: Load, @@ -218,12 +255,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 165..187, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 165..187, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..174, id: Name("expr"), ctx: Load, @@ -231,6 +271,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("x"), ctx: Load, @@ -238,10 +279,12 @@ Module( ), orelse: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 180..187, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 186..187, id: Name("y"), ctx: Load, @@ -256,12 +299,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 188..215, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 188..215, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..197, id: Name("expr"), ctx: Load, @@ -269,6 +315,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 188..189, id: Name("x"), ctx: Load, @@ -276,9 +323,11 @@ Module( ), orelse: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 203..215, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_default_parameters.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_default_parameters.py.snap index 3d9df97c4640eb..ac95e6aff76d7a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_default_parameters.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_default_parameters.py.snap @@ -1,33 +1,41 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/lambda_default_parameters.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..20, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..20, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..20, parameters: Some( Parameters { range: 7..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 7..8, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 7..8, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -35,17 +43,21 @@ Module( }, ParameterWithDefault { range: 10..14, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 10..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 10..11, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 12..14, value: Int( 20, @@ -56,11 +68,14 @@ Module( }, ParameterWithDefault { range: 16..17, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 16..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 16..17, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -74,6 +89,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_duplicate_parameters.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_duplicate_parameters.py.snap index 6ea4a192edaf1e..30e3bf68b1f181 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_duplicate_parameters.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__lambda_duplicate_parameters.py.snap @@ -7,26 +7,35 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/lambda_dupli ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..91, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..14, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..14, parameters: Some( Parameters { range: 7..11, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 7..8, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 7..8, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -34,11 +43,14 @@ Module( }, ParameterWithDefault { range: 10..11, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 10..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 10..11, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -52,6 +64,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, @@ -64,22 +77,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..33, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 16..33, parameters: Some( Parameters { range: 23..30, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 23..24, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 23..24, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 23..24, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -90,11 +111,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 29..30, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 29..30, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 29..30, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -106,6 +130,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, @@ -118,22 +143,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..52, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 35..52, parameters: Some( Parameters { range: 42..49, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 42..43, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 42..43, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 42..43, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -141,17 +174,21 @@ Module( }, ParameterWithDefault { range: 45..49, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 45..46, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 45..46, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 47..49, value: Int( 20, @@ -168,6 +205,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, @@ -180,22 +218,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..69, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 54..69, parameters: Some( Parameters { range: 61..66, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 61..62, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 61..62, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 61..62, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -205,9 +251,11 @@ Module( vararg: Some( Parameter { range: 64..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 65..66, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -218,6 +266,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 68..69, value: Int( 1, @@ -230,22 +279,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..90, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 71..90, parameters: Some( Parameters { range: 78..87, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 78..79, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 78..79, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 78..79, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -257,9 +314,11 @@ Module( kwarg: Some( Parameter { range: 84..87, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 86..87, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -268,6 +327,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__comprehension.py.snap index 124079789dd458..110db0cba9fa19 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__comprehension.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/compreh ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..376, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 33..48, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 33..48, elt: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 34..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("x"), ctx: Load, @@ -31,8 +36,10 @@ Module( generators: [ Comprehension { range: 37..47, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Store, @@ -40,6 +47,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("y"), ctx: Load, @@ -55,12 +63,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..81, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 67..81, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -69,8 +80,10 @@ Module( generators: [ Comprehension { range: 70..80, + node_index: AtomicNodeIndex(..), target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 74..75, value: Int( 1, @@ -79,6 +92,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("y"), ctx: Load, @@ -94,12 +108,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..98, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 82..98, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -108,13 +125,16 @@ Module( generators: [ Comprehension { range: 85..97, + node_index: AtomicNodeIndex(..), target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 89..92, value: StringLiteralValue { inner: Single( StringLiteral { range: 89..92, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Single, @@ -128,6 +148,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("y"), ctx: Load, @@ -143,12 +164,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 99..118, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 99..118, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Load, @@ -157,11 +181,14 @@ Module( generators: [ Comprehension { range: 102..117, + node_index: AtomicNodeIndex(..), target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 106..112, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..110, id: Name("call"), ctx: Load, @@ -169,6 +196,7 @@ Module( ), arguments: Arguments { range: 110..112, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -176,6 +204,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("y"), ctx: Load, @@ -191,12 +220,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..138, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 119..138, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("x"), ctx: Load, @@ -205,12 +237,15 @@ Module( generators: [ Comprehension { range: 122..137, + node_index: AtomicNodeIndex(..), target: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 126..132, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("a"), ctx: Load, @@ -218,6 +253,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..131, id: Name("b"), ctx: Load, @@ -228,6 +264,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("y"), ctx: Load, @@ -243,12 +280,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..170, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 155..170, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("x"), ctx: Load, @@ -257,8 +297,10 @@ Module( generators: [ Comprehension { range: 158..169, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 162..163, id: Name("x"), ctx: Store, @@ -266,9 +308,11 @@ Module( ), iter: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 167..169, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..169, id: Name("y"), ctx: Load, @@ -287,12 +331,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 171..191, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 171..191, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 172..173, id: Name("x"), ctx: Load, @@ -301,8 +348,10 @@ Module( generators: [ Comprehension { range: 174..190, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("x"), ctx: Store, @@ -310,10 +359,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 183..190, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("y"), ctx: Load, @@ -332,12 +383,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 192..217, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 192..217, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..194, id: Name("x"), ctx: Load, @@ -346,8 +400,10 @@ Module( generators: [ Comprehension { range: 195..216, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("x"), ctx: Store, @@ -355,9 +411,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 204..216, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 215..216, id: Name("y"), ctx: Load, @@ -375,12 +433,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 218..242, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 218..242, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 219..220, id: Name("x"), ctx: Load, @@ -389,8 +450,10 @@ Module( generators: [ Comprehension { range: 221..241, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("x"), ctx: Store, @@ -398,19 +461,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 230..241, parameters: Some( Parameters { range: 237..238, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 237..238, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 237..238, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 237..238, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -424,6 +494,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 240..241, id: Name("y"), ctx: Load, @@ -441,12 +512,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 257..280, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 257..280, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..259, id: Name("x"), ctx: Load, @@ -455,8 +529,10 @@ Module( generators: [ Comprehension { range: 260..279, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 264..265, id: Name("x"), ctx: Store, @@ -464,6 +540,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 269..273, id: Name("data"), ctx: Load, @@ -472,9 +549,11 @@ Module( ifs: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 277..279, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 278..279, id: Name("y"), ctx: Load, @@ -493,12 +572,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 281..309, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 281..309, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 282..283, id: Name("x"), ctx: Load, @@ -507,8 +589,10 @@ Module( generators: [ Comprehension { range: 284..308, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 288..289, id: Name("x"), ctx: Store, @@ -516,6 +600,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 293..297, id: Name("data"), ctx: Load, @@ -524,10 +609,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 301..308, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 307..308, id: Name("y"), ctx: Load, @@ -546,12 +633,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 310..343, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 310..343, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 311..312, id: Name("x"), ctx: Load, @@ -560,8 +650,10 @@ Module( generators: [ Comprehension { range: 313..342, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 317..318, id: Name("x"), ctx: Store, @@ -569,6 +661,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 322..326, id: Name("data"), ctx: Load, @@ -577,9 +670,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 330..342, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 341..342, id: Name("y"), ctx: Load, @@ -597,12 +692,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 344..376, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 344..376, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 345..346, id: Name("x"), ctx: Load, @@ -611,8 +709,10 @@ Module( generators: [ Comprehension { range: 347..375, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..352, id: Name("x"), ctx: Store, @@ -620,6 +720,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 356..360, id: Name("data"), ctx: Load, @@ -628,19 +729,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 364..375, parameters: Some( Parameters { range: 371..372, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 371..372, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 371..372, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 371..372, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -654,6 +762,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 374..375, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_0.py.snap index efe0f9aaa32cb7..04f72aae2310ff 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_0.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 42..43, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 42..43, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..43, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_1.py.snap index 20f04c756cc78d..da496ea1cd9c41 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_1.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..133, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..133, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 125..133, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 128..133, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("x"), ctx: Load, @@ -29,6 +34,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_2.py.snap index c253897cdee8f3..c65c7f3f8f7f57 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_2.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..141, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 131..141, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 131..141, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 132..133, value: Int( 1, @@ -26,9 +30,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 136..141, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_3.py.snap index 9a64dd38fcd626..833765842b84fe 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__missing_closing_bracket_3.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/missing ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..140, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 114..119, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 114..119, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 1, @@ -26,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 2, @@ -40,16 +45,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 121..140, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 125..128, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 128..130, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -60,6 +70,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 136..140, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__recover.py.snap index a3979fcf8a6da3..e3535344a65053 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__recover.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/recover ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..208, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..85, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 82..85, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..83, id: Name(""), ctx: Invalid, @@ -31,13 +35,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..93, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 87..93, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 1, @@ -46,6 +53,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 91..92, value: Int( 2, @@ -60,13 +68,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..100, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 95..100, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 96..97, value: Int( 1, @@ -81,13 +92,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..123, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 118..123, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -96,6 +110,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 121..122, value: Int( 2, @@ -110,13 +125,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 156..162, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 156..162, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 157..158, value: Int( 1, @@ -125,6 +143,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..161, value: Int( 2, @@ -139,13 +158,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..194, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 185..194, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 186..187, value: Int( 1, @@ -154,9 +176,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 189..192, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("x"), ctx: Load, @@ -165,6 +189,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..192, id: Name(""), ctx: Invalid, @@ -180,13 +205,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 196..202, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 196..202, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..198, value: Int( 1, @@ -195,6 +223,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 200..201, value: Int( 2, @@ -209,16 +238,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 204..207, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 204..207, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 205..206, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 206..206, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__star_expression_precedence.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__star_expression_precedence.py.snap index 97dfb3722e30cb..caa6a5a30d193c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__star_expression_precedence.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__list__star_expression_precedence.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/list/star_ex ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..200, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..93, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 84..93, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 86..88, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("y"), ctx: Load, @@ -44,19 +50,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..106, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 94..106, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 95..102, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 96..102, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("x"), ctx: Load, @@ -68,6 +79,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..102, id: Name("y"), ctx: Load, @@ -81,6 +93,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("z"), ctx: Load, @@ -94,20 +107,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..118, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 107..118, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 108..114, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 109..114, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("x"), ctx: Load, @@ -120,6 +138,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("z"), ctx: Load, @@ -133,21 +152,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..132, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 119..132, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 120..128, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 121..128, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..122, id: Name("x"), ctx: Load, @@ -155,6 +179,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("y"), ctx: Load, @@ -168,6 +193,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..131, id: Name("z"), ctx: Load, @@ -181,21 +207,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..145, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 133..145, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 134..141, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 135..141, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("x"), ctx: Load, @@ -203,6 +234,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("y"), ctx: Load, @@ -216,6 +248,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..144, id: Name("z"), ctx: Load, @@ -229,25 +262,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 146..168, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 146..168, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 147..164, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 148..164, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 153..157, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..149, id: Name("x"), ctx: Load, @@ -255,6 +294,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("y"), ctx: Load, @@ -267,6 +307,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("z"), ctx: Load, @@ -280,29 +321,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 169..186, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 169..186, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 170..182, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 171..182, parameters: Some( Parameters { range: 178..179, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 178..179, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 178..179, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 178..179, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -316,6 +367,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 181..182, id: Name("x"), ctx: Load, @@ -328,6 +380,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..185, id: Name("z"), ctx: Load, @@ -341,19 +394,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 187..199, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 187..199, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 188..195, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 188..190, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("x"), ctx: Store, @@ -364,6 +422,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 194..195, value: Int( 2, @@ -374,6 +433,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__invalid_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__invalid_target.py.snap index c10ee4aac896e9..e19733fe689161 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__invalid_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__invalid_target.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/invali ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..109, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..68, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 59..67, target: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 59..62, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("x"), ctx: Load, @@ -28,12 +33,14 @@ Module( attr: Identifier { id: Name("y"), range: 61..62, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 66..67, value: Int( 1, @@ -46,15 +53,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..80, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 70..79, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 70..74, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Load, @@ -62,6 +73,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("y"), ctx: Load, @@ -72,6 +84,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 1, @@ -84,15 +97,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..90, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 82..89, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 82..84, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Store, @@ -103,6 +120,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 1, @@ -115,16 +133,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..109, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 92..108, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 92..98, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("x"), ctx: Store, @@ -132,6 +154,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("y"), ctx: Store, @@ -143,10 +166,12 @@ Module( ), value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 102..108, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 103..104, value: Int( 1, @@ -155,6 +180,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 106..107, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_0.py.snap index 33371ac311523c..8015e50f6a7c09 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_0.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/missin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..75, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..72, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_1.py.snap index 7fbfdf4dc92044..af8dfa521093d6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/missin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..33, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..33, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 29..33, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Store, @@ -24,6 +28,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..33, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_2.py.snap index 77c7726c6944e9..aaa227cdfe1035 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_2.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/missin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..87, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..71, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 62..71, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Store, @@ -24,6 +28,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..71, id: Name("def"), ctx: Load, @@ -35,12 +40,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 72..87, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 72..77, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..75, id: Name("foo"), ctx: Load, @@ -48,6 +56,7 @@ Module( ), arguments: Arguments { range: 75..77, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -55,6 +64,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..87, id: Name("pass"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_3.py.snap index 1ec0d5d924e361..e7e25c09721362 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_3.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/missin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..112, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 100..112, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 101..112, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..102, id: Name("x"), ctx: Store, @@ -24,9 +28,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 107..112, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..108, id: Name("x"), ctx: Load, @@ -35,6 +41,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_4.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_4.py.snap index 8f056e9ec54ba5..dc699731cef507 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_4.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__named__missing_expression_4.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/named/missin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..78, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..71, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 65..69, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Store, @@ -24,6 +28,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..69, id: Name(""), ctx: Invalid, @@ -35,12 +40,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 73..78, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 73..78, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("x"), ctx: Load, @@ -49,6 +57,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__generator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__generator.py.snap index f547981164cd12..3ac0c1bdffc2c8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__generator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__generator.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 0..15, elt: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("x"), ctx: Load, @@ -31,8 +36,10 @@ Module( generators: [ Comprehension { range: 4..14, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("x"), ctx: Store, @@ -40,6 +47,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("y"), ctx: Load, @@ -56,16 +64,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..24, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 16..24, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 17..23, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Store, @@ -73,6 +85,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 22..23, value: Int( 1, @@ -90,10 +103,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 25..35, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Store, @@ -101,6 +116,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_0.py.snap index 7b610ed0d11b4b..1b1d87fe6d0910 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_0.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..47, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..47, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_1.py.snap index 02ff2cf395a0f5..5dee13a8a2d29e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..137, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 129..137, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 132..137, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("x"), ctx: Load, @@ -25,6 +29,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_2.py.snap index b793308c04bef3..b5721df9676f0c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_2.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..146, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..146, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 136..146, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 137..138, value: Int( 1, @@ -26,9 +30,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 141..146, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 141..142, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..146, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_3.py.snap index 9afe7ee6e72b1b..bbf1fb9749f301 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__missing_closing_paren_3.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..144, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..123, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 118..123, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -26,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 2, @@ -41,16 +46,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 125..144, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 129..132, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 132..134, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -61,6 +71,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 140..144, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__parenthesized.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__parenthesized.py.snap index c61c600e25c388..9012f3e1bef883 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__parenthesized.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__parenthesized.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..125, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..70, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 67..69, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -29,9 +33,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..120, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..120, id: Name("x"), ctx: Load, @@ -41,9 +47,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 124..125, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 124..125, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple.py.snap index e62134566929bb..a44a6fc0663a48 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..267, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..86, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 83..86, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..84, id: Name(""), ctx: Invalid, @@ -32,13 +36,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 88..94, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 88..94, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 1, @@ -47,6 +54,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 92..93, value: Int( 2, @@ -62,13 +70,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..101, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 96..101, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 97..98, value: Int( 1, @@ -84,9 +95,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..121, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 120..121, value: Int( 1, @@ -97,9 +110,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..123, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 2, @@ -110,9 +125,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 157..162, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 1, @@ -121,6 +138,7 @@ Module( ), annotation: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 161..162, value: Int( 2, @@ -133,13 +151,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 186..195, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 186..195, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 187..188, value: Int( 1, @@ -148,9 +169,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 190..193, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 190..191, id: Name("x"), ctx: Load, @@ -159,6 +182,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..193, id: Name(""), ctx: Invalid, @@ -175,9 +199,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 197..199, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 198..199, value: Int( 1, @@ -188,9 +214,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 201..202, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 2, @@ -201,13 +229,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 255..267, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 255..267, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..256, id: Name("x"), ctx: Load, @@ -215,6 +246,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..259, id: Name("y"), ctx: Load, @@ -222,6 +254,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 263..264, value: Int( 2, @@ -230,6 +263,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..267, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap index a81a6db84be53a..b566c6a5948533 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__parenthesized__tuple_starred_expr.py.snap @@ -7,23 +7,29 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/parenthesize ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..532, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 157..178, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 157..178, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 158..165, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 159..165, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..160, id: Name("x"), ctx: Load, @@ -35,6 +41,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 164..165, id: Name("y"), ctx: Load, @@ -48,6 +55,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 167..168, id: Name("z"), ctx: Load, @@ -55,12 +63,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 170..177, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 171..177, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("x"), ctx: Load, @@ -72,6 +83,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..177, id: Name("y"), ctx: Load, @@ -92,20 +104,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 179..198, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 179..198, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 180..186, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 181..186, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..186, id: Name("x"), ctx: Load, @@ -118,6 +135,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 188..189, id: Name("z"), ctx: Load, @@ -125,13 +143,16 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 191..197, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 192..197, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("x"), ctx: Load, @@ -151,21 +172,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 199..222, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 199..222, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 200..208, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 201..208, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 201..202, id: Name("x"), ctx: Load, @@ -173,6 +199,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 207..208, id: Name("y"), ctx: Load, @@ -186,6 +213,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 210..211, id: Name("z"), ctx: Load, @@ -193,14 +221,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 213..221, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 214..221, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("x"), ctx: Load, @@ -208,6 +239,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 220..221, id: Name("y"), ctx: Load, @@ -228,21 +260,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 223..244, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 223..244, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 224..231, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 225..231, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("x"), ctx: Load, @@ -250,6 +287,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 230..231, id: Name("y"), ctx: Load, @@ -263,6 +301,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..234, id: Name("z"), ctx: Load, @@ -270,14 +309,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 236..243, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 237..243, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 237..238, id: Name("x"), ctx: Load, @@ -285,6 +327,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 242..243, id: Name("y"), ctx: Load, @@ -305,25 +348,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 245..286, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 245..286, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 246..263, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 247..263, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 252..256, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 247..248, id: Name("x"), ctx: Load, @@ -331,6 +380,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 262..263, id: Name("y"), ctx: Load, @@ -343,6 +393,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("z"), ctx: Load, @@ -350,18 +401,22 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 268..285, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 269..285, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 274..278, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 269..270, id: Name("x"), ctx: Load, @@ -369,6 +424,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 284..285, id: Name("y"), ctx: Load, @@ -388,29 +444,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 287..318, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 287..318, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 288..300, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 289..300, parameters: Some( Parameters { range: 296..297, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 296..297, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 296..297, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 296..297, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -424,6 +490,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 299..300, id: Name("x"), ctx: Load, @@ -436,6 +503,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 302..303, id: Name("z"), ctx: Load, @@ -443,22 +511,30 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 305..317, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 306..317, parameters: Some( Parameters { range: 313..314, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 313..314, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 313..314, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 313..314, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -472,6 +548,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 316..317, id: Name("x"), ctx: Load, @@ -491,19 +568,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 319..340, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 319..340, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 320..327, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 320..322, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 321..322, id: Name("x"), ctx: Store, @@ -514,6 +596,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 326..327, value: Int( 2, @@ -524,6 +607,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 329..330, id: Name("z"), ctx: Load, @@ -531,12 +615,15 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 332..339, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 332..334, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 333..334, id: Name("x"), ctx: Store, @@ -547,6 +634,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 338..339, value: Int( 2, @@ -564,19 +652,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 363..382, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 363..382, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 363..370, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 364..370, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 364..365, id: Name("x"), ctx: Load, @@ -588,6 +681,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 369..370, id: Name("y"), ctx: Load, @@ -601,6 +695,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 372..373, id: Name("z"), ctx: Load, @@ -608,12 +703,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 375..382, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 376..382, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 376..377, id: Name("x"), ctx: Load, @@ -625,6 +723,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 381..382, id: Name("y"), ctx: Load, @@ -645,20 +744,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 383..400, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 383..400, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 383..389, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 384..389, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 388..389, id: Name("x"), ctx: Load, @@ -671,6 +775,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 391..392, id: Name("z"), ctx: Load, @@ -678,13 +783,16 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 394..400, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 395..400, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 399..400, id: Name("x"), ctx: Load, @@ -704,21 +812,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 401..422, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 401..422, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 401..409, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 402..409, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 402..403, id: Name("x"), ctx: Load, @@ -726,6 +839,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 408..409, id: Name("y"), ctx: Load, @@ -739,6 +853,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 411..412, id: Name("z"), ctx: Load, @@ -746,14 +861,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 414..422, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 415..422, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 415..416, id: Name("x"), ctx: Load, @@ -761,6 +879,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..422, id: Name("y"), ctx: Load, @@ -781,21 +900,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 423..442, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 423..442, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 423..430, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 424..430, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 424..425, id: Name("x"), ctx: Load, @@ -803,6 +927,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 429..430, id: Name("y"), ctx: Load, @@ -816,6 +941,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 432..433, id: Name("z"), ctx: Load, @@ -823,14 +949,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 435..442, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 436..442, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 436..437, id: Name("x"), ctx: Load, @@ -838,6 +967,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 441..442, id: Name("y"), ctx: Load, @@ -858,25 +988,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 443..482, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 443..482, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 443..460, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 444..460, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 449..453, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 444..445, id: Name("x"), ctx: Load, @@ -884,6 +1020,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 459..460, id: Name("y"), ctx: Load, @@ -896,6 +1033,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 462..463, id: Name("z"), ctx: Load, @@ -903,18 +1041,22 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 465..482, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 466..482, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 471..475, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 466..467, id: Name("x"), ctx: Load, @@ -922,6 +1064,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 481..482, id: Name("y"), ctx: Load, @@ -941,29 +1084,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 483..512, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 483..512, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 483..495, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 484..495, parameters: Some( Parameters { range: 491..492, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 491..492, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 491..492, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 491..492, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -977,6 +1130,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 494..495, id: Name("x"), ctx: Load, @@ -989,6 +1143,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 497..498, id: Name("z"), ctx: Load, @@ -996,22 +1151,30 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 500..512, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 501..512, parameters: Some( Parameters { range: 508..509, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 508..509, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 508..509, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 508..509, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1025,6 +1188,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 511..512, id: Name("x"), ctx: Load, @@ -1044,12 +1208,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 513..515, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 513..515, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 514..515, id: Name("x"), ctx: Load, @@ -1062,13 +1229,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 519..532, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 519..532, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 519..520, value: Int( 2, @@ -1077,6 +1247,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 522..523, id: Name("z"), ctx: Load, @@ -1084,9 +1255,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 525..527, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 526..527, id: Name("x"), ctx: Load, @@ -1097,6 +1270,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 531..532, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__comprehension.py.snap index b692643db9843d..abf42e4b03000d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__comprehension.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/comprehe ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..377, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 33..48, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 33..48, elt: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 34..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("x"), ctx: Load, @@ -31,8 +36,10 @@ Module( generators: [ Comprehension { range: 37..47, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Store, @@ -40,6 +47,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("y"), ctx: Load, @@ -55,12 +63,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..81, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 67..81, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -69,8 +80,10 @@ Module( generators: [ Comprehension { range: 70..80, + node_index: AtomicNodeIndex(..), target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 74..75, value: Int( 1, @@ -79,6 +92,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..80, id: Name("y"), ctx: Load, @@ -94,12 +108,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..98, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 82..98, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -108,13 +125,16 @@ Module( generators: [ Comprehension { range: 85..97, + node_index: AtomicNodeIndex(..), target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 89..92, value: StringLiteralValue { inner: Single( StringLiteral { range: 89..92, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Single, @@ -128,6 +148,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("y"), ctx: Load, @@ -143,12 +164,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 99..118, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 99..118, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Load, @@ -157,11 +181,14 @@ Module( generators: [ Comprehension { range: 102..117, + node_index: AtomicNodeIndex(..), target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 106..112, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..110, id: Name("call"), ctx: Load, @@ -169,6 +196,7 @@ Module( ), arguments: Arguments { range: 110..112, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -176,6 +204,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("y"), ctx: Load, @@ -191,12 +220,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..138, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 119..138, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("x"), ctx: Load, @@ -205,12 +237,15 @@ Module( generators: [ Comprehension { range: 122..137, + node_index: AtomicNodeIndex(..), target: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 126..132, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("a"), ctx: Load, @@ -218,6 +253,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..131, id: Name("b"), ctx: Load, @@ -228,6 +264,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("y"), ctx: Load, @@ -243,12 +280,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..170, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 155..170, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("x"), ctx: Load, @@ -257,8 +297,10 @@ Module( generators: [ Comprehension { range: 158..169, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 162..163, id: Name("x"), ctx: Store, @@ -266,9 +308,11 @@ Module( ), iter: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 167..169, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..169, id: Name("y"), ctx: Load, @@ -287,12 +331,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 171..191, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 171..191, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 172..173, id: Name("x"), ctx: Load, @@ -301,8 +348,10 @@ Module( generators: [ Comprehension { range: 174..190, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("x"), ctx: Store, @@ -310,10 +359,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 183..190, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("y"), ctx: Load, @@ -332,12 +383,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 192..217, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 192..217, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..194, id: Name("x"), ctx: Load, @@ -346,8 +400,10 @@ Module( generators: [ Comprehension { range: 195..216, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("x"), ctx: Store, @@ -355,9 +411,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 204..216, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 215..216, id: Name("y"), ctx: Load, @@ -375,12 +433,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 218..242, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 218..242, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 219..220, id: Name("x"), ctx: Load, @@ -389,8 +450,10 @@ Module( generators: [ Comprehension { range: 221..241, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("x"), ctx: Store, @@ -398,19 +461,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 230..241, parameters: Some( Parameters { range: 237..238, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 237..238, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 237..238, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 237..238, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -424,6 +494,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 240..241, id: Name("y"), ctx: Load, @@ -441,12 +512,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 257..280, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 257..280, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..259, id: Name("x"), ctx: Load, @@ -455,8 +529,10 @@ Module( generators: [ Comprehension { range: 260..279, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 264..265, id: Name("x"), ctx: Store, @@ -464,6 +540,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 269..273, id: Name("data"), ctx: Load, @@ -472,9 +549,11 @@ Module( ifs: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 277..279, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 278..279, id: Name("y"), ctx: Load, @@ -493,12 +572,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 281..309, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 281..309, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 282..283, id: Name("x"), ctx: Load, @@ -507,8 +589,10 @@ Module( generators: [ Comprehension { range: 284..308, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 288..289, id: Name("x"), ctx: Store, @@ -516,6 +600,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 293..297, id: Name("data"), ctx: Load, @@ -524,10 +609,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 301..308, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 307..308, id: Name("y"), ctx: Load, @@ -546,12 +633,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 310..343, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 310..343, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 311..312, id: Name("x"), ctx: Load, @@ -560,8 +650,10 @@ Module( generators: [ Comprehension { range: 313..342, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 317..318, id: Name("x"), ctx: Store, @@ -569,6 +661,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 322..326, id: Name("data"), ctx: Load, @@ -577,9 +670,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 330..342, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 341..342, id: Name("y"), ctx: Load, @@ -597,12 +692,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 344..376, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 344..376, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 345..346, id: Name("x"), ctx: Load, @@ -611,8 +709,10 @@ Module( generators: [ Comprehension { range: 347..375, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..352, id: Name("x"), ctx: Store, @@ -620,6 +720,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 356..360, id: Name("data"), ctx: Load, @@ -628,19 +729,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 364..375, parameters: Some( Parameters { range: 371..372, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 371..372, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 371..372, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 371..372, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -654,6 +762,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 374..375, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_0.py.snap index 6b082389bac73c..43438af0b7046a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_0.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/missing_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..47, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 46..47, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..47, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_1.py.snap index 3df9492f919094..b1a51ec0e15809 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_1.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/missing_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..136, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 128..136, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 128..136, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 131..136, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("x"), ctx: Load, @@ -29,6 +34,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_2.py.snap index 101fbf0b1ab908..ee7e0553f36dba 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_2.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/missing_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..144, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 134..144, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 134..144, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 1, @@ -26,9 +30,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 139..144, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..144, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_3.py.snap index f0ac8ba0d955f3..dd12017d4f61c5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__missing_closing_curly_brace_3.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/missing_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..144, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..123, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 118..123, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -26,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 2, @@ -39,16 +44,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 125..144, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 129..132, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 132..134, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -59,6 +69,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 140..144, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap index 6df00c1bc5d313..fd02629ffdc7f2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/recover. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..323, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 197..200, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 197..200, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 198..198, id: Name(""), ctx: Invalid, @@ -30,13 +34,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 202..208, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 202..208, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 203..204, value: Int( 1, @@ -45,6 +52,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 206..207, value: Int( 2, @@ -58,13 +66,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 210..215, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 210..215, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 211..212, value: Int( 1, @@ -78,13 +89,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 233..238, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 233..238, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 234..235, value: Int( 1, @@ -93,6 +107,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 236..237, value: Int( 2, @@ -106,15 +121,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 271..277, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 271..277, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 272..273, value: Int( 1, @@ -124,6 +142,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 275..276, value: Int( 2, @@ -138,13 +157,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 300..309, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 300..309, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 301..302, value: Int( 1, @@ -153,9 +175,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 304..307, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 304..305, id: Name("x"), ctx: Load, @@ -164,6 +188,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 307..307, id: Name(""), ctx: Invalid, @@ -178,13 +203,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 311..317, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 311..317, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 312..313, value: Int( 1, @@ -193,6 +221,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 315..316, value: Int( 2, @@ -206,16 +235,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 319..322, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 319..322, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 320..321, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 321..321, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__star_expression_precedence.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__star_expression_precedence.py.snap index 5fd3b81e29e4fc..18fb551839be9a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__star_expression_precedence.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__star_expression_precedence.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/set/star_exp ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..198, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..92, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 83..92, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 85..87, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("x"), ctx: Load, @@ -31,6 +36,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("y"), ctx: Load, @@ -43,19 +49,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..105, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 93..105, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 94..101, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 95..101, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..96, id: Name("x"), ctx: Load, @@ -67,6 +78,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("y"), ctx: Load, @@ -80,6 +92,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 103..104, id: Name("z"), ctx: Load, @@ -92,20 +105,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 106..117, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 106..117, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 107..113, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 108..113, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 112..113, id: Name("x"), ctx: Load, @@ -118,6 +136,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..116, id: Name("z"), ctx: Load, @@ -130,21 +149,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..131, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 118..131, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 119..127, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 120..127, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("x"), ctx: Load, @@ -152,6 +176,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("y"), ctx: Load, @@ -165,6 +190,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("z"), ctx: Load, @@ -177,21 +203,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 132..144, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 132..144, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 133..140, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 134..140, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..135, id: Name("x"), ctx: Load, @@ -199,6 +230,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("y"), ctx: Load, @@ -212,6 +244,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..143, id: Name("z"), ctx: Load, @@ -224,25 +257,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 145..167, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 145..167, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 146..163, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 147..163, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 152..156, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 147..148, id: Name("x"), ctx: Load, @@ -250,6 +289,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 162..163, id: Name("y"), ctx: Load, @@ -262,6 +302,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("z"), ctx: Load, @@ -274,29 +315,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..185, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 168..185, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 169..181, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 170..181, parameters: Some( Parameters { range: 177..178, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 177..178, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 177..178, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 177..178, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -310,6 +361,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 180..181, id: Name("x"), ctx: Load, @@ -322,6 +374,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 183..184, id: Name("z"), ctx: Load, @@ -334,19 +387,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 186..198, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 186..198, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 187..194, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 187..189, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 188..189, id: Name("x"), ctx: Store, @@ -357,6 +415,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 193..194, value: Int( 2, @@ -367,6 +426,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__invalid_slice_element.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__invalid_slice_element.py.snap index 598a1ba08e83a8..133fc59208017f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__invalid_slice_element.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__invalid_slice_element.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/subscript/in ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..133, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 2..9, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2..8, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("x"), ctx: Store, @@ -38,6 +45,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, @@ -58,12 +66,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 33..39, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 33..39, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..34, id: Name("x"), ctx: Load, @@ -71,13 +82,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 35..38, lower: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 35..37, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("x"), ctx: Load, @@ -98,12 +112,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..46, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 40..46, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("x"), ctx: Load, @@ -111,14 +128,17 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 42..45, lower: None, upper: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 43..45, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Load, @@ -138,12 +158,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..54, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 47..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, @@ -151,15 +174,18 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 49..53, lower: None, upper: None, step: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 51..53, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("x"), ctx: Load, @@ -178,12 +204,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..73, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 70..73, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Load, @@ -191,6 +220,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name(""), ctx: Invalid, @@ -203,12 +233,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 123..133, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 123..133, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 123..124, id: Name("x"), ctx: Load, @@ -216,12 +249,15 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 125..132, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 125..127, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("x"), ctx: Store, @@ -232,6 +268,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 131..132, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_0.py.snap index 6491277ca7bcb5..734e357a4ab3db 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_0.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/subscript/un ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -24,14 +28,17 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 2..10, lower: None, upper: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -40,6 +47,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_1.py.snap index 10505cb6cb40cc..0d955770604883 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__subscript__unclosed_slice_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/subscript/un ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..25, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..9, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Load, @@ -24,12 +28,14 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 2..9, lower: None, upper: None, step: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("def"), ctx: Load, @@ -45,12 +51,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 10..25, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 10..15, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..13, id: Name("foo"), ctx: Load, @@ -58,6 +67,7 @@ Module( ), arguments: Arguments { range: 13..15, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -65,6 +75,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..25, id: Name("pass"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary.py.snap index 379c262e15bd5d..f024ef0b37aa48 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/unary.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 0..5, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -30,9 +33,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..10, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__named_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__named_expression.py.snap index feeb3a56e814fc..952700a605f505 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__named_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__named_expression.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/expressions/unary/named_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..18, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..2, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 0..2, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -30,9 +33,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..7, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 1, @@ -43,13 +48,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..13, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 8..13, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("x"), ctx: Load, @@ -61,9 +69,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..18, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..18, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_0.py.snap index 3ea59b57e8614e..90fbf1bd9f05b2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_0.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/unary/no_exp ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 0..3, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -29,12 +33,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..10, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -43,6 +50,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_1.py.snap index 62d13301c9fa86..2b9353fc3b7a7d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unary__no_expression_1.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/unary/no_exp ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..8, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..1, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 0..1, op: UAdd, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..1, id: Name(""), ctx: Invalid, @@ -29,12 +33,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3..8, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 3..8, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, @@ -43,6 +50,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__named_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__named_expression.py.snap index 84e07f14e90b9b..73536890eca5f2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__named_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__named_expression.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/yield/named_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..85, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..59, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 52..59, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("x"), ctx: Load, @@ -30,9 +34,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..64, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 1, @@ -43,17 +49,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..84, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 66..84, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 72..84, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 72..73, value: Int( 1, @@ -62,6 +72,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Load, @@ -69,6 +80,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 80..81, value: Int( 2, @@ -77,6 +89,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__star_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__star_expression.py.snap index 0045d8615f349d..6bd430e889b2c2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__star_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__star_expression.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/yield/star_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..47, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 37..47, value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 44..46, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("x"), ctx: Load, @@ -36,25 +41,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..66, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 49..66, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 55..66, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 55..63, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 56..63, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -62,6 +73,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("y"), ctx: Load, @@ -75,6 +87,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__starred_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__starred_expression.py.snap index 4adf70d811ce39..72433f9e7736f5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__starred_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__starred_expression.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/yield_from/s ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..100, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..83, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 70..83, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 81..83, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("x"), ctx: Load, @@ -34,19 +39,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..100, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 84..100, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 95..100, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 95..97, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("x"), ctx: Load, @@ -57,6 +67,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__unparenthesized.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__unparenthesized.py.snap index 40ffb45e660798..a14264ccfbb3b5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__unparenthesized.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield_from__unparenthesized.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/expressions/yield_from/u ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..192, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..47, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 35..47, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("x"), ctx: Load, @@ -28,9 +32,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 51..52, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, @@ -41,16 +47,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 89..104, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 89..104, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 100..104, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Load, @@ -58,6 +68,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 103..104, id: Name("y"), ctx: Load, @@ -74,16 +85,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..192, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 168..192, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 179..192, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 180..181, id: Name("x"), ctx: Load, @@ -91,14 +106,17 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 183..191, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 184..191, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..185, id: Name("x"), ctx: Load, @@ -106,6 +124,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 190..191, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap index c8b4ec3c122939..42acb3033d9a75 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_empty_expression.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_empty_expres ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..14, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..5, value: FStringValue { inner: Single( FString( FString { range: 0..5, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..4, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..13, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 6..13, value: FStringValue { inner: Single( FString( FString { range: 6..13, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 8..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..9, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap index 36b66627ad3995..0858ce958d9d15 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_name_tok.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_conv ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..9, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..8, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..8, value: FStringValue { inner: Single( FString( FString { range: 0..8, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..7, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap index 0ee7e5fe73f6d1..165a659e99d4d6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_conversion_flag_other_tok.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_conv ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..22, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..10, value: FStringValue { inner: Single( FString( FString { range: 0..10, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..9, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..21, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 11..21, value: FStringValue { inner: Single( FString( FString { range: 11..21, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 13..20, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap index f47c692d58e6ae..acdb317e91fe5a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_invalid_starred_expr.py.snap @@ -7,28 +7,35 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_invalid_star ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..112, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 77..83, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 77..83, value: FStringValue { inner: Single( FString( FString { range: 77..83, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 79..82, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 80..81, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..81, id: Name(""), ctx: Invalid, @@ -58,29 +65,36 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..97, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 84..97, value: FStringValue { inner: Single( FString( FString { range: 84..97, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 86..96, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 87..95, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 88..95, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("x"), ctx: Load, @@ -88,6 +102,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..95, id: Name("y"), ctx: Load, @@ -120,28 +135,35 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 98..111, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 98..111, value: FStringValue { inner: Single( FString( FString { range: 98..111, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 100..110, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 101..109, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 102..109, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap index 121ca20d3c71c4..6f98814ce98316 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_lambda_without_parentheses.py.snap @@ -7,38 +7,50 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_lambda_witho ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..17, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..16, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..16, value: FStringValue { inner: Single( FString( FString { range: 0..16, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..12, + node_index: AtomicNodeIndex(..), expression: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 3..12, parameters: Some( Parameters { range: 10..11, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 10..11, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 10..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 10..11, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -52,6 +64,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..12, id: Name(""), ctx: Invalid, @@ -67,6 +80,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 12..14, + node_index: AtomicNodeIndex(..), value: " x", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap index 9fd2f1b9b238bb..62c1efddfb7134 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_unclosed_lbr ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..4, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..4, value: FStringValue { inner: Single( FString( FString { range: 0..4, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2..3, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..3, id: Name(""), ctx: Invalid, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..14, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 5..14, value: FStringValue { inner: Single( FString( FString { range: 5..14, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 7..14, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..11, id: Name("foo"), ctx: Load, @@ -93,21 +104,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..23, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 15..23, value: FStringValue { inner: Single( FString( FString { range: 15..23, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 17..22, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..21, id: Name("foo"), ctx: Load, @@ -139,9 +155,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..37, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 24..37, value: FStringValue { inner: Concatenated( @@ -149,12 +167,15 @@ Module( FString( FString { range: 24..28, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 26..27, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..27, id: Name(""), ctx: Invalid, @@ -176,12 +197,15 @@ Module( FString( FString { range: 29..37, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 33..34, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..34, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap index 81f2c236a9bde1..9d97896c805f1f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_unclosed_lbrace_in_format_spec.py.snap @@ -7,31 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/err/f_string_unclosed_lbr ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..29, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..12, value: FStringValue { inner: Single( FString( FString { range: 0..12, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..8, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 8..11, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -42,6 +49,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 11..11, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -63,27 +71,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..28, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 13..28, value: FStringValue { inner: Single( FString( FString { range: 13..28, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 15..21, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 21..27, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -94,10 +108,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 24..27, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 24..27, + node_index: AtomicNodeIndex(..), value: ".3f", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_iter_unpack_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_iter_unpack_py38.py.snap index f4a68c5000ba0f..cf2cb1c90be85e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_iter_unpack_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_iter_unpack_py38.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/for_iter_unpack_py38. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..106, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 43..63, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -22,13 +25,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 52..58, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 52..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("a"), ctx: Load, @@ -39,6 +45,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("b"), ctx: Load, @@ -52,9 +59,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..63, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 60..63, }, ), @@ -66,10 +75,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 64..84, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Store, @@ -77,10 +88,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 74..79, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("a"), ctx: Load, @@ -88,9 +101,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 77..79, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("b"), ctx: Load, @@ -107,9 +122,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), @@ -121,10 +138,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 85..105, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("x"), ctx: Store, @@ -132,13 +151,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 94..100, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 94..96, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..96, id: Name("a"), ctx: Load, @@ -149,9 +171,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 98..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("b"), ctx: Load, @@ -168,9 +192,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 102..105, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 102..105, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_iter_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_iter_expr.py.snap index f83966eb9282a7..2520538fcc2f7b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_iter_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_iter_expr.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_iter ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..71, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..22, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Store, @@ -22,14 +25,17 @@ Module( ), iter: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 9..17, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 10..17, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("a"), ctx: Load, @@ -37,6 +43,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("b"), ctx: Load, @@ -51,9 +58,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -65,10 +74,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 23..44, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Store, @@ -76,10 +87,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 32..39, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("a"), ctx: Load, @@ -91,9 +104,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 41..44, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 41..44, }, ), @@ -105,10 +120,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 45..60, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..55, id: Name("target"), ctx: Store, @@ -116,6 +133,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("x"), ctx: Load, @@ -127,9 +145,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 64..70, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..65, value: Int( 1, @@ -138,6 +158,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 67..70, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target.py.snap index b34450098f0c17..491fd41a42e143 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_targ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..154, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..15, is_async: false, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 1, @@ -23,6 +26,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -31,9 +35,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..15, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 12..15, }, ), @@ -45,15 +51,18 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 16..33, is_async: false, target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, value: StringLiteralValue { inner: Single( StringLiteral { range: 20..23, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -67,6 +76,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Load, @@ -75,9 +85,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..33, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 30..33, }, ), @@ -89,18 +101,22 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 34..56, is_async: false, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 38..46, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 39..46, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Load, @@ -108,6 +124,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("y"), ctx: Load, @@ -121,6 +138,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..51, id: Name("z"), ctx: Load, @@ -129,9 +147,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..56, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 53..56, }, ), @@ -143,16 +163,20 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 57..77, is_async: false, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 61..67, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 62..67, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -161,6 +185,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("y"), ctx: Load, @@ -173,6 +198,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("z"), ctx: Load, @@ -181,9 +207,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 74..77, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 74..77, }, ), @@ -195,13 +223,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 78..99, is_async: false, target: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 82..89, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("x"), ctx: Load, @@ -211,6 +242,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("z"), ctx: Load, @@ -219,9 +251,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), @@ -233,17 +267,21 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 100..121, is_async: false, target: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 104..116, value: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 110..116, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 110..111, id: Name("x"), ctx: Load, @@ -255,6 +293,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..116, id: Name("y"), ctx: Load, @@ -268,6 +307,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..116, id: Name(""), ctx: Invalid, @@ -276,9 +316,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..121, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 118..121, }, ), @@ -290,14 +332,17 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 122..153, is_async: false, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 126..143, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("x"), ctx: Store, @@ -305,6 +350,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 130..131, value: Int( 1, @@ -313,6 +359,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..134, id: Name("y"), ctx: Store, @@ -320,18 +367,22 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 136..142, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 137..142, elts: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 138..141, value: StringLiteralValue { inner: Single( StringLiteral { range: 138..141, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -356,6 +407,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 147..148, id: Name("z"), ctx: Load, @@ -364,9 +416,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 150..153, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 150..153, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_binary_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_binary_expr.py.snap index 0a6052ee9de521..cb545e9b38b359 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_binary_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_binary_expr.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_target_binary_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..124, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..24, is_async: false, target: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 4..14, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -30,6 +33,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("y"), ctx: Load, @@ -40,6 +44,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("z"), ctx: Load, @@ -48,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..24, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, }, ), @@ -62,13 +69,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 25..45, is_async: false, target: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 29..35, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -80,6 +90,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("y"), ctx: Load, @@ -90,6 +101,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("z"), ctx: Load, @@ -98,9 +110,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 42..45, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 42..45, }, ), @@ -112,15 +126,18 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 46..66, is_async: false, target: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 50..56, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..51, id: Name("x"), ctx: Load, @@ -128,6 +145,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("y"), ctx: Load, @@ -138,6 +156,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("z"), ctx: Load, @@ -146,9 +165,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..66, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 63..66, }, ), @@ -160,14 +181,17 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 67..83, is_async: false, target: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 71..73, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("x"), ctx: Store, @@ -177,6 +201,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("y"), ctx: Load, @@ -185,9 +210,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 80..83, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 80..83, }, ), @@ -199,14 +226,17 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 84..103, is_async: false, target: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 88..93, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("x"), ctx: Store, @@ -216,6 +246,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("y"), ctx: Load, @@ -224,9 +255,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 100..103, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 100..103, }, ), @@ -238,13 +271,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 104..123, is_async: false, target: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 108..113, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("x"), ctx: Load, @@ -253,6 +289,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 112..113, id: Name("y"), ctx: Load, @@ -262,6 +299,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..118, id: Name("z"), ctx: Load, @@ -270,9 +308,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 120..123, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 120..123, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_in_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_in_keyword.py.snap index 6e47652cc1177d..185ab4b7a74260 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_in_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target_in_keyword.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_target_in_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..170, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 4..13, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("d"), ctx: Load, @@ -26,12 +29,15 @@ Module( ), arguments: Arguments { range: 5..13, + node_index: AtomicNodeIndex(..), args: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 6..12, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -43,6 +49,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("y"), ctx: Load, @@ -58,6 +65,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..23, id: Name("target"), ctx: Load, @@ -66,9 +74,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -80,16 +90,20 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 29..56, is_async: false, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 33..43, func: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 34..40, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Load, @@ -101,6 +115,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Load, @@ -111,6 +126,7 @@ Module( ), arguments: Arguments { range: 41..43, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -118,6 +134,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..51, id: Name("iter"), ctx: Load, @@ -126,9 +143,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..56, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 53..56, }, ), @@ -140,13 +159,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 57..82, is_async: false, target: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 62..68, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -158,6 +180,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("y"), ctx: Load, @@ -168,6 +191,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..77, id: Name("iter"), ctx: Load, @@ -176,9 +200,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 79..82, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 79..82, }, ), @@ -190,17 +216,21 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 83..111, is_async: false, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 87..98, elts: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 88..94, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("x"), ctx: Load, @@ -212,6 +242,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("y"), ctx: Load, @@ -222,6 +253,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("z"), ctx: Store, @@ -234,6 +266,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..106, id: Name("iter"), ctx: Load, @@ -242,9 +275,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 108..111, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 108..111, }, ), @@ -256,17 +291,21 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 112..140, is_async: false, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 116..127, elts: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 117..123, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..118, id: Name("x"), ctx: Load, @@ -278,6 +317,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("y"), ctx: Load, @@ -288,6 +328,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("z"), ctx: Store, @@ -299,6 +340,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..135, id: Name("iter"), ctx: Load, @@ -307,9 +349,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 137..140, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 137..140, }, ), @@ -321,17 +365,21 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 141..169, is_async: false, target: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 145..156, elts: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 146..152, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Load, @@ -343,6 +391,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("y"), ctx: Load, @@ -353,6 +402,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 154..155, id: Name("z"), ctx: Load, @@ -363,6 +413,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..164, id: Name("iter"), ctx: Load, @@ -371,9 +422,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 166..169, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 166..169, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_in_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_in_keyword.py.snap index 3390046c352a1b..d16e1ec4bc4f32 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_in_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_in_keyword.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_missing_in_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..12, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("a"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("b"), ctx: Load, @@ -31,9 +34,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..12, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 9..12, }, ), @@ -45,10 +50,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 13..23, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("a"), ctx: Store, @@ -56,6 +63,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..18, id: Name(""), ctx: Invalid, @@ -64,9 +72,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_iter.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_iter.py.snap index 240f900f06f95f..d2583423090781 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_iter.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_iter.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_missing_iter.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..20, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..19, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..8, id: Name(""), ctx: Invalid, @@ -31,10 +34,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 14..19, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("a"), ctx: Store, @@ -43,6 +48,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_target.py.snap index 3c819fc4722e6f..42ea10ed9f8128 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_missing_target.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_missing_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..14, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..13, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..6, id: Name("in"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -31,9 +34,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..13, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 10..13, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_dotted_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_dotted_names.py.snap index 36a0203b9b6f3a..2c98db482e7a71 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_dotted_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_dotted_names.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_dotted_names.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..16, module: Some( Identifier { id: Name("x"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 14..15, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -34,27 +38,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 17..34, module: Some( Identifier { id: Name("x"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 31..32, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 31..32, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 33..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 33..34, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -64,67 +74,83 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 35..66, module: Some( Identifier { id: Name("x"), range: 40..41, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 49..50, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 49..50, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 52..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 52..53, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 54..55, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 54..55, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 57..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 57..58, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 60..61, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 60..61, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 62..63, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 62..63, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 65..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("g"), range: 65..66, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_empty_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_empty_names.py.snap index fcc56371ed6d1e..b84eb9a41acc9c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_empty_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_empty_names.py.snap @@ -1,22 +1,24 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_empty_names.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..48, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..13, module: Some( Identifier { id: Name("x"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [], @@ -25,11 +27,13 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 14..30, module: Some( Identifier { id: Name("x"), range: 19..20, + node_index: AtomicNodeIndex(..), }, ), names: [], @@ -38,11 +42,13 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 31..47, module: Some( Identifier { id: Name("x"), range: 36..37, + node_index: AtomicNodeIndex(..), }, ), names: [], diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_module.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_module.py.snap index fcf455d6ba23de..9e3cbe49948920 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_module.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_module.py.snap @@ -1,17 +1,18 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_missing_module.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..19, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..4, module: None, names: [], @@ -20,14 +21,17 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 5..18, module: None, names: [ Alias { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 17..18, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_rpar.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_rpar.py.snap index 582bd2f14ec70a..761cbc2a7ec693 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_rpar.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_missing_rpar.py.snap @@ -1,38 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_missing_rpar.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..19, module: Some( Identifier { id: Name("x"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 15..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 15..16, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 18..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 18..19, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -42,12 +48,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..25, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 20..25, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 1, @@ -57,6 +66,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -69,27 +79,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 26..46, module: Some( Identifier { id: Name("x"), range: 31..32, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 41..42, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 41..42, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 44..45, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 44..45, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -99,12 +115,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..52, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 47..52, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 47..48, value: Int( 2, @@ -114,6 +133,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_star_with_other_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_star_with_other_names.py.snap index 93824bef84498f..0c0d1ff705ec87 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_star_with_other_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_star_with_other_names.py.snap @@ -1,38 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_star_with_other_names.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..87, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..18, module: Some( Identifier { id: Name("x"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 14..15, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 17..18, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -42,35 +48,43 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 19..40, module: Some( Identifier { id: Name("x"), range: 24..25, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 33..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 33..34, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 36..37, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 36..37, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 39..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 39..40, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -80,32 +94,39 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 41..64, module: Some( Identifier { id: Name("x"), range: 46..47, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 55..56, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 55..56, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 58..64, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 58..59, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("b"), range: 63..64, + node_index: AtomicNodeIndex(..), }, ), }, @@ -115,35 +136,43 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 65..86, module: Some( Identifier { id: Name("x"), range: 70..71, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 79..80, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 79..80, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 82..83, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 82..83, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 85..86, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 85..86, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_unparenthesized_trailing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_unparenthesized_trailing_comma.py.snap index 6dbfd32f09872e..4708a3d670bb1a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_unparenthesized_trailing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@from_import_unparenthesized_trailing_comma.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/from_import_unparenthesized_trailing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..59, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..16, module: Some( Identifier { id: Name("a"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 14..15, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -34,24 +38,29 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 17..38, module: Some( Identifier { id: Name("a"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 31..37, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 31..32, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("c"), range: 36..37, + node_index: AtomicNodeIndex(..), }, ), }, @@ -61,27 +70,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 39..58, module: Some( Identifier { id: Name("a"), range: 44..45, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 53..54, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 53..54, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 56..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 56..57, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_empty_body.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_empty_body.py.snap index 1fa49e573cda7e..2b793eb3a0723a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_empty_body.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_empty_body.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/function_def_empty_body.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..10, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -34,16 +39,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 11..28, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 15..18, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 18..20, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -53,6 +63,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..27, id: Name("int"), ctx: Load, @@ -64,10 +75,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 29..35, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Store, @@ -76,6 +89,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 33..35, value: Int( 42, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap index f1968dedf92e51..630b9274b75a89 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_invalid_return_expr.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/function_def_invalid_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..74, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..22, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -30,9 +36,11 @@ Module( returns: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -45,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -58,16 +68,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..47, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 27..30, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 30..32, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -77,9 +92,11 @@ Module( returns: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 37..41, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..41, id: Name("int"), ctx: Load, @@ -92,9 +109,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..47, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 44..47, }, ), @@ -105,16 +124,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 48..73, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 52..55, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 55..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -124,10 +148,12 @@ Module( returns: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 61..68, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("x"), ctx: Load, @@ -140,9 +166,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..73, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 70..73, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_identifier.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_identifier.py.snap index 12a4a4625a3eae..6b3b63253fdbd8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_identifier.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_identifier.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/function_def_missing_identifier.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..11, is_async: false, decorator_list: [], name: Identifier { id: Name(""), range: 3..3, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 4..6, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..11, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 8..11, }, ), @@ -45,16 +52,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 12..30, is_async: false, decorator_list: [], name: Identifier { id: Name(""), range: 15..15, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 16..18, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -64,6 +76,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..25, id: Name("int"), ctx: Load, @@ -73,9 +86,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..30, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 27..30, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_return_type.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_return_type.py.snap index 3552186e7a2198..e8b2fbeeac6bda 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_return_type.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_missing_return_type.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/function_def_missing_return_type.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..19, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..18, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..18, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 15..18, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_parameter_list.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_parameter_list.py.snap index bc3b151c5e388d..f3b024f7e5ad85 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_parameter_list.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_parameter_list.py.snap @@ -7,33 +7,43 @@ input_file: crates/ruff_python_parser/resources/inline/err/function_def_unclosed ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..74, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..18, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..18, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..14, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..14, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..14, id: Name("int"), ctx: Load, @@ -45,11 +55,14 @@ Module( }, ParameterWithDefault { range: 16..18, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 16..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 16..17, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -66,16 +79,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 19..43, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 23..26, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 26..28, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -86,10 +104,12 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 34..43, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..43, value: Int( 42, @@ -104,29 +124,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..74, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 51..74, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 52..58, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 52..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 52..53, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..58, id: Name("int"), ctx: Load, @@ -138,15 +167,19 @@ Module( }, ParameterWithDefault { range: 60..66, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 60..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 60..61, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..66, id: Name("str"), ctx: Load, @@ -158,17 +191,21 @@ Module( }, ParameterWithDefault { range: 67..73, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 67..68, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 67..68, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..73, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_type_param_list.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_type_param_list.py.snap index 8cc11d43fc00c0..f8ef4f81691b6a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_type_param_list.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unclosed_type_param_list.py.snap @@ -1,34 +1,39 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/function_def_unclosed_type_param_list.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..39, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 7..15, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 8..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T1"), range: 8..10, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -37,9 +42,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 12..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 13..15, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -49,15 +56,21 @@ Module( ), parameters: Parameters { range: 15..21, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 16..17, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 16..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 16..17, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -65,11 +78,14 @@ Module( }, ParameterWithDefault { range: 19..20, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 19..20, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 19..20, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -84,13 +100,16 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 27..39, value: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 34..39, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("a"), ctx: Load, @@ -99,6 +118,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("b"), ctx: Load, @@ -114,10 +134,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 40..46, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("x"), ctx: Store, @@ -126,6 +148,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..46, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unparenthesized_return_types.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unparenthesized_return_types.py.snap index ef7baf9f917c73..a2e19de94c776a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unparenthesized_return_types.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_def_unparenthesized_return_types.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/function_def_unparenthesized_return_types.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..50, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..22, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,10 +36,12 @@ Module( returns: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 13..17, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..16, id: Name("int"), ctx: Load, @@ -49,9 +56,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -62,16 +71,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..49, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 27..30, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 30..32, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -81,10 +95,12 @@ Module( returns: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 36..44, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..39, id: Name("int"), ctx: Load, @@ -92,6 +108,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..44, id: Name("str"), ctx: Load, @@ -106,9 +123,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..49, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 46..49, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap index 80edd73d05ae26..18039d0ce0948b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap @@ -7,27 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/err/function_type_params_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..79, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..61, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 51..54, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 52..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 52..53, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -38,6 +44,9 @@ Module( ), parameters: Parameters { range: 54..56, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -48,9 +57,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -61,21 +72,27 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 62..78, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 66..69, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 69..71, + node_index: AtomicNodeIndex(..), type_params: [], }, ), parameters: Parameters { range: 71..73, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -86,9 +103,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..78, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 75..78, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_empty.py.snap index 6a89da1d796232..6255c8fe595fc1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_empty.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_empty.py.snap @@ -1,17 +1,18 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/global_stmt_empty.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..7, body: [ Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 0..6, names: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_expression.py.snap index b2305e0df74145..d179c96eef2bb5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_expression.py.snap @@ -1,35 +1,40 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/global_stmt_expression.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..13, body: [ Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 0..8, names: [ Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, ], }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..12, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 9..12, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_trailing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_trailing_comma.py.snap index b6c872e3996ceb..232acca04ddcc1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_trailing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@global_stmt_trailing_comma.py.snap @@ -1,43 +1,49 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/global_stmt_trailing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..32, body: [ Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 0..8, names: [], }, ), Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 9..18, names: [ Identifier { id: Name("x"), range: 16..17, + node_index: AtomicNodeIndex(..), }, ], }, ), Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 19..31, names: [ Identifier { id: Name("x"), range: 26..27, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("y"), range: 29..30, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_elif_missing_colon.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_elif_missing_colon.py.snap index d891db30aeb0e4..888e539c24195f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_elif_missing_colon.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_elif_missing_colon.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_elif_missing_colon.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..46, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..45, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, @@ -23,6 +25,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 10..14, }, ), @@ -30,9 +33,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 15..30, + node_index: AtomicNodeIndex(..), test: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("y"), ctx: Load, @@ -42,6 +47,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 26..30, }, ), @@ -49,10 +55,12 @@ Module( }, ElifElseClause { range: 31..45, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 41..45, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_empty_body.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_empty_body.py.snap index 9b30c83a9f2bf4..0b8fecc04863ba 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_empty_body.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_empty_body.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_empty_body.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..15, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..8, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -25,12 +27,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..14, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 9..14, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, @@ -40,6 +45,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_elif_test_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_elif_test_expr.py.snap index dbc135744a7664..ee46056de58360 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_elif_test_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_elif_test_expr.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_invalid_elif_test_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..56, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..55, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, @@ -23,6 +25,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 10..14, }, ), @@ -30,12 +33,15 @@ Module( elif_else_clauses: [ ElifElseClause { range: 15..32, + node_index: AtomicNodeIndex(..), test: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 20..22, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("x"), ctx: Load, @@ -48,6 +54,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 28..32, }, ), @@ -55,13 +62,16 @@ Module( }, ElifElseClause { range: 33..55, + node_index: AtomicNodeIndex(..), test: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 38..45, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Load, @@ -74,6 +84,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 51..55, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_test_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_test_expr.py.snap index 558aa9bdbc2fa3..0562b031f9beb3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_test_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_invalid_test_expr.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_invalid_test_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..48, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..10, test: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3..5, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -29,9 +32,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..10, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 7..10, }, ), @@ -43,13 +48,16 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 11..26, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 14..21, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("x"), ctx: Load, @@ -61,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..26, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 23..26, }, ), @@ -75,12 +85,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 27..47, test: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 30..42, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Load, @@ -91,9 +104,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..47, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 44..47, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_colon.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_colon.py.snap index b97e20e9bcfe91..091fe218f51a64 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_colon.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_colon.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_missing_colon.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..25, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..4, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("x"), ctx: Load, @@ -26,9 +28,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 5..18, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("x"), ctx: Load, @@ -37,6 +41,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 14..18, }, ), @@ -46,10 +51,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 19..24, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("a"), ctx: Store, @@ -58,6 +65,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_test.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_test.py.snap index bc2a35ae1d0a19..13cacd4b1073d0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_test.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_missing_test.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_missing_test.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..9, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..8, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..2, id: Name(""), ctx: Invalid, @@ -23,9 +25,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..8, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5..8, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_misspelled_elif.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_misspelled_elif.py.snap index b8d421961c8f61..bef4a1b8523e6a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_misspelled_elif.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@if_stmt_misspelled_elif.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/if_stmt_misspelled_el ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..17, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -21,6 +24,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 13..17, }, ), @@ -30,9 +34,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 18..22, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..21, id: Name("elf"), ctx: Store, @@ -40,6 +46,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..22, id: Name(""), ctx: Invalid, @@ -51,11 +58,13 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 27..31, }, ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 42..46, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap index 5488502486f4c6..887ae672205679 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string.py.snap @@ -7,18 +7,22 @@ input_file: crates/ruff_python_parser/resources/inline/err/implicitly_concatenat ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..7, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..7, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Single, @@ -34,12 +38,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..20, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 15..20, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 15..16, value: Int( 1, @@ -49,6 +56,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 1, @@ -61,9 +69,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..40, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 21..40, value: FStringValue { inner: Concatenated( @@ -71,6 +81,7 @@ Module( Literal( StringLiteral { range: 21..28, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Single, @@ -82,18 +93,22 @@ Module( FString( FString { range: 29..40, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 31..37, + node_index: AtomicNodeIndex(..), value: "world ", }, ), Interpolation( InterpolatedElement { range: 37..40, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("x"), ctx: Load, @@ -121,12 +136,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 41..46, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 41..46, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..42, value: Int( 2, @@ -136,6 +154,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap index 672216d159feb3..da094ee09c49b3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@implicitly_concatenated_unterminated_string_multiline.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/implicitly_concatenat ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..85, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..31, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 6..31, value: FStringValue { inner: Concatenated( @@ -21,6 +24,7 @@ Module( Literal( StringLiteral { range: 6..13, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Single, @@ -32,18 +36,22 @@ Module( FString( FString { range: 18..31, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 20..26, + node_index: AtomicNodeIndex(..), value: "world ", }, ), Interpolation( InterpolatedElement { range: 26..29, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Load, @@ -71,12 +79,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..37, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 32..37, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, @@ -86,6 +97,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..37, value: Int( 1, @@ -98,14 +110,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..51, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 44..51, value: StringLiteralValue { inner: Single( StringLiteral { range: 44..51, + node_index: AtomicNodeIndex(..), value: "first", flags: StringLiteralFlags { quote_style: Single, @@ -121,19 +136,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..76, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 68..76, value: FStringValue { inner: Single( FString( FString { range: 68..76, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 70..75, + node_index: AtomicNodeIndex(..), value: "third", }, ), @@ -153,12 +172,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 79..84, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 79..84, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 2, @@ -168,6 +190,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_alias_missing_asname.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_alias_missing_asname.py.snap index 230ad97b22181e..23ff8ca3c45b8d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_alias_missing_asname.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_alias_missing_asname.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/import_alias_missing_asname.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..12, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..11, names: [ Alias { range: 7..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_empty.py.snap index ad8888e518cedc..b198a714774b85 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_empty.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_empty.py.snap @@ -1,17 +1,18 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/import_stmt_empty.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..7, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..6, names: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_parenthesized_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_parenthesized_names.py.snap index a9509f3d16f567..dedb2fd84b90dc 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_parenthesized_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_parenthesized_names.py.snap @@ -1,26 +1,29 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/import_stmt_parenthesized_names.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..25, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..6, names: [], }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("a"), ctx: Load, @@ -30,19 +33,23 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 11..17, names: [], }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..24, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 18..24, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("a"), ctx: Load, @@ -50,6 +57,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_star_import.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_star_import.py.snap index 70f4c3c14095bd..81439cdee22290 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_star_import.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_star_import.py.snap @@ -1,29 +1,33 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/import_stmt_star_import.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..6, names: [], }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..8, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 7..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..8, id: Name(""), ctx: Invalid, @@ -36,13 +40,16 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 9..18, names: [ Alias { range: 16..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 16..17, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -51,16 +58,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..23, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 19..23, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 19..20, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..20, id: Name(""), ctx: Invalid, @@ -71,6 +82,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_trailing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_trailing_comma.py.snap index f5ab95692d9c10..d9ea1890a59a8a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_trailing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@import_stmt_trailing_comma.py.snap @@ -1,38 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/import_stmt_trailing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..22, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..8, names: [], }, ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 9..21, names: [ Alias { range: 16..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 16..17, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 19..20, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 19..20, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_class.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_class.py.snap index a53ebf2fd25761..815e9022559ca6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_class.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_class.py.snap @@ -7,26 +7,32 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_annotation_cl ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..247, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..26, decorator_list: [], name: Identifier { id: Name("F"), range: 6..7, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 7..10, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 8..9, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -38,12 +44,15 @@ Module( arguments: Some( Arguments { range: 10..21, + node_index: AtomicNodeIndex(..), args: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 11..20, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("y"), ctx: Store, @@ -51,6 +60,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..20, id: Name("list"), ctx: Load, @@ -65,9 +75,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..26, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 23..26, }, ), @@ -78,22 +90,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 27..53, decorator_list: [], name: Identifier { id: Name("I"), range: 33..34, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 34..37, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 35..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 35..36, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -105,13 +122,16 @@ Module( arguments: Some( Arguments { range: 37..48, + node_index: AtomicNodeIndex(..), args: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 39..46, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 1, @@ -128,9 +148,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..53, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 50..53, }, ), @@ -141,22 +163,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 54..85, decorator_list: [], name: Identifier { id: Name("J"), range: 60..61, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 61..64, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 62..63, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 62..63, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -168,12 +195,15 @@ Module( arguments: Some( Arguments { range: 64..80, + node_index: AtomicNodeIndex(..), args: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 66..78, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 77..78, value: Int( 1, @@ -189,9 +219,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..85, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 82..85, }, ), @@ -202,30 +234,37 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 86..112, decorator_list: [], name: Identifier { id: Name("K"), range: 92..93, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 93..107, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 94..106, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 94..95, + node_index: AtomicNodeIndex(..), }, bound: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 98..105, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..105, value: Int( 1, @@ -246,9 +285,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..112, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, }, ), @@ -259,29 +300,36 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 137..162, decorator_list: [], name: Identifier { id: Name("L"), range: 143..144, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 144..157, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 145..156, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 145..146, + node_index: AtomicNodeIndex(..), }, bound: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 149..155, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 149..150, id: Name("x"), ctx: Store, @@ -289,6 +337,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 154..155, value: Int( 1, @@ -308,9 +357,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 159..162, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 159..162, }, ), @@ -321,22 +372,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 193..219, decorator_list: [], name: Identifier { id: Name("M"), range: 199..200, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 200..203, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 201..202, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 201..202, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -348,12 +404,15 @@ Module( arguments: Some( Arguments { range: 203..214, + node_index: AtomicNodeIndex(..), args: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 205..212, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 211..212, value: Int( 1, @@ -369,9 +428,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 216..219, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 216..219, }, ), @@ -382,29 +443,36 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 220..246, decorator_list: [], name: Identifier { id: Name("N"), range: 226..227, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 227..241, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 228..240, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 228..229, + node_index: AtomicNodeIndex(..), }, bound: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 232..239, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 238..239, value: Int( 1, @@ -424,9 +492,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 243..246, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 243..246, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap index 66d0def077a544..2953a62e5533cf 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function.py.snap @@ -7,27 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_annotation_fu ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..987, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, decorator_list: [], name: Identifier { id: Name("d"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 5..8, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 6..7, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 6..7, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -38,6 +44,9 @@ Module( ), parameters: Parameters { range: 8..10, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -47,9 +56,11 @@ Module( returns: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 15..22, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 1, @@ -62,9 +73,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -75,23 +88,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 29..58, is_async: false, decorator_list: [], name: Identifier { id: Name("e"), range: 33..34, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 34..37, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 35..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 35..36, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -102,22 +120,30 @@ Module( ), parameters: Parameters { range: 37..53, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 38..52, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 38..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 38..41, + node_index: AtomicNodeIndex(..), }, annotation: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 44..51, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 50..51, value: Int( 1, @@ -139,9 +165,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..58, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 55..58, }, ), @@ -152,23 +180,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 59..86, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 63..64, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 64..67, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 65..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 65..66, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -179,6 +212,9 @@ Module( ), parameters: Parameters { range: 67..69, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -188,9 +224,11 @@ Module( returns: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 74..80, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("y"), ctx: Store, @@ -198,6 +236,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 3, @@ -210,9 +249,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..86, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 83..86, }, ), @@ -223,23 +264,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 87..115, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 91..92, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 92..95, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 93..94, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 93..94, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -250,22 +296,30 @@ Module( ), parameters: Parameters { range: 95..110, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 96..109, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 96..109, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 96..99, + node_index: AtomicNodeIndex(..), }, annotation: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 102..108, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("x"), ctx: Store, @@ -273,6 +327,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 107..108, value: Int( 1, @@ -294,9 +349,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 112..115, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 112..115, }, ), @@ -307,23 +364,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 116..143, is_async: false, decorator_list: [], name: Identifier { id: Name("h"), range: 120..121, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 121..124, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 122..123, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 122..123, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -334,23 +396,31 @@ Module( ), parameters: Parameters { range: 124..138, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 125..137, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 125..137, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 125..126, + node_index: AtomicNodeIndex(..), }, annotation: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 129..136, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 1, @@ -373,9 +443,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 140..143, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 140..143, }, ), @@ -386,23 +458,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 144..172, is_async: false, decorator_list: [], name: Identifier { id: Name("j"), range: 148..149, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 149..152, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 150..151, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 150..151, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -413,6 +490,9 @@ Module( ), parameters: Parameters { range: 152..154, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -422,10 +502,12 @@ Module( returns: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 159..166, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 165..166, value: Int( 1, @@ -439,9 +521,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 169..172, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 169..172, }, ), @@ -452,23 +536,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 173..205, is_async: false, decorator_list: [], name: Identifier { id: Name("l"), range: 177..178, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 178..181, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 179..180, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 179..180, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -479,22 +568,30 @@ Module( ), parameters: Parameters { range: 181..200, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 182..199, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 182..199, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 182..183, + node_index: AtomicNodeIndex(..), }, annotation: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 186..198, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..198, value: Int( 1, @@ -516,9 +613,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 202..205, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 202..205, }, ), @@ -529,23 +628,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 206..239, is_async: false, decorator_list: [], name: Identifier { id: Name("n"), range: 210..211, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 211..214, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 212..213, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 212..213, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -556,6 +660,9 @@ Module( ), parameters: Parameters { range: 214..216, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -565,9 +672,11 @@ Module( returns: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 221..233, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 232..233, value: Int( 1, @@ -580,9 +689,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 236..239, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 236..239, }, ), @@ -593,31 +704,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 240..266, is_async: false, decorator_list: [], name: Identifier { id: Name("p"), range: 244..245, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 245..259, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 246..258, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 246..247, + node_index: AtomicNodeIndex(..), }, bound: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 250..257, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 256..257, value: Int( 1, @@ -636,6 +754,9 @@ Module( ), parameters: Parameters { range: 259..261, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -646,9 +767,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 263..266, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 263..266, }, ), @@ -659,32 +782,39 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 297..324, is_async: false, decorator_list: [], name: Identifier { id: Name("q"), range: 301..302, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 302..317, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 303..316, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 303..304, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 308..315, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 314..315, value: Int( 1, @@ -702,6 +832,9 @@ Module( ), parameters: Parameters { range: 317..319, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -712,9 +845,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 321..324, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 321..324, }, ), @@ -725,31 +860,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 356..385, is_async: false, decorator_list: [], name: Identifier { id: Name("r"), range: 360..361, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 361..378, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 362..377, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 363..365, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 369..376, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 375..376, value: Int( 1, @@ -767,6 +909,9 @@ Module( ), parameters: Parameters { range: 378..380, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -777,9 +922,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 382..385, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 382..385, }, ), @@ -790,31 +937,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 420..450, is_async: false, decorator_list: [], name: Identifier { id: Name("s"), range: 424..425, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 425..443, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 426..442, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 428..430, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 434..441, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 440..441, value: Int( 1, @@ -832,6 +986,9 @@ Module( ), parameters: Parameters { range: 443..445, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -842,9 +999,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 447..450, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 447..450, }, ), @@ -855,30 +1014,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 481..506, is_async: false, decorator_list: [], name: Identifier { id: Name("t"), range: 485..486, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 486..499, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 487..498, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 487..488, + node_index: AtomicNodeIndex(..), }, bound: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 491..497, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 491..492, id: Name("x"), ctx: Store, @@ -886,6 +1052,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 496..497, value: Int( 1, @@ -903,6 +1070,9 @@ Module( ), parameters: Parameters { range: 499..501, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -913,9 +1083,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 503..506, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 503..506, }, ), @@ -926,31 +1098,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 543..569, is_async: false, decorator_list: [], name: Identifier { id: Name("u"), range: 547..548, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 548..562, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 549..561, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 549..550, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 554..560, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 554..555, id: Name("x"), ctx: Store, @@ -958,6 +1137,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 559..560, value: Int( 1, @@ -974,6 +1154,9 @@ Module( ), parameters: Parameters { range: 562..564, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -984,9 +1167,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 566..569, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 566..569, }, ), @@ -997,30 +1182,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 607..635, is_async: false, decorator_list: [], name: Identifier { id: Name("v"), range: 611..612, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 612..628, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 613..627, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 614..616, + node_index: AtomicNodeIndex(..), }, default: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 620..626, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 620..621, id: Name("x"), ctx: Store, @@ -1028,6 +1220,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 625..626, value: Int( 1, @@ -1044,6 +1237,9 @@ Module( ), parameters: Parameters { range: 628..630, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1054,9 +1250,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 632..635, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 632..635, }, ), @@ -1067,30 +1265,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 676..705, is_async: false, decorator_list: [], name: Identifier { id: Name("w"), range: 680..681, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 681..698, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 682..697, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 684..686, + node_index: AtomicNodeIndex(..), }, default: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 690..696, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 690..691, id: Name("x"), ctx: Store, @@ -1098,6 +1303,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 695..696, value: Int( 1, @@ -1114,6 +1320,9 @@ Module( ), parameters: Parameters { range: 698..700, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1124,9 +1333,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 702..705, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 702..705, }, ), @@ -1137,30 +1348,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 742..768, is_async: false, decorator_list: [], name: Identifier { id: Name("t"), range: 746..747, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 747..761, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 748..760, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 748..749, + node_index: AtomicNodeIndex(..), }, bound: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 752..759, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 758..759, value: Int( 1, @@ -1178,6 +1396,9 @@ Module( ), parameters: Parameters { range: 761..763, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1188,9 +1409,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 765..768, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 765..768, }, ), @@ -1201,31 +1424,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 800..827, is_async: false, decorator_list: [], name: Identifier { id: Name("u"), range: 804..805, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 805..820, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 806..819, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 806..807, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 811..818, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 817..818, value: Int( 1, @@ -1242,6 +1472,9 @@ Module( ), parameters: Parameters { range: 820..822, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1252,9 +1485,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 824..827, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 824..827, }, ), @@ -1265,30 +1500,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 860..889, is_async: false, decorator_list: [], name: Identifier { id: Name("v"), range: 864..865, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 865..882, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 866..881, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 867..869, + node_index: AtomicNodeIndex(..), }, default: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 873..880, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 879..880, value: Int( 1, @@ -1305,6 +1547,9 @@ Module( ), parameters: Parameters { range: 882..884, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1315,9 +1560,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 886..889, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 886..889, }, ), @@ -1328,30 +1575,37 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 925..955, is_async: false, decorator_list: [], name: Identifier { id: Name("w"), range: 929..930, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 930..948, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 931..947, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 933..935, + node_index: AtomicNodeIndex(..), }, default: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 939..946, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 945..946, value: Int( 1, @@ -1368,6 +1622,9 @@ Module( ), parameters: Parameters { range: 948..950, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -1378,9 +1635,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 952..955, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 952..955, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function_py314.py.snap index 49533fb33fce27..81e3240ff76ab7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function_py314.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_function_py314.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_annotation_fu ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..316, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..68, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 48..49, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 49..51, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -30,9 +36,11 @@ Module( returns: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 56..62, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("y"), ctx: Store, @@ -40,6 +48,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 3, @@ -52,9 +61,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..68, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 65..68, }, ), @@ -65,32 +76,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 69..94, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 73..74, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 74..89, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 75..88, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 75..88, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 75..78, + node_index: AtomicNodeIndex(..), }, annotation: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 81..87, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("x"), ctx: Store, @@ -98,6 +119,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..87, value: Int( 1, @@ -119,9 +141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..94, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 91..94, }, ), @@ -132,16 +156,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 95..235, is_async: false, decorator_list: [], name: Identifier { id: Name("outer"), range: 99..104, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 104..106, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -152,33 +181,43 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 112..136, is_async: false, decorator_list: [], name: Identifier { id: Name("i"), range: 116..117, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 117..131, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 118..130, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 118..130, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 118..119, + node_index: AtomicNodeIndex(..), }, annotation: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 122..129, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 1, @@ -201,9 +240,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..136, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 133..136, }, ), @@ -214,16 +255,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 141..166, is_async: false, decorator_list: [], name: Identifier { id: Name("k"), range: 145..146, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 146..148, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -233,10 +279,12 @@ Module( returns: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 153..160, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 159..160, value: Int( 1, @@ -250,9 +298,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..166, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 163..166, }, ), @@ -263,32 +313,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 171..200, is_async: false, decorator_list: [], name: Identifier { id: Name("m"), range: 175..176, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 176..195, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 177..194, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 177..194, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 177..178, + node_index: AtomicNodeIndex(..), }, annotation: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 181..193, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 192..193, value: Int( 1, @@ -310,9 +370,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 197..200, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 197..200, }, ), @@ -323,16 +385,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 205..235, is_async: false, decorator_list: [], name: Identifier { id: Name("o"), range: 209..210, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 210..212, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -342,9 +409,11 @@ Module( returns: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 217..229, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 228..229, value: Int( 1, @@ -357,9 +426,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 232..235, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 232..235, }, ), @@ -373,16 +444,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 236..315, is_async: true, decorator_list: [], name: Identifier { id: Name("outer"), range: 246..251, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 251..253, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -393,16 +469,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 259..284, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 263..264, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 264..266, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -412,9 +493,11 @@ Module( returns: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 271..278, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 277..278, value: Int( 1, @@ -427,9 +510,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 281..284, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 281..284, }, ), @@ -440,32 +525,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 289..315, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 293..294, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 294..310, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 295..309, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 295..309, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 295..298, + node_index: AtomicNodeIndex(..), }, annotation: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 301..308, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 307..308, value: Int( 1, @@ -487,9 +582,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 312..315, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 312..315, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_py314.py.snap index 113edf8f44105f..78ac67583b03bd 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_py314.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_py314.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_annotation_py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..144, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 44..55, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("a"), ctx: Store, @@ -21,9 +24,11 @@ Module( ), annotation: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 48..54, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("x"), ctx: Store, @@ -31,6 +36,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 1, @@ -45,16 +51,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 56..107, is_async: false, decorator_list: [], name: Identifier { id: Name("outer"), range: 60..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -65,9 +76,11 @@ Module( body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 73..85, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("b"), ctx: Store, @@ -75,10 +88,12 @@ Module( ), annotation: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 77..84, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 1, @@ -94,9 +109,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 90..107, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("c"), ctx: Store, @@ -104,9 +121,11 @@ Module( ), annotation: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 94..106, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 1, @@ -124,16 +143,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 108..143, is_async: true, decorator_list: [], name: Identifier { id: Name("outer"), range: 118..123, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 123..125, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -144,9 +168,11 @@ Module( body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 131..143, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("d"), ctx: Store, @@ -154,9 +180,11 @@ Module( ), annotation: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 135..142, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 141..142, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_type_alias.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_type_alias.py.snap index 8b4e9638b59c35..10e387a20bc1f1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_type_alias.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_annotation_type_alias.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_annotation_ty ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..406, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..26, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -22,21 +25,26 @@ Module( type_params: Some( TypeParams { range: 6..20, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 11..18, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..18, value: Int( 1, @@ -55,6 +63,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..26, id: Name("int"), ctx: Load, @@ -64,9 +73,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 48..75, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("X"), ctx: Store, @@ -75,22 +86,27 @@ Module( type_params: Some( TypeParams { range: 54..69, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 55..68, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 55..56, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 60..67, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 66..67, value: Int( 1, @@ -108,6 +124,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..75, id: Name("int"), ctx: Load, @@ -117,9 +134,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 98..127, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 103..104, id: Name("X"), ctx: Store, @@ -128,21 +147,26 @@ Module( type_params: Some( TypeParams { range: 104..121, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 105..120, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 106..108, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 112..119, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 1, @@ -160,6 +184,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..127, id: Name("int"), ctx: Load, @@ -169,9 +194,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 153..183, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 158..159, id: Name("X"), ctx: Store, @@ -180,21 +207,26 @@ Module( type_params: Some( TypeParams { range: 159..177, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 160..176, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 162..164, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 168..175, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 174..175, value: Int( 1, @@ -212,6 +244,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 180..183, id: Name("int"), ctx: Load, @@ -221,9 +254,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 205..223, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 210..211, id: Name("Y"), ctx: Store, @@ -232,10 +267,12 @@ Module( type_params: None, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 215..222, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 221..222, value: Int( 1, @@ -249,9 +286,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 254..271, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("Y"), ctx: Store, @@ -260,9 +299,11 @@ Module( type_params: None, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 264..270, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 264..265, id: Name("x"), ctx: Store, @@ -270,6 +311,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 269..270, value: Int( 1, @@ -282,9 +324,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 308..334, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 313..314, id: Name("Y"), ctx: Store, @@ -293,20 +337,25 @@ Module( type_params: Some( TypeParams { range: 314..328, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 315..327, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 315..316, + node_index: AtomicNodeIndex(..), }, bound: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 319..326, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 325..326, value: Int( 1, @@ -324,6 +373,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 331..334, id: Name("int"), ctx: Load, @@ -333,9 +383,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 357..375, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 362..363, id: Name("Y"), ctx: Store, @@ -344,9 +396,11 @@ Module( type_params: None, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 367..374, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 373..374, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_byte_literal.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_byte_literal.py.snap index 0cb2a556d7b944..67b56f461720f4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_byte_literal.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_byte_literal.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/invalid_byte_literal.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..44, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 0..12, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 0..12, + node_index: AtomicNodeIndex(..), value: [], flags: BytesLiteralFlags { quote_style: Single, @@ -35,14 +38,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..26, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 13..26, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 13..26, + node_index: AtomicNodeIndex(..), value: [], flags: BytesLiteralFlags { quote_style: Double, @@ -60,14 +66,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..43, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 27..43, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 27..43, + node_index: AtomicNodeIndex(..), value: [], flags: BytesLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap index c3731e868d9699..444e3dd9792a16 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/invalid_del_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..75, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..9, targets: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4..9, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Load, @@ -27,6 +30,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 1, @@ -40,21 +44,25 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 10..22, targets: [ Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 14..22, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 15..18, value: StringLiteralValue { inner: Single( StringLiteral { range: 15..18, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Single, @@ -69,6 +77,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 1, @@ -84,19 +93,23 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 23..37, targets: [ Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 27..37, elts: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 28..31, value: StringLiteralValue { inner: Single( StringLiteral { range: 28..31, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Single, @@ -110,11 +123,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 33..36, value: StringLiteralValue { inner: Single( StringLiteral { range: 33..36, + node_index: AtomicNodeIndex(..), value: "y", flags: StringLiteralFlags { quote_style: Single, @@ -134,27 +149,32 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 38..74, targets: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 42..46, }, ), BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 48..52, value: true, }, ), BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 54..59, value: false, }, ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 1, @@ -163,6 +183,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..67, value: Float( 1.0, @@ -171,11 +192,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 69..74, value: StringLiteralValue { inner: Single( StringLiteral { range: 69..74, + node_index: AtomicNodeIndex(..), value: "abc", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap index 9c628a81d1d7f8..d401ee2d552193 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_fstring_literal_element.py.snap @@ -7,23 +7,28 @@ input_file: crates/ruff_python_parser/resources/inline/err/invalid_fstring_liter ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..58, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..26, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..26, value: FStringValue { inner: Single( FString( FString { range: 0..26, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..25, + node_index: AtomicNodeIndex(..), value: "", }, ), @@ -43,19 +48,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..57, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 27..57, value: FStringValue { inner: Single( FString( FString { range: 27..57, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 31..54, + node_index: AtomicNodeIndex(..), value: "", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_string_literal.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_string_literal.py.snap index f459755d73f0ac..93f0879fae1ae9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_string_literal.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_string_literal.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/invalid_string_literal.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..56, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..25, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..25, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..25, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -35,14 +38,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..55, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 26..55, value: StringLiteralValue { inner: Single( StringLiteral { range: 26..55, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap index ca46e5e687880f..3853b04afdb878 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/irrefutable_case_patt ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..317, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..61, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,14 +25,17 @@ Module( cases: [ MatchCase { range: 13..26, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 18..21, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("var"), range: 18..21, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..26, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 23..26, }, ), @@ -50,11 +58,14 @@ Module( }, MatchCase { range: 50..61, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 55..56, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 2, @@ -67,9 +78,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -82,9 +95,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 62..102, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -93,9 +108,11 @@ Module( cases: [ MatchCase { range: 75..86, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 80..81, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -104,9 +121,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..86, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 83..86, }, ), @@ -116,11 +135,14 @@ Module( }, MatchCase { range: 91..102, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 96..97, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 96..97, value: Int( 2, @@ -133,9 +155,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 99..102, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 99..102, }, ), @@ -148,9 +172,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 125..222, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("x"), ctx: Load, @@ -159,18 +185,22 @@ Module( cases: [ MatchCase { range: 138..160, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 143..155, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 143..147, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("var1"), range: 143..147, + node_index: AtomicNodeIndex(..), }, ), }, @@ -180,6 +210,7 @@ Module( Identifier { id: Name("var2"), range: 151..155, + node_index: AtomicNodeIndex(..), }, ), }, @@ -188,9 +219,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 157..160, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 157..160, }, ), @@ -200,11 +233,14 @@ Module( }, MatchCase { range: 211..222, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 216..217, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 216..217, value: Int( 2, @@ -217,9 +253,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 219..222, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 219..222, }, ), @@ -232,9 +270,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 223..316, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 229..230, id: Name("x"), ctx: Load, @@ -243,18 +283,23 @@ Module( cases: [ MatchCase { range: 236..264, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 241..259, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 241..253, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 241..253, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..245, id: Name("enum"), ctx: Load, @@ -263,6 +308,7 @@ Module( attr: Identifier { id: Name("variant"), range: 246..253, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -272,11 +318,13 @@ Module( MatchAs( PatternMatchAs { range: 256..259, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("var"), range: 256..259, + node_index: AtomicNodeIndex(..), }, ), }, @@ -288,9 +336,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 261..264, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 261..264, }, ), @@ -300,11 +350,14 @@ Module( }, MatchCase { range: 305..316, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 310..311, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 310..311, value: Int( 2, @@ -317,9 +370,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 313..316, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 313..316, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_return_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_return_py37.py.snap index 0aa69e8be1803e..305adc1eeaced2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_return_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_return_py37.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/iter_unpack_return_py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..91, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..90, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,14 +90,17 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 69..90, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 76..90, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 1, @@ -94,6 +109,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 2, @@ -102,6 +118,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 3, @@ -110,9 +127,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 85..90, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..90, id: Name("rest"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_yield_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_yield_py37.py.snap index 44b0fe51e34c84..f3e6ac63aa8ade 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_yield_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@iter_unpack_yield_py37.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/iter_unpack_yield_py3 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..128, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..89, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,17 +90,21 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..89, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 69..89, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 75..89, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 1, @@ -97,6 +113,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 2, @@ -105,6 +122,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 3, @@ -113,9 +131,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 84..89, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..89, id: Name("rest"), ctx: Load, @@ -139,16 +159,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 90..127, is_async: false, decorator_list: [], name: Identifier { id: Name("h"), range: 94..95, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 95..97, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -159,17 +184,21 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 99..127, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 99..127, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 105..127, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 1, @@ -178,14 +207,17 @@ Module( ), Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 109..123, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 115..123, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 2, @@ -194,9 +226,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 118..123, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..123, id: Name("rest"), ctx: Load, @@ -215,6 +249,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 126..127, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_starred_expr.py.snap index 98b0ef84a3e3da..920e8ab7d7d32c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_starred_expr.py.snap @@ -1,33 +1,41 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/lambda_body_with_starred_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..62, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..12, parameters: Some( Parameters { range: 7..8, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 7..8, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,9 +49,11 @@ Module( ), body: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 10..12, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("y"), ctx: Load, @@ -58,26 +68,35 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..26, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 13..26, elts: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 13..25, parameters: Some( Parameters { range: 20..21, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 20..21, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 20..21, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 20..21, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -91,9 +110,11 @@ Module( ), body: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 23..25, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("y"), ctx: Load, @@ -113,26 +134,35 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..42, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 27..42, elts: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 27..39, parameters: Some( Parameters { range: 34..35, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 34..35, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 34..35, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 34..35, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -146,9 +176,11 @@ Module( ), body: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 37..39, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("y"), ctx: Load, @@ -161,6 +193,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("z"), ctx: Load, @@ -175,22 +208,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..61, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 43..61, parameters: Some( Parameters { range: 50..51, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 50..51, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 50..51, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 50..51, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -204,14 +245,17 @@ Module( ), body: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 53..61, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 54..61, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("y"), ctx: Load, @@ -219,6 +263,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_yield_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_yield_expr.py.snap index fe4a4b01abd511..1b3cd9547258f3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_yield_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@lambda_body_with_yield_expr.py.snap @@ -1,33 +1,41 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/lambda_body_with_yield_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..17, parameters: Some( Parameters { range: 7..8, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 7..8, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,10 +49,12 @@ Module( ), body: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 10..17, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("y"), ctx: Load, @@ -59,22 +69,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..40, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 18..40, parameters: Some( Parameters { range: 25..26, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 25..26, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 25..26, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 25..26, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -88,9 +106,11 @@ Module( ), body: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 28..40, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_before_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_before_py310.py.snap index a951295d8a8091..f4cf349ddd2b04 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_before_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_before_py310.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/match_before_py310.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..79, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 45..78, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 2, @@ -23,11 +26,14 @@ Module( cases: [ MatchCase { range: 58..78, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 63..64, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 1, @@ -40,6 +46,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 74..78, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword.py.snap index 81e020fe54a613..e4e0b0db637137 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_classify_as_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..33, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..32, subject: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 6..15, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..15, id: Name("foo"), ctx: Load, @@ -30,9 +33,11 @@ Module( cases: [ MatchCase { range: 21..32, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 26..27, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -41,9 +46,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword_or_identifier.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword_or_identifier.py.snap index 9681e681bba8b2..5ce94bf138ca1b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword_or_identifier.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_classify_as_keyword_or_identifier.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_classify_as_keyword_or_identifier.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..39, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..38, subject: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..10, id: Name("foo"), ctx: Load, @@ -29,9 +32,11 @@ Module( cases: [ MatchCase { range: 27..38, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 32..33, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -40,9 +45,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..38, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 35..38, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_expected_colon.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_expected_colon.py.snap index 42e2347dd2fc36..6415f9724a5519 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_expected_colon.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_expected_colon.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_expected_colon.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..29, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..28, subject: List( ExprList { + node_index: AtomicNodeIndex(..), range: 6..12, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, @@ -27,6 +30,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 2, @@ -40,9 +44,11 @@ Module( cases: [ MatchCase { range: 17..28, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 22..23, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -51,9 +57,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expect_indented_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expect_indented_block.py.snap index 5ba948d16b0524..273c444e538400 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expect_indented_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expect_indented_block.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_expect_ind ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..23, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..22, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -22,9 +25,11 @@ Module( cases: [ MatchCase { range: 11..22, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 16..17, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -33,9 +38,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expected_case_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expected_case_block.py.snap index 2844e904f71562..528b87d4e7afe0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expected_case_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_expected_case_block.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_expected_c ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..61, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..13, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -24,10 +27,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 13..18, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("x"), ctx: Store, @@ -36,6 +41,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..18, value: Int( 1, @@ -46,9 +52,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 19..32, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Load, @@ -59,9 +67,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 32..60, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("y"), ctx: Load, @@ -70,9 +80,11 @@ Module( cases: [ MatchCase { range: 49..60, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 54..55, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -81,9 +93,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_guard_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_guard_expr.py.snap index 5028df2a0d252c..0f5a1d34f4894f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_guard_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_guard_expr.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_invalid_guard_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..100, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..30, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -23,14 +25,17 @@ Module( cases: [ MatchCase { range: 13..30, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 18..19, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 18..19, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +43,11 @@ Module( guard: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 23..25, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("a"), ctx: Load, @@ -53,9 +60,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..30, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 27..30, }, ), @@ -68,9 +77,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 31..63, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("x"), ctx: Load, @@ -79,14 +90,17 @@ Module( cases: [ MatchCase { range: 44..63, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 49..50, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 49..50, + node_index: AtomicNodeIndex(..), }, ), }, @@ -94,9 +108,11 @@ Module( guard: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 55..57, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("a"), ctx: Load, @@ -109,9 +125,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..63, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 60..63, }, ), @@ -124,9 +142,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 64..99, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Load, @@ -135,14 +155,17 @@ Module( cases: [ MatchCase { range: 77..99, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 82..83, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 82..83, + node_index: AtomicNodeIndex(..), }, ), }, @@ -150,10 +173,12 @@ Module( guard: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 87..94, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("x"), ctx: Load, @@ -166,9 +191,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_subject_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_subject_expr.py.snap index e7e2359f8363b6..13285ab3c48f5b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_subject_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_invalid_subject_expr.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_invalid_subject_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..131, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..27, subject: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 7..9, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("x"), ctx: Load, @@ -29,9 +32,11 @@ Module( cases: [ MatchCase { range: 16..27, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 21..22, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -40,9 +45,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 24..27, }, ), @@ -55,21 +62,26 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 65..99, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 71..82, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 71..79, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 72..79, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("x"), ctx: Load, @@ -77,6 +89,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("y"), ctx: Load, @@ -90,6 +103,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("z"), ctx: Load, @@ -103,9 +117,11 @@ Module( cases: [ MatchCase { range: 88..99, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 93..94, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -114,9 +130,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), @@ -129,13 +147,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 100..130, subject: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 106..113, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 112..113, id: Name("x"), ctx: Load, @@ -147,9 +168,11 @@ Module( cases: [ MatchCase { range: 119..130, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 124..125, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -158,9 +181,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 127..130, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 127..130, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_guard_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_guard_expr.py.snap index 2dbcec05b1263c..bda3c6e5c36927 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_guard_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_guard_expr.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_missing_guard_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..27, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -23,14 +25,17 @@ Module( cases: [ MatchCase { range: 13..27, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 18..19, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 18..19, + node_index: AtomicNodeIndex(..), }, ), }, @@ -39,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 24..27, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_pattern.py.snap index 81183473ca9699..3b604f0505f194 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_missing_pattern.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_missing_pattern.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..23, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -23,11 +25,14 @@ Module( cases: [ MatchCase { range: 13..23, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 17..17, + node_index: AtomicNodeIndex(..), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..17, id: Name(""), ctx: Invalid, @@ -39,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_no_newline_before_case.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_no_newline_before_case.py.snap index 6a9ba216b3a0c2..f22cb551fca4da 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_no_newline_before_case.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_no_newline_before_case.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_no_newline ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..23, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..22, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -22,9 +25,11 @@ Module( cases: [ MatchCase { range: 11..22, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 16..17, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -33,9 +38,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_single_starred_subject.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_single_starred_subject.py.snap index affa89318ac75c..1bfa4ac064cd67 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_single_starred_subject.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@match_stmt_single_starred_subject.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/match_stmt_single_starred_subject.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..27, subject: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..10, id: Name("foo"), ctx: Load, @@ -29,9 +32,11 @@ Module( cases: [ MatchCase { range: 16..27, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 21..22, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -40,9 +45,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 24..27, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap index 35384a51a4b42f..192d87e0b5485e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@mixed_bytes_and_non_bytes_literals.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/mixed_bytes_and_non_b ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..64, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..17, value: StringLiteralValue { inner: Concatenated( @@ -21,6 +24,7 @@ Module( strings: [ StringLiteral { range: 0..7, + node_index: AtomicNodeIndex(..), value: "first", flags: StringLiteralFlags { quote_style: Single, @@ -30,6 +34,7 @@ Module( }, StringLiteral { range: 8..17, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -48,9 +53,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..36, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 18..36, value: FStringValue { inner: Concatenated( @@ -58,10 +65,12 @@ Module( FString( FString { range: 18..26, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 20..25, + node_index: AtomicNodeIndex(..), value: "first", }, ), @@ -76,6 +85,7 @@ Module( Literal( StringLiteral { range: 27..36, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -93,9 +103,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..63, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 37..63, value: FStringValue { inner: Concatenated( @@ -103,6 +115,7 @@ Module( Literal( StringLiteral { range: 37..44, + node_index: AtomicNodeIndex(..), value: "first", flags: StringLiteralFlags { quote_style: Single, @@ -114,10 +127,12 @@ Module( FString( FString { range: 45..54, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 47..53, + node_index: AtomicNodeIndex(..), value: "second", }, ), @@ -132,6 +147,7 @@ Module( Literal( StringLiteral { range: 55..63, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap index c47d0febba18ff..c3e7a8341c2131 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/multiple_assignment_i ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..456, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..444, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 2, @@ -23,18 +26,22 @@ Module( cases: [ MatchCase { range: 13..32, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 18..27, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 19..20, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 19..20, + node_index: AtomicNodeIndex(..), }, ), }, @@ -42,11 +49,13 @@ Module( MatchAs( PatternMatchAs { range: 22..23, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ), }, @@ -54,11 +63,13 @@ Module( MatchAs( PatternMatchAs { range: 25..26, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 25..26, + node_index: AtomicNodeIndex(..), }, ), }, @@ -70,9 +81,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -82,18 +95,22 @@ Module( }, MatchCase { range: 54..74, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 59..69, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 60..61, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 60..61, + node_index: AtomicNodeIndex(..), }, ), }, @@ -101,11 +118,13 @@ Module( MatchAs( PatternMatchAs { range: 63..64, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 63..64, + node_index: AtomicNodeIndex(..), }, ), }, @@ -113,10 +132,12 @@ Module( MatchStar( PatternMatchStar { range: 66..68, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("y"), range: 67..68, + node_index: AtomicNodeIndex(..), }, ), }, @@ -128,9 +149,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..74, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 71..74, }, ), @@ -140,18 +163,22 @@ Module( }, MatchCase { range: 96..115, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 101..110, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 102..103, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 102..103, + node_index: AtomicNodeIndex(..), }, ), }, @@ -159,11 +186,13 @@ Module( MatchAs( PatternMatchAs { range: 105..106, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 105..106, + node_index: AtomicNodeIndex(..), }, ), }, @@ -171,11 +200,13 @@ Module( MatchAs( PatternMatchAs { range: 108..109, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 108..109, + node_index: AtomicNodeIndex(..), }, ), }, @@ -187,9 +218,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 112..115, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 112..115, }, ), @@ -199,12 +232,15 @@ Module( }, MatchCase { range: 146..168, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 151..163, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 152..153, value: Int( 1, @@ -213,6 +249,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 2, @@ -224,11 +261,13 @@ Module( MatchAs( PatternMatchAs { range: 155..156, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 155..156, + node_index: AtomicNodeIndex(..), }, ), }, @@ -236,11 +275,13 @@ Module( MatchAs( PatternMatchAs { range: 161..162, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 161..162, + node_index: AtomicNodeIndex(..), }, ), }, @@ -253,9 +294,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 165..168, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 165..168, }, ), @@ -265,12 +308,15 @@ Module( }, MatchCase { range: 207..228, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 212..223, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..214, value: Int( 1, @@ -282,11 +328,13 @@ Module( MatchAs( PatternMatchAs { range: 216..217, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 216..217, + node_index: AtomicNodeIndex(..), }, ), }, @@ -296,6 +344,7 @@ Module( Identifier { id: Name("x"), range: 221..222, + node_index: AtomicNodeIndex(..), }, ), }, @@ -304,9 +353,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 225..228, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 225..228, }, ), @@ -316,11 +367,14 @@ Module( }, MatchCase { range: 269..290, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 274..285, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 274..279, id: Name("Class"), ctx: Load, @@ -328,15 +382,18 @@ Module( ), arguments: PatternArguments { range: 279..285, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 280..281, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 280..281, + node_index: AtomicNodeIndex(..), }, ), }, @@ -344,11 +401,13 @@ Module( MatchAs( PatternMatchAs { range: 283..284, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 283..284, + node_index: AtomicNodeIndex(..), }, ), }, @@ -362,9 +421,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 287..290, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 287..290, }, ), @@ -374,11 +435,14 @@ Module( }, MatchCase { range: 320..345, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 325..340, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 325..330, id: Name("Class"), ctx: Load, @@ -386,22 +450,27 @@ Module( ), arguments: PatternArguments { range: 330..340, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 331..334, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 331..332, + node_index: AtomicNodeIndex(..), }, pattern: MatchAs( PatternMatchAs { range: 333..334, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 333..334, + node_index: AtomicNodeIndex(..), }, ), }, @@ -409,18 +478,22 @@ Module( }, PatternKeyword { range: 336..339, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("z"), range: 336..337, + node_index: AtomicNodeIndex(..), }, pattern: MatchAs( PatternMatchAs { range: 338..339, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 338..339, + node_index: AtomicNodeIndex(..), }, ), }, @@ -434,9 +507,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 342..345, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 342..345, }, ), @@ -446,22 +521,27 @@ Module( }, MatchCase { range: 372..412, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 377..407, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 377..380, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 378..379, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 378..379, + node_index: AtomicNodeIndex(..), }, ), }, @@ -472,9 +552,11 @@ Module( MatchMapping( PatternMatchMapping { range: 383..389, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 384..385, value: Int( 1, @@ -486,11 +568,13 @@ Module( MatchAs( PatternMatchAs { range: 387..388, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 387..388, + node_index: AtomicNodeIndex(..), }, ), }, @@ -502,8 +586,10 @@ Module( MatchClass( PatternMatchClass { range: 392..407, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 392..397, id: Name("Class"), ctx: Load, @@ -511,22 +597,27 @@ Module( ), arguments: PatternArguments { range: 397..407, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 398..401, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 398..399, + node_index: AtomicNodeIndex(..), }, pattern: MatchAs( PatternMatchAs { range: 400..401, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 400..401, + node_index: AtomicNodeIndex(..), }, ), }, @@ -534,18 +625,22 @@ Module( }, PatternKeyword { range: 403..406, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("z"), range: 403..404, + node_index: AtomicNodeIndex(..), }, pattern: MatchAs( PatternMatchAs { range: 405..406, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 405..406, + node_index: AtomicNodeIndex(..), }, ), }, @@ -562,9 +657,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 409..412, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 409..412, }, ), @@ -574,18 +671,22 @@ Module( }, MatchCase { range: 428..444, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 433..439, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 433..434, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 433..434, + node_index: AtomicNodeIndex(..), }, ), }, @@ -595,6 +696,7 @@ Module( Identifier { id: Name("x"), range: 438..439, + node_index: AtomicNodeIndex(..), }, ), }, @@ -603,9 +705,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 441..444, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 441..444, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_clauses_on_same_line.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_clauses_on_same_line.py.snap index f67856c23fbd2c..e7bbeb557a229d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_clauses_on_same_line.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_clauses_on_same_line.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/multiple_clauses_on_same_line.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..258, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..41, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -22,6 +24,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -29,9 +32,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 14..30, + node_index: AtomicNodeIndex(..), test: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 19..24, value: false, }, @@ -40,6 +45,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 26..30, }, ), @@ -47,10 +53,12 @@ Module( }, ElifElseClause { range: 31..41, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 37..41, }, ), @@ -61,9 +69,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 42..85, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 45..49, value: true, }, @@ -71,6 +81,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 51..55, }, ), @@ -78,9 +89,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 57..73, + node_index: AtomicNodeIndex(..), test: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 62..67, value: false, }, @@ -89,6 +102,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 69..73, }, ), @@ -96,10 +110,12 @@ Module( }, ElifElseClause { range: 75..85, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 81..85, }, ), @@ -110,10 +126,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 86..117, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("x"), ctx: Store, @@ -121,6 +139,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..99, id: Name("iter"), ctx: Load, @@ -129,6 +148,7 @@ Module( body: [ Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 101..106, }, ), @@ -136,6 +156,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 113..117, }, ), @@ -144,10 +165,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 118..150, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("x"), ctx: Store, @@ -155,6 +178,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..131, id: Name("iter"), ctx: Load, @@ -163,6 +187,7 @@ Module( body: [ Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 133..138, }, ), @@ -170,6 +195,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 146..150, }, ), @@ -178,10 +204,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 151..202, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 156..160, }, ), @@ -190,9 +218,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 161..177, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..171, id: Name("exc"), ctx: Load, @@ -203,6 +233,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 173..177, }, ), @@ -213,6 +244,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 184..188, }, ), @@ -220,6 +252,7 @@ Module( finalbody: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 198..202, }, ), @@ -229,10 +262,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 203..257, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 208..212, }, ), @@ -241,9 +276,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 214..230, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 221..224, id: Name("exc"), ctx: Load, @@ -254,6 +291,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 226..230, }, ), @@ -264,6 +302,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 238..242, }, ), @@ -271,6 +310,7 @@ Module( finalbody: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 253..257, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap index 815b74f88cd785..0e70f2a74ac12a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..119, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 80..92, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 80..92, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..83, id: Name("lst"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 84..91, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 84..88, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..85, id: Name("x"), ctx: Store, @@ -38,6 +45,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 87..88, value: Int( 1, @@ -50,10 +58,12 @@ Module( upper: Some( UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 89..91, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 90..91, value: Int( 1, @@ -73,12 +83,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..100, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 93..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..96, id: Name("lst"), ctx: Load, @@ -86,10 +99,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 97..100, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 97..98, value: Int( 1, @@ -100,6 +115,7 @@ Module( upper: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("x"), ctx: Load, @@ -116,9 +132,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 102..103, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 102..103, value: Int( 1, @@ -129,12 +147,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 105..114, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 105..114, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..108, id: Name("lst"), ctx: Load, @@ -142,10 +163,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 109..114, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 109..110, value: Int( 1, @@ -156,6 +179,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 111..112, value: Int( 3, @@ -166,6 +190,7 @@ Module( step: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("x"), ctx: Load, @@ -181,9 +206,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 116..117, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 116..117, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap index d0166af2f4f0ef..044df9bbb71cf6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice_pars ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..130, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..129, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 117..129, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..120, id: Name("lst"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 121..128, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 121..125, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..122, id: Name("x"), ctx: Store, @@ -38,6 +45,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 124..125, value: Int( 1, @@ -50,10 +58,12 @@ Module( upper: Some( UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 126..128, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 127..128, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nested_async_comprehension_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nested_async_comprehension_py310.py.snap index cec50a8151184e..9d6245421f2e98 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nested_async_comprehension_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nested_async_comprehension_py310.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/nested_async_comprehe ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..467, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..111, is_async: true, decorator_list: [], name: Identifier { id: Name("f"), range: 54..55, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 55..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 59..111, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 66..111, elt: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 67..92, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -49,8 +59,10 @@ Module( generators: [ Comprehension { range: 70..91, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..81, id: Name("x"), ctx: Store, @@ -58,9 +70,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 85..91, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..88, id: Name("foo"), ctx: Load, @@ -68,9 +82,11 @@ Module( ), arguments: Arguments { range: 88..91, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("n"), ctx: Load, @@ -90,8 +106,10 @@ Module( generators: [ Comprehension { range: 93..110, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("n"), ctx: Store, @@ -99,9 +117,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 102..110, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..107, id: Name("range"), ctx: Load, @@ -109,9 +129,11 @@ Module( ), arguments: Arguments { range: 107..110, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 108..109, value: Int( 3, @@ -137,16 +159,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 122..192, is_async: true, decorator_list: [], name: Identifier { id: Name("g"), range: 132..133, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 133..135, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -157,16 +184,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 137..192, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 144..192, elt: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 145..173, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Load, @@ -174,6 +205,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 1, @@ -183,8 +215,10 @@ Module( generators: [ Comprehension { range: 151..172, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("x"), ctx: Store, @@ -192,9 +226,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 166..172, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..169, id: Name("foo"), ctx: Load, @@ -202,9 +238,11 @@ Module( ), arguments: Arguments { range: 169..172, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("n"), ctx: Load, @@ -224,8 +262,10 @@ Module( generators: [ Comprehension { range: 174..191, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("n"), ctx: Store, @@ -233,9 +273,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 183..191, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 183..188, id: Name("range"), ctx: Load, @@ -243,9 +285,11 @@ Module( ), arguments: Arguments { range: 188..191, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 189..190, value: Int( 3, @@ -271,16 +315,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 200..267, is_async: true, decorator_list: [], name: Identifier { id: Name("h"), range: 210..211, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 211..213, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -291,16 +340,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 215..267, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 222..267, elt: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 223..248, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 224..225, id: Name("x"), ctx: Load, @@ -309,8 +362,10 @@ Module( generators: [ Comprehension { range: 226..247, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("x"), ctx: Store, @@ -318,9 +373,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 241..247, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..244, id: Name("foo"), ctx: Load, @@ -328,9 +385,11 @@ Module( ), arguments: Arguments { range: 244..247, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("n"), ctx: Load, @@ -350,8 +409,10 @@ Module( generators: [ Comprehension { range: 249..266, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 253..254, id: Name("n"), ctx: Store, @@ -359,9 +420,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 258..266, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..263, id: Name("range"), ctx: Load, @@ -369,9 +432,11 @@ Module( ), arguments: Arguments { range: 263..266, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 264..265, value: Int( 3, @@ -397,16 +462,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 277..371, is_async: true, decorator_list: [], name: Identifier { id: Name("i"), range: 287..288, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 288..290, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -417,20 +487,25 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 292..371, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 299..371, elt: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 300..352, elts: [ ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 301..328, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 302..303, id: Name("y"), ctx: Load, @@ -439,8 +514,10 @@ Module( generators: [ Comprehension { range: 304..327, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 314..315, id: Name("y"), ctx: Store, @@ -448,9 +525,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 319..327, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 319..324, id: Name("range"), ctx: Load, @@ -458,9 +537,11 @@ Module( ), arguments: Arguments { range: 324..327, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 325..326, value: Int( 1, @@ -480,9 +561,11 @@ Module( ), ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 330..351, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 331..332, id: Name("z"), ctx: Load, @@ -491,8 +574,10 @@ Module( generators: [ Comprehension { range: 333..350, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 337..338, id: Name("z"), ctx: Store, @@ -500,9 +585,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 342..350, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 342..347, id: Name("range"), ctx: Load, @@ -510,9 +597,11 @@ Module( ), arguments: Arguments { range: 347..350, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 348..349, value: Int( 2, @@ -538,8 +627,10 @@ Module( generators: [ Comprehension { range: 353..370, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 357..358, id: Name("x"), ctx: Store, @@ -547,9 +638,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 362..370, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 362..367, id: Name("range"), ctx: Load, @@ -557,9 +650,11 @@ Module( ), arguments: Arguments { range: 367..370, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 368..369, value: Int( 5, @@ -585,16 +680,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 372..466, is_async: true, decorator_list: [], name: Identifier { id: Name("j"), range: 382..383, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 383..385, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -605,20 +705,25 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 387..466, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 394..466, elt: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 395..447, elts: [ ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 396..417, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 397..398, id: Name("y"), ctx: Load, @@ -627,8 +732,10 @@ Module( generators: [ Comprehension { range: 399..416, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 403..404, id: Name("y"), ctx: Store, @@ -636,9 +743,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 408..416, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 408..413, id: Name("range"), ctx: Load, @@ -646,9 +755,11 @@ Module( ), arguments: Arguments { range: 413..416, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 414..415, value: Int( 1, @@ -668,9 +779,11 @@ Module( ), ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 419..446, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 420..421, id: Name("z"), ctx: Load, @@ -679,8 +792,10 @@ Module( generators: [ Comprehension { range: 422..445, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 432..433, id: Name("z"), ctx: Store, @@ -688,9 +803,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 437..445, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 437..442, id: Name("range"), ctx: Load, @@ -698,9 +815,11 @@ Module( ), arguments: Arguments { range: 442..445, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 443..444, value: Int( 2, @@ -726,8 +845,10 @@ Module( generators: [ Comprehension { range: 448..465, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 452..453, id: Name("x"), ctx: Store, @@ -735,9 +856,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 457..465, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 457..462, id: Name("range"), ctx: Load, @@ -745,9 +868,11 @@ Module( ), arguments: Arguments { range: 462..465, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 463..464, value: Int( 5, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@node_range_with_gaps.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@node_range_with_gaps.py.snap index e6364e6c4c577e..95c44e302d5e46 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@node_range_with_gaps.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@node_range_with_gaps.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/node_range_with_gaps. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..7, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -33,16 +39,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 18..32, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 22..25, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 25..27, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -53,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -66,16 +79,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 33..40, is_async: false, decorator_list: [], name: Identifier { id: Name("baz"), range: 37..40, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 40..40, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_declaration_at_module_level.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_declaration_at_module_level.py.snap index 8b63405c852398..d49417620bdb04 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_declaration_at_module_level.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_declaration_at_module_level.py.snap @@ -7,30 +7,36 @@ input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_declaration_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..25, body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 0..10, names: [ Identifier { id: Name("x"), range: 9..10, + node_index: AtomicNodeIndex(..), }, ], }, ), Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 11..24, names: [ Identifier { id: Name("x"), range: 20..21, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("y"), range: 23..24, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_empty.py.snap index 67c9ca6335fa2b..dde2775dc31d2a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_empty.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_empty.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_empty.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..22, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..21, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,6 +37,7 @@ Module( body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 13..21, names: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_expression.py.snap index b5d768d9a32e32..739b49643a7081 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_expression.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_express ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..27, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,24 +37,29 @@ Module( body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 13..23, names: [ Identifier { id: Name("x"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ], }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 24..27, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..27, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_trailing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_trailing_comma.py.snap index ee76a9c939bb7e..10cedaf7ba3260 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_trailing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@nonlocal_stmt_trailing_comma.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_trailin ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..59, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..58, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,32 +37,38 @@ Module( body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 13..23, names: [], }, ), Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 28..39, names: [ Identifier { id: Name("x"), range: 37..38, + node_index: AtomicNodeIndex(..), }, ], }, ), Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 44..58, names: [ Identifier { id: Name("x"), range: 53..54, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("y"), range: 56..57, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_annotation.py.snap index b0d2dec14add76..f23ebd2f93cc25 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_annotation.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/param_missing_annotation.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..35, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..16, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..11, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..10, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -45,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..16, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 13..16, }, ), @@ -58,25 +68,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 17..34, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 21..24, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 24..29, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 25..27, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 25..27, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 25..26, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -91,9 +109,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..34, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 31..34, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_default.py.snap index f263e09822eff0..ee03c9a0a6a567 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_missing_default.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/param_missing_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..16, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..11, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..10, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -45,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..16, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 13..16, }, ), @@ -58,29 +68,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 17..40, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 21..24, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 24..35, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 25..33, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 25..31, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 25..26, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..31, id: Name("int"), ctx: Load, @@ -99,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..40, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 37..40, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap index d799a7893971dd..779f868a448aea 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_annotation.py.snap @@ -7,36 +7,47 @@ input_file: crates/ruff_python_parser/resources/inline/err/param_with_invalid_an ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..81, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..23, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..18, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..17, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 8..11, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -58,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), @@ -71,33 +84,43 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 24..52, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 28..31, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 31..47, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 32..46, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 32..46, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 32..35, + node_index: AtomicNodeIndex(..), }, annotation: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 37..46, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("int"), ctx: Load, @@ -119,9 +142,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), @@ -132,29 +157,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 53..80, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 57..60, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 60..75, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 61..67, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 61..67, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 61..64, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("x"), ctx: Load, @@ -166,11 +200,14 @@ Module( }, ParameterWithDefault { range: 71..74, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 71..74, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("int"), range: 71..74, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -185,9 +222,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 77..80, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 77..80, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_default.py.snap index 6bbc3b41f52fa0..bec69b1894bed7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_default.py.snap @@ -1,45 +1,55 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/param_with_invalid_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..68, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..20, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..15, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..14, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 10..14, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..14, id: Name("int"), ctx: Load, @@ -59,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), @@ -72,34 +84,44 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 21..43, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 25..28, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 28..38, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 29..37, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 29..30, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 29..30, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 32..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..36, id: Name("int"), ctx: Load, @@ -119,9 +141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..43, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 40..43, }, ), @@ -132,35 +156,45 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..67, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 51..62, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 52..61, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 52..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 52..53, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 54..61, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("y"), ctx: Load, @@ -180,9 +214,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..67, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 64..67, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap index 13eeeed68978f2..9360cfde290d92 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_invalid_star_annotation.py.snap @@ -7,35 +7,45 @@ input_file: crates/ruff_python_parser/resources/inline/err/param_with_invalid_st ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..150, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..22, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 8..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 9..13, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 15..16, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..16, id: Name(""), ctx: Invalid, @@ -54,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -67,34 +79,44 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..57, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 27..30, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 30..52, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 31..51, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 32..36, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 39..50, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 40..50, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..45, id: Name("tuple"), ctx: Load, @@ -102,6 +124,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..49, id: Name("int"), ctx: Load, @@ -123,9 +146,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..57, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 54..57, }, ), @@ -136,36 +161,46 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 58..90, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 62..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..85, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 66..84, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 67..71, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 73..84, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 74..84, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..77, id: Name("int"), ctx: Load, @@ -173,6 +208,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..84, id: Name("str"), ctx: Load, @@ -194,9 +230,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..90, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 87..90, }, ), @@ -207,35 +245,45 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 91..120, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 95..98, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 98..115, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 99..114, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 100..104, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 106..114, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 107..114, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("x"), ctx: Load, @@ -257,9 +305,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..120, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 117..120, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap index ef87ca461ff9dd..224824a9e7325a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap @@ -7,35 +7,45 @@ input_file: crates/ruff_python_parser/resources/inline/err/param_with_star_annot ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..69, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..68, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 51..63, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 52..62, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 53..57, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 59..62, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..62, id: Name("Ts"), ctx: Load, @@ -54,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..68, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 65..68, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_duplicate_names.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_duplicate_names.py.snap index 50e5e76378f269..9e0f94fdad06f6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_duplicate_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_duplicate_names.py.snap @@ -7,29 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/err/params_duplicate_name ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..42, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..41, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..36, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -37,17 +46,21 @@ Module( }, ParameterWithDefault { range: 11..15, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 11..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 11..12, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..15, value: Int( 10, @@ -60,9 +73,11 @@ Module( vararg: Some( Parameter { range: 17..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 18..19, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -70,11 +85,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 21..22, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 21..22, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 21..22, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -82,15 +100,19 @@ Module( }, ParameterWithDefault { range: 24..30, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 24..30, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 24..25, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..30, id: Name("str"), ctx: Load, @@ -104,9 +126,11 @@ Module( kwarg: Some( Parameter { range: 32..35, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 34..35, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -116,9 +140,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..41, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 38..41, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_expected_after_star_separator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_expected_after_star_separator.py.snap index 9a1f540021a7e3..c2fdb7f4d5043b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_expected_after_star_separator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_expected_after_star_separator.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_expected_after_star_separator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..98, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..15, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..10, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..15, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 12..15, }, ), @@ -45,16 +52,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 16..32, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 20..23, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 23..27, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -65,9 +77,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -78,25 +92,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 33..51, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 37..40, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 40..46, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 41..42, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 41..42, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 41..42, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -111,9 +133,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 48..51, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 48..51, }, ), @@ -124,25 +148,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 52..71, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 56..59, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 59..66, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 60..61, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 60..61, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 60..61, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -157,9 +189,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..71, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 68..71, }, ), @@ -170,16 +204,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 72..97, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 76..79, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 79..92, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -187,9 +226,11 @@ Module( kwarg: Some( Parameter { range: 83..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 85..91, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -199,9 +240,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..97, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 94..97, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_kwarg_after_star_separator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_kwarg_after_star_separator.py.snap index 3decf036cf2ed9..71846f8fc78c2b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_kwarg_after_star_separator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_kwarg_after_star_separator.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_kwarg_after_star_separator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..26, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..25, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..20, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -29,9 +34,11 @@ Module( kwarg: Some( Parameter { range: 11..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 13..19, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,9 +48,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..25, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 22..25, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_kwargs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_kwargs.py.snap index 6fa8e4a41d19a3..f26f826c50ebb7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_kwargs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_kwargs.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_multiple_kwargs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..37, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..32, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -42,9 +50,11 @@ Module( kwarg: Some( Parameter { range: 22..31, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs2"), range: 24..31, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -54,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..37, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 34..37, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_slash_separator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_slash_separator.py.snap index 5c081e0e498280..5bbcc798903453 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_slash_separator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_slash_separator.py.snap @@ -1,35 +1,43 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_multiple_slash_separator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..24, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..19, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -39,11 +47,14 @@ Module( args: [ ParameterWithDefault { range: 17..18, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 17..18, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -58,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..24, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, }, ), @@ -71,24 +84,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 25..52, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 29..32, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 32..47, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 33..34, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 33..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 33..34, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -98,11 +119,14 @@ Module( args: [ ParameterWithDefault { range: 39..40, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 39..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 39..40, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -110,11 +134,14 @@ Module( }, ParameterWithDefault { range: 42..43, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 42..43, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 42..43, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -129,9 +156,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_star_separator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_star_separator.py.snap index 68c9459f96df13..9e2ccfb2c9a614 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_star_separator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_star_separator.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_multiple_star_separator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..24, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..19, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,11 +49,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 17..18, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 17..18, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -58,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..24, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, }, ), @@ -71,25 +84,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 25..52, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 29..32, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 32..47, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 33..34, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 33..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 33..34, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -100,11 +121,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 39..40, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 39..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 39..40, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -112,11 +136,14 @@ Module( }, ParameterWithDefault { range: 42..43, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 42..43, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 42..43, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -129,9 +156,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_varargs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_varargs.py.snap index 4e350369bf8886..a8a26d73bc2c0a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_varargs.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_multiple_varargs.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_multiple_varargs.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..136, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..23, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -40,9 +48,11 @@ Module( vararg: Some( Parameter { range: 14..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 15..19, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -50,11 +60,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 21..22, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 21..22, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 21..22, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -67,9 +80,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -80,25 +95,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 63..97, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 67..70, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 70..92, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 71..72, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 71..72, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 71..72, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -108,9 +131,11 @@ Module( vararg: Some( Parameter { range: 74..80, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args1"), range: 75..80, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -118,11 +143,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 90..91, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 90..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 90..91, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -135,9 +163,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..97, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 94..97, }, ), @@ -148,25 +178,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 98..135, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 102..105, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 105..130, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 106..107, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 106..107, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 106..107, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -176,9 +214,11 @@ Module( vararg: Some( Parameter { range: 109..115, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args1"), range: 110..115, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -186,11 +226,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 117..118, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 117..118, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 117..118, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -198,11 +241,14 @@ Module( }, ParameterWithDefault { range: 120..121, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 120..121, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 120..121, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -215,9 +261,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 132..135, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 132..135, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_no_arg_before_slash.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_no_arg_before_slash.py.snap index 0a3233a0be0a75..be7f6b1b352530 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_no_arg_before_slash.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_no_arg_before_slash.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_no_arg_before_slash.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..35, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..15, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..10, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..15, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 12..15, }, ), @@ -45,25 +52,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 16..34, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 20..23, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 23..29, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 27..28, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 27..28, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 27..28, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -78,9 +93,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..34, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 31..34, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_non_default_after_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_non_default_after_default.py.snap index b8ce121d93bcc5..4e01047ebb8768 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_non_default_after_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_non_default_after_default.py.snap @@ -1,42 +1,51 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_non_default_after_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..30, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..29, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..24, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..12, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..12, value: Int( 10, @@ -47,11 +56,14 @@ Module( }, ParameterWithDefault { range: 14..15, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 14..15, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -59,15 +71,19 @@ Module( }, ParameterWithDefault { range: 17..23, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 17..23, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 17..18, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..23, id: Name("int"), ctx: Load, @@ -86,9 +102,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 26..29, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_after_slash.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_after_slash.py.snap index e1bb0c58b95bdf..c2db1084e77804 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_after_slash.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_after_slash.py.snap @@ -1,35 +1,42 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_star_after_slash.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..105, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..19, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..14, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 8..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 9..10, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,9 +48,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..19, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 16..19, }, ), @@ -54,24 +63,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 20..48, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 24..27, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 27..43, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 28..29, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 28..29, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 28..29, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -82,9 +99,11 @@ Module( vararg: Some( Parameter { range: 31..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 32..36, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -92,11 +111,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 38..39, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 38..39, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 38..39, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -109,9 +131,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 45..48, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 45..48, }, ), @@ -122,24 +146,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 49..73, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 53..56, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 56..68, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 57..58, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 57..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 57..58, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -151,11 +183,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 66..67, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 66..67, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 66..67, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -168,9 +203,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..73, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 70..73, }, ), @@ -181,24 +218,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 74..104, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 78..81, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 81..99, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 82..83, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 82..83, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 82..83, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -210,11 +255,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 88..89, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 88..89, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 88..89, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -222,11 +270,14 @@ Module( }, ParameterWithDefault { range: 91..92, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 91..92, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 91..92, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -234,11 +285,14 @@ Module( }, ParameterWithDefault { range: 97..98, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 97..98, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 97..98, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -251,9 +305,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 101..104, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 101..104, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_separator_after_star_param.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_separator_after_star_param.py.snap index 0521d51896083d..aa5149155a8f36 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_separator_after_star_param.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_star_separator_after_star_param.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_star_separator_after_star_param.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..61, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..23, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -40,9 +48,11 @@ Module( vararg: Some( Parameter { range: 11..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 12..16, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -50,11 +60,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 21..22, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 21..22, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 21..22, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -67,9 +80,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -80,25 +95,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 29..60, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 33..36, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 36..55, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 37..38, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 37..38, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 37..38, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -108,9 +131,11 @@ Module( vararg: Some( Parameter { range: 40..45, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 41..45, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -118,11 +143,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 47..48, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 47..48, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 47..48, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -130,11 +158,14 @@ Module( }, ParameterWithDefault { range: 50..51, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 50..51, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 50..51, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -147,9 +178,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap index 9bde14d96327e6..f56f7d96abdc1e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_var_keyword_with_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..36, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..20, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -42,9 +50,11 @@ Module( kwarg: Some( Parameter { range: 11..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 13..19, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -54,20 +64,24 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..36, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 20..36, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, value: StringLiteralValue { inner: Single( StringLiteral { range: 21..24, + node_index: AtomicNodeIndex(..), value: "b", flags: StringLiteralFlags { quote_style: Single, @@ -82,6 +96,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..27, value: Int( 1, @@ -93,11 +108,13 @@ Module( key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, value: StringLiteralValue { inner: Single( StringLiteral { range: 29..32, + node_index: AtomicNodeIndex(..), value: "c", flags: StringLiteralFlags { quote_style: Single, @@ -112,6 +129,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 34..35, value: Int( 2, @@ -129,9 +147,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..42, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 39..42, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_positional_with_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_positional_with_default.py.snap index cafb4857d2a910..5ca631e7d1f503 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_positional_with_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_positional_with_default.py.snap @@ -1,36 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/params_var_positional_with_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..30, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..23, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..9, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -40,9 +48,11 @@ Module( vararg: Some( Parameter { range: 11..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 12..16, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -54,13 +64,16 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..23, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 17..23, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 1, @@ -69,6 +82,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 2, @@ -87,9 +101,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 26..29, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_context_manager_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_context_manager_py38.py.snap index 0cc6748cbb185c..ca32f57b183130 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_context_manager_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_context_manager_py38.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/parenthesized_context ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..126, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 43..73, is_async: false, items: [ WithItem { range: 49..57, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..52, id: Name("foo"), ctx: Load, @@ -26,6 +30,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Store, @@ -35,8 +40,10 @@ Module( }, WithItem { range: 59..67, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..62, id: Name("bar"), ctx: Load, @@ -45,6 +52,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("y"), ctx: Store, @@ -56,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..73, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 70..73, }, ), @@ -69,13 +79,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 74..99, is_async: false, items: [ WithItem { range: 80..83, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..83, id: Name("foo"), ctx: Load, @@ -85,8 +98,10 @@ Module( }, WithItem { range: 85..93, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..88, id: Name("bar"), ctx: Load, @@ -95,6 +110,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("y"), ctx: Store, @@ -106,9 +122,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), @@ -119,13 +137,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 100..125, is_async: false, items: [ WithItem { range: 106..114, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..109, id: Name("foo"), ctx: Load, @@ -134,6 +155,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("x"), ctx: Store, @@ -143,8 +165,10 @@ Module( }, WithItem { range: 116..119, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..119, id: Name("bar"), ctx: Load, @@ -156,9 +180,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..125, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 122..125, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_kwarg_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_kwarg_py38.py.snap index 4b55ae1a8a6ffb..90fb2c97daf104 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_kwarg_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@parenthesized_kwarg_py38.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/parenthesized_kwarg_p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..77, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..51, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 43..51, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("f"), ctx: Load, @@ -24,18 +28,22 @@ Module( ), arguments: Arguments { range: 44..51, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 45..50, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 46..47, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 1, @@ -51,12 +59,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..62, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 52..62, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("f"), ctx: Load, @@ -64,18 +75,22 @@ Module( ), arguments: Arguments { range: 53..62, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 54..61, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 55..56, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 60..61, value: Int( 1, @@ -91,12 +106,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..76, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 63..76, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("f"), ctx: Load, @@ -104,18 +122,22 @@ Module( ), arguments: Arguments { range: 64..76, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 66..75, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 68..69, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 74..75, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap index 34308fc5976051..78be042c66925e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap @@ -7,34 +7,42 @@ input_file: crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..549, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..74, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 44..74, value: FStringValue { inner: Single( FString( FString { range: 44..74, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 46..58, + node_index: AtomicNodeIndex(..), value: "Magic wand: ", }, ), Interpolation( InterpolatedElement { range: 58..73, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 60..71, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("bag"), ctx: Load, @@ -42,11 +50,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 64..70, value: StringLiteralValue { inner: Single( StringLiteral { range: 64..70, + node_index: AtomicNodeIndex(..), value: "wand", flags: StringLiteralFlags { quote_style: Single, @@ -82,32 +92,40 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..112, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 95..112, value: FStringValue { inner: Single( FString( FString { range: 95..112, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 97..111, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 98..110, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 98..107, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 98..102, value: StringLiteralValue { inner: Single( StringLiteral { range: 98..102, + node_index: AtomicNodeIndex(..), value: "\n", flags: StringLiteralFlags { quote_style: Single, @@ -122,15 +140,18 @@ Module( attr: Identifier { id: Name("join"), range: 103..107, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 107..110, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("a"), ctx: Load, @@ -162,30 +183,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..220, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 148..220, value: FStringValue { inner: Single( FString( FString { range: 148..220, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 152..169, + node_index: AtomicNodeIndex(..), value: "A complex trick: ", }, ), Interpolation( InterpolatedElement { range: 169..217, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 175..185, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 175..178, id: Name("bag"), ctx: Load, @@ -193,11 +221,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 179..184, value: StringLiteralValue { inner: Single( StringLiteral { range: 179..184, + node_index: AtomicNodeIndex(..), value: "bag", flags: StringLiteralFlags { quote_style: Single, @@ -233,84 +263,105 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 221..254, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 221..254, value: FStringValue { inner: Single( FString( FString { range: 221..254, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 223..253, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 224..252, value: FStringValue { inner: Single( FString( FString { range: 224..252, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 226..251, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 227..250, value: FStringValue { inner: Single( FString( FString { range: 227..250, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 229..249, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 230..248, value: FStringValue { inner: Single( FString( FString { range: 230..248, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 232..247, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 233..246, value: FStringValue { inner: Single( FString( FString { range: 233..246, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 235..245, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 236..244, value: FStringValue { inner: Single( FString( FString { range: 236..244, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 238..243, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 239..242, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..240, value: Int( 1, @@ -320,6 +371,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 241..242, value: Int( 1, @@ -434,38 +486,47 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 276..310, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 276..310, value: FStringValue { inner: Single( FString( FString { range: 276..310, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 278..303, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 279..302, value: FStringValue { inner: Single( FString( FString { range: 279..302, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 283..293, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 284..292, value: StringLiteralValue { inner: Single( StringLiteral { range: 284..292, + node_index: AtomicNodeIndex(..), value: "nested", flags: StringLiteralFlags { quote_style: Double, @@ -485,6 +546,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 293..299, + node_index: AtomicNodeIndex(..), value: " inner", }, ), @@ -508,6 +570,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 303..309, + node_index: AtomicNodeIndex(..), value: " outer", }, ), @@ -527,27 +590,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 336..359, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 336..359, value: FStringValue { inner: Single( FString( FString { range: 336..359, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 338..343, + node_index: AtomicNodeIndex(..), value: "test ", }, ), Interpolation( InterpolatedElement { range: 343..353, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 344..345, id: Name("a"), ctx: Load, @@ -561,6 +630,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 353..358, + node_index: AtomicNodeIndex(..), value: " more", }, ), @@ -580,33 +650,41 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 403..422, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 403..422, value: FStringValue { inner: Single( FString( FString { range: 403..422, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 407..419, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 408..418, value: FStringValue { inner: Single( FString( FString { range: 408..418, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 412..415, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 413..414, id: Name("x"), ctx: Load, @@ -650,32 +728,40 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 468..502, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 468..502, value: FStringValue { inner: Single( FString( FString { range: 468..502, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 470..501, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 471..500, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 471..480, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 471..475, value: StringLiteralValue { inner: Single( StringLiteral { range: 471..475, + node_index: AtomicNodeIndex(..), value: "\n", flags: StringLiteralFlags { quote_style: Single, @@ -690,24 +776,29 @@ Module( attr: Identifier { id: Name("join"), range: 476..480, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 480..500, + node_index: AtomicNodeIndex(..), args: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 481..499, elts: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 482..486, value: StringLiteralValue { inner: Single( StringLiteral { range: 482..486, + node_index: AtomicNodeIndex(..), value: "\t", flags: StringLiteralFlags { quote_style: Single, @@ -721,11 +812,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 488..492, value: StringLiteralValue { inner: Single( StringLiteral { range: 488..492, + node_index: AtomicNodeIndex(..), value: "\u{b}", flags: StringLiteralFlags { quote_style: Single, @@ -739,11 +832,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 494..498, value: StringLiteralValue { inner: Single( StringLiteral { range: 494..498, + node_index: AtomicNodeIndex(..), value: "\r", flags: StringLiteralFlags { quote_style: Single, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pos_only_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pos_only_py37.py.snap index adb8b30bc14dd1..6e1bdfaf6e122f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pos_only_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pos_only_py37.py.snap @@ -7,28 +7,37 @@ input_file: crates/ruff_python_parser/resources/inline/err/pos_only_py37.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..136, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 43..61, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 47..50, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 50..56, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 51..52, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 51..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 51..52, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -44,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -57,24 +68,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 62..86, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 66..69, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 69..81, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 70..71, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 70..71, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 70..71, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -84,11 +103,14 @@ Module( args: [ ParameterWithDefault { range: 76..77, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 76..77, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 76..77, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -103,9 +125,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..86, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 83..86, }, ), @@ -116,24 +140,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 87..115, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 91..94, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 94..110, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 95..96, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 95..96, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 95..96, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -144,9 +176,11 @@ Module( vararg: Some( Parameter { range: 98..103, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 99..103, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -154,11 +188,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 108..109, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 108..109, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 108..109, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -171,9 +208,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 112..115, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 112..115, }, ), @@ -184,25 +223,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 116..135, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 120..123, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 123..130, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 124..125, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 124..125, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 124..125, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -217,9 +264,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 132..135, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 132..135, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_from_without_exc.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_from_without_exc.py.snap index bfd31fbf6dc4cc..d6a06bfe2f1e5b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_from_without_exc.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_from_without_exc.py.snap @@ -7,15 +7,18 @@ input_file: crates/ruff_python_parser/resources/inline/err/raise_stmt_from_witho ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 0..14, exc: None, cause: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..14, id: Name("exc"), ctx: Load, @@ -26,11 +29,13 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 15..30, exc: None, cause: Some( NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 26..30, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_cause.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_cause.py.snap index 5d6d3eef90d22d..969eb0b526af8d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_cause.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_cause.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/raise_stmt_invalid_cause.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 0..15, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -25,9 +27,11 @@ Module( cause: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("y"), ctx: Load, @@ -41,10 +45,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 16..36, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -54,10 +60,12 @@ Module( cause: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 29..36, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("y"), ctx: Load, @@ -71,10 +79,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 37..51, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("x"), ctx: Load, @@ -84,6 +94,7 @@ Module( cause: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..51, id: Name("y"), ctx: Load, @@ -94,9 +105,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..56, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_exc.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_exc.py.snap index 3bb6f030740368..fc1d86ea861a4a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_exc.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_invalid_exc.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/raise_stmt_invalid_exc.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 0..8, exc: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -33,14 +36,17 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 9..22, exc: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 15..22, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("x"), ctx: Load, @@ -55,10 +61,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 23..30, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -70,9 +78,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..35, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 34..35, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_cause.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_cause.py.snap index c000ffe4707758..d25483b1bc1e22 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_cause.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_cause.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/raise_stmt_unparenthesized_tuple_cause.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..34, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 0..15, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -25,10 +27,12 @@ Module( cause: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 13..15, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("y"), ctx: Load, @@ -44,10 +48,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 16..33, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -57,10 +63,12 @@ Module( cause: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 29..33, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("y"), ctx: Load, @@ -68,6 +76,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_exc.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_exc.py.snap index a5bd41c66eb8c4..36f4db810c8648 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_exc.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@raise_stmt_unparenthesized_tuple_exc.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/raise_stmt_unparenthesized_tuple_exc.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..38, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 0..8, exc: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 6..8, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -36,14 +39,17 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 9..19, exc: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 15..19, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("x"), ctx: Load, @@ -51,6 +57,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("y"), ctx: Load, @@ -67,14 +74,17 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 20..37, exc: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 26..30, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("x"), ctx: Load, @@ -82,6 +92,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("y"), ctx: Load, @@ -96,6 +107,7 @@ Module( cause: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap index 9db9481b224dae..aec52e3b459e72 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lex_logical_token.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..979, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 48..59, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 51..59, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..55, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 55..59, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..59, id: Name("foo"), ctx: Load, @@ -43,16 +49,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..79, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 64..67, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 67..69, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -63,6 +74,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 75..79, }, ), @@ -71,12 +83,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 113..152, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 116..124, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..120, id: Name("call"), ctx: Load, @@ -84,9 +99,11 @@ Module( ), arguments: Arguments { range: 120..124, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..124, id: Name("foo"), ctx: Load, @@ -100,16 +117,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 129..152, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 133..136, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 136..138, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -120,6 +142,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 148..152, }, ), @@ -132,12 +155,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 228..269, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 231..239, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 231..235, id: Name("call"), ctx: Load, @@ -145,9 +171,11 @@ Module( ), arguments: Arguments { range: 235..239, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..239, id: Name("foo"), ctx: Load, @@ -161,16 +189,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 246..269, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 250..253, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 253..255, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -181,6 +214,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 265..269, }, ), @@ -193,12 +227,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 344..392, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 347..355, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 347..351, id: Name("call"), ctx: Load, @@ -206,9 +243,11 @@ Module( ), arguments: Arguments { range: 351..355, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 352..355, id: Name("foo"), ctx: Load, @@ -222,16 +261,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 369..392, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 373..376, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 376..378, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -242,6 +286,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 388..392, }, ), @@ -254,12 +299,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 453..499, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 456..472, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 456..460, id: Name("call"), ctx: Load, @@ -267,9 +315,11 @@ Module( ), arguments: Arguments { range: 460..472, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 461..464, id: Name("foo"), ctx: Load, @@ -277,10 +327,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 466..471, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 467..468, id: Name("a"), ctx: Load, @@ -288,6 +340,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 470..471, id: Name("b"), ctx: Load, @@ -305,16 +358,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 476..499, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 480..483, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 483..485, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -325,6 +383,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 495..499, }, ), @@ -337,12 +396,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 564..611, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 567..583, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 567..571, id: Name("call"), ctx: Load, @@ -350,9 +412,11 @@ Module( ), arguments: Arguments { range: 571..583, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 572..575, id: Name("foo"), ctx: Load, @@ -360,10 +424,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 577..582, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 578..579, id: Name("a"), ctx: Load, @@ -371,6 +437,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 581..582, id: Name("b"), ctx: Load, @@ -388,16 +455,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 588..611, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 592..595, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 595..597, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -408,6 +480,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 607..611, }, ), @@ -420,12 +493,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 772..824, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 775..796, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 775..779, id: Name("call"), ctx: Load, @@ -433,9 +509,11 @@ Module( ), arguments: Arguments { range: 779..796, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 780..783, id: Name("foo"), ctx: Load, @@ -443,10 +521,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 785..794, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 786..787, id: Name("a"), ctx: Load, @@ -454,6 +534,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 793..794, id: Name("b"), ctx: Load, @@ -471,16 +552,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 801..824, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 805..808, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 808..810, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -491,6 +577,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 820..824, }, ), @@ -503,12 +590,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 887..933, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 890..905, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 890..894, id: Name("call"), ctx: Load, @@ -516,27 +606,33 @@ Module( ), arguments: Arguments { range: 894..905, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 895..905, value: FStringValue { inner: Single( FString( FString { range: 895..905, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 897..903, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 903..905, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 904..905, id: Name("x"), ctx: Load, @@ -567,16 +663,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 910..933, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 914..917, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 917..919, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -587,6 +688,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 929..933, }, ), @@ -599,12 +701,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 936..956, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 939..956, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 939..943, id: Name("call"), ctx: Load, @@ -612,15 +717,18 @@ Module( ), arguments: Arguments { range: 943..956, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 944..951, value: FStringValue { inner: Single( FString( FString { range: 944..951, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, @@ -644,16 +752,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 956..979, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 960..963, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 963..965, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -664,6 +777,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 975..979, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_mac_eol.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_mac_eol.py.snap index e9879e5ba33052..065645a7ac3f20 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_mac_eol.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_mac_eol.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..46, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..46, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3..19, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 7..19, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..11, id: Name("foo"), ctx: Load, @@ -34,10 +40,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 13..18, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("a"), ctx: Load, @@ -45,6 +53,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("b"), ctx: Load, @@ -62,16 +71,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..46, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 27..30, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 30..32, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -82,6 +96,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 42..46, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_windows_eol.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_windows_eol.py.snap index 31bb3a87f2044e..37943705bb1062 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_windows_eol.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lex_logical_token_windows_eol.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lex_logical_token_win ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..50, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..48, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3..20, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..7, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 7..20, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..11, id: Name("foo"), ctx: Load, @@ -34,10 +40,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 13..18, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("a"), ctx: Load, @@ -45,6 +53,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("b"), ctx: Load, @@ -62,16 +71,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 24..48, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 28..31, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 31..33, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -82,6 +96,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 44..48, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap index bb98cde3115c02..708cad03c7ecd3 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__fstring_format_spec_1.py.snap @@ -7,36 +7,44 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/fstring_format ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..298, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 162..192, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 162..192, value: FStringValue { inner: Single( FString( FString { range: 162..192, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 164..171, + node_index: AtomicNodeIndex(..), value: "middle ", }, ), Interpolation( InterpolatedElement { range: 171..191, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 172..180, value: StringLiteralValue { inner: Single( StringLiteral { range: 172..180, + node_index: AtomicNodeIndex(..), value: "string", flags: StringLiteralFlags { quote_style: Single, @@ -53,10 +61,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 181..191, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 181..191, + node_index: AtomicNodeIndex(..), value: " ", }, ), @@ -81,9 +91,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 192..198, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..198, id: Name("format"), ctx: Load, @@ -93,9 +105,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 199..203, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..203, id: Name("spec"), ctx: Load, @@ -105,32 +119,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 207..228, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 207..228, value: FStringValue { inner: Single( FString( FString { range: 207..228, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 209..216, + node_index: AtomicNodeIndex(..), value: "middle ", }, ), Interpolation( InterpolatedElement { range: 216..228, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 217..225, value: StringLiteralValue { inner: Single( StringLiteral { range: 217..225, + node_index: AtomicNodeIndex(..), value: "string", flags: StringLiteralFlags { quote_style: Single, @@ -147,10 +168,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 226..228, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 226..228, + node_index: AtomicNodeIndex(..), value: "\\", }, ), @@ -175,14 +198,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 237..250, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 237..250, value: StringLiteralValue { inner: Single( StringLiteral { range: 237..250, + node_index: AtomicNodeIndex(..), value: "format spec", flags: StringLiteralFlags { quote_style: Single, @@ -198,32 +224,39 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 253..285, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 253..285, value: FStringValue { inner: Single( FString( FString { range: 253..285, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 255..262, + node_index: AtomicNodeIndex(..), value: "middle ", }, ), Interpolation( InterpolatedElement { range: 262..284, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 263..271, value: StringLiteralValue { inner: Single( StringLiteral { range: 263..271, + node_index: AtomicNodeIndex(..), value: "string", flags: StringLiteralFlags { quote_style: Single, @@ -240,10 +273,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 272..284, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 272..284, + node_index: AtomicNodeIndex(..), value: "\\ ", }, ), @@ -268,9 +303,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 285..291, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 285..291, id: Name("format"), ctx: Load, @@ -280,9 +317,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 292..296, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 292..296, id: Name("spec"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_1.py.snap index abeaef0f131ae2..ae9e10591970e4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_1.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/line_continuat ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..13, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..13, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("a"), ctx: Load, @@ -34,6 +40,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("b"), ctx: Load, @@ -48,16 +55,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 16..35, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 20..23, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 23..25, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -68,6 +80,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 31..35, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_windows_eol.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_windows_eol.py.snap index 935f436e821bd2..97fbdf8b65be5e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_windows_eol.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__line_continuation_windows_eol.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/line_continuat ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..46, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..10, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("call"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), arguments: Arguments { range: 4..10, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("a"), ctx: Load, @@ -34,6 +40,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("b"), ctx: Load, @@ -48,16 +55,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 26..46, is_async: false, decorator_list: [], name: Identifier { id: Name("bar"), range: 30..33, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 33..35, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -68,6 +80,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 42..46, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap index efb2fecf820ca3..4aa6298cabfae1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_1.py.snap @@ -7,31 +7,38 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/triple_quoted_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..198, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 166..178, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 166..178, value: FStringValue { inner: Single( FString( FString { range: 166..178, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 170..176, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 176..178, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 177..178, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap index c8431a27c9d175..6a553051a5cb65 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_2.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/triple_quoted_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..183, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 167..183, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 167..183, value: FStringValue { inner: Single( FString( FString { range: 167..183, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 171..180, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 172..175, id: Name("foo"), ctx: Load, @@ -36,10 +42,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 176..180, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 176..180, + node_index: AtomicNodeIndex(..), value: ".3f\n", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap index e59c846c0f85b5..2db1a776b19ccd 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@re_lexing__triple_quoted_fstring_3.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/invalid/re_lexing/triple_quoted_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..262, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 231..262, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 234..253, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..238, id: Name("call"), ctx: Load, @@ -24,21 +28,26 @@ Module( ), arguments: Arguments { range: 238..253, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 239..253, value: FStringValue { inner: Single( FString( FString { range: 239..253, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 243..250, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 244..245, id: Name("x"), ctx: Load, @@ -49,10 +58,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 246..250, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 246..250, + node_index: AtomicNodeIndex(..), value: ".3f\n", }, ), @@ -81,6 +92,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 258..262, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@rebound_comprehension_variable.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@rebound_comprehension_variable.py.snap index d4093c88394d33..e14ea1a6543301 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@rebound_comprehension_variable.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@rebound_comprehension_variable.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/inline/err/rebound_comprehension ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..342, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..28, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 0..28, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2..8, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("a"), ctx: Store, @@ -27,6 +32,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 0, @@ -38,8 +44,10 @@ Module( generators: [ Comprehension { range: 10..27, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("a"), ctx: Store, @@ -47,9 +55,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 19..27, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..24, id: Name("range"), ctx: Load, @@ -57,9 +67,11 @@ Module( ), arguments: Arguments { range: 24..27, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 0, @@ -81,15 +93,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..57, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 29..57, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 31..37, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..32, id: Name("a"), ctx: Store, @@ -97,6 +113,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..37, value: Int( 0, @@ -108,8 +125,10 @@ Module( generators: [ Comprehension { range: 39..56, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("a"), ctx: Store, @@ -117,9 +136,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 48..56, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..53, id: Name("range"), ctx: Load, @@ -127,9 +148,11 @@ Module( ), arguments: Arguments { range: 53..56, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 0, @@ -151,15 +174,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..91, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 58..91, key: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 60..66, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("a"), ctx: Store, @@ -167,6 +194,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 0, @@ -177,6 +205,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..72, id: Name("val"), ctx: Load, @@ -185,8 +214,10 @@ Module( generators: [ Comprehension { range: 73..90, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("a"), ctx: Store, @@ -194,9 +225,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 82..90, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..87, id: Name("range"), ctx: Load, @@ -204,9 +237,11 @@ Module( ), arguments: Arguments { range: 87..90, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 0, @@ -228,12 +263,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..125, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 92..125, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..96, id: Name("key"), ctx: Load, @@ -241,9 +279,11 @@ Module( ), value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 99..105, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("a"), ctx: Store, @@ -251,6 +291,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..105, value: Int( 0, @@ -262,8 +303,10 @@ Module( generators: [ Comprehension { range: 107..124, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("a"), ctx: Store, @@ -271,9 +314,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 116..124, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..121, id: Name("range"), ctx: Load, @@ -281,9 +326,11 @@ Module( ), arguments: Arguments { range: 121..124, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 0, @@ -305,15 +352,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 126..154, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 126..154, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 128..134, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("a"), ctx: Store, @@ -321,6 +372,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 133..134, value: Int( 0, @@ -332,8 +384,10 @@ Module( generators: [ Comprehension { range: 136..153, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("a"), ctx: Store, @@ -341,9 +395,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 145..153, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..150, id: Name("range"), ctx: Load, @@ -351,9 +407,11 @@ Module( ), arguments: Arguments { range: 150..153, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 151..152, value: Int( 0, @@ -376,19 +434,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..185, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 155..185, elt: List( ExprList { + node_index: AtomicNodeIndex(..), range: 156..166, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 158..164, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 158..159, id: Name("a"), ctx: Store, @@ -396,6 +459,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 163..164, value: Int( 0, @@ -411,8 +475,10 @@ Module( generators: [ Comprehension { range: 167..184, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("a"), ctx: Store, @@ -420,9 +486,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 176..184, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..181, id: Name("range"), ctx: Load, @@ -430,9 +498,11 @@ Module( ), arguments: Arguments { range: 181..184, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 182..183, value: Int( 0, @@ -454,15 +524,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 186..233, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 186..233, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 188..194, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 188..189, id: Name("a"), ctx: Store, @@ -470,6 +544,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 193..194, value: Int( 0, @@ -481,8 +556,10 @@ Module( generators: [ Comprehension { range: 196..214, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 200..201, id: Name("b"), ctx: Store, @@ -490,9 +567,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 205..214, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 205..210, id: Name("range"), ctx: Load, @@ -500,9 +579,11 @@ Module( ), arguments: Arguments { range: 211..214, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 212..213, value: Int( 0, @@ -519,8 +600,10 @@ Module( }, Comprehension { range: 215..232, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 219..220, id: Name("a"), ctx: Store, @@ -528,9 +611,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 224..232, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 224..229, id: Name("range"), ctx: Load, @@ -538,9 +623,11 @@ Module( ), arguments: Arguments { range: 229..232, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 230..231, value: Int( 0, @@ -562,15 +649,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 234..281, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 234..281, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 236..242, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("a"), ctx: Store, @@ -578,6 +669,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 241..242, value: Int( 0, @@ -589,8 +681,10 @@ Module( generators: [ Comprehension { range: 244..262, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 248..249, id: Name("a"), ctx: Store, @@ -598,9 +692,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 253..262, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 253..258, id: Name("range"), ctx: Load, @@ -608,9 +704,11 @@ Module( ), arguments: Arguments { range: 259..262, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 260..261, value: Int( 0, @@ -627,8 +725,10 @@ Module( }, Comprehension { range: 263..280, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 267..268, id: Name("b"), ctx: Store, @@ -636,9 +736,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 272..280, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 272..277, id: Name("range"), ctx: Load, @@ -646,9 +748,11 @@ Module( ), arguments: Arguments { range: 277..280, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 278..279, value: Int( 0, @@ -670,19 +774,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 282..341, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 282..341, elt: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 283..303, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 285..291, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 285..286, id: Name("a"), ctx: Store, @@ -690,6 +799,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 290..291, value: Int( 0, @@ -700,9 +810,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 295..301, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 295..296, id: Name("b"), ctx: Store, @@ -710,6 +822,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 300..301, value: Int( 1, @@ -726,8 +839,10 @@ Module( generators: [ Comprehension { range: 304..322, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 308..309, id: Name("a"), ctx: Store, @@ -735,9 +850,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 313..322, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 313..318, id: Name("range"), ctx: Load, @@ -745,9 +862,11 @@ Module( ), arguments: Arguments { range: 319..322, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 320..321, value: Int( 0, @@ -764,8 +883,10 @@ Module( }, Comprehension { range: 323..340, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..328, id: Name("b"), ctx: Store, @@ -773,9 +894,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 332..340, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 332..337, id: Name("range"), ctx: Load, @@ -783,9 +906,11 @@ Module( ), arguments: Arguments { range: 337..340, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 338..339, value: Int( 0, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@return_stmt_invalid_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@return_stmt_invalid_expr.py.snap index a6518b0d868c9b..b93edc683b61d1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@return_stmt_invalid_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@return_stmt_invalid_expr.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/return_stmt_invalid_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..74, body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 0..8, value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 7..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..8, id: Name(""), ctx: Invalid, @@ -31,14 +35,17 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 9..23, value: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 16..23, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -52,13 +59,16 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 24..43, value: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 31..43, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("x"), ctx: Load, @@ -71,10 +81,12 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 44..52, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("x"), ctx: Load, @@ -85,9 +97,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..57, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 56..57, value: Int( 1, @@ -98,18 +112,22 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 58..73, value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 65..73, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 66..73, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("x"), ctx: Load, @@ -117,6 +135,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line.py.snap index b1abaaa51346db..208ce1f1b03705 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/simple_and_compound_stmt_on_same_line.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..17, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..1, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Load, @@ -24,9 +26,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 3..16, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("b"), ctx: Load, @@ -35,14 +39,17 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..16, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line_in_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line_in_block.py.snap index 2973bbb41cc84e..5c6e1ab082a410 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line_in_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_and_compound_stmt_on_same_line_in_block.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/simple_and_compound_stmt_on_same_line_in_block.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..59, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..13, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -22,6 +24,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -31,9 +34,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 14..28, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 17..22, value: false, }, @@ -41,6 +46,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 24..28, }, ), @@ -50,9 +56,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 29..42, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 32..36, value: true, }, @@ -60,6 +68,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 38..42, }, ), @@ -69,9 +78,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 44..58, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 47..52, value: false, }, @@ -79,6 +90,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 54..58, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line.py.snap index 9bfaeb67a2f094..3e2eb8febcd1b5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/simple_stmts_on_same_line.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..1, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Load, @@ -24,9 +26,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("b"), ctx: Load, @@ -36,12 +40,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..9, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4..9, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("a"), ctx: Load, @@ -50,6 +57,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("b"), ctx: Load, @@ -61,12 +69,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..15, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 10..15, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("c"), ctx: Load, @@ -75,6 +86,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("d"), ctx: Load, @@ -86,26 +98,31 @@ Module( ), Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 16..21, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 23..31, }, ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 32..36, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 38..46, }, ), Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 47..52, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line_in_block.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line_in_block.py.snap index 9f66a287490268..0e424616ad6516 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line_in_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@simple_stmts_on_same_line_in_block.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/simple_stmts_on_same_line_in_block.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..46, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..45, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -22,26 +24,31 @@ Module( body: [ Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 9..14, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 16..24, }, ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 25..29, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 31..39, }, ), Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 40..45, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_for.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_for.py.snap index a5699bbc2b90b4..75ce9e03204f3c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_for.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_for.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/single_star_for.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..35, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..16, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("_"), ctx: Store, @@ -22,9 +25,11 @@ Module( ), iter: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 9..11, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Load, @@ -36,9 +41,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..16, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 13..16, }, ), @@ -50,13 +57,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 17..34, is_async: false, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 21..23, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Store, @@ -67,6 +77,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..29, id: Name("xs"), ctx: Load, @@ -75,9 +86,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..34, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 31..34, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_return.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_return.py.snap index 91feb561c855d2..8c08d4f66c828f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_return.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_return.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/single_star_return.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..19, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..18, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,13 +37,16 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 9..18, value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 16..18, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_yield.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_yield.py.snap index d89940beaa5254..273bb6dd53ccd9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_yield.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_star_yield.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/err/single_star_yield.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..18, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..17, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..17, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 9..17, value: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 15..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_starred_assignment_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_starred_assignment_target.py.snap index 406d8532da4c74..fd275c3a2ed14c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_starred_assignment_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@single_starred_assignment_target.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/single_starred_assign ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..9, targets: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 0..2, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("a"), ctx: Store, @@ -29,10 +33,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5..9, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap index 0f06b4ecff29ed..2f547ff2723136 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/star_index_py310.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..293, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..55, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 44..55, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..47, id: Name("lst"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 48..54, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 48..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..54, id: Name("index"), ctx: Load, @@ -51,22 +58,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 72..112, decorator_list: [], name: Identifier { id: Name("Array"), range: 78..83, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 83..107, + node_index: AtomicNodeIndex(..), args: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 84..106, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..91, id: Name("Generic"), ctx: Load, @@ -74,10 +86,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 92..105, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..97, id: Name("DType"), ctx: Load, @@ -85,9 +99,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 99..105, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..105, id: Name("Shape"), ctx: Load, @@ -111,9 +127,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..112, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, }, ), @@ -124,12 +142,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..161, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 148..161, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..151, id: Name("lst"), ctx: Load, @@ -137,10 +158,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 152..160, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..153, id: Name("a"), ctx: Load, @@ -148,9 +171,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 155..157, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("b"), ctx: Load, @@ -161,6 +186,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..160, id: Name("c"), ctx: Load, @@ -178,12 +204,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..198, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 185..198, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..188, id: Name("lst"), ctx: Load, @@ -191,10 +220,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 189..197, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("a"), ctx: Load, @@ -202,6 +233,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..193, id: Name("b"), ctx: Load, @@ -209,9 +241,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 195..197, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("c"), ctx: Load, @@ -232,12 +266,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 222..233, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 222..233, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..225, id: Name("lst"), ctx: Load, @@ -245,13 +282,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 226..232, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 226..228, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 227..228, id: Name("a"), ctx: Load, @@ -262,9 +302,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 230..232, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 231..232, id: Name("b"), ctx: Load, @@ -285,12 +327,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 254..271, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 254..271, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 254..259, id: Name("array"), ctx: Load, @@ -298,14 +343,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 260..270, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 260..263, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 260..261, value: Int( 3, @@ -316,6 +364,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 262..263, value: Int( 5, @@ -328,9 +377,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 265..270, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..270, id: Name("idxs"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap index de033a8afe37bc..4f9cc982f6ee76 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/star_slices.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..19, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..18, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..18, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("array"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 6..17, lower: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..12, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..12, id: Name("start"), ctx: Load, @@ -43,9 +50,11 @@ Module( upper: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("end"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__function_type_parameters.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__function_type_parameters.py.snap index 0c547eb7b4826f..5853cdff0029ff 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__function_type_parameters.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__function_type_parameters.py.snap @@ -7,27 +7,33 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/function_type ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..988, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 796..824, is_async: false, decorator_list: [], name: Identifier { id: Name("keyword"), range: 800..807, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 807..817, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 808..809, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("A"), range: 808..809, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -36,9 +42,11 @@ Module( TypeVar( TypeParamTypeVar { range: 811..816, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("await"), range: 811..816, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -49,6 +57,9 @@ Module( ), parameters: Parameters { range: 817..819, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -59,9 +70,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 821..824, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 821..824, }, ), @@ -72,23 +85,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 826..862, is_async: false, decorator_list: [], name: Identifier { id: Name("not_a_type_param"), range: 830..846, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 846..855, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 847..848, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("A"), range: 847..848, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -97,9 +115,11 @@ Module( TypeVar( TypeParamTypeVar { range: 853..854, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("B"), range: 853..854, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -110,6 +130,9 @@ Module( ), parameters: Parameters { range: 855..857, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -120,9 +143,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 859..862, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 859..862, }, ), @@ -133,23 +158,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 864..896, is_async: false, decorator_list: [], name: Identifier { id: Name("multiple_commas"), range: 868..883, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 883..889, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 884..885, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("A"), range: 884..885, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -158,9 +188,11 @@ Module( TypeVar( TypeParamTypeVar { range: 887..888, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("B"), range: 887..888, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -171,6 +203,9 @@ Module( ), parameters: Parameters { range: 889..891, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -181,9 +216,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 893..896, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 893..896, }, ), @@ -194,23 +231,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 898..938, is_async: false, decorator_list: [], name: Identifier { id: Name("multiple_trailing_commas"), range: 902..926, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 926..931, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 927..928, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("A"), range: 927..928, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -221,6 +263,9 @@ Module( ), parameters: Parameters { range: 931..933, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -231,9 +276,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 935..938, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 935..938, }, ), @@ -244,23 +291,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 940..979, is_async: false, decorator_list: [], name: Identifier { id: Name("multiple_commas_and_recovery"), range: 944..972, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 972..976, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 973..974, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("A"), range: 973..974, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -271,6 +323,9 @@ Module( ), parameters: Parameters { range: 976..976, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -281,9 +336,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 976..979, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 976..979, value: Int( 100, @@ -297,9 +354,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 980..987, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 980..982, elts: [], ctx: Store, @@ -308,6 +367,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 984..987, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_closing_parentheses.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_closing_parentheses.py.snap index ea5649412f7d1a..5ca66592b10f35 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_closing_parentheses.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_closing_parentheses.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/if_extra_clos ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..110, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 90..97, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 93..97, value: true, }, @@ -24,6 +27,7 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 105..109, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_indent.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_indent.py.snap index 76a3ff101f677d..0a04441bbbf1b0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_indent.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__if_extra_indent.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/if_extra_inde ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..153, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 103..134, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 106..110, value: true, }, @@ -21,17 +24,21 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 116..120, }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 129..134, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 129..134, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("a"), ctx: Load, @@ -40,6 +47,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..134, id: Name("b"), ctx: Load, @@ -55,15 +63,18 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 140..144, }, ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 146..152, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("a"), ctx: Store, @@ -72,6 +83,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 150..152, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap index 7159d351f2c3ed..26643d505d71ae 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/invalid_assig ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..788, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 201..206, targets: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 5, @@ -24,6 +27,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 205..206, value: Int( 3, @@ -34,9 +38,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 208..214, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 208..209, value: Int( 5, @@ -46,6 +52,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..214, value: Int( 3, @@ -56,9 +63,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 216..228, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 217..218, value: Int( 5, @@ -67,6 +76,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 221..224, id: Name("int"), ctx: Load, @@ -75,6 +85,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 227..228, value: Int( 3, @@ -87,15 +98,18 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 303..314, targets: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 303..309, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 303..304, id: Name("x"), ctx: Load, @@ -103,6 +117,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 308..309, id: Name("y"), ctx: Load, @@ -114,6 +129,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 312..314, value: Int( 42, @@ -124,13 +140,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 315..328, targets: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 316..322, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 316..317, id: Name("x"), ctx: Store, @@ -138,6 +157,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 321..322, value: Int( 5, @@ -149,6 +169,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 326..328, value: Int( 42, @@ -159,13 +180,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 329..339, targets: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 329..334, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 329..330, id: Name("x"), ctx: Load, @@ -174,6 +198,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 333..334, id: Name("y"), ctx: Load, @@ -184,6 +209,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 337..339, value: Int( 42, @@ -194,14 +220,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 340..347, targets: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 340..342, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 341..342, id: Name("x"), ctx: Store, @@ -212,6 +241,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 345..347, value: Int( 42, @@ -222,23 +252,31 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 348..366, targets: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 349..360, parameters: Some( Parameters { range: 356..357, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 356..357, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 356..357, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("_"), range: 356..357, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -252,6 +290,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 359..360, value: Int( 1, @@ -263,6 +302,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 364..366, value: Int( 42, @@ -273,13 +313,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 367..385, targets: [ If( ExprIf { + node_index: AtomicNodeIndex(..), range: 367..380, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 372..373, id: Name("b"), ctx: Load, @@ -287,6 +330,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 367..368, id: Name("a"), ctx: Load, @@ -294,6 +338,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 379..380, id: Name("c"), ctx: Load, @@ -304,6 +349,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 383..385, value: Int( 42, @@ -314,21 +360,25 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 386..399, targets: [ Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 386..394, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 387..390, value: StringLiteralValue { inner: Single( StringLiteral { range: 387..390, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -343,6 +393,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 392..393, value: Int( 5, @@ -356,6 +407,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 397..399, value: Int( 42, @@ -366,14 +418,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 400..408, targets: [ Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 400..403, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 401..402, id: Name("a"), ctx: Load, @@ -385,6 +440,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 406..408, value: Int( 42, @@ -395,13 +451,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 409..429, targets: [ ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 409..424, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 410..411, id: Name("x"), ctx: Load, @@ -410,8 +469,10 @@ Module( generators: [ Comprehension { range: 412..423, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 416..417, id: Name("x"), ctx: Store, @@ -419,6 +480,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..423, id: Name("xs"), ctx: Load, @@ -433,6 +495,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 427..429, value: Int( 42, @@ -443,13 +506,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 430..450, targets: [ SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 430..445, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 431..432, id: Name("x"), ctx: Load, @@ -458,8 +524,10 @@ Module( generators: [ Comprehension { range: 433..444, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 437..438, id: Name("x"), ctx: Store, @@ -467,6 +535,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 442..444, id: Name("xs"), ctx: Load, @@ -481,6 +550,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 448..450, value: Int( 42, @@ -491,13 +561,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 451..478, targets: [ DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 451..473, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 452..453, id: Name("x"), ctx: Load, @@ -505,9 +578,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 455..460, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 455..456, id: Name("x"), ctx: Load, @@ -516,6 +591,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 459..460, value: Int( 2, @@ -527,8 +603,10 @@ Module( generators: [ Comprehension { range: 461..472, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 465..466, id: Name("x"), ctx: Store, @@ -536,6 +614,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 470..472, id: Name("xs"), ctx: Load, @@ -550,6 +629,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 476..478, value: Int( 42, @@ -560,13 +640,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 479..499, targets: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 479..494, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 480..481, id: Name("x"), ctx: Load, @@ -575,8 +658,10 @@ Module( generators: [ Comprehension { range: 482..493, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 486..487, id: Name("x"), ctx: Store, @@ -584,6 +669,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 491..493, id: Name("xs"), ctx: Load, @@ -599,6 +685,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 497..499, value: Int( 42, @@ -609,13 +696,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 500..512, targets: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 500..507, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 506..507, id: Name("x"), ctx: Load, @@ -626,6 +716,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 510..512, value: Int( 42, @@ -636,14 +727,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 513..527, targets: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 514..521, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 520..521, id: Name("x"), ctx: Load, @@ -655,6 +749,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 525..527, value: Int( 42, @@ -665,13 +760,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 528..548, targets: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 529..542, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 540..542, id: Name("xs"), ctx: Load, @@ -682,6 +780,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 546..548, value: Int( 42, @@ -692,13 +791,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 549..563, targets: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 549..558, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 549..550, id: Name("a"), ctx: Load, @@ -711,6 +813,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 553..554, id: Name("b"), ctx: Load, @@ -718,6 +821,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 557..558, id: Name("c"), ctx: Load, @@ -729,6 +833,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 561..563, value: Int( 42, @@ -739,13 +844,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 564..574, targets: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 564..569, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 564..567, id: Name("foo"), ctx: Load, @@ -753,6 +861,7 @@ Module( ), arguments: Arguments { range: 567..569, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -761,6 +870,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 572..574, value: Int( 42, @@ -771,22 +881,27 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 576..590, targets: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 576..585, value: FStringValue { inner: Single( FString( FString { range: 576..585, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 578..584, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 579..583, id: Name("quux"), ctx: Load, @@ -812,6 +927,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 588..590, value: Int( 42, @@ -822,22 +938,27 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 591..614, targets: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 591..609, value: FStringValue { inner: Single( FString( FString { range: 591..609, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 593..598, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 594..597, id: Name("foo"), ctx: Load, @@ -851,14 +972,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 598..603, + node_index: AtomicNodeIndex(..), value: " and ", }, ), Interpolation( InterpolatedElement { range: 603..608, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 604..607, id: Name("bar"), ctx: Load, @@ -884,6 +1008,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 612..614, value: Int( 42, @@ -894,15 +1019,18 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 616..626, targets: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 616..621, value: StringLiteralValue { inner: Single( StringLiteral { range: 616..621, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -917,6 +1045,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 624..626, value: Int( 42, @@ -927,15 +1056,18 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 627..638, targets: [ BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 627..633, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 627..633, + node_index: AtomicNodeIndex(..), value: [ 102, 111, @@ -954,6 +1086,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 636..638, value: Int( 42, @@ -964,10 +1097,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 639..647, targets: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 639..642, value: Int( 123, @@ -977,6 +1112,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 645..647, value: Int( 42, @@ -987,10 +1123,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 648..657, targets: [ BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 648..652, value: true, }, @@ -998,6 +1136,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 655..657, value: Int( 42, @@ -1008,16 +1147,19 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 658..667, targets: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 658..662, }, ), ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 665..667, value: Int( 42, @@ -1028,16 +1170,19 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 668..676, targets: [ EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 668..671, }, ), ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 674..676, value: Int( 42, @@ -1048,16 +1193,20 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 677..688, targets: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 677..683, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 678..683, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 678..681, id: Name("foo"), ctx: Load, @@ -1065,6 +1214,7 @@ Module( ), arguments: Arguments { range: 681..683, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1076,6 +1226,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 686..688, value: Int( 42, @@ -1086,14 +1237,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 689..717, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 689..702, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 690..691, id: Name("x"), ctx: Store, @@ -1101,9 +1255,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 693..698, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 693..696, id: Name("foo"), ctx: Load, @@ -1111,6 +1267,7 @@ Module( ), arguments: Arguments { range: 696..698, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1118,6 +1275,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 700..701, id: Name("y"), ctx: Store, @@ -1130,10 +1288,12 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 705..717, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 706..708, value: Int( 42, @@ -1142,6 +1302,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 710..712, value: Int( 42, @@ -1150,6 +1311,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 714..716, value: Int( 42, @@ -1164,18 +1326,22 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 718..758, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 718..737, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 719..725, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 720..721, id: Name("a"), ctx: Store, @@ -1183,6 +1349,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 723..724, id: Name("b"), ctx: Store, @@ -1194,14 +1361,17 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 727..733, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 728..732, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 729..731, value: Int( 42, @@ -1218,6 +1388,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 735..736, id: Name("d"), ctx: Store, @@ -1230,14 +1401,17 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 740..758, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 741..747, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 742..743, value: Int( 1, @@ -1246,6 +1420,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 745..746, value: Int( 2, @@ -1258,14 +1433,17 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 749..754, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 750..753, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 751..752, value: Int( 3, @@ -1282,6 +1460,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 756..757, value: Int( 4, @@ -1296,14 +1475,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 759..787, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 759..772, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 760..761, id: Name("x"), ctx: Store, @@ -1311,9 +1493,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 763..768, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 763..766, id: Name("foo"), ctx: Load, @@ -1321,6 +1505,7 @@ Module( ), arguments: Arguments { range: 766..768, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1328,6 +1513,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 770..771, id: Name("y"), ctx: Store, @@ -1341,10 +1527,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 775..787, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 776..778, value: Int( 42, @@ -1353,6 +1541,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 780..782, value: Int( 42, @@ -1361,6 +1550,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 784..786, value: Int( 42, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap index ec4380012b752e..788a678251bd22 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap @@ -7,18 +7,22 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/invalid_augme ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..611, body: [ AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 97..109, target: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 97..103, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("x"), ctx: Load, @@ -26,6 +30,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("y"), ctx: Load, @@ -37,6 +42,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 107..109, value: Int( 42, @@ -47,12 +53,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 110..124, target: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 111..117, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("x"), ctx: Store, @@ -60,6 +69,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 116..117, value: Int( 5, @@ -71,6 +81,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..124, value: Int( 42, @@ -81,12 +92,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 125..136, target: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 125..130, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("x"), ctx: Load, @@ -95,6 +109,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("y"), ctx: Load, @@ -105,6 +120,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 134..136, value: Int( 42, @@ -115,13 +131,16 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 137..145, target: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 137..139, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 138..139, id: Name("x"), ctx: Store, @@ -132,6 +151,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 143..145, value: Int( 42, @@ -142,22 +162,30 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 146..165, target: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 147..158, parameters: Some( Parameters { range: 154..155, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 154..155, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 154..155, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("_"), range: 154..155, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -171,6 +199,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 157..158, value: Int( 1, @@ -182,6 +211,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 163..165, value: Int( 42, @@ -192,12 +222,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 166..185, target: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 166..179, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("b"), ctx: Load, @@ -205,6 +238,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("a"), ctx: Load, @@ -212,6 +246,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("c"), ctx: Load, @@ -222,6 +257,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 183..185, value: Int( 42, @@ -232,20 +268,24 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 186..200, target: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 186..194, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 187..190, value: StringLiteralValue { inner: Single( StringLiteral { range: 187..190, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -260,6 +300,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 192..193, value: Int( 5, @@ -273,6 +314,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 198..200, value: Int( 42, @@ -283,13 +325,16 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 201..210, target: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 201..204, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..203, id: Name("a"), ctx: Load, @@ -301,6 +346,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 208..210, value: Int( 42, @@ -311,12 +357,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 211..232, target: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 211..226, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 212..213, id: Name("x"), ctx: Load, @@ -325,8 +374,10 @@ Module( generators: [ Comprehension { range: 214..225, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 218..219, id: Name("x"), ctx: Store, @@ -334,6 +385,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 223..225, id: Name("xs"), ctx: Load, @@ -348,6 +400,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 230..232, value: Int( 42, @@ -358,12 +411,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 233..254, target: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 233..248, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..235, id: Name("x"), ctx: Load, @@ -372,8 +428,10 @@ Module( generators: [ Comprehension { range: 236..247, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 240..241, id: Name("x"), ctx: Store, @@ -381,6 +439,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..247, id: Name("xs"), ctx: Load, @@ -395,6 +454,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 252..254, value: Int( 42, @@ -405,12 +465,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 255..283, target: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 255..277, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 256..257, id: Name("x"), ctx: Load, @@ -418,9 +481,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 259..264, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("x"), ctx: Load, @@ -429,6 +494,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 263..264, value: Int( 2, @@ -440,8 +506,10 @@ Module( generators: [ Comprehension { range: 265..276, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 269..270, id: Name("x"), ctx: Store, @@ -449,6 +517,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 274..276, id: Name("xs"), ctx: Load, @@ -463,6 +532,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 281..283, value: Int( 42, @@ -473,12 +543,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 284..305, target: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 284..299, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 285..286, id: Name("x"), ctx: Load, @@ -487,8 +560,10 @@ Module( generators: [ Comprehension { range: 287..298, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 291..292, id: Name("x"), ctx: Store, @@ -496,6 +571,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 296..298, id: Name("xs"), ctx: Load, @@ -511,6 +587,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 303..305, value: Int( 42, @@ -521,12 +598,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 306..319, target: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 306..313, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 312..313, id: Name("x"), ctx: Load, @@ -537,6 +617,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 317..319, value: Int( 42, @@ -547,13 +628,16 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 320..335, target: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 321..328, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..328, id: Name("x"), ctx: Load, @@ -565,6 +649,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 333..335, value: Int( 42, @@ -575,12 +660,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 336..357, target: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 337..350, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 348..350, id: Name("xs"), ctx: Load, @@ -591,6 +679,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 355..357, value: Int( 42, @@ -601,12 +690,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 358..373, target: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 358..367, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 358..359, id: Name("a"), ctx: Load, @@ -619,6 +711,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 362..363, id: Name("b"), ctx: Load, @@ -626,6 +719,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 366..367, id: Name("c"), ctx: Load, @@ -637,6 +731,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 371..373, value: Int( 42, @@ -647,12 +742,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 374..385, target: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 374..379, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 374..377, id: Name("foo"), ctx: Load, @@ -660,6 +758,7 @@ Module( ), arguments: Arguments { range: 377..379, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -668,6 +767,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 383..385, value: Int( 42, @@ -678,21 +778,26 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 387..402, target: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 387..396, value: FStringValue { inner: Single( FString( FString { range: 387..396, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 389..395, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 390..394, id: Name("quux"), ctx: Load, @@ -718,6 +823,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 400..402, value: Int( 42, @@ -728,21 +834,26 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 403..427, target: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 403..421, value: FStringValue { inner: Single( FString( FString { range: 403..421, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 405..410, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 406..409, id: Name("foo"), ctx: Load, @@ -756,14 +867,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 410..415, + node_index: AtomicNodeIndex(..), value: " and ", }, ), Interpolation( InterpolatedElement { range: 415..420, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 416..419, id: Name("bar"), ctx: Load, @@ -789,6 +903,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 425..427, value: Int( 42, @@ -799,14 +914,17 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 429..440, target: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 429..434, value: StringLiteralValue { inner: Single( StringLiteral { range: 429..434, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -821,6 +939,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 438..440, value: Int( 42, @@ -831,14 +950,17 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 441..453, target: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 441..447, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 441..447, + node_index: AtomicNodeIndex(..), value: [ 102, 111, @@ -857,6 +979,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 451..453, value: Int( 42, @@ -867,9 +990,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 454..463, target: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 454..457, value: Int( 123, @@ -879,6 +1004,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 461..463, value: Int( 42, @@ -889,9 +1015,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 464..474, target: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 464..468, value: true, }, @@ -899,6 +1027,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 472..474, value: Int( 42, @@ -909,15 +1038,18 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 475..485, target: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 475..479, }, ), op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 483..485, value: Int( 42, @@ -928,15 +1060,18 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 486..495, target: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 486..489, }, ), op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 493..495, value: Int( 42, @@ -947,15 +1082,19 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 496..508, target: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 496..502, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 497..502, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 497..500, id: Name("foo"), ctx: Load, @@ -963,6 +1102,7 @@ Module( ), arguments: Arguments { range: 500..502, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -974,6 +1114,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 506..508, value: Int( 42, @@ -984,13 +1125,16 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 509..538, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 509..522, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 510..511, id: Name("x"), ctx: Store, @@ -998,9 +1142,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 513..518, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 513..516, id: Name("foo"), ctx: Load, @@ -1008,6 +1154,7 @@ Module( ), arguments: Arguments { range: 516..518, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1015,6 +1162,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 520..521, id: Name("y"), ctx: Store, @@ -1027,10 +1175,12 @@ Module( op: Add, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 526..538, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 527..529, value: Int( 42, @@ -1039,6 +1189,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 531..533, value: Int( 42, @@ -1047,6 +1198,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 535..537, value: Int( 42, @@ -1061,17 +1213,21 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 539..580, target: List( ExprList { + node_index: AtomicNodeIndex(..), range: 539..558, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 540..546, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 541..542, id: Name("a"), ctx: Store, @@ -1079,6 +1235,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 544..545, id: Name("b"), ctx: Store, @@ -1090,14 +1247,17 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 548..554, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 549..553, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 550..552, value: Int( 42, @@ -1114,6 +1274,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 556..557, id: Name("d"), ctx: Store, @@ -1126,14 +1287,17 @@ Module( op: Add, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 562..580, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 563..569, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 564..565, value: Int( 1, @@ -1142,6 +1306,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 567..568, value: Int( 2, @@ -1154,14 +1319,17 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 571..576, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 572..575, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 573..574, value: Int( 3, @@ -1178,6 +1346,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 578..579, value: Int( 4, @@ -1192,13 +1361,16 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 581..610, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 581..594, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 582..583, id: Name("x"), ctx: Store, @@ -1206,9 +1378,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 585..590, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 585..588, id: Name("foo"), ctx: Load, @@ -1216,6 +1390,7 @@ Module( ), arguments: Arguments { range: 588..590, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1223,6 +1398,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 592..593, id: Name("y"), ctx: Store, @@ -1236,10 +1412,12 @@ Module( op: Add, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 598..610, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 599..601, value: Int( 42, @@ -1248,6 +1426,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 603..605, value: Int( 42, @@ -1256,6 +1435,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 607..609, value: Int( 42, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_0.py.snap index 45f7c9abf76173..58087f43bd7e88 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_0.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/as_pattern_0.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..198, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..197, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -23,11 +25,14 @@ Module( cases: [ MatchCase { range: 127..197, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 132..146, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..139, id: Name(""), ctx: Invalid, @@ -35,15 +40,18 @@ Module( ), arguments: PatternArguments { range: 140..146, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 141..142, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 141..142, + node_index: AtomicNodeIndex(..), }, ), }, @@ -51,11 +59,13 @@ Module( MatchAs( PatternMatchAs { range: 144..145, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 144..145, + node_index: AtomicNodeIndex(..), }, ), }, @@ -69,6 +79,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 193..197, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_1.py.snap index a2e2631cab94fb..c33ba5de9a3bd1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_1.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/as_pattern_1.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..210, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..209, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -23,14 +25,18 @@ Module( cases: [ MatchCase { range: 140..209, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 145..158, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 145..158, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..152, id: Name(""), ctx: Invalid, @@ -39,6 +45,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 156..158, value: Complex { real: 0.0, @@ -54,6 +61,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 205..209, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_2.py.snap index 1607c5185fb0fa..056268c64d25f4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_2.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/match/as_patt ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..190, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..176, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -22,18 +25,22 @@ Module( cases: [ MatchCase { range: 159..176, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 164..170, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 164..165, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 164..165, + node_index: AtomicNodeIndex(..), }, ), }, @@ -43,6 +50,7 @@ Module( Identifier { id: Name("y"), range: 169..170, + node_index: AtomicNodeIndex(..), }, ), }, @@ -51,13 +59,16 @@ Module( body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 171..176, target: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 171..175, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 173..175, value: Complex { real: 0.0, @@ -69,6 +80,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..176, id: Name(""), ctx: Invalid, @@ -85,6 +97,7 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 185..189, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap index cf7490e5fd2fb8..6ebf0be44b9fca 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/match/as_patt ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..136, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..120, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -22,30 +25,37 @@ Module( cases: [ MatchCase { range: 103..120, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 108..117, + node_index: AtomicNodeIndex(..), cls: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 108..109, items: [], }, ), arguments: PatternArguments { range: 109..117, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 110..116, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 110..111, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 110..111, + node_index: AtomicNodeIndex(..), }, ), }, @@ -55,6 +65,7 @@ Module( Identifier { id: Name("y"), range: 115..116, + node_index: AtomicNodeIndex(..), }, ), }, @@ -68,9 +79,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..120, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -86,6 +99,7 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 131..135, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_4.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_4.py.snap index a05914e0333633..6d705cce3a44db 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_4.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_4.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/as_pattern_4.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..187, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..186, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -23,12 +25,15 @@ Module( cases: [ MatchCase { range: 156..186, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 161..172, + node_index: AtomicNodeIndex(..), keys: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 162..163, id: Name("x"), ctx: Store, @@ -36,6 +41,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 167..168, id: Name("y"), ctx: Store, @@ -46,11 +52,13 @@ Module( MatchAs( PatternMatchAs { range: 164..166, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("as"), range: 164..166, + node_index: AtomicNodeIndex(..), }, ), }, @@ -58,8 +66,10 @@ Module( MatchValue( PatternMatchValue { range: 170..171, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 170..171, value: Int( 1, @@ -76,6 +86,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 182..186, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_class_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_class_pattern.py.snap index 9bbae6b7802b3c..1687f42248ed82 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_class_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_class_pattern.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/invalid_class_pattern.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..383, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 44..285, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..57, id: Name("subject"), ctx: Load, @@ -23,11 +25,14 @@ Module( cases: [ MatchCase { range: 63..97, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 68..83, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..71, id: Name("Foo"), ctx: Load, @@ -35,19 +40,24 @@ Module( ), arguments: PatternArguments { range: 71..83, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 72..82, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 80..80, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 81..82, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 1, @@ -65,6 +75,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 93..97, }, ), @@ -72,11 +83,14 @@ Module( }, MatchCase { range: 102..135, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 107..121, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..110, id: Name("Foo"), ctx: Load, @@ -84,19 +98,24 @@ Module( ), arguments: PatternArguments { range: 110..121, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 111..120, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 118..118, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 119..120, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -114,6 +133,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 131..135, }, ), @@ -121,11 +141,14 @@ Module( }, MatchCase { range: 140..174, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 145..160, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..148, id: Name("Foo"), ctx: Load, @@ -133,19 +156,24 @@ Module( ), arguments: PatternArguments { range: 148..160, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 149..159, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 157..157, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 158..159, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 1, @@ -163,6 +191,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 170..174, }, ), @@ -170,11 +199,14 @@ Module( }, MatchCase { range: 179..217, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 184..203, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..187, id: Name("Foo"), ctx: Load, @@ -182,19 +214,24 @@ Module( ), arguments: PatternArguments { range: 187..203, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 188..202, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 200..200, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 201..202, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 1, @@ -212,6 +249,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 213..217, }, ), @@ -219,11 +257,14 @@ Module( }, MatchCase { range: 222..249, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 227..235, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 227..230, id: Name("Foo"), ctx: Load, @@ -231,19 +272,24 @@ Module( ), arguments: PatternArguments { range: 230..235, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 231..234, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 233..233, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 233..234, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 233..234, value: Int( 1, @@ -261,6 +307,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 245..249, }, ), @@ -268,11 +315,14 @@ Module( }, MatchCase { range: 254..285, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 259..271, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..262, id: Name("Foo"), ctx: Load, @@ -280,19 +330,24 @@ Module( ), arguments: PatternArguments { range: 262..271, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 263..270, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name(""), range: 269..269, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 269..270, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 269..270, value: Int( 1, @@ -310,6 +365,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 281..285, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap index 4ff98106389ff0..abf680ec652dd7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/invalid_lhs_or_rhs_pattern.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..695, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..332, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..25, id: Name("invalid_lhs_pattern"), ctx: Load, @@ -23,17 +25,22 @@ Module( cases: [ MatchCase { range: 31..60, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 36..46, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 36..46, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 36..41, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..39, id: Name("Foo"), ctx: Load, @@ -41,6 +48,7 @@ Module( ), arguments: Arguments { range: 39..41, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -49,6 +57,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..46, value: Complex { real: 0.0, @@ -64,6 +73,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 56..60, }, ), @@ -71,14 +81,18 @@ Module( }, MatchCase { range: 65..90, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 70..76, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 70..76, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Store, @@ -87,6 +101,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 74..76, value: Complex { real: 0.0, @@ -102,6 +117,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 86..90, }, ), @@ -109,14 +125,18 @@ Module( }, MatchCase { range: 95..120, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 100..106, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 100..106, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("_"), ctx: Store, @@ -125,6 +145,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..106, value: Complex { real: 0.0, @@ -140,6 +161,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 116..120, }, ), @@ -147,17 +169,22 @@ Module( }, MatchCase { range: 125..156, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 130..142, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 130..142, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 131..136, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 131..132, value: Int( 1, @@ -167,6 +194,7 @@ Module( op: BitOr, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 2, @@ -178,6 +206,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 140..142, value: Complex { real: 0.0, @@ -193,6 +222,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 152..156, }, ), @@ -200,18 +230,23 @@ Module( }, MatchCase { range: 161..191, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 166..177, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 166..177, left: List( ExprList { + node_index: AtomicNodeIndex(..), range: 166..172, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 167..168, value: Int( 1, @@ -220,6 +255,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 170..171, value: Int( 2, @@ -233,6 +269,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 175..177, value: Complex { real: 0.0, @@ -248,6 +285,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 187..191, }, ), @@ -255,20 +293,25 @@ Module( }, MatchCase { range: 196..229, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 201..215, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 201..215, left: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 201..210, items: [ DictItem { key: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 202..206, value: true, }, @@ -276,6 +319,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 208..209, value: Int( 1, @@ -289,6 +333,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..215, value: Complex { real: 0.0, @@ -304,6 +349,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 225..229, }, ), @@ -311,14 +357,18 @@ Module( }, MatchCase { range: 234..260, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 239..246, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 239..246, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..241, value: Complex { real: 0.0, @@ -329,6 +379,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 244..246, value: Complex { real: 0.0, @@ -344,6 +395,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 256..260, }, ), @@ -351,18 +403,23 @@ Module( }, MatchCase { range: 265..292, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 270..278, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 270..278, left: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 270..273, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 271..273, value: Complex { real: 0.0, @@ -375,6 +432,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 276..278, value: Complex { real: 0.0, @@ -390,6 +448,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 288..292, }, ), @@ -397,17 +456,22 @@ Module( }, MatchCase { range: 297..332, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 302..318, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 302..318, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 302..313, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 302..305, id: Name("Foo"), ctx: Load, @@ -415,9 +479,11 @@ Module( ), arguments: Arguments { range: 305..313, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 306..312, id: Name(""), ctx: Invalid, @@ -431,6 +497,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 316..318, value: Complex { real: 0.0, @@ -446,6 +513,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 328..332, }, ), @@ -456,9 +524,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 334..625, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 340..359, id: Name("invalid_rhs_pattern"), ctx: Load, @@ -467,14 +537,18 @@ Module( cases: [ MatchCase { range: 365..393, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 370..379, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 370..379, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 370..371, value: Int( 1, @@ -484,9 +558,11 @@ Module( op: Add, right: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 374..379, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 374..377, id: Name("Foo"), ctx: Load, @@ -494,6 +570,7 @@ Module( ), arguments: Arguments { range: 377..379, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -507,6 +584,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 389..393, }, ), @@ -514,14 +592,18 @@ Module( }, MatchCase { range: 398..422, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 403..408, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 403..408, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 403..404, value: Int( 2, @@ -531,6 +613,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 407..408, id: Name("x"), ctx: Store, @@ -544,6 +627,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 418..422, }, ), @@ -551,14 +635,18 @@ Module( }, MatchCase { range: 427..451, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 432..437, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 432..437, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 432..433, value: Int( 3, @@ -568,6 +656,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 436..437, id: Name("_"), ctx: Store, @@ -581,6 +670,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 447..451, }, ), @@ -588,14 +678,18 @@ Module( }, MatchCase { range: 456..486, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 461..472, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 461..472, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 461..462, value: Int( 4, @@ -605,9 +699,11 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 466..471, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 466..467, value: Int( 1, @@ -617,6 +713,7 @@ Module( op: BitOr, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 470..471, value: Int( 2, @@ -633,6 +730,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 482..486, }, ), @@ -640,14 +738,18 @@ Module( }, MatchCase { range: 491..520, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 496..506, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 496..506, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 496..497, value: Int( 5, @@ -657,10 +759,12 @@ Module( op: Add, right: List( ExprList { + node_index: AtomicNodeIndex(..), range: 500..506, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 501..502, value: Int( 1, @@ -669,6 +773,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 504..505, value: Int( 2, @@ -687,6 +792,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 516..520, }, ), @@ -694,14 +800,18 @@ Module( }, MatchCase { range: 525..557, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 530..543, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 530..543, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 530..531, value: Int( 6, @@ -711,12 +821,14 @@ Module( op: Add, right: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 534..543, items: [ DictItem { key: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 535..539, value: true, }, @@ -724,6 +836,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 541..542, value: Int( 1, @@ -742,6 +855,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 553..557, }, ), @@ -749,14 +863,18 @@ Module( }, MatchCase { range: 562..586, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 567..572, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 567..572, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 567..568, value: Int( 1, @@ -766,6 +884,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 571..572, value: Int( 2, @@ -780,6 +899,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 582..586, }, ), @@ -787,14 +907,18 @@ Module( }, MatchCase { range: 591..625, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 596..611, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 596..611, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 596..597, value: Int( 1, @@ -804,9 +928,11 @@ Module( op: Add, right: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 600..611, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 600..603, id: Name("Foo"), ctx: Load, @@ -814,9 +940,11 @@ Module( ), arguments: Arguments { range: 603..611, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 604..610, id: Name(""), ctx: Invalid, @@ -835,6 +963,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 621..625, }, ), @@ -845,9 +974,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 627..694, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 633..656, id: Name("invalid_lhs_rhs_pattern"), ctx: Load, @@ -856,17 +987,22 @@ Module( cases: [ MatchCase { range: 662..694, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 667..680, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 667..680, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 667..672, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 667..670, id: Name("Foo"), ctx: Load, @@ -874,6 +1010,7 @@ Module( ), arguments: Arguments { range: 670..672, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -882,9 +1019,11 @@ Module( op: Add, right: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 675..680, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 675..678, id: Name("Bar"), ctx: Load, @@ -892,6 +1031,7 @@ Module( ), arguments: Arguments { range: 678..680, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -905,6 +1045,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 690..694, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_mapping_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_mapping_pattern.py.snap index dddbd65f4a5891..69e3b740ee67db 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_mapping_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_mapping_pattern.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/invalid_mapping_pattern.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..509, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 61..209, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..74, id: Name("subject"), ctx: Load, @@ -23,15 +25,19 @@ Module( cases: [ MatchCase { range: 80..105, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 85..91, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 86..90, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..90, id: Name("key"), ctx: Store, @@ -45,8 +51,10 @@ Module( MatchValue( PatternMatchValue { range: 90..90, + node_index: AtomicNodeIndex(..), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..90, id: Name(""), ctx: Invalid, @@ -62,6 +70,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 101..105, }, ), @@ -69,15 +78,19 @@ Module( }, MatchCase { range: 110..138, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 115..124, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 116..120, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..120, id: Name("key"), ctx: Store, @@ -91,8 +104,10 @@ Module( MatchValue( PatternMatchValue { range: 122..123, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 1, @@ -109,6 +124,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 134..138, }, ), @@ -116,15 +132,19 @@ Module( }, MatchCase { range: 143..170, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 148..156, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 149..153, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..153, id: Name("key"), ctx: Store, @@ -138,8 +158,10 @@ Module( MatchValue( PatternMatchValue { range: 154..155, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 154..155, value: Int( 1, @@ -156,6 +178,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 166..170, }, ), @@ -163,15 +186,19 @@ Module( }, MatchCase { range: 175..209, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 180..195, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 181..185, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 182..185, id: Name("key"), ctx: Store, @@ -182,6 +209,7 @@ Module( ), NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 187..191, }, ), @@ -190,8 +218,10 @@ Module( MatchValue( PatternMatchValue { range: 185..185, + node_index: AtomicNodeIndex(..), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..185, id: Name(""), ctx: Invalid, @@ -202,8 +232,10 @@ Module( MatchValue( PatternMatchValue { range: 193..194, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 193..194, value: Int( 1, @@ -220,6 +252,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 205..209, }, ), @@ -230,9 +263,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 305..462, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 311..318, id: Name("subject"), ctx: Load, @@ -241,12 +276,15 @@ Module( cases: [ MatchCase { range: 324..360, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 329..346, + node_index: AtomicNodeIndex(..), keys: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 338..342, }, ), @@ -255,8 +293,10 @@ Module( MatchValue( PatternMatchValue { range: 344..345, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 344..345, value: Int( 1, @@ -270,6 +310,7 @@ Module( Identifier { id: Name("rest"), range: 332..336, + node_index: AtomicNodeIndex(..), }, ), }, @@ -278,6 +319,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 356..360, }, ), @@ -285,12 +327,15 @@ Module( }, MatchCase { range: 365..411, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 370..397, + node_index: AtomicNodeIndex(..), keys: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 389..393, }, ), @@ -299,8 +344,10 @@ Module( MatchValue( PatternMatchValue { range: 395..396, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 395..396, value: Int( 1, @@ -314,6 +361,7 @@ Module( Identifier { id: Name("rest2"), range: 382..387, + node_index: AtomicNodeIndex(..), }, ), }, @@ -322,6 +370,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 407..411, }, ), @@ -329,12 +378,15 @@ Module( }, MatchCase { range: 416..462, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 421..448, + node_index: AtomicNodeIndex(..), keys: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 431..435, }, ), @@ -343,8 +395,10 @@ Module( MatchValue( PatternMatchValue { range: 437..438, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 437..438, value: Int( 1, @@ -358,6 +412,7 @@ Module( Identifier { id: Name("rest2"), range: 442..447, + node_index: AtomicNodeIndex(..), }, ), }, @@ -366,6 +421,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 458..462, }, ), @@ -376,9 +432,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 464..509, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 470..477, id: Name("subject"), ctx: Load, @@ -387,15 +445,19 @@ Module( cases: [ MatchCase { range: 483..509, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 488..504, + node_index: AtomicNodeIndex(..), keys: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 489..500, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 489..492, id: Name("Foo"), ctx: Load, @@ -403,9 +465,11 @@ Module( ), arguments: Arguments { range: 492..500, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 493..499, id: Name(""), ctx: Invalid, @@ -421,8 +485,10 @@ Module( MatchValue( PatternMatchValue { range: 502..503, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 502..503, value: Int( 1, @@ -439,9 +505,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 506..509, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 506..509, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap index fa73b40cf644bc..ca41862659884d 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/star_pattern_usage.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..408, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 57..407, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..70, id: Name("subject"), ctx: Load, @@ -23,9 +25,11 @@ Module( cases: [ MatchCase { range: 76..97, + node_index: AtomicNodeIndex(..), pattern: MatchStar( PatternMatchStar { range: 81..83, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -33,6 +37,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 93..97, }, ), @@ -40,13 +45,16 @@ Module( }, MatchCase { range: 102..128, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 107..114, + node_index: AtomicNodeIndex(..), pattern: Some( MatchStar( PatternMatchStar { range: 107..109, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -55,6 +63,7 @@ Module( Identifier { id: Name("x"), range: 113..114, + node_index: AtomicNodeIndex(..), }, ), }, @@ -63,6 +72,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 124..128, }, ), @@ -70,13 +80,16 @@ Module( }, MatchCase { range: 133..156, + node_index: AtomicNodeIndex(..), pattern: MatchStar( PatternMatchStar { range: 138..142, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("foo"), range: 139..142, + node_index: AtomicNodeIndex(..), }, ), }, @@ -85,6 +98,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 152..156, }, ), @@ -92,17 +106,21 @@ Module( }, MatchCase { range: 161..188, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 166..174, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 166..170, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("foo"), range: 167..170, + node_index: AtomicNodeIndex(..), }, ), }, @@ -110,8 +128,10 @@ Module( MatchValue( PatternMatchValue { range: 173..174, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 173..174, value: Int( 1, @@ -127,6 +147,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 184..188, }, ), @@ -134,15 +155,19 @@ Module( }, MatchCase { range: 193..220, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 198..206, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 198..199, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 198..199, value: Int( 1, @@ -154,10 +179,12 @@ Module( MatchStar( PatternMatchStar { range: 202..206, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("foo"), range: 203..206, + node_index: AtomicNodeIndex(..), }, ), }, @@ -169,6 +196,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 216..220, }, ), @@ -176,11 +204,14 @@ Module( }, MatchCase { range: 225..251, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 230..237, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 230..233, id: Name("Foo"), ctx: Load, @@ -188,10 +219,12 @@ Module( ), arguments: PatternArguments { range: 233..237, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 234..236, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -204,6 +237,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 247..251, }, ), @@ -211,11 +245,14 @@ Module( }, MatchCase { range: 256..284, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 261..270, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 261..264, id: Name("Foo"), ctx: Load, @@ -223,17 +260,21 @@ Module( ), arguments: PatternArguments { range: 264..270, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 265..269, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 265..266, + node_index: AtomicNodeIndex(..), }, pattern: MatchStar( PatternMatchStar { range: 267..269, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -246,6 +287,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 280..284, }, ), @@ -253,15 +295,19 @@ Module( }, MatchCase { range: 289..312, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 294..298, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 295..297, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 296..297, id: Name("_"), ctx: Store, @@ -275,8 +321,10 @@ Module( MatchValue( PatternMatchValue { range: 297..297, + node_index: AtomicNodeIndex(..), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 297..297, id: Name(""), ctx: Invalid, @@ -292,6 +340,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 308..312, }, ), @@ -299,15 +348,19 @@ Module( }, MatchCase { range: 317..343, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 322..329, + node_index: AtomicNodeIndex(..), keys: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 323..325, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 324..325, id: Name("_"), ctx: Store, @@ -321,8 +374,10 @@ Module( MatchValue( PatternMatchValue { range: 327..328, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 327..328, value: Int( 1, @@ -339,6 +394,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 339..343, }, ), @@ -346,12 +402,15 @@ Module( }, MatchCase { range: 348..377, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 353..363, + node_index: AtomicNodeIndex(..), keys: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 354..358, }, ), @@ -360,6 +419,7 @@ Module( MatchStar( PatternMatchStar { range: 360..362, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -371,6 +431,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 373..377, }, ), @@ -378,14 +439,18 @@ Module( }, MatchCase { range: 382..407, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 387..393, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 387..393, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 387..388, value: Int( 1, @@ -395,9 +460,11 @@ Module( op: Add, right: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 391..393, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 392..393, id: Name("_"), ctx: Store, @@ -414,6 +481,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 403..407, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__unary_add_usage.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__unary_add_usage.py.snap index f4dfce5b704807..b0a9f945ab898b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__unary_add_usage.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__unary_add_usage.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/invalid/statements/match/unary_add_usage.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..269, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 74..268, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..87, id: Name("subject"), ctx: Load, @@ -23,15 +25,19 @@ Module( cases: [ MatchCase { range: 93..114, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 98..100, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 98..100, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 99..100, value: Int( 1, @@ -46,6 +52,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 110..114, }, ), @@ -53,15 +60,19 @@ Module( }, MatchCase { range: 119..149, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 124..135, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 124..125, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 124..125, value: Int( 1, @@ -73,12 +84,15 @@ Module( MatchValue( PatternMatchValue { range: 128..130, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 128..130, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 129..130, value: Int( 2, @@ -92,12 +106,15 @@ Module( MatchValue( PatternMatchValue { range: 133..135, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 133..135, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 134..135, value: Int( 3, @@ -115,6 +132,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 145..149, }, ), @@ -122,15 +140,19 @@ Module( }, MatchCase { range: 154..184, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 159..170, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 160..161, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..161, value: Int( 1, @@ -142,12 +164,15 @@ Module( MatchValue( PatternMatchValue { range: 163..165, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 163..165, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 2, @@ -161,12 +186,15 @@ Module( MatchValue( PatternMatchValue { range: 167..169, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 167..169, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 168..169, value: Int( 3, @@ -184,6 +212,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 180..184, }, ), @@ -191,11 +220,14 @@ Module( }, MatchCase { range: 189..223, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 194..209, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 194..197, id: Name("Foo"), ctx: Load, @@ -203,23 +235,29 @@ Module( ), arguments: PatternArguments { range: 197..209, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 198..202, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 198..199, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 200..202, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 200..202, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 1, @@ -233,19 +271,24 @@ Module( }, PatternKeyword { range: 204..208, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 204..205, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 206..208, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 206..208, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 207..208, value: Int( 2, @@ -265,6 +308,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 219..223, }, ), @@ -272,18 +316,22 @@ Module( }, MatchCase { range: 228..268, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 233..254, + node_index: AtomicNodeIndex(..), keys: [ BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 234..238, value: true, }, ), BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 244..249, value: false, }, @@ -293,12 +341,15 @@ Module( MatchValue( PatternMatchValue { range: 240..242, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 240..242, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 241..242, value: Int( 1, @@ -312,12 +363,15 @@ Module( MatchValue( PatternMatchValue { range: 251..253, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 251..253, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 252..253, value: Int( 2, @@ -336,6 +390,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 264..268, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__ambiguous_lpar_with_items.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__ambiguous_lpar_with_items.py.snap index e05f49c03b4453..3c070146144891 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__ambiguous_lpar_with_items.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__ambiguous_lpar_with_items.py.snap @@ -7,21 +7,26 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/with/ambiguou ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..950, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 163..188, is_async: false, items: [ WithItem { range: 168..182, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 168..182, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 169..174, id: Name("item1"), ctx: Load, @@ -29,6 +34,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..181, id: Name("item2"), ctx: Load, @@ -45,9 +51,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..188, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 185..188, }, ), @@ -58,17 +66,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 189..219, is_async: false, items: [ WithItem { range: 194..208, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 194..208, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 195..200, id: Name("item1"), ctx: Load, @@ -76,6 +88,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..207, id: Name("item2"), ctx: Load, @@ -90,8 +103,10 @@ Module( }, WithItem { range: 213..214, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 213..214, id: Name("f"), ctx: Load, @@ -103,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 216..219, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 216..219, }, ), @@ -116,17 +133,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 220..252, is_async: false, items: [ WithItem { range: 225..239, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 225..239, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 226..231, id: Name("item1"), ctx: Load, @@ -134,6 +155,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..238, id: Name("item2"), ctx: Load, @@ -148,8 +170,10 @@ Module( }, WithItem { range: 241..246, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..246, id: Name("item3"), ctx: Load, @@ -161,9 +185,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 249..252, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 249..252, }, ), @@ -174,16 +200,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 253..270, is_async: false, items: [ WithItem { range: 258..265, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 259..264, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..264, id: Name("item"), ctx: Load, @@ -198,9 +228,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 267..270, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 267..270, }, ), @@ -211,16 +243,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 271..293, is_async: false, items: [ WithItem { range: 276..288, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 277..282, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 278..282, id: Name("item"), ctx: Load, @@ -232,6 +268,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 287..288, id: Name("f"), ctx: Store, @@ -243,9 +280,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 290..293, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 290..293, }, ), @@ -256,16 +295,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 294..321, is_async: false, items: [ WithItem { range: 300..315, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 300..310, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 300..304, id: Name("item"), ctx: Store, @@ -273,6 +316,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 308..310, value: Int( 10, @@ -284,6 +328,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 314..315, id: Name("f"), ctx: Store, @@ -295,9 +340,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 318..321, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 318..321, }, ), @@ -308,13 +355,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 322..357, is_async: false, items: [ WithItem { range: 328..333, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 328..333, id: Name("item1"), ctx: Load, @@ -324,11 +374,14 @@ Module( }, WithItem { range: 335..351, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 335..346, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 335..340, id: Name("item2"), ctx: Store, @@ -336,6 +389,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 344..346, value: Int( 10, @@ -347,6 +401,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 350..351, id: Name("f"), ctx: Store, @@ -358,9 +413,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 354..357, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 354..357, }, ), @@ -371,16 +428,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 358..396, is_async: false, items: [ WithItem { range: 363..384, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 363..384, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 364..365, id: Name("x"), ctx: Load, @@ -389,8 +450,10 @@ Module( generators: [ Comprehension { range: 366..384, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 370..371, id: Name("x"), ctx: Store, @@ -398,9 +461,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 375..384, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 375..380, id: Name("range"), ctx: Load, @@ -408,9 +473,11 @@ Module( ), arguments: Arguments { range: 380..384, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 381..383, value: Int( 10, @@ -433,8 +500,10 @@ Module( }, WithItem { range: 386..390, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 386..390, id: Name("item"), ctx: Load, @@ -446,9 +515,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 393..396, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 393..396, }, ), @@ -459,17 +530,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 397..410, is_async: false, items: [ WithItem { range: 402..410, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 402..410, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 403..407, id: Name("item"), ctx: Load, @@ -477,6 +552,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 409..410, id: Name("x"), ctx: Load, @@ -495,10 +571,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 411..429, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 415..416, id: Name("x"), ctx: Store, @@ -506,9 +584,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 420..429, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 420..425, id: Name("range"), ctx: Load, @@ -516,9 +596,11 @@ Module( ), arguments: Arguments { range: 425..429, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 426..428, value: Int( 10, @@ -536,9 +618,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 432..435, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 432..435, }, ), @@ -546,16 +630,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 496..515, is_async: false, items: [ WithItem { range: 502..509, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 503..508, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 504..508, id: Name("item"), ctx: Load, @@ -570,9 +658,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 512..515, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 512..515, }, ), @@ -583,19 +673,24 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 517..551, is_async: false, items: [ WithItem { range: 522..539, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 522..539, elt: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 523..525, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 524..525, id: Name("x"), ctx: Load, @@ -607,8 +702,10 @@ Module( generators: [ Comprehension { range: 526..539, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 530..531, id: Name("x"), ctx: Store, @@ -616,6 +713,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 535..539, id: Name("iter"), ctx: Load, @@ -632,8 +730,10 @@ Module( }, WithItem { range: 541..545, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 541..545, id: Name("item"), ctx: Load, @@ -645,9 +745,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 548..551, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 548..551, }, ), @@ -658,17 +760,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 552..567, is_async: false, items: [ WithItem { range: 557..567, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 557..567, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 558..563, id: Name("item1"), ctx: Load, @@ -676,9 +782,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 565..567, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 566..567, id: Name("x"), ctx: Load, @@ -700,10 +808,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 568..588, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 572..573, id: Name("x"), ctx: Store, @@ -711,10 +821,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 577..588, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 577..581, id: Name("iter"), ctx: Load, @@ -722,6 +834,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 583..588, id: Name("item2"), ctx: Load, @@ -738,9 +851,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 591..594, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 591..594, }, ), @@ -748,13 +863,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 595..617, is_async: false, items: [ WithItem { range: 601..607, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 601..602, id: Name("x"), ctx: Load, @@ -763,6 +881,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 606..607, id: Name("f"), ctx: Store, @@ -772,11 +891,14 @@ Module( }, WithItem { range: 609..611, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 609..611, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 610..611, id: Name("y"), ctx: Load, @@ -791,9 +913,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 614..617, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 614..617, }, ), @@ -804,16 +928,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 618..640, is_async: false, items: [ WithItem { range: 624..626, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 624..626, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 625..626, id: Name("x"), ctx: Load, @@ -826,8 +954,10 @@ Module( }, WithItem { range: 628..634, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 628..629, id: Name("y"), ctx: Load, @@ -836,6 +966,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 633..634, id: Name("f"), ctx: Store, @@ -847,9 +978,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 637..640, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 637..640, }, ), @@ -860,17 +993,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 641..663, is_async: false, items: [ WithItem { range: 646..658, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 646..658, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 647..648, id: Name("x"), ctx: Load, @@ -878,10 +1015,12 @@ Module( ), Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 650..657, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 656..657, id: Name("y"), ctx: Load, @@ -901,9 +1040,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 660..663, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 660..663, }, ), @@ -914,17 +1055,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 664..689, is_async: false, items: [ WithItem { range: 669..684, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 669..684, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 670..671, id: Name("x"), ctx: Load, @@ -932,14 +1077,17 @@ Module( ), Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 673..683, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 679..683, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 679..680, id: Name("y"), ctx: Load, @@ -947,6 +1095,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 682..683, id: Name("z"), ctx: Load, @@ -971,9 +1120,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 686..689, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 686..689, }, ), @@ -984,17 +1135,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 690..717, is_async: false, items: [ WithItem { range: 695..712, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 695..712, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 696..697, id: Name("x"), ctx: Load, @@ -1002,9 +1157,11 @@ Module( ), YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 699..711, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 710..711, id: Name("y"), ctx: Load, @@ -1023,9 +1180,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 714..717, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 714..717, }, ), @@ -1036,13 +1195,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 718..734, is_async: false, items: [ WithItem { range: 724..730, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 724..725, id: Name("x"), ctx: Load, @@ -1051,6 +1213,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 729..730, id: Name("f"), ctx: Store, @@ -1060,8 +1223,10 @@ Module( }, WithItem { range: 732..733, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 732..733, id: Name("y"), ctx: Load, @@ -1075,9 +1240,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 738..744, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 738..739, id: Name("f"), ctx: Store, @@ -1085,6 +1252,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 741..744, }, ), @@ -1094,16 +1262,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 745..777, is_async: false, items: [ WithItem { range: 750..771, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 750..766, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 751..752, id: Name("x"), ctx: Load, @@ -1112,8 +1284,10 @@ Module( generators: [ Comprehension { range: 753..766, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 757..758, id: Name("x"), ctx: Store, @@ -1121,6 +1295,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 762..766, id: Name("iter"), ctx: Load, @@ -1136,6 +1311,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 770..771, id: Name("y"), ctx: Store, @@ -1147,9 +1323,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 774..777, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 774..777, }, ), @@ -1160,13 +1338,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 837..854, is_async: false, items: [ WithItem { range: 843..853, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 844..848, id: Name("item"), ctx: Load, @@ -1175,6 +1356,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 852..853, id: Name("f"), ctx: Store, @@ -1188,9 +1370,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 857..860, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 857..860, }, ), @@ -1198,13 +1382,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 862..878, is_async: false, items: [ WithItem { range: 868..877, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 868..872, id: Name("item"), ctx: Load, @@ -1213,6 +1400,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 876..877, id: Name("f"), ctx: Store, @@ -1226,9 +1414,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 880..886, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 880..881, id: Name("x"), ctx: Store, @@ -1236,6 +1426,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 883..886, }, ), @@ -1245,13 +1436,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 887..904, is_async: false, items: [ WithItem { range: 893..903, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 893..897, id: Name("item"), ctx: Load, @@ -1260,6 +1454,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 901..903, id: Name("f1"), ctx: Store, @@ -1273,9 +1468,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 908..915, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 908..910, id: Name("f2"), ctx: Store, @@ -1283,6 +1480,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 912..915, }, ), @@ -1292,13 +1490,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 916..950, is_async: false, items: [ WithItem { range: 922..932, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 922..927, id: Name("item1"), ctx: Load, @@ -1307,6 +1508,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 931..932, id: Name("f"), ctx: Store, @@ -1316,11 +1518,14 @@ Module( }, WithItem { range: 934..944, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 934..944, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 934..939, id: Name("item2"), ctx: Store, @@ -1328,6 +1533,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 943..944, value: Int( 0, @@ -1342,9 +1548,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 947..950, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 947..950, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__empty_with_items.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__empty_with_items.py.snap index 9d3a53afb0bca7..1798ad6c236133 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__empty_with_items.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__empty_with_items.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/with/empty_wi ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..105, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 88..98, is_async: false, items: [], body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..98, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 95..98, }, ), @@ -30,12 +34,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 100..105, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 100..105, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Load, @@ -44,6 +51,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar.py.snap index e389ad74809d95..15bb2f058b3bc0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/with/unclosed ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..14, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..14, is_async: false, items: [ WithItem { range: 5..6, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..6, id: Name(""), ctx: Invalid, @@ -29,12 +33,15 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..14, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 9..14, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -43,6 +50,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar_eof.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar_eof.py.snap index 057cb44426006a..ce5678ded9b496 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar_eof.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unclosed_ambiguous_lpar_eof.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/with/unclosed ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..6, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..6, is_async: false, items: [ WithItem { range: 5..6, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..6, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unparenthesized_with_items.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unparenthesized_with_items.py.snap index 7182e64ab16f09..c93979143a337b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unparenthesized_with_items.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__with__unparenthesized_with_items.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/invalid/statements/with/unparent ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..249, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 86..102, is_async: false, items: [ WithItem { range: 91..95, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..95, id: Name("item"), ctx: Load, @@ -29,6 +33,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 98..102, }, ), @@ -37,13 +42,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 103..124, is_async: false, items: [ WithItem { range: 108..117, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..112, id: Name("item"), ctx: Load, @@ -52,6 +60,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("x"), ctx: Store, @@ -63,6 +72,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 120..124, }, ), @@ -71,16 +81,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 125..141, is_async: false, items: [ WithItem { range: 130..135, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 130..135, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..135, id: Name("item"), ctx: Load, @@ -95,6 +109,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 137..141, }, ), @@ -103,16 +118,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 142..163, is_async: false, items: [ WithItem { range: 147..157, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 147..152, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..152, id: Name("item"), ctx: Load, @@ -124,6 +143,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("x"), ctx: Store, @@ -135,6 +155,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 159..163, }, ), @@ -143,16 +164,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 164..193, is_async: false, items: [ WithItem { range: 169..175, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 169..175, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..175, id: Name("item1"), ctx: Load, @@ -165,8 +190,10 @@ Module( }, WithItem { range: 177..187, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 177..182, id: Name("item2"), ctx: Load, @@ -175,6 +202,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 186..187, id: Name("f"), ctx: Store, @@ -186,6 +214,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 189..193, }, ), @@ -194,13 +223,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 194..223, is_async: false, items: [ WithItem { range: 199..209, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..204, id: Name("item1"), ctx: Load, @@ -209,6 +241,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 208..209, id: Name("f"), ctx: Store, @@ -218,11 +251,14 @@ Module( }, WithItem { range: 211..217, + node_index: AtomicNodeIndex(..), context_expr: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 211..217, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 212..217, id: Name("item2"), ctx: Load, @@ -237,6 +273,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 219..223, }, ), @@ -245,13 +282,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 224..249, is_async: false, items: [ WithItem { range: 229..233, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 229..233, id: Name("item"), ctx: Load, @@ -261,8 +301,10 @@ Module( }, WithItem { range: 237..243, + node_index: AtomicNodeIndex(..), context_expr: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 237..238, value: Int( 0, @@ -272,6 +314,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 242..243, id: Name("f"), ctx: Store, @@ -283,6 +326,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 245..249, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap index 423ad64c794f5d..6b3811f0e18a9f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_empty_expression.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_empty_expres ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..58, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..49, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..49, value: TStringValue { inner: Single( TString( TString { range: 44..49, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 46..48, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..47, id: Name(""), ctx: Invalid, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..57, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 50..57, value: TStringValue { inner: Single( TString( TString { range: 50..57, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 52..56, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..53, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap index b65770e8d804ee..ebf78178588aa9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_name_tok.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_conv ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..52, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..52, value: TStringValue { inner: Single( TString( TString { range: 44..52, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 46..51, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap index 10324a349f6d30..f4485237350b54 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_conversion_flag_other_tok.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_conv ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..66, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..54, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..54, value: TStringValue { inner: Single( TString( TString { range: 44..54, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 46..53, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 55..65, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 55..65, value: TStringValue { inner: Single( TString( TString { range: 55..65, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 57..64, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap index a2fb0fda2de48f..b2a595bf80eb7f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_invalid_starred_expr.py.snap @@ -7,28 +7,35 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_invalid_star ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..156, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 121..127, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 121..127, value: TStringValue { inner: Single( TString( TString { range: 121..127, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 123..126, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 124..125, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..125, id: Name(""), ctx: Invalid, @@ -58,29 +65,36 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 128..141, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 128..141, value: TStringValue { inner: Single( TString( TString { range: 128..141, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 130..140, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 131..139, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 132..139, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("x"), ctx: Load, @@ -88,6 +102,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 138..139, id: Name("y"), ctx: Load, @@ -120,28 +135,35 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 142..155, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 142..155, value: TStringValue { inner: Single( TString( TString { range: 142..155, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 144..154, + node_index: AtomicNodeIndex(..), expression: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 145..153, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 146..153, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..153, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap index 1138fe8530d7a2..121d002f283909 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_lambda_without_parentheses.py.snap @@ -7,38 +7,50 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_lambda_witho ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..61, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..60, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..60, value: TStringValue { inner: Single( TString( TString { range: 44..60, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 46..56, + node_index: AtomicNodeIndex(..), expression: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 47..56, parameters: Some( Parameters { range: 54..55, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 54..55, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 54..55, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 54..55, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -52,6 +64,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..56, id: Name(""), ctx: Invalid, @@ -67,6 +80,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 56..58, + node_index: AtomicNodeIndex(..), value: " x", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap index b910137829a7e5..5c1f43a2b766a6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace.py.snap @@ -7,25 +7,31 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbr ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..82, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..48, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..48, value: TStringValue { inner: Single( TString( TString { range: 44..48, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 46..47, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..47, id: Name(""), ctx: Invalid, @@ -52,21 +58,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..58, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 49..58, value: TStringValue { inner: Single( TString( TString { range: 49..58, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 51..58, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..55, id: Name("foo"), ctx: Load, @@ -93,21 +104,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..67, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 59..67, value: TStringValue { inner: Single( TString( TString { range: 59..67, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 61..66, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..65, id: Name("foo"), ctx: Load, @@ -139,9 +155,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..81, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 68..81, value: TStringValue { inner: Concatenated( @@ -149,12 +167,15 @@ Module( TString( TString { range: 68..72, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 70..71, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..71, id: Name(""), ctx: Invalid, @@ -176,12 +197,15 @@ Module( TString( TString { range: 73..81, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 77..78, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..78, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap index 3c16c4c1617842..117976d898dfc5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@t_string_unclosed_lbrace_in_format_spec.py.snap @@ -7,31 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/err/t_string_unclosed_lbr ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..56, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..56, value: TStringValue { inner: Single( TString( TString { range: 44..56, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 46..52, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 52..55, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("x"), ctx: Load, @@ -42,6 +49,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 55..55, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -63,27 +71,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..72, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 57..72, value: TStringValue { inner: Single( TString( TString { range: 57..72, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 59..65, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 65..71, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("x"), ctx: Load, @@ -94,10 +108,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 68..71, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 68..71, + node_index: AtomicNodeIndex(..), value: ".3f", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_invalid_order.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_invalid_order.py.snap index f3525218f438cd..81e4bacde69967 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_invalid_order.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_invalid_order.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/try_stmt_invalid_orde ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..47, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..31, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -24,6 +27,7 @@ Module( finalbody: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 27..31, }, ), @@ -33,6 +37,7 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 42..46, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_missing_except_finally.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_missing_except_finally.py.snap index df9779babddc40..b86a2ad5bf5bf5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_missing_except_finally.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_missing_except_finally.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/try_stmt_missing_exce ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..13, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -27,10 +30,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 14..42, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 23..27, }, ), @@ -39,6 +44,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 38..42, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_misspelled_except.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_misspelled_except.py.snap index 93dc9f223d15a5..2bb1a1e8282ea0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_misspelled_except.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_misspelled_except.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/try_stmt_misspelled_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..165, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..13, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -27,9 +30,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 14..20, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..19, id: Name("exept"), ctx: Store, @@ -37,6 +42,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..20, id: Name(""), ctx: Invalid, @@ -48,20 +54,24 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 54..58, }, ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 72..76, }, ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 77..82, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("a"), ctx: Store, @@ -70,6 +80,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 1, @@ -80,10 +91,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 83..113, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 92..96, }, ), @@ -92,11 +105,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 97..113, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 109..113, }, ), @@ -111,9 +126,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 114..120, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..119, id: Name("exept"), ctx: Store, @@ -121,6 +138,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..120, id: Name(""), ctx: Invalid, @@ -132,15 +150,18 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 154..158, }, ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 159..164, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..160, id: Name("b"), ctx: Store, @@ -149,6 +170,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 163..164, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap index 0117d562168542..689c42622ecbe7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/try_stmt_mixed_except ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..242, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..63, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -23,11 +26,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 14..30, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 26..30, }, ), @@ -37,9 +42,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 31..63, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..53, id: Name("ExceptionGroup"), ctx: Load, @@ -50,6 +57,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 59..63, }, ), @@ -64,10 +72,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 64..127, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 73..77, }, ), @@ -76,9 +86,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 78..110, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..100, id: Name("ExceptionGroup"), ctx: Load, @@ -89,6 +101,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 106..110, }, ), @@ -98,11 +111,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 111..127, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 123..127, }, ), @@ -117,10 +132,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 128..241, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 137..141, }, ), @@ -129,11 +146,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 142..158, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 154..158, }, ), @@ -143,11 +162,13 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 159..175, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 171..175, }, ), @@ -157,9 +178,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 176..208, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..198, id: Name("ExceptionGroup"), ctx: Load, @@ -170,6 +193,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 204..208, }, ), @@ -179,9 +203,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 209..241, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 217..231, id: Name("ExceptionGroup"), ctx: Load, @@ -192,6 +218,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 237..241, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@tuple_context_manager_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@tuple_context_manager_py38.py.snap index 4cb4a0a1bc59de..b0c18e8a43e7db 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@tuple_context_manager_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@tuple_context_manager_py38.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/err/tuple_context_manager ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..327, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 216..236, is_async: false, items: [ WithItem { range: 222..225, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..225, id: Name("foo"), ctx: Load, @@ -27,8 +31,10 @@ Module( }, WithItem { range: 227..230, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 227..230, id: Name("bar"), ctx: Load, @@ -40,9 +46,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 233..236, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 233..236, }, ), @@ -53,16 +61,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 237..274, is_async: false, items: [ WithItem { range: 242..269, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 246..261, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 246..250, id: Name("open"), ctx: Load, @@ -70,14 +82,17 @@ Module( ), arguments: Arguments { range: 250..261, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 251..260, value: StringLiteralValue { inner: Single( StringLiteral { range: 251..260, + node_index: AtomicNodeIndex(..), value: "foo.txt", flags: StringLiteralFlags { quote_style: Single, @@ -97,6 +112,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..269, id: Name("foo"), ctx: Store, @@ -108,9 +124,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 271..274, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 271..274, }, ), @@ -121,13 +139,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 275..309, is_async: false, items: [ WithItem { range: 284..287, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 284..287, id: Name("foo"), ctx: Load, @@ -137,8 +158,10 @@ Module( }, WithItem { range: 291..294, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 291..294, id: Name("bar"), ctx: Load, @@ -148,8 +171,10 @@ Module( }, WithItem { range: 298..301, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 298..301, id: Name("baz"), ctx: Load, @@ -161,9 +186,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 306..309, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 306..309, }, ), @@ -174,13 +201,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 310..326, is_async: false, items: [ WithItem { range: 316..319, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 316..319, id: Name("foo"), ctx: Load, @@ -192,9 +222,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 323..326, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 323..326, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_incomplete_stmt.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_incomplete_stmt.py.snap index 8878c1663a8b95..96e430a647c3e2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_incomplete_stmt.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_incomplete_stmt.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_alias_incomplete_stmt.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..21, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..4, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("type"), ctx: Load, @@ -24,9 +26,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..9, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..9, id: Name("type"), ctx: Load, @@ -36,9 +40,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..11, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 12..20, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Store, @@ -59,6 +67,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..20, id: Name(""), ctx: Invalid, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_invalid_value_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_invalid_value_expr.py.snap index 3b1d5c40abd1c8..a7f11501f60d9a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_invalid_value_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_alias_invalid_value_expr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_alias_invalid_va ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..11, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Store, @@ -22,9 +25,11 @@ Module( type_params: None, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 9..11, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("y"), ctx: Load, @@ -37,9 +42,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 12..28, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Store, @@ -48,10 +55,12 @@ Module( type_params: None, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 21..28, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("y"), ctx: Load, @@ -64,9 +73,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 29..50, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Store, @@ -75,9 +86,11 @@ Module( type_params: None, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 38..50, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("y"), ctx: Load, @@ -89,9 +102,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 51..61, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Store, @@ -100,6 +115,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("x"), ctx: Load, @@ -109,9 +125,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..66, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_default_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_default_py312.py.snap index 68d3ff052490d2..c9de513d8a75ed 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_default_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_default_py312.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_param_default_py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..149, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 44..65, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("X"), ctx: Store, @@ -22,18 +25,22 @@ Module( type_params: Some( TypeParams { range: 50..59, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 51..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 51..52, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..58, id: Name("int"), ctx: Load, @@ -47,6 +54,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..65, id: Name("int"), ctx: Load, @@ -56,28 +64,34 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 66..87, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 70..71, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 71..80, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 72..79, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 72..73, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("int"), ctx: Load, @@ -91,6 +105,9 @@ Module( ), parameters: Parameters { range: 80..82, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -101,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..87, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 84..87, }, ), @@ -114,27 +133,33 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 88..111, decorator_list: [], name: Identifier { id: Name("C"), range: 94..95, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 95..104, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 96..103, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 96..97, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..103, id: Name("int"), ctx: Load, @@ -149,6 +174,7 @@ Module( arguments: Some( Arguments { range: 104..106, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -156,9 +182,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 108..111, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 108..111, }, ), @@ -169,22 +197,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 112..148, decorator_list: [], name: Identifier { id: Name("D"), range: 118..119, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 119..141, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 120..121, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("S"), range: 120..121, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -193,14 +226,17 @@ Module( TypeVar( TypeParamTypeVar { range: 123..130, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 123..124, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..130, id: Name("int"), ctx: Load, @@ -212,14 +248,17 @@ Module( TypeVar( TypeParamTypeVar { range: 132..140, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 132..133, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..140, id: Name("uint"), ctx: Load, @@ -234,6 +273,7 @@ Module( arguments: Some( Arguments { range: 141..143, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -241,9 +281,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 145..148, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 145..148, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_invalid_bound_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_invalid_bound_expr.py.snap index 6de003c4146faa..ce4ecda18c8315 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_invalid_bound_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_invalid_bound_expr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_param_invalid_bo ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..103, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..21, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -22,20 +25,25 @@ Module( type_params: Some( TypeParams { range: 6..15, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..14, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 10..14, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..14, id: Name("int"), ctx: Load, @@ -53,6 +61,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..21, id: Name("int"), ctx: Load, @@ -62,9 +71,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 22..46, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("X"), ctx: Store, @@ -73,21 +84,26 @@ Module( type_params: Some( TypeParams { range: 28..40, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 29..39, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 29..30, + node_index: AtomicNodeIndex(..), }, bound: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 32..39, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("x"), ctx: Load, @@ -105,6 +121,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("int"), ctx: Load, @@ -114,9 +131,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 47..76, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("X"), ctx: Store, @@ -125,20 +144,25 @@ Module( type_params: Some( TypeParams { range: 53..70, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 54..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 54..55, + node_index: AtomicNodeIndex(..), }, bound: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 57..69, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -155,6 +179,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..76, id: Name("int"), ctx: Load, @@ -164,9 +189,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 77..102, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("X"), ctx: Store, @@ -175,17 +202,21 @@ Module( type_params: Some( TypeParams { range: 83..96, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 84..88, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 84..85, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("x"), ctx: Load, @@ -198,9 +229,11 @@ Module( TypeVar( TypeParamTypeVar { range: 92..95, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("int"), range: 92..95, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -211,6 +244,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..102, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_missing_bound.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_missing_bound.py.snap index 0e9672fd1c34c9..84551e10b69c1a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_missing_bound.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_missing_bound.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_missing_bound.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..41, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..17, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..11, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -40,6 +45,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -49,9 +55,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 18..40, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("X"), ctx: Store, @@ -60,13 +68,16 @@ Module( type_params: Some( TypeParams { range: 24..34, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 25..28, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T1"), range: 25..27, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -75,9 +86,11 @@ Module( TypeVar( TypeParamTypeVar { range: 31..33, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 31..33, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -88,6 +101,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..40, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_bound.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_bound.py.snap index 79c0ae76890c34..7b4e3d833c6a2a 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_bound.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_bound.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_param_spec_bound.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..23, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..10, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..10, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 7..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 9..10, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..10, id: Name(""), ctx: Invalid, @@ -48,9 +54,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..15, id: Name("int"), ctx: Load, @@ -60,9 +68,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..22, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_invalid_default_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_invalid_default_expr.py.snap index 757545ca6efca6..6cde8b6197543b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_invalid_default_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_invalid_default_expr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_param_param_spec ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..140, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..24, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -22,20 +25,25 @@ Module( type_params: Some( TypeParams { range: 6..18, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 7..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 9..10, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -52,6 +60,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..24, id: Name("int"), ctx: Load, @@ -61,9 +70,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 25..52, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..31, id: Name("X"), ctx: Store, @@ -72,21 +83,26 @@ Module( type_params: Some( TypeParams { range: 31..46, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 32..45, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 34..35, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 38..45, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Load, @@ -103,6 +119,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..52, id: Name("int"), ctx: Load, @@ -112,9 +129,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 53..85, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("X"), ctx: Store, @@ -123,20 +142,25 @@ Module( type_params: Some( TypeParams { range: 59..79, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 60..78, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 62..63, + node_index: AtomicNodeIndex(..), }, default: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 66..78, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("x"), ctx: Load, @@ -152,6 +176,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..85, id: Name("int"), ctx: Load, @@ -161,9 +186,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 86..114, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("X"), ctx: Store, @@ -172,17 +199,21 @@ Module( type_params: Some( TypeParams { range: 92..108, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 93..100, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 95..96, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("x"), ctx: Load, @@ -194,9 +225,11 @@ Module( TypeVar( TypeParamTypeVar { range: 104..107, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("int"), range: 104..107, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -207,6 +240,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..114, id: Name("int"), ctx: Load, @@ -216,9 +250,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 115..139, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("X"), ctx: Store, @@ -227,20 +263,25 @@ Module( type_params: Some( TypeParams { range: 121..133, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 122..132, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 124..125, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 128..132, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..132, id: Name("int"), ctx: Load, @@ -257,6 +298,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..139, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_missing_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_missing_default.py.snap index 8ed2eb4c050409..ce7099a5e3aecf 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_missing_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_missing_default.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_param_spec_missing_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..44, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..19, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..13, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 7..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 9..10, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..19, id: Name("int"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 20..43, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("X"), ctx: Store, @@ -59,13 +67,16 @@ Module( type_params: Some( TypeParams { range: 26..37, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 27..32, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 29..30, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -73,9 +84,11 @@ Module( TypeVar( TypeParamTypeVar { range: 34..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 34..36, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -86,6 +99,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..43, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_invalid_default_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_invalid_default_expr.py.snap index 9d93c799c78e49..17ce8c72fdb211 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_invalid_default_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_invalid_default_expr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_param_type_var_i ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..163, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..22, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -22,21 +25,26 @@ Module( type_params: Some( TypeParams { range: 6..16, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 11..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..15, id: Name("int"), ctx: Load, @@ -53,6 +61,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..22, id: Name("int"), ctx: Load, @@ -62,9 +71,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 23..48, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("X"), ctx: Store, @@ -73,22 +84,27 @@ Module( type_params: Some( TypeParams { range: 29..42, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 30..41, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 30..31, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 34..41, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("x"), ctx: Load, @@ -105,6 +121,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..48, id: Name("int"), ctx: Load, @@ -114,9 +131,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 49..76, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("X"), ctx: Store, @@ -125,22 +144,27 @@ Module( type_params: Some( TypeParams { range: 55..70, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 56..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 56..57, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 61..68, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("x"), ctx: Load, @@ -157,6 +181,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..76, id: Name("int"), ctx: Load, @@ -166,9 +191,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 77..107, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("X"), ctx: Store, @@ -177,21 +204,26 @@ Module( type_params: Some( TypeParams { range: 83..101, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 84..100, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 84..85, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 88..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("x"), ctx: Load, @@ -207,6 +239,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..107, id: Name("int"), ctx: Load, @@ -216,9 +249,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 108..134, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("X"), ctx: Store, @@ -227,18 +262,22 @@ Module( type_params: Some( TypeParams { range: 114..128, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 115..120, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 115..116, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..120, id: Name("x"), ctx: Load, @@ -250,9 +289,11 @@ Module( TypeVar( TypeParamTypeVar { range: 124..127, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("int"), range: 124..127, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -263,6 +304,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..134, id: Name("int"), ctx: Load, @@ -272,9 +314,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 135..162, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("X"), ctx: Store, @@ -283,17 +327,21 @@ Module( type_params: Some( TypeParams { range: 141..156, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 142..155, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 142..143, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..148, id: Name("int"), ctx: Load, @@ -303,9 +351,11 @@ Module( default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 151..155, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..155, id: Name("int"), ctx: Load, @@ -322,6 +372,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..162, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_missing_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_missing_default.py.snap index 1547b5dd007f37..5ad35632e68925 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_missing_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_missing_default.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_type_var_missing_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..64, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..17, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..11, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -40,6 +45,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -49,9 +55,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 18..40, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("X"), ctx: Store, @@ -60,17 +68,21 @@ Module( type_params: Some( TypeParams { range: 24..34, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 25..33, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 25..26, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..31, id: Name("int"), ctx: Load, @@ -85,6 +97,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..40, id: Name("int"), ctx: Load, @@ -94,9 +107,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 41..63, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("X"), ctx: Store, @@ -105,13 +120,16 @@ Module( type_params: Some( TypeParams { range: 47..57, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 48..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T1"), range: 48..50, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -120,9 +138,11 @@ Module( TypeVar( TypeParamTypeVar { range: 54..56, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 54..56, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -133,6 +153,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_bound.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_bound.py.snap index 3e6e16fa6f5de4..3ffc891d81d144 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_bound.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_bound.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_bound.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..22, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..9, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..9, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 7..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 8..9, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..9, id: Name(""), ctx: Invalid, @@ -48,9 +54,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..14, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..14, id: Name("int"), ctx: Load, @@ -60,9 +68,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..21, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..21, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_invalid_default_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_invalid_default_expr.py.snap index 61860a15efe404..c6ab71d84ff4a7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_invalid_default_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_invalid_default_expr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_param_type_var_t ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..147, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..24, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -22,20 +25,25 @@ Module( type_params: Some( TypeParams { range: 6..18, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 7..17, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 8..10, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -52,6 +60,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..24, id: Name("int"), ctx: Load, @@ -61,9 +70,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 25..56, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..31, id: Name("X"), ctx: Store, @@ -72,25 +83,31 @@ Module( type_params: Some( TypeParams { range: 31..50, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 32..49, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 33..35, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 38..49, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 39..49, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..42, id: Name("int"), ctx: Load, @@ -98,6 +115,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..49, id: Name("str"), ctx: Load, @@ -117,6 +135,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..56, id: Name("int"), ctx: Load, @@ -126,9 +145,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 57..84, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("X"), ctx: Store, @@ -137,21 +158,26 @@ Module( type_params: Some( TypeParams { range: 63..78, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 64..77, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 65..67, + node_index: AtomicNodeIndex(..), }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 70..77, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..77, id: Name("x"), ctx: Load, @@ -168,6 +194,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..84, id: Name("int"), ctx: Load, @@ -177,9 +204,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 85..117, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("X"), ctx: Store, @@ -188,20 +217,25 @@ Module( type_params: Some( TypeParams { range: 91..111, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 92..110, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 93..95, + node_index: AtomicNodeIndex(..), }, default: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 98..110, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..110, id: Name("x"), ctx: Load, @@ -217,6 +251,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..117, id: Name("int"), ctx: Load, @@ -226,9 +261,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 118..146, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 123..124, id: Name("X"), ctx: Store, @@ -237,17 +274,21 @@ Module( type_params: Some( TypeParams { range: 124..140, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 125..132, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 126..128, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("x"), ctx: Load, @@ -259,9 +300,11 @@ Module( TypeVar( TypeParamTypeVar { range: 136..139, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("int"), range: 136..139, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -272,6 +315,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..146, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_missing_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_missing_default.py.snap index 17303b668739a7..b7e862a1056cc1 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_missing_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_missing_default.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_missing_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..44, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..19, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..13, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 7..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 8..10, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..19, id: Name("int"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 20..43, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("X"), ctx: Store, @@ -59,13 +67,16 @@ Module( type_params: Some( TypeParams { range: 26..37, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 27..32, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 28..30, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -73,9 +84,11 @@ Module( TypeVar( TypeParamTypeVar { range: 34..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T2"), range: 34..36, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -86,6 +99,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..43, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap index fff0c28aa0033c..fd3e6b168fe01c 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap @@ -1,32 +1,38 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/type_params_empty.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..52, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..21, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 7..9, + node_index: AtomicNodeIndex(..), type_params: [], }, ), parameters: Parameters { range: 9..11, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -37,6 +43,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 17..21, }, ), @@ -45,9 +52,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 22..51, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..36, id: Name("ListOrSet"), ctx: Store, @@ -56,14 +65,17 @@ Module( type_params: Some( TypeParams { range: 36..38, + node_index: AtomicNodeIndex(..), type_params: [], }, ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 41..51, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..45, id: Name("list"), ctx: Load, @@ -72,6 +84,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..51, id: Name("set"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_stmt_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_stmt_py311.py.snap index dfb7a15121bce5..df2f28b34dd9e7 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_stmt_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_stmt_py311.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_stmt_py311.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 44..56, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("x"), ctx: Store, @@ -22,6 +25,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..56, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap index 55d81b949d7cda..cbeb13482ff1c9 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..52, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 43..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("lst"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 47..51, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -34,6 +40,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 50..51, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap index a608f551217879..abbb6e06fa69ee 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..72, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 43..72, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 44..53, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..48, id: Name("last"), ctx: Store, @@ -27,6 +32,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("x"), ctx: Load, @@ -37,8 +43,10 @@ Module( generators: [ Comprehension { range: 54..71, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("x"), ctx: Store, @@ -46,9 +54,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 63..71, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..68, id: Name("range"), ctx: Load, @@ -56,9 +66,11 @@ Module( ), arguments: Arguments { range: 68..71, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 69..70, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap index d03f0ebe48f06a..16c0b89b5378c5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..88, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..57, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 43..57, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 44..50, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Store, @@ -28,6 +33,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 1, @@ -38,6 +44,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 2, @@ -46,6 +53,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 3, @@ -59,13 +67,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..72, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 58..72, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 59..60, value: Int( 1, @@ -74,9 +85,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 62..68, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Store, @@ -84,6 +97,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 67..68, value: Int( 2, @@ -94,6 +108,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 70..71, value: Int( 3, @@ -107,13 +122,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 73..87, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 73..87, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 74..75, value: Int( 1, @@ -122,6 +140,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 77..78, value: Int( 2, @@ -130,9 +149,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 80..86, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..81, id: Name("x"), ctx: Store, @@ -140,6 +161,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 85..86, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap index ad595e25bbbfef..7f914852c9f0ba 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unterminated_fstring_newline_recovery.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/inline/err/unterminated_fstring_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..7, value: FStringValue { inner: Single( FString( FString { range: 0..7, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, @@ -36,12 +40,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..13, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 8..13, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 1, @@ -51,6 +58,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 12..13, value: Int( 1, @@ -63,27 +71,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..24, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 14..24, value: FStringValue { inner: Single( FString( FString { range: 14..24, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 16..22, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 22..24, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("x"), ctx: Load, @@ -110,12 +124,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..30, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 25..30, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 2, @@ -125,6 +142,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 29..30, value: Int( 2, @@ -137,27 +155,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..42, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 31..42, value: FStringValue { inner: Single( FString( FString { range: 31..42, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 33..39, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 39..42, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("x"), ctx: Load, @@ -168,6 +192,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 42..42, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -189,12 +214,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..48, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 43..48, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..44, value: Int( 3, @@ -204,6 +232,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 47..48, value: Int( 3, @@ -216,27 +245,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..60, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 49..60, value: FStringValue { inner: Single( FString( FString { range: 49..60, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 51..57, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 57..60, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("x"), ctx: Load, @@ -263,12 +298,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..66, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 61..66, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 4, @@ -278,6 +316,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 4, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@walrus_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@walrus_py37.py.snap index 402445a7f6d109..9949fa6cbc5f12 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@walrus_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@walrus_py37.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/err/walrus_py37.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..54, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 45..53, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 46..52, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("x"), ctx: Store, @@ -24,6 +28,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_invalid_test_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_invalid_test_expr.py.snap index 0e8d49ac7517b6..4f87608d79b56e 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_invalid_test_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_invalid_test_expr.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/while_stmt_invalid_test_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..70, body: [ While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 0..13, test: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 6..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -29,9 +32,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..13, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 10..13, }, ), @@ -43,13 +48,16 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 14..32, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 20..27, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("x"), ctx: Load, @@ -61,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -75,9 +85,11 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 33..40, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("a"), ctx: Load, @@ -89,9 +101,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 42..48, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("b"), ctx: Store, @@ -99,6 +113,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 45..48, }, ), @@ -108,12 +123,15 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 49..61, test: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 55..61, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("a"), ctx: Store, @@ -121,6 +139,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 60..61, value: Int( 1, @@ -135,9 +154,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 63..69, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("b"), ctx: Store, @@ -145,6 +166,7 @@ Module( ), annotation: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 66..69, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_colon.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_colon.py.snap index 39dfbec62b9195..7006daf690aa92 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_colon.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_colon.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/while_stmt_missing_colon.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..40, body: [ While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 0..39, test: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 12..18, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("a"), ctx: Load, @@ -29,6 +32,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..18, value: Int( 30, @@ -41,6 +45,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 35..39, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_test.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_test.py.snap index 59871538d36fc1..e86db412de29b2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_test.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@while_stmt_missing_test.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/while_stmt_missing_test.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..30, body: [ While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 0..11, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..5, id: Name(""), ctx: Invalid, @@ -23,9 +25,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..11, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 8..11, }, ), @@ -37,9 +41,11 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 12..29, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..17, id: Name(""), ctx: Invalid, @@ -48,10 +54,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 24..29, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("a"), ctx: Store, @@ -60,6 +68,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_colon.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_colon.py.snap index c73b00b2b3b0e6..8a24d7009d3795 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_colon.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_colon.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/with_items_parenthesized_missing_colon.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 28..56, is_async: false, items: [ WithItem { range: 34..39, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..39, id: Name("item1"), ctx: Load, @@ -28,8 +31,10 @@ Module( }, WithItem { range: 41..46, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..46, id: Name("item2"), ctx: Load, @@ -41,6 +46,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 52..56, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_comma.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_comma.py.snap index 817874cdbed270..7a3cf04fb17471 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_comma.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@with_items_parenthesized_missing_comma.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/err/with_items_parenthesized_missing_comma.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..160, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..23, is_async: false, items: [ WithItem { range: 6..11, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..11, id: Name("item1"), ctx: Load, @@ -28,8 +31,10 @@ Module( }, WithItem { range: 12..17, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..17, id: Name("item2"), ctx: Load, @@ -41,9 +46,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), @@ -54,13 +61,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 24..53, is_async: false, items: [ WithItem { range: 30..41, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 30..35, id: Name("item1"), ctx: Load, @@ -69,6 +79,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..41, id: Name("f1"), ctx: Store, @@ -78,8 +89,10 @@ Module( }, WithItem { range: 42..47, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..47, id: Name("item2"), ctx: Load, @@ -91,9 +104,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..53, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 50..53, }, ), @@ -104,13 +119,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 54..91, is_async: false, items: [ WithItem { range: 60..65, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..65, id: Name("item1"), ctx: Load, @@ -120,8 +138,10 @@ Module( }, WithItem { range: 67..72, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..72, id: Name("item2"), ctx: Load, @@ -131,8 +151,10 @@ Module( }, WithItem { range: 73..78, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..78, id: Name("item3"), ctx: Load, @@ -142,8 +164,10 @@ Module( }, WithItem { range: 80..85, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..85, id: Name("item4"), ctx: Load, @@ -155,9 +179,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 88..91, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 88..91, }, ), @@ -168,13 +194,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 92..135, is_async: false, items: [ WithItem { range: 98..103, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..103, id: Name("item1"), ctx: Load, @@ -184,8 +213,10 @@ Module( }, WithItem { range: 105..116, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..110, id: Name("item2"), ctx: Load, @@ -194,6 +225,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..116, id: Name("f1"), ctx: Store, @@ -203,8 +235,10 @@ Module( }, WithItem { range: 117..122, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..122, id: Name("item3"), ctx: Load, @@ -214,8 +248,10 @@ Module( }, WithItem { range: 124..129, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..129, id: Name("item4"), ctx: Load, @@ -227,9 +263,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 132..135, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 132..135, }, ), @@ -240,17 +278,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 136..159, is_async: false, items: [ WithItem { range: 141..154, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 141..154, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..147, id: Name("item1"), ctx: Load, @@ -258,6 +300,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 149..154, id: Name("item2"), ctx: Load, @@ -274,9 +317,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 156..159, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 156..159, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap index 348aa344d8688e..cf762a1792c964 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@write_to_debug_expr.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/err/write_to_debug_expr.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..83, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..13, id: Name("__debug__"), ctx: Del, @@ -25,10 +28,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 14..36, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("x"), ctx: Del, @@ -36,6 +41,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("y"), ctx: Del, @@ -43,6 +49,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..33, id: Name("__debug__"), ctx: Del, @@ -50,6 +57,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("z"), ctx: Del, @@ -60,10 +68,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 37..50, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..46, id: Name("__debug__"), ctx: Store, @@ -72,6 +82,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 1, @@ -82,14 +93,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 51..82, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 51..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("x"), ctx: Store, @@ -97,6 +111,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("y"), ctx: Store, @@ -104,6 +119,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..66, id: Name("__debug__"), ctx: Store, @@ -111,6 +127,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("z"), ctx: Store, @@ -124,10 +141,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 72..82, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 72..73, value: Int( 1, @@ -136,6 +155,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 2, @@ -144,6 +164,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 3, @@ -152,6 +173,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 4, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@all_async_comprehension_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@all_async_comprehension_py310.py.snap index 85cb1326986897..411a58148d8f52 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@all_async_comprehension_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@all_async_comprehension_py310.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/all_async_comprehensio ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..126, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..125, is_async: true, decorator_list: [], name: Identifier { id: Name("test"), range: 54..58, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 58..60, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 62..125, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 69..125, elt: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 70..100, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("x"), ctx: Load, @@ -49,8 +59,10 @@ Module( generators: [ Comprehension { range: 73..99, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Store, @@ -58,9 +70,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 88..99, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..96, id: Name("elements"), ctx: Load, @@ -68,9 +82,11 @@ Module( ), arguments: Arguments { range: 96..99, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("n"), ctx: Load, @@ -90,8 +106,10 @@ Module( generators: [ Comprehension { range: 101..124, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("n"), ctx: Store, @@ -99,9 +117,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 116..124, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..121, id: Name("range"), ctx: Load, @@ -109,9 +129,11 @@ Module( ), arguments: Arguments { range: 121..124, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap index bfcd7c88d8e6f8..40c7d68cfd2d99 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap @@ -1,29 +1,33 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_binary_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..337, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 124..143, is_async: false, items: [ WithItem { range: 129..138, + node_index: AtomicNodeIndex(..), context_expr: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 129..138, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..131, id: Name("a"), ctx: Load, @@ -31,6 +35,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("b"), ctx: Load, @@ -45,9 +50,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 140..143, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 140..143, }, ), @@ -58,16 +65,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 144..166, is_async: false, items: [ WithItem { range: 149..161, + node_index: AtomicNodeIndex(..), context_expr: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 149..161, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("a"), ctx: Load, @@ -79,6 +90,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("b"), ctx: Load, @@ -93,9 +105,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..166, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 163..166, }, ), @@ -106,18 +120,22 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 196..220, is_async: false, items: [ WithItem { range: 201..215, + node_index: AtomicNodeIndex(..), context_expr: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 201..215, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..203, id: Name("a"), ctx: Load, @@ -125,11 +143,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 208..215, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 208..209, id: Name("b"), ctx: Load, @@ -137,6 +157,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("c"), ctx: Load, @@ -154,9 +175,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 217..220, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 217..220, }, ), @@ -167,23 +190,28 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 221..245, is_async: false, items: [ WithItem { range: 226..240, + node_index: AtomicNodeIndex(..), context_expr: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 226..240, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 226..235, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 227..228, id: Name("a"), ctx: Load, @@ -191,6 +219,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..235, id: Name("b"), ctx: Load, @@ -201,6 +230,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 239..240, id: Name("c"), ctx: Load, @@ -215,9 +245,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 242..245, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 242..245, }, ), @@ -228,22 +260,28 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 246..272, is_async: false, items: [ WithItem { range: 251..267, + node_index: AtomicNodeIndex(..), context_expr: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 251..267, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 251..263, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 252..257, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 252..253, id: Name("a"), ctx: Load, @@ -252,6 +290,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 256..257, id: Name("b"), ctx: Load, @@ -262,6 +301,7 @@ Module( op: LShift, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 262..263, id: Name("c"), ctx: Load, @@ -272,6 +312,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..267, id: Name("d"), ctx: Load, @@ -285,9 +326,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 269..272, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 269..272, }, ), @@ -298,19 +341,24 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 312..336, is_async: false, items: [ WithItem { range: 317..331, + node_index: AtomicNodeIndex(..), context_expr: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 317..331, left: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 317..323, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 318..319, id: Name("a"), ctx: Load, @@ -318,6 +366,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 321..322, value: Int( 0, @@ -330,9 +379,11 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 326..331, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 326..327, id: Name("b"), ctx: Load, @@ -341,6 +392,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 330..331, id: Name("c"), ctx: Load, @@ -356,9 +408,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 333..336, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 333..336, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_if_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_if_expr.py.snap index 22de70bfc86e46..00a32e1ab3bd57 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_if_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_if_expr.py.snap @@ -1,33 +1,38 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_if_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..153, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, items: [ WithItem { range: 5..23, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 5..23, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 12..16, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -35,6 +40,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("y"), ctx: Load, @@ -48,9 +54,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -61,25 +69,31 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 29..71, is_async: false, items: [ WithItem { range: 34..66, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 34..66, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 55..59, value: true, }, ), body: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 34..51, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("x"), ctx: Load, @@ -88,8 +102,10 @@ Module( generators: [ Comprehension { range: 37..50, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Store, @@ -97,6 +113,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..50, id: Name("iter"), ctx: Load, @@ -111,6 +128,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("y"), ctx: Load, @@ -124,9 +142,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..71, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 68..71, }, ), @@ -137,25 +157,31 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 72..120, is_async: false, items: [ WithItem { range: 77..115, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 77..115, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 104..108, value: true, }, ), body: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 77..100, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("x"), ctx: Load, @@ -164,8 +190,10 @@ Module( generators: [ Comprehension { range: 80..99, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("x"), ctx: Store, @@ -173,6 +201,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..99, id: Name("iter"), ctx: Load, @@ -187,6 +216,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("y"), ctx: Load, @@ -200,9 +230,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..120, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 117..120, }, ), @@ -213,25 +245,31 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 121..152, is_async: false, items: [ WithItem { range: 126..147, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 126..147, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 136..140, value: true, }, ), body: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 126..132, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("x"), ctx: Load, @@ -239,6 +277,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 130..131, value: Int( 0, @@ -250,6 +289,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("y"), ctx: Load, @@ -263,9 +303,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 149..152, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 149..152, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ann_assign_stmt_simple_target.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ann_assign_stmt_simple_target.py.snap index 8dbc86f99307d2..c0dc9bb14803dc 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ann_assign_stmt_simple_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ann_assign_stmt_simple_target.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/ann_assign_stmt_simple_target.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..45, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..6, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Store, @@ -22,6 +24,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("int"), ctx: Load, @@ -33,9 +36,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 17..25, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("a"), ctx: Store, @@ -43,6 +48,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..25, id: Name("int"), ctx: Load, @@ -54,12 +60,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 26..34, target: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 26..29, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("a"), ctx: Load, @@ -68,12 +77,14 @@ Module( attr: Identifier { id: Name("b"), range: 28..29, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..34, id: Name("int"), ctx: Load, @@ -85,12 +96,15 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 35..44, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 35..39, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("a"), ctx: Load, @@ -98,6 +112,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 37..38, value: Int( 0, @@ -109,6 +124,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..44, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap index 043e187b9df33e..650a3b6e53aebd 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_g ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..107, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..51, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..51, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..3, id: Name("zip"), ctx: Load, @@ -24,12 +28,15 @@ Module( ), arguments: Arguments { range: 3..51, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 4..26, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("x"), ctx: Load, @@ -38,8 +45,10 @@ Module( generators: [ Comprehension { range: 7..25, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("x"), ctx: Store, @@ -47,9 +56,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..25, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..21, id: Name("range"), ctx: Load, @@ -57,9 +68,11 @@ Module( ), arguments: Arguments { range: 21..25, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 22..24, value: Int( 10, @@ -80,9 +93,11 @@ Module( ), Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 28..50, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("y"), ctx: Load, @@ -91,8 +106,10 @@ Module( generators: [ Comprehension { range: 31..49, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("y"), ctx: Store, @@ -100,9 +117,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 40..49, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..45, id: Name("range"), ctx: Load, @@ -110,9 +129,11 @@ Module( ), arguments: Arguments { range: 45..49, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 46..48, value: Int( 10, @@ -140,12 +161,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..77, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 52..77, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..55, id: Name("sum"), ctx: Load, @@ -153,12 +177,15 @@ Module( ), arguments: Arguments { range: 55..77, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 56..76, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -167,8 +194,10 @@ Module( generators: [ Comprehension { range: 58..76, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Store, @@ -176,9 +205,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 67..76, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..72, id: Name("range"), ctx: Load, @@ -186,9 +217,11 @@ Module( ), arguments: Arguments { range: 72..76, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 73..75, value: Int( 10, @@ -216,12 +249,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..106, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 78..106, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..81, id: Name("sum"), ctx: Load, @@ -229,12 +265,15 @@ Module( ), arguments: Arguments { range: 81..106, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 82..104, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -243,8 +282,10 @@ Module( generators: [ Comprehension { range: 85..103, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("x"), ctx: Store, @@ -252,9 +293,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 94..103, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..99, id: Name("range"), ctx: Load, @@ -262,9 +305,11 @@ Module( ), arguments: Arguments { range: 99..103, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..102, value: Int( 10, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_stmt_starred_expr_value.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_stmt_starred_expr_value.py.snap index 5b5bf36875ed72..a9a4f610780212 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_stmt_starred_expr_value.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_stmt_starred_expr_value.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/assign_stmt_starred_ex ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..5, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("_"), ctx: Store, @@ -23,6 +26,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..5, value: Int( 4, @@ -33,10 +37,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 6..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("_"), ctx: Store, @@ -45,10 +51,12 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 10..13, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 4, @@ -63,10 +71,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 14..25, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("_"), ctx: Store, @@ -75,17 +85,21 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 18..25, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 19..23, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 20..23, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 1, @@ -108,10 +122,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 26..35, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("_"), ctx: Store, @@ -120,17 +136,21 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 30..35, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 30..34, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 31..34, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_targets_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_targets_terminator.py.snap index f188573e181461..52e3ad233850c4 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_targets_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@assign_targets_terminator.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/assign_targets_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..39, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("y"), ctx: Store, @@ -30,6 +33,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("z"), ctx: Store, @@ -38,6 +42,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 12..13, value: Int( 1, @@ -48,13 +53,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..19, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 15..19, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("a"), ctx: Load, @@ -62,6 +70,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("b"), ctx: Load, @@ -76,10 +85,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 20..33, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("x"), ctx: Store, @@ -87,6 +98,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("y"), ctx: Store, @@ -94,6 +106,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("z"), ctx: Store, @@ -102,6 +115,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, @@ -112,13 +126,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..38, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 34..38, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("a"), ctx: Load, @@ -126,6 +143,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_for_statement.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_for_statement.py.snap index dc8808567876c9..f01730187c3117 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_for_statement.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_for_statement.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/async_for_statement.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..30, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..29, is_async: true, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..16, id: Name("target"), ctx: Store, @@ -23,6 +25,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..24, id: Name("iter"), ctx: Load, @@ -31,9 +34,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 26..29, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_function_definition.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_function_definition.py.snap index 1ec8e02a7c166c..52ec987c61341a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_function_definition.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_function_definition.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/async_function_definition.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..21, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..20, is_async: true, decorator_list: [], name: Identifier { id: Name("foo"), range: 10..13, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 13..15, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_with_statement.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_with_statement.py.snap index 2e7e68961ee63e..be892875b2369b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_with_statement.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@async_with_statement.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/async_with_statement.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..21, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 0..20, is_async: true, items: [ WithItem { range: 11..15, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..15, id: Name("item"), ctx: Load, @@ -30,9 +33,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 17..20, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_def_arguments.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_def_arguments.py.snap index 37e765799fed32..d1f1b93250fe3c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_def_arguments.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_def_arguments.py.snap @@ -1,31 +1,35 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/class_def_arguments.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..32, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..14, decorator_list: [], name: Identifier { id: Name("Foo"), range: 6..9, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..14, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 11..14, }, ), @@ -36,16 +40,19 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 15..31, decorator_list: [], name: Identifier { id: Name("Foo"), range: 21..24, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 24..26, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -53,9 +60,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..31, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 28..31, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap index 99fc05f67b6a3e..ad057f45f606e6 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..34, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..33, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 2, @@ -23,11 +26,14 @@ Module( cases: [ MatchCase { range: 13..33, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 18..28, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..23, id: Name("Class"), ctx: Load, @@ -35,22 +41,27 @@ Module( ), arguments: PatternArguments { range: 23..28, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 24..27, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 24..25, + node_index: AtomicNodeIndex(..), }, pattern: MatchAs( PatternMatchAs { range: 26..27, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 26..27, + node_index: AtomicNodeIndex(..), }, ), }, @@ -64,9 +75,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..33, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 30..33, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap index 8ca2d420378d47..e55236a7065768 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap @@ -7,34 +7,42 @@ input_file: crates/ruff_python_parser/resources/inline/ok/class_type_params_py31 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..96, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 44..95, decorator_list: [], name: Identifier { id: Name("Foo"), range: 50..53, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 53..90, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 54..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("S"), range: 54..55, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 57..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..61, id: Name("str"), ctx: Load, @@ -42,6 +50,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..68, id: Name("bytes"), ctx: Load, @@ -59,13 +68,16 @@ Module( TypeVar( TypeParamTypeVar { range: 71..79, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 71..72, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..79, id: Name("float"), ctx: Load, @@ -78,9 +90,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 81..84, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 82..84, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -88,9 +102,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 86..89, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 88..89, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -102,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..95, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 92..95, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@comma_separated_regular_list_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@comma_separated_regular_list_terminator.py.snap index 78f64ab6bdce9e..9d9c72049a34ce 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@comma_separated_regular_list_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@comma_separated_regular_list_terminator.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/comma_separated_regular_list_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..181, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 141..144, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 141..144, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 142..143, value: Int( 0, @@ -33,13 +36,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 145..151, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 145..151, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 146..147, value: Int( 0, @@ -48,6 +54,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 1, @@ -62,13 +69,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 152..159, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 152..159, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 153..154, value: Int( 0, @@ -77,6 +87,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 156..157, value: Int( 1, @@ -91,13 +102,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 160..169, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 160..169, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 161..162, value: Int( 0, @@ -106,6 +120,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 1, @@ -114,6 +129,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 167..168, value: Int( 2, @@ -128,13 +144,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 170..180, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 170..180, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 171..172, value: Int( 0, @@ -143,6 +162,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 174..175, value: Int( 1, @@ -151,6 +171,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 177..178, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@debug_rename_import.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@debug_rename_import.py.snap index 6289f593278247..3a7f83d488b45a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@debug_rename_import.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@debug_rename_import.py.snap @@ -7,22 +7,27 @@ input_file: crates/ruff_python_parser/resources/inline/ok/debug_rename_import.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..86, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..25, names: [ Alias { range: 7..25, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 7..16, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("debug"), range: 20..25, + node_index: AtomicNodeIndex(..), }, ), }, @@ -31,19 +36,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 26..52, module: Some( Identifier { id: Name("__debug__"), range: 31..40, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 48..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Some"), range: 48..52, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -53,24 +62,29 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 53..85, module: Some( Identifier { id: Name("x"), range: 58..59, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 67..85, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("__debug__"), range: 67..76, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("debug"), range: 80..85, + node_index: AtomicNodeIndex(..), }, ), }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_async_function.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_async_function.py.snap index 53f7aff8e85075..14d34fa18b017c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_async_function.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_async_function.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/decorator_async_function.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..32, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..31, is_async: true, decorator_list: [ Decorator { range: 0..10, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..10, id: Name("decorator"), ctx: Load, @@ -29,10 +32,14 @@ Module( name: Identifier { id: Name("foo"), range: 21..24, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 24..26, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -43,9 +50,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..31, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 28..31, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_await_expression_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_await_expression_py39.py.snap index 20b6b6d6dfb47d..4ad9fb1dc8face 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_await_expression_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_await_expression_py39.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/decorator_await_expres ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..96, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..95, is_async: true, decorator_list: [], name: Identifier { id: Name("foo"), range: 55..58, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 58..60, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 66..95, is_async: false, decorator_list: [ Decorator { range: 66..76, + node_index: AtomicNodeIndex(..), expression: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 67..76, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..76, id: Name("bar"), ctx: Load, @@ -53,10 +63,14 @@ Module( name: Identifier { id: Name("baz"), range: 85..88, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 88..90, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -67,9 +81,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..95, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 92..95, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_dotted_ident_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_dotted_ident_py38.py.snap index c18f2aa994db4e..25bdacc7477a89 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_dotted_ident_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_dotted_ident_py38.py.snap @@ -7,23 +7,29 @@ input_file: crates/ruff_python_parser/resources/inline/ok/decorator_expression_d ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..86, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..85, is_async: false, decorator_list: [ Decorator { range: 45..69, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..69, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..61, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..53, id: Name("buttons"), ctx: Load, @@ -32,6 +38,7 @@ Module( attr: Identifier { id: Name("clicked"), range: 54..61, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -39,6 +46,7 @@ Module( attr: Identifier { id: Name("connect"), range: 62..69, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -48,10 +56,14 @@ Module( name: Identifier { id: Name("spam"), range: 74..78, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 78..80, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -62,9 +74,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..85, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 82..85, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_eval_hack_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_eval_hack_py38.py.snap index ff8fce644ff083..d3208630da234c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_eval_hack_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_eval_hack_py38.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/inline/ok/decorator_expression_e ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..97, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..96, is_async: false, decorator_list: [ Decorator { range: 45..80, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 46..80, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..50, id: Name("eval"), ctx: Load, @@ -28,14 +33,17 @@ Module( ), arguments: Arguments { range: 50..80, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 51..79, value: StringLiteralValue { inner: Single( StringLiteral { range: 51..79, + node_index: AtomicNodeIndex(..), value: "buttons[0].clicked.connect", flags: StringLiteralFlags { quote_style: Double, @@ -57,10 +65,14 @@ Module( name: Identifier { id: Name("spam"), range: 85..89, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 89..91, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -71,9 +83,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..96, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 93..96, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_identity_hack_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_identity_hack_py38.py.snap index cedb9372d8c7d6..f83bba2ca64824 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_identity_hack_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_identity_hack_py38.py.snap @@ -7,29 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/ok/decorator_expression_i ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..111, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..63, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 49..50, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 50..53, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 51..52, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 51..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 51..52, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -44,10 +53,12 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 55..63, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -61,16 +72,20 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 64..110, is_async: false, decorator_list: [ Decorator { range: 64..94, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 65..94, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("_"), ctx: Load, @@ -78,18 +93,23 @@ Module( ), arguments: Arguments { range: 66..94, + node_index: AtomicNodeIndex(..), args: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 67..93, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 67..85, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 67..77, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..74, id: Name("buttons"), ctx: Load, @@ -97,6 +117,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 0, @@ -109,6 +130,7 @@ Module( attr: Identifier { id: Name("clicked"), range: 78..85, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -116,6 +138,7 @@ Module( attr: Identifier { id: Name("connect"), range: 86..93, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -130,10 +153,14 @@ Module( name: Identifier { id: Name("spam"), range: 99..103, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 103..105, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -144,9 +171,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..110, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 107..110, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_py39.py.snap index 50c0502003ea5e..500d2a4b2389be 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@decorator_expression_py39.py.snap @@ -7,26 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/ok/decorator_expression_p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..129, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 45..88, is_async: false, decorator_list: [ Decorator { range: 45..72, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..72, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 46..64, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 46..56, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..53, id: Name("buttons"), ctx: Load, @@ -34,6 +41,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 0, @@ -46,6 +54,7 @@ Module( attr: Identifier { id: Name("clicked"), range: 57..64, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -53,6 +62,7 @@ Module( attr: Identifier { id: Name("connect"), range: 65..72, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -62,10 +72,14 @@ Module( name: Identifier { id: Name("spam"), range: 77..81, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 81..83, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -76,9 +90,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..88, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 85..88, }, ), @@ -89,19 +105,24 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 89..128, is_async: false, decorator_list: [ Decorator { range: 89..113, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 90..113, func: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 91..107, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("x"), ctx: Store, @@ -109,19 +130,26 @@ Module( ), value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 96..107, parameters: Some( Parameters { range: 103..104, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 103..104, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 103..104, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 103..104, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -135,6 +163,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..107, id: Name("x"), ctx: Load, @@ -146,9 +175,11 @@ Module( ), arguments: Arguments { range: 108..113, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..112, id: Name("foo"), ctx: Load, @@ -164,10 +195,14 @@ Module( name: Identifier { id: Name("bar"), range: 118..121, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 121..123, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -178,9 +213,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..128, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 125..128, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_debug_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_debug_py38.py.snap index 1ba0db0c2d5335..ebaae1fd524662 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_debug_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_debug_py38.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/del_debug_py38.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 43..56, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..56, id: Name("__debug__"), ctx: Del, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_targets_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_targets_terminator.py.snap index 8c0830ab6e4021..cd953e90416dd1 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_targets_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@del_targets_terminator.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/del_targets_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..29, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..8, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("a"), ctx: Del, @@ -23,6 +25,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("b"), ctx: Del, @@ -33,13 +36,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..14, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 10..14, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("c"), ctx: Load, @@ -47,6 +53,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("d"), ctx: Load, @@ -61,10 +68,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 15..23, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("a"), ctx: Del, @@ -72,6 +81,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("b"), ctx: Del, @@ -82,13 +92,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..28, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 24..28, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("c"), ctx: Load, @@ -96,6 +109,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("d"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@dotted_name_normalized_spaces.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@dotted_name_normalized_spaces.py.snap index 2d63a9baf14336..9c3c3f576f89c2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@dotted_name_normalized_spaces.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@dotted_name_normalized_spaces.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/dotted_name_normalized_spaces.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..32, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..12, names: [ Alias { range: 7..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a.b.c"), range: 7..12, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -27,13 +30,16 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 13..31, names: [ Alias { range: 20..31, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a.b.c"), range: 20..31, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@duplicate_match_key_attr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@duplicate_match_key_attr.py.snap index 56b342c8b5eb2e..277f35c1505e2e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@duplicate_match_key_attr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@duplicate_match_key_attr.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/duplicate_match_key_at ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..40, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..39, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,15 +25,19 @@ Module( cases: [ MatchCase { range: 13..39, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 18..34, + node_index: AtomicNodeIndex(..), keys: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 19..22, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("x"), ctx: Load, @@ -39,15 +46,18 @@ Module( attr: Identifier { id: Name("a"), range: 21..22, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 27..30, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("x"), ctx: Load, @@ -56,6 +66,7 @@ Module( attr: Identifier { id: Name("a"), range: 29..30, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -65,8 +76,10 @@ Module( MatchValue( PatternMatchValue { range: 24..25, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -78,8 +91,10 @@ Module( MatchValue( PatternMatchValue { range: 32..33, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 2, @@ -96,9 +111,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..39, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 36..39, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap index f0a6018f84d553..c3efa0df923313 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/ok/except_star_py311.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..77, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 44..76, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), @@ -28,9 +32,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 53..76, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..71, id: Name("ValueError"), ctx: Load, @@ -41,9 +47,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 73..76, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 73..76, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_as_name_soft_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_as_name_soft_keyword.py.snap index c27aa0a2e9139f..07ef1ddbd3c03e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_as_name_soft_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_as_name_soft_keyword.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/except_stmt_as_name_soft_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..100, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..99, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5..8, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5..8, }, ), @@ -29,9 +32,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 9..39, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..25, id: Name("Exception"), ctx: Load, @@ -42,14 +47,17 @@ Module( Identifier { id: Name("match"), range: 29..34, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..39, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 36..39, }, ), @@ -61,9 +69,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 40..69, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..56, id: Name("Exception"), ctx: Load, @@ -74,14 +84,17 @@ Module( Identifier { id: Name("case"), range: 60..64, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..69, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 66..69, }, ), @@ -93,9 +106,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 70..99, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..86, id: Name("Exception"), ctx: Load, @@ -106,14 +121,17 @@ Module( Identifier { id: Name("type"), range: 90..94, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_unparenthesized_tuple_no_as_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_unparenthesized_tuple_no_as_py314.py.snap index 044e166fa61e43..bf94e92b01cfd7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_unparenthesized_tuple_no_as_py314.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_stmt_unparenthesized_tuple_no_as_py314.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/except_stmt_unparenthe ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..117, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 44..79, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 53..57, }, ), @@ -23,13 +26,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 58..79, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 65..69, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Load, @@ -37,6 +43,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("y"), ctx: Load, @@ -52,6 +59,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 75..79, }, ), @@ -66,10 +74,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 80..116, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 89..93, }, ), @@ -78,13 +88,16 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 94..116, + node_index: AtomicNodeIndex(..), type_: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 102..106, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("x"), ctx: Load, @@ -92,6 +105,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("y"), ctx: Load, @@ -107,6 +121,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 112..116, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__arguments.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__arguments.py.snap index a7b3283c41e660..d5977dbfc4e144 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__arguments.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__arguments.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/valid/expressions/arguments.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..805, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 102..108, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 102..108, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..106, id: Name("call"), ctx: Load, @@ -24,6 +28,7 @@ Module( ), arguments: Arguments { range: 106..108, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -33,12 +38,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..119, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 109..119, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..113, id: Name("call"), ctx: Load, @@ -46,9 +54,11 @@ Module( ), arguments: Arguments { range: 113..119, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("x"), ctx: Load, @@ -56,6 +66,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 117..118, id: Name("y"), ctx: Load, @@ -70,12 +81,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 120..131, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 120..131, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..124, id: Name("call"), ctx: Load, @@ -83,9 +97,11 @@ Module( ), arguments: Arguments { range: 124..131, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("x"), ctx: Load, @@ -93,6 +109,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("y"), ctx: Load, @@ -107,12 +124,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 150..164, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 150..164, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..154, id: Name("call"), ctx: Load, @@ -120,18 +140,22 @@ Module( ), arguments: Arguments { range: 154..164, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 155..158, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 155..156, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 157..158, value: Int( 1, @@ -141,14 +165,17 @@ Module( }, Keyword { range: 160..163, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("y"), range: 160..161, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 162..163, value: Int( 2, @@ -164,12 +191,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 165..173, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 165..173, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..169, id: Name("call"), ctx: Load, @@ -177,12 +207,15 @@ Module( ), arguments: Arguments { range: 169..173, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 170..172, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("x"), ctx: Load, @@ -200,12 +233,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 174..183, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 174..183, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 174..178, id: Name("call"), ctx: Load, @@ -213,13 +249,16 @@ Module( ), arguments: Arguments { range: 178..183, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 179..182, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 181..182, id: Name("x"), ctx: Load, @@ -234,12 +273,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 193..205, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 193..205, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..197, id: Name("call"), ctx: Load, @@ -247,9 +289,11 @@ Module( ), arguments: Arguments { range: 197..205, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 198..199, id: Name("x"), ctx: Load, @@ -259,14 +303,17 @@ Module( keywords: [ Keyword { range: 201..204, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("y"), range: 201..202, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 203..204, value: Int( 1, @@ -282,12 +329,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 206..217, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 206..217, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 206..210, id: Name("call"), ctx: Load, @@ -295,9 +345,11 @@ Module( ), arguments: Arguments { range: 210..217, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 211..212, id: Name("x"), ctx: Load, @@ -305,9 +357,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 214..216, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 215..216, id: Name("y"), ctx: Load, @@ -325,12 +379,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 218..230, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 218..230, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 218..222, id: Name("call"), ctx: Load, @@ -338,9 +395,11 @@ Module( ), arguments: Arguments { range: 222..230, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 223..224, id: Name("x"), ctx: Load, @@ -350,9 +409,11 @@ Module( keywords: [ Keyword { range: 226..229, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 228..229, id: Name("y"), ctx: Load, @@ -367,12 +428,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 231..244, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 231..244, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 231..235, id: Name("call"), ctx: Load, @@ -380,12 +444,15 @@ Module( ), arguments: Arguments { range: 235..244, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 241..243, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 242..243, id: Name("y"), ctx: Load, @@ -398,14 +465,17 @@ Module( keywords: [ Keyword { range: 236..239, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 236..237, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 238..239, value: Int( 1, @@ -421,12 +491,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 245..259, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 245..259, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..249, id: Name("call"), ctx: Load, @@ -434,18 +507,22 @@ Module( ), arguments: Arguments { range: 249..259, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 250..253, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 250..251, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 252..253, value: Int( 1, @@ -455,9 +532,11 @@ Module( }, Keyword { range: 255..258, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 257..258, id: Name("y"), ctx: Load, @@ -472,12 +551,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 260..273, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 260..273, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..264, id: Name("call"), ctx: Load, @@ -485,12 +567,15 @@ Module( ), arguments: Arguments { range: 264..273, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 265..267, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..267, id: Name("x"), ctx: Load, @@ -503,9 +588,11 @@ Module( keywords: [ Keyword { range: 269..272, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 271..272, id: Name("y"), ctx: Load, @@ -520,12 +607,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 274..288, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 274..288, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 274..278, id: Name("call"), ctx: Load, @@ -533,12 +623,15 @@ Module( ), arguments: Arguments { range: 278..288, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 279..281, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 280..281, id: Name("x"), ctx: Load, @@ -549,6 +642,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 283..284, id: Name("y"), ctx: Load, @@ -556,6 +650,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 286..287, id: Name("z"), ctx: Load, @@ -570,12 +665,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 289..308, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 289..308, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 289..293, id: Name("call"), ctx: Load, @@ -583,13 +681,16 @@ Module( ), arguments: Arguments { range: 293..308, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 294..297, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 296..297, id: Name("x"), ctx: Load, @@ -598,14 +699,17 @@ Module( }, Keyword { range: 299..302, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("y"), range: 299..300, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 301..302, value: Int( 1, @@ -615,14 +719,17 @@ Module( }, Keyword { range: 304..307, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("z"), range: 304..305, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 306..307, value: Int( 2, @@ -638,12 +745,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 309..335, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 309..335, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 309..313, id: Name("call"), ctx: Load, @@ -651,12 +761,15 @@ Module( ), arguments: Arguments { range: 313..335, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 314..317, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 315..317, id: Name("x1"), ctx: Load, @@ -667,9 +780,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 319..322, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 320..322, id: Name("x2"), ctx: Load, @@ -682,9 +797,11 @@ Module( keywords: [ Keyword { range: 324..328, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 326..328, id: Name("y1"), ctx: Load, @@ -693,9 +810,11 @@ Module( }, Keyword { range: 330..334, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 332..334, id: Name("y2"), ctx: Load, @@ -710,12 +829,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 336..355, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 336..355, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 336..340, id: Name("call"), ctx: Load, @@ -723,18 +845,22 @@ Module( ), arguments: Arguments { range: 340..355, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 341..344, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 341..342, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 343..344, value: Int( 1, @@ -744,9 +870,11 @@ Module( }, Keyword { range: 346..349, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 348..349, id: Name("y"), ctx: Load, @@ -755,14 +883,17 @@ Module( }, Keyword { range: 351..354, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("z"), range: 351..352, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 353..354, value: Int( 1, @@ -778,12 +909,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 378..402, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 378..402, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 378..382, id: Name("call"), ctx: Load, @@ -791,27 +925,33 @@ Module( ), arguments: Arguments { range: 382..402, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 383..401, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 383..384, + node_index: AtomicNodeIndex(..), }, ), value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 385..401, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 390..394, value: true, }, ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 385..386, value: Int( 1, @@ -820,6 +960,7 @@ Module( ), orelse: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 400..401, value: Int( 2, @@ -837,12 +978,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 403..418, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 403..418, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 403..407, id: Name("call"), ctx: Load, @@ -850,21 +994,26 @@ Module( ), arguments: Arguments { range: 407..418, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 408..417, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 408..409, + node_index: AtomicNodeIndex(..), }, ), value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 410..417, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 416..417, id: Name("y"), ctx: Load, @@ -881,12 +1030,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 419..438, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 419..438, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 419..423, id: Name("call"), ctx: Load, @@ -894,31 +1046,41 @@ Module( ), arguments: Arguments { range: 423..438, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 424..437, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 424..425, + node_index: AtomicNodeIndex(..), }, ), value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 426..437, parameters: Some( Parameters { range: 433..434, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 433..434, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 433..434, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 433..434, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -932,6 +1094,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 436..437, id: Name("y"), ctx: Load, @@ -948,12 +1111,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 439..455, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 439..455, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 439..443, id: Name("call"), ctx: Load, @@ -961,21 +1127,26 @@ Module( ), arguments: Arguments { range: 443..455, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 444..454, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("x"), range: 444..445, + node_index: AtomicNodeIndex(..), }, ), value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 447..453, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 447..448, id: Name("y"), ctx: Store, @@ -983,6 +1154,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 452..453, value: Int( 1, @@ -1000,12 +1172,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 476..491, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 476..491, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 476..480, id: Name("call"), ctx: Load, @@ -1013,13 +1188,16 @@ Module( ), arguments: Arguments { range: 480..491, + node_index: AtomicNodeIndex(..), args: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 482..489, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 488..489, id: Name("x"), ctx: Load, @@ -1037,12 +1215,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 492..512, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 492..512, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 492..496, id: Name("call"), ctx: Load, @@ -1050,12 +1231,15 @@ Module( ), arguments: Arguments { range: 496..512, + node_index: AtomicNodeIndex(..), args: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 498..510, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 509..510, id: Name("x"), ctx: Load, @@ -1072,12 +1256,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 533..545, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 533..545, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 533..537, id: Name("call"), ctx: Load, @@ -1085,12 +1272,15 @@ Module( ), arguments: Arguments { range: 537..545, + node_index: AtomicNodeIndex(..), args: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 538..544, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 538..539, id: Name("x"), ctx: Store, @@ -1098,6 +1288,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 543..544, value: Int( 1, @@ -1115,12 +1306,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 546..572, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 546..572, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 546..550, id: Name("call"), ctx: Load, @@ -1128,15 +1322,19 @@ Module( ), arguments: Arguments { range: 550..572, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 551..571, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 551..557, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 551..552, id: Name("x"), ctx: Store, @@ -1144,6 +1342,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 556..557, value: Int( 1, @@ -1155,8 +1354,10 @@ Module( generators: [ Comprehension { range: 558..571, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 562..563, id: Name("i"), ctx: Store, @@ -1164,6 +1365,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 567..571, id: Name("iter"), ctx: Load, @@ -1185,12 +1387,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 596..610, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 596..610, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 596..600, id: Name("call"), ctx: Load, @@ -1198,17 +1403,21 @@ Module( ), arguments: Arguments { range: 600..610, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 601..609, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 602..609, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 602..603, id: Name("x"), ctx: Load, @@ -1216,6 +1425,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 608..609, id: Name("y"), ctx: Load, @@ -1236,12 +1446,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 611..623, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 611..623, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 611..615, id: Name("call"), ctx: Load, @@ -1249,15 +1462,19 @@ Module( ), arguments: Arguments { range: 615..623, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 616..622, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 617..622, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 617..618, id: Name("x"), ctx: Load, @@ -1266,6 +1483,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 621..622, id: Name("y"), ctx: Load, @@ -1285,12 +1503,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 624..638, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 624..638, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 624..628, id: Name("call"), ctx: Load, @@ -1298,15 +1519,19 @@ Module( ), arguments: Arguments { range: 628..638, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 629..637, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 630..637, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 636..637, id: Name("x"), ctx: Load, @@ -1326,12 +1551,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 639..657, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 639..657, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 639..643, id: Name("call"), ctx: Load, @@ -1339,25 +1567,34 @@ Module( ), arguments: Arguments { range: 643..657, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 644..656, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 645..656, parameters: Some( Parameters { range: 652..653, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 652..653, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 652..653, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 652..653, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1371,6 +1608,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 655..656, id: Name("x"), ctx: Load, @@ -1390,12 +1628,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 658..681, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 658..681, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 658..662, id: Name("call"), ctx: Load, @@ -1403,21 +1644,26 @@ Module( ), arguments: Arguments { range: 662..681, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 663..680, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 664..680, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 669..673, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 664..665, id: Name("x"), ctx: Load, @@ -1425,6 +1671,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 679..680, id: Name("y"), ctx: Load, @@ -1444,12 +1691,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 700..709, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 700..709, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 700..704, id: Name("call"), ctx: Load, @@ -1457,13 +1707,16 @@ Module( ), arguments: Arguments { range: 704..709, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 705..708, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 707..708, id: Name("x"), ctx: Load, @@ -1478,12 +1731,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 710..725, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 710..725, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 710..714, id: Name("call"), ctx: Load, @@ -1491,18 +1747,22 @@ Module( ), arguments: Arguments { range: 714..725, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 715..724, + node_index: AtomicNodeIndex(..), arg: None, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 717..724, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 717..718, id: Name("x"), ctx: Load, @@ -1510,6 +1770,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 723..724, id: Name("y"), ctx: Load, @@ -1527,12 +1788,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 726..741, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 726..741, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 726..730, id: Name("call"), ctx: Load, @@ -1540,16 +1804,20 @@ Module( ), arguments: Arguments { range: 730..741, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 731..740, + node_index: AtomicNodeIndex(..), arg: None, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 733..740, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 739..740, id: Name("x"), ctx: Load, @@ -1566,12 +1834,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 742..766, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 742..766, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 742..746, id: Name("call"), ctx: Load, @@ -1579,22 +1850,27 @@ Module( ), arguments: Arguments { range: 746..766, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 747..765, + node_index: AtomicNodeIndex(..), arg: None, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 749..765, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 754..758, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 749..750, id: Name("x"), ctx: Load, @@ -1602,6 +1878,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 764..765, id: Name("y"), ctx: Load, @@ -1618,12 +1895,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 767..784, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 767..784, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 767..771, id: Name("call"), ctx: Load, @@ -1631,17 +1911,21 @@ Module( ), arguments: Arguments { range: 771..784, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 772..783, + node_index: AtomicNodeIndex(..), arg: None, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 775..782, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 781..782, id: Name("x"), ctx: Load, @@ -1659,12 +1943,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 785..804, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 785..804, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 785..789, id: Name("call"), ctx: Load, @@ -1672,26 +1959,35 @@ Module( ), arguments: Arguments { range: 789..804, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 790..803, + node_index: AtomicNodeIndex(..), arg: None, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 792..803, parameters: Some( Parameters { range: 799..800, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 799..800, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 799..800, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 799..800, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1705,6 +2001,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 802..803, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__attribute.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__attribute.py.snap index 2a37d0a8a9da03..e6b46d32b7b584 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__attribute.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__attribute.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/attribute.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..90, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 0..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("value"), ctx: Load, @@ -26,6 +29,7 @@ Module( attr: Identifier { id: Name("attr"), range: 6..10, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -34,15 +38,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..23, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 11..23, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 11..21, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..16, id: Name("value"), ctx: Load, @@ -51,12 +59,14 @@ Module( attr: Identifier { id: Name("attr"), range: 17..21, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 21..23, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -66,15 +76,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..36, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 24..36, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 24..31, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..29, id: Name("value"), ctx: Load, @@ -82,6 +96,7 @@ Module( ), arguments: Arguments { range: 29..31, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -90,6 +105,7 @@ Module( attr: Identifier { id: Name("attr"), range: 32..36, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -98,21 +114,27 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..55, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 37..55, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 37..51, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 37..49, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 37..44, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..42, id: Name("value"), ctx: Load, @@ -120,6 +142,7 @@ Module( ), arguments: Arguments { range: 42..44, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -128,12 +151,14 @@ Module( attr: Identifier { id: Name("attr"), range: 45..49, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 49..51, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -142,6 +167,7 @@ Module( attr: Identifier { id: Name("foo"), range: 52..55, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -150,15 +176,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..70, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 56..70, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 56..66, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..61, id: Name("value"), ctx: Load, @@ -167,6 +197,7 @@ Module( attr: Identifier { id: Name("attr"), range: 62..66, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -174,6 +205,7 @@ Module( attr: Identifier { id: Name("foo"), range: 67..70, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -182,18 +214,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..89, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 71..89, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 71..85, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 71..83, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..77, id: Name("value"), ctx: Load, @@ -202,12 +239,14 @@ Module( attr: Identifier { id: Name("attr"), range: 79..83, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 83..85, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -216,6 +255,7 @@ Module( attr: Identifier { id: Name("foo"), range: 86..89, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap index e5351ffe1e94ea..d2791d35df9a42 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/await.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..211, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 0..7, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -29,15 +32,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..19, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 8..19, left: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 8..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Load, @@ -48,6 +55,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 1, @@ -60,17 +68,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..33, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 20..33, op: And, values: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 20..27, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("a"), ctx: Load, @@ -80,6 +92,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("b"), ctx: Load, @@ -92,15 +105,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..43, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 34..43, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 40..43, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("f"), ctx: Load, @@ -108,6 +125,7 @@ Module( ), arguments: Arguments { range: 41..43, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -119,16 +137,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..56, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 44..56, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 50..56, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, @@ -137,6 +159,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 2, @@ -153,16 +176,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..69, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 57..69, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 63..69, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..65, value: Int( 3, @@ -171,6 +198,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 67..68, value: Int( 4, @@ -186,18 +214,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..82, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 70..82, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 76..82, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("i"), ctx: Load, @@ -206,6 +238,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 80..81, value: Int( 5, @@ -222,16 +255,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..93, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 83..93, elts: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 83..90, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 7, @@ -242,6 +279,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 92..93, value: Int( 8, @@ -257,16 +295,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..107, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 94..107, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 100..107, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 101..102, value: Int( 9, @@ -275,6 +317,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..106, value: Int( 10, @@ -292,15 +335,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 108..120, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 108..120, left: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 108..115, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 114..115, value: Int( 1, @@ -315,6 +362,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -328,21 +376,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 121..146, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 121..146, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 132..136, value: true, }, ), body: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 121..128, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("x"), ctx: Load, @@ -352,6 +405,7 @@ Module( ), orelse: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 142..146, }, ), @@ -361,19 +415,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 147..158, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 147..158, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 153..158, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 154..156, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("x"), ctx: Load, @@ -393,25 +452,34 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 159..178, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 159..178, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 166..177, parameters: Some( Parameters { range: 173..174, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 173..174, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 173..174, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 173..174, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -425,6 +493,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..177, id: Name("x"), ctx: Load, @@ -438,15 +507,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 179..192, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 179..192, left: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 179..186, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..186, id: Name("x"), ctx: Load, @@ -457,10 +530,12 @@ Module( op: Pow, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 190..192, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 191..192, id: Name("x"), ctx: Load, @@ -474,15 +549,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 193..211, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 193..211, left: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 193..200, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("x"), ctx: Load, @@ -493,9 +572,11 @@ Module( op: Pow, right: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 204..211, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 210..211, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bin_op.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bin_op.py.snap index a6cc4528a1bfe3..ac83b26a4d1a88 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bin_op.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bin_op.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/valid/expressions/bin_op.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..397, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..14, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 9..14, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, @@ -26,6 +30,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 2, @@ -38,12 +43,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..20, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 15..20, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 15..16, value: Int( 1, @@ -53,6 +61,7 @@ Module( op: Sub, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 2, @@ -65,12 +74,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..26, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 21..26, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 1, @@ -80,6 +92,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 2, @@ -92,12 +105,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..32, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 27..32, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 27..28, value: Int( 1, @@ -107,6 +123,7 @@ Module( op: Div, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 31..32, value: Int( 2, @@ -119,12 +136,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 33..39, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 33..39, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 33..34, value: Int( 1, @@ -134,6 +154,7 @@ Module( op: FloorDiv, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..39, value: Int( 2, @@ -146,12 +167,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..45, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 40..45, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 40..41, value: Int( 1, @@ -161,6 +185,7 @@ Module( op: Mod, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..45, value: Int( 2, @@ -173,12 +198,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..52, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 46..52, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 46..47, value: Int( 1, @@ -188,6 +216,7 @@ Module( op: Pow, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 2, @@ -200,12 +229,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..58, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 53..58, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 1, @@ -215,6 +247,7 @@ Module( op: BitOr, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 2, @@ -227,12 +260,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..64, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 59..64, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 59..60, value: Int( 1, @@ -242,6 +278,7 @@ Module( op: BitXor, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 2, @@ -254,12 +291,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..70, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 65..70, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 1, @@ -269,6 +309,7 @@ Module( op: BitAnd, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 69..70, value: Int( 2, @@ -281,12 +322,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..77, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 71..77, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 1, @@ -296,6 +340,7 @@ Module( op: RShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 2, @@ -308,12 +353,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..84, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 78..84, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 1, @@ -323,6 +371,7 @@ Module( op: LShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 2, @@ -335,12 +384,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..90, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 85..90, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 85..86, value: Int( 1, @@ -350,6 +402,7 @@ Module( op: MatMult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 2, @@ -362,18 +415,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 110..123, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 110..123, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 110..119, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 110..115, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 110..111, value: Int( 1, @@ -383,6 +441,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 114..115, value: Int( 2, @@ -394,6 +453,7 @@ Module( op: Sub, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 3, @@ -405,6 +465,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 4, @@ -417,24 +478,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 124..146, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 124..146, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 124..142, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 124..138, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 124..133, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 124..129, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 124..125, value: Int( 1, @@ -444,6 +512,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 2, @@ -455,6 +524,7 @@ Module( op: Div, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 132..133, value: Int( 3, @@ -466,6 +536,7 @@ Module( op: FloorDiv, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 137..138, value: Int( 4, @@ -477,6 +548,7 @@ Module( op: MatMult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 141..142, value: Int( 5, @@ -488,6 +560,7 @@ Module( op: Mod, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 145..146, value: Int( 6, @@ -500,21 +573,27 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 147..168, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 147..168, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 147..163, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 147..158, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 147..153, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 147..148, value: Int( 1, @@ -524,6 +603,7 @@ Module( op: LShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 152..153, value: Int( 2, @@ -535,6 +615,7 @@ Module( op: RShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 157..158, value: Int( 3, @@ -546,6 +627,7 @@ Module( op: RShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 162..163, value: Int( 4, @@ -557,6 +639,7 @@ Module( op: LShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 167..168, value: Int( 5, @@ -569,12 +652,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 193..202, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 193..202, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 193..194, value: Int( 1, @@ -584,9 +670,11 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 197..202, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..198, value: Int( 2, @@ -596,6 +684,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 3, @@ -610,15 +699,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 203..212, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 203..212, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 203..208, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 203..204, value: Int( 1, @@ -628,6 +721,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 207..208, value: Int( 2, @@ -639,6 +733,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 211..212, value: Int( 3, @@ -651,24 +746,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 213..244, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 213..244, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 213..235, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 213..231, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 213..223, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 213..219, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..214, value: Int( 1, @@ -678,6 +780,7 @@ Module( op: Pow, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 218..219, value: Int( 2, @@ -689,6 +792,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 222..223, value: Int( 3, @@ -700,9 +804,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 226..231, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 226..227, value: Int( 4, @@ -712,6 +818,7 @@ Module( op: MatMult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 230..231, value: Int( 5, @@ -725,6 +832,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 234..235, value: Int( 6, @@ -736,9 +844,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 238..244, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 238..239, value: Int( 7, @@ -748,6 +858,7 @@ Module( op: FloorDiv, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 243..244, value: Int( 8, @@ -762,12 +873,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 270..306, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 270..306, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 270..271, value: Int( 1, @@ -777,12 +891,15 @@ Module( op: BitOr, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 274..306, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 274..279, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 274..275, value: Int( 2, @@ -792,6 +909,7 @@ Module( op: BitAnd, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 278..279, value: Int( 3, @@ -803,15 +921,19 @@ Module( op: BitXor, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 282..306, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 282..301, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 282..291, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 282..283, value: Int( 4, @@ -821,9 +943,11 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 286..291, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 286..287, value: Int( 5, @@ -833,6 +957,7 @@ Module( op: MatMult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 290..291, value: Int( 6, @@ -846,9 +971,11 @@ Module( op: LShift, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 295..301, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 295..296, value: Int( 7, @@ -858,6 +985,7 @@ Module( op: FloorDiv, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 300..301, value: Int( 8, @@ -871,6 +999,7 @@ Module( op: RShift, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 305..306, value: Int( 9, @@ -887,15 +1016,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 324..339, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 324..339, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 324..335, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 324..325, value: Int( 1, @@ -905,9 +1038,11 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 329..334, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 329..330, value: Int( 2, @@ -917,6 +1052,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 333..334, value: Int( 3, @@ -930,6 +1066,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 338..339, value: Int( 4, @@ -942,15 +1079,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 340..359, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 340..359, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 340..345, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 340..341, value: Int( 1, @@ -960,6 +1101,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 344..345, value: Int( 2, @@ -971,12 +1113,15 @@ Module( op: Add, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 349..358, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 349..354, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 349..350, value: Int( 3, @@ -986,6 +1131,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 353..354, value: Int( 4, @@ -997,6 +1143,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 357..358, value: Int( 5, @@ -1011,12 +1158,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 390..396, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 390..396, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 390..391, id: Name("x"), ctx: Load, @@ -1025,10 +1175,12 @@ Module( op: Add, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 393..396, op: UAdd, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 395..396, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bool_op.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bool_op.py.snap index 53f4b05b7a1ccb..7805f521fb4f39 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bool_op.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__bool_op.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/bool_op.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..142, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..7, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 0..7, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Load, @@ -27,6 +30,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("b"), ctx: Load, @@ -39,14 +43,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 8..21, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 8..21, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("a"), ctx: Load, @@ -54,6 +61,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("b"), ctx: Load, @@ -61,6 +69,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("c"), ctx: Load, @@ -73,14 +82,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..28, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 22..28, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("a"), ctx: Load, @@ -88,6 +100,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..28, id: Name("b"), ctx: Load, @@ -100,14 +113,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..40, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 29..40, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("a"), ctx: Load, @@ -115,6 +131,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("b"), ctx: Load, @@ -122,6 +139,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("c"), ctx: Load, @@ -134,19 +152,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 41..53, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 41..53, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 41..48, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("a"), ctx: Load, @@ -154,6 +176,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("b"), ctx: Load, @@ -164,6 +187,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..53, id: Name("c"), ctx: Load, @@ -176,19 +200,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..88, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 54..88, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 54..67, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("a"), ctx: Load, @@ -196,6 +224,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("b"), ctx: Load, @@ -203,6 +232,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("c"), ctx: Load, @@ -213,6 +243,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("d"), ctx: Load, @@ -220,11 +251,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 76..83, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..77, id: Name("e"), ctx: Load, @@ -232,6 +265,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("f"), ctx: Load, @@ -242,6 +276,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("g"), ctx: Load, @@ -254,19 +289,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 89..105, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 89..105, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 89..100, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("a"), ctx: Load, @@ -274,10 +313,12 @@ Module( ), UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 95..100, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("b"), ctx: Load, @@ -290,6 +331,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("c"), ctx: Load, @@ -302,23 +344,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 106..124, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 106..124, value: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 112..124, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 112..119, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 112..113, id: Name("a"), ctx: Load, @@ -326,6 +373,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 118..119, id: Name("b"), ctx: Load, @@ -336,6 +384,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 123..124, id: Name("c"), ctx: Load, @@ -351,23 +400,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..141, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 125..141, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 125..136, op: And, values: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 125..130, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("a"), ctx: Load, @@ -377,6 +431,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("b"), ctx: Load, @@ -387,6 +442,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("c"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap index a83f3774421b07..7a33b5db768791 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/call.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..349, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 114..120, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 114..120, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..118, id: Name("call"), ctx: Load, @@ -25,6 +28,7 @@ Module( ), arguments: Arguments { range: 118..120, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -34,15 +38,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 121..132, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 121..132, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 121..130, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..125, id: Name("attr"), ctx: Load, @@ -51,12 +59,14 @@ Module( attr: Identifier { id: Name("expr"), range: 126..130, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 130..132, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -66,15 +76,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..150, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 133..150, func: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 133..148, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..142, id: Name("subscript"), ctx: Load, @@ -82,10 +96,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 143..147, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 143..144, value: Int( 1, @@ -94,6 +110,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 146..147, value: Int( 2, @@ -110,6 +127,7 @@ Module( ), arguments: Arguments { range: 148..150, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -119,15 +137,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 151..162, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 151..162, func: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 151..160, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..156, id: Name("slice"), ctx: Load, @@ -135,11 +157,13 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 157..159, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 1, @@ -155,6 +179,7 @@ Module( ), arguments: Arguments { range: 160..162, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -164,16 +189,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..174, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 163..174, func: List( ExprList { + node_index: AtomicNodeIndex(..), range: 163..172, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 1, @@ -182,6 +211,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 167..168, value: Int( 2, @@ -190,6 +220,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 170..171, value: Int( 3, @@ -202,6 +233,7 @@ Module( ), arguments: Arguments { range: 172..174, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -211,16 +243,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 175..186, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 175..186, func: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 175..184, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 176..177, value: Int( 1, @@ -229,6 +265,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 179..180, value: Int( 2, @@ -237,6 +274,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 182..183, value: Int( 3, @@ -250,6 +288,7 @@ Module( ), arguments: Arguments { range: 184..186, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -259,15 +298,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 187..206, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 187..206, func: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 187..204, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 188..189, id: Name("x"), ctx: Load, @@ -276,8 +319,10 @@ Module( generators: [ Comprehension { range: 190..203, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 194..195, id: Name("x"), ctx: Store, @@ -285,6 +330,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..203, id: Name("iter"), ctx: Load, @@ -299,6 +345,7 @@ Module( ), arguments: Arguments { range: 204..206, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -308,16 +355,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 207..218, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 207..218, func: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 207..216, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 208..209, value: Int( 1, @@ -326,6 +377,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 211..212, value: Int( 2, @@ -334,6 +386,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 214..215, value: Int( 3, @@ -345,6 +398,7 @@ Module( ), arguments: Arguments { range: 216..218, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -354,18 +408,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 219..233, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 219..233, func: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 219..231, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 220..221, value: Int( 1, @@ -375,6 +433,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 223..224, value: Int( 2, @@ -386,6 +445,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 226..227, value: Int( 3, @@ -395,6 +455,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 229..230, value: Int( 4, @@ -407,6 +468,7 @@ Module( ), arguments: Arguments { range: 231..233, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -416,16 +478,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 234..245, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 234..245, func: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 235..242, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..242, id: Name("x"), ctx: Load, @@ -436,6 +502,7 @@ Module( ), arguments: Arguments { range: 243..245, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -445,18 +512,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 306..312, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 306..312, func: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 306..310, value: true, }, ), arguments: Arguments { range: 310..312, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -466,18 +537,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 313..320, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 313..320, func: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 313..318, value: false, }, ), arguments: Arguments { range: 318..320, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -487,17 +562,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 321..327, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 321..327, func: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 321..325, }, ), arguments: Arguments { range: 325..327, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -507,17 +586,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 328..338, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 328..338, func: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 328..336, value: StringLiteralValue { inner: Single( StringLiteral { range: 328..336, + node_index: AtomicNodeIndex(..), value: "string", flags: StringLiteralFlags { quote_style: Double, @@ -531,6 +614,7 @@ Module( ), arguments: Arguments { range: 336..338, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -540,12 +624,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 339..342, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 339..342, func: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 339..340, value: Int( 1, @@ -554,6 +641,7 @@ Module( ), arguments: Arguments { range: 340..342, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -563,12 +651,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 343..348, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 343..348, func: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 343..346, value: Float( 1.0, @@ -577,6 +668,7 @@ Module( ), arguments: Arguments { range: 346..348, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__compare.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__compare.py.snap index ec86643e380541..e2bd2b568cd30c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__compare.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__compare.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/compare.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..542, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..15, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 9..15, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("a"), ctx: Load, @@ -29,6 +32,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("b"), ctx: Load, @@ -41,12 +45,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..21, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 16..21, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("b"), ctx: Load, @@ -58,6 +65,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("a"), ctx: Load, @@ -70,12 +78,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..27, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 22..27, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("b"), ctx: Load, @@ -87,6 +98,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("a"), ctx: Load, @@ -99,12 +111,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..34, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 28..34, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("a"), ctx: Load, @@ -116,6 +131,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..34, id: Name("b"), ctx: Load, @@ -128,12 +144,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..41, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 35..41, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("a"), ctx: Load, @@ -145,6 +164,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("b"), ctx: Load, @@ -157,12 +177,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 42..48, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 42..48, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("a"), ctx: Load, @@ -174,6 +197,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("b"), ctx: Load, @@ -186,12 +210,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..55, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 49..55, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("a"), ctx: Load, @@ -203,6 +230,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("c"), ctx: Load, @@ -215,12 +243,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..62, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 56..62, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("a"), ctx: Load, @@ -232,6 +263,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..62, id: Name("b"), ctx: Load, @@ -244,12 +276,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..73, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 63..73, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("a"), ctx: Load, @@ -261,6 +296,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("c"), ctx: Load, @@ -273,12 +309,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 74..84, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 74..84, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("a"), ctx: Load, @@ -290,6 +329,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("b"), ctx: Load, @@ -302,12 +342,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 110..156, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 110..156, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 110..111, id: Name("a"), ctx: Load, @@ -323,6 +366,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..120, id: Name("b"), ctx: Load, @@ -330,6 +374,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("c"), ctx: Load, @@ -337,6 +382,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("d"), ctx: Load, @@ -344,6 +390,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("e"), ctx: Load, @@ -351,6 +398,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("f"), ctx: Load, @@ -363,15 +411,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 177..203, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 177..203, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 177..182, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 177..178, id: Name("a"), ctx: Load, @@ -380,6 +432,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 181..182, id: Name("b"), ctx: Load, @@ -394,9 +447,11 @@ Module( comparators: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 185..190, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..186, id: Name("c"), ctx: Load, @@ -405,6 +460,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("d"), ctx: Load, @@ -414,9 +470,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 198..203, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 198..199, id: Name("e"), ctx: Load, @@ -425,6 +483,7 @@ Module( op: BitAnd, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..203, id: Name("f"), ctx: Load, @@ -439,16 +498,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 379..393, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 379..393, op: Not, operand: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 383..393, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 383..384, id: Name("x"), ctx: Load, @@ -460,6 +523,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 392..393, id: Name("y"), ctx: Load, @@ -474,14 +538,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 395..416, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 395..416, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 395..396, id: Name("x"), ctx: Load, @@ -489,14 +556,17 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 400..416, op: And, values: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 400..410, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 400..401, id: Name("y"), ctx: Load, @@ -508,6 +578,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 409..410, id: Name("z"), ctx: Load, @@ -518,6 +589,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 415..416, id: Name("a"), ctx: Load, @@ -533,12 +605,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 417..429, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 417..429, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 417..418, id: Name("x"), ctx: Load, @@ -550,9 +625,11 @@ Module( comparators: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 422..429, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 428..429, id: Name("y"), ctx: Load, @@ -567,12 +644,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 430..446, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 430..446, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 430..431, id: Name("x"), ctx: Load, @@ -584,9 +664,11 @@ Module( comparators: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 439..446, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 445..446, id: Name("y"), ctx: Load, @@ -601,12 +683,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 489..541, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 489..541, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 489..490, id: Name("a"), ctx: Load, @@ -626,6 +711,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 493..494, id: Name("b"), ctx: Load, @@ -633,6 +719,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 498..499, id: Name("c"), ctx: Load, @@ -640,6 +727,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 502..503, id: Name("d"), ctx: Load, @@ -647,6 +735,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 507..508, id: Name("e"), ctx: Load, @@ -654,6 +743,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 516..517, id: Name("f"), ctx: Load, @@ -661,6 +751,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 525..526, id: Name("g"), ctx: Load, @@ -668,6 +759,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 530..531, id: Name("h"), ctx: Load, @@ -675,6 +767,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 535..536, id: Name("i"), ctx: Load, @@ -682,6 +775,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 540..541, id: Name("j"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap index 705faf5d2c801d..d368b8654bceba 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/dictionary.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..622, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..11, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 9..11, items: [], }, @@ -23,15 +25,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..18, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 12..18, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, @@ -41,6 +46,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 2, @@ -55,15 +61,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..43, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 19..43, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 1, @@ -73,6 +82,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 2, @@ -84,6 +94,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("a"), ctx: Load, @@ -92,6 +103,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 29..30, value: Int( 1, @@ -103,6 +115,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("b"), ctx: Load, @@ -111,11 +124,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 35..42, value: StringLiteralValue { inner: Single( StringLiteral { range: 35..42, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Single, @@ -135,9 +150,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..69, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 66..69, items: [], }, @@ -146,15 +163,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..100, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 70..100, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 1, @@ -164,6 +184,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 2, @@ -175,6 +196,7 @@ Module( key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 90..91, value: Int( 3, @@ -184,6 +206,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 97..98, value: Int( 4, @@ -198,21 +221,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 111..132, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 111..132, items: [ DictItem { key: Some( Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 112..118, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 113..114, value: Int( 1, @@ -222,6 +249,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 116..117, value: Int( 2, @@ -235,12 +263,14 @@ Module( ), value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 120..131, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 121..122, value: Int( 3, @@ -250,12 +280,14 @@ Module( ), value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 124..130, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 125..126, value: Int( 4, @@ -265,6 +297,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 5, @@ -287,28 +320,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..171, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 155..171, items: [ DictItem { key: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 156..167, parameters: Some( Parameters { range: 163..164, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 163..164, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 163..164, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 163..164, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -322,6 +364,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("x"), ctx: Load, @@ -332,6 +375,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 169..170, value: Int( 1, @@ -346,20 +390,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 172..202, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 172..202, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 173..176, value: StringLiteralValue { inner: Single( StringLiteral { range: 173..176, + node_index: AtomicNodeIndex(..), value: "A", flags: StringLiteralFlags { quote_style: Single, @@ -374,19 +422,26 @@ Module( ), value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 178..192, parameters: Some( Parameters { range: 185..186, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 185..186, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 185..186, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("p"), range: 185..186, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -400,6 +455,7 @@ Module( ), body: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 188..192, }, ), @@ -410,11 +466,13 @@ Module( key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 194..197, value: StringLiteralValue { inner: Single( StringLiteral { range: 194..197, + node_index: AtomicNodeIndex(..), value: "B", flags: StringLiteralFlags { quote_style: Single, @@ -429,6 +487,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 199..200, id: Name("C"), ctx: Load, @@ -442,18 +501,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 224..237, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 224..237, items: [ DictItem { key: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 226..232, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 226..227, id: Name("x"), ctx: Store, @@ -461,6 +524,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 231..232, value: Int( 1, @@ -472,6 +536,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 235..236, id: Name("y"), ctx: Load, @@ -485,18 +550,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 238..258, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 238..258, items: [ DictItem { key: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 240..246, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 240..241, id: Name("x"), ctx: Store, @@ -504,6 +573,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 245..246, value: Int( 1, @@ -515,9 +585,11 @@ Module( ), value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 250..256, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..251, id: Name("y"), ctx: Store, @@ -525,6 +597,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 255..256, value: Int( 2, @@ -541,15 +614,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 284..289, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 284..289, items: [ DictItem { key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 287..288, id: Name("d"), ctx: Load, @@ -563,15 +639,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 290..301, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 290..301, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 291..292, id: Name("a"), ctx: Load, @@ -580,6 +659,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 294..295, id: Name("b"), ctx: Load, @@ -590,6 +670,7 @@ Module( key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 299..300, id: Name("d"), ctx: Load, @@ -603,15 +684,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 302..312, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 302..312, items: [ DictItem { key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 305..306, id: Name("a"), ctx: Load, @@ -622,6 +706,7 @@ Module( key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 310..311, id: Name("b"), ctx: Load, @@ -635,20 +720,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 313..338, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 313..338, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 314..317, value: StringLiteralValue { inner: Single( StringLiteral { range: 314..317, + node_index: AtomicNodeIndex(..), value: "a", flags: StringLiteralFlags { quote_style: Double, @@ -663,11 +752,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 319..322, value: StringLiteralValue { inner: Single( StringLiteral { range: 319..322, + node_index: AtomicNodeIndex(..), value: "b", flags: StringLiteralFlags { quote_style: Double, @@ -684,6 +775,7 @@ Module( key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 326..327, id: Name("c"), ctx: Load, @@ -694,11 +786,13 @@ Module( key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 329..332, value: StringLiteralValue { inner: Single( StringLiteral { range: 329..332, + node_index: AtomicNodeIndex(..), value: "d", flags: StringLiteralFlags { quote_style: Double, @@ -713,11 +807,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 334..337, value: StringLiteralValue { inner: Single( StringLiteral { range: 334..337, + node_index: AtomicNodeIndex(..), value: "e", flags: StringLiteralFlags { quote_style: Double, @@ -737,15 +833,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 339..367, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 339..367, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 340..341, value: Int( 1, @@ -755,6 +854,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 343..344, value: Int( 2, @@ -766,17 +866,20 @@ Module( key: None, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 348..366, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 349..357, value: StringLiteralValue { inner: Single( StringLiteral { range: 349..357, + node_index: AtomicNodeIndex(..), value: "nested", flags: StringLiteralFlags { quote_style: Single, @@ -791,11 +894,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 359..365, value: StringLiteralValue { inner: Single( StringLiteral { range: 359..365, + node_index: AtomicNodeIndex(..), value: "dict", flags: StringLiteralFlags { quote_style: Single, @@ -819,18 +924,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 368..393, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 368..393, items: [ DictItem { key: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 369..374, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 369..370, id: Name("x"), ctx: Load, @@ -839,6 +948,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 373..374, value: Int( 1, @@ -850,9 +960,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 376..382, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 376..377, id: Name("y"), ctx: Load, @@ -861,6 +973,7 @@ Module( op: Pow, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 381..382, value: Int( 2, @@ -874,9 +987,11 @@ Module( key: None, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 386..392, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 386..390, id: Name("call"), ctx: Load, @@ -884,6 +999,7 @@ Module( ), arguments: Arguments { range: 390..392, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -897,19 +1013,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 460..471, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 460..471, items: [ DictItem { key: None, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 464..469, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 468..469, id: Name("x"), ctx: Load, @@ -925,15 +1045,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 494..515, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 494..515, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 495..496, value: Int( 1, @@ -943,15 +1066,18 @@ Module( ), value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 498..514, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 503..507, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 498..499, id: Name("x"), ctx: Load, @@ -959,6 +1085,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 513..514, id: Name("y"), ctx: Load, @@ -974,21 +1101,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 516..575, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 516..575, key: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 517..533, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 522..526, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 517..518, id: Name("x"), ctx: Load, @@ -996,6 +1128,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 532..533, id: Name("y"), ctx: Load, @@ -1005,6 +1138,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 535..536, id: Name("y"), ctx: Load, @@ -1013,8 +1147,10 @@ Module( generators: [ Comprehension { range: 537..555, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 541..542, id: Name("x"), ctx: Store, @@ -1022,9 +1158,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 546..555, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 546..551, id: Name("range"), ctx: Load, @@ -1032,9 +1170,11 @@ Module( ), arguments: Arguments { range: 551..555, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 552..554, value: Int( 10, @@ -1051,8 +1191,10 @@ Module( }, Comprehension { range: 556..574, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 560..561, id: Name("y"), ctx: Store, @@ -1060,9 +1202,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 565..574, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 565..570, id: Name("range"), ctx: Load, @@ -1070,9 +1214,11 @@ Module( ), arguments: Arguments { range: 570..574, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 571..573, value: Int( 10, @@ -1094,19 +1240,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 576..600, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 576..600, items: [ DictItem { key: Some( Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 577..583, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 578..579, value: Int( 1, @@ -1115,6 +1265,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 581..582, value: Int( 2, @@ -1127,6 +1278,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 585..586, value: Int( 3, @@ -1138,6 +1290,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 588..589, id: Name("x"), ctx: Load, @@ -1146,12 +1299,14 @@ Module( ), value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 591..598, items: [ DictItem { key: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 592..593, value: Int( 1, @@ -1161,6 +1316,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 595..596, value: Int( 2, @@ -1179,15 +1335,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 601..621, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 601..621, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 603..604, id: Name("x"), ctx: Load, @@ -1196,6 +1355,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 608..609, id: Name("y"), ctx: Load, @@ -1206,6 +1366,7 @@ Module( key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 613..614, id: Name("z"), ctx: Load, @@ -1214,6 +1375,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 618..619, id: Name("a"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary_comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary_comprehension.py.snap index ea7d3324bc4999..d54aceae23f221 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary_comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary_comprehension.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/dictionary_comprehension.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..589, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 0..22, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("y"), ctx: Load, @@ -26,8 +29,10 @@ Module( generators: [ Comprehension { range: 3..21, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("y"), ctx: Store, @@ -35,10 +40,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 12..21, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, @@ -47,6 +54,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 2, @@ -55,6 +63,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 3, @@ -76,12 +85,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..42, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 23..42, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..26, id: Name("x1"), ctx: Load, @@ -89,6 +101,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..30, id: Name("x2"), ctx: Load, @@ -97,8 +110,10 @@ Module( generators: [ Comprehension { range: 31..41, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("y"), ctx: Store, @@ -106,6 +121,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("z"), ctx: Load, @@ -121,15 +137,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..73, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 43..73, key: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 44..49, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Load, @@ -138,6 +158,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 48..49, value: Int( 1, @@ -148,11 +169,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 51..54, value: StringLiteralValue { inner: Single( StringLiteral { range: 51..54, + node_index: AtomicNodeIndex(..), value: "x", flags: StringLiteralFlags { quote_style: Single, @@ -167,8 +190,10 @@ Module( generators: [ Comprehension { range: 55..72, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("i"), ctx: Store, @@ -176,9 +201,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 64..72, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..69, id: Name("range"), ctx: Load, @@ -186,9 +213,11 @@ Module( ), arguments: Arguments { range: 69..72, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 70..71, value: Int( 5, @@ -210,12 +239,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 74..122, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 74..122, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("b"), ctx: Load, @@ -223,9 +255,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 78..83, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("c"), ctx: Load, @@ -234,6 +268,7 @@ Module( op: Mult, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 2, @@ -245,8 +280,10 @@ Module( generators: [ Comprehension { range: 84..121, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("c"), ctx: Store, @@ -254,6 +291,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("d"), ctx: Load, @@ -262,9 +300,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 98..104, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("x"), ctx: Load, @@ -276,6 +316,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 103..104, id: Name("w"), ctx: Load, @@ -286,11 +327,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 108..116, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("y"), ctx: Load, @@ -298,6 +341,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..116, id: Name("yy"), ctx: Load, @@ -308,6 +352,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("z"), ctx: Load, @@ -323,12 +368,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 123..176, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 123..176, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..125, id: Name("a"), ctx: Load, @@ -336,9 +384,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 127..133, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("a"), ctx: Load, @@ -347,6 +397,7 @@ Module( op: Pow, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 132..133, value: Int( 2, @@ -358,8 +409,10 @@ Module( generators: [ Comprehension { range: 134..155, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 138..139, id: Name("b"), ctx: Store, @@ -367,6 +420,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..144, id: Name("c"), ctx: Load, @@ -375,11 +429,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 148..155, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..149, id: Name("d"), ctx: Load, @@ -387,6 +443,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 154..155, id: Name("e"), ctx: Load, @@ -400,8 +457,10 @@ Module( }, Comprehension { range: 156..175, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("f"), ctx: Store, @@ -409,6 +468,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("j"), ctx: Load, @@ -417,9 +477,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 170..175, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("k"), ctx: Load, @@ -431,6 +493,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 174..175, id: Name("h"), ctx: Load, @@ -449,12 +512,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 177..231, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 177..231, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("a"), ctx: Load, @@ -462,6 +528,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 181..182, id: Name("b"), ctx: Load, @@ -470,8 +537,10 @@ Module( generators: [ Comprehension { range: 183..204, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 187..188, id: Name("b"), ctx: Store, @@ -479,6 +548,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..193, id: Name("c"), ctx: Load, @@ -487,11 +557,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 197..204, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("d"), ctx: Load, @@ -499,6 +571,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 203..204, id: Name("e"), ctx: Load, @@ -512,8 +585,10 @@ Module( }, Comprehension { range: 205..230, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 215..216, id: Name("f"), ctx: Store, @@ -521,6 +596,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 220..221, id: Name("j"), ctx: Load, @@ -529,9 +605,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 225..230, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("k"), ctx: Load, @@ -543,6 +621,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 229..230, id: Name("h"), ctx: Load, @@ -561,12 +640,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 232..252, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 232..252, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..234, id: Name("a"), ctx: Load, @@ -574,6 +656,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("a"), ctx: Load, @@ -582,12 +665,15 @@ Module( generators: [ Comprehension { range: 238..251, + node_index: AtomicNodeIndex(..), target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 242..246, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 242..243, id: Name("b"), ctx: Store, @@ -595,6 +681,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("c"), ctx: Store, @@ -607,6 +694,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..251, id: Name("d"), ctx: Load, @@ -622,12 +710,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 391..416, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 391..416, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 392..393, id: Name("x"), ctx: Load, @@ -635,6 +726,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 395..396, id: Name("y"), ctx: Load, @@ -643,8 +735,10 @@ Module( generators: [ Comprehension { range: 397..415, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 401..402, id: Name("x"), ctx: Store, @@ -652,10 +746,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 407..414, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 413..414, id: Name("y"), ctx: Load, @@ -674,12 +770,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 417..447, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 417..447, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 418..419, id: Name("x"), ctx: Load, @@ -687,6 +786,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..422, id: Name("y"), ctx: Load, @@ -695,8 +795,10 @@ Module( generators: [ Comprehension { range: 423..446, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 427..428, id: Name("x"), ctx: Store, @@ -704,9 +806,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 433..445, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 444..445, id: Name("y"), ctx: Load, @@ -724,12 +828,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 448..477, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 448..477, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 449..450, id: Name("x"), ctx: Load, @@ -737,6 +844,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 452..453, id: Name("y"), ctx: Load, @@ -745,8 +853,10 @@ Module( generators: [ Comprehension { range: 454..476, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 458..459, id: Name("x"), ctx: Store, @@ -754,19 +864,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 464..475, parameters: Some( Parameters { range: 471..472, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 471..472, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 471..472, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 471..472, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -780,6 +897,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 474..475, id: Name("y"), ctx: Load, @@ -797,12 +915,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 478..511, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 478..511, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 479..480, id: Name("x"), ctx: Load, @@ -810,6 +931,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 482..483, id: Name("y"), ctx: Load, @@ -818,8 +940,10 @@ Module( generators: [ Comprehension { range: 484..510, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 488..489, id: Name("x"), ctx: Store, @@ -827,6 +951,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 493..497, id: Name("data"), ctx: Load, @@ -835,10 +960,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 502..509, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 508..509, id: Name("y"), ctx: Load, @@ -857,12 +984,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 512..550, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 512..550, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 513..514, id: Name("x"), ctx: Load, @@ -870,6 +1000,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 516..517, id: Name("y"), ctx: Load, @@ -878,8 +1009,10 @@ Module( generators: [ Comprehension { range: 518..549, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 522..523, id: Name("x"), ctx: Store, @@ -887,6 +1020,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 527..531, id: Name("data"), ctx: Load, @@ -895,9 +1029,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 536..548, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 547..548, id: Name("y"), ctx: Load, @@ -915,12 +1051,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 551..588, value: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 551..588, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 552..553, id: Name("x"), ctx: Load, @@ -928,6 +1067,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 555..556, id: Name("y"), ctx: Load, @@ -936,8 +1076,10 @@ Module( generators: [ Comprehension { range: 557..587, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 561..562, id: Name("x"), ctx: Store, @@ -945,6 +1087,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 566..570, id: Name("data"), ctx: Load, @@ -953,19 +1096,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 575..586, parameters: Some( Parameters { range: 582..583, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 582..583, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 582..583, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 582..583, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -979,6 +1129,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 585..586, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap index 99067deeda0f23..1fe9ef6fa7b96d 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/valid/expressions/f_string.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..979, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..21, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 18..21, value: FStringValue { inner: Single( FString( FString { range: 18..21, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, @@ -36,15 +40,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..25, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 22..25, value: FStringValue { inner: Single( FString( FString { range: 22..25, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, @@ -61,15 +68,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 26..29, value: FStringValue { inner: Single( FString( FString { range: 26..29, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Single, @@ -86,15 +96,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..37, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 30..37, value: FStringValue { inner: Single( FString( FString { range: 30..37, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Double, @@ -111,15 +124,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..45, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 38..45, value: FStringValue { inner: Single( FString( FString { range: 38..45, + node_index: AtomicNodeIndex(..), elements: [], flags: FStringFlags { quote_style: Single, @@ -136,26 +152,32 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..56, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 47..56, value: FStringValue { inner: Single( FString( FString { range: 47..56, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 49..55, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 50..54, value: StringLiteralValue { inner: Single( StringLiteral { range: 50..54, + node_index: AtomicNodeIndex(..), value: " f", flags: StringLiteralFlags { quote_style: Double, @@ -188,21 +210,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..67, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 57..67, value: FStringValue { inner: Single( FString( FString { range: 57..67, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 59..66, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("foo"), ctx: Load, @@ -229,25 +256,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..75, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 68..75, value: FStringValue { inner: Single( FString( FString { range: 68..75, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 70..74, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 71..73, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 3, @@ -280,24 +313,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 76..86, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 76..86, value: FStringValue { inner: Single( FString( FString { range: 76..86, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 78..85, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 79..83, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 3, @@ -310,6 +349,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 4, @@ -324,6 +364,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 84..84, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -345,21 +386,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..102, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 87..102, value: FStringValue { inner: Single( FString( FString { range: 87..102, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 89..101, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 90..91, value: Int( 3, @@ -371,17 +417,21 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 92..100, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 92..97, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 93..96, value: StringLiteralValue { inner: Single( StringLiteral { range: 93..96, + node_index: AtomicNodeIndex(..), value: "}", flags: StringLiteralFlags { quote_style: Double, @@ -401,6 +451,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 97..100, + node_index: AtomicNodeIndex(..), value: ">10", }, ), @@ -425,21 +476,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..118, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 103..118, value: FStringValue { inner: Single( FString( FString { range: 103..118, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 105..117, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 106..107, value: Int( 3, @@ -451,17 +507,21 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 108..116, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 108..113, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, value: StringLiteralValue { inner: Single( StringLiteral { range: 109..112, + node_index: AtomicNodeIndex(..), value: "{", flags: StringLiteralFlags { quote_style: Double, @@ -481,6 +541,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 113..116, + node_index: AtomicNodeIndex(..), value: ">10", }, ), @@ -505,21 +566,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..133, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 119..133, value: FStringValue { inner: Single( FString( FString { range: 119..133, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 121..132, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..127, id: Name("foo"), ctx: Load, @@ -551,21 +617,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 134..154, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 134..154, value: FStringValue { inner: Single( FString( FString { range: 134..154, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 136..153, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..142, id: Name("foo"), ctx: Load, @@ -581,10 +652,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 147..152, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 147..152, + node_index: AtomicNodeIndex(..), value: ".3f ", }, ), @@ -609,21 +682,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..173, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 155..173, value: FStringValue { inner: Single( FString( FString { range: 155..173, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 157..172, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..163, id: Name("foo"), ctx: Load, @@ -655,25 +733,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 174..190, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 174..190, value: FStringValue { inner: Single( FString( FString { range: 174..190, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 176..189, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 179..183, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 179..180, value: Int( 1, @@ -682,6 +766,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 182..183, value: Int( 2, @@ -719,33 +804,41 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 191..217, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 191..217, value: FStringValue { inner: Single( FString( FString { range: 191..217, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 193..216, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 194..210, value: FStringValue { inner: Single( FString( FString { range: 194..210, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 196..209, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..203, value: Float( 3.1415, @@ -762,10 +855,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 205..208, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 205..208, + node_index: AtomicNodeIndex(..), value: ".1f", }, ), @@ -791,10 +886,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 211..215, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 211..215, + node_index: AtomicNodeIndex(..), value: "*^20", }, ), @@ -819,15 +916,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 219..253, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 219..253, items: [ DictItem { key: Some( FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 220..248, value: FStringValue { inner: Concatenated( @@ -835,6 +935,7 @@ Module( Literal( StringLiteral { range: 220..226, + node_index: AtomicNodeIndex(..), value: "foo ", flags: StringLiteralFlags { quote_style: Double, @@ -846,21 +947,26 @@ Module( FString( FString { range: 227..242, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 229..233, + node_index: AtomicNodeIndex(..), value: "bar ", }, ), Interpolation( InterpolatedElement { range: 233..240, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 234..239, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..235, id: Name("x"), ctx: Load, @@ -869,6 +975,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 238..239, id: Name("y"), ctx: Load, @@ -884,6 +991,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 240..241, + node_index: AtomicNodeIndex(..), value: " ", }, ), @@ -898,6 +1006,7 @@ Module( Literal( StringLiteral { range: 243..248, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -914,6 +1023,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 250..252, value: Int( 10, @@ -928,9 +1038,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 254..345, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..263, id: Name("foo"), ctx: Load, @@ -939,16 +1051,20 @@ Module( cases: [ MatchCase { range: 269..293, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 274..279, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 274..279, value: StringLiteralValue { inner: Single( StringLiteral { range: 274..279, + node_index: AtomicNodeIndex(..), value: "one", flags: StringLiteralFlags { quote_style: Double, @@ -966,6 +1082,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 289..293, }, ), @@ -973,11 +1090,14 @@ Module( }, MatchCase { range: 298..345, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 303..331, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 303..331, value: StringLiteralValue { inner: Concatenated( @@ -985,6 +1105,7 @@ Module( strings: [ StringLiteral { range: 303..316, + node_index: AtomicNodeIndex(..), value: "implicitly ", flags: StringLiteralFlags { quote_style: Double, @@ -994,6 +1115,7 @@ Module( }, StringLiteral { range: 317..331, + node_index: AtomicNodeIndex(..), value: "concatenated", flags: StringLiteralFlags { quote_style: Double, @@ -1014,6 +1136,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 341..345, }, ), @@ -1024,27 +1147,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 347..364, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 347..364, value: FStringValue { inner: Single( FString( FString { range: 347..364, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 349..350, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 350..355, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..354, id: Name("foo"), ctx: Load, @@ -1058,14 +1187,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 355..356, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 356..363, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 357..360, id: Name("bar"), ctx: Load, @@ -1076,10 +1208,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 361..362, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 361..362, + node_index: AtomicNodeIndex(..), value: "\\", }, ), @@ -1104,19 +1238,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 365..379, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 365..379, value: FStringValue { inner: Single( FString( FString { range: 365..379, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 367..378, + node_index: AtomicNodeIndex(..), value: "\\{foo\\}", }, ), @@ -1136,21 +1274,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 380..420, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 380..420, value: FStringValue { inner: Single( FString( FString { range: 380..420, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 384..417, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 390..393, id: Name("foo"), ctx: Load, @@ -1161,10 +1304,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 394..416, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 394..416, + node_index: AtomicNodeIndex(..), value: "x\n y\n z\n", }, ), @@ -1189,21 +1334,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 421..439, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 421..439, value: FStringValue { inner: Single( FString( FString { range: 421..439, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 423..438, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 428..431, id: Name("foo"), ctx: Load, @@ -1235,27 +1385,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 441..486, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 441..486, value: FStringValue { inner: Single( FString( FString { range: 441..486, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 443..450, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 450..455, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 451..454, id: Name("foo"), ctx: Load, @@ -1269,14 +1425,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 455..468, + node_index: AtomicNodeIndex(..), value: " {another} ", }, ), Interpolation( InterpolatedElement { range: 468..473, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 469..472, id: Name("bar"), ctx: Load, @@ -1290,14 +1449,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 473..476, + node_index: AtomicNodeIndex(..), value: " {", }, ), Interpolation( InterpolatedElement { range: 476..483, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 477..482, id: Name("three"), ctx: Load, @@ -1311,6 +1473,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 483..485, + node_index: AtomicNodeIndex(..), value: "}", }, ), @@ -1330,27 +1493,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 487..529, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 487..529, value: FStringValue { inner: Single( FString( FString { range: 487..529, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 489..496, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 496..503, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 497..500, id: Name("foo"), ctx: Load, @@ -1364,14 +1533,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 503..504, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 504..511, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 505..508, id: Name("bar"), ctx: Load, @@ -1385,14 +1557,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 511..512, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 512..519, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 513..516, id: Name("baz"), ctx: Load, @@ -1406,14 +1581,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 519..520, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 520..528, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 521..527, id: Name("foobar"), ctx: Load, @@ -1440,27 +1618,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 530..549, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 530..549, value: FStringValue { inner: Single( FString( FString { range: 530..549, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 532..539, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 539..548, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 540..541, id: Name("x"), ctx: Load, @@ -1471,10 +1655,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 542..547, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 542..547, + node_index: AtomicNodeIndex(..), value: "y + 2", }, ), @@ -1499,21 +1685,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 550..568, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 550..568, value: FStringValue { inner: Single( FString( FString { range: 550..568, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 552..567, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 553..554, id: Name("x"), ctx: Load, @@ -1524,22 +1715,28 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 555..566, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 555..566, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 556..565, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 556..563, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 556..559, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 557..558, value: Int( 1, @@ -1552,12 +1749,14 @@ Module( attr: Identifier { id: Name("pop"), range: 560..563, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 563..565, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1589,34 +1788,45 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 569..588, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 569..588, value: FStringValue { inner: Single( FString( FString { range: 569..588, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 571..587, + node_index: AtomicNodeIndex(..), expression: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 573..585, parameters: Some( Parameters { range: 580..581, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 580..581, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 580..581, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 580..581, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1630,10 +1840,12 @@ Module( ), body: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 582..585, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 583..584, id: Name("x"), ctx: Load, @@ -1665,21 +1877,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 589..597, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 589..597, value: FStringValue { inner: Single( FString( FString { range: 589..597, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 591..596, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 592..593, id: Name("x"), ctx: Load, @@ -1711,21 +1928,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 598..611, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 598..611, value: FStringValue { inner: Single( FString( FString { range: 598..611, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 600..610, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 605..606, id: Name("x"), ctx: Load, @@ -1757,21 +1979,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 612..621, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 612..621, value: FStringValue { inner: Single( FString( FString { range: 612..621, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 614..620, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 615..616, id: Name("x"), ctx: Load, @@ -1803,21 +2030,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 622..636, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 622..636, value: FStringValue { inner: Single( FString( FString { range: 622..636, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 624..635, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 625..626, id: Name("x"), ctx: Load, @@ -1828,10 +2060,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 627..634, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 627..634, + node_index: AtomicNodeIndex(..), value: ".3f!r =", }, ), @@ -1856,21 +2090,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 637..653, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 637..653, value: FStringValue { inner: Single( FString( FString { range: 637..653, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 639..652, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 640..641, id: Name("x"), ctx: Load, @@ -1886,10 +2125,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 648..651, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 648..651, + node_index: AtomicNodeIndex(..), value: ".3f", }, ), @@ -1914,21 +2155,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 654..667, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 654..667, value: FStringValue { inner: Single( FString( FString { range: 654..667, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 656..666, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 657..658, id: Name("x"), ctx: Load, @@ -1939,10 +2185,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 659..665, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 659..665, + node_index: AtomicNodeIndex(..), value: ".3f=!r", }, ), @@ -1967,9 +2215,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 668..682, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 668..682, value: FStringValue { inner: Concatenated( @@ -1977,6 +2227,7 @@ Module( Literal( StringLiteral { range: 668..675, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Double, @@ -1988,12 +2239,15 @@ Module( FString( FString { range: 676..682, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 678..681, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 679..680, id: Name("x"), ctx: Load, @@ -2021,9 +2275,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 683..696, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 683..696, value: FStringValue { inner: Concatenated( @@ -2031,12 +2287,15 @@ Module( FString( FString { range: 683..689, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 685..688, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 686..687, id: Name("x"), ctx: Load, @@ -2058,12 +2317,15 @@ Module( FString( FString { range: 690..696, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 692..695, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 693..694, id: Name("y"), ctx: Load, @@ -2091,9 +2353,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 697..711, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 697..711, value: FStringValue { inner: Concatenated( @@ -2101,12 +2365,15 @@ Module( FString( FString { range: 697..703, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 699..702, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 700..701, id: Name("x"), ctx: Load, @@ -2128,6 +2395,7 @@ Module( Literal( StringLiteral { range: 704..711, + node_index: AtomicNodeIndex(..), value: "world", flags: StringLiteralFlags { quote_style: Double, @@ -2145,31 +2413,38 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 712..756, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 712..756, value: FStringValue { inner: Single( FString( FString { range: 712..756, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 714..739, + node_index: AtomicNodeIndex(..), value: "Invalid args in command: ", }, ), Interpolation( InterpolatedElement { range: 739..755, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 740..754, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 740..747, id: Name("command"), ctx: Load, @@ -2177,9 +2452,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 749..754, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 750..754, id: Name("args"), ctx: Load, @@ -2214,9 +2491,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 757..775, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 757..775, value: FStringValue { inner: Concatenated( @@ -2224,6 +2503,7 @@ Module( Literal( StringLiteral { range: 757..762, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2235,12 +2515,15 @@ Module( FString( FString { range: 763..769, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 765..768, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 766..767, id: Name("x"), ctx: Load, @@ -2262,6 +2545,7 @@ Module( Literal( StringLiteral { range: 770..775, + node_index: AtomicNodeIndex(..), value: "bar", flags: StringLiteralFlags { quote_style: Double, @@ -2279,9 +2563,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 776..825, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 782..823, value: FStringValue { inner: Concatenated( @@ -2289,10 +2575,12 @@ Module( FString( FString { range: 782..786, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 784..785, + node_index: AtomicNodeIndex(..), value: "a", }, ), @@ -2307,10 +2595,12 @@ Module( FString( FString { range: 791..795, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 793..794, + node_index: AtomicNodeIndex(..), value: "b", }, ), @@ -2325,6 +2615,7 @@ Module( Literal( StringLiteral { range: 800..803, + node_index: AtomicNodeIndex(..), value: "c", flags: StringLiteralFlags { quote_style: Double, @@ -2336,10 +2627,12 @@ Module( FString( FString { range: 808..813, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 811..812, + node_index: AtomicNodeIndex(..), value: "d", }, ), @@ -2356,10 +2649,12 @@ Module( FString( FString { range: 818..823, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 821..822, + node_index: AtomicNodeIndex(..), value: "e", }, ), @@ -2382,9 +2677,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 850..879, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 850..879, value: FStringValue { inner: Concatenated( @@ -2392,6 +2689,7 @@ Module( Literal( StringLiteral { range: 850..856, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2403,12 +2701,15 @@ Module( FString( FString { range: 857..865, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 859..864, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 860..863, id: Name("bar"), ctx: Load, @@ -2430,6 +2731,7 @@ Module( Literal( StringLiteral { range: 866..871, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2441,6 +2743,7 @@ Module( Literal( StringLiteral { range: 872..879, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2458,9 +2761,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 880..909, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 880..909, value: FStringValue { inner: Concatenated( @@ -2468,6 +2773,7 @@ Module( Literal( StringLiteral { range: 880..885, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2479,12 +2785,15 @@ Module( FString( FString { range: 886..894, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 888..893, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 889..892, id: Name("bar"), ctx: Load, @@ -2506,6 +2815,7 @@ Module( Literal( StringLiteral { range: 895..901, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2517,6 +2827,7 @@ Module( Literal( StringLiteral { range: 902..909, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2534,9 +2845,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 910..939, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 910..939, value: FStringValue { inner: Concatenated( @@ -2544,6 +2857,7 @@ Module( Literal( StringLiteral { range: 910..915, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2555,12 +2869,15 @@ Module( FString( FString { range: 916..924, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 918..923, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 919..922, id: Name("bar"), ctx: Load, @@ -2582,6 +2899,7 @@ Module( Literal( StringLiteral { range: 925..930, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2593,6 +2911,7 @@ Module( Literal( StringLiteral { range: 931..939, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2610,9 +2929,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 940..978, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 940..978, value: FStringValue { inner: Concatenated( @@ -2620,6 +2941,7 @@ Module( Literal( StringLiteral { range: 940..946, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2631,18 +2953,22 @@ Module( FString( FString { range: 947..966, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 949..953, + node_index: AtomicNodeIndex(..), value: "bar ", }, ), Interpolation( InterpolatedElement { range: 953..958, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 954..957, id: Name("baz"), ctx: Load, @@ -2656,6 +2982,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 958..965, + node_index: AtomicNodeIndex(..), value: " really", }, ), @@ -2670,6 +2997,7 @@ Module( Literal( StringLiteral { range: 967..973, + node_index: AtomicNodeIndex(..), value: "bar", flags: StringLiteralFlags { quote_style: Double, @@ -2681,6 +3009,7 @@ Module( Literal( StringLiteral { range: 974..978, + node_index: AtomicNodeIndex(..), value: "no", flags: StringLiteralFlags { quote_style: Double, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__generator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__generator.py.snap index d93aef523add56..80e25b5146c21c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__generator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__generator.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/generator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..482, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..22, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 0..22, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -26,8 +29,10 @@ Module( generators: [ Comprehension { range: 3..21, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..13, id: Name("target"), ctx: Store, @@ -35,6 +40,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..21, id: Name("iter"), ctx: Load, @@ -51,12 +57,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..51, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 23..51, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -65,8 +74,10 @@ Module( generators: [ Comprehension { range: 26..50, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..42, id: Name("target"), ctx: Store, @@ -74,6 +85,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..50, id: Name("iter"), ctx: Load, @@ -90,12 +102,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..100, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 52..100, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("x"), ctx: Load, @@ -104,8 +119,10 @@ Module( generators: [ Comprehension { range: 55..99, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..65, id: Name("target"), ctx: Store, @@ -113,6 +130,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..73, id: Name("iter"), ctx: Load, @@ -121,9 +139,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 77..83, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("x"), ctx: Load, @@ -135,6 +155,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("y"), ctx: Load, @@ -145,11 +166,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 87..94, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("a"), ctx: Load, @@ -157,6 +180,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("b"), ctx: Load, @@ -167,6 +191,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("c"), ctx: Load, @@ -183,12 +208,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 101..166, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 101..166, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("x"), ctx: Load, @@ -197,8 +225,10 @@ Module( generators: [ Comprehension { range: 104..135, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..115, id: Name("target1"), ctx: Store, @@ -206,6 +236,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..124, id: Name("iter1"), ctx: Load, @@ -214,11 +245,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 128..135, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("x"), ctx: Load, @@ -226,6 +259,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..135, id: Name("y"), ctx: Load, @@ -239,8 +273,10 @@ Module( }, Comprehension { range: 136..165, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..147, id: Name("target2"), ctx: Store, @@ -248,6 +284,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..156, id: Name("iter2"), ctx: Load, @@ -256,9 +293,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 160..165, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("a"), ctx: Load, @@ -270,6 +309,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 164..165, id: Name("b"), ctx: Load, @@ -289,12 +329,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 167..238, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 167..238, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..169, id: Name("x"), ctx: Load, @@ -303,8 +346,10 @@ Module( generators: [ Comprehension { range: 170..201, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 174..181, id: Name("target1"), ctx: Store, @@ -312,6 +357,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..190, id: Name("iter1"), ctx: Load, @@ -320,11 +366,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 194..201, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 194..195, id: Name("x"), ctx: Load, @@ -332,6 +380,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 200..201, id: Name("y"), ctx: Load, @@ -345,8 +394,10 @@ Module( }, Comprehension { range: 202..237, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 212..219, id: Name("target2"), ctx: Store, @@ -354,6 +405,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 223..228, id: Name("iter2"), ctx: Load, @@ -362,9 +414,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 232..237, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 232..233, id: Name("a"), ctx: Load, @@ -376,6 +430,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("b"), ctx: Load, @@ -395,15 +450,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 259..282, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 259..282, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 260..270, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..261, id: Name("x"), ctx: Store, @@ -411,9 +470,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 265..270, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("y"), ctx: Load, @@ -422,6 +483,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 269..270, value: Int( 1, @@ -435,8 +497,10 @@ Module( generators: [ Comprehension { range: 271..281, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 275..276, id: Name("y"), ctx: Store, @@ -444,6 +508,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 280..281, id: Name("z"), ctx: Load, @@ -460,15 +525,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 300..326, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 300..326, elt: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 301..314, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 306..307, id: Name("y"), ctx: Load, @@ -476,6 +545,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 301..302, id: Name("x"), ctx: Load, @@ -483,6 +553,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 313..314, id: Name("y"), ctx: Load, @@ -493,8 +564,10 @@ Module( generators: [ Comprehension { range: 315..325, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 319..320, id: Name("y"), ctx: Store, @@ -502,6 +575,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 324..325, id: Name("z"), ctx: Load, @@ -518,20 +592,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 340..481, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 340..481, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 340..348, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 340..343, value: StringLiteralValue { inner: Single( StringLiteral { range: 340..343, + node_index: AtomicNodeIndex(..), value: " ", flags: StringLiteralFlags { quote_style: Double, @@ -546,18 +625,22 @@ Module( attr: Identifier { id: Name("join"), range: 344..348, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 348..481, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 354..479, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 354..357, id: Name("sql"), ctx: Load, @@ -566,8 +649,10 @@ Module( generators: [ Comprehension { range: 362..479, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 366..369, id: Name("sql"), ctx: Store, @@ -575,13 +660,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 373..479, elts: [ If( ExprIf { + node_index: AtomicNodeIndex(..), range: 383..420, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 405..410, id: Name("limit"), ctx: Load, @@ -589,14 +677,17 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 383..401, left: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 383..393, value: StringLiteralValue { inner: Single( StringLiteral { range: 383..393, + node_index: AtomicNodeIndex(..), value: "LIMIT %d", flags: StringLiteralFlags { quote_style: Double, @@ -611,6 +702,7 @@ Module( op: Mod, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 396..401, id: Name("limit"), ctx: Load, @@ -620,6 +712,7 @@ Module( ), orelse: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 416..420, }, ), @@ -627,9 +720,11 @@ Module( ), If( ExprIf { + node_index: AtomicNodeIndex(..), range: 430..472, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 456..462, id: Name("offset"), ctx: Load, @@ -637,14 +732,17 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 431..451, left: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 431..442, value: StringLiteralValue { inner: Single( StringLiteral { range: 431..442, + node_index: AtomicNodeIndex(..), value: "OFFSET %d", flags: StringLiteralFlags { quote_style: Double, @@ -659,6 +757,7 @@ Module( op: Mod, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 445..451, id: Name("offset"), ctx: Load, @@ -668,6 +767,7 @@ Module( ), orelse: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 468..472, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__if.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__if.py.snap index 5cca4b3a1bac39..f21914594fbaec 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__if.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__if.py.snap @@ -1,29 +1,33 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/if.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..423, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..16, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 0..16, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 5..9, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("a"), ctx: Load, @@ -31,6 +35,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..16, id: Name("b"), ctx: Load, @@ -42,12 +47,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..35, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 17..35, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -55,9 +63,11 @@ Module( ), body: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 17..20, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("f"), ctx: Load, @@ -65,6 +75,7 @@ Module( ), arguments: Arguments { range: 18..20, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -72,6 +83,7 @@ Module( ), orelse: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 31..35, }, ), @@ -81,12 +93,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..61, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 36..61, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("b"), ctx: Load, @@ -94,6 +109,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("a"), ctx: Load, @@ -101,9 +117,11 @@ Module( ), orelse: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 48..61, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("d"), ctx: Load, @@ -111,6 +129,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("c"), ctx: Load, @@ -118,6 +137,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("e"), ctx: Load, @@ -131,15 +151,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 62..84, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 62..84, test: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 71..76, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 1, @@ -152,6 +176,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 0, @@ -163,9 +188,11 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 62..67, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 62..63, value: Int( 1, @@ -175,6 +202,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("x"), ctx: Load, @@ -184,10 +212,12 @@ Module( ), orelse: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 82..84, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 1, @@ -202,12 +232,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..108, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 85..108, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("x"), ctx: Load, @@ -215,11 +248,13 @@ Module( ), body: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 85..92, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..86, id: Name("a"), ctx: Load, @@ -227,6 +262,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("b"), ctx: Load, @@ -237,6 +273,7 @@ Module( ), orelse: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 103..108, value: false, }, @@ -247,12 +284,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..127, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 109..127, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..120, id: Name("y"), ctx: Load, @@ -260,9 +300,11 @@ Module( ), body: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 109..115, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..110, id: Name("x"), ctx: Load, @@ -274,6 +316,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("y"), ctx: Load, @@ -284,6 +327,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("x"), ctx: Load, @@ -295,17 +339,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 128..154, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 128..154, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 136..143, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("a"), ctx: Load, @@ -313,6 +361,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..143, id: Name("b"), ctx: Load, @@ -323,12 +372,14 @@ Module( ), body: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 128..132, value: true, }, ), orelse: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 149..154, value: false, }, @@ -339,13 +390,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..171, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 155..171, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 155..156, value: Int( 1, @@ -354,9 +408,11 @@ Module( ), If( ExprIf { + node_index: AtomicNodeIndex(..), range: 158..171, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("a"), ctx: Load, @@ -364,6 +420,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 158..159, value: Int( 1, @@ -372,6 +429,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("c"), ctx: Load, @@ -388,18 +446,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 214..240, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 214..240, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 219..223, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("x"), ctx: Load, @@ -407,19 +469,26 @@ Module( ), orelse: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 229..240, parameters: Some( Parameters { range: 236..237, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 236..237, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 236..237, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 236..237, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -433,6 +502,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 239..240, id: Name("y"), ctx: Load, @@ -446,16 +516,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 302..323, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 302..323, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 308..315, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 314..315, id: Name("x"), ctx: Load, @@ -466,6 +540,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 302..303, id: Name("x"), ctx: Load, @@ -473,6 +548,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 322..323, id: Name("y"), ctx: Load, @@ -484,15 +560,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 324..350, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 324..350, test: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 330..342, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 341..342, id: Name("x"), ctx: Load, @@ -502,6 +582,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 324..325, id: Name("x"), ctx: Load, @@ -509,6 +590,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 349..350, id: Name("y"), ctx: Load, @@ -520,25 +602,34 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 351..376, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 351..376, test: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 357..368, parameters: Some( Parameters { range: 364..365, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 364..365, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 364..365, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 364..365, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -552,6 +643,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 367..368, id: Name("x"), ctx: Load, @@ -561,6 +653,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..352, id: Name("x"), ctx: Load, @@ -568,6 +661,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 375..376, id: Name("y"), ctx: Load, @@ -579,12 +673,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 408..423, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 409..422, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 414..415, id: Name("y"), ctx: Load, @@ -592,6 +689,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 409..410, id: Name("x"), ctx: Load, @@ -599,6 +697,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..422, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__lambda.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__lambda.py.snap index a21cc82b8a3169..9e614f6c7e3a63 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__lambda.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__lambda.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/lambda.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..530, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..9, parameters: None, body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("a"), ctx: Load, @@ -30,13 +33,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 10..19, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 10..19, parameters: None, body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 1, @@ -49,22 +55,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..31, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 20..31, parameters: Some( Parameters { range: 27..28, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 27..28, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 27..28, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 27..28, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -78,6 +92,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 30..31, value: Int( 1, @@ -90,22 +105,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..48, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 32..48, parameters: Some( Parameters { range: 39..43, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 39..40, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 39..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 39..40, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -113,11 +136,14 @@ Module( }, ParameterWithDefault { range: 42..43, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 42..43, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 42..43, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -131,6 +157,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 45..48, }, ), @@ -140,22 +167,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..66, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 49..66, parameters: Some( Parameters { range: 56..63, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 56..57, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 56..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 56..57, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -163,11 +198,14 @@ Module( }, ParameterWithDefault { range: 59..60, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 59..60, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 59..60, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -175,11 +213,14 @@ Module( }, ParameterWithDefault { range: 62..63, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 62..63, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 62..63, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -193,6 +234,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 1, @@ -205,22 +247,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..90, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 67..90, parameters: Some( Parameters { range: 74..87, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 74..75, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 74..75, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 74..75, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -228,17 +278,21 @@ Module( }, ParameterWithDefault { range: 77..81, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 77..78, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 77..78, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..81, value: Int( 20, @@ -249,17 +303,21 @@ Module( }, ParameterWithDefault { range: 83..87, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 83..84, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 83..84, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 85..87, value: Int( 30, @@ -276,6 +334,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 1, @@ -288,22 +347,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..109, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 91..109, parameters: Some( Parameters { range: 98..102, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 98..99, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 98..99, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 98..99, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -311,11 +378,14 @@ Module( }, ParameterWithDefault { range: 101..102, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 101..102, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 101..102, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -329,9 +399,11 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 104..109, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("x"), ctx: Load, @@ -340,6 +412,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("y"), ctx: Load, @@ -353,22 +426,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 110..130, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 110..130, parameters: Some( Parameters { range: 117..123, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 117..118, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 117..118, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 117..118, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -376,17 +457,21 @@ Module( }, ParameterWithDefault { range: 120..123, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 120..121, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("z"), range: 120..121, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 122..123, value: Int( 1, @@ -403,9 +488,11 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 125..130, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("z"), ctx: Load, @@ -414,6 +501,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("y"), ctx: Load, @@ -427,21 +515,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 131..143, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 131..143, parameters: Some( Parameters { range: 138..140, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 138..140, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 139..140, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -452,6 +547,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..143, id: Name("a"), ctx: Load, @@ -463,21 +559,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 144..166, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 144..166, parameters: Some( Parameters { range: 151..161, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 151..153, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 152..153, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -485,11 +588,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 155..156, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 155..156, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("z"), range: 155..156, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -497,17 +603,21 @@ Module( }, ParameterWithDefault { range: 158..161, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 158..159, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 158..159, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..161, value: Int( 0, @@ -522,6 +632,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 163..166, }, ), @@ -531,24 +642,32 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 167..187, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 167..187, parameters: Some( Parameters { range: 174..184, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 177..178, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 177..178, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 177..178, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -556,11 +675,14 @@ Module( }, ParameterWithDefault { range: 180..181, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 180..181, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 180..181, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -568,11 +690,14 @@ Module( }, ParameterWithDefault { range: 183..184, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 183..184, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 183..184, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -584,6 +709,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 186..187, value: Int( 1, @@ -596,24 +722,32 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 188..214, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 188..214, parameters: Some( Parameters { range: 195..211, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 198..199, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 198..199, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 198..199, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -621,17 +755,21 @@ Module( }, ParameterWithDefault { range: 201..205, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 201..202, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 201..202, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 203..205, value: Int( 20, @@ -642,17 +780,21 @@ Module( }, ParameterWithDefault { range: 207..211, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 207..208, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 207..208, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 209..211, value: Int( 30, @@ -667,6 +809,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..214, value: Int( 1, @@ -679,22 +822,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 215..241, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 215..241, parameters: Some( Parameters { range: 222..238, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 222..223, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 222..223, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 222..223, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -702,11 +853,14 @@ Module( }, ParameterWithDefault { range: 225..226, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 225..226, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 225..226, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -714,11 +868,14 @@ Module( }, ParameterWithDefault { range: 228..229, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 228..229, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 228..229, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -729,11 +886,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 234..235, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 234..235, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 234..235, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -741,11 +901,14 @@ Module( }, ParameterWithDefault { range: 237..238, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 237..238, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 237..238, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -757,6 +920,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 240..241, value: Int( 0, @@ -769,13 +933,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 242..262, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 242..262, parameters: Some( Parameters { range: 249..257, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -783,9 +952,11 @@ Module( kwarg: Some( Parameter { range: 249..257, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 251..257, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -794,9 +965,11 @@ Module( ), body: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 259..262, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("f"), ctx: Load, @@ -804,6 +977,7 @@ Module( ), arguments: Arguments { range: 260..262, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -815,21 +989,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 263..294, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 263..294, parameters: Some( Parameters { range: 270..285, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 270..275, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 271..275, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -838,9 +1019,11 @@ Module( kwarg: Some( Parameter { range: 277..285, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 279..285, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -849,12 +1032,15 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 287..294, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 287..290, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 287..288, id: Name("f"), ctx: Load, @@ -862,6 +1048,7 @@ Module( ), arguments: Arguments { range: 288..290, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -870,6 +1057,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 293..294, value: Int( 1, @@ -884,21 +1072,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 295..334, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 295..334, parameters: Some( Parameters { range: 302..325, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 302..307, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 303..307, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -906,11 +1101,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 309..310, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 309..310, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 309..310, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -918,17 +1116,21 @@ Module( }, ParameterWithDefault { range: 312..315, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 312..313, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 312..313, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 314..315, value: Int( 1, @@ -941,9 +1143,11 @@ Module( kwarg: Some( Parameter { range: 317..325, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 319..325, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -952,12 +1156,15 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 327..334, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 327..330, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..328, id: Name("f"), ctx: Load, @@ -965,6 +1172,7 @@ Module( ), arguments: Arguments { range: 328..330, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -973,6 +1181,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 333..334, value: Int( 1, @@ -987,21 +1196,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 335..351, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 335..351, parameters: Some( Parameters { range: 342..346, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 342..343, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 342..343, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 342..343, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1016,6 +1233,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 348..351, }, ), @@ -1025,21 +1243,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 352..371, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 352..371, parameters: Some( Parameters { range: 359..366, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 359..360, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 359..360, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 359..360, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1049,11 +1275,14 @@ Module( args: [ ParameterWithDefault { range: 365..366, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 365..366, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 365..366, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1067,6 +1296,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 368..371, }, ), @@ -1076,27 +1306,36 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 372..391, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 372..391, parameters: Some( Parameters { range: 379..386, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 379..382, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 379..380, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 379..380, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 381..382, value: Int( 1, @@ -1114,6 +1353,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 388..391, }, ), @@ -1123,21 +1363,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 392..417, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 392..417, parameters: Some( Parameters { range: 399..412, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 399..400, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 399..400, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 399..400, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1145,11 +1393,14 @@ Module( }, ParameterWithDefault { range: 402..403, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 402..403, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 402..403, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1161,11 +1412,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 411..412, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 411..412, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 411..412, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1177,6 +1431,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 414..417, }, ), @@ -1186,28 +1441,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 418..440, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 418..440, parameters: Some( Parameters { range: 425..435, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 425..429, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 425..427, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kw"), range: 425..427, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 428..429, value: Int( 1, @@ -1221,11 +1485,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 434..435, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 434..435, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 434..435, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1237,6 +1504,7 @@ Module( ), body: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 437..440, }, ), @@ -1246,21 +1514,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 441..467, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 441..467, parameters: Some( Parameters { range: 448..464, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 448..449, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 448..449, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 448..449, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1268,17 +1544,21 @@ Module( }, ParameterWithDefault { range: 451..455, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 451..452, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 451..452, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 453..455, value: Int( 20, @@ -1291,17 +1571,21 @@ Module( args: [ ParameterWithDefault { range: 460..464, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 460..461, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 460..461, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 462..464, value: Int( 30, @@ -1318,6 +1602,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 466..467, value: Int( 1, @@ -1330,21 +1615,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 468..497, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 468..497, parameters: Some( Parameters { range: 475..494, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 475..476, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 475..476, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 475..476, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1352,11 +1645,14 @@ Module( }, ParameterWithDefault { range: 478..479, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 478..479, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 478..479, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1366,11 +1662,14 @@ Module( args: [ ParameterWithDefault { range: 484..485, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 484..485, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 484..485, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1381,11 +1680,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 490..491, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 490..491, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 490..491, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1393,11 +1695,14 @@ Module( }, ParameterWithDefault { range: 493..494, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 493..494, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 493..494, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1409,6 +1714,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 496..497, value: Int( 0, @@ -1421,21 +1727,29 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 498..530, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 498..530, parameters: Some( Parameters { range: 505..527, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 505..506, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 505..506, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 505..506, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1443,11 +1757,14 @@ Module( }, ParameterWithDefault { range: 508..509, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 508..509, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 508..509, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1457,11 +1774,14 @@ Module( args: [ ParameterWithDefault { range: 514..515, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 514..515, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 514..515, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1471,9 +1791,11 @@ Module( vararg: Some( Parameter { range: 517..519, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 518..519, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1481,11 +1803,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 521..522, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 521..522, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 521..522, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1495,9 +1820,11 @@ Module( kwarg: Some( Parameter { range: 524..527, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 526..527, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1506,6 +1833,7 @@ Module( ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 529..530, value: Int( 0, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap index ae3fc6d22cbfd9..dec8d0d2792341 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/list.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..384, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..17, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 15..17, elts: [], ctx: Load, @@ -24,13 +26,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..21, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 18..21, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 1, @@ -45,13 +50,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..26, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 22..26, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 1, @@ -66,13 +74,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..36, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 27..36, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 1, @@ -81,6 +92,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 31..32, value: Int( 2, @@ -89,6 +101,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 34..35, value: Int( 3, @@ -103,13 +116,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..47, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 37..47, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..39, value: Int( 1, @@ -118,6 +134,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..42, value: Int( 2, @@ -126,6 +143,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..45, value: Int( 3, @@ -140,9 +158,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..78, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 75..78, elts: [], ctx: Load, @@ -152,13 +172,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 79..92, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 79..92, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 1, @@ -173,13 +196,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..114, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 93..114, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 99..100, value: Int( 1, @@ -188,6 +214,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 110..111, value: Int( 2, @@ -202,21 +229,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..132, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 125..132, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 126..131, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 127..130, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 1, @@ -239,17 +271,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..149, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 133..149, elts: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 134..140, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 1, @@ -258,6 +294,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 138..139, value: Int( 2, @@ -270,10 +307,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 142..148, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 143..144, value: Int( 3, @@ -282,6 +321,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 146..147, value: Int( 4, @@ -300,16 +340,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 170..178, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 170..178, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 171..177, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("x"), ctx: Store, @@ -317,6 +361,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 176..177, value: Int( 2, @@ -333,16 +378,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 179..188, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 179..188, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 180..186, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 180..181, id: Name("x"), ctx: Store, @@ -350,6 +399,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 185..186, value: Int( 2, @@ -366,13 +416,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 189..203, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 189..203, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 190..191, value: Int( 1, @@ -381,9 +434,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 193..199, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..194, id: Name("x"), ctx: Store, @@ -391,6 +446,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 198..199, value: Int( 2, @@ -401,6 +457,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 3, @@ -415,13 +472,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 223..233, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 223..233, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 224..225, value: Int( 1, @@ -430,9 +490,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 227..229, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 228..229, id: Name("x"), ctx: Load, @@ -443,6 +505,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 231..232, value: Int( 3, @@ -457,13 +520,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 234..248, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 234..248, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 235..236, value: Int( 1, @@ -472,12 +538,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 238..244, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 239..244, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 239..240, id: Name("x"), ctx: Load, @@ -486,6 +555,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 243..244, id: Name("y"), ctx: Load, @@ -498,6 +568,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 246..247, value: Int( 3, @@ -512,16 +583,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 271..334, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 271..334, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 272..277, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 272..273, value: Int( 1, @@ -531,6 +606,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 276..277, value: Int( 2, @@ -541,10 +617,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 279..291, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 280..281, value: Int( 1, @@ -553,6 +631,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 283..284, value: Int( 2, @@ -561,6 +640,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 286..287, value: Int( 3, @@ -569,6 +649,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 289..290, value: Int( 4, @@ -581,10 +662,12 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 293..306, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 294..295, id: Name("a"), ctx: Load, @@ -592,9 +675,11 @@ Module( ), BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 297..302, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 297..298, id: Name("b"), ctx: Load, @@ -603,6 +688,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 301..302, id: Name("c"), ctx: Load, @@ -612,6 +698,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 304..305, id: Name("d"), ctx: Load, @@ -624,10 +711,12 @@ Module( ), Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 308..317, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 309..310, id: Name("a"), ctx: Load, @@ -635,6 +724,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 312..313, id: Name("b"), ctx: Load, @@ -642,6 +732,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 315..316, id: Name("c"), ctx: Load, @@ -652,12 +743,14 @@ Module( ), Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 319..325, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 320..321, id: Name("a"), ctx: Load, @@ -666,6 +759,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 323..324, value: Int( 1, @@ -678,9 +772,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 327..333, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..328, id: Name("x"), ctx: Store, @@ -688,6 +784,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 332..333, value: Int( 2, @@ -704,16 +801,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 335..383, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 335..383, elts: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 336..382, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 336..341, id: Name("call1"), ctx: Load, @@ -721,15 +822,19 @@ Module( ), arguments: Arguments { range: 341..382, + node_index: AtomicNodeIndex(..), args: [ Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 342..381, elt: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 342..361, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 342..347, id: Name("call2"), ctx: Load, @@ -737,15 +842,19 @@ Module( ), arguments: Arguments { range: 347..361, + node_index: AtomicNodeIndex(..), args: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 348..360, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 348..358, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 348..353, id: Name("value"), ctx: Load, @@ -754,12 +863,14 @@ Module( attr: Identifier { id: Name("attr"), range: 354..358, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 358..360, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -773,8 +884,10 @@ Module( generators: [ Comprehension { range: 362..381, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 366..373, id: Name("element"), ctx: Store, @@ -782,6 +895,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 377..381, id: Name("iter"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list_comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list_comprehension.py.snap index b81ae4169e2b16..de7a7eb783a044 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list_comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list_comprehension.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/list_comprehension.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..776, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..26, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -24,9 +26,11 @@ Module( ], value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 4..26, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("y"), ctx: Load, @@ -35,8 +39,10 @@ Module( generators: [ Comprehension { range: 7..25, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("y"), ctx: Store, @@ -44,10 +50,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 16..25, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..18, value: Int( 1, @@ -56,6 +64,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 2, @@ -64,6 +73,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 3, @@ -85,12 +95,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..49, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 28..49, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -99,8 +112,10 @@ Module( generators: [ Comprehension { range: 31..48, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("i"), ctx: Store, @@ -108,9 +123,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 40..48, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..45, id: Name("range"), ctx: Load, @@ -118,9 +135,11 @@ Module( ), arguments: Arguments { range: 45..48, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 46..47, value: Int( 5, @@ -142,12 +161,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..91, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 50..91, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("b"), ctx: Load, @@ -156,8 +178,10 @@ Module( generators: [ Comprehension { range: 53..90, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("c"), ctx: Store, @@ -165,6 +189,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("d"), ctx: Load, @@ -173,9 +198,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 67..73, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("x"), ctx: Load, @@ -187,6 +214,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("w"), ctx: Load, @@ -197,11 +225,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 77..85, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("y"), ctx: Load, @@ -209,6 +239,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..85, id: Name("yy"), ctx: Load, @@ -219,6 +250,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("z"), ctx: Load, @@ -234,12 +266,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..137, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 92..137, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("a"), ctx: Load, @@ -248,8 +283,10 @@ Module( generators: [ Comprehension { range: 95..116, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("b"), ctx: Store, @@ -257,6 +294,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("c"), ctx: Load, @@ -265,11 +303,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 109..116, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..110, id: Name("d"), ctx: Load, @@ -277,6 +317,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..116, id: Name("e"), ctx: Load, @@ -290,8 +331,10 @@ Module( }, Comprehension { range: 117..136, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..122, id: Name("f"), ctx: Store, @@ -299,6 +342,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("j"), ctx: Load, @@ -307,9 +351,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 131..136, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("k"), ctx: Load, @@ -321,6 +367,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("h"), ctx: Load, @@ -339,12 +386,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 138..189, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 138..189, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("a"), ctx: Load, @@ -353,8 +403,10 @@ Module( generators: [ Comprehension { range: 141..162, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..146, id: Name("b"), ctx: Store, @@ -362,6 +414,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("c"), ctx: Load, @@ -370,11 +423,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 155..162, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("d"), ctx: Load, @@ -382,6 +437,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("e"), ctx: Load, @@ -395,8 +451,10 @@ Module( }, Comprehension { range: 163..188, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 173..174, id: Name("f"), ctx: Store, @@ -404,6 +462,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("j"), ctx: Load, @@ -412,9 +471,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 183..188, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 183..184, id: Name("k"), ctx: Load, @@ -426,6 +487,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 187..188, id: Name("h"), ctx: Load, @@ -444,12 +506,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 190..209, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 190..209, elt: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 191..192, value: Int( 1, @@ -459,8 +524,10 @@ Module( generators: [ Comprehension { range: 193..208, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("i"), ctx: Store, @@ -468,9 +535,11 @@ Module( ), iter: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 202..208, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..203, id: Name("x"), ctx: Load, @@ -482,6 +551,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 207..208, id: Name("a"), ctx: Load, @@ -500,12 +570,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 210..227, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 210..227, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 211..212, id: Name("a"), ctx: Load, @@ -514,12 +587,15 @@ Module( generators: [ Comprehension { range: 213..226, + node_index: AtomicNodeIndex(..), target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 217..221, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 217..218, id: Name("a"), ctx: Store, @@ -527,6 +603,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 220..221, id: Name("b"), ctx: Store, @@ -539,6 +616,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("G"), ctx: Load, @@ -554,15 +632,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 228..257, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 228..257, elt: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 234..241, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 240..241, id: Name("x"), ctx: Load, @@ -573,12 +655,15 @@ Module( generators: [ Comprehension { range: 242..255, + node_index: AtomicNodeIndex(..), target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 246..250, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 246..247, id: Name("a"), ctx: Store, @@ -586,6 +671,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 249..250, id: Name("b"), ctx: Store, @@ -598,6 +684,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 254..255, id: Name("C"), ctx: Load, @@ -613,12 +700,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 258..300, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 258..300, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("i"), ctx: Load, @@ -627,8 +717,10 @@ Module( generators: [ Comprehension { range: 261..299, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("i"), ctx: Store, @@ -636,9 +728,11 @@ Module( ), iter: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 270..277, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 276..277, id: Name("x"), ctx: Load, @@ -649,9 +743,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 281..299, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 281..287, id: Name("entity"), ctx: Load, @@ -663,6 +759,7 @@ Module( comparators: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 295..299, }, ), @@ -679,12 +776,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 301..337, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 301..337, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 302..303, id: Name("x"), ctx: Load, @@ -693,8 +793,10 @@ Module( generators: [ Comprehension { range: 304..336, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 308..309, id: Name("x"), ctx: Store, @@ -702,15 +804,18 @@ Module( ), iter: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 314..330, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 319..323, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 314..315, id: Name("l"), ctx: Load, @@ -718,6 +823,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 329..330, id: Name("L"), ctx: Load, @@ -728,6 +834,7 @@ Module( ifs: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 335..336, id: Name("T"), ctx: Load, @@ -743,12 +850,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 338..380, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 338..380, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 339..340, id: Name("i"), ctx: Load, @@ -757,8 +867,10 @@ Module( generators: [ Comprehension { range: 341..379, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 345..346, id: Name("i"), ctx: Store, @@ -766,18 +878,22 @@ Module( ), iter: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 351..373, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 362..366, value: true, }, ), body: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 351..358, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 357..358, id: Name("x"), ctx: Load, @@ -787,6 +903,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 372..373, id: Name("X"), ctx: Load, @@ -797,6 +914,7 @@ Module( ifs: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 378..379, id: Name("F"), ctx: Load, @@ -812,12 +930,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 381..423, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 381..423, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 382..383, id: Name("i"), ctx: Load, @@ -826,8 +947,10 @@ Module( generators: [ Comprehension { range: 384..422, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 388..389, id: Name("i"), ctx: Store, @@ -835,18 +958,22 @@ Module( ), iter: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 393..417, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 400..416, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 405..409, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 400..401, id: Name("x"), ctx: Load, @@ -854,6 +981,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 415..416, id: Name("X"), ctx: Load, @@ -866,6 +994,7 @@ Module( ifs: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..422, id: Name("F"), ctx: Load, @@ -881,12 +1010,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 424..457, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 424..457, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 425..426, id: Name("f"), ctx: Load, @@ -895,8 +1027,10 @@ Module( generators: [ Comprehension { range: 427..456, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 431..432, id: Name("f"), ctx: Store, @@ -904,9 +1038,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 436..456, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 436..437, id: Name("c"), ctx: Load, @@ -914,18 +1050,22 @@ Module( ), arguments: Arguments { range: 437..456, + node_index: AtomicNodeIndex(..), args: [ If( ExprIf { + node_index: AtomicNodeIndex(..), range: 438..455, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 443..447, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 438..439, id: Name("x"), ctx: Load, @@ -933,6 +1073,7 @@ Module( ), orelse: List( ExprList { + node_index: AtomicNodeIndex(..), range: 453..455, elts: [], ctx: Load, @@ -955,12 +1096,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 596..618, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 596..618, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 597..598, id: Name("x"), ctx: Load, @@ -969,8 +1113,10 @@ Module( generators: [ Comprehension { range: 599..617, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 603..604, id: Name("x"), ctx: Store, @@ -978,10 +1124,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 609..616, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 615..616, id: Name("y"), ctx: Load, @@ -1000,12 +1148,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 619..646, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 619..646, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 620..621, id: Name("x"), ctx: Load, @@ -1014,8 +1165,10 @@ Module( generators: [ Comprehension { range: 622..645, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 626..627, id: Name("x"), ctx: Store, @@ -1023,9 +1176,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 632..644, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 643..644, id: Name("y"), ctx: Load, @@ -1043,12 +1198,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 647..673, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 647..673, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 648..649, id: Name("x"), ctx: Load, @@ -1057,8 +1215,10 @@ Module( generators: [ Comprehension { range: 650..672, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 654..655, id: Name("x"), ctx: Store, @@ -1066,19 +1226,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 660..671, parameters: Some( Parameters { range: 667..668, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 667..668, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 667..668, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 667..668, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1092,6 +1259,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 670..671, id: Name("y"), ctx: Load, @@ -1109,12 +1277,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 674..704, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 674..704, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 675..676, id: Name("x"), ctx: Load, @@ -1123,8 +1294,10 @@ Module( generators: [ Comprehension { range: 677..703, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 681..682, id: Name("x"), ctx: Store, @@ -1132,6 +1305,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 686..690, id: Name("data"), ctx: Load, @@ -1140,10 +1314,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 695..702, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 701..702, id: Name("y"), ctx: Load, @@ -1162,12 +1338,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 705..740, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 705..740, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 706..707, id: Name("x"), ctx: Load, @@ -1176,8 +1355,10 @@ Module( generators: [ Comprehension { range: 708..739, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 712..713, id: Name("x"), ctx: Store, @@ -1185,6 +1366,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 717..721, id: Name("data"), ctx: Load, @@ -1193,9 +1375,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 726..738, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 737..738, id: Name("y"), ctx: Load, @@ -1213,12 +1397,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 741..775, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 741..775, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 742..743, id: Name("x"), ctx: Load, @@ -1227,8 +1414,10 @@ Module( generators: [ Comprehension { range: 744..774, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 748..749, id: Name("x"), ctx: Store, @@ -1236,6 +1425,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 753..757, id: Name("data"), ctx: Load, @@ -1244,19 +1434,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 762..773, parameters: Some( Parameters { range: 769..770, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 769..770, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 769..770, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 769..770, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1270,6 +1467,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 772..773, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__name.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__name.py.snap index dd9d18e848b94d..506ee527223dc7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__name.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__name.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/name.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..76, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..1, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("_"), ctx: Load, @@ -24,9 +26,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2..5, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..4, id: Name("_"), ctx: Load, @@ -36,9 +40,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..8, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..8, id: Name("__"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..17, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..17, id: Name("__init__"), ctx: Load, @@ -60,9 +68,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..22, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..22, id: Name("name"), ctx: Load, @@ -72,9 +82,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..29, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..28, id: Name("name"), ctx: Load, @@ -84,9 +96,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..65, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..65, id: Name("match"), ctx: Load, @@ -96,9 +110,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..70, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..70, id: Name("case"), ctx: Load, @@ -108,9 +124,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..75, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..75, id: Name("type"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__named.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__named.py.snap index 098a3372776485..1ac703b53b2e6f 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__named.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__named.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/named.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..157, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1..10, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..5, id: Name("name"), ctx: Store, @@ -25,6 +28,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 0, @@ -37,12 +41,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..29, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 13..28, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..17, id: Name("name"), ctx: Store, @@ -50,9 +57,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 22..27, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -61,6 +70,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("y"), ctx: Load, @@ -74,12 +84,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..45, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 31..44, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..35, id: Name("name"), ctx: Store, @@ -87,9 +100,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 39..44, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 39..40, value: Int( 1, @@ -99,6 +114,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..44, value: Int( 1, @@ -113,12 +129,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..63, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 47..62, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..51, id: Name("name"), ctx: Store, @@ -126,13 +145,16 @@ Module( ), value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 55..62, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 56..58, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("x"), ctx: Load, @@ -143,6 +165,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("y"), ctx: Load, @@ -159,12 +182,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..90, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 65..89, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..69, id: Name("name"), ctx: Store, @@ -172,15 +198,18 @@ Module( ), value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 73..89, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 78..82, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("x"), ctx: Load, @@ -188,6 +217,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..89, id: Name("y"), ctx: Load, @@ -201,12 +231,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..112, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 92..111, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..96, id: Name("name"), ctx: Store, @@ -214,19 +247,26 @@ Module( ), value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 100..111, parameters: Some( Parameters { range: 107..108, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 107..108, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 107..108, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 107..108, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -240,6 +280,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 110..111, id: Name("x"), ctx: Load, @@ -253,12 +294,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 113..132, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 114..131, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..118, id: Name("name"), ctx: Store, @@ -266,10 +310,12 @@ Module( ), value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 123..130, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("x"), ctx: Load, @@ -284,12 +330,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..157, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 134..156, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..138, id: Name("name"), ctx: Store, @@ -297,9 +346,11 @@ Module( ), value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 143..155, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 154..155, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__number_literal.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__number_literal.py.snap index 64d1e63f40b2e0..46dac564f0a85b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__number_literal.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__number_literal.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/number_literal.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..700, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -24,6 +26,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4..13, value: Int( 123456789, @@ -34,10 +37,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 14..24, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Store, @@ -46,6 +51,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..24, value: Int( 123456, @@ -56,10 +62,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 25..31, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Store, @@ -68,6 +76,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 29..31, value: Float( 0.1, @@ -78,10 +87,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 32..38, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("x"), ctx: Store, @@ -90,6 +101,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..38, value: Float( 1.0, @@ -100,10 +112,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 39..47, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Store, @@ -112,6 +126,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..47, value: Float( 10.0, @@ -122,10 +137,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 48..56, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("x"), ctx: Store, @@ -134,6 +151,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..56, value: Float( 0.1, @@ -144,10 +162,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 57..73, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("x"), ctx: Store, @@ -156,6 +176,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..73, value: Float( 1.00000001, @@ -166,10 +187,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 74..97, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("x"), ctx: Store, @@ -178,6 +201,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..97, value: Float( 123456789.12345679, @@ -188,10 +212,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 98..131, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("x"), ctx: Store, @@ -200,6 +226,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 102..131, value: Float( inf, @@ -210,10 +237,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 132..155, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("x"), ctx: Store, @@ -222,6 +251,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 136..155, value: Float( inf, @@ -232,10 +262,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 156..170, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("x"), ctx: Store, @@ -244,6 +276,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..170, value: Complex { real: 0.0, @@ -255,10 +288,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 171..195, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("x"), ctx: Store, @@ -267,6 +302,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 175..195, value: Complex { real: 0.0, @@ -278,10 +314,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 196..207, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("x"), ctx: Store, @@ -290,6 +328,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 200..207, value: Int( 727756, @@ -300,10 +339,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 208..218, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 208..209, id: Name("x"), ctx: Store, @@ -312,6 +353,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 212..218, value: Int( 11, @@ -322,10 +364,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 219..228, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 219..220, id: Name("x"), ctx: Store, @@ -334,6 +378,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 223..228, value: Int( 511, @@ -344,10 +389,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 229..244, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 229..230, id: Name("x"), ctx: Store, @@ -356,6 +403,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 233..244, value: Float( 6e-9, @@ -366,10 +414,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 245..254, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("x"), ctx: Store, @@ -378,6 +428,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 249..254, value: Int( 10000, @@ -388,10 +439,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 255..265, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..256, id: Name("x"), ctx: Store, @@ -400,6 +453,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 259..265, value: Int( 133333, @@ -410,10 +464,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 286..298, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 286..287, id: Name("x"), ctx: Store, @@ -422,9 +478,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 290..298, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 290..292, value: Float( 1.0, @@ -434,6 +492,7 @@ Module( attr: Identifier { id: Name("imag"), range: 294..298, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -442,10 +501,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 299..312, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 299..300, id: Name("x"), ctx: Store, @@ -454,9 +515,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 303..312, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 303..307, value: Float( 10.0, @@ -466,6 +529,7 @@ Module( attr: Identifier { id: Name("imag"), range: 308..312, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -474,10 +538,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 313..326, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 313..314, id: Name("x"), ctx: Store, @@ -486,9 +552,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 317..326, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 317..321, value: Float( 0.1, @@ -498,6 +566,7 @@ Module( attr: Identifier { id: Name("real"), range: 322..326, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -506,10 +575,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 327..356, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..328, id: Name("x"), ctx: Store, @@ -518,12 +589,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 331..356, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 331..354, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 331..350, value: Float( 123456789.12345679, @@ -533,12 +607,14 @@ Module( attr: Identifier { id: Name("hex"), range: 351..354, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 354..356, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -548,10 +624,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 357..396, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 357..358, id: Name("x"), ctx: Store, @@ -560,9 +638,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 361..396, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 361..390, value: Float( inf, @@ -572,6 +652,7 @@ Module( attr: Identifier { id: Name("real"), range: 392..396, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -580,10 +661,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 397..433, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 397..398, id: Name("x"), ctx: Store, @@ -592,12 +675,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 401..433, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 401..431, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 401..420, value: Float( inf, @@ -607,12 +693,14 @@ Module( attr: Identifier { id: Name("conjugate"), range: 422..431, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 431..433, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -622,10 +710,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 434..453, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 434..435, id: Name("x"), ctx: Store, @@ -634,9 +724,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 438..453, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 438..448, value: Complex { real: 0.0, @@ -647,6 +739,7 @@ Module( attr: Identifier { id: Name("real"), range: 449..453, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -655,10 +748,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 454..507, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 454..455, id: Name("x"), ctx: Store, @@ -667,12 +762,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 458..507, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 458..486, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 458..478, value: Complex { real: 0.0, @@ -683,21 +781,26 @@ Module( attr: Identifier { id: Name("__add__"), range: 479..486, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 486..507, + node_index: AtomicNodeIndex(..), args: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 487..506, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 487..504, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 487..493, value: Int( 11, @@ -707,12 +810,14 @@ Module( attr: Identifier { id: Name("bit_length"), range: 494..504, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 504..506, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -727,10 +832,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 508..531, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 508..509, id: Name("x"), ctx: Store, @@ -739,12 +846,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 512..531, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 512..529, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 512..519, value: Int( 727756, @@ -754,12 +864,14 @@ Module( attr: Identifier { id: Name("conjugate"), range: 520..529, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 529..531, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -769,10 +881,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 532..555, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 532..533, id: Name("x"), ctx: Store, @@ -781,12 +895,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 536..555, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 536..553, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 536..542, value: Int( 11, @@ -796,12 +913,14 @@ Module( attr: Identifier { id: Name("conjugate"), range: 544..553, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 553..555, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -811,10 +930,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 556..571, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 556..557, id: Name("x"), ctx: Store, @@ -823,9 +944,11 @@ Module( ], value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 560..571, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 560..565, value: Int( 511, @@ -835,6 +958,7 @@ Module( attr: Identifier { id: Name("real"), range: 567..571, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -843,10 +967,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 572..595, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 572..573, id: Name("x"), ctx: Store, @@ -855,12 +981,15 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 576..595, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 576..593, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 576..587, value: Float( 6e-9, @@ -870,12 +999,14 @@ Module( attr: Identifier { id: Name("hex"), range: 590..593, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 593..595, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -885,10 +1016,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 596..610, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 596..597, id: Name("x"), ctx: Store, @@ -897,10 +1030,12 @@ Module( ], value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 600..610, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 601..610, value: Complex { real: 0.0, @@ -914,12 +1049,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 612..632, test: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 615..623, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 615..617, value: Int( 10, @@ -929,6 +1067,7 @@ Module( attr: Identifier { id: Name("real"), range: 619..623, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -936,9 +1075,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 629..632, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 629..632, }, ), @@ -950,10 +1091,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 677..688, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 677..678, id: Name("y"), ctx: Store, @@ -962,9 +1105,11 @@ Module( ], value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 681..688, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 681..684, value: Int( 100, @@ -973,6 +1118,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 685..687, id: Name("no"), ctx: Load, @@ -985,10 +1131,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 689..700, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 689..690, id: Name("y"), ctx: Store, @@ -997,9 +1145,11 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 693..700, func: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 693..696, value: Int( 100, @@ -1008,9 +1158,11 @@ Module( ), arguments: Arguments { range: 696..700, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 697..699, id: Name("no"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__parenthesized.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__parenthesized.py.snap index f5a6858f1d4d77..747ed8ee3decee 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__parenthesized.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__parenthesized.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/parenthesized.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..6, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..5, id: Name("expr"), ctx: Load, @@ -24,12 +26,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 7..15, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 7..15, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..12, id: Name("expr"), ctx: Load, @@ -37,6 +42,7 @@ Module( ), arguments: Arguments { range: 13..15, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -46,18 +52,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..28, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..28, func: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..26, func: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 16..24, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..21, id: Name("expr"), ctx: Load, @@ -65,6 +76,7 @@ Module( ), arguments: Arguments { range: 22..24, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -72,6 +84,7 @@ Module( ), arguments: Arguments { range: 24..26, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -79,6 +92,7 @@ Module( ), arguments: Arguments { range: 26..28, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -88,19 +102,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..44, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 31..43, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 31..38, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..32, id: Name("a"), ctx: Load, @@ -108,6 +126,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("b"), ctx: Load, @@ -118,6 +137,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 42..43, id: Name("c"), ctx: Load, @@ -130,22 +150,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 45..58, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 46..57, parameters: Some( Parameters { range: 53..54, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 53..54, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 53..54, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 53..54, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -159,6 +187,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -170,12 +199,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..67, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 60..66, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("x"), ctx: Store, @@ -183,6 +215,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 2, @@ -195,13 +228,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..77, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 69..76, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Load, @@ -214,12 +250,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..92, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 79..91, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap index f8ed36c227da78..eb99b5e4d078c7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/set.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..313, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..16, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 14..16, items: [], }, @@ -23,13 +25,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..20, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 17..20, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 1, @@ -43,13 +48,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..25, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 21..25, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 22..23, value: Int( 1, @@ -63,13 +71,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..35, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 26..35, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 27..28, value: Int( 1, @@ -78,6 +89,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 30..31, value: Int( 2, @@ -86,6 +98,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 33..34, value: Int( 3, @@ -99,13 +112,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..46, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 36..46, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 37..38, value: Int( 1, @@ -114,6 +130,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 40..41, value: Int( 2, @@ -122,6 +139,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..44, value: Int( 3, @@ -135,9 +153,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 74..77, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 74..77, items: [], }, @@ -146,13 +166,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..91, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 78..91, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 1, @@ -166,13 +189,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..113, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 92..113, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 98..99, value: Int( 1, @@ -181,6 +207,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 109..110, value: Int( 2, @@ -194,17 +221,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 124..129, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 124..129, elts: [ Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 125..128, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 126..127, value: Int( 1, @@ -221,17 +252,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 130..146, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 130..146, elts: [ Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 131..137, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 132..133, value: Int( 1, @@ -240,6 +275,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 2, @@ -251,10 +287,12 @@ Module( ), Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 139..145, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 140..141, value: Int( 3, @@ -263,6 +301,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 143..144, value: Int( 4, @@ -279,16 +318,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 167..175, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 167..175, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 168..174, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..169, id: Name("x"), ctx: Store, @@ -296,6 +339,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 173..174, value: Int( 2, @@ -311,13 +355,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 176..190, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 176..190, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 177..178, value: Int( 1, @@ -326,9 +373,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 180..186, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 180..181, id: Name("x"), ctx: Store, @@ -336,6 +385,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 185..186, value: Int( 2, @@ -346,6 +396,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 188..189, value: Int( 3, @@ -359,13 +410,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 191..205, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 191..205, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 192..193, value: Int( 1, @@ -374,9 +428,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 196..202, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("x"), ctx: Store, @@ -384,6 +440,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 201..202, value: Int( 2, @@ -399,13 +456,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 225..235, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 225..235, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 226..227, value: Int( 1, @@ -414,9 +474,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 229..231, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 230..231, id: Name("x"), ctx: Load, @@ -427,6 +489,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 233..234, value: Int( 3, @@ -440,13 +503,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 236..250, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 236..250, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 237..238, value: Int( 1, @@ -455,12 +521,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 240..246, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 241..246, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..242, id: Name("x"), ctx: Load, @@ -469,6 +538,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("y"), ctx: Load, @@ -481,6 +551,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 248..249, value: Int( 3, @@ -494,16 +565,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 273..312, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 273..312, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 274..279, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 274..275, value: Int( 1, @@ -513,6 +588,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 278..279, value: Int( 2, @@ -523,10 +599,12 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 281..287, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 282..283, id: Name("a"), ctx: Load, @@ -534,6 +612,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 285..286, id: Name("b"), ctx: Load, @@ -546,10 +625,12 @@ Module( ), Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 289..298, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 290..291, value: Int( 1, @@ -558,6 +639,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 293..294, value: Int( 2, @@ -566,6 +648,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 296..297, value: Int( 3, @@ -577,12 +660,14 @@ Module( ), Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 300..311, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 301..302, id: Name("a"), ctx: Load, @@ -591,6 +676,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 304..305, id: Name("b"), ctx: Load, @@ -601,6 +687,7 @@ Module( key: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 309..310, id: Name("d"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set_comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set_comprehension.py.snap index 5365de4ac2dfe6..8ead74d20d4579 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set_comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set_comprehension.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/set_comprehension.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..492, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..15, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 0..15, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("x"), ctx: Load, @@ -26,8 +29,10 @@ Module( generators: [ Comprehension { range: 3..14, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("i"), ctx: Store, @@ -35,6 +40,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..14, id: Name("ll"), ctx: Load, @@ -50,12 +56,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..57, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 16..57, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("b"), ctx: Load, @@ -64,8 +73,10 @@ Module( generators: [ Comprehension { range: 19..56, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("c"), ctx: Store, @@ -73,6 +84,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("d"), ctx: Load, @@ -81,9 +93,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 33..39, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..34, id: Name("x"), ctx: Load, @@ -95,6 +109,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("w"), ctx: Load, @@ -105,11 +120,13 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 43..51, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("y"), ctx: Load, @@ -117,6 +134,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..51, id: Name("yy"), ctx: Load, @@ -127,6 +145,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("z"), ctx: Load, @@ -142,12 +161,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..103, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 58..103, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("a"), ctx: Load, @@ -156,8 +178,10 @@ Module( generators: [ Comprehension { range: 61..82, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("b"), ctx: Store, @@ -165,6 +189,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("c"), ctx: Load, @@ -173,11 +198,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 75..82, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("d"), ctx: Load, @@ -185,6 +212,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("e"), ctx: Load, @@ -198,8 +226,10 @@ Module( }, Comprehension { range: 83..102, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..88, id: Name("f"), ctx: Store, @@ -207,6 +237,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("j"), ctx: Load, @@ -215,9 +246,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 97..102, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("k"), ctx: Load, @@ -229,6 +262,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..102, id: Name("h"), ctx: Load, @@ -247,12 +281,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 104..155, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 104..155, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("a"), ctx: Load, @@ -261,8 +298,10 @@ Module( generators: [ Comprehension { range: 107..128, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..112, id: Name("b"), ctx: Store, @@ -270,6 +309,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("c"), ctx: Load, @@ -278,11 +318,13 @@ Module( ifs: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 121..128, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..122, id: Name("d"), ctx: Load, @@ -290,6 +332,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("e"), ctx: Load, @@ -303,8 +346,10 @@ Module( }, Comprehension { range: 129..154, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("f"), ctx: Store, @@ -312,6 +357,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 144..145, id: Name("j"), ctx: Load, @@ -320,9 +366,11 @@ Module( ifs: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 149..154, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 149..150, id: Name("k"), ctx: Load, @@ -334,6 +382,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 153..154, id: Name("h"), ctx: Load, @@ -352,12 +401,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 156..173, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 156..173, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..158, id: Name("a"), ctx: Load, @@ -366,12 +418,15 @@ Module( generators: [ Comprehension { range: 159..172, + node_index: AtomicNodeIndex(..), target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 163..167, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("a"), ctx: Store, @@ -379,6 +434,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("b"), ctx: Store, @@ -391,6 +447,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..172, id: Name("G"), ctx: Load, @@ -406,12 +463,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 312..334, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 312..334, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 313..314, id: Name("x"), ctx: Load, @@ -420,8 +480,10 @@ Module( generators: [ Comprehension { range: 315..333, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 319..320, id: Name("x"), ctx: Store, @@ -429,10 +491,12 @@ Module( ), iter: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 325..332, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 331..332, id: Name("y"), ctx: Load, @@ -451,12 +515,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 335..362, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 335..362, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 336..337, id: Name("x"), ctx: Load, @@ -465,8 +532,10 @@ Module( generators: [ Comprehension { range: 338..361, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 342..343, id: Name("x"), ctx: Store, @@ -474,9 +543,11 @@ Module( ), iter: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 348..360, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 359..360, id: Name("y"), ctx: Load, @@ -494,12 +565,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 363..389, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 363..389, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 364..365, id: Name("x"), ctx: Load, @@ -508,8 +582,10 @@ Module( generators: [ Comprehension { range: 366..388, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 370..371, id: Name("x"), ctx: Store, @@ -517,19 +593,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 376..387, parameters: Some( Parameters { range: 383..384, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 383..384, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 383..384, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 383..384, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -543,6 +626,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 386..387, id: Name("y"), ctx: Load, @@ -560,12 +644,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 390..420, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 390..420, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 391..392, id: Name("x"), ctx: Load, @@ -574,8 +661,10 @@ Module( generators: [ Comprehension { range: 393..419, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 397..398, id: Name("x"), ctx: Store, @@ -583,6 +672,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 402..406, id: Name("data"), ctx: Load, @@ -591,10 +681,12 @@ Module( ifs: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 411..418, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 417..418, id: Name("y"), ctx: Load, @@ -613,12 +705,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 421..456, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 421..456, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 422..423, id: Name("x"), ctx: Load, @@ -627,8 +722,10 @@ Module( generators: [ Comprehension { range: 424..455, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 428..429, id: Name("x"), ctx: Store, @@ -636,6 +733,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 433..437, id: Name("data"), ctx: Load, @@ -644,9 +742,11 @@ Module( ifs: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 442..454, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 453..454, id: Name("y"), ctx: Load, @@ -664,12 +764,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 457..491, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 457..491, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 458..459, id: Name("x"), ctx: Load, @@ -678,8 +781,10 @@ Module( generators: [ Comprehension { range: 460..490, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 464..465, id: Name("x"), ctx: Store, @@ -687,6 +792,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 469..473, id: Name("data"), ctx: Load, @@ -695,19 +801,26 @@ Module( ifs: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 478..489, parameters: Some( Parameters { range: 485..486, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 485..486, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 485..486, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 485..486, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -721,6 +834,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 488..489, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__slice.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__slice.py.snap index 2f33310702852f..c04177fae75bdb 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__slice.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__slice.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/slice.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..211, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..27, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 23..27, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("x"), ctx: Load, @@ -25,6 +28,7 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 25..26, lower: None, upper: None, @@ -38,12 +42,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..33, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 28..33, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("x"), ctx: Load, @@ -51,10 +58,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 30..32, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 30..31, value: Int( 1, @@ -73,12 +82,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 34..39, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 34..39, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Load, @@ -86,11 +98,13 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 36..38, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 37..38, value: Int( 2, @@ -108,12 +122,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..46, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 40..46, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("x"), ctx: Load, @@ -121,10 +138,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 42..45, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 42..43, value: Int( 1, @@ -135,6 +154,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..45, value: Int( 2, @@ -152,12 +172,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..52, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 47..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Load, @@ -165,6 +188,7 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 49..51, lower: None, upper: None, @@ -178,12 +202,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..59, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 53..59, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("x"), ctx: Load, @@ -191,10 +218,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 55..58, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 1, @@ -213,12 +242,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..66, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 60..66, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("x"), ctx: Load, @@ -226,11 +258,13 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 62..65, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 2, @@ -248,12 +282,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..74, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 67..74, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("x"), ctx: Load, @@ -261,10 +298,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 69..73, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 69..70, value: Int( 1, @@ -275,6 +314,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 2, @@ -292,12 +332,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..81, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 75..81, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Load, @@ -305,12 +348,14 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 77..80, lower: None, upper: None, step: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 3, @@ -327,12 +372,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..89, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 82..89, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("x"), ctx: Load, @@ -340,10 +388,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 84..88, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 84..85, value: Int( 1, @@ -355,6 +405,7 @@ Module( step: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 87..88, value: Int( 3, @@ -371,12 +422,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 90..97, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 90..97, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("x"), ctx: Load, @@ -384,11 +438,13 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 92..96, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 93..94, value: Int( 2, @@ -399,6 +455,7 @@ Module( step: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 95..96, value: Int( 3, @@ -415,12 +472,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 98..106, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 98..106, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("x"), ctx: Load, @@ -428,10 +488,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 100..105, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..101, value: Int( 1, @@ -442,6 +504,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 102..103, value: Int( 2, @@ -452,6 +515,7 @@ Module( step: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..105, value: Int( 3, @@ -468,12 +532,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 127..136, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 127..136, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("x"), ctx: Load, @@ -481,9 +548,11 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 129..135, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("y"), ctx: Store, @@ -491,6 +560,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 134..135, value: Int( 2, @@ -506,12 +576,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 137..149, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 137..149, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("x"), ctx: Load, @@ -519,13 +592,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 139..148, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 140..146, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 140..141, id: Name("y"), ctx: Store, @@ -533,6 +609,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 145..146, value: Int( 2, @@ -553,12 +630,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 150..160, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 150..160, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("x"), ctx: Load, @@ -566,13 +646,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 152..159, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 152..158, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..153, id: Name("y"), ctx: Store, @@ -580,6 +663,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 157..158, value: Int( 2, @@ -600,12 +684,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 202..210, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 202..210, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 202..203, id: Name("x"), ctx: Load, @@ -613,10 +700,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 204..209, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 204..205, value: Int( 1, @@ -625,11 +714,13 @@ Module( ), Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 206..208, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 207..208, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__starred.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__starred.py.snap index 3d1f786a8c23f6..a4fe4f78b4714d 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__starred.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__starred.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/starred.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..172, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..2, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 0..2, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("a"), ctx: Load, @@ -30,15 +33,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3..11, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3..11, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5..10, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("a"), ctx: Load, @@ -47,6 +54,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, @@ -62,15 +70,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..19, value: Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 12..19, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 13..19, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..14, id: Name("x"), ctx: Load, @@ -79,6 +91,7 @@ Module( attr: Identifier { id: Name("attr"), range: 15..19, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -90,10 +103,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 21..57, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..32, id: Name("array_slice"), ctx: Store, @@ -102,9 +117,11 @@ Module( ], value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 35..57, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..40, id: Name("array"), ctx: Load, @@ -112,10 +129,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 41..56, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..42, value: Int( 0, @@ -124,9 +143,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 44..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..52, id: Name("indexes"), ctx: Load, @@ -137,10 +158,12 @@ Module( ), UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 54..56, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 1, @@ -161,13 +184,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 58..94, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 58..80, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..63, id: Name("array"), ctx: Load, @@ -175,10 +201,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 64..79, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..65, value: Int( 0, @@ -187,9 +215,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 67..75, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..75, id: Name("indexes"), ctx: Load, @@ -200,10 +230,12 @@ Module( ), UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 77..79, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 1, @@ -223,6 +255,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..94, id: Name("array_slice"), ctx: Load, @@ -232,12 +265,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..140, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 95..140, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..100, id: Name("array"), ctx: Load, @@ -245,13 +281,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 101..139, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 101..119, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..119, id: Name("indexes_to_select"), ctx: Load, @@ -262,9 +301,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 121..139, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..139, id: Name("indexes_to_select"), ctx: Load, @@ -285,12 +326,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 141..171, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 141..171, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 141..146, id: Name("array"), ctx: Load, @@ -298,14 +342,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 147..170, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 147..150, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 147..148, value: Int( 3, @@ -316,6 +363,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 5, @@ -328,9 +376,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 152..170, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 153..170, id: Name("indexes_to_select"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__string.py.snap index 6cddb0139928a2..f9480bed422c73 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__string.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/string.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..163, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..13, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 0..13, value: StringLiteralValue { inner: Single( StringLiteral { range: 0..13, + node_index: AtomicNodeIndex(..), value: "Hello World", flags: StringLiteralFlags { quote_style: Single, @@ -35,14 +38,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..20, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 14..20, value: StringLiteralValue { inner: Single( StringLiteral { range: 14..20, + node_index: AtomicNodeIndex(..), value: "😎", flags: StringLiteralFlags { quote_style: Double, @@ -58,9 +64,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..32, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 21..32, value: StringLiteralValue { inner: Concatenated( @@ -68,6 +76,7 @@ Module( strings: [ StringLiteral { range: 21..26, + node_index: AtomicNodeIndex(..), value: "Foo", flags: StringLiteralFlags { quote_style: Single, @@ -77,6 +86,7 @@ Module( }, StringLiteral { range: 27..32, + node_index: AtomicNodeIndex(..), value: "Bar", flags: StringLiteralFlags { quote_style: Single, @@ -95,9 +105,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 33..60, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 39..58, value: StringLiteralValue { inner: Concatenated( @@ -105,6 +117,7 @@ Module( strings: [ StringLiteral { range: 39..42, + node_index: AtomicNodeIndex(..), value: "A", flags: StringLiteralFlags { quote_style: Single, @@ -114,6 +127,7 @@ Module( }, StringLiteral { range: 47..50, + node_index: AtomicNodeIndex(..), value: "B", flags: StringLiteralFlags { quote_style: Single, @@ -123,6 +137,7 @@ Module( }, StringLiteral { range: 55..58, + node_index: AtomicNodeIndex(..), value: "C", flags: StringLiteralFlags { quote_style: Single, @@ -141,14 +156,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 61..79, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 61..79, value: StringLiteralValue { inner: Single( StringLiteral { range: 61..79, + node_index: AtomicNodeIndex(..), value: "Olá, Mundo!", flags: StringLiteralFlags { quote_style: Single, @@ -164,14 +182,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 80..91, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 80..91, value: StringLiteralValue { inner: Single( StringLiteral { range: 80..91, + node_index: AtomicNodeIndex(..), value: "ABCDE", flags: StringLiteralFlags { quote_style: Double, @@ -187,9 +208,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..121, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 98..119, value: StringLiteralValue { inner: Concatenated( @@ -197,6 +220,7 @@ Module( strings: [ StringLiteral { range: 98..106, + node_index: AtomicNodeIndex(..), value: "aB", flags: StringLiteralFlags { quote_style: Single, @@ -206,6 +230,7 @@ Module( }, StringLiteral { range: 111..119, + node_index: AtomicNodeIndex(..), value: "cD", flags: StringLiteralFlags { quote_style: Single, @@ -224,14 +249,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..136, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 122..136, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 122..136, + node_index: AtomicNodeIndex(..), value: [ 104, 101, @@ -259,15 +287,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 137..161, value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 137..161, value: BytesLiteralValue { inner: Concatenated( [ BytesLiteral { range: 137..145, + node_index: AtomicNodeIndex(..), value: [ 98, 121, @@ -283,6 +314,7 @@ Module( }, BytesLiteral { range: 146..161, + node_index: AtomicNodeIndex(..), value: [ 99, 111, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__subscript.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__subscript.py.snap index 23d49419b20daf..f90b4a3033bf76 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__subscript.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__subscript.py.snap @@ -1,26 +1,30 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/subscript.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..266, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..10, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..10, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 0..7, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..4, id: Name("data"), ctx: Load, @@ -28,6 +32,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 0, @@ -39,6 +44,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 0, @@ -52,12 +58,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 11..21, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 11..21, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..15, id: Name("data"), ctx: Load, @@ -65,10 +74,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 16..20, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 0, @@ -77,6 +88,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 19..20, value: Int( 1, @@ -95,12 +107,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..31, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 22..31, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..26, id: Name("data"), ctx: Load, @@ -108,14 +123,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 27..30, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 27..29, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 27..28, value: Int( 0, @@ -139,12 +157,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..43, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 32..43, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..36, id: Name("data"), ctx: Load, @@ -152,14 +173,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 37..42, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 37..39, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 37..38, value: Int( 0, @@ -173,6 +197,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..42, value: Int( 1, @@ -191,12 +216,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..56, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 44..56, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..48, id: Name("data"), ctx: Load, @@ -204,14 +232,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 49..55, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 49..52, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 0, @@ -222,6 +253,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, @@ -234,6 +266,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 2, @@ -252,12 +285,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..80, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 57..80, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..61, id: Name("data"), ctx: Load, @@ -265,14 +301,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 62..79, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 62..67, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 62..63, value: Int( 0, @@ -283,6 +322,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..65, value: Int( 1, @@ -293,6 +333,7 @@ Module( step: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 66..67, value: Int( 2, @@ -304,6 +345,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 69..70, value: Int( 3, @@ -312,10 +354,12 @@ Module( ), Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 72..79, lower: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("a"), ctx: Load, @@ -325,9 +369,11 @@ Module( upper: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 74..79, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("b"), ctx: Load, @@ -336,6 +382,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 1, @@ -360,12 +407,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..93, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 81..93, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..85, id: Name("data"), ctx: Load, @@ -373,9 +423,11 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 86..92, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("a"), ctx: Store, @@ -383,6 +435,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("b"), ctx: Load, @@ -397,12 +450,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..106, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 94..106, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 94..98, id: Name("data"), ctx: Load, @@ -410,10 +466,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 99..105, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 99..100, lower: None, upper: None, @@ -422,11 +480,13 @@ Module( ), Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 102..105, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 103..105, value: Int( 11, @@ -449,12 +509,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..120, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 107..120, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..111, id: Name("data"), ctx: Load, @@ -462,10 +525,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 112..119, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 112..113, value: Int( 1, @@ -474,6 +539,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 2, @@ -482,6 +548,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 3, @@ -500,12 +567,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 121..132, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 121..132, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 121..125, id: Name("data"), ctx: Load, @@ -513,10 +583,12 @@ Module( ), slice: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 126..131, op: Invert, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..131, id: Name("flag"), ctx: Load, @@ -531,12 +603,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..148, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 133..148, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..137, id: Name("data"), ctx: Load, @@ -544,13 +619,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 138..147, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 139..145, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("a"), ctx: Store, @@ -558,6 +636,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 144..145, value: Int( 0, @@ -578,12 +657,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 149..165, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 149..165, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 149..153, id: Name("data"), ctx: Load, @@ -591,13 +673,16 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 154..164, lower: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 155..161, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("a"), ctx: Store, @@ -605,6 +690,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..161, value: Int( 0, @@ -617,6 +703,7 @@ Module( upper: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("y"), ctx: Load, @@ -633,12 +720,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 226..234, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 226..234, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 226..230, id: Name("data"), ctx: Load, @@ -646,13 +736,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 231..233, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 231..233, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 232..233, id: Name("x"), ctx: Load, @@ -673,12 +766,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 235..249, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 235..249, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 235..239, id: Name("data"), ctx: Load, @@ -686,18 +782,22 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 240..248, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 240..248, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 241..248, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..242, id: Name("x"), ctx: Load, @@ -705,6 +805,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 247..248, id: Name("y"), ctx: Load, @@ -728,12 +829,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 250..265, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 250..265, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..254, id: Name("data"), ctx: Load, @@ -741,16 +845,20 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 255..264, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 255..264, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 257..263, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 257..258, id: Name("x"), ctx: Store, @@ -758,6 +866,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 262..263, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap index 99a7bcd3b9c1e0..b9d44898d11d62 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__t_string.py.snap @@ -7,19 +7,23 @@ input_file: crates/ruff_python_parser/resources/valid/expressions/t_string.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..1143, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..21, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 18..21, value: TStringValue { inner: Single( TString( TString { range: 18..21, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Double, @@ -36,15 +40,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..25, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 22..25, value: TStringValue { inner: Single( TString( TString { range: 22..25, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Double, @@ -61,15 +68,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 26..29, value: TStringValue { inner: Single( TString( TString { range: 26..29, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Single, @@ -86,15 +96,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..37, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 30..37, value: TStringValue { inner: Single( TString( TString { range: 30..37, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Double, @@ -111,15 +124,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..45, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 38..45, value: TStringValue { inner: Single( TString( TString { range: 38..45, + node_index: AtomicNodeIndex(..), elements: [], flags: TStringFlags { quote_style: Single, @@ -136,26 +152,32 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 47..56, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 47..56, value: TStringValue { inner: Single( TString( TString { range: 47..56, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 49..55, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 50..54, value: StringLiteralValue { inner: Single( StringLiteral { range: 50..54, + node_index: AtomicNodeIndex(..), value: " t", flags: StringLiteralFlags { quote_style: Double, @@ -188,21 +210,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..67, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 57..67, value: TStringValue { inner: Single( TString( TString { range: 57..67, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 59..66, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("foo"), ctx: Load, @@ -229,25 +256,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..75, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 68..75, value: TStringValue { inner: Single( TString( TString { range: 68..75, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 70..74, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 71..73, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 71..72, value: Int( 3, @@ -280,24 +313,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 76..86, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 76..86, value: TStringValue { inner: Single( TString( TString { range: 76..86, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 78..85, + node_index: AtomicNodeIndex(..), expression: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 79..83, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 3, @@ -310,6 +349,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 4, @@ -324,6 +364,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 84..84, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -345,21 +386,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..102, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 87..102, value: TStringValue { inner: Single( TString( TString { range: 87..102, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 89..101, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 90..91, value: Int( 3, @@ -371,17 +417,21 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 92..100, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 92..97, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 93..96, value: StringLiteralValue { inner: Single( StringLiteral { range: 93..96, + node_index: AtomicNodeIndex(..), value: "}", flags: StringLiteralFlags { quote_style: Double, @@ -401,6 +451,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 97..100, + node_index: AtomicNodeIndex(..), value: ">10", }, ), @@ -425,21 +476,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..118, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 103..118, value: TStringValue { inner: Single( TString( TString { range: 103..118, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 105..117, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 106..107, value: Int( 3, @@ -451,17 +507,21 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 108..116, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 108..113, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, value: StringLiteralValue { inner: Single( StringLiteral { range: 109..112, + node_index: AtomicNodeIndex(..), value: "{", flags: StringLiteralFlags { quote_style: Double, @@ -481,6 +541,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 113..116, + node_index: AtomicNodeIndex(..), value: ">10", }, ), @@ -505,21 +566,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 119..133, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 119..133, value: TStringValue { inner: Single( TString( TString { range: 119..133, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 121..132, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..127, id: Name("foo"), ctx: Load, @@ -551,21 +617,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 134..154, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 134..154, value: TStringValue { inner: Single( TString( TString { range: 134..154, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 136..153, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..142, id: Name("foo"), ctx: Load, @@ -581,10 +652,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 147..152, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 147..152, + node_index: AtomicNodeIndex(..), value: ".3f ", }, ), @@ -609,21 +682,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 155..173, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 155..173, value: TStringValue { inner: Single( TString( TString { range: 155..173, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 157..172, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..163, id: Name("foo"), ctx: Load, @@ -655,25 +733,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 174..190, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 174..190, value: TStringValue { inner: Single( TString( TString { range: 174..190, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 176..189, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 179..183, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 179..180, value: Int( 1, @@ -682,6 +766,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 182..183, value: Int( 2, @@ -719,33 +804,41 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 191..217, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 191..217, value: TStringValue { inner: Single( TString( TString { range: 191..217, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 193..216, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 194..210, value: TStringValue { inner: Single( TString( TString { range: 194..210, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 196..209, + node_index: AtomicNodeIndex(..), expression: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 197..203, value: Float( 3.1415, @@ -762,10 +855,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 205..208, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 205..208, + node_index: AtomicNodeIndex(..), value: ".1f", }, ), @@ -791,10 +886,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 211..215, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 211..215, + node_index: AtomicNodeIndex(..), value: "*^20", }, ), @@ -819,15 +916,18 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 219..253, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 219..253, items: [ DictItem { key: Some( TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 220..248, value: TStringValue { inner: Concatenated( @@ -835,6 +935,7 @@ Module( Literal( StringLiteral { range: 220..226, + node_index: AtomicNodeIndex(..), value: "foo ", flags: StringLiteralFlags { quote_style: Double, @@ -846,21 +947,26 @@ Module( TString( TString { range: 227..242, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 229..233, + node_index: AtomicNodeIndex(..), value: "bar ", }, ), Interpolation( InterpolatedElement { range: 233..240, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 234..239, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..235, id: Name("x"), ctx: Load, @@ -869,6 +975,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 238..239, id: Name("y"), ctx: Load, @@ -884,6 +991,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 240..241, + node_index: AtomicNodeIndex(..), value: " ", }, ), @@ -898,6 +1006,7 @@ Module( Literal( StringLiteral { range: 243..248, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -914,6 +1023,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 250..252, value: Int( 10, @@ -928,9 +1038,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 254..345, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..263, id: Name("foo"), ctx: Load, @@ -939,16 +1051,20 @@ Module( cases: [ MatchCase { range: 269..293, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 274..279, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 274..279, value: StringLiteralValue { inner: Single( StringLiteral { range: 274..279, + node_index: AtomicNodeIndex(..), value: "one", flags: StringLiteralFlags { quote_style: Double, @@ -966,6 +1082,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 289..293, }, ), @@ -973,11 +1090,14 @@ Module( }, MatchCase { range: 298..345, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 303..331, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 303..331, value: StringLiteralValue { inner: Concatenated( @@ -985,6 +1105,7 @@ Module( strings: [ StringLiteral { range: 303..316, + node_index: AtomicNodeIndex(..), value: "implicitly ", flags: StringLiteralFlags { quote_style: Double, @@ -994,6 +1115,7 @@ Module( }, StringLiteral { range: 317..331, + node_index: AtomicNodeIndex(..), value: "concatenated", flags: StringLiteralFlags { quote_style: Double, @@ -1014,6 +1136,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 341..345, }, ), @@ -1024,27 +1147,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 347..364, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 347..364, value: TStringValue { inner: Single( TString( TString { range: 347..364, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 349..350, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 350..355, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..354, id: Name("foo"), ctx: Load, @@ -1058,14 +1187,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 355..356, + node_index: AtomicNodeIndex(..), value: "\\", }, ), Interpolation( InterpolatedElement { range: 356..363, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 357..360, id: Name("bar"), ctx: Load, @@ -1076,10 +1208,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 361..362, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 361..362, + node_index: AtomicNodeIndex(..), value: "\\", }, ), @@ -1104,19 +1238,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 365..379, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 365..379, value: TStringValue { inner: Single( TString( TString { range: 365..379, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 367..378, + node_index: AtomicNodeIndex(..), value: "\\{foo\\}", }, ), @@ -1136,21 +1274,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 380..420, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 380..420, value: TStringValue { inner: Single( TString( TString { range: 380..420, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 384..417, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 390..393, id: Name("foo"), ctx: Load, @@ -1161,10 +1304,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 394..416, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 394..416, + node_index: AtomicNodeIndex(..), value: "x\n y\n z\n", }, ), @@ -1189,21 +1334,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 421..439, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 421..439, value: TStringValue { inner: Single( TString( TString { range: 421..439, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 423..438, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 428..431, id: Name("foo"), ctx: Load, @@ -1235,27 +1385,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 441..486, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 441..486, value: TStringValue { inner: Single( TString( TString { range: 441..486, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 443..450, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 450..455, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 451..454, id: Name("foo"), ctx: Load, @@ -1269,14 +1425,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 455..468, + node_index: AtomicNodeIndex(..), value: " {another} ", }, ), Interpolation( InterpolatedElement { range: 468..473, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 469..472, id: Name("bar"), ctx: Load, @@ -1290,14 +1449,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 473..476, + node_index: AtomicNodeIndex(..), value: " {", }, ), Interpolation( InterpolatedElement { range: 476..483, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 477..482, id: Name("three"), ctx: Load, @@ -1311,6 +1473,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 483..485, + node_index: AtomicNodeIndex(..), value: "}", }, ), @@ -1330,27 +1493,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 487..529, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 487..529, value: TStringValue { inner: Single( TString( TString { range: 487..529, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 489..496, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 496..503, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 497..500, id: Name("foo"), ctx: Load, @@ -1364,14 +1533,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 503..504, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 504..511, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 505..508, id: Name("bar"), ctx: Load, @@ -1385,14 +1557,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 511..512, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 512..519, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 513..516, id: Name("baz"), ctx: Load, @@ -1406,14 +1581,17 @@ Module( Literal( InterpolatedStringLiteralElement { range: 519..520, + node_index: AtomicNodeIndex(..), value: " ", }, ), Interpolation( InterpolatedElement { range: 520..528, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 521..527, id: Name("foobar"), ctx: Load, @@ -1440,27 +1618,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 530..549, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 530..549, value: TStringValue { inner: Single( TString( TString { range: 530..549, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 532..539, + node_index: AtomicNodeIndex(..), value: "normal ", }, ), Interpolation( InterpolatedElement { range: 539..548, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 540..541, id: Name("x"), ctx: Load, @@ -1471,10 +1655,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 542..547, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 542..547, + node_index: AtomicNodeIndex(..), value: "y + 2", }, ), @@ -1499,21 +1685,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 550..568, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 550..568, value: TStringValue { inner: Single( TString( TString { range: 550..568, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 552..567, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 553..554, id: Name("x"), ctx: Load, @@ -1524,22 +1715,28 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 555..566, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 555..566, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 556..565, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 556..563, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 556..559, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 557..558, value: Int( 1, @@ -1552,12 +1749,14 @@ Module( attr: Identifier { id: Name("pop"), range: 560..563, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 563..565, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1589,34 +1788,45 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 569..588, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 569..588, value: TStringValue { inner: Single( TString( TString { range: 569..588, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 571..587, + node_index: AtomicNodeIndex(..), expression: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 573..585, parameters: Some( Parameters { range: 580..581, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 580..581, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 580..581, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 580..581, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1630,10 +1840,12 @@ Module( ), body: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 582..585, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 583..584, id: Name("x"), ctx: Load, @@ -1665,21 +1877,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 589..597, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 589..597, value: TStringValue { inner: Single( TString( TString { range: 589..597, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 591..596, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 592..593, id: Name("x"), ctx: Load, @@ -1711,21 +1928,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 598..611, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 598..611, value: TStringValue { inner: Single( TString( TString { range: 598..611, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 600..610, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 605..606, id: Name("x"), ctx: Load, @@ -1757,21 +1979,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 612..621, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 612..621, value: TStringValue { inner: Single( TString( TString { range: 612..621, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 614..620, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 615..616, id: Name("x"), ctx: Load, @@ -1803,21 +2030,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 622..636, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 622..636, value: TStringValue { inner: Single( TString( TString { range: 622..636, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 624..635, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 625..626, id: Name("x"), ctx: Load, @@ -1828,10 +2060,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 627..634, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 627..634, + node_index: AtomicNodeIndex(..), value: ".3f!r =", }, ), @@ -1856,21 +2090,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 637..653, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 637..653, value: TStringValue { inner: Single( TString( TString { range: 637..653, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 639..652, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 640..641, id: Name("x"), ctx: Load, @@ -1886,10 +2125,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 648..651, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 648..651, + node_index: AtomicNodeIndex(..), value: ".3f", }, ), @@ -1914,21 +2155,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 654..667, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 654..667, value: TStringValue { inner: Single( TString( TString { range: 654..667, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 656..666, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 657..658, id: Name("x"), ctx: Load, @@ -1939,10 +2185,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 659..665, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 659..665, + node_index: AtomicNodeIndex(..), value: ".3f=!r", }, ), @@ -1967,9 +2215,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 668..682, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 668..682, value: TStringValue { inner: Concatenated( @@ -1977,6 +2227,7 @@ Module( Literal( StringLiteral { range: 668..675, + node_index: AtomicNodeIndex(..), value: "hello", flags: StringLiteralFlags { quote_style: Double, @@ -1988,12 +2239,15 @@ Module( TString( TString { range: 676..682, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 678..681, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 679..680, id: Name("x"), ctx: Load, @@ -2021,9 +2275,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 683..696, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 683..696, value: TStringValue { inner: Concatenated( @@ -2031,12 +2287,15 @@ Module( TString( TString { range: 683..689, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 685..688, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 686..687, id: Name("x"), ctx: Load, @@ -2058,12 +2317,15 @@ Module( TString( TString { range: 690..696, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 692..695, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 693..694, id: Name("y"), ctx: Load, @@ -2091,9 +2353,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 697..711, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 697..711, value: TStringValue { inner: Concatenated( @@ -2101,12 +2365,15 @@ Module( TString( TString { range: 697..703, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 699..702, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 700..701, id: Name("x"), ctx: Load, @@ -2128,6 +2395,7 @@ Module( Literal( StringLiteral { range: 704..711, + node_index: AtomicNodeIndex(..), value: "world", flags: StringLiteralFlags { quote_style: Double, @@ -2145,31 +2413,38 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 712..756, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 712..756, value: TStringValue { inner: Single( TString( TString { range: 712..756, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 714..739, + node_index: AtomicNodeIndex(..), value: "Invalid args in command: ", }, ), Interpolation( InterpolatedElement { range: 739..755, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 740..754, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 740..747, id: Name("command"), ctx: Load, @@ -2177,9 +2452,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 749..754, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 750..754, id: Name("args"), ctx: Load, @@ -2214,9 +2491,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 757..775, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 757..775, value: TStringValue { inner: Concatenated( @@ -2224,6 +2503,7 @@ Module( Literal( StringLiteral { range: 757..762, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2235,12 +2515,15 @@ Module( TString( TString { range: 763..769, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 765..768, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 766..767, id: Name("x"), ctx: Load, @@ -2262,6 +2545,7 @@ Module( Literal( StringLiteral { range: 770..775, + node_index: AtomicNodeIndex(..), value: "bar", flags: StringLiteralFlags { quote_style: Double, @@ -2279,9 +2563,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 776..825, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 782..823, value: TStringValue { inner: Concatenated( @@ -2289,10 +2575,12 @@ Module( TString( TString { range: 782..786, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 784..785, + node_index: AtomicNodeIndex(..), value: "a", }, ), @@ -2307,10 +2595,12 @@ Module( TString( TString { range: 791..795, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 793..794, + node_index: AtomicNodeIndex(..), value: "b", }, ), @@ -2325,6 +2615,7 @@ Module( Literal( StringLiteral { range: 800..803, + node_index: AtomicNodeIndex(..), value: "c", flags: StringLiteralFlags { quote_style: Double, @@ -2336,10 +2627,12 @@ Module( TString( TString { range: 808..813, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 811..812, + node_index: AtomicNodeIndex(..), value: "d", }, ), @@ -2356,10 +2649,12 @@ Module( FString( FString { range: 818..823, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 821..822, + node_index: AtomicNodeIndex(..), value: "e", }, ), @@ -2382,9 +2677,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 850..879, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 850..879, value: TStringValue { inner: Concatenated( @@ -2392,6 +2689,7 @@ Module( Literal( StringLiteral { range: 850..856, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2403,12 +2701,15 @@ Module( TString( TString { range: 857..865, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 859..864, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 860..863, id: Name("bar"), ctx: Load, @@ -2430,6 +2731,7 @@ Module( Literal( StringLiteral { range: 866..871, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2441,6 +2743,7 @@ Module( Literal( StringLiteral { range: 872..879, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2458,9 +2761,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 880..909, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 880..909, value: TStringValue { inner: Concatenated( @@ -2468,6 +2773,7 @@ Module( Literal( StringLiteral { range: 880..885, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2479,12 +2785,15 @@ Module( TString( TString { range: 886..894, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 888..893, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 889..892, id: Name("bar"), ctx: Load, @@ -2506,6 +2815,7 @@ Module( Literal( StringLiteral { range: 895..901, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2517,6 +2827,7 @@ Module( Literal( StringLiteral { range: 902..909, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2534,9 +2845,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 910..939, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 910..939, value: TStringValue { inner: Concatenated( @@ -2544,6 +2857,7 @@ Module( Literal( StringLiteral { range: 910..915, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2555,12 +2869,15 @@ Module( TString( TString { range: 916..924, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 918..923, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 919..922, id: Name("bar"), ctx: Load, @@ -2582,6 +2899,7 @@ Module( Literal( StringLiteral { range: 925..930, + node_index: AtomicNodeIndex(..), value: "baz", flags: StringLiteralFlags { quote_style: Double, @@ -2593,6 +2911,7 @@ Module( Literal( StringLiteral { range: 931..939, + node_index: AtomicNodeIndex(..), value: " some", flags: StringLiteralFlags { quote_style: Double, @@ -2610,9 +2929,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 940..978, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 940..978, value: TStringValue { inner: Concatenated( @@ -2620,6 +2941,7 @@ Module( Literal( StringLiteral { range: 940..946, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -2631,18 +2953,22 @@ Module( TString( TString { range: 947..966, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 949..953, + node_index: AtomicNodeIndex(..), value: "bar ", }, ), Interpolation( InterpolatedElement { range: 953..958, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 954..957, id: Name("baz"), ctx: Load, @@ -2656,6 +2982,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 958..965, + node_index: AtomicNodeIndex(..), value: " really", }, ), @@ -2670,6 +2997,7 @@ Module( Literal( StringLiteral { range: 967..973, + node_index: AtomicNodeIndex(..), value: "bar", flags: StringLiteralFlags { quote_style: Double, @@ -2681,6 +3009,7 @@ Module( Literal( StringLiteral { range: 974..978, + node_index: AtomicNodeIndex(..), value: "no", flags: StringLiteralFlags { quote_style: Double, @@ -2698,9 +3027,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 998..1017, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 998..1017, value: TStringValue { inner: Concatenated( @@ -2708,12 +3039,15 @@ Module( FString( FString { range: 998..1007, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1000..1006, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1001..1005, id: Name("this"), ctx: Load, @@ -2735,12 +3069,15 @@ Module( TString( TString { range: 1008..1017, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1010..1016, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1011..1015, id: Name("that"), ctx: Load, @@ -2768,9 +3105,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1018..1036, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 1018..1036, value: TStringValue { inner: Concatenated( @@ -2778,12 +3117,15 @@ Module( TString( TString { range: 1018..1027, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1020..1026, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1021..1025, id: Name("this"), ctx: Load, @@ -2805,12 +3147,15 @@ Module( FString( FString { range: 1027..1036, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1029..1035, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1030..1034, id: Name("that"), ctx: Load, @@ -2838,9 +3183,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1037..1064, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 1037..1064, value: TStringValue { inner: Concatenated( @@ -2848,12 +3195,15 @@ Module( TString( TString { range: 1037..1046, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1039..1045, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1040..1044, id: Name("this"), ctx: Load, @@ -2875,6 +3225,7 @@ Module( Literal( StringLiteral { range: 1047..1053, + node_index: AtomicNodeIndex(..), value: "that", flags: StringLiteralFlags { quote_style: Double, @@ -2886,12 +3237,15 @@ Module( FString( FString { range: 1054..1064, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1056..1063, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1057..1062, id: Name("other"), ctx: Load, @@ -2919,9 +3273,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1065..1111, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 1065..1111, value: TStringValue { inner: Concatenated( @@ -2929,18 +3285,22 @@ Module( FString( FString { range: 1065..1082, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 1067..1071, + node_index: AtomicNodeIndex(..), value: "one ", }, ), Interpolation( InterpolatedElement { range: 1071..1077, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1072..1076, id: Name("this"), ctx: Load, @@ -2954,6 +3314,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 1077..1081, + node_index: AtomicNodeIndex(..), value: " two", }, ), @@ -2968,6 +3329,7 @@ Module( Literal( StringLiteral { range: 1083..1089, + node_index: AtomicNodeIndex(..), value: "that", flags: StringLiteralFlags { quote_style: Double, @@ -2979,18 +3341,22 @@ Module( TString( TString { range: 1090..1111, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 1092..1098, + node_index: AtomicNodeIndex(..), value: "three ", }, ), Interpolation( InterpolatedElement { range: 1098..1105, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1099..1104, id: Name("other"), ctx: Load, @@ -3004,6 +3370,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 1105..1110, + node_index: AtomicNodeIndex(..), value: " four", }, ), @@ -3024,45 +3391,56 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1123..1142, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 1123..1142, value: TStringValue { inner: Single( TString( TString { range: 1123..1142, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1125..1141, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 1126..1140, value: FStringValue { inner: Single( FString( FString { range: 1126..1140, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1128..1139, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 1129..1138, value: TStringValue { inner: Single( TString( TString { range: 1129..1138, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1131..1137, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1132..1136, id: Name("this"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__tuple.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__tuple.py.snap index e8a88a1b9da76c..4b019c87550a9a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__tuple.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__tuple.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/tuple.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..276, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..21, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 19..21, elts: [], ctx: Load, @@ -25,9 +27,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 22..26, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 23..25, elts: [], ctx: Load, @@ -38,13 +42,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 27..37, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 27..37, elts: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 29..31, elts: [], ctx: Load, @@ -53,6 +60,7 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 34..36, elts: [], ctx: Load, @@ -68,13 +76,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..42, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 38..42, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("a"), ctx: Load, @@ -89,13 +100,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..49, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 43..49, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("a"), ctx: Load, @@ -103,6 +117,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("b"), ctx: Load, @@ -117,13 +132,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..57, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..57, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("a"), ctx: Load, @@ -131,6 +149,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("b"), ctx: Load, @@ -145,13 +164,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..66, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 59..65, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..61, id: Name("a"), ctx: Load, @@ -159,6 +181,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("b"), ctx: Load, @@ -173,13 +196,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 90..92, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 90..92, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("a"), ctx: Load, @@ -194,13 +220,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..97, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 93..97, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("a"), ctx: Load, @@ -208,6 +237,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 96..97, id: Name("b"), ctx: Load, @@ -222,13 +252,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 98..103, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 98..103, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("a"), ctx: Load, @@ -236,6 +269,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..102, id: Name("b"), ctx: Load, @@ -250,16 +284,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 126..129, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 126..129, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 126..128, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("a"), ctx: Load, @@ -277,13 +315,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 130..135, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 130..135, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 130..131, id: Name("a"), ctx: Load, @@ -291,9 +332,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 133..135, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..135, id: Name("b"), ctx: Load, @@ -311,19 +354,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..161, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 136..161, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 136..142, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 137..142, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("a"), ctx: Load, @@ -332,6 +380,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 141..142, id: Name("b"), ctx: Load, @@ -344,12 +393,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 144..152, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 145..152, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("x"), ctx: Load, @@ -362,6 +414,7 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 154..156, elts: [], ctx: Load, @@ -370,9 +423,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 158..161, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 159..161, elts: [], ctx: Load, @@ -391,16 +446,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 162..167, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 162..167, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 163..165, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 164..165, id: Name("a"), ctx: Load, @@ -418,13 +477,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..175, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 168..175, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 169..170, id: Name("a"), ctx: Load, @@ -432,9 +494,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 172..174, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 173..174, id: Name("b"), ctx: Load, @@ -452,19 +516,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 176..203, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 176..203, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 177..183, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 178..183, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("a"), ctx: Load, @@ -473,6 +542,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 182..183, id: Name("b"), ctx: Load, @@ -485,12 +555,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 185..193, value: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 186..193, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..193, id: Name("x"), ctx: Load, @@ -503,6 +576,7 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 195..197, elts: [], ctx: Load, @@ -511,9 +585,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 199..202, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 200..202, elts: [], ctx: Load, @@ -532,16 +608,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 224..233, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 224..233, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 225..231, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("x"), ctx: Store, @@ -549,6 +629,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 230..231, value: Int( 1, @@ -566,13 +647,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 234..245, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 234..245, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 235..236, id: Name("x"), ctx: Load, @@ -580,9 +664,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 238..244, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 238..239, id: Name("y"), ctx: Store, @@ -590,6 +676,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 243..244, value: Int( 2, @@ -607,13 +694,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 246..260, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 246..260, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 247..248, id: Name("x"), ctx: Load, @@ -621,9 +711,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 250..256, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..251, id: Name("y"), ctx: Store, @@ -631,6 +723,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 255..256, value: Int( 2, @@ -641,6 +734,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..259, id: Name("z"), ctx: Load, @@ -655,13 +749,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 261..275, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 261..275, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 261..262, id: Name("x"), ctx: Load, @@ -669,9 +766,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 265..271, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("y"), ctx: Store, @@ -679,6 +778,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 270..271, value: Int( 2, @@ -689,6 +789,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 274..275, id: Name("z"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__unary_op.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__unary_op.py.snap index 6e164b817fcf3b..cf09c3dcf5ee15 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__unary_op.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__unary_op.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/unary_op.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..276, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..11, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 9..11, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 1, @@ -31,13 +34,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..14, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 12..14, op: UAdd, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 13..14, value: Int( 1, @@ -50,13 +56,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..17, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 15..17, op: Invert, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 1, @@ -69,13 +78,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 18..23, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 18..23, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -87,21 +99,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 36..40, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 36..40, op: USub, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 37..40, op: USub, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 38..40, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 39..40, value: Int( 1, @@ -118,21 +135,26 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 41..45, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 41..45, op: USub, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 42..45, op: UAdd, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 43..45, op: Invert, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..45, value: Int( 1, @@ -149,25 +171,31 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 46..53, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 46..53, op: Not, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 49..53, op: USub, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 50..53, op: UAdd, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 51..53, op: Invert, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 1, @@ -186,17 +214,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..63, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 54..63, op: Not, operand: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 58..63, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..63, id: Name("x"), ctx: Load, @@ -210,16 +242,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..93, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 84..93, op: USub, operand: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 86..93, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 92..93, value: Int( 1, @@ -234,19 +270,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 94..109, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 94..109, op: UAdd, operand: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 96..109, left: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 96..103, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 102..103, value: Int( 1, @@ -258,10 +299,12 @@ Module( op: Pow, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 107..109, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 108..109, value: Int( 2, @@ -278,17 +321,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 110..117, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 110..117, op: Invert, operand: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 111..117, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 112..113, value: Int( 1, @@ -297,6 +344,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 2, @@ -314,16 +362,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..124, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 118..124, left: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 118..120, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 119..120, value: Int( 1, @@ -335,6 +387,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 123..124, value: Int( 2, @@ -347,23 +400,28 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 212..246, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 212..246, op: Or, values: [ BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 212..223, op: And, values: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 212..217, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 216..217, id: Name("a"), ctx: Load, @@ -373,6 +431,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..223, id: Name("b"), ctx: Load, @@ -383,18 +442,22 @@ Module( ), BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 227..246, op: And, values: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 227..236, op: Not, operand: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 231..236, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 231..232, id: Name("c"), ctx: Load, @@ -403,6 +466,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 235..236, id: Name("d"), ctx: Load, @@ -414,10 +478,12 @@ Module( ), UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 241..246, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("e"), ctx: Load, @@ -435,16 +501,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 247..259, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 247..259, op: Not, operand: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 252..258, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 252..253, id: Name("x"), ctx: Store, @@ -452,6 +522,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 257..258, value: Int( 1, @@ -466,16 +537,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 260..275, value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 260..275, op: Not, operand: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 264..275, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 264..265, id: Name("a"), ctx: Load, @@ -484,10 +559,12 @@ Module( op: BitOr, right: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 269..274, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 273..274, id: Name("b"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap index 063373e6a3da35..cafb60b46058d5 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/valid/expressions/yield.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..166, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 0..5, value: None, }, @@ -22,13 +25,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..13, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 6..13, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("x"), ctx: Load, @@ -41,16 +47,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..25, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 14..25, value: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 20..25, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("x"), ctx: Load, @@ -59,6 +69,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 24..25, value: Int( 1, @@ -74,18 +85,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..39, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 26..39, value: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 32..39, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("x"), ctx: Load, @@ -93,6 +108,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("y"), ctx: Load, @@ -108,16 +124,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..52, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 40..52, value: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 46..52, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..50, id: Name("call"), ctx: Load, @@ -125,6 +145,7 @@ Module( ), arguments: Arguments { range: 50..52, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -137,17 +158,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..65, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 53..65, value: Some( List( ExprList { + node_index: AtomicNodeIndex(..), range: 59..65, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 60..61, value: Int( 1, @@ -156,6 +181,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 63..64, value: Int( 2, @@ -173,17 +199,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 66..78, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 66..78, value: Some( Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 72..78, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 73..74, value: Int( 3, @@ -192,6 +222,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 4, @@ -208,19 +239,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 79..91, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 79..91, value: Some( Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 85..91, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("x"), ctx: Load, @@ -229,6 +264,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 89..90, value: Int( 5, @@ -246,17 +282,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..102, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 92..102, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 98..102, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("x"), ctx: Load, @@ -264,6 +304,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..102, id: Name("y"), ctx: Load, @@ -281,17 +322,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..115, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 103..115, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 109..115, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 110..111, id: Name("x"), ctx: Load, @@ -299,6 +344,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("y"), ctx: Load, @@ -316,16 +362,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 116..128, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 116..128, value: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 122..128, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("x"), ctx: Load, @@ -337,6 +387,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 127..128, id: Name("y"), ctx: Load, @@ -352,16 +403,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 129..143, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 129..143, value: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 136..142, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("x"), ctx: Store, @@ -369,6 +424,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 141..142, value: Int( 1, @@ -384,17 +440,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 144..155, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 144..155, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 150..155, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("x"), ctx: Load, @@ -402,9 +462,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 153..155, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 154..155, id: Name("y"), ctx: Load, @@ -425,20 +487,25 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 156..165, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 156..165, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 162..165, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 162..164, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap index d35161d5fce1b0..0d7bfe36e3b237 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/expressions/yield_from.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..199, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 0..12, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("x"), ctx: Load, @@ -29,15 +32,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..29, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 13..29, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 24..29, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -46,6 +53,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 1, @@ -60,17 +68,21 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..48, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 30..48, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 41..48, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Load, @@ -78,6 +90,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("y"), ctx: Load, @@ -92,15 +105,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..66, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 49..66, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 60..66, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..64, id: Name("call"), ctx: Load, @@ -108,6 +125,7 @@ Module( ), arguments: Arguments { range: 64..66, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -119,16 +137,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..84, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 67..84, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 78..84, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 1, @@ -137,6 +159,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 2, @@ -153,16 +176,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..102, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 85..102, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 96..102, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 97..98, value: Int( 3, @@ -171,6 +198,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..101, value: Int( 4, @@ -186,18 +214,22 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..120, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 103..120, value: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 114..120, items: [ DictItem { key: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..116, id: Name("x"), ctx: Load, @@ -206,6 +238,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 118..119, value: Int( 5, @@ -222,16 +255,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 121..138, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 121..138, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 132..138, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 133..134, id: Name("x"), ctx: Load, @@ -239,6 +276,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..137, id: Name("y"), ctx: Load, @@ -255,15 +293,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 139..156, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 139..156, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 150..156, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("x"), ctx: Load, @@ -275,6 +317,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 155..156, id: Name("y"), ctx: Load, @@ -289,15 +332,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 157..176, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 157..176, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 169..175, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 169..170, id: Name("x"), ctx: Store, @@ -305,6 +352,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 174..175, value: Int( 1, @@ -319,16 +367,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 177..199, value: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 177..199, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 188..199, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("x"), ctx: Load, @@ -336,12 +388,15 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 192..198, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 193..198, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..194, id: Name("x"), ctx: Load, @@ -350,6 +405,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_in_target_valid_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_in_target_valid_expr.py.snap index 82261413967ea8..8d3f2a874385a9 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_in_target_valid_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_in_target_valid_expr.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/for_in_target_valid_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..89, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 4..13, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("d"), ctx: Load, @@ -26,9 +29,11 @@ Module( ), slice: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 6..12, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -40,6 +45,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("y"), ctx: Load, @@ -53,6 +59,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..23, id: Name("target"), ctx: Load, @@ -61,9 +68,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -75,16 +84,20 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 29..57, is_async: false, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 33..44, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 34..40, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("x"), ctx: Load, @@ -96,6 +109,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Load, @@ -106,6 +120,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 42..43, value: Int( 0, @@ -117,6 +132,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..52, id: Name("iter"), ctx: Load, @@ -125,9 +141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..57, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 54..57, }, ), @@ -139,16 +157,20 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 58..88, is_async: false, target: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 62..75, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 63..69, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..64, id: Name("x"), ctx: Load, @@ -160,6 +182,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("y"), ctx: Load, @@ -171,12 +194,14 @@ Module( attr: Identifier { id: Name("attr"), range: 71..75, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..83, id: Name("iter"), ctx: Load, @@ -185,9 +210,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..88, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 85..88, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py38.py.snap index 892b22f86a3a1f..e981ecdd5d6764 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py38.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/for_iter_unpack_py38.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..112, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 43..65, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -22,13 +25,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 52..60, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 53..55, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("a"), ctx: Load, @@ -39,6 +45,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("b"), ctx: Load, @@ -52,9 +59,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 62..65, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 62..65, }, ), @@ -66,10 +75,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 66..88, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Store, @@ -77,10 +88,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 75..83, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("a"), ctx: Load, @@ -88,9 +101,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 80..82, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("b"), ctx: Load, @@ -107,9 +122,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 85..88, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 85..88, }, ), @@ -121,10 +138,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 89..111, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("x"), ctx: Store, @@ -132,13 +151,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 98..106, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 99..101, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("a"), ctx: Load, @@ -149,9 +171,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 103..105, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("b"), ctx: Load, @@ -168,9 +192,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 108..111, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 108..111, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py39.py.snap index ac67500fd781a3..2579c5e35f3a1b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@for_iter_unpack_py39.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/for_iter_unpack_py39.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..106, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 43..63, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -22,13 +25,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 52..58, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 52..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("a"), ctx: Load, @@ -39,6 +45,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("b"), ctx: Load, @@ -52,9 +59,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..63, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 60..63, }, ), @@ -66,10 +75,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 64..84, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Store, @@ -77,10 +88,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 74..79, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("a"), ctx: Load, @@ -88,9 +101,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 77..79, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("b"), ctx: Load, @@ -107,9 +122,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), @@ -121,10 +138,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 85..105, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("x"), ctx: Store, @@ -132,13 +151,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 94..100, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 94..96, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..96, id: Name("a"), ctx: Load, @@ -149,9 +171,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 98..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("b"), ctx: Load, @@ -168,9 +192,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 102..105, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 102..105, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_no_space.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_no_space.py.snap index 52b37be22a3f14..4e27abaf5828ba 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_no_space.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_no_space.py.snap @@ -1,25 +1,28 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/from_import_no_space.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..30, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..13, module: None, names: [ Alias { range: 12..13, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 12..13, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -29,14 +32,17 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 14..29, module: None, names: [ Alias { range: 28..29, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 28..29, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_soft_keyword_module_name.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_soft_keyword_module_name.py.snap index 9b5dde8170a24d..ffbf2fa38ddc23 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_soft_keyword_module_name.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_soft_keyword_module_name.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/from_import_soft_keyword_module_name.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..104, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..25, module: Some( Identifier { id: Name("match"), range: 5..10, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 18..25, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("pattern"), range: 18..25, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -34,19 +38,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 26..46, module: Some( Identifier { id: Name("type"), range: 31..35, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 43..46, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("bar"), range: 43..46, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -56,19 +64,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 47..71, module: Some( Identifier { id: Name("case"), range: 52..56, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 64..71, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("pattern"), range: 64..71, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -78,19 +90,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 72..103, module: Some( Identifier { id: Name("match.type.case"), range: 77..92, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 100..103, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("foo"), range: 100..103, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_stmt_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_stmt_terminator.py.snap index ec6c1d92dc39ab..afc649c201f3a2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_stmt_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@from_import_stmt_terminator.py.snap @@ -1,38 +1,44 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/from_import_stmt_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..97, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..20, module: Some( Identifier { id: Name("a"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 15..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 15..16, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 18..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 18..19, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -42,27 +48,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 21..41, module: Some( Identifier { id: Name("a"), range: 26..27, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 36..37, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 36..37, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 39..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 39..40, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -72,13 +84,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..47, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 43..47, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("x"), ctx: Load, @@ -86,6 +101,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("y"), ctx: Load, @@ -100,27 +116,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 48..66, module: Some( Identifier { id: Name("a"), range: 53..54, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 62..63, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 62..63, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 65..66, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 65..66, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -130,13 +152,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..72, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 68..72, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -144,6 +169,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("y"), ctx: Load, @@ -158,27 +184,33 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 73..91, module: Some( Identifier { id: Name("a"), range: 78..79, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 87..88, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 87..88, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 90..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 90..91, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -188,13 +220,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 92..96, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 92..96, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("x"), ctx: Load, @@ -202,6 +237,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..96, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap index 34167d13a7014e..386e5a0f15d950 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@fstring_format_spec_terminator.py.snap @@ -7,31 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/ok/fstring_format_spec_te ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..43, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..19, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 0..19, value: FStringValue { inner: Single( FString( FString { range: 0..19, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 2..8, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 8..12, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..10, id: Name("x"), ctx: Load, @@ -42,6 +49,7 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 11..11, + node_index: AtomicNodeIndex(..), elements: [], }, ), @@ -50,6 +58,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 12..18, + node_index: AtomicNodeIndex(..), value: " world", }, ), @@ -69,27 +78,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..42, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 20..42, value: FStringValue { inner: Single( FString( FString { range: 20..42, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 22..28, + node_index: AtomicNodeIndex(..), value: "hello ", }, ), Interpolation( InterpolatedElement { range: 28..35, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -100,10 +115,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 31..34, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 31..34, + node_index: AtomicNodeIndex(..), value: ".3f", }, ), @@ -115,6 +132,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 35..41, + node_index: AtomicNodeIndex(..), value: " world", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parameter_range.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parameter_range.py.snap index 17d571227aed75..68a01e322fcbd7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parameter_range.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parameter_range.py.snap @@ -1,40 +1,49 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/function_def_parameter_range.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..56, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..55, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..43, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 13..23, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 13..23, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("first"), range: 13..18, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..23, id: Name("int"), ctx: Load, @@ -46,15 +55,19 @@ Module( }, ParameterWithDefault { range: 29..40, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 29..40, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("second"), range: 29..35, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..40, id: Name("int"), ctx: Load, @@ -72,6 +85,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..50, id: Name("int"), ctx: Load, @@ -81,9 +95,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 52..55, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 52..55, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parenthesized_return_types.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parenthesized_return_types.py.snap index 7076142333c7db..2008c48ad0c35e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parenthesized_return_types.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_parenthesized_return_types.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/function_def_parenthesized_return_types.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..54, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..24, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,10 +36,12 @@ Module( returns: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 13..19, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -49,9 +56,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..24, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, }, ), @@ -62,16 +71,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 25..53, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 29..32, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 32..34, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -81,10 +95,12 @@ Module( returns: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 38..48, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..42, id: Name("int"), ctx: Load, @@ -92,6 +108,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..47, id: Name("str"), ctx: Load, @@ -106,9 +123,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..53, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 50..53, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_valid_return_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_valid_return_expr.py.snap index f4bd699f74239c..f3762e02fdce53 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_valid_return_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_def_valid_return_expr.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/function_def_valid_ret ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..97, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..27, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..9, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -30,9 +36,11 @@ Module( returns: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 13..22, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..16, id: Name("int"), ctx: Load, @@ -41,6 +49,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..22, id: Name("str"), ctx: Load, @@ -52,9 +61,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 24..27, }, ), @@ -65,16 +76,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 28..57, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 32..35, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 35..37, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -84,19 +100,26 @@ Module( returns: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 41..52, parameters: Some( Parameters { range: 48..49, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 48..49, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 48..49, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 48..49, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -110,6 +133,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..52, id: Name("x"), ctx: Load, @@ -121,9 +145,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..57, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 54..57, }, ), @@ -134,16 +160,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 58..96, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 62..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -153,15 +184,18 @@ Module( returns: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 71..91, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 78..82, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..74, id: Name("int"), ctx: Load, @@ -169,6 +203,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..91, id: Name("str"), ctx: Load, @@ -180,9 +215,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..96, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 93..96, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap index ed6c0528a6835b..c4bcad78137cc1 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap @@ -7,27 +7,33 @@ input_file: crates/ruff_python_parser/resources/inline/ok/function_type_params_p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..62, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..61, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 51..54, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 52..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 52..53, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -38,6 +44,9 @@ Module( ), parameters: Parameters { range: 54..56, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -48,9 +57,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@global_stmt.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@global_stmt.py.snap index 24c5d44be2c4b5..e5a51faf375a4f 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@global_stmt.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@global_stmt.py.snap @@ -1,41 +1,47 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/global_stmt.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 0..8, names: [ Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, ], }, ), Global( StmtGlobal { + node_index: AtomicNodeIndex(..), range: 9..23, names: [ Identifier { id: Name("x"), range: 16..17, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("y"), range: 19..20, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("z"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_as_name_soft_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_as_name_soft_keyword.py.snap index f5e2f2da77c85e..15b856b9bc98c3 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_as_name_soft_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_as_name_soft_keyword.py.snap @@ -1,29 +1,33 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/import_as_name_soft_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..58, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..19, names: [ Alias { range: 7..19, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("foo"), range: 7..10, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("match"), range: 14..19, + node_index: AtomicNodeIndex(..), }, ), }, @@ -32,18 +36,22 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 20..38, names: [ Alias { range: 27..38, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("bar"), range: 27..30, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("case"), range: 34..38, + node_index: AtomicNodeIndex(..), }, ), }, @@ -52,18 +60,22 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 39..57, names: [ Alias { range: 46..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("baz"), range: 46..49, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("type"), range: 53..57, + node_index: AtomicNodeIndex(..), }, ), }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_stmt_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_stmt_terminator.py.snap index 0d1775d267d0d0..507563d546b40a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_stmt_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@import_stmt_terminator.py.snap @@ -1,32 +1,37 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/import_stmt_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..42, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..11, names: [ Alias { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 7..8, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 10..11, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 10..11, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -35,21 +40,26 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 13..24, names: [ Alias { range: 20..21, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 20..21, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 23..24, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 23..24, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -58,21 +68,26 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 25..36, names: [ Alias { range: 32..33, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 32..33, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 35..36, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 35..36, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -81,13 +96,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 37..41, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 37..41, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("c"), ctx: Load, @@ -95,6 +113,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("d"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@irrefutable_case_pattern_at_end.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@irrefutable_case_pattern_at_end.py.snap index 0d818e98ea7945..4dc33017099675 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@irrefutable_case_pattern_at_end.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@irrefutable_case_pattern_at_end.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/irrefutable_case_patte ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..176, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..42, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -22,11 +25,14 @@ Module( cases: [ MatchCase { range: 13..24, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 18..19, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 2, @@ -39,9 +45,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 21..24, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 21..24, }, ), @@ -51,14 +59,17 @@ Module( }, MatchCase { range: 29..42, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 34..37, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("var"), range: 34..37, + node_index: AtomicNodeIndex(..), }, ), }, @@ -67,9 +78,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..42, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 39..42, }, ), @@ -82,9 +95,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 43..83, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("x"), ctx: Load, @@ -93,11 +108,14 @@ Module( cases: [ MatchCase { range: 56..67, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 61..62, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 2, @@ -110,9 +128,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..67, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 64..67, }, ), @@ -122,9 +142,11 @@ Module( }, MatchCase { range: 72..83, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 77..78, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -133,9 +155,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 80..83, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 80..83, }, ), @@ -148,9 +172,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 84..175, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("x"), ctx: Load, @@ -159,14 +185,17 @@ Module( cases: [ MatchCase { range: 97..118, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 102..105, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("var"), range: 102..105, + node_index: AtomicNodeIndex(..), }, ), }, @@ -174,6 +203,7 @@ Module( guard: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 109..113, value: true, }, @@ -182,9 +212,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 115..118, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 115..118, }, ), @@ -194,11 +226,14 @@ Module( }, MatchCase { range: 164..175, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 169..170, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 169..170, value: Int( 2, @@ -211,9 +246,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 172..175, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 172..175, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py37.py.snap index a925e03d2ee820..470474aa7fb6f3 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py37.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/iter_unpack_return_py3 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..93, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..92, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,14 +90,17 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 69..92, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 76..92, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 77..78, value: Int( 1, @@ -94,6 +109,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 80..81, value: Int( 2, @@ -102,6 +118,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 3, @@ -110,9 +127,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 86..91, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..91, id: Name("rest"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py38.py.snap index d1fbfcc158f7b1..e0f7c8ec2297b8 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_return_py38.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/iter_unpack_return_py3 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..91, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..90, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,14 +90,17 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 69..90, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 76..90, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 1, @@ -94,6 +109,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 2, @@ -102,6 +118,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 3, @@ -110,9 +127,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 85..90, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..90, id: Name("rest"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py37.py.snap index e27bc0edc092b5..092f50c3127003 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py37.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/iter_unpack_yield_py37 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..91, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,17 +90,21 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..91, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 69..91, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 75..91, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 76..77, value: Int( 1, @@ -97,6 +113,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 79..80, value: Int( 2, @@ -105,6 +122,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 3, @@ -113,9 +131,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 85..90, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..90, id: Name("rest"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py38.py.snap index 4aa3293a645dbf..78df6541052c5e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@iter_unpack_yield_py38.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/inline/ok/iter_unpack_yield_py38 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..128, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 43..59, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..47, id: Name("rest"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 50..59, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 4, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 5, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 6, @@ -58,16 +65,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 60..89, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 64..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -78,17 +90,21 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 69..89, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 69..89, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 75..89, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 1, @@ -97,6 +113,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 2, @@ -105,6 +122,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 3, @@ -113,9 +131,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 84..89, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..89, id: Name("rest"), ctx: Load, @@ -139,16 +159,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 90..127, is_async: false, decorator_list: [], name: Identifier { id: Name("h"), range: 94..95, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 95..97, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -159,17 +184,21 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 99..127, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 99..127, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 105..127, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 1, @@ -178,14 +207,17 @@ Module( ), Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 109..123, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 115..123, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 115..116, value: Int( 2, @@ -194,9 +226,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 118..123, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..123, id: Name("rest"), ctx: Load, @@ -215,6 +249,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 126..127, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_no_parameters.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_no_parameters.py.snap index 6ecfea43d1d611..7a76f5f14cc874 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_no_parameters.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_no_parameters.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/lambda_with_no_parameters.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..10, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..9, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..9, parameters: None, body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_valid_body.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_valid_body.py.snap index 61366ad154bb13..c407279f9daeaf 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_valid_body.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@lambda_with_valid_body.py.snap @@ -1,33 +1,41 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/lambda_with_valid_body.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..152, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..11, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 0..11, parameters: Some( Parameters { range: 7..8, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 7..8, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 7..8, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -41,6 +49,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..11, id: Name("x"), ctx: Load, @@ -52,22 +61,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 12..38, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 12..38, parameters: Some( Parameters { range: 19..20, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 19..20, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 19..20, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 19..20, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -81,15 +98,18 @@ Module( ), body: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 22..38, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 27..31, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..23, id: Name("x"), ctx: Load, @@ -97,6 +117,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("y"), ctx: Load, @@ -110,22 +131,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..56, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 39..56, parameters: Some( Parameters { range: 46..47, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 46..47, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 46..47, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 46..47, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -139,9 +168,11 @@ Module( ), body: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 49..56, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("x"), ctx: Load, @@ -155,22 +186,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..82, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 57..82, parameters: Some( Parameters { range: 64..65, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 64..65, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 64..65, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 64..65, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -184,19 +223,26 @@ Module( ), body: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 67..82, parameters: Some( Parameters { range: 74..75, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 74..75, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 74..75, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 74..75, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -210,9 +256,11 @@ Module( ), body: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 77..82, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("x"), ctx: Load, @@ -221,6 +269,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("y"), ctx: Load, @@ -236,22 +285,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..102, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 83..102, parameters: Some( Parameters { range: 90..91, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 90..91, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 90..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 90..91, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -265,10 +322,12 @@ Module( ), body: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 94..101, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Load, @@ -283,26 +342,35 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 136..151, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 136..151, elts: [ Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 136..147, parameters: Some( Parameters { range: 143..144, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 143..144, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 143..144, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 143..144, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -316,6 +384,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Load, @@ -325,9 +394,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 149..151, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_after_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_after_py310.py.snap index 7c12a5fce74880..a4b120445780c3 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_after_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_after_py310.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/match_after_py310.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..80, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 46..79, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 2, @@ -23,11 +26,14 @@ Module( cases: [ MatchCase { range: 59..79, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 64..65, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 64..65, value: Int( 1, @@ -40,6 +46,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 75..79, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern.py.snap index 8f967d7704c246..c300ac48439f64 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/match_as_pattern.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..60, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..32, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -22,14 +25,17 @@ Module( cases: [ MatchCase { range: 15..32, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 20..27, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("foo_bar"), range: 20..27, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 29..32, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 29..32, }, ), @@ -53,9 +61,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 33..59, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..42, id: Name("foo"), ctx: Load, @@ -64,9 +74,11 @@ Module( cases: [ MatchCase { range: 48..59, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 53..54, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -75,9 +87,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..59, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 56..59, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern_soft_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern_soft_keyword.py.snap index fd3c2f1ed1a4e4..6ca3b670574fd5 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern_soft_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_as_pattern_soft_keyword.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/match_as_pattern_soft_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..91, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..29, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -22,14 +25,17 @@ Module( cases: [ MatchCase { range: 15..29, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 20..24, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("case"), range: 20..24, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +44,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 26..29, }, ), @@ -53,9 +61,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 30..60, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..39, id: Name("foo"), ctx: Load, @@ -64,14 +74,17 @@ Module( cases: [ MatchCase { range: 45..60, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 50..55, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("match"), range: 50..55, + node_index: AtomicNodeIndex(..), }, ), }, @@ -80,9 +93,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), @@ -95,9 +110,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 61..90, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..70, id: Name("foo"), ctx: Load, @@ -106,14 +123,17 @@ Module( cases: [ MatchCase { range: 76..90, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 81..85, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("type"), range: 81..85, + node_index: AtomicNodeIndex(..), }, ), }, @@ -122,9 +142,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 87..90, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 87..90, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_attr_pattern_soft_keyword.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_attr_pattern_soft_keyword.py.snap index 833fa6d042cd1d..17a7f854b3a2f2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_attr_pattern_soft_keyword.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_attr_pattern_soft_keyword.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_attr_pattern_soft_keyword.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..131, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..130, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -23,14 +25,18 @@ Module( cases: [ MatchCase { range: 15..34, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 20..29, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 20..29, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..25, id: Name("match"), ctx: Load, @@ -39,6 +45,7 @@ Module( attr: Identifier { id: Name("bar"), range: 26..29, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -49,9 +56,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..34, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 31..34, }, ), @@ -61,14 +70,18 @@ Module( }, MatchCase { range: 39..57, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 44..52, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 44..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..48, id: Name("case"), ctx: Load, @@ -77,6 +90,7 @@ Module( attr: Identifier { id: Name("bar"), range: 49..52, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -87,9 +101,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 54..57, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 54..57, }, ), @@ -99,14 +115,18 @@ Module( }, MatchCase { range: 62..80, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 67..75, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 67..75, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..71, id: Name("type"), ctx: Load, @@ -115,6 +135,7 @@ Module( attr: Identifier { id: Name("bar"), range: 72..75, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -125,9 +146,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 77..80, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 77..80, }, ), @@ -137,29 +160,38 @@ Module( }, MatchCase { range: 85..130, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 90..125, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..125, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..119, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..114, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..109, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..105, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 90..100, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..95, id: Name("match"), ctx: Load, @@ -168,6 +200,7 @@ Module( attr: Identifier { id: Name("case"), range: 96..100, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -175,6 +208,7 @@ Module( attr: Identifier { id: Name("type"), range: 101..105, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -182,6 +216,7 @@ Module( attr: Identifier { id: Name("bar"), range: 106..109, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -189,6 +224,7 @@ Module( attr: Identifier { id: Name("type"), range: 110..114, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -196,6 +232,7 @@ Module( attr: Identifier { id: Name("case"), range: 115..119, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -203,6 +240,7 @@ Module( attr: Identifier { id: Name("match"), range: 120..125, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -213,9 +251,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 127..130, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 127..130, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_1.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_1.py.snap index 5e454f2909e294..7c7131c6e54c2a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_1.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_identifier_1.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..18, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..17, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 0..17, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("match"), ctx: Load, @@ -29,6 +32,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..17, id: Name("case"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_2.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_2.py.snap index f80bb85b577dfa..e0c274a03d11e8 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_identifier_2.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_identifier_2.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..149, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..5, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("match"), ctx: Load, @@ -24,12 +26,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..18, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 6..18, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..11, id: Name("match"), ctx: Load, @@ -41,6 +46,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 15..18, id: Name("foo"), ctx: Load, @@ -53,13 +59,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..31, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 19..31, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..23, id: Name("foo"), ctx: Load, @@ -67,6 +76,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..30, id: Name("match"), ctx: Load, @@ -81,13 +91,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..44, value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 32..44, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 33..36, id: Name("foo"), ctx: Load, @@ -95,6 +108,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..43, id: Name("match"), ctx: Load, @@ -108,13 +122,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 45..57, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 45..57, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..49, id: Name("foo"), ctx: Load, @@ -122,6 +139,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..56, id: Name("match"), ctx: Load, @@ -134,9 +152,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..63, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..63, id: Name("match"), ctx: Load, @@ -146,9 +166,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 65..75, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..70, id: Name("match"), ctx: Store, @@ -156,6 +178,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..75, id: Name("int"), ctx: Load, @@ -167,13 +190,16 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 76..82, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 76..82, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..81, id: Name("match"), ctx: Load, @@ -188,12 +214,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..92, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 83..92, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..88, id: Name("match"), ctx: Load, @@ -202,6 +231,7 @@ Module( attr: Identifier { id: Name("foo"), range: 89..92, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -210,12 +240,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 93..104, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 93..104, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..98, id: Name("match"), ctx: Load, @@ -224,6 +257,7 @@ Module( op: Div, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 101..104, id: Name("foo"), ctx: Load, @@ -235,12 +269,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 105..117, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 105..117, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..110, id: Name("match"), ctx: Load, @@ -249,6 +286,7 @@ Module( op: LShift, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..117, id: Name("foo"), ctx: Load, @@ -260,14 +298,17 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 118..131, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 118..131, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 118..123, id: Name("match"), ctx: Load, @@ -275,6 +316,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..131, id: Name("foo"), ctx: Load, @@ -287,12 +329,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 132..148, value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 132..148, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..137, id: Name("match"), ctx: Load, @@ -304,6 +349,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 145..148, id: Name("foo"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap index 16fc4e36e36706..2345dca24948a3 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_1.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_keyw ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..358, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..26, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..9, id: Name("foo"), ctx: Load, @@ -22,9 +25,11 @@ Module( cases: [ MatchCase { range: 15..26, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 20..21, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -33,9 +38,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 23..26, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 23..26, }, ), @@ -48,9 +55,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 27..51, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 33..34, value: Int( 1, @@ -60,9 +69,11 @@ Module( cases: [ MatchCase { range: 40..51, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 45..46, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -71,9 +82,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 48..51, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 48..51, }, ), @@ -86,9 +99,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 52..78, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, value: Float( 1.0, @@ -98,9 +113,11 @@ Module( cases: [ MatchCase { range: 67..78, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 72..73, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -109,9 +126,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..78, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 75..78, }, ), @@ -124,9 +143,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 79..104, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 85..87, value: Complex { real: 0.0, @@ -137,9 +158,11 @@ Module( cases: [ MatchCase { range: 93..104, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 98..99, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -148,9 +171,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 101..104, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 101..104, }, ), @@ -163,14 +188,17 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 105..133, subject: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 111..116, value: StringLiteralValue { inner: Single( StringLiteral { range: 111..116, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -185,9 +213,11 @@ Module( cases: [ MatchCase { range: 122..133, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 127..128, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -196,9 +226,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 130..133, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 130..133, }, ), @@ -211,27 +243,33 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 134..167, subject: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 140..150, value: FStringValue { inner: Single( FString( FString { range: 140..150, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 142..146, + node_index: AtomicNodeIndex(..), value: "foo ", }, ), Interpolation( InterpolatedElement { range: 146..149, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 147..148, id: Name("x"), ctx: Load, @@ -257,9 +295,11 @@ Module( cases: [ MatchCase { range: 156..167, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 161..162, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -268,9 +308,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 164..167, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 164..167, }, ), @@ -283,13 +325,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 168..197, subject: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 174..180, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 175..176, value: Int( 1, @@ -298,6 +343,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 178..179, value: Int( 2, @@ -310,9 +356,11 @@ Module( cases: [ MatchCase { range: 186..197, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 191..192, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -321,9 +369,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 194..197, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 194..197, }, ), @@ -336,13 +386,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 198..225, subject: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 204..208, op: Invert, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 205..208, id: Name("foo"), ctx: Load, @@ -353,9 +406,11 @@ Module( cases: [ MatchCase { range: 214..225, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 219..220, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -364,9 +419,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 222..225, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 222..225, }, ), @@ -379,18 +436,22 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 226..252, subject: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 232..235, }, ), cases: [ MatchCase { range: 241..252, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 246..247, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -399,9 +460,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 249..252, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 249..252, }, ), @@ -414,13 +477,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 253..283, subject: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 259..266, op: Not, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 263..266, id: Name("foo"), ctx: Load, @@ -431,9 +497,11 @@ Module( cases: [ MatchCase { range: 272..283, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 277..278, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -442,9 +510,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 280..283, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 280..283, }, ), @@ -457,15 +527,19 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 284..318, subject: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 290..301, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 296..301, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 296..299, id: Name("foo"), ctx: Load, @@ -473,6 +547,7 @@ Module( ), arguments: Arguments { range: 299..301, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -483,9 +558,11 @@ Module( cases: [ MatchCase { range: 307..318, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 312..313, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -494,9 +571,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 315..318, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 315..318, }, ), @@ -509,22 +588,30 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 319..357, subject: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 325..340, parameters: Some( Parameters { range: 332..335, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 332..335, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 332..335, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("foo"), range: 332..335, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -538,6 +625,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 337..340, id: Name("foo"), ctx: Load, @@ -548,9 +636,11 @@ Module( cases: [ MatchCase { range: 346..357, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 351..352, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -559,9 +649,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 354..357, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 354..357, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_2.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_2.py.snap index d82c8dc0e1c857..d4d62a6c6f3869 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_2.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_keyword_2.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..170, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..28, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..11, id: Name("match"), ctx: Load, @@ -23,9 +25,11 @@ Module( cases: [ MatchCase { range: 17..28, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 22..23, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -34,9 +38,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -49,9 +55,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 29..56, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..39, id: Name("case"), ctx: Load, @@ -60,9 +68,11 @@ Module( cases: [ MatchCase { range: 45..56, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 50..51, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -71,9 +81,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..56, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 53..56, }, ), @@ -86,9 +98,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 57..84, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..67, id: Name("type"), ctx: Load, @@ -97,9 +111,11 @@ Module( cases: [ MatchCase { range: 73..84, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 78..79, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -108,9 +124,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), @@ -123,18 +141,22 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 85..112, subject: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 91..95, }, ), cases: [ MatchCase { range: 101..112, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 106..107, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -143,9 +165,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..112, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, }, ), @@ -158,9 +182,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 113..140, subject: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 119..123, value: true, }, @@ -168,9 +194,11 @@ Module( cases: [ MatchCase { range: 129..140, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 134..135, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -179,9 +207,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 137..140, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 137..140, }, ), @@ -194,9 +224,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 141..169, subject: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 147..152, value: false, }, @@ -204,9 +236,11 @@ Module( cases: [ MatchCase { range: 158..169, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 163..164, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -215,9 +249,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 166..169, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 166..169, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_or_identifier.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_or_identifier.py.snap index 1c15bbba25b50d..849251e03e8117 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_or_identifier.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_classify_as_keyword_or_identifier.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_classify_as_keyword_or_identifier.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..225, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..12, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 0..12, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..5, id: Name("match"), ctx: Load, @@ -25,9 +28,11 @@ Module( ), arguments: Arguments { range: 6..12, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, @@ -36,6 +41,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..11, value: Int( 2, @@ -51,13 +57,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 27..67, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 33..39, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 34..35, value: Int( 1, @@ -66,6 +75,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 37..38, value: Int( 2, @@ -80,9 +90,11 @@ Module( cases: [ MatchCase { range: 56..67, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 61..62, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -91,9 +103,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 64..67, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 64..67, }, ), @@ -106,12 +120,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..78, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 68..78, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..73, id: Name("match"), ctx: Load, @@ -119,10 +136,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 75..77, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 75..76, value: Int( 1, @@ -141,13 +160,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 93..133, subject: List( ExprList { + node_index: AtomicNodeIndex(..), range: 99..105, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 100..101, value: Int( 1, @@ -156,6 +178,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 103..104, value: Int( 2, @@ -169,9 +192,11 @@ Module( cases: [ MatchCase { range: 122..133, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 127..128, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -180,9 +205,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 130..133, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 130..133, }, ), @@ -195,12 +222,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 134..145, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 134..145, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..139, id: Name("match"), ctx: Load, @@ -209,6 +239,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..145, id: Name("foo"), ctx: Load, @@ -220,12 +251,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 160..171, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 160..171, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..165, id: Name("match"), ctx: Load, @@ -234,6 +268,7 @@ Module( op: Sub, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 168..171, id: Name("foo"), ctx: Load, @@ -245,13 +280,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 186..224, subject: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 192..196, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 193..196, id: Name("foo"), ctx: Load, @@ -262,9 +300,11 @@ Module( cases: [ MatchCase { range: 213..224, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 218..219, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -273,9 +313,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 221..224, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 221..224, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_parentheses_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_parentheses_terminator.py.snap index 965e821ef377e8..7416b29f05f23f 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_parentheses_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_parentheses_terminator.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_sequence_pattern_parentheses_terminator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..56, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -23,18 +25,22 @@ Module( cases: [ MatchCase { range: 19..35, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 24..30, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 25..26, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 25..26, + node_index: AtomicNodeIndex(..), }, ), }, @@ -42,11 +48,13 @@ Module( MatchAs( PatternMatchAs { range: 28..29, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 28..29, + node_index: AtomicNodeIndex(..), }, ), }, @@ -58,9 +66,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..35, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 32..35, }, ), @@ -70,18 +80,22 @@ Module( }, MatchCase { range: 40..56, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 45..51, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 46..47, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 46..47, + node_index: AtomicNodeIndex(..), }, ), }, @@ -89,11 +103,13 @@ Module( MatchAs( PatternMatchAs { range: 49..50, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 49..50, + node_index: AtomicNodeIndex(..), }, ), }, @@ -105,9 +121,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 53..56, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 53..56, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_terminator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_terminator.py.snap index df1137b501fabb..91f128eafcd65c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_terminator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_sequence_pattern_terminator.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/match_sequence_pattern ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..95, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..94, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..13, id: Name("subject"), ctx: Load, @@ -22,14 +25,17 @@ Module( cases: [ MatchCase { range: 19..35, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 24..25, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 24..25, + node_index: AtomicNodeIndex(..), }, ), }, @@ -37,6 +43,7 @@ Module( guard: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Load, @@ -46,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 32..35, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 32..35, }, ), @@ -58,18 +67,22 @@ Module( }, MatchCase { range: 40..54, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 45..49, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 45..46, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 45..46, + node_index: AtomicNodeIndex(..), }, ), }, @@ -77,11 +90,13 @@ Module( MatchAs( PatternMatchAs { range: 48..49, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 48..49, + node_index: AtomicNodeIndex(..), }, ), }, @@ -93,9 +108,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 51..54, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 51..54, }, ), @@ -105,18 +122,22 @@ Module( }, MatchCase { range: 59..78, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 64..68, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 64..65, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 64..65, + node_index: AtomicNodeIndex(..), }, ), }, @@ -124,11 +145,13 @@ Module( MatchAs( PatternMatchAs { range: 67..68, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 67..68, + node_index: AtomicNodeIndex(..), }, ), }, @@ -139,6 +162,7 @@ Module( guard: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("x"), ctx: Load, @@ -148,9 +172,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 75..78, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 75..78, }, ), @@ -160,14 +186,17 @@ Module( }, MatchCase { range: 83..94, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 88..89, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 88..89, + node_index: AtomicNodeIndex(..), }, ), }, @@ -176,9 +205,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..94, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 91..94, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_subject_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_subject_expr.py.snap index fd8bd905dc4bad..e0042a2cc5a752 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_subject_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_subject_expr.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_stmt_subject_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..185, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..29, subject: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 6..12, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Store, @@ -25,6 +28,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 1, @@ -36,9 +40,11 @@ Module( cases: [ MatchCase { range: 18..29, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 23..24, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -47,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..29, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 26..29, }, ), @@ -62,12 +70,15 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 30..61, subject: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 37..43, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("x"), ctx: Store, @@ -75,6 +86,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 42..43, value: Int( 1, @@ -86,9 +98,11 @@ Module( cases: [ MatchCase { range: 50..61, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 55..56, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -97,9 +111,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -112,19 +128,24 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 121..153, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 127..136, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 127..133, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 128..133, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("x"), ctx: Load, @@ -133,6 +154,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("y"), ctx: Load, @@ -145,6 +167,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("z"), ctx: Load, @@ -158,9 +181,11 @@ Module( cases: [ MatchCase { range: 142..153, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 147..148, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -169,9 +194,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 150..153, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 150..153, }, ), @@ -184,12 +211,15 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 154..184, subject: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 160..167, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("x"), ctx: Load, @@ -200,9 +230,11 @@ Module( cases: [ MatchCase { range: 173..184, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 178..179, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -211,9 +243,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 181..184, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 181..184, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_valid_guard_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_valid_guard_expr.py.snap index 17ac4a2fa65ffe..32f6791cd44fc1 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_valid_guard_expr.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@match_stmt_valid_guard_expr.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/match_stmt_valid_guard_expr.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..158, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..34, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -23,14 +25,17 @@ Module( cases: [ MatchCase { range: 13..34, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 18..19, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 18..19, + node_index: AtomicNodeIndex(..), }, ), }, @@ -38,9 +43,11 @@ Module( guard: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 23..29, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("a"), ctx: Store, @@ -48,6 +55,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 1, @@ -60,9 +68,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 31..34, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 31..34, }, ), @@ -75,9 +85,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 35..79, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..42, id: Name("x"), ctx: Load, @@ -86,14 +98,17 @@ Module( cases: [ MatchCase { range: 48..79, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 53..54, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 53..54, + node_index: AtomicNodeIndex(..), }, ), }, @@ -101,15 +116,18 @@ Module( guard: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 58..74, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 63..67, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 58..59, id: Name("a"), ctx: Load, @@ -117,6 +135,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("b"), ctx: Load, @@ -128,9 +147,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 76..79, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 76..79, }, ), @@ -143,9 +164,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 80..119, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("x"), ctx: Load, @@ -154,14 +177,17 @@ Module( cases: [ MatchCase { range: 93..119, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 98..99, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 98..99, + node_index: AtomicNodeIndex(..), }, ), }, @@ -169,19 +195,26 @@ Module( guard: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 103..114, parameters: Some( Parameters { range: 110..111, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 110..111, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 110..111, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 110..111, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -195,6 +228,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("b"), ctx: Load, @@ -206,9 +240,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 116..119, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 116..119, }, ), @@ -221,9 +257,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 120..157, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("x"), ctx: Load, @@ -232,14 +270,17 @@ Module( cases: [ MatchCase { range: 133..157, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 138..139, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 138..139, + node_index: AtomicNodeIndex(..), }, ), }, @@ -247,10 +288,12 @@ Module( guard: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 144..151, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 150..151, id: Name("x"), ctx: Load, @@ -263,9 +306,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 154..157, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 154..157, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@multiple_assignment_in_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@multiple_assignment_in_case_pattern.py.snap index a12c28b913544c..b61390df0d8bb8 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@multiple_assignment_in_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@multiple_assignment_in_case_pattern.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/multiple_assignment_in_case_pattern.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..42, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 0..41, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 2, @@ -24,15 +26,19 @@ Module( cases: [ MatchCase { range: 13..41, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 18..36, + node_index: AtomicNodeIndex(..), patterns: [ MatchClass( PatternMatchClass { range: 18..26, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..23, id: Name("Class"), ctx: Load, @@ -40,15 +46,18 @@ Module( ), arguments: PatternArguments { range: 23..26, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 24..25, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 24..25, + node_index: AtomicNodeIndex(..), }, ), }, @@ -61,15 +70,18 @@ Module( MatchSequence( PatternMatchSequence { range: 29..32, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 30..31, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 30..31, + node_index: AtomicNodeIndex(..), }, ), }, @@ -80,11 +92,13 @@ Module( MatchAs( PatternMatchAs { range: 35..36, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 35..36, + node_index: AtomicNodeIndex(..), }, ), }, @@ -96,9 +110,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 38..41, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 38..41, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py310.py.snap index 2fb0f626fe0a04..b36ef0858ecad3 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py310.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/nested_async_comprehen ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..181, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..116, is_async: true, decorator_list: [], name: Identifier { id: Name("f"), range: 54..55, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 55..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,12 +37,15 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..84, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 63..84, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..65, id: Name("_"), ctx: Load, @@ -45,8 +54,10 @@ Module( generators: [ Comprehension { range: 66..83, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("n"), ctx: Store, @@ -54,9 +65,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 75..83, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..80, id: Name("range"), ctx: Load, @@ -64,9 +77,11 @@ Module( ), arguments: Arguments { range: 80..83, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 81..82, value: Int( 3, @@ -88,12 +103,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 89..116, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 89..116, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("_"), ctx: Load, @@ -102,8 +120,10 @@ Module( generators: [ Comprehension { range: 92..115, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("n"), ctx: Store, @@ -111,9 +131,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 107..115, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..112, id: Name("range"), ctx: Load, @@ -121,9 +143,11 @@ Module( ), arguments: Arguments { range: 112..115, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 113..114, value: Int( 3, @@ -148,16 +172,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 117..180, is_async: true, decorator_list: [], name: Identifier { id: Name("f"), range: 127..128, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 128..130, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -168,16 +197,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 136..148, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 140..141, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 141..143, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -188,9 +222,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 145..148, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 145..148, }, ), @@ -201,12 +237,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 153..180, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 153..180, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 154..155, id: Name("_"), ctx: Load, @@ -215,8 +254,10 @@ Module( generators: [ Comprehension { range: 156..179, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("n"), ctx: Store, @@ -224,9 +265,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 171..179, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 171..176, id: Name("range"), ctx: Load, @@ -234,9 +277,11 @@ Module( ), arguments: Arguments { range: 176..179, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 177..178, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py311.py.snap index b0fc9d849b97bb..2a0a766dabd894 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nested_async_comprehension_py311.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/nested_async_comprehen ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..277, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..111, is_async: true, decorator_list: [], name: Identifier { id: Name("f"), range: 54..55, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 55..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,16 +37,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 59..111, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 66..111, elt: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 67..92, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("x"), ctx: Load, @@ -49,8 +59,10 @@ Module( generators: [ Comprehension { range: 70..91, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..81, id: Name("x"), ctx: Store, @@ -58,9 +70,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 85..91, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..88, id: Name("foo"), ctx: Load, @@ -68,9 +82,11 @@ Module( ), arguments: Arguments { range: 88..91, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 89..90, id: Name("n"), ctx: Load, @@ -90,8 +106,10 @@ Module( generators: [ Comprehension { range: 93..110, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("n"), ctx: Store, @@ -99,9 +117,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 102..110, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..107, id: Name("range"), ctx: Load, @@ -109,9 +129,11 @@ Module( ), arguments: Arguments { range: 107..110, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 108..109, value: Int( 3, @@ -137,16 +159,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 122..192, is_async: true, decorator_list: [], name: Identifier { id: Name("g"), range: 132..133, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 133..135, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -157,16 +184,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 137..192, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 144..192, elt: DictComp( ExprDictComp { + node_index: AtomicNodeIndex(..), range: 145..173, key: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Load, @@ -174,6 +205,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 1, @@ -183,8 +215,10 @@ Module( generators: [ Comprehension { range: 151..172, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("x"), ctx: Store, @@ -192,9 +226,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 166..172, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..169, id: Name("foo"), ctx: Load, @@ -202,9 +238,11 @@ Module( ), arguments: Arguments { range: 169..172, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("n"), ctx: Load, @@ -224,8 +262,10 @@ Module( generators: [ Comprehension { range: 174..191, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 178..179, id: Name("n"), ctx: Store, @@ -233,9 +273,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 183..191, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 183..188, id: Name("range"), ctx: Load, @@ -243,9 +285,11 @@ Module( ), arguments: Arguments { range: 188..191, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 189..190, value: Int( 3, @@ -271,16 +315,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 200..267, is_async: true, decorator_list: [], name: Identifier { id: Name("h"), range: 210..211, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 211..213, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -291,16 +340,20 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 215..267, value: Some( ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 222..267, elt: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 223..248, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 224..225, id: Name("x"), ctx: Load, @@ -309,8 +362,10 @@ Module( generators: [ Comprehension { range: 226..247, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("x"), ctx: Store, @@ -318,9 +373,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 241..247, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 241..244, id: Name("foo"), ctx: Load, @@ -328,9 +385,11 @@ Module( ), arguments: Arguments { range: 244..247, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 245..246, id: Name("n"), ctx: Load, @@ -350,8 +409,10 @@ Module( generators: [ Comprehension { range: 249..266, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 253..254, id: Name("n"), ctx: Store, @@ -359,9 +420,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 258..266, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 258..263, id: Name("range"), ctx: Load, @@ -369,9 +432,11 @@ Module( ), arguments: Arguments { range: 263..266, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 264..265, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_duplicate_type_parameter_names.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_duplicate_type_parameter_names.py.snap index 766ff4bc236cd8..76ed4f593790a1 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_duplicate_type_parameter_names.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_duplicate_type_parameter_names.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/non_duplicate_type_par ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..150, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..23, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..10, id: Name("Alias"), ctx: Store, @@ -22,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 10..13, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 11..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 11..12, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -39,9 +45,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 16..23, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..20, id: Name("list"), ctx: Load, @@ -49,6 +57,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("T"), ctx: Load, @@ -61,23 +70,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 24..43, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 28..29, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 29..32, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 30..31, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 30..31, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -88,19 +102,26 @@ Module( ), parameters: Parameters { range: 32..38, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 33..37, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 33..37, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("t"), range: 33..34, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("T"), ctx: Load, @@ -119,9 +140,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 40..43, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 40..43, }, ), @@ -132,22 +155,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 44..59, decorator_list: [], name: Identifier { id: Name("C"), range: 50..51, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 51..54, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 52..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 52..53, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -160,9 +188,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 56..59, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 56..59, }, ), @@ -173,22 +203,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 60..81, decorator_list: [], name: Identifier { id: Name("C"), range: 66..67, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 67..76, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 68..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 68..69, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -197,9 +232,11 @@ Module( TypeVar( TypeParamTypeVar { range: 71..72, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 71..72, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -208,9 +245,11 @@ Module( TypeVar( TypeParamTypeVar { range: 74..75, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("V"), range: 74..75, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -223,9 +262,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 78..81, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 78..81, }, ), @@ -236,9 +277,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 82..149, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 87..92, id: Name("Alias"), ctx: Store, @@ -247,13 +290,16 @@ Module( type_params: Some( TypeParams { range: 92..143, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 93..94, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 93..94, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -262,13 +308,16 @@ Module( TypeVar( TypeParamTypeVar { range: 96..102, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 96..97, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..102, id: Name("str"), ctx: Load, @@ -281,17 +330,21 @@ Module( TypeVar( TypeParamTypeVar { range: 104..119, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("V"), range: 104..105, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 107..119, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..111, id: Name("str"), ctx: Load, @@ -299,6 +352,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..118, id: Name("bytes"), ctx: Load, @@ -316,9 +370,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 121..124, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 122..124, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -326,9 +382,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 126..129, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 128..129, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -336,14 +394,17 @@ Module( TypeVar( TypeParamTypeVar { range: 131..142, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("D"), range: 131..132, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..142, id: Name("default"), ctx: Load, @@ -357,6 +418,7 @@ Module( ), value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 146..149, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_rebound_comprehension_variable.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_rebound_comprehension_variable.py.snap index 8c2d4b83431f4e..3e80ba428d9531 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_rebound_comprehension_variable.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@non_rebound_comprehension_variable.py.snap @@ -7,19 +7,24 @@ input_file: crates/ruff_python_parser/resources/inline/ok/non_rebound_comprehens ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..27, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..26, value: ListComp( ExprListComp { + node_index: AtomicNodeIndex(..), range: 0..26, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1..7, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..2, id: Name("a"), ctx: Store, @@ -27,6 +32,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..7, value: Int( 0, @@ -38,8 +44,10 @@ Module( generators: [ Comprehension { range: 8..25, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..13, id: Name("x"), ctx: Store, @@ -47,9 +55,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 17..25, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..22, id: Name("range"), ctx: Load, @@ -57,9 +67,11 @@ Module( ), arguments: Arguments { range: 22..25, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 0, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_declaration_at_module_level.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_declaration_at_module_level.py.snap index b1646e4b6cc09f..c3a1f13b039e3c 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_declaration_at_module_level.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_declaration_at_module_level.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/nonlocal_declaration_a ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..24, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..23, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,11 +37,13 @@ Module( body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 13..23, names: [ Identifier { id: Name("x"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_stmt.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_stmt.py.snap index f7b7d0a1c8cbf7..6ff8f3a6850ba0 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_stmt.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@nonlocal_stmt.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/nonlocal_stmt.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..45, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..44, is_async: false, decorator_list: [], name: Identifier { id: Name("_"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,30 +37,36 @@ Module( body: [ Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 13..23, names: [ Identifier { id: Name("x"), range: 22..23, + node_index: AtomicNodeIndex(..), }, ], }, ), Nonlocal( StmtNonlocal { + node_index: AtomicNodeIndex(..), range: 28..44, names: [ Identifier { id: Name("x"), range: 37..38, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("y"), range: 40..41, + node_index: AtomicNodeIndex(..), }, Identifier { id: Name("z"), range: 43..44, + node_index: AtomicNodeIndex(..), }, ], }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__atom.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__atom.py.snap index 1fa2f56ed587a7..c990da6f6ec14a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__atom.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__atom.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/other/atom.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..73, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 0..3, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 0..3, }, ), @@ -22,9 +24,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4..8, value: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 4..8, value: true, }, @@ -33,9 +37,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..14, value: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 9..14, value: false, }, @@ -44,9 +50,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 15..19, value: NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 15..19, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__decorator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__decorator.py.snap index fd32829f6c6afe..4278651bd25314 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__decorator.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@other__decorator.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/other/decorator.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..407, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..40, is_async: false, decorator_list: [ Decorator { range: 0..19, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1..19, id: Name("function_decorator"), ctx: Load, @@ -29,10 +32,14 @@ Module( name: Identifier { id: Name("test"), range: 24..28, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 28..30, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -43,6 +50,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 36..40, }, ), @@ -51,12 +59,15 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 43..80, decorator_list: [ Decorator { range: 43..59, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..59, id: Name("class_decorator"), ctx: Load, @@ -67,12 +78,14 @@ Module( name: Identifier { id: Name("Test"), range: 66..70, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 76..80, }, ), @@ -81,13 +94,16 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 83..106, is_async: false, decorator_list: [ Decorator { range: 83..93, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..93, id: Name("decorator"), ctx: Load, @@ -98,10 +114,14 @@ Module( name: Identifier { id: Name("f"), range: 98..99, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 99..101, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -112,9 +132,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..106, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 103..106, }, ), @@ -125,19 +147,24 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 109..128, is_async: false, decorator_list: [ Decorator { range: 109..115, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 110..115, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 110..113, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 110..111, id: Name("a"), ctx: Load, @@ -146,6 +173,7 @@ Module( attr: Identifier { id: Name("b"), range: 112..113, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -153,6 +181,7 @@ Module( attr: Identifier { id: Name("c"), range: 114..115, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -162,10 +191,14 @@ Module( name: Identifier { id: Name("f"), range: 120..121, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 121..123, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -176,9 +209,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 125..128, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 125..128, }, ), @@ -189,13 +224,16 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 131..153, is_async: false, decorator_list: [ Decorator { range: 131..133, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("a"), ctx: Load, @@ -204,14 +242,18 @@ Module( }, Decorator { range: 134..140, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 135..140, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 135..138, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 135..136, id: Name("a"), ctx: Load, @@ -220,6 +262,7 @@ Module( attr: Identifier { id: Name("b"), range: 137..138, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -227,6 +270,7 @@ Module( attr: Identifier { id: Name("c"), range: 139..140, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -236,10 +280,14 @@ Module( name: Identifier { id: Name("f"), range: 145..146, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 146..148, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -250,9 +298,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 150..153, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 150..153, }, ), @@ -263,12 +313,15 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 156..185, decorator_list: [ Decorator { range: 156..158, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..158, id: Name("a"), ctx: Load, @@ -277,11 +330,14 @@ Module( }, Decorator { range: 159..165, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 160..165, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 160..161, value: Int( 1, @@ -291,6 +347,7 @@ Module( op: BitOr, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 2, @@ -302,14 +359,18 @@ Module( }, Decorator { range: 166..172, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 167..172, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 167..170, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 167..168, id: Name("a"), ctx: Load, @@ -318,6 +379,7 @@ Module( attr: Identifier { id: Name("b"), range: 169..170, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -325,6 +387,7 @@ Module( attr: Identifier { id: Name("c"), range: 171..172, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -334,15 +397,18 @@ Module( name: Identifier { id: Name("T"), range: 179..180, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 182..185, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 182..185, }, ), @@ -353,16 +419,20 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 188..269, is_async: false, decorator_list: [ Decorator { range: 188..195, + node_index: AtomicNodeIndex(..), expression: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 189..195, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("x"), ctx: Store, @@ -370,6 +440,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 194..195, value: Int( 1, @@ -381,17 +452,21 @@ Module( }, Decorator { range: 196..213, + node_index: AtomicNodeIndex(..), expression: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 197..213, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 202..206, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("x"), ctx: Load, @@ -399,6 +474,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 212..213, id: Name("y"), ctx: Load, @@ -409,21 +485,29 @@ Module( }, Decorator { range: 214..226, + node_index: AtomicNodeIndex(..), expression: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 215..226, parameters: Some( Parameters { range: 222..223, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 222..223, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 222..223, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 222..223, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -437,6 +521,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 225..226, id: Name("x"), ctx: Load, @@ -447,13 +532,16 @@ Module( }, Decorator { range: 227..235, + node_index: AtomicNodeIndex(..), expression: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 228..235, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 228..229, id: Name("x"), ctx: Load, @@ -461,6 +549,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 234..235, id: Name("y"), ctx: Load, @@ -472,12 +561,15 @@ Module( }, Decorator { range: 236..246, + node_index: AtomicNodeIndex(..), expression: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 238..245, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 244..245, id: Name("x"), ctx: Load, @@ -489,15 +581,19 @@ Module( }, Decorator { range: 247..256, + node_index: AtomicNodeIndex(..), expression: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 248..256, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 249..251, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 250..251, id: Name("x"), ctx: Load, @@ -508,9 +604,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 253..255, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 254..255, id: Name("y"), ctx: Load, @@ -529,10 +627,14 @@ Module( name: Identifier { id: Name("f"), range: 261..262, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 262..264, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -543,9 +645,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 266..269, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 266..269, }, ), @@ -556,16 +660,20 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 360..380, is_async: false, decorator_list: [ Decorator { range: 360..365, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 361..365, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 361..362, id: Name("x"), ctx: Load, @@ -574,6 +682,7 @@ Module( op: MatMult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 364..365, id: Name("y"), ctx: Load, @@ -586,10 +695,14 @@ Module( name: Identifier { id: Name("foo"), range: 370..373, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 373..375, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -600,9 +713,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 377..380, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 377..380, }, ), @@ -613,13 +728,16 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 383..407, is_async: false, decorator_list: [ Decorator { range: 383..385, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 384..385, id: Name("x"), ctx: Load, @@ -628,8 +746,10 @@ Module( }, Decorator { range: 388..390, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 389..390, id: Name("y"), ctx: Load, @@ -640,10 +760,14 @@ Module( name: Identifier { id: Name("foo"), range: 397..400, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 400..402, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -654,9 +778,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 404..407, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 404..407, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap index 004ec6c603a774..af944be2fba0cc 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_annotation.py.snap @@ -7,33 +7,43 @@ input_file: crates/ruff_python_parser/resources/inline/ok/param_with_annotation. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..54, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..22, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..17, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..16, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 8..11, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..16, id: Name("int"), ctx: Load, @@ -52,9 +62,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 19..22, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 19..22, }, ), @@ -65,42 +77,57 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 23..53, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 27..30, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 30..48, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 31..47, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 31..47, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 31..34, + node_index: AtomicNodeIndex(..), }, annotation: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 36..47, parameters: Some( Parameters { range: 43..44, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 43..44, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 43..44, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 43..44, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -114,6 +141,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("x"), ctx: Load, @@ -134,9 +162,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 50..53, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 50..53, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_default.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_default.py.snap index 62383e628332cf..7951972b14ba86 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_default.py.snap @@ -1,55 +1,70 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/param_with_default.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..111, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..27, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..22, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..21, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 10..21, parameters: Some( Parameters { range: 17..18, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 17..18, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 17..18, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -63,6 +78,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("y"), ctx: Load, @@ -81,9 +97,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 24..27, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 24..27, }, ), @@ -94,40 +112,51 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 28..60, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 32..35, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 35..55, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 36..54, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 36..37, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 36..37, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 38..54, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 43..47, value: true, }, ), body: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 38..39, value: Int( 1, @@ -136,6 +165,7 @@ Module( ), orelse: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 2, @@ -155,9 +185,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), @@ -168,34 +200,44 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 61..84, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 65..68, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 68..79, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 69..78, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 69..70, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 69..70, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 71..78, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("y"), ctx: Load, @@ -214,9 +256,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), @@ -227,35 +271,45 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 85..110, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 89..92, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 92..105, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 93..104, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 93..94, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 93..94, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 96..103, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 102..103, id: Name("y"), ctx: Load, @@ -275,9 +329,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..110, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 107..110, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation.py.snap index bcf3af426dfa22..a9987458b010fc 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation.py.snap @@ -1,45 +1,55 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/param_with_star_annotation.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..67, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..31, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..26, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 8..25, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 9..13, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 15..25, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 16..25, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..19, id: Name("int"), ctx: Load, @@ -48,6 +58,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..25, id: Name("str"), ctx: Load, @@ -68,9 +79,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 28..31, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 28..31, }, ), @@ -81,36 +94,46 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 32..66, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 36..39, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 39..61, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 40..60, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 41..45, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 47..60, value: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 49..59, op: Or, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..52, id: Name("int"), ctx: Load, @@ -118,6 +141,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..59, id: Name("str"), ctx: Load, @@ -139,9 +163,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 63..66, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 63..66, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py310.py.snap index ad415e16579794..8e1d5b24be1bac 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py310.py.snap @@ -7,31 +7,38 @@ input_file: crates/ruff_python_parser/resources/inline/ok/param_with_star_annota ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..432, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 169..206, module: Some( Identifier { id: Name("typing"), range: 174..180, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 188..197, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Annotated"), range: 188..197, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 199..206, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Literal"), range: 199..206, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -41,28 +48,36 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 207..230, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 211..214, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 214..225, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 215..224, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 216..220, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..224, id: Name("Ts"), ctx: Load, @@ -78,9 +93,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 227..230, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 227..230, }, ), @@ -91,31 +108,40 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 231..295, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 235..238, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 238..290, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 239..289, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 240..241, + node_index: AtomicNodeIndex(..), }, annotation: Some( Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 243..289, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 243..250, id: Name("Literal"), ctx: Load, @@ -123,11 +149,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 251..288, value: StringLiteralValue { inner: Single( StringLiteral { range: 251..288, + node_index: AtomicNodeIndex(..), value: "this should allow arbitrary strings", flags: StringLiteralFlags { quote_style: Double, @@ -152,9 +180,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 292..295, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 292..295, }, ), @@ -165,31 +195,40 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 296..367, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 300..303, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 303..362, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 304..361, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 305..306, + node_index: AtomicNodeIndex(..), }, annotation: Some( Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 308..361, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 308..317, id: Name("Annotated"), ctx: Load, @@ -197,10 +236,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 318..360, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 318..321, id: Name("str"), ctx: Load, @@ -208,11 +249,13 @@ Module( ), StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 323..360, value: StringLiteralValue { inner: Single( StringLiteral { range: 323..360, + node_index: AtomicNodeIndex(..), value: "this should allow arbitrary strings", flags: StringLiteralFlags { quote_style: Double, @@ -242,9 +285,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 364..367, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 364..367, }, ), @@ -255,28 +300,36 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 368..405, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 372..375, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 375..400, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 376..386, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 377..381, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 383..386, id: Name("str"), ctx: Load, @@ -289,13 +342,16 @@ Module( kwarg: Some( Parameter { range: 388..399, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwds"), range: 390..394, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 396..399, id: Name("int"), ctx: Load, @@ -309,9 +365,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 402..405, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 402..405, }, ), @@ -322,31 +380,40 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 406..431, is_async: false, decorator_list: [], name: Identifier { id: Name("union"), range: 410..415, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 415..426, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 416..425, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 417..418, + node_index: AtomicNodeIndex(..), }, annotation: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 420..425, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 420..421, id: Name("A"), ctx: Load, @@ -355,6 +422,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 424..425, id: Name("B"), ctx: Load, @@ -372,9 +440,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 428..431, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 428..431, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap index 7156b308c55d21..0f1eed04f9f26e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap @@ -7,35 +7,45 @@ input_file: crates/ruff_python_parser/resources/inline/ok/param_with_star_annota ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..69, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..68, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 48..51, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 51..63, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 52..62, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 53..57, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 59..62, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..62, id: Name("Ts"), ctx: Load, @@ -54,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..68, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 65..68, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_non_default_after_star.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_non_default_after_star.py.snap index 4c6a5faaeb1023..2a4bdc6a75a1b2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_non_default_after_star.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_non_default_after_star.py.snap @@ -1,42 +1,51 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/params_non_default_after_star.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..72, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..33, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..28, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 8..12, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 8..9, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 8..9, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 10..12, value: Int( 10, @@ -50,11 +59,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 17..18, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 17..18, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 17..18, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -62,17 +74,21 @@ Module( }, ParameterWithDefault { range: 20..24, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 20..21, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 20..21, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 22..24, value: Int( 11, @@ -83,11 +99,14 @@ Module( }, ParameterWithDefault { range: 26..27, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 26..27, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 26..27, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -100,9 +119,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 30..33, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 30..33, }, ), @@ -113,31 +134,40 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 34..71, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 38..41, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 41..66, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 42..46, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 42..43, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 42..43, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 44..46, value: Int( 10, @@ -150,9 +180,11 @@ Module( vararg: Some( Parameter { range: 48..53, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 49..53, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -160,11 +192,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 55..56, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 55..56, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 55..56, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -172,17 +207,21 @@ Module( }, ParameterWithDefault { range: 58..62, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 58..59, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 58..59, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 60..62, value: Int( 11, @@ -193,11 +232,14 @@ Module( }, ParameterWithDefault { range: 64..65, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 64..65, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 64..65, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -210,9 +252,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 68..71, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 68..71, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_seen_keyword_only_param_after_star.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_seen_keyword_only_param_after_star.py.snap index a01d1c06653edb..458f38d3292e00 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_seen_keyword_only_param_after_star.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@params_seen_keyword_only_param_after_star.py.snap @@ -1,38 +1,46 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/params_seen_keyword_only_param_after_star.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..61, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 4..7, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 7..23, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 11..12, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 11..12, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 11..12, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -42,9 +50,11 @@ Module( kwarg: Some( Parameter { range: 14..22, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 16..22, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -54,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -67,33 +79,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 29..60, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 33..36, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 36..55, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 40..44, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 40..41, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 40..41, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 42..44, value: Int( 10, @@ -106,9 +127,11 @@ Module( kwarg: Some( Parameter { range: 46..54, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 48..54, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -118,9 +141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_context_manager_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_context_manager_py39.py.snap index fa05d59e248973..b8b9b08747d5b9 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_context_manager_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_context_manager_py39.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_context_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..126, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 43..73, is_async: false, items: [ WithItem { range: 49..57, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..52, id: Name("foo"), ctx: Load, @@ -26,6 +30,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Store, @@ -35,8 +40,10 @@ Module( }, WithItem { range: 59..67, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..62, id: Name("bar"), ctx: Load, @@ -45,6 +52,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("y"), ctx: Store, @@ -56,9 +64,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 70..73, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 70..73, }, ), @@ -69,13 +79,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 74..99, is_async: false, items: [ WithItem { range: 80..83, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..83, id: Name("foo"), ctx: Load, @@ -85,8 +98,10 @@ Module( }, WithItem { range: 85..93, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..88, id: Name("bar"), ctx: Load, @@ -95,6 +110,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..93, id: Name("y"), ctx: Store, @@ -106,9 +122,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 96..99, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 96..99, }, ), @@ -119,13 +137,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 100..125, is_async: false, items: [ WithItem { range: 106..114, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..109, id: Name("foo"), ctx: Load, @@ -134,6 +155,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..114, id: Name("x"), ctx: Store, @@ -143,8 +165,10 @@ Module( }, WithItem { range: 116..119, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..119, id: Name("bar"), ctx: Load, @@ -156,9 +180,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 122..125, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 122..125, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_kwarg_py37.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_kwarg_py37.py.snap index bb42e78d8c5a99..15feb12d3839c7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_kwarg_py37.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_kwarg_py37.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_kwarg_py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..52, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..51, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 43..51, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("f"), ctx: Load, @@ -24,18 +28,22 @@ Module( ), arguments: Arguments { range: 44..51, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 45..50, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 46..47, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap index c8e09fd54a9953..b5b5b300e52a17 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_ex ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..55, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..54, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 43..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("lst"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 48..52, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("x"), ctx: Store, @@ -34,6 +40,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap index 18a6f2406c045c..c1f65ffc1dd2a7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_ex ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..59, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 43..59, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 45..51, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..46, id: Name("x"), ctx: Store, @@ -28,6 +33,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 50..51, value: Int( 1, @@ -38,6 +44,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 54..55, value: Int( 2, @@ -46,6 +53,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 57..58, value: Int( 3, @@ -59,15 +67,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 60..91, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 60..91, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 62..71, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..66, id: Name("last"), ctx: Store, @@ -75,6 +87,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("x"), ctx: Load, @@ -85,8 +98,10 @@ Module( generators: [ Comprehension { range: 73..90, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("x"), ctx: Store, @@ -94,9 +109,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 82..90, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..87, id: Name("range"), ctx: Load, @@ -104,9 +121,11 @@ Module( ), arguments: Arguments { range: 87..90, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 88..89, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_star_index_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_star_index_py310.py.snap index 10df276256e0c6..a1f2cd011db2c2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_star_index_py310.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_star_index_py310.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_star_index_py310.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..94, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 44..93, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 44..89, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..47, id: Name("out"), ctx: Load, @@ -26,19 +29,24 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 48..88, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 49..81, value: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 50..81, elt: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 51..62, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 51..56, id: Name("slice"), ctx: Load, @@ -46,9 +54,11 @@ Module( ), arguments: Arguments { range: 56..62, + node_index: AtomicNodeIndex(..), args: [ NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 57..61, }, ), @@ -60,8 +70,10 @@ Module( generators: [ Comprehension { range: 63..80, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("_"), ctx: Store, @@ -69,9 +81,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 72..80, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..77, id: Name("range"), ctx: Load, @@ -79,9 +93,11 @@ Module( ), arguments: Arguments { range: 77..80, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 78..79, value: Int( 2, @@ -105,9 +121,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 83..87, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..87, id: Name("ind"), ctx: Load, @@ -127,6 +145,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 92..93, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap index cbec152ee7579b..6f728ba6daf917 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap @@ -7,36 +7,44 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..278, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..72, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 44..72, value: FStringValue { inner: Single( FString( FString { range: 44..72, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 46..52, + node_index: AtomicNodeIndex(..), value: "outer ", }, ), Interpolation( InterpolatedElement { range: 52..71, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 53..70, value: StringLiteralValue { inner: Single( StringLiteral { range: 53..70, + node_index: AtomicNodeIndex(..), value: "# not a comment", flags: StringLiteralFlags { quote_style: Single, @@ -69,27 +77,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 73..106, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 73..106, value: FStringValue { inner: Single( FString( FString { range: 73..106, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 75..81, + node_index: AtomicNodeIndex(..), value: "outer ", }, ), Interpolation( InterpolatedElement { range: 81..105, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("x"), ctx: Load, @@ -100,17 +114,21 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 84..104, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 84..103, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 85..102, value: StringLiteralValue { inner: Single( StringLiteral { range: 85..102, + node_index: AtomicNodeIndex(..), value: "# not a comment", flags: StringLiteralFlags { quote_style: Double, @@ -130,6 +148,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 103..104, + node_index: AtomicNodeIndex(..), value: " ", }, ), @@ -154,50 +173,62 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 107..147, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 107..147, value: FStringValue { inner: Single( FString( FString { range: 107..147, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 111..144, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 112..143, value: FStringValue { inner: Single( FString( FString { range: 112..143, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 116..140, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 117..139, value: FStringValue { inner: Single( FString( FString { range: 117..139, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 119..138, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 120..137, value: StringLiteralValue { inner: Single( StringLiteral { range: 120..137, + node_index: AtomicNodeIndex(..), value: "# not a comment", flags: StringLiteralFlags { quote_style: Double, @@ -264,78 +295,96 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..230, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 148..230, value: FStringValue { inner: Single( FString( FString { range: 148..230, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 152..208, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 153..207, value: FStringValue { inner: Single( FString( FString { range: 153..207, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 157..177, + node_index: AtomicNodeIndex(..), value: "# before expression ", }, ), Interpolation( InterpolatedElement { range: 177..204, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 178..203, value: FStringValue { inner: Single( FString( FString { range: 178..203, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 180..185, + node_index: AtomicNodeIndex(..), value: "# aro", }, ), Interpolation( InterpolatedElement { range: 185..197, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 186..196, value: FStringValue { inner: Single( FString( FString { range: 186..196, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 188..189, + node_index: AtomicNodeIndex(..), value: "#", }, ), Interpolation( InterpolatedElement { range: 189..194, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 190..193, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 190..191, value: Int( 1, @@ -345,6 +394,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 192..193, value: Int( 1, @@ -361,6 +411,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 194..195, + node_index: AtomicNodeIndex(..), value: "#", }, ), @@ -384,6 +435,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 197..202, + node_index: AtomicNodeIndex(..), value: "und #", }, ), @@ -424,6 +476,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 208..227, + node_index: AtomicNodeIndex(..), value: " # after expression", }, ), @@ -443,27 +496,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 231..263, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 231..263, value: FStringValue { inner: Single( FString( FString { range: 231..263, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 233..254, + node_index: AtomicNodeIndex(..), value: "escape outside of \t ", }, ), Interpolation( InterpolatedElement { range: 254..260, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..259, id: Name("expr"), ctx: Load, @@ -477,6 +536,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 260..262, + node_index: AtomicNodeIndex(..), value: "\n", }, ), @@ -496,19 +556,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 264..277, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 264..277, value: FStringValue { inner: Single( FString( FString { range: 264..277, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 266..276, + node_index: AtomicNodeIndex(..), value: "test\"abcd", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap index 09817c82834226..aad89368d17545 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap @@ -7,34 +7,42 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..403, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..74, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 44..74, value: FStringValue { inner: Single( FString( FString { range: 44..74, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 46..58, + node_index: AtomicNodeIndex(..), value: "Magic wand: ", }, ), Interpolation( InterpolatedElement { range: 58..73, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 60..71, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("bag"), ctx: Load, @@ -42,11 +50,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 64..70, value: StringLiteralValue { inner: Single( StringLiteral { range: 64..70, + node_index: AtomicNodeIndex(..), value: "wand", flags: StringLiteralFlags { quote_style: Single, @@ -82,32 +92,40 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..112, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 95..112, value: FStringValue { inner: Single( FString( FString { range: 95..112, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 97..111, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 98..110, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 98..107, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 98..102, value: StringLiteralValue { inner: Single( StringLiteral { range: 98..102, + node_index: AtomicNodeIndex(..), value: "\n", flags: StringLiteralFlags { quote_style: Single, @@ -122,15 +140,18 @@ Module( attr: Identifier { id: Name("join"), range: 103..107, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 107..110, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("a"), ctx: Load, @@ -162,30 +183,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..220, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 148..220, value: FStringValue { inner: Single( FString( FString { range: 148..220, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 152..169, + node_index: AtomicNodeIndex(..), value: "A complex trick: ", }, ), Interpolation( InterpolatedElement { range: 169..217, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 175..185, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 175..178, id: Name("bag"), ctx: Load, @@ -193,11 +221,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 179..184, value: StringLiteralValue { inner: Single( StringLiteral { range: 179..184, + node_index: AtomicNodeIndex(..), value: "bag", flags: StringLiteralFlags { quote_style: Single, @@ -233,84 +263,105 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 221..254, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 221..254, value: FStringValue { inner: Single( FString( FString { range: 221..254, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 223..253, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 224..252, value: FStringValue { inner: Single( FString( FString { range: 224..252, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 226..251, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 227..250, value: FStringValue { inner: Single( FString( FString { range: 227..250, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 229..249, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 230..248, value: FStringValue { inner: Single( FString( FString { range: 230..248, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 232..247, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 233..246, value: FStringValue { inner: Single( FString( FString { range: 233..246, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 235..245, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 236..244, value: FStringValue { inner: Single( FString( FString { range: 236..244, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 238..243, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 239..242, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..240, value: Int( 1, @@ -320,6 +371,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 241..242, value: Int( 1, @@ -434,38 +486,47 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 276..310, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 276..310, value: FStringValue { inner: Single( FString( FString { range: 276..310, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 278..303, + node_index: AtomicNodeIndex(..), expression: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 279..302, value: FStringValue { inner: Single( FString( FString { range: 279..302, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 283..293, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 284..292, value: StringLiteralValue { inner: Single( StringLiteral { range: 284..292, + node_index: AtomicNodeIndex(..), value: "nested", flags: StringLiteralFlags { quote_style: Double, @@ -485,6 +546,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 293..299, + node_index: AtomicNodeIndex(..), value: " inner", }, ), @@ -508,6 +570,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 303..309, + node_index: AtomicNodeIndex(..), value: " outer", }, ), @@ -527,27 +590,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 336..359, value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 336..359, value: FStringValue { inner: Single( FString( FString { range: 336..359, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 338..343, + node_index: AtomicNodeIndex(..), value: "test ", }, ), Interpolation( InterpolatedElement { range: 343..353, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 344..345, id: Name("a"), ctx: Load, @@ -561,6 +630,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 353..358, + node_index: AtomicNodeIndex(..), value: " more", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap index 3d322265abe67e..6ad4b1584c3e73 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep750_t_string_py314.py.snap @@ -7,34 +7,42 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep750_t_string_py314. ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..403, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..74, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 44..74, value: TStringValue { inner: Single( TString( TString { range: 44..74, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 46..58, + node_index: AtomicNodeIndex(..), value: "Magic wand: ", }, ), Interpolation( InterpolatedElement { range: 58..73, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 60..71, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 60..63, id: Name("bag"), ctx: Load, @@ -42,11 +50,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 64..70, value: StringLiteralValue { inner: Single( StringLiteral { range: 64..70, + node_index: AtomicNodeIndex(..), value: "wand", flags: StringLiteralFlags { quote_style: Single, @@ -82,32 +92,40 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 95..112, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 95..112, value: TStringValue { inner: Single( TString( TString { range: 95..112, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 97..111, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 98..110, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 98..107, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 98..102, value: StringLiteralValue { inner: Single( StringLiteral { range: 98..102, + node_index: AtomicNodeIndex(..), value: "\n", flags: StringLiteralFlags { quote_style: Single, @@ -122,15 +140,18 @@ Module( attr: Identifier { id: Name("join"), range: 103..107, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 107..110, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("a"), ctx: Load, @@ -162,30 +183,37 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..220, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 148..220, value: TStringValue { inner: Single( TString( TString { range: 148..220, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 152..169, + node_index: AtomicNodeIndex(..), value: "A complex trick: ", }, ), Interpolation( InterpolatedElement { range: 169..217, + node_index: AtomicNodeIndex(..), expression: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 175..185, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 175..178, id: Name("bag"), ctx: Load, @@ -193,11 +221,13 @@ Module( ), slice: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 179..184, value: StringLiteralValue { inner: Single( StringLiteral { range: 179..184, + node_index: AtomicNodeIndex(..), value: "bag", flags: StringLiteralFlags { quote_style: Single, @@ -233,84 +263,105 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 221..254, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 221..254, value: TStringValue { inner: Single( TString( TString { range: 221..254, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 223..253, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 224..252, value: TStringValue { inner: Single( TString( TString { range: 224..252, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 226..251, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 227..250, value: TStringValue { inner: Single( TString( TString { range: 227..250, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 229..249, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 230..248, value: TStringValue { inner: Single( TString( TString { range: 230..248, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 232..247, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 233..246, value: TStringValue { inner: Single( TString( TString { range: 233..246, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 235..245, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 236..244, value: TStringValue { inner: Single( TString( TString { range: 236..244, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 238..243, + node_index: AtomicNodeIndex(..), expression: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 239..242, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..240, value: Int( 1, @@ -320,6 +371,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 241..242, value: Int( 1, @@ -434,38 +486,47 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 276..310, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 276..310, value: TStringValue { inner: Single( TString( TString { range: 276..310, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 278..303, + node_index: AtomicNodeIndex(..), expression: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 279..302, value: TStringValue { inner: Single( TString( TString { range: 279..302, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 283..293, + node_index: AtomicNodeIndex(..), expression: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 284..292, value: StringLiteralValue { inner: Single( StringLiteral { range: 284..292, + node_index: AtomicNodeIndex(..), value: "nested", flags: StringLiteralFlags { quote_style: Double, @@ -485,6 +546,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 293..299, + node_index: AtomicNodeIndex(..), value: " inner", }, ), @@ -508,6 +570,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 303..309, + node_index: AtomicNodeIndex(..), value: " outer", }, ), @@ -527,27 +590,33 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 336..359, value: TString( ExprTString { + node_index: AtomicNodeIndex(..), range: 336..359, value: TStringValue { inner: Single( TString( TString { range: 336..359, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 338..343, + node_index: AtomicNodeIndex(..), value: "test ", }, ), Interpolation( InterpolatedElement { range: 343..353, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 344..345, id: Name("a"), ctx: Load, @@ -561,6 +630,7 @@ Module( Literal( InterpolatedStringLiteralElement { range: 353..358, + node_index: AtomicNodeIndex(..), value: " more", }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pos_only_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pos_only_py38.py.snap index 2e5f4b9e98a9a7..69b218e41941d5 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pos_only_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pos_only_py38.py.snap @@ -7,28 +7,37 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pos_only_py38.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..62, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 43..61, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 47..50, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 50..56, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 51..52, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 51..52, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 51..52, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -44,9 +53,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@read_from_debug.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@read_from_debug.py.snap index 5bfd7c4128930b..7d3b052d80b2c7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@read_from_debug.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@read_from_debug.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/read_from_debug.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..32, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..17, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..12, id: Name("__debug__"), ctx: Load, @@ -22,9 +25,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 14..17, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 14..17, }, ), @@ -36,10 +41,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 18..31, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("x"), ctx: Store, @@ -48,6 +55,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..31, id: Name("__debug__"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_in_block.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_in_block.py.snap index a819e565172796..46c8be7e446987 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_in_block.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_in_block.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/simple_stmts_in_block.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..84, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..13, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 3..7, value: true, }, @@ -22,6 +24,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 9..13, }, ), @@ -31,9 +34,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 14..27, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 17..21, value: true, }, @@ -41,6 +46,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 23..27, }, ), @@ -50,9 +56,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 29..52, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 32..36, value: true, }, @@ -60,11 +68,13 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 38..42, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 44..52, }, ), @@ -74,9 +84,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 53..76, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 56..60, value: true, }, @@ -84,11 +96,13 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 62..66, }, ), Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 68..76, }, ), @@ -98,10 +112,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 78..83, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..79, id: Name("x"), ctx: Store, @@ -110,6 +126,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_with_semicolons.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_with_semicolons.py.snap index 2588cbfe7624ac..41be29071fedc8 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_with_semicolons.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@simple_stmts_with_semicolons.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/simple_stmts_with_semicolons.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..51, body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 0..6, value: None, }, ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 8..16, names: [ Alias { range: 15..16, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 15..16, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -33,19 +37,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 18..33, module: Some( Identifier { id: Name("x"), range: 23..24, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 32..33, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 32..33, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -55,9 +63,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 35..36, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("z"), ctx: Load, @@ -67,9 +77,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 38..50, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("T"), ctx: Store, @@ -78,6 +90,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..50, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_star_in_tuple.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_star_in_tuple.py.snap index ae3a5f8fc470b9..4c44c9cbb38ef7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_star_in_tuple.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_star_in_tuple.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/single_star_in_tuple.p ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..84, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..20, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 4..5, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 5..7, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -31,20 +37,25 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..20, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 9..20, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 15..20, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 16..18, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 17..18, id: Name("x"), ctx: Load, @@ -68,16 +79,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 21..42, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 25..26, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 26..28, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -88,17 +104,21 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 30..42, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 37..42, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 38..40, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("x"), ctx: Load, @@ -120,10 +140,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 43..62, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("_"), ctx: Store, @@ -131,13 +153,16 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 52..57, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 53..55, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..55, id: Name("x"), ctx: Load, @@ -154,9 +179,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 59..62, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 59..62, }, ), @@ -168,17 +195,21 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 63..83, is_async: false, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 67..72, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 68..70, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..70, id: Name("x"), ctx: Store, @@ -194,6 +225,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..78, id: Name("xs"), ctx: Load, @@ -202,9 +234,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 80..83, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 80..83, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_starred_assignment_target.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_starred_assignment_target.py.snap index bd1289f2b39c0b..b2022525334a45 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_starred_assignment_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@single_starred_assignment_target.py.snap @@ -7,21 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/single_starred_assignm ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..36, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..12, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 0..5, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1..3, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2..3, id: Name("a"), ctx: Store, @@ -38,10 +43,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 8..12, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 9..10, value: Int( 1, @@ -57,17 +64,21 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 13..23, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 13..16, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 13..15, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("a"), ctx: Store, @@ -84,10 +95,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 19..23, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 20..21, value: Int( 1, @@ -103,17 +116,21 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 24..35, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 24..28, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 25..27, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("a"), ctx: Store, @@ -129,10 +146,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 31..35, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 32..33, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap index 241edaeadd8f8e..daf54439c214ae 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/star_index_py311.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..293, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 44..55, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 44..55, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..47, id: Name("lst"), ctx: Load, @@ -24,13 +28,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 48..54, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 48..54, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..54, id: Name("index"), ctx: Load, @@ -51,22 +58,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 72..112, decorator_list: [], name: Identifier { id: Name("Array"), range: 78..83, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 83..107, + node_index: AtomicNodeIndex(..), args: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 84..106, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..91, id: Name("Generic"), ctx: Load, @@ -74,10 +86,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 92..105, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 92..97, id: Name("DType"), ctx: Load, @@ -85,9 +99,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 99..105, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..105, id: Name("Shape"), ctx: Load, @@ -111,9 +127,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 109..112, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 109..112, }, ), @@ -124,12 +142,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..161, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 148..161, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..151, id: Name("lst"), ctx: Load, @@ -137,10 +158,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 152..160, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 152..153, id: Name("a"), ctx: Load, @@ -148,9 +171,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 155..157, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("b"), ctx: Load, @@ -161,6 +186,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..160, id: Name("c"), ctx: Load, @@ -178,12 +204,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 185..198, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 185..198, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..188, id: Name("lst"), ctx: Load, @@ -191,10 +220,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 189..197, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("a"), ctx: Load, @@ -202,6 +233,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 192..193, id: Name("b"), ctx: Load, @@ -209,9 +241,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 195..197, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("c"), ctx: Load, @@ -232,12 +266,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 222..233, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 222..233, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..225, id: Name("lst"), ctx: Load, @@ -245,13 +282,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 226..232, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 226..228, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 227..228, id: Name("a"), ctx: Load, @@ -262,9 +302,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 230..232, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 231..232, id: Name("b"), ctx: Load, @@ -285,12 +327,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 254..271, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 254..271, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 254..259, id: Name("array"), ctx: Load, @@ -298,14 +343,17 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 260..270, elts: [ Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 260..263, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 260..261, value: Int( 3, @@ -316,6 +364,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 262..263, value: Int( 5, @@ -328,9 +377,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 265..270, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..270, id: Name("idxs"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap index b651e7c557e19e..df0e3f80ed7b35 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__ambiguous_lpar_with_items.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/valid/statement/ambiguous_lpar_w ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..3620, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 588..604, is_async: false, items: [ WithItem { range: 594..598, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 594..598, id: Name("item"), ctx: Load, @@ -29,9 +33,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 601..604, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 601..604, }, ), @@ -42,13 +48,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 605..622, is_async: false, items: [ WithItem { range: 611..615, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 611..615, id: Name("item"), ctx: Load, @@ -60,9 +69,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 619..622, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 619..622, }, ), @@ -73,13 +84,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 648..668, is_async: false, items: [ WithItem { range: 654..662, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 656..660, id: Name("item"), ctx: Load, @@ -91,9 +105,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 665..668, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 665..668, }, ), @@ -104,13 +120,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 669..693, is_async: false, items: [ WithItem { range: 675..680, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 675..680, id: Name("item1"), ctx: Load, @@ -120,8 +139,10 @@ Module( }, WithItem { range: 682..687, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 682..687, id: Name("item2"), ctx: Load, @@ -133,9 +154,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 690..693, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 690..693, }, ), @@ -146,13 +169,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 694..719, is_async: false, items: [ WithItem { range: 700..705, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 700..705, id: Name("item1"), ctx: Load, @@ -162,8 +188,10 @@ Module( }, WithItem { range: 707..712, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 707..712, id: Name("item2"), ctx: Load, @@ -175,9 +203,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 716..719, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 716..719, }, ), @@ -188,13 +218,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 745..794, is_async: false, items: [ WithItem { range: 751..758, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 752..757, id: Name("item1"), ctx: Load, @@ -204,8 +237,10 @@ Module( }, WithItem { range: 760..767, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 761..766, id: Name("item2"), ctx: Load, @@ -215,8 +250,10 @@ Module( }, WithItem { range: 769..779, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 769..774, id: Name("item3"), ctx: Load, @@ -225,6 +262,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 778..779, id: Name("f"), ctx: Store, @@ -234,8 +272,10 @@ Module( }, WithItem { range: 781..788, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 782..787, id: Name("item4"), ctx: Load, @@ -247,9 +287,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 791..794, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 791..794, }, ), @@ -260,17 +302,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 795..828, is_async: false, items: [ WithItem { range: 801..815, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 801..815, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 802..807, id: Name("item1"), ctx: Load, @@ -278,6 +324,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 809..814, id: Name("item2"), ctx: Load, @@ -292,8 +339,10 @@ Module( }, WithItem { range: 817..822, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 817..822, id: Name("item3"), ctx: Load, @@ -305,9 +354,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 825..828, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 825..828, }, ), @@ -318,17 +369,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 829..852, is_async: false, items: [ WithItem { range: 835..846, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 835..841, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 836..837, id: Name("x"), ctx: Load, @@ -336,6 +391,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 839..840, id: Name("y"), ctx: Load, @@ -349,6 +405,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 845..846, id: Name("f"), ctx: Store, @@ -360,9 +417,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 849..852, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 849..852, }, ), @@ -373,13 +432,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 853..889, is_async: false, items: [ WithItem { range: 859..870, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 859..864, id: Name("item1"), ctx: Load, @@ -388,6 +450,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 868..870, id: Name("f1"), ctx: Store, @@ -397,8 +460,10 @@ Module( }, WithItem { range: 872..883, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 872..877, id: Name("item2"), ctx: Load, @@ -407,6 +472,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 881..883, id: Name("f2"), ctx: Store, @@ -418,9 +484,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 886..889, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 886..889, }, ), @@ -431,13 +499,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 890..927, is_async: false, items: [ WithItem { range: 896..907, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 896..901, id: Name("item1"), ctx: Load, @@ -446,6 +517,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 905..907, id: Name("f1"), ctx: Store, @@ -455,8 +527,10 @@ Module( }, WithItem { range: 909..920, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 909..914, id: Name("item2"), ctx: Load, @@ -465,6 +539,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 918..920, id: Name("f2"), ctx: Store, @@ -476,9 +551,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 924..927, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 924..927, }, ), @@ -489,16 +566,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 953..976, is_async: false, items: [ WithItem { range: 959..969, + node_index: AtomicNodeIndex(..), context_expr: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 959..969, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 959..963, id: Name("item"), ctx: Load, @@ -510,6 +591,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 967..969, value: Int( 10, @@ -525,9 +607,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 973..976, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 973..976, }, ), @@ -538,16 +622,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 977..1001, is_async: false, items: [ WithItem { range: 983..995, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 984..994, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 984..988, id: Name("item"), ctx: Store, @@ -555,6 +643,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 992..994, value: Int( 10, @@ -569,9 +658,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 998..1001, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 998..1001, }, ), @@ -582,20 +673,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1002..1027, is_async: false, items: [ WithItem { range: 1008..1021, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1008..1021, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1009..1019, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1009..1013, id: Name("item"), ctx: Store, @@ -603,6 +699,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1017..1019, value: Int( 10, @@ -622,9 +719,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1024..1027, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1024..1027, }, ), @@ -635,20 +734,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1028..1048, is_async: false, items: [ WithItem { range: 1034..1042, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1034..1042, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1035..1040, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1036..1040, id: Name("item"), ctx: Load, @@ -668,9 +772,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1045..1048, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1045..1048, }, ), @@ -681,16 +787,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1049..1081, is_async: false, items: [ WithItem { range: 1055..1068, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1056..1067, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1056..1061, id: Name("item1"), ctx: Store, @@ -698,6 +808,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1065..1067, value: Int( 10, @@ -710,8 +821,10 @@ Module( }, WithItem { range: 1070..1075, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1070..1075, id: Name("item2"), ctx: Load, @@ -723,9 +836,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1078..1081, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1078..1081, }, ), @@ -736,13 +851,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1082..1119, is_async: false, items: [ WithItem { range: 1088..1098, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1088..1093, id: Name("item1"), ctx: Load, @@ -751,6 +869,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1097..1098, id: Name("f"), ctx: Store, @@ -760,11 +879,14 @@ Module( }, WithItem { range: 1100..1113, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1101..1112, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1101..1106, id: Name("item2"), ctx: Store, @@ -772,6 +894,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1110..1112, value: Int( 10, @@ -786,9 +909,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1116..1119, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1116..1119, }, ), @@ -799,16 +924,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1120..1137, is_async: false, items: [ WithItem { range: 1126..1131, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1126..1131, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1126..1129, id: Name("foo"), ctx: Load, @@ -816,6 +945,7 @@ Module( ), arguments: Arguments { range: 1129..1131, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -827,9 +957,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1134..1137, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1134..1137, }, ), @@ -840,16 +972,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1138..1156, is_async: false, items: [ WithItem { range: 1144..1149, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1144..1149, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1144..1147, id: Name("foo"), ctx: Load, @@ -857,6 +993,7 @@ Module( ), arguments: Arguments { range: 1147..1149, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -868,9 +1005,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1153..1156, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1153..1156, }, ), @@ -881,16 +1020,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1157..1179, is_async: false, items: [ WithItem { range: 1163..1173, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1163..1168, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1163..1166, id: Name("foo"), ctx: Load, @@ -898,6 +1041,7 @@ Module( ), arguments: Arguments { range: 1166..1168, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -906,6 +1050,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1172..1173, id: Name("f"), ctx: Store, @@ -917,9 +1062,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1176..1179, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1176..1179, }, ), @@ -930,25 +1077,31 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1180..1207, is_async: false, items: [ WithItem { range: 1186..1201, + node_index: AtomicNodeIndex(..), context_expr: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 1186..1201, value: FStringValue { inner: Single( FString( FString { range: 1186..1201, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1188..1200, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1189..1193, id: Name("item"), ctx: Load, @@ -959,10 +1112,12 @@ Module( format_spec: Some( InterpolatedStringFormatSpec { range: 1195..1199, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 1195..1199, + node_index: AtomicNodeIndex(..), value: "= 42", }, ), @@ -989,9 +1144,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1204..1207, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1204..1207, }, ), @@ -1002,28 +1159,35 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1208..1237, is_async: false, items: [ WithItem { range: 1214..1231, + node_index: AtomicNodeIndex(..), context_expr: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 1214..1231, value: FStringValue { inner: Single( FString( FString { range: 1214..1231, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 1216..1230, + node_index: AtomicNodeIndex(..), expression: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1218..1228, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1218..1222, id: Name("item"), ctx: Store, @@ -1031,6 +1195,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1226..1228, value: Int( 42, @@ -1062,9 +1227,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1234..1237, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1234..1237, }, ), @@ -1075,16 +1242,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1238..1278, is_async: false, items: [ WithItem { range: 1244..1266, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 1244..1266, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1245..1246, id: Name("x"), ctx: Load, @@ -1093,8 +1264,10 @@ Module( generators: [ Comprehension { range: 1247..1265, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1251..1252, id: Name("x"), ctx: Store, @@ -1102,9 +1275,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1256..1265, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1256..1261, id: Name("range"), ctx: Load, @@ -1112,9 +1287,11 @@ Module( ), arguments: Arguments { range: 1261..1265, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1262..1264, value: Int( 10, @@ -1137,8 +1314,10 @@ Module( }, WithItem { range: 1268..1272, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1268..1272, id: Name("item"), ctx: Load, @@ -1150,9 +1329,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1275..1278, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1275..1278, }, ), @@ -1163,13 +1344,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1279..1319, is_async: false, items: [ WithItem { range: 1285..1289, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1285..1289, id: Name("item"), ctx: Load, @@ -1179,11 +1363,14 @@ Module( }, WithItem { range: 1291..1313, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 1291..1313, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1292..1293, id: Name("x"), ctx: Load, @@ -1192,8 +1379,10 @@ Module( generators: [ Comprehension { range: 1294..1312, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1298..1299, id: Name("x"), ctx: Store, @@ -1201,9 +1390,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1303..1312, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1303..1308, id: Name("range"), ctx: Load, @@ -1211,9 +1402,11 @@ Module( ), arguments: Arguments { range: 1308..1312, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1309..1311, value: Int( 10, @@ -1238,9 +1431,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1316..1319, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1316..1319, }, ), @@ -1251,13 +1446,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1320..1366, is_async: false, items: [ WithItem { range: 1326..1330, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1326..1330, id: Name("item"), ctx: Load, @@ -1267,11 +1465,14 @@ Module( }, WithItem { range: 1332..1354, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 1332..1354, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1333..1334, id: Name("x"), ctx: Load, @@ -1280,8 +1481,10 @@ Module( generators: [ Comprehension { range: 1335..1353, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1339..1340, id: Name("x"), ctx: Store, @@ -1289,9 +1492,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1344..1353, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1344..1349, id: Name("range"), ctx: Load, @@ -1299,9 +1504,11 @@ Module( ), arguments: Arguments { range: 1349..1353, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1350..1352, value: Int( 10, @@ -1324,8 +1531,10 @@ Module( }, WithItem { range: 1356..1360, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1356..1360, id: Name("item"), ctx: Load, @@ -1337,9 +1546,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1363..1366, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1363..1366, }, ), @@ -1350,16 +1561,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1367..1388, is_async: false, items: [ WithItem { range: 1373..1382, + node_index: AtomicNodeIndex(..), context_expr: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1373..1382, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1373..1377, id: Name("data"), ctx: Load, @@ -1367,10 +1582,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 1378..1381, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1378..1379, value: Int( 1, @@ -1381,6 +1598,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1380..1381, value: Int( 2, @@ -1400,9 +1618,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1385..1388, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1385..1388, }, ), @@ -1413,16 +1633,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1389..1415, is_async: false, items: [ WithItem { range: 1395..1409, + node_index: AtomicNodeIndex(..), context_expr: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1395..1404, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1395..1399, id: Name("data"), ctx: Load, @@ -1430,10 +1654,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 1400..1403, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1400..1401, value: Int( 1, @@ -1444,6 +1670,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1402..1403, value: Int( 2, @@ -1460,6 +1687,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1408..1409, id: Name("f"), ctx: Store, @@ -1471,9 +1699,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1412..1415, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1412..1415, }, ), @@ -1484,16 +1714,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1416..1450, is_async: false, items: [ WithItem { range: 1422..1444, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 1422..1439, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1423..1424, id: Name("x"), ctx: Load, @@ -1502,8 +1736,10 @@ Module( generators: [ Comprehension { range: 1425..1438, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1429..1430, id: Name("x"), ctx: Store, @@ -1511,6 +1747,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1434..1438, id: Name("iter"), ctx: Load, @@ -1526,6 +1763,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1443..1444, id: Name("y"), ctx: Store, @@ -1537,9 +1775,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1447..1450, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1447..1450, }, ), @@ -1550,13 +1790,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1663..1684, is_async: false, items: [ WithItem { range: 1668..1679, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1669..1673, id: Name("item"), ctx: Load, @@ -1565,6 +1808,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1678..1679, id: Name("f"), ctx: Store, @@ -1576,9 +1820,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1681..1684, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1681..1684, }, ), @@ -1589,16 +1835,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1685..1707, is_async: false, items: [ WithItem { range: 1690..1702, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1691..1701, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1691..1695, id: Name("item"), ctx: Store, @@ -1606,6 +1856,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1699..1701, value: Int( 10, @@ -1620,9 +1871,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1704..1707, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1704..1707, }, ), @@ -1633,16 +1886,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1708..1735, is_async: false, items: [ WithItem { range: 1713..1730, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1714..1724, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1714..1718, id: Name("item"), ctx: Store, @@ -1650,6 +1907,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1722..1724, value: Int( 10, @@ -1661,6 +1919,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1729..1730, id: Name("f"), ctx: Store, @@ -1672,9 +1931,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1732..1735, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1732..1735, }, ), @@ -1685,16 +1946,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1736..1762, is_async: false, items: [ WithItem { range: 1741..1757, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1744..1753, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1744..1748, id: Name("item"), ctx: Store, @@ -1702,6 +1967,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1752..1753, value: Int( 1, @@ -1716,9 +1982,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1759..1762, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1759..1762, }, ), @@ -1729,16 +1997,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1763..1793, is_async: false, items: [ WithItem { range: 1768..1781, + node_index: AtomicNodeIndex(..), context_expr: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1769..1780, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1769..1774, id: Name("item1"), ctx: Store, @@ -1746,6 +2018,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1778..1780, value: Int( 42, @@ -1758,8 +2031,10 @@ Module( }, WithItem { range: 1783..1788, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1783..1788, id: Name("item2"), ctx: Load, @@ -1771,9 +2046,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1790..1793, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1790..1793, }, ), @@ -1784,22 +2061,28 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1794..1828, is_async: false, items: [ WithItem { range: 1799..1823, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1799..1823, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1799..1821, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1800..1815, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1800..1804, id: Name("root"), ctx: Load, @@ -1808,6 +2091,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1807..1815, id: Name("filename"), ctx: Load, @@ -1818,12 +2102,14 @@ Module( attr: Identifier { id: Name("read"), range: 1817..1821, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 1821..1823, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1835,9 +2121,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1825..1828, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1825..1828, }, ), @@ -1848,22 +2136,28 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1851..1890, is_async: false, items: [ WithItem { range: 1856..1885, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1856..1880, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1856..1878, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1857..1872, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1857..1861, id: Name("root"), ctx: Load, @@ -1872,6 +2166,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1864..1872, id: Name("filename"), ctx: Load, @@ -1882,12 +2177,14 @@ Module( attr: Identifier { id: Name("read"), range: 1874..1878, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 1878..1880, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1896,6 +2193,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1884..1885, id: Name("f"), ctx: Store, @@ -1907,9 +2205,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1887..1890, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1887..1890, }, ), @@ -1920,16 +2220,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1913..1930, is_async: false, items: [ WithItem { range: 1918..1925, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1918..1925, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1919..1922, id: Name("foo"), ctx: Load, @@ -1937,6 +2241,7 @@ Module( ), arguments: Arguments { range: 1923..1925, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1948,9 +2253,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1927..1930, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1927..1930, }, ), @@ -1961,16 +2268,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1953..1975, is_async: false, items: [ WithItem { range: 1958..1970, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1958..1965, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1959..1962, id: Name("foo"), ctx: Load, @@ -1978,6 +2289,7 @@ Module( ), arguments: Arguments { range: 1963..1965, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1986,6 +2298,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1969..1970, id: Name("f"), ctx: Store, @@ -1997,9 +2310,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1972..1975, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1972..1975, }, ), @@ -2010,16 +2325,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 1998..2020, is_async: false, items: [ WithItem { range: 2003..2015, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 2004..2009, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2004..2007, id: Name("foo"), ctx: Load, @@ -2027,6 +2346,7 @@ Module( ), arguments: Arguments { range: 2007..2009, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -2035,6 +2355,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2014..2015, id: Name("f"), ctx: Store, @@ -2046,9 +2367,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2017..2020, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2017..2020, }, ), @@ -2059,16 +2382,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2021..2047, is_async: false, items: [ WithItem { range: 2026..2042, + node_index: AtomicNodeIndex(..), context_expr: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 2027..2036, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2027..2031, id: Name("data"), ctx: Load, @@ -2076,10 +2403,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 2032..2035, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2032..2033, value: Int( 1, @@ -2090,6 +2419,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2034..2035, value: Int( 2, @@ -2106,6 +2436,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2041..2042, id: Name("f"), ctx: Store, @@ -2117,9 +2448,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2044..2047, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2044..2047, }, ), @@ -2130,20 +2463,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2048..2070, is_async: false, items: [ WithItem { range: 2053..2065, + node_index: AtomicNodeIndex(..), context_expr: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 2053..2065, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2053..2062, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2054..2055, value: Int( 1, @@ -2152,6 +2490,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2057..2058, value: Int( 2, @@ -2160,6 +2499,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2060..2061, value: Int( 3, @@ -2173,6 +2513,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2063..2064, value: Int( 0, @@ -2188,9 +2529,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2067..2070, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2067..2070, }, ), @@ -2201,20 +2544,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2093..2120, is_async: false, items: [ WithItem { range: 2098..2115, + node_index: AtomicNodeIndex(..), context_expr: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 2098..2110, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2098..2107, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2099..2100, value: Int( 1, @@ -2223,6 +2571,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2102..2103, value: Int( 2, @@ -2231,6 +2580,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2105..2106, value: Int( 3, @@ -2244,6 +2594,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2108..2109, value: Int( 0, @@ -2256,6 +2607,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2114..2115, id: Name("f"), ctx: Store, @@ -2267,9 +2619,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2117..2120, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2117..2120, }, ), @@ -2280,13 +2634,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2143..2169, is_async: false, items: [ WithItem { range: 2148..2155, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2149..2154, id: Name("item1"), ctx: Load, @@ -2296,8 +2653,10 @@ Module( }, WithItem { range: 2157..2164, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2158..2163, id: Name("item2"), ctx: Load, @@ -2309,9 +2668,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2166..2169, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2166..2169, }, ), @@ -2322,16 +2683,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2170..2210, is_async: false, items: [ WithItem { range: 2175..2189, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 2176..2188, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2176..2180, id: Name("open"), ctx: Load, @@ -2339,14 +2704,17 @@ Module( ), arguments: Arguments { range: 2180..2188, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2181..2187, value: StringLiteralValue { inner: Single( StringLiteral { range: 2181..2187, + node_index: AtomicNodeIndex(..), value: "a.py", flags: StringLiteralFlags { quote_style: Single, @@ -2367,11 +2735,14 @@ Module( }, WithItem { range: 2191..2205, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 2192..2204, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2192..2196, id: Name("open"), ctx: Load, @@ -2379,14 +2750,17 @@ Module( ), arguments: Arguments { range: 2196..2204, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2197..2203, value: StringLiteralValue { inner: Single( StringLiteral { range: 2197..2203, + node_index: AtomicNodeIndex(..), value: "b.py", flags: StringLiteralFlags { quote_style: Single, @@ -2409,9 +2783,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2207..2210, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2207..2210, }, ), @@ -2422,17 +2798,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2211..2230, is_async: false, items: [ WithItem { range: 2216..2225, + node_index: AtomicNodeIndex(..), context_expr: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 2217..2224, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2223..2224, id: Name("x"), ctx: Load, @@ -2447,9 +2827,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2227..2230, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2227..2230, }, ), @@ -2460,17 +2842,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2231..2252, is_async: false, items: [ WithItem { range: 2237..2246, + node_index: AtomicNodeIndex(..), context_expr: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 2238..2245, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2244..2245, id: Name("x"), ctx: Load, @@ -2485,9 +2871,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2249..2252, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2249..2252, }, ), @@ -2498,16 +2886,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2253..2277, is_async: false, items: [ WithItem { range: 2258..2272, + node_index: AtomicNodeIndex(..), context_expr: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 2259..2271, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2270..2271, id: Name("x"), ctx: Load, @@ -2521,9 +2913,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2274..2277, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2274..2277, }, ), @@ -2534,16 +2928,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2278..2304, is_async: false, items: [ WithItem { range: 2284..2298, + node_index: AtomicNodeIndex(..), context_expr: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 2285..2297, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2296..2297, id: Name("x"), ctx: Load, @@ -2557,9 +2955,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2301..2304, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2301..2304, }, ), @@ -2570,17 +2970,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2305..2329, is_async: false, items: [ WithItem { range: 2310..2324, + node_index: AtomicNodeIndex(..), context_expr: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 2311..2318, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2317..2318, id: Name("x"), ctx: Load, @@ -2592,6 +2996,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2323..2324, id: Name("f"), ctx: Store, @@ -2603,9 +3008,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2326..2329, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2326..2329, }, ), @@ -2616,21 +3023,26 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2330..2355, is_async: false, items: [ WithItem { range: 2335..2350, + node_index: AtomicNodeIndex(..), context_expr: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 2336..2344, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2342..2344, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2342..2343, id: Name("x"), ctx: Load, @@ -2647,6 +3059,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2349..2350, id: Name("f"), ctx: Store, @@ -2658,9 +3071,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2352..2355, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2352..2355, }, ), @@ -2671,13 +3086,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2741..2753, is_async: false, items: [ WithItem { range: 2746..2748, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2746..2748, elts: [], ctx: Load, @@ -2690,9 +3108,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2750..2753, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2750..2753, }, ), @@ -2703,13 +3123,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2754..2771, is_async: false, items: [ WithItem { range: 2759..2766, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2759..2761, elts: [], ctx: Load, @@ -2719,6 +3142,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2765..2766, id: Name("f"), ctx: Store, @@ -2730,9 +3154,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2768..2771, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2768..2771, }, ), @@ -2743,20 +3169,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2772..2795, is_async: false, items: [ WithItem { range: 2777..2790, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2777..2790, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2778..2788, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2778..2782, id: Name("item"), ctx: Store, @@ -2764,6 +3195,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2786..2788, value: Int( 42, @@ -2783,9 +3215,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2792..2795, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2792..2795, }, ), @@ -2796,17 +3230,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2796..2820, is_async: false, items: [ WithItem { range: 2801..2815, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2801..2815, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2802..2803, value: Int( 1, @@ -2815,9 +3253,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2805..2814, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2805..2809, id: Name("item"), ctx: Store, @@ -2825,6 +3265,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2813..2814, value: Int( 2, @@ -2844,9 +3285,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2817..2820, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2817..2820, }, ), @@ -2857,20 +3300,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2821..2851, is_async: false, items: [ WithItem { range: 2826..2846, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2826..2846, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2827..2838, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2827..2832, id: Name("item1"), ctx: Store, @@ -2878,6 +3326,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2836..2838, value: Int( 10, @@ -2888,6 +3337,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2840..2845, id: Name("item2"), ctx: Load, @@ -2904,9 +3354,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2848..2851, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2848..2851, }, ), @@ -2917,17 +3369,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2852..2893, is_async: false, items: [ WithItem { range: 2857..2888, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2857..2883, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2858..2863, id: Name("item1"), ctx: Load, @@ -2935,9 +3391,11 @@ Module( ), Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2865..2875, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2865..2870, id: Name("item2"), ctx: Store, @@ -2945,6 +3403,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2874..2875, value: Int( 2, @@ -2955,6 +3414,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2877..2882, id: Name("item3"), ctx: Load, @@ -2968,6 +3428,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2887..2888, id: Name("f"), ctx: Store, @@ -2979,9 +3440,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2890..2893, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2890..2893, }, ), @@ -2992,17 +3455,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2894..2916, is_async: false, items: [ WithItem { range: 2899..2911, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2899..2906, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2900..2904, id: Name("item"), ctx: Load, @@ -3016,6 +3483,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2910..2911, id: Name("f"), ctx: Store, @@ -3027,9 +3495,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2913..2916, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2913..2916, }, ), @@ -3040,20 +3510,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2917..2935, is_async: false, items: [ WithItem { range: 2922..2930, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2922..2930, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 2923..2928, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2924..2928, id: Name("item"), ctx: Load, @@ -3073,9 +3548,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2932..2935, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2932..2935, }, ), @@ -3086,20 +3563,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2936..2959, is_async: false, items: [ WithItem { range: 2941..2954, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2941..2949, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 2942..2947, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2943..2947, id: Name("item"), ctx: Load, @@ -3116,6 +3598,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2953..2954, id: Name("f"), ctx: Store, @@ -3127,9 +3610,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2956..2959, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2956..2959, }, ), @@ -3140,17 +3625,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2960..2989, is_async: false, items: [ WithItem { range: 2965..2984, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2965..2979, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2966..2971, id: Name("item1"), ctx: Load, @@ -3158,6 +3647,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2973..2978, id: Name("item2"), ctx: Load, @@ -3171,6 +3661,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2983..2984, id: Name("f"), ctx: Store, @@ -3182,9 +3673,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2986..2989, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2986..2989, }, ), @@ -3195,17 +3688,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 2990..3020, is_async: false, items: [ WithItem { range: 2995..3015, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2995..3010, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2996..3001, id: Name("item1"), ctx: Load, @@ -3213,6 +3710,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3003..3008, id: Name("item2"), ctx: Load, @@ -3226,6 +3724,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3014..3015, id: Name("f"), ctx: Store, @@ -3237,9 +3736,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3017..3020, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3017..3020, }, ), @@ -3250,17 +3751,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3021..3052, is_async: false, items: [ WithItem { range: 3026..3040, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3026..3040, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3027..3032, id: Name("item1"), ctx: Load, @@ -3268,6 +3773,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3034..3039, id: Name("item2"), ctx: Load, @@ -3282,8 +3788,10 @@ Module( }, WithItem { range: 3042..3047, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3042..3047, id: Name("item3"), ctx: Load, @@ -3295,9 +3803,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3049..3052, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3049..3052, }, ), @@ -3308,21 +3818,26 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3053..3091, is_async: false, items: [ WithItem { range: 3058..3086, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3058..3081, elts: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3059..3073, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3060..3065, id: Name("item1"), ctx: Load, @@ -3330,6 +3845,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3067..3072, id: Name("item2"), ctx: Load, @@ -3342,6 +3858,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3075..3080, id: Name("item3"), ctx: Load, @@ -3355,6 +3872,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3085..3086, id: Name("f"), ctx: Store, @@ -3366,9 +3884,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3088..3091, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3088..3091, }, ), @@ -3379,17 +3899,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3092..3138, is_async: false, items: [ WithItem { range: 3097..3105, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3097..3105, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3098..3103, id: Name("item1"), ctx: Load, @@ -3404,8 +3928,10 @@ Module( }, WithItem { range: 3107..3112, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3107..3112, id: Name("item2"), ctx: Load, @@ -3415,12 +3941,15 @@ Module( }, WithItem { range: 3114..3133, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3114..3128, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3115..3120, id: Name("item3"), ctx: Load, @@ -3428,6 +3957,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3122..3127, id: Name("item4"), ctx: Load, @@ -3441,6 +3971,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3132..3133, id: Name("f"), ctx: Store, @@ -3452,9 +3983,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3135..3138, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3135..3138, }, ), @@ -3465,17 +3998,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3139..3182, is_async: false, items: [ WithItem { range: 3144..3164, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3144..3158, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3145..3150, id: Name("item1"), ctx: Load, @@ -3483,6 +4020,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3152..3157, id: Name("item2"), ctx: Load, @@ -3496,6 +4034,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3162..3164, id: Name("f1"), ctx: Store, @@ -3505,8 +4044,10 @@ Module( }, WithItem { range: 3166..3177, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3166..3171, id: Name("item3"), ctx: Load, @@ -3515,6 +4056,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3175..3177, id: Name("f2"), ctx: Store, @@ -3526,9 +4068,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3179..3182, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3179..3182, }, ), @@ -3539,17 +4083,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3183..3208, is_async: false, items: [ WithItem { range: 3188..3203, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3188..3203, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3189..3194, id: Name("item1"), ctx: Load, @@ -3557,9 +4105,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3196..3202, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3197..3202, id: Name("item2"), ctx: Load, @@ -3579,9 +4129,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3205..3208, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3205..3208, }, ), @@ -3592,17 +4144,21 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3209..3239, is_async: false, items: [ WithItem { range: 3214..3234, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3214..3229, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3215..3220, id: Name("item1"), ctx: Load, @@ -3610,9 +4166,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3222..3228, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3223..3228, id: Name("item2"), ctx: Load, @@ -3629,6 +4187,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3233..3234, id: Name("f"), ctx: Store, @@ -3640,9 +4199,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3236..3239, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3236..3239, }, ), @@ -3653,20 +4214,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3240..3271, is_async: false, items: [ WithItem { range: 3245..3266, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3245..3266, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 3246..3257, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3246..3251, id: Name("item1"), ctx: Store, @@ -3674,6 +4240,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3255..3257, value: Int( 10, @@ -3684,9 +4251,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3259..3265, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3260..3265, id: Name("item2"), ctx: Load, @@ -3706,9 +4275,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3268..3271, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3268..3271, }, ), @@ -3719,20 +4290,25 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3272..3305, is_async: false, items: [ WithItem { range: 3277..3300, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3277..3300, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 3279..3290, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3279..3284, id: Name("item1"), ctx: Store, @@ -3740,6 +4316,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3288..3290, value: Int( 10, @@ -3750,9 +4327,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 3293..3299, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3294..3299, id: Name("item2"), ctx: Load, @@ -3772,9 +4351,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3302..3305, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3302..3305, }, ), @@ -3785,16 +4366,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3510..3542, is_async: false, items: [ WithItem { range: 3515..3537, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 3515..3537, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3516..3517, id: Name("x"), ctx: Load, @@ -3803,8 +4388,10 @@ Module( generators: [ Comprehension { range: 3518..3536, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3522..3523, id: Name("x"), ctx: Store, @@ -3812,9 +4399,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3527..3536, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3527..3532, id: Name("range"), ctx: Load, @@ -3822,9 +4411,11 @@ Module( ), arguments: Arguments { range: 3532..3536, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3533..3535, value: Int( 10, @@ -3849,9 +4440,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3539..3542, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3539..3542, }, ), @@ -3862,16 +4455,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3543..3581, is_async: false, items: [ WithItem { range: 3548..3576, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 3548..3576, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3549..3550, id: Name("x"), ctx: Load, @@ -3880,8 +4477,10 @@ Module( generators: [ Comprehension { range: 3551..3575, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3561..3562, id: Name("x"), ctx: Store, @@ -3889,9 +4488,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3566..3575, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3566..3571, id: Name("range"), ctx: Load, @@ -3899,9 +4500,11 @@ Module( ), arguments: Arguments { range: 3571..3575, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3572..3574, value: Int( 10, @@ -3926,9 +4529,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3578..3581, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3578..3581, }, ), @@ -3939,16 +4544,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 3582..3620, is_async: false, items: [ WithItem { range: 3587..3609, + node_index: AtomicNodeIndex(..), context_expr: Generator( ExprGenerator { + node_index: AtomicNodeIndex(..), range: 3587..3609, elt: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3588..3589, id: Name("x"), ctx: Load, @@ -3957,8 +4566,10 @@ Module( generators: [ Comprehension { range: 3590..3608, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3594..3595, id: Name("x"), ctx: Store, @@ -3966,9 +4577,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3599..3608, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3599..3604, id: Name("range"), ctx: Load, @@ -3976,9 +4589,11 @@ Module( ), arguments: Arguments { range: 3604..3608, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3605..3607, value: Int( 10, @@ -4001,8 +4616,10 @@ Module( }, WithItem { range: 3611..3615, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3611..3615, id: Name("item"), ctx: Load, @@ -4014,9 +4631,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3617..3620, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3617..3620, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__annotated_assignment.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__annotated_assignment.py.snap index 493f3ba3d14376..4f2c579ca34c48 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__annotated_assignment.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__annotated_assignment.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/annotated_assignment.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..103, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 0..6, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -22,6 +24,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3..6, id: Name("int"), ctx: Load, @@ -33,9 +36,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 7..17, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Store, @@ -43,6 +48,7 @@ Module( ), annotation: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 10..13, id: Name("int"), ctx: Load, @@ -51,6 +57,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 16..17, value: Int( 1, @@ -63,9 +70,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 18..28, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("x"), ctx: Store, @@ -73,9 +82,11 @@ Module( ), annotation: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 23..28, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 23..24, value: Int( 1, @@ -85,6 +96,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 27..28, value: Int( 2, @@ -99,9 +111,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 29..55, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("x"), ctx: Store, @@ -109,12 +123,15 @@ Module( ), annotation: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 32..48, left: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 32..42, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..37, id: Name("tuple"), ctx: Load, @@ -122,6 +139,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..41, id: Name("int"), ctx: Load, @@ -133,6 +151,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 45..48, id: Name("int"), ctx: Load, @@ -143,10 +162,12 @@ Module( value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 51..55, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 1, @@ -164,9 +185,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 56..83, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Store, @@ -174,15 +197,18 @@ Module( ), annotation: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 59..79, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 66..70, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..62, id: Name("int"), ctx: Load, @@ -190,6 +216,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("str"), ctx: Load, @@ -200,6 +227,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 82..83, value: Int( 1, @@ -212,9 +240,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 84..102, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..85, id: Name("x"), ctx: Store, @@ -222,19 +252,26 @@ Module( ), annotation: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 87..98, parameters: Some( Parameters { range: 94..95, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 94..95, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 94..95, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 94..95, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -248,6 +285,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 97..98, id: Name("y"), ctx: Load, @@ -258,6 +296,7 @@ Module( value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 101..102, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assert.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assert.py.snap index 1dce325d01c291..e73517455e5c7d 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assert.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assert.py.snap @@ -1,23 +1,26 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/assert.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..186, body: [ Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 0..12, test: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 7..12, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 7..8, value: Int( 1, @@ -30,6 +33,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 2, @@ -44,12 +48,15 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 13..26, test: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 20..26, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..24, id: Name("call"), ctx: Load, @@ -57,6 +64,7 @@ Module( ), arguments: Arguments { range: 24..26, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -67,14 +75,17 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 27..41, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 34..41, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..35, id: Name("a"), ctx: Load, @@ -82,6 +93,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 40..41, id: Name("b"), ctx: Load, @@ -95,22 +107,30 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 42..60, test: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 49..60, parameters: Some( Parameters { range: 56..57, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 56..57, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 56..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 56..57, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -124,6 +144,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("y"), ctx: Load, @@ -136,12 +157,15 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 61..75, test: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 68..75, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("x"), ctx: Load, @@ -154,18 +178,22 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 76..99, test: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 83..99, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 88..92, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -173,6 +201,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("y"), ctx: Load, @@ -185,9 +214,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 101..118, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 108..109, id: Name("x"), ctx: Load, @@ -196,11 +227,13 @@ Module( msg: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 111..118, value: StringLiteralValue { inner: Single( StringLiteral { range: 111..118, + node_index: AtomicNodeIndex(..), value: "error", flags: StringLiteralFlags { quote_style: Double, @@ -217,9 +250,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 119..140, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 126..127, id: Name("x"), ctx: Load, @@ -228,19 +263,26 @@ Module( msg: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 129..140, parameters: Some( Parameters { range: 136..137, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 136..137, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 136..137, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 136..137, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -254,6 +296,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..140, id: Name("y"), ctx: Load, @@ -266,9 +309,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 141..158, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..149, id: Name("x"), ctx: Load, @@ -277,9 +322,11 @@ Module( msg: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 151..158, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..158, id: Name("x"), ctx: Load, @@ -292,9 +339,11 @@ Module( ), Assert( StmtAssert { + node_index: AtomicNodeIndex(..), range: 159..185, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("x"), ctx: Load, @@ -303,15 +352,18 @@ Module( msg: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 169..185, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 174..178, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 169..170, id: Name("x"), ctx: Load, @@ -319,6 +371,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..185, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assignment.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assignment.py.snap index 606a9ea32422bf..d649315b7d8b5b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assignment.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__assignment.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/valid/statement/assignment.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..729, body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 0..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,10 +26,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 4..13, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -35,6 +40,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 8..9, value: Int( 2, @@ -43,6 +49,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 11..12, value: Int( 3, @@ -58,14 +65,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 15..33, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 15..21, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 16..17, id: Name("x"), ctx: Store, @@ -73,6 +83,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 19..20, id: Name("y"), ctx: Store, @@ -86,10 +97,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 24..33, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 25..26, value: Int( 1, @@ -98,6 +111,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 28..29, value: Int( 2, @@ -106,6 +120,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 31..32, value: Int( 3, @@ -121,14 +136,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 35..53, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 35..41, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("x"), ctx: Store, @@ -136,6 +154,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("y"), ctx: Store, @@ -148,10 +167,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 44..53, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 1, @@ -160,6 +181,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 48..49, value: Int( 2, @@ -168,6 +190,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 3, @@ -183,13 +206,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 55..70, targets: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 55..58, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..56, id: Name("x"), ctx: Load, @@ -198,6 +224,7 @@ Module( attr: Identifier { id: Name("y"), range: 57..58, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, @@ -205,10 +232,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 61..70, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 62..63, value: Int( 1, @@ -217,6 +246,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 65..66, value: Int( 2, @@ -225,6 +255,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 68..69, value: Int( 3, @@ -240,13 +271,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 72..88, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 72..76, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("x"), ctx: Load, @@ -254,6 +288,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 74..75, id: Name("y"), ctx: Load, @@ -265,10 +300,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 79..88, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 80..81, value: Int( 1, @@ -277,6 +314,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 2, @@ -285,6 +323,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..87, value: Int( 3, @@ -300,14 +339,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 90..109, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 90..97, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("x"), ctx: Store, @@ -315,9 +357,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 94..96, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..96, id: Name("y"), ctx: Store, @@ -334,10 +378,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 100..109, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 101..102, value: Int( 1, @@ -346,6 +392,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..105, value: Int( 2, @@ -354,6 +401,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 107..108, value: Int( 3, @@ -369,14 +417,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 259..280, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 259..268, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..261, id: Name("x"), ctx: Store, @@ -384,6 +435,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 263..264, id: Name("y"), ctx: Store, @@ -391,6 +443,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 266..267, id: Name("z"), ctx: Store, @@ -403,10 +456,12 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 271..280, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 272..273, value: Int( 1, @@ -415,6 +470,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 275..276, value: Int( 2, @@ -423,6 +479,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 278..279, value: Int( 3, @@ -437,14 +494,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 282..303, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 282..291, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 283..284, id: Name("x"), ctx: Store, @@ -452,6 +512,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 286..287, id: Name("y"), ctx: Store, @@ -459,6 +520,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 289..290, id: Name("z"), ctx: Store, @@ -472,10 +534,12 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 294..303, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 295..296, value: Int( 1, @@ -484,6 +548,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 298..299, value: Int( 2, @@ -492,6 +557,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 301..302, value: Int( 3, @@ -507,13 +573,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 304..313, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 304..308, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 304..305, id: Name("x"), ctx: Load, @@ -521,6 +590,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 306..307, value: Int( 0, @@ -533,6 +603,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 311..313, value: Int( 42, @@ -543,13 +614,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 410..419, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 410..414, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 410..411, value: Int( 5, @@ -558,6 +632,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 412..413, value: Int( 0, @@ -570,6 +645,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 417..419, value: Int( 42, @@ -580,13 +656,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 420..433, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 420..426, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 420..421, id: Name("x"), ctx: Load, @@ -594,10 +673,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 422..425, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 422..423, value: Int( 1, @@ -608,6 +689,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 424..425, value: Int( 2, @@ -624,10 +706,12 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 429..433, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 430..432, value: Int( 42, @@ -642,13 +726,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 529..542, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 529..535, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 529..530, value: Int( 5, @@ -657,10 +744,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 531..534, lower: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 531..532, value: Int( 1, @@ -671,6 +760,7 @@ Module( upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 533..534, value: Int( 2, @@ -687,10 +777,12 @@ Module( ], value: List( ExprList { + node_index: AtomicNodeIndex(..), range: 538..542, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 539..541, value: Int( 42, @@ -705,13 +797,16 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 544..556, targets: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 544..551, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 544..547, id: Name("foo"), ctx: Load, @@ -720,6 +815,7 @@ Module( attr: Identifier { id: Name("bar"), range: 548..551, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, @@ -727,6 +823,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 554..556, value: Int( 42, @@ -737,18 +834,22 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 658..670, targets: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 658..665, value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 658..663, value: StringLiteralValue { inner: Single( StringLiteral { range: 658..663, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -763,6 +864,7 @@ Module( attr: Identifier { id: Name("y"), range: 664..665, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, @@ -770,6 +872,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 668..670, value: Int( 42, @@ -780,10 +883,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 672..680, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 672..675, id: Name("foo"), ctx: Store, @@ -792,6 +897,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 678..680, value: Int( 42, @@ -802,10 +908,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 682..695, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 682..684, elts: [], ctx: Store, @@ -814,13 +922,16 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 687..695, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 688..693, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 689..693, id: Name("data"), ctx: Load, @@ -838,10 +949,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 696..709, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 696..698, elts: [], ctx: Store, @@ -851,13 +964,16 @@ Module( ], value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 701..709, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 702..707, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 703..707, id: Name("data"), ctx: Load, @@ -875,14 +991,17 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 710..719, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 710..714, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 710..711, id: Name("a"), ctx: Store, @@ -890,6 +1009,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 713..714, id: Name("b"), ctx: Store, @@ -903,6 +1023,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 717..719, id: Name("ab"), ctx: Load, @@ -912,10 +1033,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 720..729, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 720..721, id: Name("a"), ctx: Store, @@ -923,6 +1046,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 724..725, id: Name("b"), ctx: Store, @@ -931,6 +1055,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 728..729, id: Name("c"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__augmented_assignment.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__augmented_assignment.py.snap index da75ead75c8177..aabb6b245bd574 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__augmented_assignment.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__augmented_assignment.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/augmented_assignment.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..212, body: [ AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 0..6, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 0..1, id: Name("x"), ctx: Store, @@ -23,6 +25,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5..6, value: Int( 1, @@ -33,12 +36,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 7..23, target: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 7..10, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 7..8, id: Name("x"), ctx: Load, @@ -47,6 +53,7 @@ Module( attr: Identifier { id: Name("y"), range: 9..10, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, @@ -54,10 +61,12 @@ Module( op: Add, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 14..23, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 15..16, value: Int( 1, @@ -66,6 +75,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 18..19, value: Int( 2, @@ -74,6 +84,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 21..22, value: Int( 3, @@ -89,12 +100,15 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 24..41, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 24..28, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -102,6 +116,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 26..27, id: Name("y"), ctx: Load, @@ -113,10 +128,12 @@ Module( op: Add, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 32..41, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 33..34, value: Int( 1, @@ -125,6 +142,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 36..37, value: Int( 2, @@ -133,6 +151,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 39..40, value: Int( 3, @@ -148,9 +167,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 86..92, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..87, id: Name("x"), ctx: Store, @@ -159,6 +180,7 @@ Module( op: Add, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 91..92, value: Int( 1, @@ -169,9 +191,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 93..99, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("x"), ctx: Store, @@ -180,6 +204,7 @@ Module( op: Sub, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 98..99, value: Int( 1, @@ -190,9 +215,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 100..106, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("x"), ctx: Store, @@ -201,6 +228,7 @@ Module( op: Mult, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 1, @@ -211,9 +239,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 107..113, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..108, id: Name("x"), ctx: Store, @@ -222,6 +252,7 @@ Module( op: Div, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 112..113, value: Int( 1, @@ -232,9 +263,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 114..121, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("x"), ctx: Store, @@ -243,6 +276,7 @@ Module( op: FloorDiv, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 120..121, value: Int( 1, @@ -253,9 +287,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 122..128, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("x"), ctx: Store, @@ -264,6 +300,7 @@ Module( op: Mod, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 127..128, value: Int( 1, @@ -274,9 +311,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 129..136, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..130, id: Name("x"), ctx: Store, @@ -285,6 +324,7 @@ Module( op: Pow, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 135..136, value: Int( 1, @@ -295,9 +335,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 137..143, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 137..138, id: Name("x"), ctx: Store, @@ -306,6 +348,7 @@ Module( op: BitAnd, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 142..143, value: Int( 1, @@ -316,9 +359,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 144..150, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 144..145, id: Name("x"), ctx: Store, @@ -327,6 +372,7 @@ Module( op: BitOr, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 149..150, value: Int( 1, @@ -337,9 +383,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 151..157, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("x"), ctx: Store, @@ -348,6 +396,7 @@ Module( op: BitXor, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 156..157, value: Int( 1, @@ -358,9 +407,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 158..165, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 158..159, id: Name("x"), ctx: Store, @@ -369,6 +420,7 @@ Module( op: LShift, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 164..165, value: Int( 1, @@ -379,9 +431,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 166..173, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 166..167, id: Name("x"), ctx: Store, @@ -390,6 +444,7 @@ Module( op: RShift, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 172..173, value: Int( 1, @@ -400,9 +455,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 174..180, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 174..175, id: Name("x"), ctx: Store, @@ -411,6 +468,7 @@ Module( op: MatMult, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 179..180, value: Int( 1, @@ -421,9 +479,11 @@ Module( ), AugAssign( StmtAugAssign { + node_index: AtomicNodeIndex(..), range: 190..212, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 190..191, id: Name("a"), ctx: Store, @@ -432,12 +492,15 @@ Module( op: FloorDiv, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 196..212, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 197..202, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 197..198, id: Name("a"), ctx: Load, @@ -446,6 +509,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 201..202, id: Name("b"), ctx: Load, @@ -456,9 +520,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 206..212, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 206..207, id: Name("c"), ctx: Load, @@ -467,6 +533,7 @@ Module( op: Pow, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 211..212, value: Int( 2, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__class.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__class.py.snap index 8a6c4d8e8764b2..9882e263715924 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__class.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__class.py.snap @@ -1,31 +1,35 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/class.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..1023, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..19, decorator_list: [], name: Identifier { id: Name("Test"), range: 6..10, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 16..19, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 16..19, }, ), @@ -36,16 +40,19 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 22..80, decorator_list: [], name: Identifier { id: Name("Test"), range: 28..32, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 32..34, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -53,25 +60,33 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..80, is_async: false, decorator_list: [], name: Identifier { id: Name("__init__"), range: 48..56, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 56..62, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 57..61, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 57..61, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("self"), range: 57..61, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -86,6 +101,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 76..80, }, ), @@ -97,22 +113,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 83..116, decorator_list: [], name: Identifier { id: Name("Test"), range: 89..93, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 93..107, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 99..101, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..101, id: Name("A"), ctx: Load, @@ -125,14 +146,17 @@ Module( keywords: [ Keyword { range: 94..97, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("a"), range: 94..95, + node_index: AtomicNodeIndex(..), }, ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 96..97, value: Int( 1, @@ -142,9 +166,11 @@ Module( }, Keyword { range: 103..106, + node_index: AtomicNodeIndex(..), arg: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("k"), ctx: Load, @@ -157,9 +183,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 113..116, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 113..116, }, ), @@ -170,27 +198,34 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 119..168, decorator_list: [], name: Identifier { id: Name("Test"), range: 125..129, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 135..168, is_async: false, decorator_list: [], name: Identifier { id: Name("method"), range: 139..145, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 145..147, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -201,14 +236,17 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 157..168, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 157..161, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..158, id: Name("a"), ctx: Store, @@ -216,6 +254,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("b"), ctx: Store, @@ -229,6 +268,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 164..168, id: Name("data"), ctx: Load, @@ -244,19 +284,23 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 171..289, decorator_list: [], name: Identifier { id: Name("Test"), range: 177..181, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 181..187, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 182..183, id: Name("A"), ctx: Load, @@ -264,6 +308,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 185..186, id: Name("B"), ctx: Load, @@ -276,25 +321,33 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 193..225, is_async: false, decorator_list: [], name: Identifier { id: Name("__init__"), range: 197..205, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 205..211, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 206..210, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 206..210, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("self"), range: 206..210, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -309,6 +362,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 221..225, }, ), @@ -317,25 +371,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 231..289, is_async: false, decorator_list: [], name: Identifier { id: Name("method_with_default"), range: 235..254, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 254..275, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 255..259, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 255..259, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("self"), range: 255..259, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -343,22 +405,27 @@ Module( }, ParameterWithDefault { range: 261..274, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 261..264, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 261..264, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 265..274, value: StringLiteralValue { inner: Single( StringLiteral { range: 265..274, + node_index: AtomicNodeIndex(..), value: "default", flags: StringLiteralFlags { quote_style: Single, @@ -381,6 +448,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 285..289, }, ), @@ -392,22 +460,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 331..351, decorator_list: [], name: Identifier { id: Name("Test"), range: 337..341, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 341..344, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 342..343, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 342..343, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -419,6 +492,7 @@ Module( arguments: Some( Arguments { range: 344..346, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -426,9 +500,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 348..351, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 348..351, }, ), @@ -439,27 +515,33 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 376..402, decorator_list: [], name: Identifier { id: Name("Test"), range: 382..386, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 386..395, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 387..394, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 387..388, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 391..394, id: Name("str"), ctx: Load, @@ -474,6 +556,7 @@ Module( arguments: Some( Arguments { range: 395..397, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -481,9 +564,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 399..402, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 399..402, }, ), @@ -494,26 +579,32 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 425..450, decorator_list: [], name: Identifier { id: Name("Test"), range: 431..435, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 435..443, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 436..442, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 436..437, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 439..442, id: Name("str"), ctx: Load, @@ -529,6 +620,7 @@ Module( arguments: Some( Arguments { range: 443..445, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -536,9 +628,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 447..450, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 447..450, }, ), @@ -549,29 +643,36 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 485..522, decorator_list: [], name: Identifier { id: Name("Test"), range: 491..495, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 495..515, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 496..514, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 496..497, + node_index: AtomicNodeIndex(..), }, bound: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 499..508, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 499..502, id: Name("int"), ctx: Load, @@ -580,6 +681,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 505..508, id: Name("str"), ctx: Load, @@ -591,6 +693,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 511..514, id: Name("int"), ctx: Load, @@ -605,6 +708,7 @@ Module( arguments: Some( Arguments { range: 515..517, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -612,9 +716,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 519..522, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 519..522, }, ), @@ -625,30 +731,37 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 551..585, decorator_list: [], name: Identifier { id: Name("Test"), range: 557..561, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 561..578, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 562..577, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 562..563, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 565..577, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 566..569, id: Name("str"), ctx: Load, @@ -656,6 +769,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 571..576, id: Name("bytes"), ctx: Load, @@ -676,6 +790,7 @@ Module( arguments: Some( Arguments { range: 578..580, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -683,9 +798,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 582..585, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 582..585, }, ), @@ -696,22 +813,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 606..629, decorator_list: [], name: Identifier { id: Name("Test"), range: 612..616, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 616..622, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 617..618, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 617..618, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -720,9 +842,11 @@ Module( TypeVar( TypeParamTypeVar { range: 620..621, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 620..621, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -734,6 +858,7 @@ Module( arguments: Some( Arguments { range: 622..624, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -741,9 +866,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 626..629, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 626..629, }, ), @@ -754,22 +881,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 648..672, decorator_list: [], name: Identifier { id: Name("Test"), range: 654..658, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 658..665, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 659..660, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 659..660, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -778,9 +910,11 @@ Module( TypeVar( TypeParamTypeVar { range: 662..663, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 662..663, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -792,6 +926,7 @@ Module( arguments: Some( Arguments { range: 665..667, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -799,9 +934,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 669..672, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 669..672, }, ), @@ -812,22 +949,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 689..711, decorator_list: [], name: Identifier { id: Name("Test"), range: 695..699, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 699..704, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 700..703, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 701..703, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -838,6 +980,7 @@ Module( arguments: Some( Arguments { range: 704..706, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -845,9 +988,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 708..711, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 708..711, }, ), @@ -858,29 +1003,36 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 741..789, decorator_list: [], name: Identifier { id: Name("Test"), range: 747..751, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 751..782, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 752..781, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 753..755, + node_index: AtomicNodeIndex(..), }, default: Some( Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 758..781, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 758..764, id: Name("Unpack"), ctx: Load, @@ -888,9 +1040,11 @@ Module( ), slice: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 765..780, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 765..770, id: Name("tuple"), ctx: Load, @@ -898,10 +1052,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 771..779, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 771..774, id: Name("int"), ctx: Load, @@ -909,6 +1065,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 776..779, id: Name("str"), ctx: Load, @@ -934,6 +1091,7 @@ Module( arguments: Some( Arguments { range: 782..784, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -941,9 +1099,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 786..789, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 786..789, }, ), @@ -954,32 +1114,40 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 827..868, decorator_list: [], name: Identifier { id: Name("Test"), range: 833..837, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 837..861, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 838..860, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 839..841, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 844..860, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 845..860, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 845..850, id: Name("tuple"), ctx: Load, @@ -987,10 +1155,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 851..859, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 851..854, id: Name("int"), ctx: Load, @@ -998,6 +1168,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 856..859, id: Name("str"), ctx: Load, @@ -1023,6 +1194,7 @@ Module( arguments: Some( Arguments { range: 861..863, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1030,9 +1202,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 865..868, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 865..868, }, ), @@ -1043,22 +1217,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 882..904, decorator_list: [], name: Identifier { id: Name("Test"), range: 888..892, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 892..897, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 893..896, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 895..896, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -1069,6 +1248,7 @@ Module( arguments: Some( Arguments { range: 897..899, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1076,9 +1256,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 901..904, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 901..904, }, ), @@ -1089,30 +1271,37 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 931..966, decorator_list: [], name: Identifier { id: Name("Test"), range: 937..941, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 941..959, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 942..958, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 944..945, + node_index: AtomicNodeIndex(..), }, default: Some( List( ExprList { + node_index: AtomicNodeIndex(..), range: 948..958, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 949..952, id: Name("int"), ctx: Load, @@ -1120,6 +1309,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 954..957, id: Name("str"), ctx: Load, @@ -1138,6 +1328,7 @@ Module( arguments: Some( Arguments { range: 959..961, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1145,9 +1336,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 963..966, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 963..966, }, ), @@ -1158,22 +1351,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 982..1022, decorator_list: [], name: Identifier { id: Name("Test"), range: 988..992, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 992..1012, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 993..994, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("X"), range: 993..994, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -1182,13 +1380,16 @@ Module( TypeVar( TypeParamTypeVar { range: 996..1002, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Y"), range: 996..997, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 999..1002, id: Name("str"), ctx: Load, @@ -1201,9 +1402,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 1004..1006, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 1005..1006, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -1211,9 +1414,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 1008..1011, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 1010..1011, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -1224,6 +1429,7 @@ Module( arguments: Some( Arguments { range: 1012..1014, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1231,6 +1437,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1018..1022, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__delete.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__delete.py.snap index fbb6d09bf2b0f8..bc20d70fae3c5b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__delete.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__delete.py.snap @@ -1,21 +1,23 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/delete.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..122, body: [ Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 0..5, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..5, id: Name("x"), ctx: Del, @@ -26,10 +28,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 6..13, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 11..12, id: Name("x"), ctx: Del, @@ -40,10 +44,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 14..23, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("a"), ctx: Del, @@ -51,6 +57,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("b"), ctx: Del, @@ -61,10 +68,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 24..40, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("a"), ctx: Del, @@ -72,10 +81,12 @@ Module( ), Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 31..37, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("b"), ctx: Del, @@ -83,6 +94,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 35..36, id: Name("c"), ctx: Del, @@ -95,6 +107,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 39..40, id: Name("d"), ctx: Del, @@ -105,14 +118,17 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 41..51, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 45..51, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("a"), ctx: Del, @@ -120,6 +136,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("b"), ctx: Del, @@ -134,14 +151,17 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 52..70, targets: [ List( ExprList { + node_index: AtomicNodeIndex(..), range: 56..70, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 57..58, id: Name("a"), ctx: Del, @@ -149,10 +169,12 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 60..66, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..62, id: Name("b"), ctx: Del, @@ -160,6 +182,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 64..65, id: Name("c"), ctx: Del, @@ -171,6 +194,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("d"), ctx: Del, @@ -185,13 +209,16 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 71..78, targets: [ Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 75..78, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("x"), ctx: Load, @@ -200,6 +227,7 @@ Module( attr: Identifier { id: Name("y"), range: 77..78, + node_index: AtomicNodeIndex(..), }, ctx: Del, }, @@ -209,13 +237,16 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 79..87, targets: [ Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 83..87, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 83..84, id: Name("x"), ctx: Load, @@ -223,6 +254,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 85..86, id: Name("y"), ctx: Load, @@ -236,14 +268,17 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 88..121, targets: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 92..121, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("x"), ctx: Del, @@ -251,9 +286,11 @@ Module( ), Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 105..108, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 105..106, id: Name("x"), ctx: Load, @@ -262,15 +299,18 @@ Module( attr: Identifier { id: Name("y"), range: 107..108, + node_index: AtomicNodeIndex(..), }, ctx: Del, }, ), Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 114..118, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("x"), ctx: Load, @@ -278,6 +318,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 116..117, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__for.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__for.py.snap index fa69892a4a0110..a003c4896d7162 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__for.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__for.py.snap @@ -7,14 +7,17 @@ input_file: crates/ruff_python_parser/resources/valid/statement/for.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..523, body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 0..28, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4..10, id: Name("target"), ctx: Store, @@ -22,6 +25,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..18, id: Name("iter"), ctx: Load, @@ -30,6 +34,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 24..28, }, ), @@ -39,10 +44,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 30..63, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..40, id: Name("target"), ctx: Store, @@ -50,10 +57,12 @@ Module( ), iter: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 44..53, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 1, @@ -62,6 +71,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 48..49, value: Int( 2, @@ -70,6 +80,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 3, @@ -84,6 +95,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 59..63, }, ), @@ -93,13 +105,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 65..100, is_async: false, target: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 69..80, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..75, id: Name("target"), ctx: Load, @@ -108,15 +123,18 @@ Module( attr: Identifier { id: Name("attr"), range: 76..80, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 84..90, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..88, id: Name("call"), ctx: Load, @@ -124,6 +142,7 @@ Module( ), arguments: Arguments { range: 88..90, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -132,6 +151,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 96..100, }, ), @@ -141,13 +161,16 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 102..135, is_async: false, target: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 106..115, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..112, id: Name("target"), ctx: Load, @@ -155,6 +178,7 @@ Module( ), slice: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 113..114, value: Int( 0, @@ -166,9 +190,11 @@ Module( ), iter: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 119..125, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 119..120, id: Name("x"), ctx: Load, @@ -177,6 +203,7 @@ Module( attr: Identifier { id: Name("attr"), range: 121..125, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -184,6 +211,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 131..135, }, ), @@ -193,10 +221,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 137..167, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 141..147, id: Name("target"), ctx: Store, @@ -204,9 +234,11 @@ Module( ), iter: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 151..157, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("x"), ctx: Load, @@ -218,6 +250,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("y"), ctx: Load, @@ -229,6 +262,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 163..167, }, ), @@ -238,10 +272,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 169..200, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 173..179, id: Name("target"), ctx: Store, @@ -249,11 +285,13 @@ Module( ), iter: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 183..190, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 183..184, id: Name("a"), ctx: Load, @@ -261,6 +299,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..190, id: Name("b"), ctx: Load, @@ -272,6 +311,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 196..200, }, ), @@ -281,14 +321,17 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 202..232, is_async: false, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 206..214, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 206..207, id: Name("a"), ctx: Store, @@ -296,6 +339,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 209..210, id: Name("b"), ctx: Store, @@ -303,6 +347,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 212..213, id: Name("c"), ctx: Store, @@ -315,6 +360,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 218..222, id: Name("iter"), ctx: Load, @@ -323,6 +369,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 228..232, }, ), @@ -332,14 +379,17 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 234..262, is_async: false, target: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 238..244, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 239..240, id: Name("a"), ctx: Store, @@ -347,6 +397,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 242..243, id: Name("b"), ctx: Store, @@ -359,6 +410,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 248..252, id: Name("iter"), ctx: Load, @@ -367,6 +419,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 258..262, }, ), @@ -376,10 +429,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 264..294, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 268..274, id: Name("target"), ctx: Store, @@ -387,10 +442,12 @@ Module( ), iter: List( ExprList { + node_index: AtomicNodeIndex(..), range: 278..284, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 279..280, value: Int( 1, @@ -399,6 +456,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 282..283, value: Int( 2, @@ -412,6 +470,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 290..294, }, ), @@ -421,10 +480,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 296..322, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 300..306, id: Name("target"), ctx: Store, @@ -432,9 +493,11 @@ Module( ), iter: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 310..317, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 316..317, id: Name("x"), ctx: Load, @@ -445,9 +508,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 319..322, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 319..322, }, ), @@ -459,10 +524,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 323..353, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..333, id: Name("target"), ctx: Store, @@ -470,19 +537,26 @@ Module( ), iter: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 337..348, parameters: Some( Parameters { range: 344..345, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 344..345, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 344..345, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 344..345, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -496,6 +570,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 347..348, id: Name("x"), ctx: Load, @@ -506,9 +581,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 350..353, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 350..353, }, ), @@ -520,10 +597,12 @@ Module( ), For( StmtFor { + node_index: AtomicNodeIndex(..), range: 354..389, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 358..364, id: Name("target"), ctx: Store, @@ -531,15 +610,18 @@ Module( ), iter: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 368..384, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 373..377, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 368..369, id: Name("x"), ctx: Load, @@ -547,6 +629,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 383..384, id: Name("y"), ctx: Load, @@ -557,9 +640,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 386..389, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 386..389, }, ), @@ -571,9 +656,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 391..522, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 394..395, id: Name("x"), ctx: Load, @@ -582,10 +669,12 @@ Module( body: [ For( StmtFor { + node_index: AtomicNodeIndex(..), range: 401..433, is_async: false, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 405..411, id: Name("target"), ctx: Store, @@ -593,6 +682,7 @@ Module( ), iter: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 415..419, id: Name("iter"), ctx: Load, @@ -601,6 +691,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 429..433, }, ), @@ -612,10 +703,12 @@ Module( elif_else_clauses: [ ElifElseClause { range: 508..522, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 518..522, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__from_import.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__from_import.py.snap index 3fa62287490398..d6f6e5015867e6 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__from_import.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__from_import.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/from_import.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..259, body: [ ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 0..15, module: Some( Identifier { id: Name("a"), range: 5..6, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 14..15, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 14..15, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -34,14 +38,17 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 27..42, module: None, names: [ Alias { range: 41..42, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 41..42, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -51,37 +58,45 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 43..85, module: Some( Identifier { id: Name("foo.bar"), range: 48..55, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 63..71, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("baz"), range: 63..66, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("b"), range: 70..71, + node_index: AtomicNodeIndex(..), }, ), }, Alias { range: 73..85, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("FooBar"), range: 73..79, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("fb"), range: 83..85, + node_index: AtomicNodeIndex(..), }, ), }, @@ -91,19 +106,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 86..102, module: Some( Identifier { id: Name("a"), range: 92..93, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 101..102, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 101..102, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -113,14 +132,17 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 103..120, module: None, names: [ Alias { range: 119..120, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 119..120, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -130,14 +152,17 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 121..161, module: None, names: [ Alias { range: 160..161, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 160..161, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -147,19 +172,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 162..207, module: Some( Identifier { id: Name("a.b.c"), range: 193..198, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 206..207, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 206..207, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -169,40 +198,49 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 208..242, module: Some( Identifier { id: Name("module"), range: 213..219, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 228..229, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 228..229, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 231..237, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 231..232, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("B"), range: 236..237, + node_index: AtomicNodeIndex(..), }, ), }, Alias { range: 239..240, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 239..240, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -212,19 +250,23 @@ Module( ), ImportFrom( StmtImportFrom { + node_index: AtomicNodeIndex(..), range: 243..258, module: Some( Identifier { id: Name("a"), range: 248..249, + node_index: AtomicNodeIndex(..), }, ), names: [ Alias { range: 257..258, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("*"), range: 257..258, + node_index: AtomicNodeIndex(..), }, asname: None, }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__function.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__function.py.snap index 0dbedd8e92761f..d2b7caef141217 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__function.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__function.py.snap @@ -1,27 +1,32 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/function.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..2399, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 0..29, is_async: false, decorator_list: [], name: Identifier { id: Name("no_parameters"), range: 4..17, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 17..19, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -32,6 +37,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 25..29, }, ), @@ -40,25 +46,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 32..76, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_parameters"), range: 36..57, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 57..66, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 58..59, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 58..59, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 58..59, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -66,11 +80,14 @@ Module( }, ParameterWithDefault { range: 61..62, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 61..62, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 61..62, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -78,11 +95,14 @@ Module( }, ParameterWithDefault { range: 64..65, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 64..65, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 64..65, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -97,6 +117,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 72..76, }, ), @@ -105,25 +126,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 79..149, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_parameters_with_default_values"), range: 83..124, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 124..139, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 125..126, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 125..126, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 125..126, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -131,17 +160,21 @@ Module( }, ParameterWithDefault { range: 128..132, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 128..129, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 128..129, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 130..132, value: Int( 20, @@ -152,17 +185,21 @@ Module( }, ParameterWithDefault { range: 134..138, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 134..135, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 134..135, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 136..138, value: Int( 30, @@ -180,6 +217,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 145..149, }, ), @@ -188,24 +226,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 152..226, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_parameters_with_default_values2"), range: 156..198, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 198..216, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 199..200, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 199..200, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 199..200, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -213,17 +259,21 @@ Module( }, ParameterWithDefault { range: 202..206, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 202..203, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 202..203, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 204..206, value: Int( 20, @@ -236,17 +286,21 @@ Module( args: [ ParameterWithDefault { range: 211..215, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 211..212, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 211..212, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..215, value: Int( 30, @@ -264,6 +318,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 222..226, }, ), @@ -272,24 +327,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 229..296, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_only_and_positional_parameters"), range: 233..274, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 274..286, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 275..276, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 275..276, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 275..276, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -299,11 +362,14 @@ Module( args: [ ParameterWithDefault { range: 281..282, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 281..282, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 281..282, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -311,11 +377,14 @@ Module( }, ParameterWithDefault { range: 284..285, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 284..285, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 284..285, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -330,6 +399,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 292..296, }, ), @@ -338,24 +408,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 299..393, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_args_with_defaults_and_varargs_and_kwargs"), range: 303..348, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 348..383, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 349..350, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 349..350, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 349..350, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -363,17 +441,21 @@ Module( }, ParameterWithDefault { range: 352..356, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 352..353, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 352..353, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 354..356, value: Int( 20, @@ -386,17 +468,21 @@ Module( args: [ ParameterWithDefault { range: 361..365, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 361..362, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 361..362, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 363..365, value: Int( 30, @@ -409,9 +495,11 @@ Module( vararg: Some( Parameter { range: 367..372, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 368..372, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -420,9 +508,11 @@ Module( kwarg: Some( Parameter { range: 374..382, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 376..382, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -432,6 +522,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 389..393, }, ), @@ -440,27 +531,35 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 396..445, is_async: false, decorator_list: [], name: Identifier { id: Name("keyword_only_parameters"), range: 400..423, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 423..435, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 427..428, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 427..428, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 427..428, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -468,11 +567,14 @@ Module( }, ParameterWithDefault { range: 430..431, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 430..431, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 430..431, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -480,11 +582,14 @@ Module( }, ParameterWithDefault { range: 433..434, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 433..434, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 433..434, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -497,6 +602,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 441..445, }, ), @@ -505,27 +611,35 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 448..517, is_async: false, decorator_list: [], name: Identifier { id: Name("keyword_only_parameters_with_defaults"), range: 452..489, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 489..507, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 493..494, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 493..494, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 493..494, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -533,17 +647,21 @@ Module( }, ParameterWithDefault { range: 496..500, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 496..497, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 496..497, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 498..500, value: Int( 20, @@ -554,17 +672,21 @@ Module( }, ParameterWithDefault { range: 502..506, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 502..503, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 502..503, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 504..506, value: Int( 30, @@ -580,6 +702,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 513..517, }, ), @@ -588,24 +711,31 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 520..594, is_async: false, decorator_list: [], name: Identifier { id: Name("kw_only_args_with_defaults_and_varargs"), range: 524..562, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 562..584, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 563..568, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 564..568, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -613,11 +743,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 570..571, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 570..571, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 570..571, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -625,17 +758,21 @@ Module( }, ParameterWithDefault { range: 573..577, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 573..574, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 573..574, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 575..577, value: Int( 20, @@ -646,17 +783,21 @@ Module( }, ParameterWithDefault { range: 579..583, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 579..580, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 579..580, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 581..583, value: Int( 30, @@ -672,6 +813,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 590..594, }, ), @@ -680,27 +822,35 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 597..676, is_async: false, decorator_list: [], name: Identifier { id: Name("kw_only_args_with_defaults_and_kwargs"), range: 601..638, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 638..666, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, kwonlyargs: [ ParameterWithDefault { range: 642..643, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 642..643, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 642..643, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -708,17 +858,21 @@ Module( }, ParameterWithDefault { range: 645..649, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 645..646, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 645..646, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 647..649, value: Int( 20, @@ -729,17 +883,21 @@ Module( }, ParameterWithDefault { range: 651..655, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 651..652, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 651..652, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 653..655, value: Int( 30, @@ -752,9 +910,11 @@ Module( kwarg: Some( Parameter { range: 657..665, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 659..665, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -764,6 +924,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 672..676, }, ), @@ -772,24 +933,31 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 679..774, is_async: false, decorator_list: [], name: Identifier { id: Name("kw_only_args_with_defaults_and_varargs_and_kwargs"), range: 683..732, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 732..764, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 733..738, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 734..738, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -797,11 +965,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 740..741, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 740..741, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 740..741, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -809,17 +980,21 @@ Module( }, ParameterWithDefault { range: 743..747, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 743..744, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 743..744, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 745..747, value: Int( 20, @@ -830,17 +1005,21 @@ Module( }, ParameterWithDefault { range: 749..753, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 749..750, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 749..750, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 751..753, value: Int( 30, @@ -853,9 +1032,11 @@ Module( kwarg: Some( Parameter { range: 755..763, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 757..763, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -865,6 +1046,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 770..774, }, ), @@ -873,24 +1055,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 777..835, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_and_kw_only_args"), range: 781..801, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 801..825, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 802..803, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 802..803, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 802..803, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -898,11 +1088,14 @@ Module( }, ParameterWithDefault { range: 805..806, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 805..806, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 805..806, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -912,11 +1105,14 @@ Module( args: [ ParameterWithDefault { range: 811..812, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 811..812, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 811..812, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -927,11 +1123,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 817..818, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 817..818, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 817..818, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -939,11 +1138,14 @@ Module( }, ParameterWithDefault { range: 820..821, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 820..821, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 820..821, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -951,11 +1153,14 @@ Module( }, ParameterWithDefault { range: 823..824, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 823..824, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 823..824, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -968,6 +1173,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 831..835, }, ), @@ -976,24 +1182,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 838..916, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_and_kw_only_args_with_defaults"), range: 842..876, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 876..906, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 877..878, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 877..878, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 877..878, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1001,11 +1215,14 @@ Module( }, ParameterWithDefault { range: 880..881, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 880..881, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 880..881, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1015,11 +1232,14 @@ Module( args: [ ParameterWithDefault { range: 886..887, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 886..887, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 886..887, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1030,11 +1250,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 892..893, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 892..893, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 892..893, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1042,17 +1265,21 @@ Module( }, ParameterWithDefault { range: 895..899, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 895..896, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 895..896, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 897..899, value: Int( 20, @@ -1063,17 +1290,21 @@ Module( }, ParameterWithDefault { range: 901..905, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 901..902, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 901..902, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 903..905, value: Int( 30, @@ -1089,6 +1320,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 912..916, }, ), @@ -1097,24 +1329,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 919..1013, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_and_kw_only_args_with_defaults_and_varargs"), range: 923..969, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 969..1003, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 970..971, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 970..971, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 970..971, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1122,11 +1362,14 @@ Module( }, ParameterWithDefault { range: 973..974, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 973..974, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 973..974, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1136,11 +1379,14 @@ Module( args: [ ParameterWithDefault { range: 979..980, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 979..980, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 979..980, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1150,9 +1396,11 @@ Module( vararg: Some( Parameter { range: 982..987, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 983..987, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1160,11 +1408,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 989..990, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 989..990, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 989..990, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1172,17 +1423,21 @@ Module( }, ParameterWithDefault { range: 992..996, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 992..993, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 992..993, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 994..996, value: Int( 20, @@ -1193,17 +1448,21 @@ Module( }, ParameterWithDefault { range: 998..1002, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 998..999, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 998..999, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1000..1002, value: Int( 30, @@ -1219,6 +1478,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1009..1013, }, ), @@ -1227,24 +1487,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1016..1121, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_and_kw_only_args_with_defaults_and_kwargs"), range: 1020..1065, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1065..1111, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 1071..1072, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1071..1072, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1071..1072, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1252,11 +1520,14 @@ Module( }, ParameterWithDefault { range: 1074..1075, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1074..1075, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1074..1075, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1266,11 +1537,14 @@ Module( args: [ ParameterWithDefault { range: 1080..1081, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1080..1081, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1080..1081, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1281,11 +1555,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1086..1087, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1086..1087, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1086..1087, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1293,17 +1570,21 @@ Module( }, ParameterWithDefault { range: 1089..1093, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1089..1090, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1089..1090, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1091..1093, value: Int( 20, @@ -1314,17 +1595,21 @@ Module( }, ParameterWithDefault { range: 1095..1099, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1095..1096, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1095..1096, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1097..1099, value: Int( 30, @@ -1337,9 +1622,11 @@ Module( kwarg: Some( Parameter { range: 1101..1109, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 1103..1109, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1349,6 +1636,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1117..1121, }, ), @@ -1357,24 +1645,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1124..1245, is_async: false, decorator_list: [], name: Identifier { id: Name("pos_and_kw_only_args_with_defaults_and_varargs_and_kwargs"), range: 1128..1185, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1185..1235, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 1191..1192, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1191..1192, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1191..1192, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1382,11 +1678,14 @@ Module( }, ParameterWithDefault { range: 1194..1195, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1194..1195, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1194..1195, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1396,11 +1695,14 @@ Module( args: [ ParameterWithDefault { range: 1200..1201, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1200..1201, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1200..1201, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1410,9 +1712,11 @@ Module( vararg: Some( Parameter { range: 1203..1208, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 1204..1208, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1420,11 +1724,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1210..1211, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1210..1211, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1210..1211, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1432,17 +1739,21 @@ Module( }, ParameterWithDefault { range: 1213..1217, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1213..1214, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1213..1214, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1215..1217, value: Int( 20, @@ -1453,17 +1764,21 @@ Module( }, ParameterWithDefault { range: 1219..1223, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1219..1220, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1219..1220, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1221..1223, value: Int( 30, @@ -1476,9 +1791,11 @@ Module( kwarg: Some( Parameter { range: 1225..1233, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 1227..1233, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1488,6 +1805,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1241..1245, }, ), @@ -1496,25 +1814,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1248..1316, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_and_keyword_parameters"), range: 1252..1285, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1285..1306, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1286..1287, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1286..1287, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1286..1287, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1522,11 +1848,14 @@ Module( }, ParameterWithDefault { range: 1289..1290, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1289..1290, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1289..1290, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1534,11 +1863,14 @@ Module( }, ParameterWithDefault { range: 1292..1293, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1292..1293, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1292..1293, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1549,11 +1881,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1298..1299, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1298..1299, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1298..1299, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1561,11 +1896,14 @@ Module( }, ParameterWithDefault { range: 1301..1302, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1301..1302, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1301..1302, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1573,11 +1911,14 @@ Module( }, ParameterWithDefault { range: 1304..1305, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1304..1305, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1304..1305, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1590,6 +1931,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1312..1316, }, ), @@ -1598,25 +1940,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1319..1407, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_and_keyword_parameters_with_defaults"), range: 1323..1370, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1370..1397, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1371..1372, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1371..1372, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1371..1372, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1624,11 +1974,14 @@ Module( }, ParameterWithDefault { range: 1374..1375, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1374..1375, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1374..1375, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1636,11 +1989,14 @@ Module( }, ParameterWithDefault { range: 1377..1378, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1377..1378, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1377..1378, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1651,11 +2007,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1383..1384, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1383..1384, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1383..1384, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1663,17 +2022,21 @@ Module( }, ParameterWithDefault { range: 1386..1390, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1386..1387, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1386..1387, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1388..1390, value: Int( 20, @@ -1684,17 +2047,21 @@ Module( }, ParameterWithDefault { range: 1392..1396, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1392..1393, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1392..1393, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1394..1396, value: Int( 30, @@ -1710,6 +2077,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1403..1407, }, ), @@ -1718,25 +2086,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1410..1520, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_and_keyword_parameters_with_defaults_and_varargs"), range: 1414..1473, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1473..1510, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1479..1480, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1479..1480, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1479..1480, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1744,11 +2120,14 @@ Module( }, ParameterWithDefault { range: 1482..1483, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1482..1483, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1482..1483, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1756,11 +2135,14 @@ Module( }, ParameterWithDefault { range: 1485..1486, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1485..1486, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1485..1486, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1770,9 +2152,11 @@ Module( vararg: Some( Parameter { range: 1488..1493, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 1489..1493, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1780,11 +2164,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1495..1496, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1495..1496, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1495..1496, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1792,17 +2179,21 @@ Module( }, ParameterWithDefault { range: 1498..1502, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1498..1499, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1498..1499, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1500..1502, value: Int( 20, @@ -1813,17 +2204,21 @@ Module( }, ParameterWithDefault { range: 1504..1508, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1504..1505, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1504..1505, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1506..1508, value: Int( 30, @@ -1839,6 +2234,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1516..1520, }, ), @@ -1847,25 +2243,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1523..1654, is_async: false, decorator_list: [], name: Identifier { id: Name("positional_and_keyword_parameters_with_defaults_and_varargs_and_kwargs"), range: 1527..1597, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1597..1644, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1603..1604, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1603..1604, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1603..1604, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1873,11 +2277,14 @@ Module( }, ParameterWithDefault { range: 1606..1607, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1606..1607, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 1606..1607, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1885,11 +2292,14 @@ Module( }, ParameterWithDefault { range: 1609..1610, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1609..1610, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 1609..1610, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1899,9 +2309,11 @@ Module( vararg: Some( Parameter { range: 1612..1617, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 1613..1617, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1909,11 +2321,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 1619..1620, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1619..1620, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("d"), range: 1619..1620, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1921,17 +2336,21 @@ Module( }, ParameterWithDefault { range: 1622..1626, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1622..1623, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("e"), range: 1622..1623, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1624..1626, value: Int( 20, @@ -1942,17 +2361,21 @@ Module( }, ParameterWithDefault { range: 1628..1632, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1628..1629, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("f"), range: 1628..1629, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1630..1632, value: Int( 30, @@ -1965,9 +2388,11 @@ Module( kwarg: Some( Parameter { range: 1634..1642, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 1636..1642, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1977,6 +2402,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1650..1654, }, ), @@ -1985,23 +2411,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1703..1735, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1707..1711, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1711..1714, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1712..1713, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1712..1713, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -2012,19 +2443,26 @@ Module( ), parameters: Parameters { range: 1714..1720, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1715..1719, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1715..1719, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1715..1716, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1718..1719, id: Name("T"), ctx: Load, @@ -2042,6 +2480,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1724..1725, id: Name("T"), ctx: Load, @@ -2051,6 +2490,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1731..1735, }, ), @@ -2059,27 +2499,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1738..1775, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1742..1746, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1746..1754, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1747..1753, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1747..1748, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1750..1753, id: Name("str"), ctx: Load, @@ -2094,19 +2540,26 @@ Module( ), parameters: Parameters { range: 1754..1760, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1755..1759, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1755..1759, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1755..1756, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1758..1759, id: Name("T"), ctx: Load, @@ -2124,6 +2577,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1764..1765, id: Name("T"), ctx: Load, @@ -2133,6 +2587,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1771..1775, }, ), @@ -2141,31 +2596,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1778..1824, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1782..1786, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1786..1803, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1787..1802, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1787..1788, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1790..1802, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1791..1794, id: Name("str"), ctx: Load, @@ -2173,6 +2635,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1796..1801, id: Name("bytes"), ctx: Load, @@ -2192,19 +2655,26 @@ Module( ), parameters: Parameters { range: 1803..1809, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1804..1808, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1804..1808, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1804..1805, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1807..1808, id: Name("T"), ctx: Load, @@ -2222,6 +2692,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1813..1814, id: Name("T"), ctx: Load, @@ -2231,6 +2702,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1820..1824, }, ), @@ -2239,23 +2711,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1827..1873, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1831..1835, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1835..1840, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 1836..1839, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 1837..1839, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -2265,21 +2742,28 @@ Module( ), parameters: Parameters { range: 1840..1849, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 1841..1848, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 1842..1843, + node_index: AtomicNodeIndex(..), }, annotation: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1845..1848, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1846..1848, id: Name("Ts"), ctx: Load, @@ -2297,9 +2781,11 @@ Module( returns: Some( Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1853..1863, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1853..1858, id: Name("Tuple"), ctx: Load, @@ -2307,13 +2793,16 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1859..1862, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1859..1862, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1860..1862, id: Name("Ts"), ctx: Load, @@ -2334,6 +2823,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1869..1873, }, ), @@ -2342,23 +2832,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1876..1934, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1880..1884, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1884..1889, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 1885..1888, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 1887..1888, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -2368,21 +2863,28 @@ Module( ), parameters: Parameters { range: 1889..1924, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 1890..1903, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 1891..1895, + node_index: AtomicNodeIndex(..), }, annotation: Some( Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1897..1903, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1897..1898, id: Name("P"), ctx: Load, @@ -2391,6 +2893,7 @@ Module( attr: Identifier { id: Name("args"), range: 1899..1903, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2402,16 +2905,20 @@ Module( kwarg: Some( Parameter { range: 1905..1923, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 1907..1913, + node_index: AtomicNodeIndex(..), }, annotation: Some( Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1915..1923, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1915..1916, id: Name("P"), ctx: Load, @@ -2420,6 +2927,7 @@ Module( attr: Identifier { id: Name("kwargs"), range: 1917..1923, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2432,6 +2940,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1930..1934, }, ), @@ -2440,23 +2949,28 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1937..1978, is_async: false, decorator_list: [], name: Identifier { id: Name("func"), range: 1941..1945, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 1945..1966, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1946..1947, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1946..1947, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -2465,13 +2979,16 @@ Module( TypeVar( TypeParamTypeVar { range: 1949..1955, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 1949..1950, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1952..1955, id: Name("str"), ctx: Load, @@ -2484,9 +3001,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 1957..1960, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 1958..1960, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -2494,9 +3013,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 1962..1965, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 1964..1965, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -2506,6 +3027,9 @@ Module( ), parameters: Parameters { range: 1966..1968, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -2516,6 +3040,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1974..1978, }, ), @@ -2524,16 +3049,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 1981..2000, is_async: false, decorator_list: [], name: Identifier { id: Name("ellipsis"), range: 1985..1993, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 1993..1995, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -2544,9 +3074,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1997..2000, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 1997..2000, }, ), @@ -2557,16 +3089,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2003..2064, is_async: false, decorator_list: [], name: Identifier { id: Name("multiple_statements"), range: 2007..2026, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2026..2028, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -2576,6 +3113,7 @@ Module( returns: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2032..2035, id: Name("int"), ctx: Load, @@ -2585,12 +3123,15 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2041..2047, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 2041..2047, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2041..2045, id: Name("call"), ctx: Load, @@ -2598,6 +3139,7 @@ Module( ), arguments: Arguments { range: 2045..2047, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -2607,14 +3149,17 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2052..2056, }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 2061..2064, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 2061..2064, }, ), @@ -2625,24 +3170,31 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2067..2091, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2071..2074, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2074..2081, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 2075..2080, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 2076..2080, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2654,6 +3206,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2087..2091, }, ), @@ -2662,16 +3215,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2094..2121, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2098..2101, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2101..2111, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -2679,9 +3237,11 @@ Module( kwarg: Some( Parameter { range: 2102..2110, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 2104..2110, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2691,6 +3251,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2117..2121, }, ), @@ -2699,24 +3260,31 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2124..2158, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2128..2131, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2131..2148, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: Some( Parameter { range: 2132..2137, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("args"), range: 2133..2137, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2725,9 +3293,11 @@ Module( kwarg: Some( Parameter { range: 2139..2147, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kwargs"), range: 2141..2147, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2737,6 +3307,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2154..2158, }, ), @@ -2745,24 +3316,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2161..2184, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2165..2168, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2168..2174, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 2169..2170, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2169..2170, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2169..2170, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2778,6 +3357,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2180..2184, }, ), @@ -2786,24 +3366,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2187..2213, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2191..2194, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2194..2203, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 2195..2196, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2195..2196, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2195..2196, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2813,11 +3401,14 @@ Module( args: [ ParameterWithDefault { range: 2201..2202, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2201..2202, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 2201..2202, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2832,6 +3423,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2209..2213, }, ), @@ -2840,30 +3432,39 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2216..2242, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2220..2223, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2223..2232, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 2224..2227, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2224..2225, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2224..2225, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2226..2227, value: Int( 1, @@ -2882,6 +3483,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2238..2242, }, ), @@ -2890,24 +3492,32 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2245..2277, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2249..2252, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2252..2267, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [ ParameterWithDefault { range: 2253..2254, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2253..2254, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2253..2254, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2915,11 +3525,14 @@ Module( }, ParameterWithDefault { range: 2256..2257, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2256..2257, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 2256..2257, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2931,11 +3544,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 2265..2266, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2265..2266, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 2265..2266, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2948,6 +3564,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2273..2277, }, ), @@ -2956,31 +3573,40 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2280..2309, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2284..2287, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2287..2299, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 2288..2292, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2288..2290, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("kw"), range: 2288..2290, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2291..2292, value: Int( 1, @@ -2994,11 +3620,14 @@ Module( kwonlyargs: [ ParameterWithDefault { range: 2297..2298, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2297..2298, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2297..2298, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -3011,6 +3640,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2305..2309, }, ), @@ -3019,29 +3649,38 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2312..2357, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2316..2319, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2319..2347, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 2320..2326, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2320..2326, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 2320..2321, + node_index: AtomicNodeIndex(..), }, annotation: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2323..2326, id: Name("int"), ctx: Load, @@ -3053,20 +3692,25 @@ Module( }, ParameterWithDefault { range: 2328..2336, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2328..2336, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("y"), range: 2328..2329, + node_index: AtomicNodeIndex(..), }, annotation: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2331..2336, value: StringLiteralValue { inner: Single( StringLiteral { range: 2331..2336, + node_index: AtomicNodeIndex(..), value: "str", flags: StringLiteralFlags { quote_style: Double, @@ -3084,18 +3728,23 @@ Module( }, ParameterWithDefault { range: 2338..2346, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2338..2346, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("z"), range: 2338..2339, + node_index: AtomicNodeIndex(..), }, annotation: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 2341..2346, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2341..2342, value: Int( 1, @@ -3105,6 +3754,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2345..2346, value: Int( 2, @@ -3126,6 +3776,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2353..2357, }, ), @@ -3134,25 +3785,33 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 2360..2398, is_async: false, decorator_list: [], name: Identifier { id: Name("foo"), range: 2364..2367, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 2367..2388, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 2368..2372, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2368..2372, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("self"), range: 2368..2372, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -3160,17 +3819,21 @@ Module( }, ParameterWithDefault { range: 2374..2377, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2374..2375, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 2374..2375, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2376..2377, value: Int( 1, @@ -3181,17 +3844,21 @@ Module( }, ParameterWithDefault { range: 2379..2382, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2379..2380, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 2379..2380, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2381..2382, value: Int( 2, @@ -3202,17 +3869,21 @@ Module( }, ParameterWithDefault { range: 2384..2387, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 2384..2385, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 2384..2385, + node_index: AtomicNodeIndex(..), }, annotation: None, }, default: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2386..2387, value: Int( 3, @@ -3230,6 +3901,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2394..2398, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__if.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__if.py.snap index 592cb88bad05e0..51edcda2945bab 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__if.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__if.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/if.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..375, body: [ If( StmtIf { + node_index: AtomicNodeIndex(..), range: 0..28, test: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3..4, value: Int( 1, @@ -24,9 +26,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 6..8, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 6..8, value: Int( 10, @@ -39,9 +43,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 9..19, + node_index: AtomicNodeIndex(..), test: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 14..15, value: Int( 2, @@ -52,9 +58,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 17..19, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 17..19, value: Int( 20, @@ -67,13 +75,16 @@ Module( }, ElifElseClause { range: 20..28, + node_index: AtomicNodeIndex(..), test: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 26..28, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 26..28, value: Int( 30, @@ -89,9 +100,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 30..52, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 33..37, value: true, }, @@ -99,9 +112,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..44, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..44, value: Int( 1, @@ -112,9 +127,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 49..52, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 49..52, }, ), @@ -126,12 +143,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 53..85, test: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 56..61, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("x"), ctx: Load, @@ -143,6 +163,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 60..61, value: Int( 1, @@ -155,9 +176,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 67..70, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 67..70, }, ), @@ -167,10 +190,12 @@ Module( elif_else_clauses: [ ElifElseClause { range: 71..85, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 81..85, }, ), @@ -181,9 +206,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 87..117, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("a"), ctx: Load, @@ -192,6 +219,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 97..101, }, ), @@ -199,9 +227,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 102..117, + node_index: AtomicNodeIndex(..), test: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 107..108, id: Name("b"), ctx: Load, @@ -211,9 +241,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 114..117, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 114..117, }, ), @@ -226,14 +258,17 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 119..203, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 122..129, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("a"), ctx: Load, @@ -241,6 +276,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..129, id: Name("b"), ctx: Load, @@ -252,9 +288,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 135..138, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 135..138, }, ), @@ -264,9 +302,11 @@ Module( elif_else_clauses: [ ElifElseClause { range: 139..157, + node_index: AtomicNodeIndex(..), test: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 144..148, value: true, }, @@ -275,9 +315,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 154..157, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 154..157, }, ), @@ -287,9 +329,11 @@ Module( }, ElifElseClause { range: 158..173, + node_index: AtomicNodeIndex(..), test: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 163..164, id: Name("c"), ctx: Load, @@ -299,9 +343,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 170..173, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 170..173, }, ), @@ -311,9 +357,11 @@ Module( }, ElifElseClause { range: 174..189, + node_index: AtomicNodeIndex(..), test: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 179..180, id: Name("d"), ctx: Load, @@ -323,9 +371,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 186..189, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 186..189, }, ), @@ -335,16 +385,20 @@ Module( }, ElifElseClause { range: 190..203, + node_index: AtomicNodeIndex(..), test: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 200..203, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 200..203, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 200..201, id: Name("f"), ctx: Load, @@ -352,6 +406,7 @@ Module( ), arguments: Arguments { range: 201..203, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -366,12 +421,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 229..260, test: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 232..238, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 232..233, id: Name("a"), ctx: Store, @@ -379,6 +437,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 237..238, id: Name("b"), ctx: Load, @@ -389,9 +448,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 240..243, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 240..243, }, ), @@ -401,12 +462,15 @@ Module( elif_else_clauses: [ ElifElseClause { range: 244..260, + node_index: AtomicNodeIndex(..), test: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 249..255, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 249..250, id: Name("a"), ctx: Store, @@ -414,6 +478,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 254..255, id: Name("b"), ctx: Load, @@ -425,9 +490,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 257..260, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 257..260, }, ), @@ -440,22 +507,30 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 261..302, test: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 264..275, parameters: Some( Parameters { range: 271..272, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 271..272, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 271..272, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 271..272, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -469,6 +544,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 274..275, id: Name("x"), ctx: Load, @@ -479,9 +555,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 277..280, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 277..280, }, ), @@ -491,22 +569,30 @@ Module( elif_else_clauses: [ ElifElseClause { range: 281..302, + node_index: AtomicNodeIndex(..), test: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 286..297, parameters: Some( Parameters { range: 293..294, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 293..294, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 293..294, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 293..294, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -520,6 +606,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 296..297, id: Name("x"), ctx: Load, @@ -531,9 +618,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 299..302, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 299..302, }, ), @@ -546,12 +635,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 303..336, test: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 306..313, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 312..313, id: Name("x"), ctx: Load, @@ -562,9 +654,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 315..318, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 315..318, }, ), @@ -574,12 +668,15 @@ Module( elif_else_clauses: [ ElifElseClause { range: 319..336, + node_index: AtomicNodeIndex(..), test: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 324..331, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 330..331, id: Name("x"), ctx: Load, @@ -591,9 +688,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 333..336, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 333..336, }, ), @@ -606,13 +705,16 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 337..374, test: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 341..348, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 347..348, id: Name("x"), ctx: Load, @@ -624,9 +726,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 351..354, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 351..354, }, ), @@ -636,13 +740,16 @@ Module( elif_else_clauses: [ ElifElseClause { range: 355..374, + node_index: AtomicNodeIndex(..), test: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 361..368, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 367..368, id: Name("x"), ctx: Load, @@ -655,9 +762,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 371..374, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 371..374, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__import.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__import.py.snap index 37d5ef833ac367..4d4d08b9635b8d 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__import.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__import.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/import.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..92, body: [ Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 0..8, names: [ Alias { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 7..8, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -27,13 +30,16 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 9..21, names: [ Alias { range: 16..21, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a.b.c"), range: 16..21, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -42,18 +48,22 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 22..39, names: [ Alias { range: 29..39, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a.b.c"), range: 29..34, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("d"), range: 38..39, + node_index: AtomicNodeIndex(..), }, ), }, @@ -62,29 +72,36 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 40..54, names: [ Alias { range: 47..48, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a"), range: 47..48, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 50..51, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("b"), range: 50..51, + node_index: AtomicNodeIndex(..), }, asname: None, }, Alias { range: 53..54, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("c"), range: 53..54, + node_index: AtomicNodeIndex(..), }, asname: None, }, @@ -93,31 +110,38 @@ Module( ), Import( StmtImport { + node_index: AtomicNodeIndex(..), range: 55..91, names: [ Alias { range: 62..74, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("foo.bar"), range: 62..69, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("a"), range: 73..74, + node_index: AtomicNodeIndex(..), }, ), }, Alias { range: 76..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("a.b.c.d"), range: 76..83, + node_index: AtomicNodeIndex(..), }, asname: Some( Identifier { id: Name("abcd"), range: 87..91, + node_index: AtomicNodeIndex(..), }, ), }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap index eb24f71df19e0c..1a7192474dcd73 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/valid/statement/match.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..5770, body: [ Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 67..103, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("x"), ctx: Load, @@ -22,15 +25,19 @@ Module( cases: [ MatchCase { range: 80..103, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 85..88, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 85..88, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..88, value: Complex { real: 0.0, @@ -46,10 +53,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 98..103, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 98..99, id: Name("y"), ctx: Store, @@ -58,6 +67,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 102..103, value: Int( 0, @@ -73,9 +83,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 126..167, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("x"), ctx: Load, @@ -84,11 +96,14 @@ Module( cases: [ MatchCase { range: 139..167, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 144..152, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 144..149, id: Name("bytes"), ctx: Load, @@ -96,15 +111,18 @@ Module( ), arguments: PatternArguments { range: 149..152, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 150..151, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 150..151, + node_index: AtomicNodeIndex(..), }, ), }, @@ -118,10 +136,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 162..167, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 162..163, id: Name("y"), ctx: Store, @@ -130,6 +150,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 166..167, value: Int( 0, @@ -145,9 +166,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 190..260, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 196..197, id: Name("x"), ctx: Load, @@ -156,11 +179,14 @@ Module( cases: [ MatchCase { range: 203..229, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 208..209, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 208..209, value: Int( 0, @@ -172,6 +198,7 @@ Module( guard: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 213..214, value: Int( 0, @@ -182,10 +209,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 224..229, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 224..225, id: Name("y"), ctx: Store, @@ -194,6 +223,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 228..229, value: Int( 0, @@ -206,11 +236,14 @@ Module( }, MatchCase { range: 234..260, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 239..240, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 239..240, value: Int( 0, @@ -222,6 +255,7 @@ Module( guard: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 244..245, value: Int( 1, @@ -232,10 +266,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 255..260, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..256, id: Name("y"), ctx: Store, @@ -244,6 +280,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 259..260, value: Int( 1, @@ -259,9 +296,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 283..332, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 289..290, value: Int( 3, @@ -271,15 +310,19 @@ Module( cases: [ MatchCase { range: 296..332, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 301..314, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 301..302, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 301..302, value: Int( 0, @@ -291,8 +334,10 @@ Module( MatchValue( PatternMatchValue { range: 305..306, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 305..306, value: Int( 1, @@ -304,8 +349,10 @@ Module( MatchValue( PatternMatchValue { range: 309..310, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 309..310, value: Int( 2, @@ -317,8 +364,10 @@ Module( MatchValue( PatternMatchValue { range: 313..314, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 313..314, value: Int( 3, @@ -334,10 +383,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 324..332, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 324..325, id: Name("x"), ctx: Store, @@ -346,6 +397,7 @@ Module( ], value: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 328..332, value: true, }, @@ -359,9 +411,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 355..403, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 361..362, id: Name("x"), ctx: Load, @@ -370,19 +424,24 @@ Module( cases: [ MatchCase { range: 368..403, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 373..388, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 373..379, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 374..375, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 374..375, value: Int( 0, @@ -394,8 +453,10 @@ Module( MatchValue( PatternMatchValue { range: 377..378, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 377..378, value: Int( 1, @@ -410,12 +471,15 @@ Module( MatchSequence( PatternMatchSequence { range: 382..388, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 383..384, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 383..384, value: Int( 1, @@ -427,8 +491,10 @@ Module( MatchValue( PatternMatchValue { range: 386..387, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 386..387, value: Int( 0, @@ -447,10 +513,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 398..403, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 398..399, id: Name("y"), ctx: Store, @@ -459,6 +527,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 402..403, value: Int( 0, @@ -474,9 +543,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 445..523, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 451..452, id: Name("x"), ctx: Load, @@ -485,13 +556,16 @@ Module( cases: [ MatchCase { range: 458..489, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 463..467, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 464..466, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -502,15 +576,18 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 477..489, value: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 484..489, value: StringLiteralValue { inner: Single( StringLiteral { range: 484..489, + node_index: AtomicNodeIndex(..), value: "seq", flags: StringLiteralFlags { quote_style: Double, @@ -529,9 +606,11 @@ Module( }, MatchCase { range: 494..523, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 499..501, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -541,15 +620,18 @@ Module( body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 511..523, value: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 518..523, value: StringLiteralValue { inner: Single( StringLiteral { range: 518..523, + node_index: AtomicNodeIndex(..), value: "map", flags: StringLiteralFlags { quote_style: Double, @@ -571,9 +653,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 546..714, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 552..553, id: Name("x"), ctx: Load, @@ -582,12 +666,15 @@ Module( cases: [ MatchCase { range: 559..594, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 564..579, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 565..566, value: Int( 0, @@ -599,12 +686,15 @@ Module( MatchSequence( PatternMatchSequence { range: 568..578, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 569..570, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 569..570, value: Int( 1, @@ -616,8 +706,10 @@ Module( MatchValue( PatternMatchValue { range: 572..573, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 572..573, value: Int( 2, @@ -629,6 +721,7 @@ Module( MatchMapping( PatternMatchMapping { range: 575..577, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -645,10 +738,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 589..594, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 589..590, id: Name("y"), ctx: Store, @@ -657,6 +752,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 593..594, value: Int( 0, @@ -669,16 +765,20 @@ Module( }, MatchCase { range: 599..687, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 604..672, + node_index: AtomicNodeIndex(..), patterns: [ MatchMapping( PatternMatchMapping { range: 604..626, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 605..606, value: Int( 0, @@ -690,16 +790,20 @@ Module( MatchOr( PatternMatchOr { range: 608..625, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 608..618, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 609..610, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 609..610, value: Int( 1, @@ -711,8 +815,10 @@ Module( MatchValue( PatternMatchValue { range: 612..613, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 612..613, value: Int( 2, @@ -724,6 +830,7 @@ Module( MatchMapping( PatternMatchMapping { range: 615..617, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -735,6 +842,7 @@ Module( MatchSingleton( PatternMatchSingleton { range: 621..625, + node_index: AtomicNodeIndex(..), value: True, }, ), @@ -748,9 +856,11 @@ Module( MatchMapping( PatternMatchMapping { range: 629..638, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 630..631, value: Int( 1, @@ -762,10 +872,12 @@ Module( MatchSequence( PatternMatchSequence { range: 633..637, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 634..636, + node_index: AtomicNodeIndex(..), patterns: [], }, ), @@ -779,9 +891,11 @@ Module( MatchMapping( PatternMatchMapping { range: 641..656, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 642..643, value: Int( 0, @@ -793,12 +907,15 @@ Module( MatchSequence( PatternMatchSequence { range: 645..655, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 646..647, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 646..647, value: Int( 1, @@ -810,8 +927,10 @@ Module( MatchValue( PatternMatchValue { range: 649..650, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 649..650, value: Int( 2, @@ -823,6 +942,7 @@ Module( MatchMapping( PatternMatchMapping { range: 652..654, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -838,19 +958,23 @@ Module( MatchSequence( PatternMatchSequence { range: 659..661, + node_index: AtomicNodeIndex(..), patterns: [], }, ), MatchValue( PatternMatchValue { range: 664..667, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 664..667, value: StringLiteralValue { inner: Single( StringLiteral { range: 664..667, + node_index: AtomicNodeIndex(..), value: "X", flags: StringLiteralFlags { quote_style: Double, @@ -867,6 +991,7 @@ Module( MatchMapping( PatternMatchMapping { range: 670..672, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -879,10 +1004,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 682..687, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 682..683, id: Name("y"), ctx: Store, @@ -891,6 +1018,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 686..687, value: Int( 1, @@ -903,9 +1031,11 @@ Module( }, MatchCase { range: 692..714, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 697..699, + node_index: AtomicNodeIndex(..), patterns: [], }, ), @@ -913,10 +1043,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 709..714, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 709..710, id: Name("y"), ctx: Store, @@ -925,6 +1057,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 713..714, value: Int( 2, @@ -940,9 +1073,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 737..782, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 743..744, id: Name("x"), ctx: Load, @@ -951,14 +1086,18 @@ Module( cases: [ MatchCase { range: 750..782, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 755..767, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 755..767, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 755..759, value: Float( 0.25, @@ -968,6 +1107,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 762..767, value: Complex { real: 0.0, @@ -983,10 +1123,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 777..782, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 777..778, id: Name("y"), ctx: Store, @@ -995,6 +1137,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 781..782, value: Int( 0, @@ -1010,9 +1153,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 805..841, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 811..812, id: Name("x"), ctx: Load, @@ -1021,15 +1166,19 @@ Module( cases: [ MatchCase { range: 818..841, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 823..826, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 823..826, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 824..826, value: Complex { real: 0.0, @@ -1045,10 +1194,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 836..841, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 836..837, id: Name("y"), ctx: Store, @@ -1057,6 +1208,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 840..841, value: Int( 0, @@ -1072,9 +1224,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 864..913, subject: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 870..871, value: Int( 4, @@ -1084,15 +1238,19 @@ Module( cases: [ MatchCase { range: 877..913, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 882..895, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 882..883, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 882..883, value: Int( 0, @@ -1104,8 +1262,10 @@ Module( MatchValue( PatternMatchValue { range: 886..887, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 886..887, value: Int( 1, @@ -1117,8 +1277,10 @@ Module( MatchValue( PatternMatchValue { range: 890..891, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 890..891, value: Int( 2, @@ -1130,8 +1292,10 @@ Module( MatchValue( PatternMatchValue { range: 894..895, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 894..895, value: Int( 3, @@ -1147,10 +1311,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 905..913, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 905..906, id: Name("x"), ctx: Store, @@ -1159,6 +1325,7 @@ Module( ], value: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 909..913, value: true, }, @@ -1172,9 +1339,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 936..975, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 942..943, id: Name("x"), ctx: Load, @@ -1183,11 +1352,14 @@ Module( cases: [ MatchCase { range: 949..975, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 954..955, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 954..955, value: Int( 0, @@ -1199,6 +1371,7 @@ Module( guard: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 959..960, id: Name("x"), ctx: Load, @@ -1208,10 +1381,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 970..975, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 970..971, id: Name("y"), ctx: Store, @@ -1220,6 +1395,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 974..975, value: Int( 0, @@ -1235,9 +1411,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 998..1098, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1004..1005, id: Name("x"), ctx: Load, @@ -1246,12 +1424,15 @@ Module( cases: [ MatchCase { range: 1011..1037, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 1016..1022, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1017..1018, value: Int( 1, @@ -1263,8 +1444,10 @@ Module( MatchValue( PatternMatchValue { range: 1020..1021, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1020..1021, value: Int( 0, @@ -1281,10 +1464,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1032..1037, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1032..1033, id: Name("y"), ctx: Store, @@ -1293,6 +1478,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1036..1037, value: Int( 0, @@ -1305,12 +1491,15 @@ Module( }, MatchCase { range: 1042..1068, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 1047..1053, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1048..1049, value: Int( 0, @@ -1322,8 +1511,10 @@ Module( MatchValue( PatternMatchValue { range: 1051..1052, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1051..1052, value: Int( 0, @@ -1340,10 +1531,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1063..1068, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1063..1064, id: Name("y"), ctx: Store, @@ -1352,6 +1545,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1067..1068, value: Int( 1, @@ -1364,15 +1558,18 @@ Module( }, MatchCase { range: 1073..1098, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 1078..1083, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: Some( Identifier { id: Name("z"), range: 1081..1082, + node_index: AtomicNodeIndex(..), }, ), }, @@ -1381,10 +1578,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1093..1098, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1093..1094, id: Name("y"), ctx: Store, @@ -1393,6 +1592,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1097..1098, value: Int( 2, @@ -1408,12 +1608,15 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1121..1162, subject: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1127..1132, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1127..1130, id: Name("Seq"), ctx: Load, @@ -1421,6 +1624,7 @@ Module( ), arguments: Arguments { range: 1130..1132, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -1429,13 +1633,16 @@ Module( cases: [ MatchCase { range: 1138..1162, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1143..1147, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 1144..1146, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -1446,10 +1653,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1157..1162, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1157..1158, id: Name("y"), ctx: Store, @@ -1458,6 +1667,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1161..1162, value: Int( 0, @@ -1473,9 +1683,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1185..1245, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1191..1192, id: Name("x"), ctx: Load, @@ -1484,11 +1696,14 @@ Module( cases: [ MatchCase { range: 1198..1219, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 1203..1204, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1203..1204, value: Int( 1, @@ -1501,10 +1716,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1214..1219, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1214..1215, id: Name("y"), ctx: Store, @@ -1513,6 +1730,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1218..1219, value: Int( 0, @@ -1525,11 +1743,14 @@ Module( }, MatchCase { range: 1224..1245, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 1229..1230, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1229..1230, value: Int( 1, @@ -1542,10 +1763,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1240..1245, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1240..1241, id: Name("y"), ctx: Store, @@ -1554,6 +1777,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1244..1245, value: Int( 1, @@ -1569,9 +1793,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1268..1315, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1274..1275, id: Name("x"), ctx: Load, @@ -1580,17 +1806,21 @@ Module( cases: [ MatchCase { range: 1281..1315, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 1286..1298, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 1287..1292, value: StringLiteralValue { inner: Single( StringLiteral { range: 1287..1292, + node_index: AtomicNodeIndex(..), value: "foo", flags: StringLiteralFlags { quote_style: Double, @@ -1607,11 +1837,13 @@ Module( MatchAs( PatternMatchAs { range: 1294..1297, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("bar"), range: 1294..1297, + node_index: AtomicNodeIndex(..), }, ), }, @@ -1624,10 +1856,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1308..1315, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1308..1309, id: Name("y"), ctx: Store, @@ -1636,6 +1870,7 @@ Module( ], value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1312..1315, id: Name("bar"), ctx: Load, @@ -1650,13 +1885,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1338..1392, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1344..1353, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1345..1346, value: Int( 0, @@ -1665,6 +1903,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1348..1349, value: Int( 1, @@ -1673,6 +1912,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1351..1352, value: Int( 2, @@ -1687,15 +1927,19 @@ Module( cases: [ MatchCase { range: 1359..1392, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1364..1377, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 1365..1366, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1365..1366, value: Int( 0, @@ -1707,8 +1951,10 @@ Module( MatchValue( PatternMatchValue { range: 1368..1369, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1368..1369, value: Int( 1, @@ -1720,10 +1966,12 @@ Module( MatchStar( PatternMatchStar { range: 1371..1373, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("x"), range: 1372..1373, + node_index: AtomicNodeIndex(..), }, ), }, @@ -1731,8 +1979,10 @@ Module( MatchValue( PatternMatchValue { range: 1375..1376, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1375..1376, value: Int( 2, @@ -1748,10 +1998,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1387..1392, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1387..1388, id: Name("y"), ctx: Store, @@ -1760,6 +2012,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1391..1392, value: Int( 0, @@ -1775,9 +2028,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1415..1529, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1421..1422, id: Name("x"), ctx: Load, @@ -1786,15 +2041,19 @@ Module( cases: [ MatchCase { range: 1428..1451, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1433..1436, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 1434..1435, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1434..1435, value: Int( 0, @@ -1810,10 +2069,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1446..1451, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1446..1447, id: Name("y"), ctx: Store, @@ -1822,6 +2083,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1450..1451, value: Int( 0, @@ -1834,15 +2096,19 @@ Module( }, MatchCase { range: 1456..1498, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1461..1467, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 1462..1463, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1462..1463, value: Int( 1, @@ -1854,8 +2120,10 @@ Module( MatchValue( PatternMatchValue { range: 1465..1466, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1465..1466, value: Int( 0, @@ -1870,9 +2138,11 @@ Module( guard: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1472..1482, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1472..1473, id: Name("x"), ctx: Store, @@ -1880,9 +2150,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1477..1482, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1477..1478, id: Name("x"), ctx: Load, @@ -1890,11 +2162,13 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 1479..1481, lower: None, upper: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1480..1481, value: Int( 0, @@ -1914,10 +2188,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1493..1498, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1493..1494, id: Name("y"), ctx: Store, @@ -1926,6 +2202,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1497..1498, value: Int( 1, @@ -1938,15 +2215,19 @@ Module( }, MatchCase { range: 1503..1529, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1508..1514, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 1509..1510, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1509..1510, value: Int( 1, @@ -1958,8 +2239,10 @@ Module( MatchValue( PatternMatchValue { range: 1512..1513, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1512..1513, value: Int( 0, @@ -1975,10 +2258,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1524..1529, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1524..1525, id: Name("y"), ctx: Store, @@ -1987,6 +2272,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1528..1529, value: Int( 2, @@ -2002,9 +2288,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1552..1595, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1558..1559, id: Name("w"), ctx: Load, @@ -2013,18 +2301,22 @@ Module( cases: [ MatchCase { range: 1565..1595, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1570..1580, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 1571..1572, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 1571..1572, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2032,11 +2324,13 @@ Module( MatchAs( PatternMatchAs { range: 1574..1575, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 1574..1575, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2044,6 +2338,7 @@ Module( MatchStar( PatternMatchStar { range: 1577..1579, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -2054,10 +2349,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1590..1595, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1590..1591, id: Name("z"), ctx: Store, @@ -2066,6 +2363,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1594..1595, value: Int( 0, @@ -2081,9 +2379,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1618..1664, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1624..1625, id: Name("x"), ctx: Load, @@ -2092,18 +2392,23 @@ Module( cases: [ MatchCase { range: 1631..1664, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 1636..1649, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1636..1649, left: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 1636..1641, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1637..1641, value: Float( 0.25, @@ -2115,6 +2420,7 @@ Module( op: Sub, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1644..1649, value: Complex { real: 0.0, @@ -2130,10 +2436,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1659..1664, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1659..1660, id: Name("y"), ctx: Store, @@ -2142,6 +2450,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1663..1664, value: Int( 0, @@ -2157,13 +2466,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1687..1726, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1693..1697, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1694..1695, id: Name("x"), ctx: Load, @@ -2177,18 +2489,22 @@ Module( cases: [ MatchCase { range: 1703..1726, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 1708..1711, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 1709..1710, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 1709..1710, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2200,10 +2516,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1721..1726, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1721..1722, id: Name("z"), ctx: Store, @@ -2212,6 +2530,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1725..1726, value: Int( 0, @@ -2227,9 +2546,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1749..1789, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1755..1756, id: Name("x"), ctx: Load, @@ -2238,20 +2559,26 @@ Module( cases: [ MatchCase { range: 1762..1789, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 1767..1774, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1767..1774, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1767..1772, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1767..1770, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1767..1768, id: Name("A"), ctx: Load, @@ -2260,6 +2587,7 @@ Module( attr: Identifier { id: Name("B"), range: 1769..1770, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2267,6 +2595,7 @@ Module( attr: Identifier { id: Name("C"), range: 1771..1772, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2274,6 +2603,7 @@ Module( attr: Identifier { id: Name("D"), range: 1773..1774, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2284,10 +2614,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1784..1789, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1784..1785, id: Name("y"), ctx: Store, @@ -2296,6 +2628,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1788..1789, value: Int( 0, @@ -2311,9 +2644,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1812..1849, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1818..1819, id: Name("x"), ctx: Load, @@ -2322,9 +2657,11 @@ Module( cases: [ MatchCase { range: 1825..1849, + node_index: AtomicNodeIndex(..), pattern: MatchSingleton( PatternMatchSingleton { range: 1830..1834, + node_index: AtomicNodeIndex(..), value: None, }, ), @@ -2332,10 +2669,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1844..1849, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1844..1845, id: Name("y"), ctx: Store, @@ -2344,6 +2683,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1848..1849, value: Int( 0, @@ -2359,9 +2699,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1872..1906, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1878..1879, id: Name("x"), ctx: Load, @@ -2370,11 +2712,14 @@ Module( cases: [ MatchCase { range: 1885..1906, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 1890..1891, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1890..1891, value: Int( 0, @@ -2387,10 +2732,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1901..1906, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1901..1902, id: Name("y"), ctx: Store, @@ -2399,6 +2746,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1905..1906, value: Int( 0, @@ -2414,9 +2762,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1929..1967, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1935..1936, id: Name("x"), ctx: Load, @@ -2425,9 +2775,11 @@ Module( cases: [ MatchCase { range: 1942..1967, + node_index: AtomicNodeIndex(..), pattern: MatchSingleton( PatternMatchSingleton { range: 1947..1952, + node_index: AtomicNodeIndex(..), value: False, }, ), @@ -2435,10 +2787,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1962..1967, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1962..1963, id: Name("y"), ctx: Store, @@ -2447,6 +2801,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1966..1967, value: Int( 0, @@ -2462,9 +2817,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 1990..2081, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1996..1997, id: Name("x"), ctx: Load, @@ -2473,9 +2830,11 @@ Module( cases: [ MatchCase { range: 2003..2025, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2008..2010, + node_index: AtomicNodeIndex(..), patterns: [], }, ), @@ -2483,10 +2842,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2020..2025, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2020..2021, id: Name("y"), ctx: Store, @@ -2495,6 +2856,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2024..2025, value: Int( 0, @@ -2507,20 +2869,25 @@ Module( }, MatchCase { range: 2030..2054, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2035..2039, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 2036..2038, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2036..2038, value: StringLiteralValue { inner: Single( StringLiteral { range: 2036..2038, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Double, @@ -2541,10 +2908,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2049..2054, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2049..2050, id: Name("y"), ctx: Store, @@ -2553,6 +2922,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2053..2054, value: Int( 1, @@ -2565,16 +2935,20 @@ Module( }, MatchCase { range: 2059..2081, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 2064..2066, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2064..2066, value: StringLiteralValue { inner: Single( StringLiteral { range: 2064..2066, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Double, @@ -2592,10 +2966,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2076..2081, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2076..2077, id: Name("y"), ctx: Store, @@ -2604,6 +2980,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2080..2081, value: Int( 2, @@ -2619,9 +2996,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2104..2138, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2110..2111, id: Name("x"), ctx: Load, @@ -2630,14 +3009,17 @@ Module( cases: [ MatchCase { range: 2117..2138, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 2122..2123, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 2122..2123, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2646,10 +3028,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2133..2138, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2133..2134, id: Name("y"), ctx: Store, @@ -2658,6 +3042,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2137..2138, value: Int( 0, @@ -2673,9 +3058,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2161..2207, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2167..2168, id: Name("w"), ctx: Load, @@ -2684,18 +3071,22 @@ Module( cases: [ MatchCase { range: 2174..2207, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2179..2192, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 2180..2181, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("x"), range: 2180..2181, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2703,11 +3094,13 @@ Module( MatchAs( PatternMatchAs { range: 2183..2184, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 2183..2184, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2715,10 +3108,12 @@ Module( MatchStar( PatternMatchStar { range: 2186..2191, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("rest"), range: 2187..2191, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2730,10 +3125,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2202..2207, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2202..2203, id: Name("z"), ctx: Store, @@ -2742,6 +3139,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2206..2207, value: Int( 0, @@ -2757,9 +3155,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2230..2307, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2236..2237, id: Name("x"), ctx: Load, @@ -2768,19 +3168,24 @@ Module( cases: [ MatchCase { range: 2243..2307, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 2248..2278, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 2249..2255, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 2249..2250, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2249..2250, value: Int( 0, @@ -2794,6 +3199,7 @@ Module( Identifier { id: Name("z"), range: 2254..2255, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2801,12 +3207,15 @@ Module( MatchAs( PatternMatchAs { range: 2260..2266, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 2260..2261, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2260..2261, value: Int( 1, @@ -2820,6 +3229,7 @@ Module( Identifier { id: Name("z"), range: 2265..2266, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2827,12 +3237,15 @@ Module( MatchAs( PatternMatchAs { range: 2271..2277, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 2271..2272, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2271..2272, value: Int( 2, @@ -2846,6 +3259,7 @@ Module( Identifier { id: Name("z"), range: 2276..2277, + node_index: AtomicNodeIndex(..), }, ), }, @@ -2856,9 +3270,11 @@ Module( guard: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 2282..2292, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2282..2283, id: Name("z"), ctx: Load, @@ -2870,9 +3286,11 @@ Module( comparators: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 2287..2292, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2287..2288, id: Name("x"), ctx: Load, @@ -2881,6 +3299,7 @@ Module( op: Mod, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2291..2292, value: Int( 2, @@ -2896,10 +3315,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2302..2307, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2302..2303, id: Name("y"), ctx: Store, @@ -2908,6 +3329,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2306..2307, value: Int( 0, @@ -2923,9 +3345,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2330..2499, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2336..2337, id: Name("x"), ctx: Load, @@ -2934,12 +3358,15 @@ Module( cases: [ MatchCase { range: 2343..2378, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 2348..2363, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2349..2350, value: Int( 0, @@ -2951,12 +3378,15 @@ Module( MatchSequence( PatternMatchSequence { range: 2352..2362, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 2353..2354, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2353..2354, value: Int( 1, @@ -2968,8 +3398,10 @@ Module( MatchValue( PatternMatchValue { range: 2356..2357, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2356..2357, value: Int( 2, @@ -2981,6 +3413,7 @@ Module( MatchMapping( PatternMatchMapping { range: 2359..2361, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -2997,10 +3430,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2373..2378, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2373..2374, id: Name("y"), ctx: Store, @@ -3009,6 +3444,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2377..2378, value: Int( 0, @@ -3021,16 +3457,20 @@ Module( }, MatchCase { range: 2383..2472, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 2388..2457, + node_index: AtomicNodeIndex(..), patterns: [ MatchMapping( PatternMatchMapping { range: 2388..2411, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2389..2390, value: Int( 0, @@ -3042,16 +3482,20 @@ Module( MatchOr( PatternMatchOr { range: 2392..2410, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 2392..2402, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 2393..2394, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2393..2394, value: Int( 1, @@ -3063,8 +3507,10 @@ Module( MatchValue( PatternMatchValue { range: 2396..2397, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2396..2397, value: Int( 2, @@ -3076,6 +3522,7 @@ Module( MatchMapping( PatternMatchMapping { range: 2399..2401, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -3087,6 +3534,7 @@ Module( MatchSingleton( PatternMatchSingleton { range: 2405..2410, + node_index: AtomicNodeIndex(..), value: False, }, ), @@ -3100,9 +3548,11 @@ Module( MatchMapping( PatternMatchMapping { range: 2414..2423, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2415..2416, value: Int( 1, @@ -3114,10 +3564,12 @@ Module( MatchSequence( PatternMatchSequence { range: 2418..2422, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 2419..2421, + node_index: AtomicNodeIndex(..), patterns: [], }, ), @@ -3131,9 +3583,11 @@ Module( MatchMapping( PatternMatchMapping { range: 2426..2441, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2427..2428, value: Int( 0, @@ -3145,12 +3599,15 @@ Module( MatchSequence( PatternMatchSequence { range: 2430..2440, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 2431..2432, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2431..2432, value: Int( 1, @@ -3162,8 +3619,10 @@ Module( MatchValue( PatternMatchValue { range: 2434..2435, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2434..2435, value: Int( 2, @@ -3175,6 +3634,7 @@ Module( MatchMapping( PatternMatchMapping { range: 2437..2439, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -3190,19 +3650,23 @@ Module( MatchSequence( PatternMatchSequence { range: 2444..2446, + node_index: AtomicNodeIndex(..), patterns: [], }, ), MatchValue( PatternMatchValue { range: 2449..2452, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2449..2452, value: StringLiteralValue { inner: Single( StringLiteral { range: 2449..2452, + node_index: AtomicNodeIndex(..), value: "X", flags: StringLiteralFlags { quote_style: Double, @@ -3219,6 +3683,7 @@ Module( MatchMapping( PatternMatchMapping { range: 2455..2457, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: None, @@ -3231,10 +3696,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2467..2472, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2467..2468, id: Name("y"), ctx: Store, @@ -3243,6 +3710,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2471..2472, value: Int( 1, @@ -3255,9 +3723,11 @@ Module( }, MatchCase { range: 2477..2499, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2482..2484, + node_index: AtomicNodeIndex(..), patterns: [], }, ), @@ -3265,10 +3735,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2494..2499, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2494..2495, id: Name("y"), ctx: Store, @@ -3277,6 +3749,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2498..2499, value: Int( 2, @@ -3292,13 +3765,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2522..2568, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2528..2537, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2529..2530, value: Int( 0, @@ -3307,6 +3783,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2532..2533, value: Int( 1, @@ -3315,6 +3792,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2535..2536, value: Int( 2, @@ -3329,15 +3807,19 @@ Module( cases: [ MatchCase { range: 2543..2568, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2548..2553, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 2548..2549, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2548..2549, value: Int( 0, @@ -3349,10 +3831,12 @@ Module( MatchStar( PatternMatchStar { range: 2551..2553, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("x"), range: 2552..2553, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3364,10 +3848,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2563..2568, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2563..2564, id: Name("y"), ctx: Store, @@ -3376,6 +3862,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2567..2568, value: Int( 0, @@ -3391,13 +3878,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2591..2638, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2597..2606, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2598..2599, value: Int( 0, @@ -3406,6 +3896,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2601..2602, value: Int( 1, @@ -3414,6 +3905,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2604..2605, value: Int( 2, @@ -3428,17 +3920,21 @@ Module( cases: [ MatchCase { range: 2612..2638, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2617..2623, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 2617..2619, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("x"), range: 2618..2619, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3446,8 +3942,10 @@ Module( MatchValue( PatternMatchValue { range: 2621..2622, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2621..2622, value: Int( 2, @@ -3463,10 +3961,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2633..2638, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2633..2634, id: Name("y"), ctx: Store, @@ -3475,6 +3975,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2637..2638, value: Int( 0, @@ -3490,13 +3991,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2661..2697, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2667..2669, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2667..2668, id: Name("x"), ctx: Load, @@ -3510,18 +4014,22 @@ Module( cases: [ MatchCase { range: 2675..2697, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2680..2682, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 2680..2681, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 2680..2681, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3533,10 +4041,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2692..2697, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2692..2693, id: Name("z"), ctx: Store, @@ -3545,6 +4055,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2696..2697, value: Int( 0, @@ -3560,13 +4071,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2720..2760, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2726..2730, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2726..2727, id: Name("w"), ctx: Load, @@ -3574,6 +4088,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2729..2730, id: Name("x"), ctx: Load, @@ -3587,18 +4102,22 @@ Module( cases: [ MatchCase { range: 2736..2760, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2741..2745, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 2741..2742, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 2741..2742, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3606,11 +4125,13 @@ Module( MatchAs( PatternMatchAs { range: 2744..2745, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 2744..2745, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3622,10 +4143,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2755..2760, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2755..2756, id: Name("v"), ctx: Store, @@ -3634,6 +4157,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2759..2760, value: Int( 0, @@ -3649,16 +4173,20 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2783..2829, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 2789..2796, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 2789..2795, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2789..2790, id: Name("w"), ctx: Store, @@ -3666,6 +4194,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2794..2795, id: Name("x"), ctx: Load, @@ -3681,22 +4210,27 @@ Module( cases: [ MatchCase { range: 2802..2829, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 2807..2814, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 2807..2813, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 2807..2808, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("y"), range: 2807..2808, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3706,6 +4240,7 @@ Module( Identifier { id: Name("v"), range: 2812..2813, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3717,10 +4252,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 2824..2829, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2824..2825, id: Name("z"), ctx: Store, @@ -3729,6 +4266,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2828..2829, value: Int( 0, @@ -3744,9 +4282,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2831..2952, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2837..2838, id: Name("x"), ctx: Load, @@ -3755,23 +4295,29 @@ Module( cases: [ MatchCase { range: 2927..2952, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 2932..2938, + node_index: AtomicNodeIndex(..), value: FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 2932..2938, value: FStringValue { inner: Single( FString( FString { range: 2932..2938, + node_index: AtomicNodeIndex(..), elements: [ Interpolation( InterpolatedElement { range: 2934..2937, + node_index: AtomicNodeIndex(..), expression: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 2935..2936, id: Name("y"), ctx: Load, @@ -3800,6 +4346,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 2948..2952, }, ), @@ -3810,20 +4357,24 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 2953..3025, subject: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 2959..2970, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 2960..2966, value: StringLiteralValue { inner: Single( StringLiteral { range: 2960..2966, + node_index: AtomicNodeIndex(..), value: "test", flags: StringLiteralFlags { quote_style: Double, @@ -3838,6 +4389,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 2968..2969, value: Int( 1, @@ -3851,15 +4403,18 @@ Module( cases: [ MatchCase { range: 2976..3025, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 2981..3004, + node_index: AtomicNodeIndex(..), keys: [], patterns: [], rest: Some( Identifier { id: Name("rest"), range: 2993..2997, + node_index: AtomicNodeIndex(..), }, ), }, @@ -3868,12 +4423,15 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3014..3025, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3014..3025, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3014..3019, id: Name("print"), ctx: Load, @@ -3881,9 +4439,11 @@ Module( ), arguments: Arguments { range: 3019..3025, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3020..3024, id: Name("rest"), ctx: Load, @@ -3903,20 +4463,24 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3026..3129, subject: Dict( ExprDict { + node_index: AtomicNodeIndex(..), range: 3032..3049, items: [ DictItem { key: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 3033..3040, value: StringLiteralValue { inner: Single( StringLiteral { range: 3033..3040, + node_index: AtomicNodeIndex(..), value: "label", flags: StringLiteralFlags { quote_style: Double, @@ -3931,11 +4495,13 @@ Module( ), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 3042..3048, value: StringLiteralValue { inner: Single( StringLiteral { range: 3042..3048, + node_index: AtomicNodeIndex(..), value: "test", flags: StringLiteralFlags { quote_style: Double, @@ -3954,17 +4520,21 @@ Module( cases: [ MatchCase { range: 3055..3129, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 3060..3107, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 3070..3077, value: StringLiteralValue { inner: Single( StringLiteral { range: 3070..3077, + node_index: AtomicNodeIndex(..), value: "label", flags: StringLiteralFlags { quote_style: Double, @@ -3981,16 +4551,20 @@ Module( MatchAs( PatternMatchAs { range: 3079..3100, + node_index: AtomicNodeIndex(..), pattern: Some( MatchOr( PatternMatchOr { range: 3079..3091, + node_index: AtomicNodeIndex(..), patterns: [ MatchClass( PatternMatchClass { range: 3079..3084, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3079..3082, id: Name("str"), ctx: Load, @@ -3998,6 +4572,7 @@ Module( ), arguments: PatternArguments { range: 3082..3084, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [], }, @@ -4006,6 +4581,7 @@ Module( MatchSingleton( PatternMatchSingleton { range: 3087..3091, + node_index: AtomicNodeIndex(..), value: None, }, ), @@ -4017,6 +4593,7 @@ Module( Identifier { id: Name("label"), range: 3095..3100, + node_index: AtomicNodeIndex(..), }, ), }, @@ -4029,12 +4606,15 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3117..3129, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 3117..3129, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3117..3122, id: Name("print"), ctx: Load, @@ -4042,9 +4622,11 @@ Module( ), arguments: Arguments { range: 3122..3129, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3123..3128, id: Name("label"), ctx: Load, @@ -4064,9 +4646,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3130..3170, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3136..3137, id: Name("x"), ctx: Load, @@ -4075,15 +4659,19 @@ Module( cases: [ MatchCase { range: 3143..3170, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 3148..3155, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 3149..3150, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3149..3150, value: Int( 0, @@ -4095,8 +4683,10 @@ Module( MatchValue( PatternMatchValue { range: 3152..3153, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3152..3153, value: Int( 1, @@ -4112,10 +4702,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 3165..3170, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3165..3166, id: Name("y"), ctx: Store, @@ -4124,6 +4716,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3169..3170, value: Int( 0, @@ -4139,9 +4732,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3171..3211, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3177..3178, id: Name("x"), ctx: Load, @@ -4150,15 +4745,19 @@ Module( cases: [ MatchCase { range: 3184..3211, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 3189..3196, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 3190..3191, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3190..3191, value: Int( 0, @@ -4170,8 +4769,10 @@ Module( MatchValue( PatternMatchValue { range: 3193..3194, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3193..3194, value: Int( 1, @@ -4187,10 +4788,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 3206..3211, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3206..3207, id: Name("y"), ctx: Store, @@ -4199,6 +4802,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3210..3211, value: Int( 0, @@ -4214,9 +4818,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3212..3249, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3218..3219, id: Name("x"), ctx: Load, @@ -4225,15 +4831,19 @@ Module( cases: [ MatchCase { range: 3225..3249, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 3230..3234, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 3231..3232, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3231..3232, value: Int( 0, @@ -4249,10 +4859,12 @@ Module( body: [ Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 3244..3249, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3244..3245, id: Name("y"), ctx: Store, @@ -4261,6 +4873,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3248..3249, value: Int( 0, @@ -4276,13 +4889,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3250..3284, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3256..3258, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3256..3257, id: Name("x"), ctx: Load, @@ -4296,14 +4912,17 @@ Module( cases: [ MatchCase { range: 3264..3284, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 3269..3270, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 3269..3270, + node_index: AtomicNodeIndex(..), }, ), }, @@ -4312,6 +4931,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 3280..3284, }, ), @@ -4322,13 +4942,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3285..3321, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3291..3295, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3291..3292, id: Name("x"), ctx: Load, @@ -4336,6 +4959,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3294..3295, id: Name("y"), ctx: Load, @@ -4349,14 +4973,17 @@ Module( cases: [ MatchCase { range: 3301..3321, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 3306..3307, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 3306..3307, + node_index: AtomicNodeIndex(..), }, ), }, @@ -4365,6 +4992,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 3317..3321, }, ), @@ -4375,13 +5003,16 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3322..3359, subject: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 3328..3333, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3328..3329, id: Name("x"), ctx: Load, @@ -4389,6 +5020,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3331..3332, id: Name("y"), ctx: Load, @@ -4402,14 +5034,17 @@ Module( cases: [ MatchCase { range: 3339..3359, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 3344..3345, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("z"), range: 3344..3345, + node_index: AtomicNodeIndex(..), }, ), }, @@ -4418,6 +5053,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 3355..3359, }, ), @@ -4428,9 +5064,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3385..3475, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3391..3392, id: Name("x"), ctx: Load, @@ -4439,9 +5077,11 @@ Module( cases: [ MatchCase { range: 3398..3420, + node_index: AtomicNodeIndex(..), pattern: MatchSingleton( PatternMatchSingleton { range: 3403..3407, + node_index: AtomicNodeIndex(..), value: None, }, ), @@ -4449,9 +5089,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3417..3420, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3417..3420, }, ), @@ -4461,9 +5103,11 @@ Module( }, MatchCase { range: 3425..3447, + node_index: AtomicNodeIndex(..), pattern: MatchSingleton( PatternMatchSingleton { range: 3430..3434, + node_index: AtomicNodeIndex(..), value: True, }, ), @@ -4471,9 +5115,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3444..3447, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3444..3447, }, ), @@ -4483,9 +5129,11 @@ Module( }, MatchCase { range: 3452..3475, + node_index: AtomicNodeIndex(..), pattern: MatchSingleton( PatternMatchSingleton { range: 3457..3462, + node_index: AtomicNodeIndex(..), value: False, }, ), @@ -4493,9 +5141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3472..3475, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3472..3475, }, ), @@ -4508,9 +5158,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3497..3821, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3503..3504, id: Name("x"), ctx: Load, @@ -4519,14 +5171,18 @@ Module( cases: [ MatchCase { range: 3510..3531, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3515..3518, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 3515..3518, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3515..3516, id: Name("a"), ctx: Load, @@ -4535,6 +5191,7 @@ Module( attr: Identifier { id: Name("b"), range: 3517..3518, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -4545,9 +5202,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3528..3531, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3528..3531, }, ), @@ -4557,17 +5216,22 @@ Module( }, MatchCase { range: 3536..3559, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3541..3546, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 3541..3546, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 3541..3544, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3541..3542, id: Name("a"), ctx: Load, @@ -4576,6 +5240,7 @@ Module( attr: Identifier { id: Name("b"), range: 3543..3544, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -4583,6 +5248,7 @@ Module( attr: Identifier { id: Name("c"), range: 3545..3546, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -4593,9 +5259,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3556..3559, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3556..3559, }, ), @@ -4605,16 +5273,20 @@ Module( }, MatchCase { range: 3564..3584, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3569..3571, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 3569..3571, value: StringLiteralValue { inner: Single( StringLiteral { range: 3569..3571, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -4632,9 +5304,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3581..3584, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3581..3584, }, ), @@ -4644,16 +5318,20 @@ Module( }, MatchCase { range: 3589..3610, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3594..3597, + node_index: AtomicNodeIndex(..), value: BytesLiteral( ExprBytesLiteral { + node_index: AtomicNodeIndex(..), range: 3594..3597, value: BytesLiteralValue { inner: Single( BytesLiteral { range: 3594..3597, + node_index: AtomicNodeIndex(..), value: [], flags: BytesLiteralFlags { quote_style: Single, @@ -4671,9 +5349,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3607..3610, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3607..3610, }, ), @@ -4683,11 +5363,14 @@ Module( }, MatchCase { range: 3615..3634, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3620..3621, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3620..3621, value: Int( 1, @@ -4700,9 +5383,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3631..3634, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3631..3634, }, ), @@ -4712,11 +5397,14 @@ Module( }, MatchCase { range: 3639..3660, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3644..3647, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3644..3647, value: Float( 1.0, @@ -4729,9 +5417,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3657..3660, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3657..3660, }, ), @@ -4741,11 +5431,14 @@ Module( }, MatchCase { range: 3665..3687, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3670..3674, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3670..3674, value: Complex { real: 0.0, @@ -4759,9 +5452,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3684..3687, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3684..3687, }, ), @@ -4771,14 +5466,18 @@ Module( }, MatchCase { range: 3692..3716, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3697..3703, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 3697..3703, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3697..3698, value: Int( 1, @@ -4788,6 +5487,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3701..3703, value: Complex { real: 0.0, @@ -4803,9 +5503,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3713..3716, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3713..3716, }, ), @@ -4815,15 +5517,19 @@ Module( }, MatchCase { range: 3721..3741, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3726..3728, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 3726..3728, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3727..3728, value: Int( 1, @@ -4838,9 +5544,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3738..3741, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3738..3741, }, ), @@ -4850,15 +5558,19 @@ Module( }, MatchCase { range: 3746..3767, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3751..3754, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 3751..3754, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3752..3754, value: Float( 1.0, @@ -4873,9 +5585,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3764..3767, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3764..3767, }, ), @@ -4885,15 +5599,19 @@ Module( }, MatchCase { range: 3772..3795, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3777..3782, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 3777..3782, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3778..3782, value: Int( 1, @@ -4908,9 +5626,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3792..3795, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3792..3795, }, ), @@ -4920,11 +5640,14 @@ Module( }, MatchCase { range: 3800..3821, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 3806..3807, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3806..3807, value: Int( 1, @@ -4937,9 +5660,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3818..3821, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3818..3821, }, ), @@ -4952,9 +5677,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3840..3927, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3846..3847, id: Name("x"), ctx: Load, @@ -4963,15 +5690,19 @@ Module( cases: [ MatchCase { range: 3853..3876, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 3858..3863, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 3858..3859, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3858..3859, value: Int( 1, @@ -4983,8 +5714,10 @@ Module( MatchValue( PatternMatchValue { range: 3862..3863, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3862..3863, value: Int( 2, @@ -5000,9 +5733,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3873..3876, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3873..3876, }, ), @@ -5012,20 +5747,25 @@ Module( }, MatchCase { range: 3881..3927, + node_index: AtomicNodeIndex(..), pattern: MatchOr( PatternMatchOr { range: 3886..3914, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 3886..3888, + node_index: AtomicNodeIndex(..), value: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 3886..3888, value: StringLiteralValue { inner: Single( StringLiteral { range: 3886..3888, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -5042,8 +5782,10 @@ Module( MatchValue( PatternMatchValue { range: 3891..3894, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3891..3894, value: Float( 1.1, @@ -5055,12 +5797,15 @@ Module( MatchValue( PatternMatchValue { range: 3897..3899, + node_index: AtomicNodeIndex(..), value: UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 3897..3899, op: USub, operand: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3898..3899, value: Int( 1, @@ -5074,11 +5819,14 @@ Module( MatchValue( PatternMatchValue { range: 3902..3908, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 3902..3908, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3902..3903, value: Int( 1, @@ -5088,6 +5836,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 3906..3908, value: Complex { real: 0.0, @@ -5102,11 +5851,14 @@ Module( MatchValue( PatternMatchValue { range: 3911..3914, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 3911..3914, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3911..3912, id: Name("a"), ctx: Load, @@ -5115,6 +5867,7 @@ Module( attr: Identifier { id: Name("b"), range: 3913..3914, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -5128,9 +5881,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3924..3927, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3924..3927, }, ), @@ -5143,9 +5898,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3946..3978, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3952..3953, id: Name("x"), ctx: Load, @@ -5154,14 +5911,17 @@ Module( cases: [ MatchCase { range: 3959..3978, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 3964..3965, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 3964..3965, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5170,9 +5930,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 3975..3978, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 3975..3978, }, ), @@ -5185,9 +5947,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 3979..4016, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 3985..3986, id: Name("x"), ctx: Load, @@ -5196,18 +5960,22 @@ Module( cases: [ MatchCase { range: 3992..4016, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 3997..4003, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 3997..3998, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 3997..3998, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5217,6 +5985,7 @@ Module( Identifier { id: Name("b"), range: 4002..4003, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5225,9 +5994,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4013..4016, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4013..4016, }, ), @@ -5240,9 +6011,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4017..4157, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4023..4024, id: Name("x"), ctx: Load, @@ -5251,19 +6024,24 @@ Module( cases: [ MatchCase { range: 4030..4060, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4035..4047, + node_index: AtomicNodeIndex(..), pattern: Some( MatchOr( PatternMatchOr { range: 4035..4040, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4035..4036, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4035..4036, value: Int( 1, @@ -5275,8 +6053,10 @@ Module( MatchValue( PatternMatchValue { range: 4039..4040, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4039..4040, value: Int( 2, @@ -5293,6 +6073,7 @@ Module( Identifier { id: Name("two"), range: 4044..4047, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5301,9 +6082,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4057..4060, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4057..4060, }, ), @@ -5313,18 +6096,23 @@ Module( }, MatchCase { range: 4065..4096, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4070..4083, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 4070..4076, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4070..4076, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4070..4071, value: Int( 1, @@ -5334,6 +6122,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4074..4076, value: Complex { real: 0.0, @@ -5350,6 +6139,7 @@ Module( Identifier { id: Name("sum"), range: 4080..4083, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5358,9 +6148,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4093..4096, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4093..4096, }, ), @@ -5370,18 +6162,23 @@ Module( }, MatchCase { range: 4101..4128, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4106..4115, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 4106..4109, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 4106..4109, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4106..4107, id: Name("a"), ctx: Load, @@ -5390,6 +6187,7 @@ Module( attr: Identifier { id: Name("b"), range: 4108..4109, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -5401,6 +6199,7 @@ Module( Identifier { id: Name("ab"), range: 4113..4115, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5409,9 +6208,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4125..4128, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4125..4128, }, ), @@ -5421,13 +6222,16 @@ Module( }, MatchCase { range: 4133..4157, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4138..4144, + node_index: AtomicNodeIndex(..), pattern: Some( MatchAs( PatternMatchAs { range: 4138..4139, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -5437,6 +6241,7 @@ Module( Identifier { id: Name("x"), range: 4143..4144, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5445,9 +6250,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4154..4157, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4154..4157, }, ), @@ -5460,9 +6267,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4158..4190, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4164..4165, id: Name("x"), ctx: Load, @@ -5471,9 +6280,11 @@ Module( cases: [ MatchCase { range: 4171..4190, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4176..4177, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -5482,9 +6293,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4187..4190, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4187..4190, }, ), @@ -5497,9 +6310,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4215..4466, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4221..4222, id: Name("x"), ctx: Load, @@ -5508,15 +6323,19 @@ Module( cases: [ MatchCase { range: 4228..4253, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4233..4240, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4233..4234, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4233..4234, value: Int( 1, @@ -5528,8 +6347,10 @@ Module( MatchValue( PatternMatchValue { range: 4236..4237, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4236..4237, value: Int( 2, @@ -5541,8 +6362,10 @@ Module( MatchValue( PatternMatchValue { range: 4239..4240, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4239..4240, value: Int( 3, @@ -5558,9 +6381,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4250..4253, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4250..4253, }, ), @@ -5570,15 +6395,19 @@ Module( }, MatchCase { range: 4258..4286, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4263..4273, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4264..4265, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4264..4265, value: Int( 1, @@ -5590,8 +6419,10 @@ Module( MatchValue( PatternMatchValue { range: 4267..4268, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4267..4268, value: Int( 2, @@ -5603,8 +6434,10 @@ Module( MatchValue( PatternMatchValue { range: 4270..4271, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4270..4271, value: Int( 3, @@ -5620,9 +6453,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4283..4286, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4283..4286, }, ), @@ -5632,18 +6467,23 @@ Module( }, MatchCase { range: 4291..4331, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4296..4318, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4297..4303, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4297..4303, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4297..4298, value: Int( 1, @@ -5653,6 +6493,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4301..4303, value: Complex { real: 0.0, @@ -5667,11 +6508,13 @@ Module( MatchAs( PatternMatchAs { range: 4305..4306, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 4305..4306, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5679,17 +6522,21 @@ Module( MatchSingleton( PatternMatchSingleton { range: 4308..4312, + node_index: AtomicNodeIndex(..), value: None, }, ), MatchValue( PatternMatchValue { range: 4314..4317, + node_index: AtomicNodeIndex(..), value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 4314..4317, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4314..4315, id: Name("a"), ctx: Load, @@ -5698,6 +6545,7 @@ Module( attr: Identifier { id: Name("b"), range: 4316..4317, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -5711,9 +6559,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4328..4331, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4328..4331, }, ), @@ -5723,23 +6573,29 @@ Module( }, MatchCase { range: 4336..4370, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 4341..4357, + node_index: AtomicNodeIndex(..), pattern: Some( MatchSequence( PatternMatchSequence { range: 4341..4352, + node_index: AtomicNodeIndex(..), patterns: [ MatchAs( PatternMatchAs { range: 4342..4348, + node_index: AtomicNodeIndex(..), pattern: Some( MatchValue( PatternMatchValue { range: 4342..4343, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4342..4343, value: Int( 1, @@ -5753,6 +6609,7 @@ Module( Identifier { id: Name("X"), range: 4347..4348, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5760,11 +6617,13 @@ Module( MatchAs( PatternMatchAs { range: 4350..4351, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("b"), range: 4350..4351, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5777,6 +6636,7 @@ Module( Identifier { id: Name("S"), range: 4356..4357, + node_index: AtomicNodeIndex(..), }, ), }, @@ -5785,9 +6645,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4367..4370, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4367..4370, }, ), @@ -5797,15 +6659,19 @@ Module( }, MatchCase { range: 4375..4407, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4380..4394, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4381..4382, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4381..4382, value: Int( 1, @@ -5817,8 +6683,10 @@ Module( MatchValue( PatternMatchValue { range: 4384..4385, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4384..4385, value: Int( 2, @@ -5830,11 +6698,14 @@ Module( MatchValue( PatternMatchValue { range: 4387..4393, + node_index: AtomicNodeIndex(..), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 4387..4393, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4387..4388, value: Int( 3, @@ -5844,6 +6715,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4391..4393, value: Complex { real: 0.0, @@ -5862,9 +6734,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4404..4407, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4404..4407, }, ), @@ -5874,19 +6748,24 @@ Module( }, MatchCase { range: 4412..4440, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4417..4427, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 4418..4423, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4419..4420, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4419..4420, value: Int( 1, @@ -5898,8 +6777,10 @@ Module( MatchValue( PatternMatchValue { range: 4421..4422, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4421..4422, value: Int( 2, @@ -5914,8 +6795,10 @@ Module( MatchValue( PatternMatchValue { range: 4425..4426, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4425..4426, value: Int( 3, @@ -5931,9 +6814,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4437..4440, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4437..4440, }, ), @@ -5943,15 +6828,19 @@ Module( }, MatchCase { range: 4445..4466, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4450..4453, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4451..4452, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4451..4452, value: Int( 1, @@ -5967,9 +6856,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4463..4466, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4463..4466, }, ), @@ -5982,9 +6873,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4487..4616, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4493..4494, id: Name("x"), ctx: Load, @@ -5993,17 +6886,21 @@ Module( cases: [ MatchCase { range: 4500..4521, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4505..4508, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 4505..4507, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("a"), range: 4506..4507, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6015,9 +6912,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4518..4521, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4518..4521, }, ), @@ -6027,13 +6926,16 @@ Module( }, MatchCase { range: 4526..4547, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4531..4534, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 4531..4533, + node_index: AtomicNodeIndex(..), name: None, }, ), @@ -6044,9 +6946,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4544..4547, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4544..4547, }, ), @@ -6056,15 +6960,19 @@ Module( }, MatchCase { range: 4552..4583, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4557..4570, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4558..4559, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4558..4559, value: Int( 1, @@ -6076,8 +6984,10 @@ Module( MatchValue( PatternMatchValue { range: 4561..4562, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4561..4562, value: Int( 2, @@ -6089,10 +6999,12 @@ Module( MatchStar( PatternMatchStar { range: 4564..4569, + node_index: AtomicNodeIndex(..), name: Some( Identifier { id: Name("rest"), range: 4565..4569, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6104,9 +7016,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4580..4583, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4580..4583, }, ), @@ -6116,21 +7030,26 @@ Module( }, MatchCase { range: 4588..4616, + node_index: AtomicNodeIndex(..), pattern: MatchSequence( PatternMatchSequence { range: 4593..4603, + node_index: AtomicNodeIndex(..), patterns: [ MatchStar( PatternMatchStar { range: 4594..4596, + node_index: AtomicNodeIndex(..), name: None, }, ), MatchValue( PatternMatchValue { range: 4598..4599, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4598..4599, value: Int( 1, @@ -6142,8 +7061,10 @@ Module( MatchValue( PatternMatchValue { range: 4601..4602, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4601..4602, value: Int( 2, @@ -6159,9 +7080,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4613..4616, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4613..4616, }, ), @@ -6174,9 +7097,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4638..4910, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4644..4645, id: Name("x"), ctx: Load, @@ -6185,11 +7110,14 @@ Module( cases: [ MatchCase { range: 4651..4676, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4656..4663, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4656..4661, id: Name("Point"), ctx: Load, @@ -6197,6 +7125,7 @@ Module( ), arguments: PatternArguments { range: 4661..4663, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [], }, @@ -6206,9 +7135,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4673..4676, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4673..4676, }, ), @@ -6218,17 +7149,22 @@ Module( }, MatchCase { range: 4681..4710, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4686..4697, + node_index: AtomicNodeIndex(..), cls: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 4686..4695, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 4686..4689, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4686..4687, id: Name("a"), ctx: Load, @@ -6237,6 +7173,7 @@ Module( attr: Identifier { id: Name("b"), range: 4688..4689, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -6244,12 +7181,14 @@ Module( attr: Identifier { id: Name("Point"), range: 4690..4695, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: PatternArguments { range: 4695..4697, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [], }, @@ -6259,9 +7198,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4707..4710, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4707..4710, }, ), @@ -6271,11 +7212,14 @@ Module( }, MatchCase { range: 4715..4745, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4720..4732, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4720..4727, id: Name("Point2D"), ctx: Load, @@ -6283,19 +7227,24 @@ Module( ), arguments: PatternArguments { range: 4727..4732, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 4728..4731, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 4728..4729, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 4730..4731, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4730..4731, value: Int( 0, @@ -6313,9 +7262,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4742..4745, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4742..4745, }, ), @@ -6325,11 +7276,14 @@ Module( }, MatchCase { range: 4750..4786, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4755..4773, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4755..4762, id: Name("Point2D"), ctx: Load, @@ -6337,19 +7291,24 @@ Module( ), arguments: PatternArguments { range: 4762..4773, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 4763..4766, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 4763..4764, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 4765..4766, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4765..4766, value: Int( 0, @@ -6361,15 +7320,19 @@ Module( }, PatternKeyword { range: 4768..4771, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 4768..4769, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 4770..4771, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4770..4771, value: Int( 0, @@ -6387,9 +7350,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4783..4786, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4783..4786, }, ), @@ -6399,11 +7364,14 @@ Module( }, MatchCase { range: 4791..4822, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4796..4809, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4796..4803, id: Name("Point2D"), ctx: Load, @@ -6411,12 +7379,15 @@ Module( ), arguments: PatternArguments { range: 4803..4809, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4804..4805, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4804..4805, value: Int( 0, @@ -6428,8 +7399,10 @@ Module( MatchValue( PatternMatchValue { range: 4807..4808, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4807..4808, value: Int( 1, @@ -6447,9 +7420,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4819..4822, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4819..4822, }, ), @@ -6459,11 +7434,14 @@ Module( }, MatchCase { range: 4827..4865, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4832..4852, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4832..4839, id: Name("Point2D"), ctx: Load, @@ -6471,16 +7449,20 @@ Module( ), arguments: PatternArguments { range: 4839..4852, + node_index: AtomicNodeIndex(..), patterns: [ MatchSequence( PatternMatchSequence { range: 4840..4846, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4841..4842, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4841..4842, value: Int( 0, @@ -6492,8 +7474,10 @@ Module( MatchValue( PatternMatchValue { range: 4844..4845, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4844..4845, value: Int( 1, @@ -6509,15 +7493,19 @@ Module( keywords: [ PatternKeyword { range: 4848..4851, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 4848..4849, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 4850..4851, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4850..4851, value: Int( 1, @@ -6535,9 +7523,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4862..4865, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4862..4865, }, ), @@ -6547,11 +7537,14 @@ Module( }, MatchCase { range: 4870..4910, + node_index: AtomicNodeIndex(..), pattern: MatchClass( PatternMatchClass { range: 4875..4897, + node_index: AtomicNodeIndex(..), cls: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4875..4882, id: Name("Point2D"), ctx: Load, @@ -6559,23 +7552,29 @@ Module( ), arguments: PatternArguments { range: 4882..4897, + node_index: AtomicNodeIndex(..), patterns: [], keywords: [ PatternKeyword { range: 4883..4891, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("x"), range: 4883..4884, + node_index: AtomicNodeIndex(..), }, pattern: MatchSequence( PatternMatchSequence { range: 4885..4891, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 4886..4887, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4886..4887, value: Int( 0, @@ -6587,8 +7586,10 @@ Module( MatchValue( PatternMatchValue { range: 4889..4890, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4889..4890, value: Int( 1, @@ -6603,15 +7604,19 @@ Module( }, PatternKeyword { range: 4893..4896, + node_index: AtomicNodeIndex(..), attr: Identifier { id: Name("y"), range: 4893..4894, + node_index: AtomicNodeIndex(..), }, pattern: MatchValue( PatternMatchValue { range: 4895..4896, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4895..4896, value: Int( 1, @@ -6629,9 +7634,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4907..4910, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4907..4910, }, ), @@ -6644,12 +7651,15 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 4934..5028, subject: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 4940..4946, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4940..4941, id: Name("x"), ctx: Store, @@ -6657,6 +7667,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 4945..4946, id: Name("b"), ctx: Load, @@ -6667,12 +7678,15 @@ Module( cases: [ MatchCase { range: 4952..4976, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 4957..4963, + node_index: AtomicNodeIndex(..), keys: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 4958..4959, value: Int( 1, @@ -6684,6 +7698,7 @@ Module( MatchAs( PatternMatchAs { range: 4961..4962, + node_index: AtomicNodeIndex(..), pattern: None, name: None, }, @@ -6696,9 +7711,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 4973..4976, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 4973..4976, }, ), @@ -6708,17 +7725,21 @@ Module( }, MatchCase { range: 4981..5028, + node_index: AtomicNodeIndex(..), pattern: MatchMapping( PatternMatchMapping { range: 4986..5015, + node_index: AtomicNodeIndex(..), keys: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 4987..4989, value: StringLiteralValue { inner: Single( StringLiteral { range: 4987..4989, + node_index: AtomicNodeIndex(..), value: "", flags: StringLiteralFlags { quote_style: Single, @@ -6732,6 +7753,7 @@ Module( ), NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 4994..4998, }, ), @@ -6740,11 +7762,13 @@ Module( MatchAs( PatternMatchAs { range: 4991..4992, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 4991..4992, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6752,12 +7776,15 @@ Module( MatchSequence( PatternMatchSequence { range: 5000..5006, + node_index: AtomicNodeIndex(..), patterns: [ MatchValue( PatternMatchValue { range: 5001..5002, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5001..5002, value: Int( 1, @@ -6769,8 +7796,10 @@ Module( MatchValue( PatternMatchValue { range: 5004..5005, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5004..5005, value: Int( 2, @@ -6787,6 +7816,7 @@ Module( Identifier { id: Name("rest"), range: 5010..5014, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6795,9 +7825,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5025..5028, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5025..5028, }, ), @@ -6810,9 +7842,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 5046..5106, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5052..5053, id: Name("y"), ctx: Load, @@ -6821,14 +7855,17 @@ Module( cases: [ MatchCase { range: 5059..5080, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 5064..5065, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("a"), range: 5064..5065, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6836,9 +7873,11 @@ Module( guard: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 5069..5075, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5069..5070, id: Name("b"), ctx: Store, @@ -6846,6 +7885,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5074..5075, id: Name("c"), ctx: Load, @@ -6857,9 +7897,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5077..5080, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5077..5080, }, ), @@ -6869,14 +7911,17 @@ Module( }, MatchCase { range: 5085..5106, + node_index: AtomicNodeIndex(..), pattern: MatchAs( PatternMatchAs { range: 5090..5091, + node_index: AtomicNodeIndex(..), pattern: None, name: Some( Identifier { id: Name("e"), range: 5090..5091, + node_index: AtomicNodeIndex(..), }, ), }, @@ -6884,9 +7929,11 @@ Module( guard: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 5096..5101, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5096..5097, value: Int( 1, @@ -6899,6 +7946,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5100..5101, value: Int( 2, @@ -6912,9 +7960,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5103..5106, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 5103..5106, }, ), @@ -6927,19 +7977,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5135..5150, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5135..5150, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5135..5147, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5135..5143, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5135..5140, id: Name("match"), ctx: Load, @@ -6948,6 +8003,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5142..5143, id: Name("a"), ctx: Load, @@ -6958,6 +8014,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5146..5147, id: Name("b"), ctx: Load, @@ -6967,6 +8024,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5149..5150, id: Name("c"), ctx: Load, @@ -6981,16 +8039,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5176..5193, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5176..5193, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5176..5190, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5176..5181, id: Name("match"), ctx: Load, @@ -6999,9 +8061,11 @@ Module( op: Mult, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5184..5189, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5184..5185, id: Name("a"), ctx: Load, @@ -7010,6 +8074,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5188..5189, id: Name("b"), ctx: Load, @@ -7021,6 +8086,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5192..5193, id: Name("c"), ctx: Load, @@ -7035,12 +8101,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5219..5236, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5219..5236, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5219..5224, id: Name("match"), ctx: Load, @@ -7048,15 +8117,19 @@ Module( ), arguments: Arguments { range: 5225..5236, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 5226..5232, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5227..5232, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5227..5228, id: Name("a"), ctx: Load, @@ -7065,6 +8138,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5231..5232, id: Name("b"), ctx: Load, @@ -7077,6 +8151,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5234..5235, id: Name("c"), ctx: Load, @@ -7091,15 +8166,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5263..5279, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5263..5279, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5263..5275, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5263..5268, id: Name("match"), ctx: Load, @@ -7108,9 +8187,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5270..5275, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5270..5271, id: Name("a"), ctx: Load, @@ -7119,6 +8200,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5274..5275, id: Name("b"), ctx: Load, @@ -7131,6 +8213,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5278..5279, id: Name("c"), ctx: Load, @@ -7142,15 +8225,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5306..5324, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5306..5324, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5306..5320, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5306..5311, id: Name("match"), ctx: Load, @@ -7159,9 +8246,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5314..5319, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5314..5315, id: Name("a"), ctx: Load, @@ -7170,6 +8259,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5318..5319, id: Name("b"), ctx: Load, @@ -7182,6 +8272,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5323..5324, id: Name("c"), ctx: Load, @@ -7193,18 +8284,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5351..5369, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5351..5369, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 5351..5365, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5351..5361, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5351..5356, id: Name("match"), ctx: Load, @@ -7212,13 +8308,16 @@ Module( ), arguments: Arguments { range: 5357..5361, + node_index: AtomicNodeIndex(..), args: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 5358..5360, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5359..5360, id: Name("a"), ctx: Load, @@ -7234,6 +8333,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5364..5365, id: Name("b"), ctx: Load, @@ -7244,6 +8344,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5368..5369, id: Name("c"), ctx: Load, @@ -7255,15 +8356,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5397..5407, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5397..5407, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5397..5405, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5397..5402, id: Name("match"), ctx: Load, @@ -7271,6 +8376,7 @@ Module( ), arguments: Arguments { range: 5403..5405, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -7279,6 +8385,7 @@ Module( attr: Identifier { id: Name("a"), range: 5406..5407, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7287,15 +8394,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5424..5436, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5424..5436, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5424..5434, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5424..5429, id: Name("match"), ctx: Load, @@ -7303,9 +8414,11 @@ Module( ), arguments: Arguments { range: 5430..5434, + node_index: AtomicNodeIndex(..), args: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5431..5433, elts: [], ctx: Load, @@ -7320,6 +8433,7 @@ Module( attr: Identifier { id: Name("a"), range: 5435..5436, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7328,15 +8442,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5455..5468, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5455..5468, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5455..5466, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5455..5460, id: Name("match"), ctx: Load, @@ -7344,9 +8462,11 @@ Module( ), arguments: Arguments { range: 5461..5466, + node_index: AtomicNodeIndex(..), args: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5462..5464, elts: [], ctx: Load, @@ -7361,6 +8481,7 @@ Module( attr: Identifier { id: Name("a"), range: 5467..5468, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7369,15 +8490,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5487..5498, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5487..5498, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 5487..5496, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5487..5492, id: Name("match"), ctx: Load, @@ -7385,6 +8510,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5494..5495, id: Name("a"), ctx: Load, @@ -7396,6 +8522,7 @@ Module( attr: Identifier { id: Name("b"), range: 5497..5498, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7404,15 +8531,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5516..5528, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5516..5528, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 5516..5526, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5516..5521, id: Name("match"), ctx: Load, @@ -7420,10 +8551,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5523..5525, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5523..5524, id: Name("a"), ctx: Load, @@ -7440,6 +8573,7 @@ Module( attr: Identifier { id: Name("b"), range: 5527..5528, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7448,15 +8582,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5569..5583, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 5569..5583, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 5569..5581, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5569..5574, id: Name("match"), ctx: Load, @@ -7464,10 +8602,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 5576..5580, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5577..5578, id: Name("a"), ctx: Load, @@ -7484,6 +8624,7 @@ Module( attr: Identifier { id: Name("b"), range: 5582..5583, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -7492,15 +8633,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5604..5621, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 5604..5621, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5604..5611, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5604..5609, id: Name("match"), ctx: Load, @@ -7508,6 +8653,7 @@ Module( ), arguments: Arguments { range: 5609..5611, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -7515,10 +8661,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 5612..5620, lower: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5612..5613, id: Name("a"), ctx: Load, @@ -7528,6 +8676,7 @@ Module( upper: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5619..5620, id: Name("b"), ctx: Load, @@ -7544,12 +8693,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 5641..5660, test: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 5644..5654, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5644..5649, id: Name("match"), ctx: Store, @@ -7557,6 +8709,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5653..5654, value: Int( 1, @@ -7568,6 +8721,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 5656..5660, }, ), @@ -7577,9 +8731,11 @@ Module( ), Match( StmtMatch { + node_index: AtomicNodeIndex(..), range: 5661..5715, subject: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5667..5672, id: Name("match"), ctx: Load, @@ -7588,11 +8744,14 @@ Module( cases: [ MatchCase { range: 5678..5690, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 5683..5684, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5683..5684, value: Int( 1, @@ -7605,6 +8764,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 5686..5690, }, ), @@ -7612,11 +8772,14 @@ Module( }, MatchCase { range: 5695..5715, + node_index: AtomicNodeIndex(..), pattern: MatchValue( PatternMatchValue { range: 5700..5701, + node_index: AtomicNodeIndex(..), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5700..5701, value: Int( 2, @@ -7629,6 +8792,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 5711..5715, }, ), @@ -7639,10 +8803,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 5716..5752, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5716..5721, id: Name("match"), ctx: Store, @@ -7651,19 +8817,26 @@ Module( ], value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 5724..5752, parameters: Some( Parameters { range: 5731..5736, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 5731..5736, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 5731..5736, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("query"), range: 5731..5736, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -7677,9 +8850,11 @@ Module( ), body: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 5738..5752, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5738..5743, id: Name("query"), ctx: Load, @@ -7691,6 +8866,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5747..5752, id: Name("event"), ctx: Load, @@ -7705,12 +8881,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 5753..5769, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5753..5769, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5753..5758, id: Name("print"), ctx: Load, @@ -7718,12 +8897,15 @@ Module( ), arguments: Arguments { range: 5758..5769, + node_index: AtomicNodeIndex(..), args: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 5759..5768, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5759..5764, id: Name("match"), ctx: Load, @@ -7731,9 +8913,11 @@ Module( ), arguments: Arguments { range: 5764..5768, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 5765..5767, value: Int( 12, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__raise.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__raise.py.snap index 1796953d2ae17c..7329f1e3a9af87 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__raise.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__raise.py.snap @@ -1,17 +1,18 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/raise.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..289, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 8..13, exc: None, cause: None, @@ -19,10 +20,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 14..21, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 20..21, id: Name("a"), ctx: Load, @@ -34,14 +37,17 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 22..34, exc: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 28..34, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 29..30, id: Name("a"), ctx: Load, @@ -49,6 +55,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 32..33, id: Name("b"), ctx: Load, @@ -65,13 +72,16 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 35..46, exc: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 41..46, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 41..42, value: Int( 1, @@ -84,6 +94,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 45..46, value: Int( 2, @@ -99,15 +110,18 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 47..60, exc: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 53..60, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..54, id: Name("a"), ctx: Load, @@ -115,6 +129,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..60, id: Name("b"), ctx: Load, @@ -129,23 +144,31 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 61..78, exc: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 67..78, parameters: Some( Parameters { range: 74..75, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 74..75, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 74..75, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 74..75, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -159,6 +182,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..78, id: Name("y"), ctx: Load, @@ -172,13 +196,16 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 79..92, exc: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 85..92, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 91..92, id: Name("x"), ctx: Load, @@ -192,19 +219,23 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 93..115, exc: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 99..115, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 104..108, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 99..100, id: Name("x"), ctx: Load, @@ -212,6 +243,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 114..115, id: Name("y"), ctx: Load, @@ -225,10 +257,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 138..152, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 144..145, id: Name("x"), ctx: Load, @@ -238,6 +272,7 @@ Module( cause: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("a"), ctx: Load, @@ -248,10 +283,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 153..172, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 159..160, id: Name("x"), ctx: Load, @@ -261,10 +298,12 @@ Module( cause: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 166..172, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 167..168, id: Name("a"), ctx: Load, @@ -272,6 +311,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("b"), ctx: Load, @@ -287,10 +327,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 173..191, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 179..180, id: Name("x"), ctx: Load, @@ -300,9 +342,11 @@ Module( cause: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 186..191, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 186..187, value: Int( 1, @@ -315,6 +359,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 190..191, value: Int( 2, @@ -329,10 +374,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 192..212, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 198..199, id: Name("x"), ctx: Load, @@ -342,11 +389,13 @@ Module( cause: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 205..212, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 205..206, id: Name("a"), ctx: Load, @@ -354,6 +403,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 211..212, id: Name("b"), ctx: Load, @@ -367,10 +417,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 213..237, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 219..220, id: Name("x"), ctx: Load, @@ -380,19 +432,26 @@ Module( cause: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 226..237, parameters: Some( Parameters { range: 233..234, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 233..234, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 233..234, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 233..234, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -406,6 +465,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("y"), ctx: Load, @@ -418,10 +478,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 238..258, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 244..245, id: Name("x"), ctx: Load, @@ -431,9 +493,11 @@ Module( cause: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 251..258, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 257..258, id: Name("x"), ctx: Load, @@ -446,10 +510,12 @@ Module( ), Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 259..288, exc: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 265..266, id: Name("x"), ctx: Load, @@ -459,15 +525,18 @@ Module( cause: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 272..288, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 277..281, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 272..273, id: Name("x"), ctx: Load, @@ -475,6 +544,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 287..288, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__return.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__return.py.snap index 162b1c9c347de2..7cb3690eadb006 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__return.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__return.py.snap @@ -7,20 +7,24 @@ input_file: crates/ruff_python_parser/resources/valid/statement/return.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..167, body: [ Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 0..6, value: None, }, ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 7..15, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..15, id: Name("x"), ctx: Load, @@ -31,17 +35,21 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 16..29, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 23..29, elts: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 23..25, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 24..25, id: Name("x"), ctx: Load, @@ -52,9 +60,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 27..29, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..29, id: Name("y"), ctx: Load, @@ -73,13 +83,16 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 30..45, value: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 38..44, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..39, id: Name("x"), ctx: Store, @@ -87,6 +100,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 43..44, value: Int( 1, @@ -100,10 +114,12 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 46..57, value: Some( NoneLiteral( ExprNoneLiteral { + node_index: AtomicNodeIndex(..), range: 53..57, }, ), @@ -112,15 +128,18 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 58..72, value: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 65..72, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 65..66, id: Name("x"), ctx: Load, @@ -128,6 +147,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 71..72, id: Name("y"), ctx: Load, @@ -141,13 +161,16 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 73..85, value: Some( Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 80..85, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 80..81, value: Int( 1, @@ -160,6 +183,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 84..85, value: Int( 2, @@ -174,14 +198,17 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 86..98, value: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 93..98, elts: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 93..94, value: Int( 1, @@ -190,6 +217,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 96..97, value: Int( 2, @@ -206,13 +234,16 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 99..112, value: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 106..112, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..110, id: Name("call"), ctx: Load, @@ -220,6 +251,7 @@ Module( ), arguments: Arguments { range: 110..112, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -230,16 +262,20 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 113..132, value: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 120..132, func: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 120..130, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..124, id: Name("attr"), ctx: Load, @@ -248,12 +284,14 @@ Module( attr: Identifier { id: Name("value"), range: 125..130, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, ), arguments: Arguments { range: 130..132, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -264,13 +302,16 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 133..147, value: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 140..147, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 146..147, id: Name("x"), ctx: Load, @@ -283,23 +324,31 @@ Module( ), Return( StmtReturn { + node_index: AtomicNodeIndex(..), range: 148..166, value: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 155..166, parameters: Some( Parameters { range: 162..163, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 162..163, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 162..163, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 162..163, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -313,6 +362,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("y"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__simple.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__simple.py.snap index 31c90f21190e9d..3865245dce0f4b 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__simple.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__simple.py.snap @@ -1,30 +1,34 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/simple.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..172, body: [ Continue( StmtContinue { + node_index: AtomicNodeIndex(..), range: 61..69, }, ), Break( StmtBreak { + node_index: AtomicNodeIndex(..), range: 70..75, }, ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 77..86, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 80..81, id: Name("x"), ctx: Load, @@ -33,9 +37,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 83..86, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 83..86, }, ), @@ -47,9 +53,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 87..100, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 90..94, value: true, }, @@ -57,6 +65,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 96..100, }, ), @@ -66,9 +75,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 101..102, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 101..102, value: Int( 1, @@ -79,9 +90,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 104..105, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 104..105, value: Int( 2, @@ -92,14 +105,17 @@ Module( ), Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 107..111, }, ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 112..113, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 112..113, value: Int( 1, @@ -110,9 +126,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 115..118, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 115..118, }, ), @@ -120,12 +138,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 120..133, value: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 120..133, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 125..126, id: Name("b"), ctx: Load, @@ -133,6 +154,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 120..121, id: Name("a"), ctx: Load, @@ -140,6 +162,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 132..133, id: Name("c"), ctx: Load, @@ -151,9 +174,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 135..157, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 138..139, id: Name("c"), ctx: Load, @@ -162,9 +187,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 141..142, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 141..142, id: Name("B"), ctx: Load, @@ -174,10 +201,12 @@ Module( ), Delete( StmtDelete { + node_index: AtomicNodeIndex(..), range: 144..149, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 148..149, id: Name("A"), ctx: Del, @@ -190,13 +219,16 @@ Module( elif_else_clauses: [ ElifElseClause { range: 150..157, + node_index: AtomicNodeIndex(..), test: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 156..157, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 156..157, id: Name("C"), ctx: Load, @@ -211,9 +243,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 158..171, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 161..162, id: Name("x"), ctx: Load, @@ -222,13 +256,16 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 164..171, value: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 164..171, value: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 170..171, id: Name("x"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap index 2413f26d906f0d..e6021299e98754 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__try.py.snap @@ -7,17 +7,21 @@ input_file: crates/ruff_python_parser/resources/valid/statement/try.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..1223, body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 0..28, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 9..12, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 9..12, }, ), @@ -28,14 +32,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 13..28, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 25..28, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 25..28, }, ), @@ -52,13 +59,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 30..106, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 39..42, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 39..42, }, ), @@ -69,9 +79,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 43..74, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 50..60, id: Name("Exception1"), ctx: Load, @@ -82,14 +94,17 @@ Module( Identifier { id: Name("e"), range: 64..65, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 71..74, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 71..74, }, ), @@ -101,9 +116,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 75..106, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..92, id: Name("Exception2"), ctx: Load, @@ -114,14 +131,17 @@ Module( Identifier { id: Name("e"), range: 96..97, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 103..106, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 103..106, }, ), @@ -138,13 +158,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 108..184, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 117..120, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 117..120, }, ), @@ -155,9 +178,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 121..151, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 128..137, id: Name("Exception"), ctx: Load, @@ -168,14 +193,17 @@ Module( Identifier { id: Name("e"), range: 141..142, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..151, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 148..151, }, ), @@ -187,14 +215,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 152..167, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 164..167, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 164..167, }, ), @@ -208,9 +239,11 @@ Module( finalbody: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 181..184, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 181..184, }, ), @@ -222,13 +255,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 186..228, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 195..198, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 195..198, }, ), @@ -239,14 +275,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 199..214, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 211..214, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 211..214, }, ), @@ -259,9 +298,11 @@ Module( orelse: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 225..228, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 225..228, }, ), @@ -274,13 +315,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 230..289, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 239..242, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 239..242, }, ), @@ -291,14 +335,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 243..258, + node_index: AtomicNodeIndex(..), type_: None, name: None, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 255..258, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 255..258, }, ), @@ -311,9 +358,11 @@ Module( orelse: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 269..272, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 269..272, }, ), @@ -323,9 +372,11 @@ Module( finalbody: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 286..289, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 286..289, }, ), @@ -337,13 +388,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 291..320, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 300..303, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 300..303, }, ), @@ -355,9 +409,11 @@ Module( finalbody: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 317..320, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 317..320, }, ), @@ -369,13 +425,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 322..365, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 331..334, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 331..334, }, ), @@ -386,9 +445,11 @@ Module( orelse: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 345..348, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 345..348, }, ), @@ -398,9 +459,11 @@ Module( finalbody: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 362..365, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 362..365, }, ), @@ -412,13 +475,16 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 367..441, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 376..379, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 376..379, }, ), @@ -429,9 +495,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 380..409, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 388..394, id: Name("GroupA"), ctx: Load, @@ -442,14 +510,17 @@ Module( Identifier { id: Name("eg"), range: 398..400, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 406..409, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 406..409, }, ), @@ -461,9 +532,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 410..441, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 418..432, id: Name("ExceptionGroup"), ctx: Load, @@ -474,9 +547,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 438..441, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 438..441, }, ), @@ -493,17 +568,21 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 443..577, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 452..471, exc: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 458..471, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 458..468, id: Name("ValueError"), ctx: Load, @@ -511,9 +590,11 @@ Module( ), arguments: Arguments { range: 468..471, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 469..470, value: Int( 1, @@ -534,9 +615,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 472..525, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 479..488, id: Name("TypeError"), ctx: Load, @@ -547,17 +630,21 @@ Module( Identifier { id: Name("e"), range: 492..493, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 499..525, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 499..525, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 499..504, id: Name("print"), ctx: Load, @@ -565,30 +652,37 @@ Module( ), arguments: Arguments { range: 504..525, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 505..524, value: FStringValue { inner: Single( FString( FString { range: 505..524, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 507..514, + node_index: AtomicNodeIndex(..), value: "caught ", }, ), Interpolation( InterpolatedElement { range: 514..523, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 515..522, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 515..519, id: Name("type"), ctx: Load, @@ -596,9 +690,11 @@ Module( ), arguments: Arguments { range: 519..522, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 520..521, id: Name("e"), ctx: Load, @@ -639,9 +735,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 526..577, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 533..540, id: Name("OSError"), ctx: Load, @@ -652,17 +750,21 @@ Module( Identifier { id: Name("e"), range: 544..545, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 551..577, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 551..577, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 551..556, id: Name("print"), ctx: Load, @@ -670,30 +772,37 @@ Module( ), arguments: Arguments { range: 556..577, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 557..576, value: FStringValue { inner: Single( FString( FString { range: 557..576, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 559..566, + node_index: AtomicNodeIndex(..), value: "caught ", }, ), Interpolation( InterpolatedElement { range: 566..575, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 567..574, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 567..571, id: Name("type"), ctx: Load, @@ -701,9 +810,11 @@ Module( ), arguments: Arguments { range: 571..574, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 572..573, id: Name("e"), ctx: Load, @@ -749,17 +860,21 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 579..831, body: [ Raise( StmtRaise { + node_index: AtomicNodeIndex(..), range: 588..669, exc: Some( Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 594..669, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 594..608, id: Name("ExceptionGroup"), ctx: Load, @@ -767,14 +882,17 @@ Module( ), arguments: Arguments { range: 608..669, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 609..613, value: StringLiteralValue { inner: Single( StringLiteral { range: 609..613, + node_index: AtomicNodeIndex(..), value: "eg", flags: StringLiteralFlags { quote_style: Double, @@ -788,13 +906,16 @@ Module( ), List( ExprList { + node_index: AtomicNodeIndex(..), range: 615..668, elts: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 616..629, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 616..626, id: Name("ValueError"), ctx: Load, @@ -802,9 +923,11 @@ Module( ), arguments: Arguments { range: 626..629, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 627..628, value: Int( 1, @@ -818,9 +941,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 631..643, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 631..640, id: Name("TypeError"), ctx: Load, @@ -828,9 +953,11 @@ Module( ), arguments: Arguments { range: 640..643, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 641..642, value: Int( 2, @@ -844,9 +971,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 645..655, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 645..652, id: Name("OSError"), ctx: Load, @@ -854,9 +983,11 @@ Module( ), arguments: Arguments { range: 652..655, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 653..654, value: Int( 3, @@ -870,9 +1001,11 @@ Module( ), Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 657..667, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 657..664, id: Name("OSError"), ctx: Load, @@ -880,9 +1013,11 @@ Module( ), arguments: Arguments { range: 664..667, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 665..666, value: Int( 4, @@ -912,9 +1047,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 670..751, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 678..687, id: Name("TypeError"), ctx: Load, @@ -925,17 +1062,21 @@ Module( Identifier { id: Name("e"), range: 691..692, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 698..751, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 698..751, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 698..703, id: Name("print"), ctx: Load, @@ -943,30 +1084,37 @@ Module( ), arguments: Arguments { range: 703..751, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 704..750, value: FStringValue { inner: Single( FString( FString { range: 704..750, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 706..713, + node_index: AtomicNodeIndex(..), value: "caught ", }, ), Interpolation( InterpolatedElement { range: 713..722, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 714..721, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 714..718, id: Name("type"), ctx: Load, @@ -974,9 +1122,11 @@ Module( ), arguments: Arguments { range: 718..721, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 719..720, id: Name("e"), ctx: Load, @@ -995,17 +1145,21 @@ Module( Literal( InterpolatedStringLiteralElement { range: 722..735, + node_index: AtomicNodeIndex(..), value: " with nested ", }, ), Interpolation( InterpolatedElement { range: 735..749, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 736..748, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 736..737, id: Name("e"), ctx: Load, @@ -1014,6 +1168,7 @@ Module( attr: Identifier { id: Name("exceptions"), range: 738..748, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -1048,9 +1203,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 752..831, + node_index: AtomicNodeIndex(..), type_: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 760..767, id: Name("OSError"), ctx: Load, @@ -1061,17 +1218,21 @@ Module( Identifier { id: Name("e"), range: 771..772, + node_index: AtomicNodeIndex(..), }, ), body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 778..831, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 778..831, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 778..783, id: Name("print"), ctx: Load, @@ -1079,30 +1240,37 @@ Module( ), arguments: Arguments { range: 783..831, + node_index: AtomicNodeIndex(..), args: [ FString( ExprFString { + node_index: AtomicNodeIndex(..), range: 784..830, value: FStringValue { inner: Single( FString( FString { range: 784..830, + node_index: AtomicNodeIndex(..), elements: [ Literal( InterpolatedStringLiteralElement { range: 786..793, + node_index: AtomicNodeIndex(..), value: "caught ", }, ), Interpolation( InterpolatedElement { range: 793..802, + node_index: AtomicNodeIndex(..), expression: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 794..801, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 794..798, id: Name("type"), ctx: Load, @@ -1110,9 +1278,11 @@ Module( ), arguments: Arguments { range: 798..801, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 799..800, id: Name("e"), ctx: Load, @@ -1131,17 +1301,21 @@ Module( Literal( InterpolatedStringLiteralElement { range: 802..815, + node_index: AtomicNodeIndex(..), value: " with nested ", }, ), Interpolation( InterpolatedElement { range: 815..829, + node_index: AtomicNodeIndex(..), expression: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 816..828, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 816..817, id: Name("e"), ctx: Load, @@ -1150,6 +1324,7 @@ Module( attr: Identifier { id: Name("exceptions"), range: 818..828, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -1189,10 +1364,12 @@ Module( ), Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 833..1075, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 842..846, }, ), @@ -1201,14 +1378,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 847..875, + node_index: AtomicNodeIndex(..), type_: Some( StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 854..865, value: StringLiteralValue { inner: Single( StringLiteral { range: 854..865, + node_index: AtomicNodeIndex(..), value: "exception", flags: StringLiteralFlags { quote_style: Double, @@ -1225,6 +1405,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 871..875, }, ), @@ -1234,9 +1415,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 876..894, + node_index: AtomicNodeIndex(..), type_: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 883..884, value: Int( 1, @@ -1248,6 +1431,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 890..894, }, ), @@ -1257,9 +1441,11 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 895..916, + node_index: AtomicNodeIndex(..), type_: Some( BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 902..906, value: true, }, @@ -1269,6 +1455,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 912..916, }, ), @@ -1278,12 +1465,15 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 917..939, + node_index: AtomicNodeIndex(..), type_: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 924..929, left: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 924..925, value: Int( 1, @@ -1293,6 +1483,7 @@ Module( op: Add, right: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 928..929, value: Int( 1, @@ -1306,6 +1497,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 935..939, }, ), @@ -1315,12 +1507,15 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 940..962, + node_index: AtomicNodeIndex(..), type_: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 947..952, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 947..948, id: Name("a"), ctx: Load, @@ -1329,6 +1524,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 951..952, id: Name("b"), ctx: Load, @@ -1341,6 +1537,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 958..962, }, ), @@ -1350,14 +1547,17 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 963..987, + node_index: AtomicNodeIndex(..), type_: Some( BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 970..977, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 970..971, id: Name("x"), ctx: Load, @@ -1365,6 +1565,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 976..977, id: Name("y"), ctx: Load, @@ -1378,6 +1579,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 983..987, }, ), @@ -1387,12 +1589,15 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 988..1012, + node_index: AtomicNodeIndex(..), type_: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 995..1002, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1001..1002, id: Name("x"), ctx: Load, @@ -1405,6 +1610,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1008..1012, }, ), @@ -1414,22 +1620,30 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 1013..1041, + node_index: AtomicNodeIndex(..), type_: Some( Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 1020..1031, parameters: Some( Parameters { range: 1027..1028, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1027..1028, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1027..1028, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 1027..1028, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -1443,6 +1657,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1030..1031, id: Name("x"), ctx: Load, @@ -1455,6 +1670,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1037..1041, }, ), @@ -1464,18 +1680,22 @@ Module( ExceptHandler( ExceptHandlerExceptHandler { range: 1042..1075, + node_index: AtomicNodeIndex(..), type_: Some( If( ExprIf { + node_index: AtomicNodeIndex(..), range: 1049..1065, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 1054..1058, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1049..1050, id: Name("x"), ctx: Load, @@ -1483,6 +1703,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1064..1065, id: Name("y"), ctx: Load, @@ -1495,6 +1716,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1071..1075, }, ), @@ -1509,9 +1731,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 1077..1222, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 1080..1084, value: true, }, @@ -1519,10 +1743,12 @@ Module( body: [ Try( StmtTry { + node_index: AtomicNodeIndex(..), range: 1090..1133, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1103..1107, }, ), @@ -1532,6 +1758,7 @@ Module( finalbody: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1129..1133, }, ), @@ -1543,10 +1770,12 @@ Module( elif_else_clauses: [ ElifElseClause { range: 1208..1222, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1218..1222, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__type.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__type.py.snap index 817ab6399e0e53..ca837dcde37868 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__type.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__type.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/type.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..1828, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..12, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,6 +25,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 9..12, id: Name("int"), ctx: Load, @@ -32,9 +35,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 13..31, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 18..19, id: Name("X"), ctx: Store, @@ -43,9 +48,11 @@ Module( type_params: None, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 22..31, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 22..25, id: Name("int"), ctx: Load, @@ -54,6 +61,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 28..31, id: Name("str"), ctx: Load, @@ -65,9 +73,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 32..60, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 37..38, id: Name("X"), ctx: Store, @@ -76,9 +86,11 @@ Module( type_params: None, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 41..60, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 41..44, id: Name("int"), ctx: Load, @@ -87,11 +99,13 @@ Module( op: BitOr, right: StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 47..60, value: StringLiteralValue { inner: Single( StringLiteral { range: 47..60, + node_index: AtomicNodeIndex(..), value: "ForwardRefY", flags: StringLiteralFlags { quote_style: Double, @@ -109,9 +123,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 61..87, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..67, id: Name("X"), ctx: Store, @@ -120,13 +136,16 @@ Module( type_params: Some( TypeParams { range: 67..70, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 68..69, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 68..69, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -137,9 +156,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 73..87, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("T"), ctx: Load, @@ -148,9 +169,11 @@ Module( op: BitOr, right: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 77..87, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 77..81, id: Name("list"), ctx: Load, @@ -158,9 +181,11 @@ Module( ), slice: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 82..86, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 82..83, id: Name("X"), ctx: Load, @@ -168,6 +193,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..85, id: Name("T"), ctx: Load, @@ -185,9 +211,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 101..116, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 106..107, id: Name("X"), ctx: Store, @@ -196,13 +224,16 @@ Module( type_params: Some( TypeParams { range: 107..110, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 108..109, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 108..109, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -213,6 +244,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 113..116, id: Name("int"), ctx: Load, @@ -222,9 +254,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 117..145, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 122..123, id: Name("X"), ctx: Store, @@ -233,13 +267,16 @@ Module( type_params: Some( TypeParams { range: 123..126, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 124..125, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 124..125, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -250,12 +287,15 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 129..145, left: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 129..136, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..133, id: Name("list"), ctx: Load, @@ -263,6 +303,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 134..135, id: Name("T"), ctx: Load, @@ -274,9 +315,11 @@ Module( op: BitOr, right: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 139..145, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 139..142, id: Name("set"), ctx: Load, @@ -284,6 +327,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..144, id: Name("T"), ctx: Load, @@ -298,9 +342,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 146..178, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 151..152, id: Name("X"), ctx: Store, @@ -309,13 +355,16 @@ Module( type_params: Some( TypeParams { range: 152..165, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 153..154, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 153..154, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -324,9 +373,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 156..159, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 157..159, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -334,9 +385,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 161..164, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 163..164, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -346,10 +399,12 @@ Module( ), value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 168..178, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 169..170, id: Name("T"), ctx: Load, @@ -357,6 +412,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 172..174, id: Name("Ts"), ctx: Load, @@ -364,6 +420,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 176..177, id: Name("P"), ctx: Load, @@ -378,9 +435,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 179..216, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..185, id: Name("X"), ctx: Store, @@ -389,17 +448,21 @@ Module( type_params: Some( TypeParams { range: 185..203, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 186..192, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 186..187, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 189..192, id: Name("int"), ctx: Load, @@ -412,9 +475,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 194..197, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 195..197, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -422,9 +487,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 199..202, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 201..202, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -434,10 +501,12 @@ Module( ), value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 206..216, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 207..208, id: Name("T"), ctx: Load, @@ -445,6 +514,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 210..212, id: Name("Ts"), ctx: Load, @@ -452,6 +522,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("P"), ctx: Load, @@ -466,9 +537,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 217..261, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..223, id: Name("X"), ctx: Store, @@ -477,21 +550,26 @@ Module( type_params: Some( TypeParams { range: 223..248, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 224..237, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 224..225, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 227..237, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 228..231, id: Name("int"), ctx: Load, @@ -499,6 +577,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..236, id: Name("str"), ctx: Load, @@ -516,9 +595,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 239..242, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 240..242, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -526,9 +607,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 244..247, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 246..247, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -538,10 +621,12 @@ Module( ), value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 251..261, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 252..253, id: Name("T"), ctx: Load, @@ -549,6 +634,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 255..257, id: Name("Ts"), ctx: Load, @@ -556,6 +642,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 259..260, id: Name("P"), ctx: Load, @@ -570,9 +657,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 262..287, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 267..268, id: Name("X"), ctx: Store, @@ -581,18 +670,22 @@ Module( type_params: Some( TypeParams { range: 268..277, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 269..276, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 269..270, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 273..276, id: Name("int"), ctx: Load, @@ -606,9 +699,11 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 280..287, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 280..281, id: Name("T"), ctx: Load, @@ -617,6 +712,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 284..287, id: Name("str"), ctx: Load, @@ -628,9 +724,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 288..330, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 293..294, id: Name("X"), ctx: Store, @@ -639,20 +737,25 @@ Module( type_params: Some( TypeParams { range: 294..314, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 295..313, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 295..296, + node_index: AtomicNodeIndex(..), }, bound: Some( BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 298..307, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 298..301, id: Name("int"), ctx: Load, @@ -661,6 +764,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 304..307, id: Name("str"), ctx: Load, @@ -672,6 +776,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 310..313, id: Name("int"), ctx: Load, @@ -685,12 +790,15 @@ Module( ), value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 317..330, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 317..324, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 317..318, id: Name("T"), ctx: Load, @@ -699,6 +807,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 321..324, id: Name("int"), ctx: Load, @@ -709,6 +818,7 @@ Module( op: BitOr, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 327..330, id: Name("str"), ctx: Load, @@ -720,9 +830,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 331..384, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 336..337, id: Name("X"), ctx: Store, @@ -731,23 +843,29 @@ Module( type_params: Some( TypeParams { range: 337..361, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 338..360, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 339..341, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 344..360, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 345..360, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 345..350, id: Name("tuple"), ctx: Load, @@ -755,10 +873,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 351..359, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 351..354, id: Name("int"), ctx: Load, @@ -766,6 +886,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 356..359, id: Name("str"), ctx: Load, @@ -790,9 +911,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 364..384, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 364..369, id: Name("tuple"), ctx: Load, @@ -800,10 +923,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 370..383, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 370..373, id: Name("int"), ctx: Load, @@ -811,9 +936,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 375..378, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 376..378, id: Name("Ts"), ctx: Load, @@ -824,6 +951,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 380..383, id: Name("str"), ctx: Load, @@ -841,9 +969,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 385..428, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 390..391, id: Name("X"), ctx: Store, @@ -852,21 +982,26 @@ Module( type_params: Some( TypeParams { range: 391..409, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 392..408, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 394..395, + node_index: AtomicNodeIndex(..), }, default: Some( List( ExprList { + node_index: AtomicNodeIndex(..), range: 398..408, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 399..402, id: Name("int"), ctx: Load, @@ -874,6 +1009,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 404..407, id: Name("str"), ctx: Load, @@ -891,9 +1027,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 412..428, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 412..420, id: Name("Callable"), ctx: Load, @@ -901,10 +1039,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 421..427, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 421..422, id: Name("P"), ctx: Load, @@ -912,6 +1052,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 424..427, id: Name("str"), ctx: Load, @@ -929,9 +1070,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 459..474, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 464..468, id: Name("type"), ctx: Store, @@ -940,6 +1083,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 471..474, id: Name("int"), ctx: Load, @@ -949,9 +1093,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 475..491, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 480..485, id: Name("match"), ctx: Store, @@ -960,6 +1106,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 488..491, id: Name("int"), ctx: Load, @@ -969,9 +1116,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 492..507, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 497..501, id: Name("case"), ctx: Store, @@ -980,6 +1129,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 504..507, id: Name("int"), ctx: Load, @@ -989,9 +1139,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 533..548, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 538..541, id: Name("foo"), ctx: Store, @@ -1000,6 +1152,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 544..548, id: Name("type"), ctx: Load, @@ -1009,9 +1162,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 549..565, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 554..557, id: Name("foo"), ctx: Store, @@ -1020,6 +1175,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 560..565, id: Name("match"), ctx: Load, @@ -1029,9 +1185,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 566..581, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 571..574, id: Name("foo"), ctx: Store, @@ -1040,6 +1198,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 577..581, id: Name("case"), ctx: Load, @@ -1049,9 +1208,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 605..620, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 613..614, id: Name("X"), ctx: Store, @@ -1060,6 +1221,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 617..620, id: Name("int"), ctx: Load, @@ -1069,9 +1231,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 621..636, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 626..627, id: Name("X"), ctx: Store, @@ -1080,6 +1244,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 633..636, id: Name("int"), ctx: Load, @@ -1089,9 +1254,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 637..652, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 642..643, id: Name("X"), ctx: Store, @@ -1100,6 +1267,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 649..652, id: Name("int"), ctx: Load, @@ -1109,9 +1277,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 653..673, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 658..659, id: Name("X"), ctx: Store, @@ -1120,6 +1290,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 668..671, id: Name("int"), ctx: Load, @@ -1129,9 +1300,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 674..693, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 685..686, id: Name("X"), ctx: Store, @@ -1140,13 +1313,16 @@ Module( type_params: Some( TypeParams { range: 686..689, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 687..688, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 687..688, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -1157,6 +1333,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 692..693, id: Name("T"), ctx: Load, @@ -1166,9 +1343,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 694..714, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 699..700, id: Name("X"), ctx: Store, @@ -1177,13 +1356,16 @@ Module( type_params: Some( TypeParams { range: 707..710, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 708..709, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 708..709, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -1194,6 +1376,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 713..714, id: Name("T"), ctx: Load, @@ -1203,9 +1386,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 715..734, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 720..721, id: Name("X"), ctx: Store, @@ -1214,13 +1399,16 @@ Module( type_params: Some( TypeParams { range: 721..724, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 722..723, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 722..723, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -1231,6 +1419,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 733..734, id: Name("T"), ctx: Load, @@ -1240,9 +1429,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 756..768, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 761..762, id: Name("X"), ctx: Store, @@ -1251,6 +1442,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 765..768, id: Name("int"), ctx: Load, @@ -1260,9 +1452,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 770..782, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 775..776, id: Name("X"), ctx: Store, @@ -1271,6 +1465,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 779..782, id: Name("str"), ctx: Load, @@ -1280,9 +1475,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 784..797, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 789..790, id: Name("X"), ctx: Store, @@ -1291,6 +1488,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 793..797, id: Name("type"), ctx: Load, @@ -1300,20 +1498,24 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 798..819, decorator_list: [], name: Identifier { id: Name("X"), range: 804..805, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: None, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 807..819, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 812..813, id: Name("X"), ctx: Store, @@ -1322,6 +1524,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 816..819, id: Name("int"), ctx: Load, @@ -1334,9 +1537,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 821..853, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 826..831, id: Name("Point"), ctx: Store, @@ -1345,9 +1550,11 @@ Module( type_params: None, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 834..853, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 834..839, id: Name("tuple"), ctx: Load, @@ -1355,10 +1562,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 840..852, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 840..845, id: Name("float"), ctx: Load, @@ -1366,6 +1575,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 847..852, id: Name("float"), ctx: Load, @@ -1383,9 +1593,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 854..881, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 859..864, id: Name("Point"), ctx: Store, @@ -1394,13 +1606,16 @@ Module( type_params: Some( TypeParams { range: 864..867, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 865..866, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 865..866, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -1411,9 +1626,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 870..881, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 870..875, id: Name("tuple"), ctx: Load, @@ -1421,10 +1638,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 876..880, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 876..877, id: Name("T"), ctx: Load, @@ -1432,6 +1651,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 879..880, id: Name("T"), ctx: Load, @@ -1449,9 +1669,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 882..918, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 887..894, id: Name("IntFunc"), ctx: Store, @@ -1460,13 +1682,16 @@ Module( type_params: Some( TypeParams { range: 894..899, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 895..898, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 897..898, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -1476,9 +1701,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 902..918, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 902..910, id: Name("Callable"), ctx: Load, @@ -1486,10 +1713,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 911..917, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 911..912, id: Name("P"), ctx: Load, @@ -1497,6 +1726,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 914..917, id: Name("int"), ctx: Load, @@ -1514,9 +1744,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 932..972, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 937..949, id: Name("LabeledTuple"), ctx: Store, @@ -1525,13 +1757,16 @@ Module( type_params: Some( TypeParams { range: 949..954, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 950..953, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 951..953, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -1541,9 +1776,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 957..972, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 957..962, id: Name("tuple"), ctx: Load, @@ -1551,10 +1788,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 963..971, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 963..966, id: Name("str"), ctx: Load, @@ -1562,9 +1801,11 @@ Module( ), Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 968..971, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 969..971, id: Name("Ts"), ctx: Load, @@ -1585,9 +1826,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 989..1037, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 994..1010, id: Name("HashableSequence"), ctx: Store, @@ -1596,17 +1839,21 @@ Module( type_params: Some( TypeParams { range: 1010..1023, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1011..1022, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1011..1012, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1014..1022, id: Name("Hashable"), ctx: Load, @@ -1621,9 +1868,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1026..1037, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1026..1034, id: Name("Sequence"), ctx: Load, @@ -1631,6 +1880,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1035..1036, id: Name("T"), ctx: Load, @@ -1643,9 +1893,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 1060..1110, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1065..1081, id: Name("IntOrStrSequence"), ctx: Store, @@ -1654,21 +1906,26 @@ Module( type_params: Some( TypeParams { range: 1081..1096, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 1082..1095, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 1082..1083, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1085..1095, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1086..1089, id: Name("int"), ctx: Load, @@ -1676,6 +1933,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1091..1094, id: Name("str"), ctx: Load, @@ -1695,9 +1953,11 @@ Module( ), value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1099..1110, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1099..1107, id: Name("Sequence"), ctx: Load, @@ -1705,6 +1965,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1108..1109, id: Name("T"), ctx: Load, @@ -1717,19 +1978,24 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1164..1178, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1164..1178, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1164..1175, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1164..1171, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1164..1168, id: Name("type"), ctx: Load, @@ -1738,6 +2004,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1170..1171, id: Name("a"), ctx: Load, @@ -1748,6 +2015,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1174..1175, id: Name("b"), ctx: Load, @@ -1757,6 +2025,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1177..1178, id: Name("c"), ctx: Load, @@ -1771,16 +2040,20 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1203..1219, value: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1203..1219, elts: [ BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1203..1216, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1203..1207, id: Name("type"), ctx: Load, @@ -1789,9 +2062,11 @@ Module( op: Mult, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1210..1215, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1210..1211, id: Name("a"), ctx: Load, @@ -1800,6 +2075,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1214..1215, id: Name("b"), ctx: Load, @@ -1811,6 +2087,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1218..1219, id: Name("c"), ctx: Load, @@ -1825,12 +2102,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1244..1260, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1244..1260, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1244..1248, id: Name("type"), ctx: Load, @@ -1838,15 +2118,19 @@ Module( ), arguments: Arguments { range: 1249..1260, + node_index: AtomicNodeIndex(..), args: [ Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 1250..1256, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1251..1256, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1251..1252, id: Name("a"), ctx: Load, @@ -1855,6 +2139,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1255..1256, id: Name("b"), ctx: Load, @@ -1867,6 +2152,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1258..1259, id: Name("c"), ctx: Load, @@ -1881,15 +2167,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1286..1301, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1286..1301, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1286..1297, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1286..1290, id: Name("type"), ctx: Load, @@ -1898,9 +2188,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1292..1297, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1292..1293, id: Name("a"), ctx: Load, @@ -1909,6 +2201,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1296..1297, id: Name("b"), ctx: Load, @@ -1921,6 +2214,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1300..1301, id: Name("c"), ctx: Load, @@ -1932,15 +2226,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1327..1344, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1327..1344, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1327..1340, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1327..1331, id: Name("type"), ctx: Load, @@ -1949,9 +2247,11 @@ Module( op: Sub, right: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1334..1339, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1334..1335, id: Name("a"), ctx: Load, @@ -1960,6 +2260,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1338..1339, id: Name("b"), ctx: Load, @@ -1972,6 +2273,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1343..1344, id: Name("c"), ctx: Load, @@ -1983,18 +2285,23 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1370..1387, value: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1370..1387, left: BinOp( ExprBinOp { + node_index: AtomicNodeIndex(..), range: 1370..1383, left: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1370..1379, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1370..1374, id: Name("type"), ctx: Load, @@ -2002,13 +2309,16 @@ Module( ), arguments: Arguments { range: 1375..1379, + node_index: AtomicNodeIndex(..), args: [ UnaryOp( ExprUnaryOp { + node_index: AtomicNodeIndex(..), range: 1376..1378, op: USub, operand: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1377..1378, id: Name("a"), ctx: Load, @@ -2024,6 +2334,7 @@ Module( op: Mult, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1382..1383, id: Name("b"), ctx: Load, @@ -2034,6 +2345,7 @@ Module( op: Add, right: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1386..1387, id: Name("c"), ctx: Load, @@ -2045,15 +2357,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1414..1423, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1414..1423, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1414..1421, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1414..1418, id: Name("type"), ctx: Load, @@ -2061,6 +2377,7 @@ Module( ), arguments: Arguments { range: 1419..1421, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -2069,6 +2386,7 @@ Module( attr: Identifier { id: Name("a"), range: 1422..1423, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2077,15 +2395,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1439..1450, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1439..1450, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1439..1448, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1439..1443, id: Name("type"), ctx: Load, @@ -2093,9 +2415,11 @@ Module( ), arguments: Arguments { range: 1444..1448, + node_index: AtomicNodeIndex(..), args: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1445..1447, elts: [], ctx: Load, @@ -2110,6 +2434,7 @@ Module( attr: Identifier { id: Name("a"), range: 1449..1450, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2118,15 +2443,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1468..1480, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1468..1480, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1468..1478, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1468..1472, id: Name("type"), ctx: Load, @@ -2134,9 +2463,11 @@ Module( ), arguments: Arguments { range: 1473..1478, + node_index: AtomicNodeIndex(..), args: [ Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1474..1476, elts: [], ctx: Load, @@ -2151,6 +2482,7 @@ Module( attr: Identifier { id: Name("a"), range: 1479..1480, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2159,15 +2491,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1498..1508, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1498..1508, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1498..1506, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1498..1502, id: Name("type"), ctx: Load, @@ -2175,6 +2511,7 @@ Module( ), slice: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1504..1505, id: Name("a"), ctx: Load, @@ -2186,6 +2523,7 @@ Module( attr: Identifier { id: Name("b"), range: 1507..1508, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2194,15 +2532,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1525..1536, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1525..1536, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1525..1534, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1525..1529, id: Name("type"), ctx: Load, @@ -2210,10 +2552,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1531..1533, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1531..1532, id: Name("a"), ctx: Load, @@ -2230,6 +2574,7 @@ Module( attr: Identifier { id: Name("b"), range: 1535..1536, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2238,15 +2583,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1575..1588, value: Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 1575..1588, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1575..1586, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1575..1579, id: Name("type"), ctx: Load, @@ -2254,10 +2603,12 @@ Module( ), slice: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 1581..1585, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1582..1583, id: Name("a"), ctx: Load, @@ -2274,6 +2625,7 @@ Module( attr: Identifier { id: Name("b"), range: 1587..1588, + node_index: AtomicNodeIndex(..), }, ctx: Load, }, @@ -2282,15 +2634,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1608..1624, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 1608..1624, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1608..1614, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1608..1612, id: Name("type"), ctx: Load, @@ -2298,6 +2654,7 @@ Module( ), arguments: Arguments { range: 1612..1614, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -2305,10 +2662,12 @@ Module( ), slice: Slice( ExprSlice { + node_index: AtomicNodeIndex(..), range: 1615..1623, lower: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1615..1616, id: Name("a"), ctx: Load, @@ -2318,6 +2677,7 @@ Module( upper: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1622..1623, id: Name("b"), ctx: Load, @@ -2334,12 +2694,15 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 1643..1661, test: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 1646..1655, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1646..1650, id: Name("type"), ctx: Store, @@ -2347,6 +2710,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1654..1655, value: Int( 1, @@ -2358,6 +2722,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 1657..1661, }, ), @@ -2367,10 +2732,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1662..1697, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1662..1666, id: Name("type"), ctx: Store, @@ -2379,19 +2746,26 @@ Module( ], value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 1669..1697, parameters: Some( Parameters { range: 1676..1681, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1676..1681, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1676..1681, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("query"), range: 1676..1681, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2405,9 +2779,11 @@ Module( ), body: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 1683..1697, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1683..1688, id: Name("query"), ctx: Load, @@ -2419,6 +2795,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1692..1697, id: Name("event"), ctx: Load, @@ -2433,12 +2810,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1698..1713, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1698..1713, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1698..1703, id: Name("print"), ctx: Load, @@ -2446,12 +2826,15 @@ Module( ), arguments: Arguments { range: 1703..1713, + node_index: AtomicNodeIndex(..), args: [ Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1704..1712, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1704..1708, id: Name("type"), ctx: Load, @@ -2459,9 +2842,11 @@ Module( ), arguments: Arguments { range: 1708..1712, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1709..1711, value: Int( 12, @@ -2482,12 +2867,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1714..1724, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1714..1724, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1714..1718, id: Name("type"), ctx: Load, @@ -2495,9 +2883,11 @@ Module( ), arguments: Arguments { range: 1718..1724, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1719..1723, id: Name("type"), ctx: Load, @@ -2512,10 +2902,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1725..1743, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1725..1726, id: Name("a"), ctx: Store, @@ -2524,9 +2916,11 @@ Module( ], value: Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 1732..1741, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1732..1736, id: Name("type"), ctx: Load, @@ -2538,6 +2932,7 @@ Module( comparators: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1740..1741, id: Name("C"), ctx: Load, @@ -2550,10 +2945,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1744..1760, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1744..1745, id: Name("a"), ctx: Store, @@ -2562,9 +2959,11 @@ Module( ], value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1751..1758, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1751..1755, id: Name("type"), ctx: Load, @@ -2572,9 +2971,11 @@ Module( ), arguments: Arguments { range: 1755..1758, + node_index: AtomicNodeIndex(..), args: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1756..1757, id: Name("b"), ctx: Load, @@ -2589,12 +2990,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1761..1778, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 1761..1778, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1761..1765, id: Name("type"), ctx: Load, @@ -2602,18 +3006,22 @@ Module( ), arguments: Arguments { range: 1766..1778, + node_index: AtomicNodeIndex(..), args: [], keywords: [ Keyword { range: 1769..1776, + node_index: AtomicNodeIndex(..), arg: Some( Identifier { id: Name("X"), range: 1769..1770, + node_index: AtomicNodeIndex(..), }, ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1773..1776, id: Name("int"), ctx: Load, @@ -2628,10 +3036,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1779..1787, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1779..1783, id: Name("type"), ctx: Store, @@ -2640,6 +3050,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1786..1787, value: Int( 1, @@ -2650,10 +3061,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1788..1800, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1788..1792, id: Name("type"), ctx: Store, @@ -2661,6 +3074,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1795..1796, id: Name("x"), ctx: Store, @@ -2669,6 +3083,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1799..1800, value: Int( 1, @@ -2679,10 +3094,12 @@ Module( ), Assign( StmtAssign { + node_index: AtomicNodeIndex(..), range: 1801..1813, targets: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1801..1802, id: Name("x"), ctx: Store, @@ -2690,6 +3107,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1805..1809, id: Name("type"), ctx: Store, @@ -2698,6 +3116,7 @@ Module( ], value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 1812..1813, value: Int( 1, @@ -2708,22 +3127,30 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 1814..1828, value: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 1814..1828, parameters: Some( Parameters { range: 1821..1822, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 1821..1822, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 1821..1822, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 1821..1822, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -2737,6 +3164,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 1824..1828, id: Name("type"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__while.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__while.py.snap index a70b73cc90e201..5c2e2603e19658 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__while.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__while.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/while.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..314, body: [ While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 0..16, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 6..7, id: Name("x"), ctx: Load, @@ -23,9 +25,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 13..16, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 13..16, }, ), @@ -37,17 +41,21 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 18..61, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 24..37, op: And, values: [ Compare( ExprCompare { + node_index: AtomicNodeIndex(..), range: 25..30, left: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 25..26, id: Name("x"), ctx: Load, @@ -59,6 +67,7 @@ Module( comparators: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 29..30, value: Int( 1, @@ -70,6 +79,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 36..37, id: Name("y"), ctx: Load, @@ -81,6 +91,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 43..47, }, ), @@ -88,9 +99,11 @@ Module( orelse: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..61, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 58..61, }, ), @@ -101,14 +114,17 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 63..152, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 69..76, op: And, values: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 69..70, id: Name("x"), ctx: Load, @@ -116,6 +132,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 75..76, id: Name("y"), ctx: Load, @@ -127,9 +144,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 82..85, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 82..85, }, ), @@ -137,12 +156,15 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 90..111, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 90..111, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..95, id: Name("print"), ctx: Load, @@ -150,14 +172,17 @@ Module( ), arguments: Arguments { range: 95..111, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 96..110, value: StringLiteralValue { inner: Single( StringLiteral { range: 96..110, + node_index: AtomicNodeIndex(..), value: "Hello World!", flags: StringLiteralFlags { quote_style: Single, @@ -180,12 +205,15 @@ Module( orelse: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 123..144, value: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 123..144, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 123..128, id: Name("print"), ctx: Load, @@ -193,14 +221,17 @@ Module( ), arguments: Arguments { range: 128..144, + node_index: AtomicNodeIndex(..), args: [ StringLiteral( ExprStringLiteral { + node_index: AtomicNodeIndex(..), range: 129..143, value: StringLiteralValue { inner: Single( StringLiteral { range: 129..143, + node_index: AtomicNodeIndex(..), value: "Olá, Mundo!", flags: StringLiteralFlags { quote_style: Single, @@ -221,9 +252,11 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 149..152, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 149..152, }, ), @@ -234,12 +267,15 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 154..171, test: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 160..166, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 160..161, id: Name("a"), ctx: Store, @@ -247,6 +283,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("b"), ctx: Load, @@ -257,9 +294,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..171, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 168..171, }, ), @@ -271,17 +310,21 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 172..197, test: BoolOp( ExprBoolOp { + node_index: AtomicNodeIndex(..), range: 178..192, op: And, values: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 179..185, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 179..180, id: Name("a"), ctx: Store, @@ -289,6 +332,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..185, id: Name("b"), ctx: Load, @@ -298,6 +342,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 191..192, id: Name("c"), ctx: Load, @@ -309,9 +354,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 194..197, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 194..197, }, ), @@ -323,22 +370,30 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 198..220, test: Lambda( ExprLambda { + node_index: AtomicNodeIndex(..), range: 204..215, parameters: Some( Parameters { range: 211..212, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 211..212, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 211..212, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 211..212, + node_index: AtomicNodeIndex(..), }, annotation: None, }, @@ -352,6 +407,7 @@ Module( ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 214..215, id: Name("x"), ctx: Load, @@ -362,9 +418,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 217..220, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 217..220, }, ), @@ -376,12 +434,15 @@ Module( ), While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 221..239, test: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 227..234, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 233..234, id: Name("x"), ctx: Load, @@ -392,9 +453,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 236..239, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 236..239, }, ), @@ -406,9 +469,11 @@ Module( ), If( StmtIf { + node_index: AtomicNodeIndex(..), range: 241..313, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 244..248, value: true, }, @@ -416,9 +481,11 @@ Module( body: [ While( StmtWhile { + node_index: AtomicNodeIndex(..), range: 254..298, test: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 260..261, id: Name("x"), ctx: Load, @@ -427,6 +494,7 @@ Module( body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 271..275, }, ), @@ -434,6 +502,7 @@ Module( orelse: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 294..298, }, ), @@ -444,10 +513,12 @@ Module( elif_else_clauses: [ ElifElseClause { range: 299..313, + node_index: AtomicNodeIndex(..), test: None, body: [ Pass( StmtPass { + node_index: AtomicNodeIndex(..), range: 309..313, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__with.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__with.py.snap index 260f39d072d031..993b8cc48ad033 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__with.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__with.py.snap @@ -1,24 +1,27 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/valid/statement/with.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..361, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 137..151, is_async: false, items: [ WithItem { range: 142..146, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 142..146, id: Name("item"), ctx: Load, @@ -30,9 +33,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 148..151, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 148..151, }, ), @@ -43,13 +48,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 152..171, is_async: false, items: [ WithItem { range: 157..166, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 157..161, id: Name("item"), ctx: Load, @@ -58,6 +66,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 165..166, id: Name("f"), ctx: Store, @@ -69,9 +78,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 168..171, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 168..171, }, ), @@ -82,13 +93,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 172..194, is_async: false, items: [ WithItem { range: 177..182, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 177..182, id: Name("item1"), ctx: Load, @@ -98,8 +112,10 @@ Module( }, WithItem { range: 184..189, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 184..189, id: Name("item2"), ctx: Load, @@ -111,9 +127,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 191..194, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 191..194, }, ), @@ -124,13 +142,16 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 195..229, is_async: false, items: [ WithItem { range: 200..211, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 200..205, id: Name("item1"), ctx: Load, @@ -139,6 +160,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 209..211, id: Name("f1"), ctx: Store, @@ -148,8 +170,10 @@ Module( }, WithItem { range: 213..224, + node_index: AtomicNodeIndex(..), context_expr: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 213..218, id: Name("item2"), ctx: Load, @@ -158,6 +182,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 222..224, id: Name("f2"), ctx: Store, @@ -169,9 +194,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 226..229, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 226..229, }, ), @@ -182,22 +209,27 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 231..257, is_async: false, items: [ WithItem { range: 236..252, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 236..252, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 241..245, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 236..237, id: Name("x"), ctx: Load, @@ -205,6 +237,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 251..252, id: Name("y"), ctx: Load, @@ -218,9 +251,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 254..257, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 254..257, }, ), @@ -231,22 +266,27 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 258..289, is_async: false, items: [ WithItem { range: 263..284, + node_index: AtomicNodeIndex(..), context_expr: If( ExprIf { + node_index: AtomicNodeIndex(..), range: 263..279, test: BooleanLiteral( ExprBooleanLiteral { + node_index: AtomicNodeIndex(..), range: 268..272, value: true, }, ), body: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 263..264, id: Name("x"), ctx: Load, @@ -254,6 +294,7 @@ Module( ), orelse: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 278..279, id: Name("y"), ctx: Load, @@ -264,6 +305,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 283..284, id: Name("f"), ctx: Store, @@ -275,9 +317,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 286..289, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 286..289, }, ), @@ -288,16 +332,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 313..334, is_async: false, items: [ WithItem { range: 318..329, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 318..324, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 318..322, id: Name("open"), ctx: Load, @@ -305,6 +353,7 @@ Module( ), arguments: Arguments { range: 322..324, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -313,6 +362,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 328..329, id: Name("f"), ctx: Store, @@ -324,9 +374,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 331..334, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 331..334, }, ), @@ -337,16 +389,20 @@ Module( ), With( StmtWith { + node_index: AtomicNodeIndex(..), range: 335..361, is_async: false, items: [ WithItem { range: 340..356, + node_index: AtomicNodeIndex(..), context_expr: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 340..346, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 340..344, id: Name("open"), ctx: Load, @@ -354,6 +410,7 @@ Module( ), arguments: Arguments { range: 344..346, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -362,9 +419,11 @@ Module( optional_vars: Some( Attribute( ExprAttribute { + node_index: AtomicNodeIndex(..), range: 350..356, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 350..351, id: Name("f"), ctx: Load, @@ -373,6 +432,7 @@ Module( attr: Identifier { id: Name("attr"), range: 352..356, + node_index: AtomicNodeIndex(..), }, ctx: Store, }, @@ -383,9 +443,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 358..361, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 358..361, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@tuple_context_manager_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@tuple_context_manager_py38.py.snap index 70180394159add..46550ad2312b14 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@tuple_context_manager_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@tuple_context_manager_py38.py.snap @@ -7,21 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/tuple_context_manager_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..85, body: [ With( StmtWith { + node_index: AtomicNodeIndex(..), range: 43..84, is_async: false, items: [ WithItem { range: 48..79, + node_index: AtomicNodeIndex(..), context_expr: Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 48..72, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 52..55, id: Name("foo"), ctx: Load, @@ -29,6 +34,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..62, id: Name("bar"), ctx: Load, @@ -36,6 +42,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 66..69, id: Name("baz"), ctx: Load, @@ -49,6 +56,7 @@ Module( optional_vars: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("tup"), ctx: Store, @@ -60,9 +68,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 81..84, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 81..84, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_default_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_default_py313.py.snap index 010da9ab552020..8387c4e874640f 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_default_py313.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_default_py313.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/type_param_default_py3 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..112, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 44..65, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("X"), ctx: Store, @@ -22,18 +25,22 @@ Module( type_params: Some( TypeParams { range: 50..59, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 51..58, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 51..52, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 55..58, id: Name("int"), ctx: Load, @@ -47,6 +54,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 62..65, id: Name("int"), ctx: Load, @@ -56,28 +64,34 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 66..87, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 70..71, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 71..80, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 72..79, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 72..73, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("int"), ctx: Load, @@ -91,6 +105,9 @@ Module( ), parameters: Parameters { range: 80..82, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -101,9 +118,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 84..87, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 84..87, }, ), @@ -114,27 +133,33 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 88..111, decorator_list: [], name: Identifier { id: Name("C"), range: 94..95, + node_index: AtomicNodeIndex(..), }, type_params: Some( TypeParams { range: 95..104, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 96..103, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 96..97, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 100..103, id: Name("int"), ctx: Load, @@ -149,6 +174,7 @@ Module( arguments: Some( Arguments { range: 104..106, + node_index: AtomicNodeIndex(..), args: [], keywords: [], }, @@ -156,9 +182,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 108..111, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 108..111, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_param_spec.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_param_spec.py.snap index ad273b844ca882..18801458d7b056 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_param_spec.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_param_spec.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/type_param_param_spec.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..90, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..17, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..11, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 7..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 9..10, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 18..41, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("X"), ctx: Store, @@ -59,17 +67,21 @@ Module( type_params: Some( TypeParams { range: 24..35, + node_index: AtomicNodeIndex(..), type_params: [ ParamSpec( TypeParamParamSpec { range: 25..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 27..28, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..34, id: Name("int"), ctx: Load, @@ -83,6 +95,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..41, id: Name("int"), ctx: Load, @@ -92,9 +105,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 42..62, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("X"), ctx: Store, @@ -103,13 +118,16 @@ Module( type_params: Some( TypeParams { range: 48..56, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 49..50, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 49..50, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -118,9 +136,11 @@ Module( ParamSpec( TypeParamParamSpec { range: 52..55, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 54..55, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -130,6 +150,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..62, id: Name("int"), ctx: Load, @@ -139,9 +160,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 63..89, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 68..69, id: Name("X"), ctx: Store, @@ -150,13 +173,16 @@ Module( type_params: Some( TypeParams { range: 69..83, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 70..71, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 70..71, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -165,13 +191,16 @@ Module( ParamSpec( TypeParamParamSpec { range: 73..82, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("P"), range: 75..76, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 79..82, id: Name("int"), ctx: Load, @@ -185,6 +214,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 86..89, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var.py.snap index 5f5cf396a8aaeb..1e340b41b1bb1f 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..147, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..15, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..9, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 7..8, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 7..8, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -40,6 +45,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 12..15, id: Name("int"), ctx: Load, @@ -49,9 +55,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 16..37, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 21..22, id: Name("X"), ctx: Store, @@ -60,18 +68,22 @@ Module( type_params: Some( TypeParams { range: 22..31, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 23..30, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 23..24, + node_index: AtomicNodeIndex(..), }, bound: None, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 27..30, id: Name("int"), ctx: Load, @@ -85,6 +97,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 34..37, id: Name("int"), ctx: Load, @@ -94,9 +107,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 38..64, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..44, id: Name("X"), ctx: Store, @@ -105,17 +120,21 @@ Module( type_params: Some( TypeParams { range: 44..58, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 45..57, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 45..46, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..51, id: Name("int"), ctx: Load, @@ -125,6 +144,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 54..57, id: Name("int"), ctx: Load, @@ -138,6 +158,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 61..64, id: Name("int"), ctx: Load, @@ -147,9 +168,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 65..98, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 70..71, id: Name("X"), ctx: Store, @@ -158,21 +181,26 @@ Module( type_params: Some( TypeParams { range: 71..92, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 72..91, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 72..73, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 75..85, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 76..79, id: Name("int"), ctx: Load, @@ -180,6 +208,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..84, id: Name("int"), ctx: Load, @@ -194,6 +223,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 88..91, id: Name("int"), ctx: Load, @@ -207,6 +237,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 95..98, id: Name("int"), ctx: Load, @@ -216,9 +247,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 99..146, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..105, id: Name("X"), ctx: Store, @@ -227,17 +260,21 @@ Module( type_params: Some( TypeParams { range: 105..140, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 106..118, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 106..107, + node_index: AtomicNodeIndex(..), }, bound: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 109..112, id: Name("int"), ctx: Load, @@ -247,6 +284,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 115..118, id: Name("int"), ctx: Load, @@ -258,17 +296,21 @@ Module( TypeVar( TypeParamTypeVar { range: 120..139, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("U"), range: 120..121, + node_index: AtomicNodeIndex(..), }, bound: Some( Tuple( ExprTuple { + node_index: AtomicNodeIndex(..), range: 123..133, elts: [ Name( ExprName { + node_index: AtomicNodeIndex(..), range: 124..127, id: Name("int"), ctx: Load, @@ -276,6 +318,7 @@ Module( ), Name( ExprName { + node_index: AtomicNodeIndex(..), range: 129..132, id: Name("int"), ctx: Load, @@ -290,6 +333,7 @@ Module( default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 136..139, id: Name("int"), ctx: Load, @@ -303,6 +347,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 143..146, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var_tuple.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var_tuple.py.snap index 93e16848e431e9..aa50926d782217 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var_tuple.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_param_type_var_tuple.py.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs input_file: crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py -snapshot_kind: text --- ## AST ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..115, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 0..17, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 5..6, id: Name("X"), ctx: Store, @@ -23,13 +25,16 @@ Module( type_params: Some( TypeParams { range: 6..11, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 7..10, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 8..10, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -39,6 +44,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 14..17, id: Name("int"), ctx: Load, @@ -48,9 +54,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 18..41, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 23..24, id: Name("X"), ctx: Store, @@ -59,17 +67,21 @@ Module( type_params: Some( TypeParams { range: 24..35, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 25..34, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 26..28, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 31..34, id: Name("int"), ctx: Load, @@ -83,6 +95,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 38..41, id: Name("int"), ctx: Load, @@ -92,9 +105,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 42..66, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("X"), ctx: Store, @@ -103,20 +118,25 @@ Module( type_params: Some( TypeParams { range: 48..60, + node_index: AtomicNodeIndex(..), type_params: [ TypeVarTuple( TypeParamTypeVarTuple { range: 49..59, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 50..52, + node_index: AtomicNodeIndex(..), }, default: Some( Starred( ExprStarred { + node_index: AtomicNodeIndex(..), range: 55..59, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..59, id: Name("int"), ctx: Load, @@ -133,6 +153,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 63..66, id: Name("int"), ctx: Load, @@ -142,9 +163,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 67..87, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 72..73, id: Name("X"), ctx: Store, @@ -153,13 +176,16 @@ Module( type_params: Some( TypeParams { range: 73..81, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 74..75, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 74..75, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -168,9 +194,11 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 77..80, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 78..80, + node_index: AtomicNodeIndex(..), }, default: None, }, @@ -180,6 +208,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 84..87, id: Name("int"), ctx: Load, @@ -189,9 +218,11 @@ Module( ), TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 88..114, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 93..94, id: Name("X"), ctx: Store, @@ -200,13 +231,16 @@ Module( type_params: Some( TypeParams { range: 94..108, + node_index: AtomicNodeIndex(..), type_params: [ TypeVar( TypeParamTypeVar { range: 95..96, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("T"), range: 95..96, + node_index: AtomicNodeIndex(..), }, bound: None, default: None, @@ -215,13 +249,16 @@ Module( TypeVarTuple( TypeParamTypeVarTuple { range: 98..107, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("Ts"), range: 99..101, + node_index: AtomicNodeIndex(..), }, default: Some( Name( ExprName { + node_index: AtomicNodeIndex(..), range: 104..107, id: Name("int"), ctx: Load, @@ -235,6 +272,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 111..114, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_stmt_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_stmt_py312.py.snap index 62eeead56c1fce..7e590b4515a157 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_stmt_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_stmt_py312.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/type_stmt_py312.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..57, body: [ TypeAlias( StmtTypeAlias { + node_index: AtomicNodeIndex(..), range: 44..56, name: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 49..50, id: Name("x"), ctx: Store, @@ -22,6 +25,7 @@ Module( type_params: None, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 53..56, id: Name("int"), ctx: Load, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap index e3329425de8f70..92c7cefb8639b7 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..53, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..52, value: Subscript( ExprSubscript { + node_index: AtomicNodeIndex(..), range: 43..52, value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 43..46, id: Name("lst"), ctx: Load, @@ -24,9 +28,11 @@ Module( ), slice: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 47..51, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 47..48, id: Name("x"), ctx: Store, @@ -34,6 +40,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 50..51, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap index 33d284c9b569a1..0f8b9f47b721d4 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap @@ -7,20 +7,25 @@ input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_ ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..88, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 43..57, value: Set( ExprSet { + node_index: AtomicNodeIndex(..), range: 43..57, elts: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 44..50, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("x"), ctx: Store, @@ -28,6 +33,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 49..50, value: Int( 1, @@ -38,6 +44,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 2, @@ -46,6 +53,7 @@ Module( ), NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 55..56, value: Int( 3, @@ -59,15 +67,19 @@ Module( ), Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 58..87, value: SetComp( ExprSetComp { + node_index: AtomicNodeIndex(..), range: 58..87, elt: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 59..68, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 59..63, id: Name("last"), ctx: Store, @@ -75,6 +87,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 67..68, id: Name("x"), ctx: Load, @@ -85,8 +98,10 @@ Module( generators: [ Comprehension { range: 69..86, + node_index: AtomicNodeIndex(..), target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("x"), ctx: Store, @@ -94,9 +109,11 @@ Module( ), iter: Call( ExprCall { + node_index: AtomicNodeIndex(..), range: 78..86, func: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 78..83, id: Name("range"), ctx: Load, @@ -104,9 +121,11 @@ Module( ), arguments: Arguments { range: 83..86, + node_index: AtomicNodeIndex(..), args: [ NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 84..85, value: Int( 3, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_class.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_class.py.snap index feeea973fbc0d9..f4de814c8daad5 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_class.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_class.py.snap @@ -7,26 +7,32 @@ input_file: crates/ruff_python_parser/resources/inline/ok/valid_annotation_class ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..137, body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 0..23, decorator_list: [], name: Identifier { id: Name("F"), range: 6..7, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 7..18, + node_index: AtomicNodeIndex(..), args: [ Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 8..17, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 8..9, id: Name("y"), ctx: Store, @@ -34,6 +40,7 @@ Module( ), value: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 13..17, id: Name("list"), ctx: Load, @@ -48,9 +55,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 20..23, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 20..23, }, ), @@ -61,16 +70,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 24..93, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 28..29, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 29..31, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -81,23 +95,28 @@ Module( body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 37..60, decorator_list: [], name: Identifier { id: Name("G"), range: 43..44, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 44..55, + node_index: AtomicNodeIndex(..), args: [ Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 46..53, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 52..53, value: Int( 1, @@ -114,9 +133,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 57..60, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 57..60, }, ), @@ -127,22 +148,27 @@ Module( ), ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 65..93, decorator_list: [], name: Identifier { id: Name("H"), range: 71..72, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 72..88, + node_index: AtomicNodeIndex(..), args: [ YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 74..86, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 85..86, value: Int( 1, @@ -158,9 +184,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 90..93, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 90..93, }, ), @@ -174,16 +202,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 94..136, is_async: true, decorator_list: [], name: Identifier { id: Name("f"), range: 104..105, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 105..107, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -194,22 +227,27 @@ Module( body: [ ClassDef( StmtClassDef { + node_index: AtomicNodeIndex(..), range: 113..136, decorator_list: [], name: Identifier { id: Name("G"), range: 119..120, + node_index: AtomicNodeIndex(..), }, type_params: None, arguments: Some( Arguments { range: 120..131, + node_index: AtomicNodeIndex(..), args: [ Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 122..129, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 1, @@ -225,9 +263,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..136, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 133..136, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_function_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_function_py313.py.snap index 1a58dffba2596b..c1c32f560556ff 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_function_py313.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_function_py313.py.snap @@ -7,20 +7,26 @@ input_file: crates/ruff_python_parser/resources/inline/ok/valid_annotation_funct ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..316, body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 44..68, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 48..49, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 49..51, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -30,9 +36,11 @@ Module( returns: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 56..62, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 56..57, id: Name("y"), ctx: Store, @@ -40,6 +48,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 61..62, value: Int( 3, @@ -52,9 +61,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 65..68, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 65..68, }, ), @@ -65,32 +76,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 69..94, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 73..74, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 74..89, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 75..88, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 75..88, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 75..78, + node_index: AtomicNodeIndex(..), }, annotation: Some( Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 81..87, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 81..82, id: Name("x"), ctx: Store, @@ -98,6 +119,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 86..87, value: Int( 1, @@ -119,9 +141,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 91..94, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 91..94, }, ), @@ -132,16 +156,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 95..235, is_async: false, decorator_list: [], name: Identifier { id: Name("outer"), range: 99..104, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 104..106, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -152,33 +181,43 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 112..136, is_async: false, decorator_list: [], name: Identifier { id: Name("i"), range: 116..117, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 117..131, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 118..130, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 118..130, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 118..119, + node_index: AtomicNodeIndex(..), }, annotation: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 122..129, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 128..129, value: Int( 1, @@ -201,9 +240,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 133..136, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 133..136, }, ), @@ -214,16 +255,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 141..166, is_async: false, decorator_list: [], name: Identifier { id: Name("k"), range: 145..146, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 146..148, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -233,10 +279,12 @@ Module( returns: Some( Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 153..160, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 159..160, value: Int( 1, @@ -250,9 +298,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 163..166, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 163..166, }, ), @@ -263,32 +313,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 171..200, is_async: false, decorator_list: [], name: Identifier { id: Name("m"), range: 175..176, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 176..195, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 177..194, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 177..194, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("x"), range: 177..178, + node_index: AtomicNodeIndex(..), }, annotation: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 181..193, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 192..193, value: Int( 1, @@ -310,9 +370,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 197..200, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 197..200, }, ), @@ -323,16 +385,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 205..235, is_async: false, decorator_list: [], name: Identifier { id: Name("o"), range: 209..210, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 210..212, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -342,9 +409,11 @@ Module( returns: Some( YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 217..229, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 228..229, value: Int( 1, @@ -357,9 +426,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 232..235, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 232..235, }, ), @@ -373,16 +444,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 236..315, is_async: true, decorator_list: [], name: Identifier { id: Name("outer"), range: 246..251, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 251..253, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -393,16 +469,21 @@ Module( body: [ FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 259..284, is_async: false, decorator_list: [], name: Identifier { id: Name("f"), range: 263..264, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 264..266, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -412,9 +493,11 @@ Module( returns: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 271..278, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 277..278, value: Int( 1, @@ -427,9 +510,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 281..284, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 281..284, }, ), @@ -440,32 +525,42 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 289..315, is_async: false, decorator_list: [], name: Identifier { id: Name("g"), range: 293..294, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 294..310, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [ ParameterWithDefault { range: 295..309, + node_index: AtomicNodeIndex(..), parameter: Parameter { range: 295..309, + node_index: AtomicNodeIndex(..), name: Identifier { id: Name("arg"), range: 295..298, + node_index: AtomicNodeIndex(..), }, annotation: Some( Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 301..308, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 307..308, value: Int( 1, @@ -487,9 +582,11 @@ Module( body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 312..315, value: EllipsisLiteral( ExprEllipsisLiteral { + node_index: AtomicNodeIndex(..), range: 312..315, }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_py313.py.snap index 9119467d8466e2..be4b3961e8e845 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_py313.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@valid_annotation_py313.py.snap @@ -7,13 +7,16 @@ input_file: crates/ruff_python_parser/resources/inline/ok/valid_annotation_py313 ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..144, body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 44..55, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 44..45, id: Name("a"), ctx: Store, @@ -21,9 +24,11 @@ Module( ), annotation: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 48..54, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 48..49, id: Name("x"), ctx: Store, @@ -31,6 +36,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 53..54, value: Int( 1, @@ -45,16 +51,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 56..107, is_async: false, decorator_list: [], name: Identifier { id: Name("outer"), range: 60..65, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 65..67, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -65,9 +76,11 @@ Module( body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 73..85, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 73..74, id: Name("b"), ctx: Store, @@ -75,10 +88,12 @@ Module( ), annotation: Yield( ExprYield { + node_index: AtomicNodeIndex(..), range: 77..84, value: Some( NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 83..84, value: Int( 1, @@ -94,9 +109,11 @@ Module( ), AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 90..107, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 90..91, id: Name("c"), ctx: Store, @@ -104,9 +121,11 @@ Module( ), annotation: YieldFrom( ExprYieldFrom { + node_index: AtomicNodeIndex(..), range: 94..106, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 105..106, value: Int( 1, @@ -124,16 +143,21 @@ Module( ), FunctionDef( StmtFunctionDef { + node_index: AtomicNodeIndex(..), range: 108..143, is_async: true, decorator_list: [], name: Identifier { id: Name("outer"), range: 118..123, + node_index: AtomicNodeIndex(..), }, type_params: None, parameters: Parameters { range: 123..125, + node_index: AtomicNodeIndex( + 0, + ), posonlyargs: [], args: [], vararg: None, @@ -144,9 +168,11 @@ Module( body: [ AnnAssign( StmtAnnAssign { + node_index: AtomicNodeIndex(..), range: 131..143, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 131..132, id: Name("d"), ctx: Store, @@ -154,9 +180,11 @@ Module( ), annotation: Await( ExprAwait { + node_index: AtomicNodeIndex(..), range: 135..142, value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 141..142, value: Int( 1, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@walrus_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@walrus_py38.py.snap index b0dfb2c4db18e1..16008f3821501a 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@walrus_py38.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@walrus_py38.py.snap @@ -7,16 +7,20 @@ input_file: crates/ruff_python_parser/resources/inline/ok/walrus_py38.py ``` Module( ModModule { + node_index: AtomicNodeIndex(..), range: 0..54, body: [ Expr( StmtExpr { + node_index: AtomicNodeIndex(..), range: 45..53, value: Named( ExprNamed { + node_index: AtomicNodeIndex(..), range: 46..52, target: Name( ExprName { + node_index: AtomicNodeIndex(..), range: 46..47, id: Name("x"), ctx: Store, @@ -24,6 +28,7 @@ Module( ), value: NumberLiteral( ExprNumberLiteral { + node_index: AtomicNodeIndex(..), range: 51..52, value: Int( 1, diff --git a/crates/ruff_python_semantic/src/analyze/function_type.rs b/crates/ruff_python_semantic/src/analyze/function_type.rs index be9cac6c60c136..5949b71c88a938 100644 --- a/crates/ruff_python_semantic/src/analyze/function_type.rs +++ b/crates/ruff_python_semantic/src/analyze/function_type.rs @@ -136,7 +136,11 @@ fn is_class_method( pub fn is_stub(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool { function_def.body.iter().all(|stmt| match stmt { Stmt::Pass(_) => true, - Stmt::Expr(StmtExpr { value, range: _ }) => { + Stmt::Expr(StmtExpr { + value, + range: _, + node_index: _, + }) => { matches!( value.as_ref(), Expr::StringLiteral(_) | Expr::EllipsisLiteral(_) @@ -144,6 +148,7 @@ pub fn is_stub(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool } Stmt::Raise(StmtRaise { range: _, + node_index: _, exc: exception, cause: _, }) => exception.as_ref().is_some_and(|exc| { diff --git a/crates/ruff_python_semantic/src/analyze/imports.rs b/crates/ruff_python_semantic/src/analyze/imports.rs index 60313eba14e1f2..e5f20f04d66f63 100644 --- a/crates/ruff_python_semantic/src/analyze/imports.rs +++ b/crates/ruff_python_semantic/src/analyze/imports.rs @@ -12,7 +12,11 @@ use crate::SemanticModel; /// ``` pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool { match stmt { - Stmt::Expr(ast::StmtExpr { value, range: _ }) => match value.as_ref() { + Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) => match value.as_ref() { Expr::Call(ast::ExprCall { func, .. }) => semantic .resolve_qualified_name(func.as_ref()) .is_some_and(|qualified_name| { @@ -96,7 +100,12 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool /// matplotlib.use("Agg") /// ``` pub fn is_matplotlib_activation(stmt: &Stmt, semantic: &SemanticModel) -> bool { - let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else { + let Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) = stmt + else { return false; }; let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else { diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 90594a15efe654..7038d267d4a027 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -287,6 +287,7 @@ pub fn is_immutable_annotation( op: Operator::BitOr, right, range: _, + node_index: _, }) => { is_immutable_annotation(left, semantic, extend_immutable_calls) && is_immutable_annotation(right, semantic, extend_immutable_calls) @@ -442,6 +443,7 @@ where left, right, range: _, + node_index: _, }) = expr { // The union data structure usually looks like this: diff --git a/crates/ruff_python_semantic/src/globals.rs b/crates/ruff_python_semantic/src/globals.rs index 9f9ff62566d67b..3a5e8307330a4f 100644 --- a/crates/ruff_python_semantic/src/globals.rs +++ b/crates/ruff_python_semantic/src/globals.rs @@ -74,7 +74,11 @@ impl<'a> GlobalsVisitor<'a> { impl<'a> StatementVisitor<'a> for GlobalsVisitor<'a> { fn visit_stmt(&mut self, stmt: &'a Stmt) { match stmt { - Stmt::Global(ast::StmtGlobal { names, range: _ }) => { + Stmt::Global(ast::StmtGlobal { + names, + range: _, + node_index: _, + }) => { for name in names { self.0.insert(name.as_str(), name.range()); } diff --git a/crates/ruff_python_semantic/src/imports.rs b/crates/ruff_python_semantic/src/imports.rs index fd4d682336242a..4cc55123cb30f3 100644 --- a/crates/ruff_python_semantic/src/imports.rs +++ b/crates/ruff_python_semantic/src/imports.rs @@ -230,6 +230,7 @@ impl<'de> serde::de::Deserialize<'de> for NameImports { names, level, range: _, + node_index: _, }) => names .iter() .map(|name| { @@ -243,7 +244,11 @@ impl<'de> serde::de::Deserialize<'de> for NameImports { }) }) .collect(), - Stmt::Import(ast::StmtImport { names, range: _ }) => names + Stmt::Import(ast::StmtImport { + names, + range: _, + node_index: _, + }) => names .iter() .map(|name| { NameImport::Import(ModuleNameImport { diff --git a/crates/ruff_python_semantic/src/model/all.rs b/crates/ruff_python_semantic/src/model/all.rs index ba2d1ddb3c41f8..bc05d96efe415a 100644 --- a/crates/ruff_python_semantic/src/model/all.rs +++ b/crates/ruff_python_semantic/src/model/all.rs @@ -82,7 +82,12 @@ impl SemanticModel<'_> { flags: &mut DunderAllFlags, ) { for elt in elts { - if let Expr::StringLiteral(ast::ExprStringLiteral { value, range }) = elt { + if let Expr::StringLiteral(ast::ExprStringLiteral { + value, + range, + node_index: _, + }) = elt + { names.push(DunderAllName { name: value.to_str(), range: *range, diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index bc9daff46e29c6..924785ca2da0ab 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -253,7 +253,7 @@ impl Project { tracing::debug_span!(parent: project_span, "check_file", ?file); let _entered = check_file_span.entered(); - let result = check_file_impl(&db, file); + let result = self.check_file_impl(&db, file); file_diagnostics.lock().unwrap().extend(result); reporter.report_file(&file); @@ -282,7 +282,7 @@ impl Project { .map(OptionDiagnostic::to_diagnostic) .collect(); - let check_diagnostics = check_file_impl(db, file); + let check_diagnostics = self.check_file_impl(db, file); file_diagnostics.extend(check_diagnostics); file_diagnostics @@ -461,61 +461,75 @@ impl Project { self.set_file_set(db).to(IndexedFiles::lazy()); } } -} -fn check_file_impl(db: &dyn Db, file: File) -> Vec { - let mut diagnostics: Vec = Vec::new(); + fn check_file_impl(self, db: &dyn Db, file: File) -> Vec { + let mut diagnostics: Vec = Vec::new(); - // Abort checking if there are IO errors. - let source = source_text(db.upcast(), file); + // Abort checking if there are IO errors. + let source = source_text(db.upcast(), file); - if let Some(read_error) = source.read_error() { - diagnostics.push( - IOErrorDiagnostic { - file: Some(file), - error: read_error.clone().into(), - } - .to_diagnostic(), + if let Some(read_error) = source.read_error() { + diagnostics.push( + IOErrorDiagnostic { + file: Some(file), + error: read_error.clone().into(), + } + .to_diagnostic(), + ); + return diagnostics; + } + + let parsed = parsed_module(db.upcast(), file); + + let parsed_ref = parsed.load(db.upcast()); + diagnostics.extend( + parsed_ref + .errors() + .iter() + .map(|error| create_parse_diagnostic(file, error)), ); - return diagnostics; - } - let parsed = parsed_module(db.upcast(), file); + diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| { + let mut error = create_unsupported_syntax_diagnostic(file, error); + add_inferred_python_version_hint_to_diagnostic( + db.upcast(), + &mut error, + "parsing syntax", + ); + error + })); - let parsed_ref = parsed.load(db.upcast()); - diagnostics.extend( - parsed_ref - .errors() - .iter() - .map(|error| create_parse_diagnostic(file, error)), - ); - - diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| { - let mut error = create_unsupported_syntax_diagnostic(file, error); - add_inferred_python_version_hint_to_diagnostic(db.upcast(), &mut error, "parsing syntax"); - error - })); - - { - let db = AssertUnwindSafe(db); - match catch(&**db, file, || check_types(db.upcast(), file)) { - Ok(Some(type_check_diagnostics)) => { - diagnostics.extend(type_check_diagnostics.into_iter().cloned()); + { + let db = AssertUnwindSafe(db); + match catch(&**db, file, || check_types(db.upcast(), file)) { + Ok(Some(type_check_diagnostics)) => { + diagnostics.extend(type_check_diagnostics.into_iter().cloned()); + } + Ok(None) => {} + Err(diagnostic) => diagnostics.push(diagnostic), } - Ok(None) => {} - Err(diagnostic) => diagnostics.push(diagnostic), } - } - diagnostics.sort_unstable_by_key(|diagnostic| { - diagnostic - .primary_span() - .and_then(|span| span.range()) - .unwrap_or_default() - .start() - }); + if self + .open_fileset(db) + .is_none_or(|files| !files.contains(&file)) + { + // Drop the AST now that we are done checking this file. It is not currently open, + // so it is unlikely to be accessed again soon. If any queries need to access the AST + // from across files, it will be re-parsed. + parsed.clear(); + } + + diagnostics.sort_unstable_by_key(|diagnostic| { + diagnostic + .primary_span() + .and_then(|span| span.range()) + .unwrap_or_default() + .start() + }); - diagnostics + diagnostics + } } #[derive(Debug)] @@ -686,8 +700,9 @@ where #[cfg(test)] mod tests { + use crate::Db; + use crate::ProjectMetadata; use crate::db::tests::TestDb; - use crate::{ProjectMetadata, check_file_impl}; use ruff_db::files::system_path_to_file; use ruff_db::source::source_text; use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf}; @@ -723,7 +738,8 @@ mod tests { assert_eq!(source_text(&db, file).as_str(), ""); assert_eq!( - check_file_impl(&db, file) + db.project() + .check_file_impl(&db, file) .into_iter() .map(|diagnostic| diagnostic.primary_message().to_string()) .collect::>(), @@ -739,7 +755,8 @@ mod tests { assert_eq!(source_text(&db, file).as_str(), ""); assert_eq!( - check_file_impl(&db, file) + db.project() + .check_file_impl(&db, file) .into_iter() .map(|diagnostic| diagnostic.primary_message().to_string()) .collect::>(), diff --git a/crates/ty_python_semantic/src/ast_node_ref.rs b/crates/ty_python_semantic/src/ast_node_ref.rs index fd3e1726dca151..3c15435f92b984 100644 --- a/crates/ty_python_semantic/src/ast_node_ref.rs +++ b/crates/ty_python_semantic/src/ast_node_ref.rs @@ -1,17 +1,17 @@ -use std::sync::Arc; +use std::fmt::Debug; +use std::marker::PhantomData; use ruff_db::parsed::ParsedModuleRef; +use ruff_python_ast::{AnyNodeRef, NodeIndex}; +use ruff_python_ast::{AnyRootNodeRef, HasNodeIndex}; +use ruff_text_size::Ranged; -/// Ref-counted owned reference to an AST node. +/// Reference to an AST node. /// -/// The type holds an owned reference to the node's ref-counted [`ParsedModuleRef`]. -/// Holding on to the node's [`ParsedModuleRef`] guarantees that the reference to the -/// node must still be valid. -/// -/// Holding on to any [`AstNodeRef`] prevents the [`ParsedModuleRef`] from being released. -/// -/// ## Equality -/// Two `AstNodeRef` are considered equal if their pointer addresses are equal. +/// This type acts as a reference to an AST node within a given module that remains +/// stable regardless of whether the AST is garbage collected. As such, accessing a +/// node through the [`AstNodeRef`] requires a reference to the current [`ParsedModuleRef`] +/// for the module containing the node. /// /// ## Usage in salsa tracked structs /// It's important that [`AstNodeRef`] fields in salsa tracked structs are tracked fields @@ -32,54 +32,83 @@ use ruff_db::parsed::ParsedModuleRef; /// run on every AST change. All other queries only run when the expression's identity changes. #[derive(Clone)] pub struct AstNodeRef { - /// Owned reference to the node's [`ParsedModuleRef`]. - /// - /// The node's reference is guaranteed to remain valid as long as it's enclosing - /// [`ParsedModuleRef`] is alive. - parsed: ParsedModuleRef, + /// A pointer to the [`ruff_db::parsed::ParsedModule`] that this node was created from. + module_ptr: *const (), + + /// Debug information. + #[cfg(debug_assertions)] + kind: ruff_python_ast::NodeKind, + #[cfg(debug_assertions)] + range: ruff_text_size::TextRange, - /// Pointer to the referenced node. - node: std::ptr::NonNull, + /// The index of the node in the AST. + index: NodeIndex, + + _node: PhantomData, } -#[expect(unsafe_code)] -impl AstNodeRef { - /// Creates a new `AstNodeRef` that references `node`. The `parsed` is the [`ParsedModuleRef`] to - /// which the `AstNodeRef` belongs. - /// - /// ## Safety +impl AstNodeRef +where + T: HasNodeIndex + Ranged + PartialEq + Debug, + for<'ast> AnyNodeRef<'ast>: From<&'ast T>, + for<'ast> &'ast T: TryFrom>, +{ + /// Creates a new `AstNodeRef` that references `node`. /// - /// Dereferencing the `node` can result in undefined behavior if `parsed` isn't the - /// [`ParsedModuleRef`] to which `node` belongs. It's the caller's responsibility to ensure that - /// the invariant `node belongs to parsed` is upheld. - pub(super) unsafe fn new(parsed: ParsedModuleRef, node: &T) -> Self { + /// This method may panic or produce unspecified results if the provided module is from a + /// different file or Salsa revision than the module to which the node belongs. + pub(super) fn new(module_ref: &ParsedModuleRef, node: &T) -> Self { + let index = node.node_index().load(); + debug_assert_eq!(module_ref.get_by_index(index).try_into().ok(), Some(node)); + Self { - parsed, - node: std::ptr::NonNull::from(node), + index, + module_ptr: module_ref.module().as_ptr(), + #[cfg(debug_assertions)] + kind: AnyNodeRef::from(node).kind(), + #[cfg(debug_assertions)] + range: node.range(), + _node: PhantomData, } } /// Returns a reference to the wrapped node. /// - /// Note that this method will panic if the provided module is from a different file or Salsa revision - /// than the module this node was created with. - pub fn node<'ast>(&self, parsed: &'ast ParsedModuleRef) -> &'ast T { - debug_assert!(Arc::ptr_eq(self.parsed.as_arc(), parsed.as_arc())); + /// This method may panic or produce unspecified results if the provided module is from a + /// different file or Salsa revision than the module to which the node belongs. + pub fn node<'ast>(&self, module_ref: &'ast ParsedModuleRef) -> &'ast T { + debug_assert_eq!(module_ref.module().as_ptr(), self.module_ptr); - // SAFETY: Holding on to `parsed` ensures that the AST to which `node` belongs is still - // alive and not moved. - unsafe { self.node.as_ref() } + // Note that the module pointer is guaranteed to be stable within the Salsa + // revision, so the file contents cannot have changed by the above assertion. + module_ref + .get_by_index(self.index) + .try_into() + .ok() + .expect("AST indices should never change within the same revision") } } -impl std::fmt::Debug for AstNodeRef +#[allow(clippy::missing_fields_in_debug)] +impl Debug for AstNodeRef where - T: std::fmt::Debug, + T: Debug, + for<'ast> &'ast T: TryFrom>, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("AstNodeRef") - .field(self.node(&self.parsed)) - .finish() + #[cfg(debug_assertions)] + { + f.debug_struct("AstNodeRef") + .field("kind", &self.kind) + .field("range", &self.range) + .finish() + } + + #[cfg(not(debug_assertions))] + { + // Unfortunately we have no access to the AST here. + f.debug_tuple("AstNodeRef").finish_non_exhaustive() + } } } @@ -88,9 +117,10 @@ unsafe impl salsa::Update for AstNodeRef { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let old_ref = unsafe { &mut (*old_pointer) }; - if Arc::ptr_eq(old_ref.parsed.as_arc(), new_value.parsed.as_arc()) - && old_ref.node.eq(&new_value.node) - { + // Two nodes are guaranteed to be equal as long as they refer to the same node index + // within the same module. Note that the module pointer is guaranteed to be stable + // within the Salsa revision, so the file contents cannot have changed. + if old_ref.module_ptr == new_value.module_ptr && old_ref.index == new_value.index { false } else { *old_ref = new_value; @@ -99,6 +129,7 @@ unsafe impl salsa::Update for AstNodeRef { } } +// SAFETY: The `module_ptr` is only used for pointer equality and never accessed directly. #[expect(unsafe_code)] unsafe impl Send for AstNodeRef where T: Send {} #[expect(unsafe_code)] diff --git a/crates/ty_python_semantic/src/module_name.rs b/crates/ty_python_semantic/src/module_name.rs index 48162fe3d934b9..2111a64b835df3 100644 --- a/crates/ty_python_semantic/src/module_name.rs +++ b/crates/ty_python_semantic/src/module_name.rs @@ -222,6 +222,7 @@ impl ModuleName { level, names: _, range: _, + node_index: _, } = node; let module = module.as_deref(); diff --git a/crates/ty_python_semantic/src/node_key.rs b/crates/ty_python_semantic/src/node_key.rs index 4c2ade0b7e1efa..470ef35df258f5 100644 --- a/crates/ty_python_semantic/src/node_key.rs +++ b/crates/ty_python_semantic/src/node_key.rs @@ -1,21 +1,14 @@ -use ruff_python_ast::AnyNodeRef; +use ruff_python_ast::{HasNodeIndex, NodeIndex}; /// Compact key for a node for use in a hash map. -/// -/// Stores the memory address of the node, because using the range and the kind -/// of the node is not enough to uniquely identify them in ASTs resulting from -/// invalid syntax. For example, parsing the input `for` results in a `StmtFor` -/// AST node where both the `target` and the `iter` field are `ExprName` nodes -/// with the same (empty) range `3..3`. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(super) struct NodeKey(usize); +pub(super) struct NodeKey(NodeIndex); impl NodeKey { - pub(super) fn from_node<'a, N>(node: N) -> Self + pub(super) fn from_node(node: N) -> Self where - N: Into>, + N: HasNodeIndex, { - let node = node.into(); - NodeKey(node.as_ptr().as_ptr() as usize) + NodeKey(node.node_index().load()) } } diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index 5c0c5dc7ff3a1b..b8e3dc5d0e6120 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -241,9 +241,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { ) { let children_start = self.scopes.next_index() + 1; - // SAFETY: `node` is guaranteed to be a child of `self.module` - #[expect(unsafe_code)] - let node_with_kind = unsafe { node.to_kind(self.module.clone()) }; + // Note `node` is guaranteed to be a child of `self.module` + let node_with_kind = node.to_kind(self.module); let scope = Scope::new( parent, @@ -473,9 +472,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { ) -> (Definition<'db>, usize) { let definition_node: DefinitionNodeRef<'ast, 'db> = definition_node.into(); - #[expect(unsafe_code)] - // SAFETY: `definition_node` is guaranteed to be a child of `self.module` - let kind = unsafe { definition_node.into_owned(self.module.clone()) }; + // Note `definition_node` is guaranteed to be a child of `self.module` + let kind = definition_node.into_owned(self.module); let category = kind.category(self.source_type.is_stub(), self.module); let is_reexported = kind.is_reexported(); @@ -782,13 +780,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { self.db, self.file, self.current_scope(), - #[expect(unsafe_code)] - unsafe { - AstNodeRef::new(self.module.clone(), expression_node) - }, - #[expect(unsafe_code)] - assigned_to - .map(|assigned_to| unsafe { AstNodeRef::new(self.module.clone(), assigned_to) }), + AstNodeRef::new(self.module, expression_node), + assigned_to.map(|assigned_to| AstNodeRef::new(self.module, assigned_to)), expression_kind, countme::Count::default(), ); @@ -810,6 +803,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { let (name, bound, default) = match type_param { ast::TypeParam::TypeVar(ast::TypeParamTypeVar { range: _, + node_index: _, name, bound, default, @@ -989,11 +983,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { self.file, value_file_scope, self.current_scope(), - // SAFETY: `target` belongs to the `self.module` tree - #[expect(unsafe_code)] - unsafe { - AstNodeRef::new(self.module.clone(), target) - }, + // Note `target` belongs to the `self.module` tree + AstNodeRef::new(self.module, target), UnpackValue::new(unpackable.kind(), value), countme::Count::default(), )); @@ -1103,6 +1094,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { body, is_async: _, range: _, + node_index: _, } = function_def; for decorator in decorator_list { self.visit_decorator(decorator); @@ -1377,6 +1369,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { test, msg, range: _, + node_index: _, }) => { // We model an `assert test, msg` statement here. Conceptually, we can think of // this as being equivalent to the following: @@ -1447,6 +1440,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::Stmt::AugAssign( aug_assign @ ast::StmtAugAssign { range: _, + node_index: _, target, op, value, @@ -1553,6 +1547,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { body, orelse, range: _, + node_index: _, }) => { self.visit_expr(test); @@ -1620,6 +1615,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }) => { for item @ ast::WithItem { range: _, + node_index: _, context_expr, optional_vars, } in items @@ -1643,6 +1639,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::Stmt::For( for_stmt @ ast::StmtFor { range: _, + node_index: _, is_async: _, target, iter, @@ -1680,6 +1677,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { subject, cases, range: _, + node_index: _, }) => { debug_assert_eq!(self.current_match_case, None); @@ -1767,6 +1765,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { finalbody, is_star, range: _, + node_index: _, }) => { self.record_ambiguous_visibility(); @@ -1814,6 +1813,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { type_: handled_exceptions, body: handler_body, range: _, + node_index: _, } = except_handler; if let Some(handled_exceptions) = handled_exceptions { @@ -1892,7 +1892,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // Everything in the current block after a terminal statement is unreachable. self.mark_unreachable(); } - ast::Stmt::Global(ast::StmtGlobal { range: _, names }) => { + ast::Stmt::Global(ast::StmtGlobal { + range: _, + node_index: _, + names, + }) => { for name in names { let symbol_id = self.add_symbol(name.id.clone()); let symbol_table = self.current_place_table(); @@ -1915,7 +1919,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { } walk_stmt(self, stmt); } - ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => { + ast::Stmt::Delete(ast::StmtDelete { + targets, + range: _, + node_index: _, + }) => { // We will check the target expressions and then delete them. walk_stmt(self, stmt); for target in targets { @@ -1926,7 +1934,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { } } } - ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) if self.in_module_scope() => { + ast::Stmt::Expr(ast::StmtExpr { + value, + range: _, + node_index: _, + }) if self.in_module_scope() => { if let Some(expr) = dunder_all_extend_argument(value) { self.add_standalone_expression(expr); } @@ -2186,6 +2198,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::Expr::BoolOp(ast::ExprBoolOp { values, range: _, + node_index: _, op, }) => { let pre_op = self.flow_snapshot(); @@ -2273,6 +2286,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { if let ast::Pattern::MatchStar(ast::PatternMatchStar { name: Some(name), range: _, + node_index: _, }) = pattern { let symbol = self.add_symbol(name.id().clone()); @@ -2556,6 +2570,7 @@ fn dunder_all_extend_argument(value: &ast::Expr) -> Option<&ast::Expr> { args, keywords, range: _, + node_index: _, }, .. } = value.as_call_expr()?; diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 391bbf87eb5f92..9e1e6a89cf4be1 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -333,15 +333,14 @@ pub(crate) struct MatchPatternDefinitionNodeRef<'ast> { } impl<'db> DefinitionNodeRef<'_, 'db> { - #[expect(unsafe_code)] - pub(super) unsafe fn into_owned(self, parsed: ParsedModuleRef) -> DefinitionKind<'db> { + pub(super) fn into_owned(self, parsed: &ParsedModuleRef) -> DefinitionKind<'db> { match self { DefinitionNodeRef::Import(ImportDefinitionNodeRef { node, alias_index, is_reexported, }) => DefinitionKind::Import(ImportDefinitionKind { - node: unsafe { AstNodeRef::new(parsed, node) }, + node: AstNodeRef::new(parsed, node), alias_index, is_reexported, }), @@ -351,28 +350,28 @@ impl<'db> DefinitionNodeRef<'_, 'db> { alias_index, is_reexported, }) => DefinitionKind::ImportFrom(ImportFromDefinitionKind { - node: unsafe { AstNodeRef::new(parsed, node) }, + node: AstNodeRef::new(parsed, node), alias_index, is_reexported, }), DefinitionNodeRef::ImportStar(star_import) => { let StarImportDefinitionNodeRef { node, place_id } = star_import; DefinitionKind::StarImport(StarImportDefinitionKind { - node: unsafe { AstNodeRef::new(parsed, node) }, + node: AstNodeRef::new(parsed, node), place_id, }) } DefinitionNodeRef::Function(function) => { - DefinitionKind::Function(unsafe { AstNodeRef::new(parsed, function) }) + DefinitionKind::Function(AstNodeRef::new(parsed, function)) } DefinitionNodeRef::Class(class) => { - DefinitionKind::Class(unsafe { AstNodeRef::new(parsed, class) }) + DefinitionKind::Class(AstNodeRef::new(parsed, class)) } DefinitionNodeRef::TypeAlias(type_alias) => { - DefinitionKind::TypeAlias(unsafe { AstNodeRef::new(parsed, type_alias) }) + DefinitionKind::TypeAlias(AstNodeRef::new(parsed, type_alias)) } DefinitionNodeRef::NamedExpression(named) => { - DefinitionKind::NamedExpression(unsafe { AstNodeRef::new(parsed, named) }) + DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named)) } DefinitionNodeRef::Assignment(AssignmentDefinitionNodeRef { unpack, @@ -380,8 +379,8 @@ impl<'db> DefinitionNodeRef<'_, 'db> { target, }) => DefinitionKind::Assignment(AssignmentDefinitionKind { target_kind: TargetKind::from(unpack), - value: unsafe { AstNodeRef::new(parsed.clone(), value) }, - target: unsafe { AstNodeRef::new(parsed, target) }, + value: AstNodeRef::new(parsed, value), + target: AstNodeRef::new(parsed, target), }), DefinitionNodeRef::AnnotatedAssignment(AnnotatedAssignmentDefinitionNodeRef { node: _, @@ -389,14 +388,12 @@ impl<'db> DefinitionNodeRef<'_, 'db> { value, target, }) => DefinitionKind::AnnotatedAssignment(AnnotatedAssignmentDefinitionKind { - target: unsafe { AstNodeRef::new(parsed.clone(), target) }, - annotation: unsafe { AstNodeRef::new(parsed.clone(), annotation) }, - value: value.map(|v| unsafe { AstNodeRef::new(parsed, v) }), + target: AstNodeRef::new(parsed, target), + annotation: AstNodeRef::new(parsed, annotation), + value: value.map(|v| AstNodeRef::new(parsed, v)), }), DefinitionNodeRef::AugmentedAssignment(augmented_assignment) => { - DefinitionKind::AugmentedAssignment(unsafe { - AstNodeRef::new(parsed, augmented_assignment) - }) + DefinitionKind::AugmentedAssignment(AstNodeRef::new(parsed, augmented_assignment)) } DefinitionNodeRef::For(ForStmtDefinitionNodeRef { unpack, @@ -405,8 +402,8 @@ impl<'db> DefinitionNodeRef<'_, 'db> { is_async, }) => DefinitionKind::For(ForStmtDefinitionKind { target_kind: TargetKind::from(unpack), - iterable: unsafe { AstNodeRef::new(parsed.clone(), iterable) }, - target: unsafe { AstNodeRef::new(parsed, target) }, + iterable: AstNodeRef::new(parsed, iterable), + target: AstNodeRef::new(parsed, target), is_async, }), DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef { @@ -417,23 +414,19 @@ impl<'db> DefinitionNodeRef<'_, 'db> { is_async, }) => DefinitionKind::Comprehension(ComprehensionDefinitionKind { target_kind: TargetKind::from(unpack), - iterable: unsafe { AstNodeRef::new(parsed.clone(), iterable) }, - target: unsafe { AstNodeRef::new(parsed, target) }, + iterable: AstNodeRef::new(parsed, iterable), + target: AstNodeRef::new(parsed, target), first, is_async, }), DefinitionNodeRef::VariadicPositionalParameter(parameter) => { - DefinitionKind::VariadicPositionalParameter(unsafe { - AstNodeRef::new(parsed, parameter) - }) + DefinitionKind::VariadicPositionalParameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::VariadicKeywordParameter(parameter) => { - DefinitionKind::VariadicKeywordParameter(unsafe { - AstNodeRef::new(parsed, parameter) - }) + DefinitionKind::VariadicKeywordParameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::Parameter(parameter) => { - DefinitionKind::Parameter(unsafe { AstNodeRef::new(parsed, parameter) }) + DefinitionKind::Parameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::WithItem(WithItemDefinitionNodeRef { unpack, @@ -442,8 +435,8 @@ impl<'db> DefinitionNodeRef<'_, 'db> { is_async, }) => DefinitionKind::WithItem(WithItemDefinitionKind { target_kind: TargetKind::from(unpack), - context_expr: unsafe { AstNodeRef::new(parsed.clone(), context_expr) }, - target: unsafe { AstNodeRef::new(parsed, target) }, + context_expr: AstNodeRef::new(parsed, context_expr), + target: AstNodeRef::new(parsed, target), is_async, }), DefinitionNodeRef::MatchPattern(MatchPatternDefinitionNodeRef { @@ -451,25 +444,25 @@ impl<'db> DefinitionNodeRef<'_, 'db> { identifier, index, }) => DefinitionKind::MatchPattern(MatchPatternDefinitionKind { - pattern: unsafe { AstNodeRef::new(parsed.clone(), pattern) }, - identifier: unsafe { AstNodeRef::new(parsed, identifier) }, + pattern: AstNodeRef::new(parsed, pattern), + identifier: AstNodeRef::new(parsed, identifier), index, }), DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, is_star, }) => DefinitionKind::ExceptHandler(ExceptHandlerDefinitionKind { - handler: unsafe { AstNodeRef::new(parsed, handler) }, + handler: AstNodeRef::new(parsed, handler), is_star, }), DefinitionNodeRef::TypeVar(node) => { - DefinitionKind::TypeVar(unsafe { AstNodeRef::new(parsed, node) }) + DefinitionKind::TypeVar(AstNodeRef::new(parsed, node)) } DefinitionNodeRef::ParamSpec(node) => { - DefinitionKind::ParamSpec(unsafe { AstNodeRef::new(parsed, node) }) + DefinitionKind::ParamSpec(AstNodeRef::new(parsed, node)) } DefinitionNodeRef::TypeVarTuple(node) => { - DefinitionKind::TypeVarTuple(unsafe { AstNodeRef::new(parsed, node) }) + DefinitionKind::TypeVarTuple(AstNodeRef::new(parsed, node)) } } } diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs index d8295ef50e81cc..f8fa9e147468ab 100644 --- a/crates/ty_python_semantic/src/semantic_index/place.rs +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -778,46 +778,42 @@ pub(crate) enum NodeWithScopeRef<'a> { impl NodeWithScopeRef<'_> { /// Converts the unowned reference to an owned [`NodeWithScopeKind`]. /// - /// # Safety - /// The node wrapped by `self` must be a child of `module`. - #[expect(unsafe_code)] - pub(super) unsafe fn to_kind(self, module: ParsedModuleRef) -> NodeWithScopeKind { - unsafe { - match self { - NodeWithScopeRef::Module => NodeWithScopeKind::Module, - NodeWithScopeRef::Class(class) => { - NodeWithScopeKind::Class(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::Function(function) => { - NodeWithScopeKind::Function(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::TypeAlias(type_alias) => { - NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { - NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::Lambda(lambda) => { - NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) - } - NodeWithScopeRef::FunctionTypeParameters(function) => { - NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::ClassTypeParameters(class) => { - NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::ListComprehension(comprehension) => { - NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::SetComprehension(comprehension) => { - NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::DictComprehension(comprehension) => { - NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::GeneratorExpression(generator) => { - NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) - } + /// Note that node wrapped by `self` must be a child of `module`. + pub(super) fn to_kind(self, module: &ParsedModuleRef) -> NodeWithScopeKind { + match self { + NodeWithScopeRef::Module => NodeWithScopeKind::Module, + NodeWithScopeRef::Class(class) => { + NodeWithScopeKind::Class(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::Function(function) => { + NodeWithScopeKind::Function(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::Lambda(lambda) => { + NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) + } + NodeWithScopeRef::FunctionTypeParameters(function) => { + NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::ClassTypeParameters(class) => { + NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::ListComprehension(comprehension) => { + NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::SetComprehension(comprehension) => { + NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::DictComprehension(comprehension) => { + NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::GeneratorExpression(generator) => { + NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) } } } diff --git a/crates/ty_python_semantic/src/semantic_index/re_exports.rs b/crates/ty_python_semantic/src/semantic_index/re_exports.rs index 1f31e05f7dbe7f..275e13d7a7bd79 100644 --- a/crates/ty_python_semantic/src/semantic_index/re_exports.rs +++ b/crates/ty_python_semantic/src/semantic_index/re_exports.rs @@ -104,6 +104,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { name, asname, range: _, + node_index: _, } = alias; let name = &name.id; @@ -126,6 +127,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { pattern, name, range: _, + node_index: _, }) => { if let Some(pattern) = pattern { self.visit_pattern(pattern); @@ -145,6 +147,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { rest, keys: _, range: _, + node_index: _, }) => { for pattern in patterns { self.visit_pattern(pattern); @@ -153,7 +156,11 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { self.possibly_add_export(&rest.id, PossibleExportKind::Normal); } } - ast::Pattern::MatchStar(ast::PatternMatchStar { name, range: _ }) => { + ast::Pattern::MatchStar(ast::PatternMatchStar { + name, + range: _, + node_index: _, + }) => { if let Some(name) = name { self.possibly_add_export(&name.id, PossibleExportKind::Normal); } @@ -176,6 +183,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { type_params: _, // We don't want to visit the type params of the class body: _, // We don't want to visit the body of the class range: _, + node_index: _, }) => { self.possibly_add_export(&name.id, PossibleExportKind::Normal); for decorator in decorator_list { @@ -194,6 +202,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { type_params: _, // We don't want to visit the type params of the function body: _, // We don't want to visit the body of the function range: _, + node_index: _, is_async: _, }) => { self.possibly_add_export(&name.id, PossibleExportKind::Normal); @@ -212,6 +221,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { annotation, simple: _, range: _, + node_index: _, }) => { if value.is_some() || self.visiting_stub_file { self.visit_expr(target); @@ -227,6 +237,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { type_params: _, value: _, range: _, + node_index: _, }) => { self.visit_expr(name); // Neither walrus expressions nor statements cannot appear in type aliases; @@ -286,7 +297,12 @@ impl<'db> Visitor<'db> for ExportFinder<'db> { fn visit_expr(&mut self, expr: &'db ast::Expr) { match expr { - ast::Expr::Name(ast::ExprName { id, ctx, range: _ }) => { + ast::Expr::Name(ast::ExprName { + id, + ctx, + range: _, + node_index: _, + }) => { if ctx.is_store() { self.possibly_add_export(id, PossibleExportKind::Normal); } @@ -359,11 +375,13 @@ impl<'db> Visitor<'db> for WalrusFinder<'_, 'db> { target, value: _, range: _, + node_index: _, }) => { if let ast::Expr::Name(ast::ExprName { id, ctx: ast::ExprContext::Store, range: _, + node_index: _, }) = &**target { self.export_finder diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index c0897c80092a76..7b3b79472f2d22 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1984,7 +1984,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match statement { ast::Stmt::FunctionDef(function) => self.infer_function_definition_statement(function), ast::Stmt::ClassDef(class) => self.infer_class_definition_statement(class), - ast::Stmt::Expr(ast::StmtExpr { range: _, value }) => { + ast::Stmt::Expr(ast::StmtExpr { + range: _, + node_index: _, + value, + }) => { self.infer_expression(value); } ast::Stmt::If(if_statement) => self.infer_if_statement(if_statement), @@ -2033,6 +2037,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::StmtFunctionDef { range: _, + node_index: _, is_async: _, name, type_params, @@ -2165,6 +2170,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_parameters(&mut self, parameters: &ast::Parameters) { let ast::Parameters { range: _, + node_index: _, posonlyargs: _, args: _, vararg, @@ -2186,6 +2192,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) { let ast::ParameterWithDefault { range: _, + node_index: _, parameter, default: _, } = parameter_with_default; @@ -2199,6 +2206,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_parameter(&mut self, parameter: &ast::Parameter) { let ast::Parameter { range: _, + node_index: _, name: _, annotation, } = parameter; @@ -2244,6 +2252,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { parameter, default, range: _, + node_index: _, } = parameter_with_default; let default_ty = default .as_ref() @@ -2378,6 +2387,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::StmtClassDef { range: _, + node_index: _, name, type_params, decorator_list, @@ -2509,6 +2519,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) { let ast::StmtIf { range: _, + node_index: _, test, body, elif_else_clauses, @@ -2525,6 +2536,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for clause in elif_else_clauses { let ast::ElifElseClause { range: _, + node_index: _, test, body, } = clause; @@ -2544,6 +2556,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_try_statement(&mut self, try_statement: &ast::StmtTry) { let ast::StmtTry { range: _, + node_index: _, body, handlers, orelse, @@ -2560,6 +2573,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { name: symbol_name, body, range: _, + node_index: _, } = handler; // If `symbol_name` is `Some()` and `handled_exceptions` is `None`, @@ -2582,6 +2596,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_with_statement(&mut self, with_statement: &ast::StmtWith) { let ast::StmtWith { range: _, + node_index: _, is_async, items, body, @@ -2781,6 +2796,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::TypeParamTypeVar { range: _, + node_index: _, name, bound, default, @@ -2848,6 +2864,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::TypeParamParamSpec { range: _, + node_index: _, name: _, default, } = node; @@ -2867,6 +2884,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::TypeParamTypeVarTuple { range: _, + node_index: _, name: _, default, } = node; @@ -2882,6 +2900,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) { let ast::StmtMatch { range: _, + node_index: _, subject, cases, } = match_statement; @@ -2891,6 +2910,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for case in cases { let ast::MatchCase { range: _, + node_index: _, body, pattern, guard, @@ -2958,6 +2978,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::Pattern::MatchClass(match_class) => { let ast::PatternMatchClass { range: _, + node_index: _, cls, arguments, } = match_class; @@ -2993,6 +3014,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::Pattern::MatchMapping(match_mapping) => { let ast::PatternMatchMapping { range: _, + node_index: _, keys, patterns, rest: _, @@ -3007,6 +3029,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::Pattern::MatchClass(match_class) => { let ast::PatternMatchClass { range: _, + node_index: _, cls, arguments, } = match_class; @@ -3035,6 +3058,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_assignment_statement(&mut self, assignment: &ast::StmtAssign) { let ast::StmtAssign { range: _, + node_index: _, targets, value, } = assignment; @@ -3652,6 +3676,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Non-name assignment targets are inferred as ordinary expressions, not definitions. let ast::StmtAnnAssign { range: _, + node_index: _, annotation, value, target, @@ -3853,6 +3878,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_augment_assignment(&mut self, assignment: &ast::StmtAugAssign) -> Type<'db> { let ast::StmtAugAssign { range: _, + node_index: _, target, op: _, value, @@ -3889,6 +3915,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) { let ast::StmtFor { range: _, + node_index: _, target, iter, body, @@ -3945,6 +3972,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_while_statement(&mut self, while_statement: &ast::StmtWhile) { let ast::StmtWhile { range: _, + node_index: _, test, body, orelse, @@ -3961,7 +3989,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_import_statement(&mut self, import: &ast::StmtImport) { - let ast::StmtImport { range: _, names } = import; + let ast::StmtImport { + range: _, + node_index: _, + names, + } = import; for alias in names { self.infer_definition(alias); @@ -4028,6 +4060,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) { let ast::Alias { range: _, + node_index: _, name, asname, } = alias; @@ -4077,6 +4110,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) { let ast::StmtImportFrom { range: _, + node_index: _, module: _, names, level: _, @@ -4094,6 +4128,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_assert_statement(&mut self, assert: &ast::StmtAssert) { let ast::StmtAssert { range: _, + node_index: _, test, msg, } = assert; @@ -4110,6 +4145,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_raise_statement(&mut self, raise: &ast::StmtRaise) { let ast::StmtRaise { range: _, + node_index: _, exc, cause, } = raise; @@ -4350,7 +4386,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_delete_statement(&mut self, delete: &ast::StmtDelete) { - let ast::StmtDelete { range: _, targets } = delete; + let ast::StmtDelete { + range: _, + node_index: _, + targets, + } = delete; for target in targets { self.infer_expression(target); } @@ -4364,6 +4404,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> { let ast::Decorator { range: _, + node_index: _, expression, } = decorator; @@ -4467,7 +4508,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_expression_impl(&mut self, expression: &ast::Expr) -> Type<'db> { let ty = match expression { - ast::Expr::NoneLiteral(ast::ExprNoneLiteral { range: _ }) => Type::none(self.db()), + ast::Expr::NoneLiteral(ast::ExprNoneLiteral { + range: _, + node_index: _, + }) => Type::none(self.db()), ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal), ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal), ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal), @@ -4526,7 +4570,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_number_literal_expression(&mut self, literal: &ast::ExprNumberLiteral) -> Type<'db> { - let ast::ExprNumberLiteral { range: _, value } = literal; + let ast::ExprNumberLiteral { + range: _, + node_index: _, + value, + } = literal; let db = self.db(); match value { @@ -4541,7 +4589,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { #[expect(clippy::unused_self)] fn infer_boolean_literal_expression(&mut self, literal: &ast::ExprBooleanLiteral) -> Type<'db> { - let ast::ExprBooleanLiteral { range: _, value } = literal; + let ast::ExprBooleanLiteral { + range: _, + node_index: _, + value, + } = literal; Type::BooleanLiteral(*value) } @@ -4561,7 +4613,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_fstring_expression(&mut self, fstring: &ast::ExprFString) -> Type<'db> { - let ast::ExprFString { range: _, value } = fstring; + let ast::ExprFString { + range: _, + node_index: _, + value, + } = fstring; let mut collector = StringPartsCollector::new(); for part in value { @@ -4577,6 +4633,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::InterpolatedStringElement::Interpolation(expression) => { let ast::InterpolatedElement { range: _, + node_index: _, expression, debug_text: _, conversion, @@ -4678,6 +4735,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> { let ast::ExprTuple { range: _, + node_index: _, elts, ctx: _, parenthesized: _, @@ -4694,6 +4752,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> { let ast::ExprList { range: _, + node_index: _, elts, ctx: _, } = list; @@ -4707,7 +4766,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_set_expression(&mut self, set: &ast::ExprSet) -> Type<'db> { - let ast::ExprSet { range: _, elts } = set; + let ast::ExprSet { + range: _, + node_index: _, + elts, + } = set; for elt in elts { self.infer_expression(elt); @@ -4718,7 +4781,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_dict_expression(&mut self, dict: &ast::ExprDict) -> Type<'db> { - let ast::ExprDict { range: _, items } = dict; + let ast::ExprDict { + range: _, + node_index: _, + items, + } = dict; for item in items { self.infer_optional_expression(item.key.as_ref()); @@ -4741,6 +4808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_generator_expression(&mut self, generator: &ast::ExprGenerator) -> Type<'db> { let ast::ExprGenerator { range: _, + node_index: _, elt: _, generators, parenthesized: _, @@ -4754,6 +4822,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> { let ast::ExprListComp { range: _, + node_index: _, elt: _, generators, } = listcomp; @@ -4766,6 +4835,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> { let ast::ExprDictComp { range: _, + node_index: _, key: _, value: _, generators, @@ -4779,6 +4849,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> { let ast::ExprSetComp { range: _, + node_index: _, elt: _, generators, } = setcomp; @@ -4791,6 +4862,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) { let ast::ExprGenerator { range: _, + node_index: _, elt, generators, parenthesized: _, @@ -4803,6 +4875,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_list_comprehension_expression_scope(&mut self, listcomp: &ast::ExprListComp) { let ast::ExprListComp { range: _, + node_index: _, elt, generators, } = listcomp; @@ -4814,6 +4887,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_dict_comprehension_expression_scope(&mut self, dictcomp: &ast::ExprDictComp) { let ast::ExprDictComp { range: _, + node_index: _, key, value, generators, @@ -4827,6 +4901,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_set_comprehension_expression_scope(&mut self, setcomp: &ast::ExprSetComp) { let ast::ExprSetComp { range: _, + node_index: _, elt, generators, } = setcomp; @@ -4849,6 +4924,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_comprehension(&mut self, comprehension: &ast::Comprehension, is_first: bool) { let ast::Comprehension { range: _, + node_index: _, target, iter, ifs, @@ -4959,6 +5035,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) -> Type<'db> { let ast::ExprNamed { range: _, + node_index: _, target, value, } = named; @@ -4974,6 +5051,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_if_expression(&mut self, if_expression: &ast::ExprIf) -> Type<'db> { let ast::ExprIf { range: _, + node_index: _, test, body, orelse, @@ -5000,6 +5078,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_lambda_expression(&mut self, lambda_expression: &ast::ExprLambda) -> Type<'db> { let ast::ExprLambda { range: _, + node_index: _, parameters, body: _, } = lambda_expression; @@ -5088,6 +5167,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) -> Type<'db> { let ast::ExprCall { range: _, + node_index: _, func, arguments, } = call_expression; @@ -5733,6 +5813,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> { let ast::ExprStarred { range: _, + node_index: _, value, ctx: _, } = starred; @@ -5748,13 +5829,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { - let ast::ExprYield { range: _, value } = yield_expression; + let ast::ExprYield { + range: _, + node_index: _, + value, + } = yield_expression; self.infer_optional_expression(value.as_deref()); todo_type!("yield expressions") } fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> { - let ast::ExprYieldFrom { range: _, value } = yield_from; + let ast::ExprYieldFrom { + range: _, + node_index: _, + value, + } = yield_from; let iterable_type = self.infer_expression(value); iterable_type.try_iterate(self.db()).unwrap_or_else(|err| { @@ -5767,7 +5856,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> { - let ast::ExprAwait { range: _, value } = await_expression; + let ast::ExprAwait { + range: _, + node_index: _, + value, + } = await_expression; self.infer_expression(value); todo_type!("generic `typing.Awaitable` type") } @@ -5794,6 +5887,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_name_load(&mut self, name_node: &ast::ExprName) -> Type<'db> { let ast::ExprName { range: _, + node_index: _, id: symbol_name, ctx: _, } = name_node; @@ -6160,6 +6254,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value, attr, range: _, + node_index: _, ctx: _, } = attribute; @@ -6253,6 +6348,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value, attr: _, range: _, + node_index: _, ctx, } = attribute; @@ -6276,6 +6372,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_unary_expression(&mut self, unary: &ast::ExprUnaryOp) -> Type<'db> { let ast::ExprUnaryOp { range: _, + node_index: _, op, operand, } = unary; @@ -6370,6 +6467,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { op, right, range: _, + node_index: _, } = binary; let left_ty = self.infer_expression(left); @@ -6765,6 +6863,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_boolean_expression(&mut self, bool_op: &ast::ExprBoolOp) -> Type<'db> { let ast::ExprBoolOp { range: _, + node_index: _, op, values, } = bool_op; @@ -6850,6 +6949,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_compare_expression(&mut self, compare: &ast::ExprCompare) -> Type<'db> { let ast::ExprCompare { range: _, + node_index: _, left, ops, comparators, @@ -7649,6 +7749,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value, slice, range: _, + node_index: _, ctx, } = subscript; @@ -7676,6 +7777,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> { let ast::ExprSubscript { range: _, + node_index: _, value, slice, ctx: _, @@ -8153,6 +8255,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let ast::ExprSlice { range: _, + node_index: _, lower, upper, step, @@ -8188,6 +8291,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) { let ast::TypeParams { range: _, + node_index: _, type_params, } = type_parameters; for type_param in type_params { @@ -8494,6 +8598,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { slice, ctx: _, range: _, + node_index: _, } = subscript; let value_ty = self.infer_expression(value); @@ -9037,6 +9142,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) -> Type<'db> { let ast::ExprSubscript { range: _, + node_index: _, value: _, slice, ctx: _, diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index a0e4ce3624cc17..9af855a100a1a7 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -605,6 +605,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { let ast::ExprCompare { range: _, + node_index: _, left, ops, comparators, @@ -656,12 +657,14 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { } ast::Expr::Call(ast::ExprCall { range: _, + node_index: _, func: callable, arguments: ast::Arguments { args, keywords, range: _, + node_index: _, }, }) if keywords.is_empty() => { let rhs_class = match rhs_ty { diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 810a5a8a8e36f1..bcaeb6be1204cf 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1097,6 +1097,7 @@ impl<'db> Parameters<'db> { kwonlyargs, kwarg, range: _, + node_index: _, } = parameters; let default_type = |param: &ast::ParameterWithDefault| { param From 793ff9bdbc2ad4909f45594726ee0d2ff166130c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=95=82?= <51281148+K-dash@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:39:55 +0900 Subject: [PATCH 411/487] Fix false positive in for mutations in return statements (B909) (#18408) ## Summary Fixes false positive in B909 (`loop-iterator-mutation`) where mutations inside return/break statements were incorrectly flagged as violations. The fix adds tracking for when mutations occur within return/break statements and excludes them from violation detection, as they don't cause the iteration issues B909 is designed to prevent. ## Test Plan - Added test cases covering the reported false positive scenarios to `B909.py` - Verified existing B909 tests continue to pass (no regressions) - Ran `cargo test -p ruff_linter --lib flake8_bugbear` successfully Fixes #18399 --- .../resources/test/fixtures/flake8_bugbear/B909.py | 14 ++++++++++++++ .../flake8_bugbear/rules/loop_iterator_mutation.rs | 1 - 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py index c9be4f036bd887..08de7b2f5be9da 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py @@ -179,3 +179,17 @@ def func(): for elem in some_list: if some_list.pop() == 2: return + +# should not error - direct return with mutation (Issue #18399) +def fail_map(mapping): + for key in mapping: + return mapping.pop(key) + +def success_map(mapping): + for key in mapping: + ret = mapping.pop(key) # should not error + return ret + +def fail_list(seq): + for val in seq: + return seq.pop(4) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs index 83094305b845a1..6a617a147de628 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/loop_iterator_mutation.rs @@ -293,7 +293,6 @@ impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> { if let Some(mutations) = self.mutations.get_mut(&self.branch) { mutations.clear(); } - visitor::walk_stmt(self, stmt); } // Avoid recursion for class and function definitions. From 1889a5e6ebeed97d9e8fbd1fd005258172954bbf Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 13 Jun 2025 14:04:37 -0500 Subject: [PATCH 412/487] [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 (#18664) Closes #18662 One question is whether we would like the range to exclude the quotes? --- .../inline/err/template_strings_py313.py | 6 + .../inline/ok/template_strings_py314.py | 6 + .../src/parser/expression.rs | 24 +- ...alid_syntax@template_strings_py313.py.snap | 231 ++++++++++++++++++ ...alid_syntax@template_strings_py314.py.snap | 194 +++++++++++++++ 5 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/err/template_strings_py313.py create mode 100644 crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap diff --git a/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py b/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py new file mode 100644 index 00000000000000..2c7fde825a9bd9 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py @@ -0,0 +1,6 @@ +# parse_options: {"target-version": "3.13"} +t"{hey}" +t'{there}' +t"""what's +happening?""" +"implicitly"t"concatenated" diff --git a/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py b/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py new file mode 100644 index 00000000000000..541f49917addc9 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py @@ -0,0 +1,6 @@ +# parse_options: {"target-version": "3.14"} +t"{hey}" +t'{there}' +t"""what's +happening?""" +"implicitly"t"concatenated" diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index ffe6a56d8a6b36..9eff91aadb1710 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1250,10 +1250,30 @@ impl<'src> Parser<'src> { .into(), )); } else if self.at(TokenKind::TStringStart) { - strings.push(StringType::TString( + // test_ok template_strings_py314 + // # parse_options: {"target-version": "3.14"} + // t"{hey}" + // t'{there}' + // t"""what's + // happening?""" + // "implicitly"t"concatenated" + + // test_err template_strings_py313 + // # parse_options: {"target-version": "3.13"} + // t"{hey}" + // t'{there}' + // t"""what's + // happening?""" + // "implicitly"t"concatenated" + let string_type = StringType::TString( self.parse_interpolated_string(InterpolatedStringKind::TString) .into(), - )); + ); + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TemplateStrings, + string_type.range(), + ); + strings.push(string_type); } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap new file mode 100644 index 00000000000000..87586ee1bcd4ab --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap @@ -0,0 +1,231 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/template_strings_py313.py +--- +## AST + +``` +Module( + ModModule { + node_index: AtomicNodeIndex(..), + range: 0..117, + body: [ + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..52, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 46..51, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 47..50, + id: Name("hey"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TStringValue { + inner: Single( + TString( + TString { + range: 53..63, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 55..62, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 56..61, + id: Name("there"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TStringValue { + inner: Single( + TString( + TString { + range: 64..88, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 68..85, + node_index: AtomicNodeIndex(..), + value: "what's\nhappening?", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 89..101, + node_index: AtomicNodeIndex(..), + value: "implicitly", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 101..116, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 103..115, + node_index: AtomicNodeIndex(..), + value: "concatenated", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.13"} +2 | t"{hey}" + | ^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +3 | t'{there}' +4 | t"""what's + | + + + | +1 | # parse_options: {"target-version": "3.13"} +2 | t"{hey}" +3 | t'{there}' + | ^^^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +4 | t"""what's +5 | happening?""" + | + + + | +2 | t"{hey}" +3 | t'{there}' +4 | / t"""what's +5 | | happening?""" + | |_____________^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +6 | "implicitly"t"concatenated" + | + + + | +4 | t"""what's +5 | happening?""" +6 | "implicitly"t"concatenated" + | ^^^^^^^^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap new file mode 100644 index 00000000000000..f314487ff6b826 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap @@ -0,0 +1,194 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py +--- +## AST + +``` +Module( + ModModule { + node_index: AtomicNodeIndex(..), + range: 0..117, + body: [ + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..52, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 46..51, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 47..50, + id: Name("hey"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TStringValue { + inner: Single( + TString( + TString { + range: 53..63, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 55..62, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 56..61, + id: Name("there"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TStringValue { + inner: Single( + TString( + TString { + range: 64..88, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 68..85, + node_index: AtomicNodeIndex(..), + value: "what's\nhappening?", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 89..101, + node_index: AtomicNodeIndex(..), + value: "implicitly", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 101..116, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 103..115, + node_index: AtomicNodeIndex(..), + value: "concatenated", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + ], + }, +) +``` From 89d915a1e34144051815dfcbe60ec2cdeb29909e Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 13 Jun 2025 21:50:57 +0200 Subject: [PATCH 413/487] [ty] Delay computation of 'unbound' visibility for implicit instance attributes (#18669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Consider the following example, which leads to a excessively large runtime on `main`. The reason for this is the following. When inferring types for `self.a`, we look up the `a` attribute on `C`. While looking for implicit instance attributes, we go through every method and check for `self.a = …` assignments. There are no such assignments here, but we always have an implicit `self.a = ` binding at the beginning over every method. This binding accumulates a complex visibility constraint in `C.f`, due to the `isinstance` checks. While evaluating that constraint, we need to infer the type of `self.b`. There's no binding for `self.b` either, but there's also an implicit `self.b = ` binding with the same complex visibility constraint (involving `self.b` recursively). This leads to a combinatorial explosion: ```py class C: def f(self: "C"): if isinstance(self.a, str): return if isinstance(self.b, str): return if isinstance(self.b, str): return if isinstance(self.b, str): return # repeat 20 times ``` (note that the `self` parameter here is annotated explicitly because we currently still infer `Unknown` for `self` otherwise) The fix proposed here is rather simple: when there are no `self.name = …` attribute assignments in a given method, we skip evaluating the visibility constraint of the implicit `self.name = ` binding. This should also generally help with performance, because that's a very common case. This is *not* a fix for cases where there *are* actual bindings in the method. When we add `self.a = 1; self.b = 1` to that example above, we still see that combinatorial explosion of runtime. I still think it's worth to make this optimization, as it fixes the problems with `pandas` and `sqlalchemy` reported by users. I will open a ticket to track that separately. closes https://github.com/astral-sh/ty/issues/627 closes https://github.com/astral-sh/ty/issues/641 ## Test Plan * Made sure that `ty` finishes quickly on the MREs in https://github.com/astral-sh/ty/issues/627 * Made sure that `ty` finishes quickly on `pandas` * Made sure that `ty` finishes quickly on `sqlalchemy` --- .../src/semantic_index/definition.rs | 4 --- crates/ty_python_semantic/src/types/class.rs | 29 ++++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 9e1e6a89cf4be1..869944eba5bceb 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -109,10 +109,6 @@ impl<'db> DefinitionState<'db> { || matches!(self, DefinitionState::Defined(def) if f(def)) } - pub(crate) fn is_undefined(self) -> bool { - matches!(self, DefinitionState::Undefined) - } - #[allow(unused)] pub(crate) fn definition(self) -> Option> { match self { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 78532e12de12bd..165a0a3b85eb6f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1640,19 +1640,21 @@ impl<'db> ClassLiteral<'db> { continue; } - let mut attribute_assignments = attribute_assignments.peekable(); - let unbound_visibility = attribute_assignments - .peek() - .map(|attribute_assignment| { - if attribute_assignment.binding.is_undefined() { - method_map.is_binding_visible(db, attribute_assignment) - } else { - Truthiness::AlwaysFalse - } - }) - .unwrap_or(Truthiness::AlwaysFalse); + // Storage for the implicit `DefinitionState::Undefined` binding. If present, it + // will be the first binding in the `attribute_assignments` iterator. + let mut unbound_binding = None; for attribute_assignment in attribute_assignments { + if let DefinitionState::Undefined = attribute_assignment.binding { + // Store the implicit unbound binding here so that we can delay the + // computation of `unbound_visibility` to the point when we actually + // need it. This is an optimization for the common case where the + // `unbound` binding is the only binding of the `name` attribute, + // i.e. if there is no `self.name = …` assignment in this method. + unbound_binding = Some(attribute_assignment); + continue; + } + let DefinitionState::Defined(binding) = attribute_assignment.binding else { continue; }; @@ -1676,6 +1678,11 @@ impl<'db> ClassLiteral<'db> { // There is at least one attribute assignment that may be visible, // so if `unbound_visibility` is always false then this attribute is considered bound. // TODO: this is incomplete logic since the attributes bound after termination are considered visible. + let unbound_visibility = unbound_binding + .as_ref() + .map(|binding| method_map.is_binding_visible(db, binding)) + .unwrap_or(Truthiness::AlwaysFalse); + if unbound_visibility .negate() .and(is_method_visible) From 6d56ee803ee79f4b15b497cc5c032d8df115dc36 Mon Sep 17 00:00:00 2001 From: InSync Date: Sat, 14 Jun 2025 05:27:45 +0700 Subject: [PATCH 414/487] [ty] Add partial support for `TypeIs` (#18589) ## Summary Part of [#117](https://github.com/astral-sh/ty/issues/117). `TypeIs[]` is a special form that allows users to define their own narrowing functions. Despite the syntax, `TypeIs` is not a generic and, on its own, it is meaningless as a type. [Officially](https://typing.python.org/en/latest/spec/narrowing.html#typeis), a function annotated as returning a `TypeIs[T]` is a type narrowing function, where `T` is called the `TypeIs` return type. A `TypeIs[T]` may or may not be bound to a symbol. Only bound types have narrowing effect: ```python def f(v: object = object()) -> TypeIs[int]: ... a: str = returns_str() if reveal_type(f()): # Unbound: TypeIs[int] reveal_type(a) # str if reveal_type(f(a)): # Bound: TypeIs[a, int] reveal_type(a) # str & int ``` Delayed usages of a bound type has no effect, however: ```python b = f(a) if b: reveal_type(a) # str ``` A `TypeIs[T]` type: * Is fully static when `T` is fully static. * Is a singleton/single-valued when it is bound. * Has exactly two runtime inhabitants when it is unbound: `True` and `False`. In other words, an unbound type have ambiguous truthiness. It is possible to infer more precise truthiness for bound types; however, that is not part of this change. `TypeIs[T]` is a subtype of or otherwise assignable to `bool`. `TypeIs` is invariant with respect to the `TypeIs` return type: `TypeIs[int]` is neither a subtype nor a supertype of `TypeIs[bool]`. When ty sees a function marked as returning `TypeIs[T]`, its `return`s will be checked against `bool` instead. ty will also report such functions if they don't accept a positional argument. Addtionally, a type narrowing function call with no positional arguments (e.g., `f()` in the example above) will be considered invalid. ## Test Plan Markdown tests. --------- Co-authored-by: Carl Meyer --- crates/ty/docs/rules.md | 174 ++++++--- .../annotations/unsupported_special_forms.md | 1 - .../resources/mdtest/narrow/type_guards.md | 330 ++++++++++++++++++ .../type_properties/is_assignable_to.md | 16 + .../type_properties/is_disjoint_from.md | 14 + .../mdtest/type_properties/is_subtype_of.md | 32 ++ crates/ty_python_semantic/src/types.rs | 119 ++++++- .../src/types/class_base.rs | 3 +- .../src/types/diagnostic.rs | 58 +++ .../ty_python_semantic/src/types/display.rs | 9 + .../src/types/ide_support.rs | 3 +- crates/ty_python_semantic/src/types/infer.rs | 107 ++++-- crates/ty_python_semantic/src/types/narrow.rs | 24 +- .../src/types/type_ordering.rs | 28 +- ty.schema.json | 20 ++ 15 files changed, 841 insertions(+), 97 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 30fe9952314334..b36ccae6576bef 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L94) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L138) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L164) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L189) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L215) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L390) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L410) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L603) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L629) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L705) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L748) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L768) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L657) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850) ## `invalid-type-form` @@ -1012,7 +1012,73 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874) + + +## `invalid-type-guard-call` + +**Default level**: error + +
    +detects type guard function calls that has no narrowing effect + +### What it does +Checks for type guard function calls without a valid target. + +### Why is this bad? +The first non-keyword non-variadic argument to a type guard function +is its target and must map to a symbol. + +Starred (`is_str(*a)`), literal (`is_str(42)`) and other non-symbol-like +expressions are invalid as narrowing targets. + +### Examples +```python +from typing import TypeIs + +def f(v: object) -> TypeIs[int]: ... + +f() # Error +f(*a) # Error +f(10) # Error +``` + +### Links +* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) +
    + +## `invalid-type-guard-definition` + +**Default level**: error + +
    +detects malformed type guard functions + +### What it does +Checks for type guard functions without +a first non-self-like non-keyword-only non-variadic parameter. + +### Why is this bad? +Type narrowing functions must accept at least one positional argument +(non-static methods must accept another in addition to `self`/`cls`). + +Extra parameters/arguments are allowed but do not affect narrowing. + +### Examples +```python +from typing import TypeIs + +def f() -> TypeIs[int]: ... # Error, no parameter +def f(*, v: object) -> TypeIs[int]: ... # Error, no positional arguments allowed +def f(*args: object) -> TypeIs[int]: ... # Error, expect variadic arguments +class C: + def f(self) -> TypeIs[int]: ... # Error, only positional argument expected is `self` +``` + +### Links +* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L898)
    ## `invalid-type-variable-constraints` @@ -1046,7 +1112,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954) ## `missing-argument` @@ -1070,7 +1136,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) ## `no-matching-overload` @@ -1098,7 +1164,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002) ## `non-subscriptable` @@ -1121,7 +1187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1025) ## `not-iterable` @@ -1146,7 +1212,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043) ## `parameter-already-assigned` @@ -1172,7 +1238,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1094) ## `raw-string-type-annotation` @@ -1231,7 +1297,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430) ## `subclass-of-final-class` @@ -1259,7 +1325,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1185) ## `too-many-positional-arguments` @@ -1285,7 +1351,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) ## `type-assertion-failure` @@ -1312,7 +1378,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1422,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) ## `unknown-argument` @@ -1382,7 +1448,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1308) ## `unresolved-attribute` @@ -1409,7 +1475,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) ## `unresolved-import` @@ -1433,7 +1499,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) ## `unresolved-reference` @@ -1457,7 +1523,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) ## `unsupported-bool-conversion` @@ -1493,7 +1559,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063) ## `unsupported-operator` @@ -1520,7 +1586,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389) ## `zero-stepsize-in-slice` @@ -1544,7 +1610,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411) ## `invalid-ignore-comment` @@ -1600,7 +1666,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1115) ## `possibly-unbound-implicit-call` @@ -1631,7 +1697,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L112) ## `possibly-unbound-import` @@ -1662,7 +1728,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137) ## `redundant-cast` @@ -1688,7 +1754,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1482) ## `undefined-reveal` @@ -1711,7 +1777,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) ## `unknown-rule` @@ -1779,7 +1845,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L490) ## `division-by-zero` @@ -1802,7 +1868,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L241) ## `possibly-unresolved-reference` @@ -1829,7 +1895,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1163) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 3887d9bb8455ed..254ed90eea8739 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -19,7 +19,6 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`) def g() -> TypeGuard[int]: ... -def h() -> TypeIs[int]: ... def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co: reveal_type(args) # revealed: tuple[@Todo(Support for `typing.ParamSpec`), ...] reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)] diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md new file mode 100644 index 00000000000000..c65a3b22c695ba --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md @@ -0,0 +1,330 @@ +# User-defined type guards + +User-defined type guards are functions of which the return type is either `TypeGuard[...]` or +`TypeIs[...]`. + +## Display + +```py +from ty_extensions import Intersection, Not, TypeOf +from typing_extensions import TypeGuard, TypeIs + +def _( + a: TypeGuard[str], + b: TypeIs[str | int], + c: TypeGuard[Intersection[complex, Not[int], Not[float]]], + d: TypeIs[tuple[TypeOf[bytes]]], + e: TypeGuard, # error: [invalid-type-form] + f: TypeIs, # error: [invalid-type-form] +): + # TODO: Should be `TypeGuard[str]` + reveal_type(a) # revealed: @Todo(`TypeGuard[]` special form) + reveal_type(b) # revealed: TypeIs[str | int] + # TODO: Should be `TypeGuard[complex & ~int & ~float]` + reveal_type(c) # revealed: @Todo(`TypeGuard[]` special form) + reveal_type(d) # revealed: TypeIs[tuple[]] + reveal_type(e) # revealed: Unknown + reveal_type(f) # revealed: Unknown + +# TODO: error: [invalid-return-type] "Function always implicitly returns `None`, which is not assignable to return type `TypeGuard[str]`" +def _(a) -> TypeGuard[str]: ... + +# error: [invalid-return-type] "Function always implicitly returns `None`, which is not assignable to return type `TypeIs[str]`" +def _(a) -> TypeIs[str]: ... +def f(a) -> TypeGuard[str]: + return True + +def g(a) -> TypeIs[str]: + return True + +def _(a: object): + # TODO: Should be `TypeGuard[str @ a]` + reveal_type(f(a)) # revealed: @Todo(`TypeGuard[]` special form) + reveal_type(g(a)) # revealed: TypeIs[str @ a] +``` + +## Parameters + +A user-defined type guard must accept at least one positional argument (in addition to `self`/`cls` +for non-static methods). + +```pyi +from typing_extensions import TypeGuard, TypeIs + +# TODO: error: [invalid-type-guard-definition] +def _() -> TypeGuard[str]: ... + +# TODO: error: [invalid-type-guard-definition] +def _(**kwargs) -> TypeIs[str]: ... + +class _: + # fine + def _(self, /, a) -> TypeGuard[str]: ... + @classmethod + def _(cls, a) -> TypeGuard[str]: ... + @staticmethod + def _(a) -> TypeIs[str]: ... + + # errors + def _(self) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition] + def _(self, /, *, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition] + @classmethod + def _(cls) -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition] + @classmethod + def _() -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition] + @staticmethod + def _(*, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition] +``` + +For `TypeIs` functions, the narrowed type must be assignable to the declared type of that parameter, +if any. + +```pyi +from typing import Any +from typing_extensions import TypeIs + +def _(a: object) -> TypeIs[str]: ... +def _(a: Any) -> TypeIs[str]: ... +def _(a: tuple[object]) -> TypeIs[tuple[str]]: ... +def _(a: str | Any) -> TypeIs[str]: ... +def _(a) -> TypeIs[str]: ... + +# TODO: error: [invalid-type-guard-definition] +def _(a: int) -> TypeIs[str]: ... + +# TODO: error: [invalid-type-guard-definition] +def _(a: bool | str) -> TypeIs[int]: ... +``` + +## Arguments to special forms + +`TypeGuard` and `TypeIs` accept exactly one type argument. + +```py +from typing_extensions import TypeGuard, TypeIs + +a = 123 + +# TODO: error: [invalid-type-form] +def f(_) -> TypeGuard[int, str]: ... + +# error: [invalid-type-form] "Special form `typing.TypeIs` expected exactly one type parameter" +# error: [invalid-type-form] "Variable of type `Literal[123]` is not allowed in a type expression" +def g(_) -> TypeIs[a, str]: ... + +# TODO: Should be `Unknown` +reveal_type(f(0)) # revealed: @Todo(`TypeGuard[]` special form) +reveal_type(g(0)) # revealed: Unknown +``` + +## Return types + +All code paths in a type guard function must return booleans. + +```py +from typing_extensions import Literal, TypeGuard, TypeIs, assert_never + +def _(a: object, flag: bool) -> TypeGuard[str]: + if flag: + return 0 + + # TODO: error: [invalid-return-type] "Return type does not match returned value: expected `TypeIs[str]`, found `Literal["foo"]`" + return "foo" + +# error: [invalid-return-type] "Function can implicitly return `None`, which is not assignable to return type `TypeIs[str]`" +def f(a: object, flag: bool) -> TypeIs[str]: + if flag: + # error: [invalid-return-type] "Return type does not match returned value: expected `TypeIs[str]`, found `float`" + return 1.2 + +def g(a: Literal["foo", "bar"]) -> TypeIs[Literal["foo"]]: + if a == "foo": + # Logically wrong, but allowed regardless + return False + + return False +``` + +## Invalid calls + +```py +from typing import Any +from typing_extensions import TypeGuard, TypeIs + +def f(a: object) -> TypeGuard[str]: + return True + +def g(a: object) -> TypeIs[int]: + return True + +def _(d: Any): + if f(): # error: [missing-argument] + ... + + # TODO: no error, once we support splatted call args + if g(*d): # error: [missing-argument] + ... + + if f("foo"): # TODO: error: [invalid-type-guard-call] + ... + + if g(a=d): # error: [invalid-type-guard-call] + ... +``` + +## Narrowing + +```py +from typing import Any +from typing_extensions import TypeGuard, TypeIs + +def guard_str(a: object) -> TypeGuard[str]: + return True + +def is_int(a: object) -> TypeIs[int]: + return True +``` + +```py +def _(a: str | int): + if guard_str(a): + # TODO: Should be `str` + reveal_type(a) # revealed: str | int + else: + reveal_type(a) # revealed: str | int + + if is_int(a): + reveal_type(a) # revealed: int + else: + reveal_type(a) # revealed: str & ~int +``` + +Attribute and subscript narrowing is supported: + +```py +from typing_extensions import Any, Generic, Protocol, TypeVar + +T = TypeVar("T") + +class C(Generic[T]): + v: T + +def _(a: tuple[str, int] | tuple[int, str], c: C[Any]): + # TODO: Should be `TypeGuard[str @ a[1]]` + if reveal_type(guard_str(a[1])): # revealed: @Todo(`TypeGuard[]` special form) + # TODO: Should be `tuple[int, str]` + reveal_type(a) # revealed: tuple[str, int] | tuple[int, str] + # TODO: Should be `str` + reveal_type(a[1]) # revealed: Unknown + + if reveal_type(is_int(a[0])): # revealed: TypeIs[int @ a[0]] + # TODO: Should be `tuple[int, str]` + reveal_type(a) # revealed: tuple[str, int] | tuple[int, str] + # TODO: Should be `int` + reveal_type(a[0]) # revealed: Unknown + + # TODO: Should be `TypeGuard[str @ c.v]` + if reveal_type(guard_str(c.v)): # revealed: @Todo(`TypeGuard[]` special form) + reveal_type(c) # revealed: C[Any] + # TODO: Should be `str` + reveal_type(c.v) # revealed: Any + + if reveal_type(is_int(c.v)): # revealed: TypeIs[int @ c.v] + reveal_type(c) # revealed: C[Any] + # TODO: Should be `int` + reveal_type(c.v) # revealed: Any +``` + +Indirect usage is supported within the same scope: + +```py +def _(a: str | int): + b = guard_str(a) + c = is_int(a) + + reveal_type(a) # revealed: str | int + # TODO: Should be `TypeGuard[str @ a]` + reveal_type(b) # revealed: @Todo(`TypeGuard[]` special form) + reveal_type(c) # revealed: TypeIs[int @ a] + + if b: + # TODO should be `str` + reveal_type(a) # revealed: str | int + else: + reveal_type(a) # revealed: str | int + + if c: + # TODO should be `int` + reveal_type(a) # revealed: str | int + else: + # TODO should be `str & ~int` + reveal_type(a) # revealed: str | int +``` + +Further writes to the narrowed place invalidate the narrowing: + +```py +def _(x: str | int, flag: bool) -> None: + b = is_int(x) + reveal_type(b) # revealed: TypeIs[int @ x] + + if flag: + x = "" + + if b: + reveal_type(x) # revealed: str | int +``` + +The `TypeIs` type remains effective across generic boundaries: + +```py +from typing_extensions import TypeVar, reveal_type + +T = TypeVar("T") + +def f(v: object) -> TypeIs[int]: + return True + +def g(v: T) -> T: + return v + +def _(a: str): + # `reveal_type()` has the type `[T]() -> T` + if reveal_type(f(a)): # revealed: TypeIs[int @ a] + reveal_type(a) # revealed: str & int + + if g(f(a)): + reveal_type(a) # revealed: str & int +``` + +## `TypeGuard` special cases + +```py +from typing import Any +from typing_extensions import TypeGuard, TypeIs + +def guard_int(a: object) -> TypeGuard[int]: + return True + +def is_int(a: object) -> TypeIs[int]: + return True + +def does_not_narrow_in_negative_case(a: str | int): + if not guard_int(a): + # TODO: Should be `str` + reveal_type(a) # revealed: str | int + else: + reveal_type(a) # revealed: str | int + +def narrowed_type_must_be_exact(a: object, b: bool): + if guard_int(b): + # TODO: Should be `int` + reveal_type(b) # revealed: bool + + if isinstance(a, bool) and is_int(a): + reveal_type(a) # revealed: bool + + if isinstance(a, bool) and guard_int(a): + # TODO: Should be `int` + reveal_type(a) # revealed: bool +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 96993f16235884..b67c74d6934e79 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -871,4 +871,20 @@ def g3(obj: Foo[tuple[A]]): f3(obj) ``` +## `TypeGuard` and `TypeIs` + +`TypeGuard[...]` and `TypeIs[...]` are always assignable to `bool`. + +```py +from ty_extensions import Unknown, is_assignable_to, static_assert +from typing_extensions import Any, TypeGuard, TypeIs + +static_assert(is_assignable_to(TypeGuard[Unknown], bool)) +static_assert(is_assignable_to(TypeIs[Any], bool)) + +# TODO no error +static_assert(not is_assignable_to(TypeGuard[Unknown], str)) # error: [static-assert-error] +static_assert(not is_assignable_to(TypeIs[Any], str)) +``` + [typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index 20725ebd89f1f2..b94093e8676d8d 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -402,6 +402,20 @@ static_assert(is_disjoint_from(TypeOf[C.prop], D)) static_assert(is_disjoint_from(D, TypeOf[C.prop])) ``` +### `TypeGuard` and `TypeIs` + +```py +from ty_extensions import static_assert, is_disjoint_from +from typing_extensions import TypeGuard, TypeIs + +static_assert(not is_disjoint_from(bool, TypeGuard[str])) +static_assert(not is_disjoint_from(bool, TypeIs[str])) + +# TODO no error +static_assert(is_disjoint_from(str, TypeGuard[str])) # error: [static-assert-error] +static_assert(is_disjoint_from(str, TypeIs[str])) +``` + ## Callables No two callable types are disjoint because there exists a non-empty callable type diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index ffa1e0fad04d72..a77022df7a9d15 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -342,6 +342,38 @@ static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal[""]]], Not[A static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy])) ``` +### `TypeGuard` and `TypeIs` + +Fully-static `TypeGuard[...]` and `TypeIs[...]` are subtypes of `bool`. + +```py +from ty_extensions import is_subtype_of, static_assert +from typing_extensions import TypeGuard, TypeIs + +# TODO: TypeGuard +# static_assert(is_subtype_of(TypeGuard[int], bool)) +# static_assert(is_subtype_of(TypeGuard[int], int)) +static_assert(is_subtype_of(TypeIs[str], bool)) +static_assert(is_subtype_of(TypeIs[str], int)) +``` + +`TypeIs` is invariant. `TypeGuard` is covariant. + +```py +from ty_extensions import is_equivalent_to, is_subtype_of, static_assert +from typing_extensions import TypeGuard, TypeIs + +# TODO: TypeGuard +# static_assert(is_subtype_of(TypeGuard[int], TypeGuard[int])) +# static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int])) +static_assert(is_subtype_of(TypeIs[int], TypeIs[int])) +static_assert(is_subtype_of(TypeIs[int], TypeIs[int])) + +static_assert(not is_subtype_of(TypeGuard[int], TypeGuard[bool])) +static_assert(not is_subtype_of(TypeIs[bool], TypeIs[int])) +static_assert(not is_subtype_of(TypeIs[int], TypeIs[bool])) +``` + ### Module literals ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 94dbe1be273c3a..08deb8327cd123 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -35,8 +35,8 @@ use crate::module_resolver::{KnownModule, resolve_module}; use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::definition::Definition; -use crate::semantic_index::place::ScopeId; -use crate::semantic_index::{imported_modules, semantic_index}; +use crate::semantic_index::place::{ScopeId, ScopedPlaceId}; +use crate::semantic_index::{imported_modules, place_table, semantic_index}; use crate::suppression::check_suppressions; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; @@ -553,6 +553,8 @@ pub enum Type<'db> { // This type doesn't handle an unbound super object like `super(A)`; for that we just use // a `Type::NominalInstance` of `builtins.super`. BoundSuper(BoundSuperType<'db>), + /// A subtype of `bool` that allows narrowing in both positive and negative cases. + TypeIs(TypeIsType<'db>), // TODO protocols, overloads, generics } @@ -726,6 +728,9 @@ impl<'db> Type<'db> { .map(|ty| ty.materialize(db, variance)), ), Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)), + Type::TypeIs(type_is) => { + type_is.with_type(db, type_is.return_type(db).materialize(db, variance)) + } } } @@ -777,6 +782,11 @@ impl<'db> Type<'db> { *self } + Self::TypeIs(type_is) => type_is.with_type( + db, + type_is.return_type(db).replace_self_reference(db, class), + ), + Self::Dynamic(_) | Self::AlwaysFalsy | Self::AlwaysTruthy @@ -910,6 +920,8 @@ impl<'db> Type<'db> { .iter() .any(|ty| ty.any_over_type(db, type_fn)), }, + + Self::TypeIs(type_is) => type_is.return_type(db).any_over_type(db, type_fn), } } @@ -1145,6 +1157,7 @@ impl<'db> Type<'db> { Type::KnownInstance(known_instance) => { Type::KnownInstance(known_instance.normalized(db)) } + Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).normalized(db)), Type::LiteralString | Type::AlwaysFalsy | Type::AlwaysTruthy @@ -1404,6 +1417,11 @@ impl<'db> Type<'db> { false } + // `TypeIs[T]` is a subtype of `bool`. + (Type::TypeIs(_), _) => KnownClass::Bool + .to_instance(db) + .has_relation_to(db, target, relation), + // Function-like callables are subtypes of `FunctionType` (Type::Callable(callable), _) if callable.is_function_like(db) @@ -1949,14 +1967,15 @@ impl<'db> Type<'db> { known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)), ) => known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)), - (Type::BooleanLiteral(..), Type::NominalInstance(instance)) - | (Type::NominalInstance(instance), Type::BooleanLiteral(..)) => { + (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) + | (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => { // A `Type::BooleanLiteral()` must be an instance of exactly `bool` // (it cannot be an instance of a `bool` subclass) !KnownClass::Bool.is_subclass_of(db, instance.class) } - (Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true, + (Type::BooleanLiteral(..) | Type::TypeIs(_), _) + | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => true, (Type::IntLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { @@ -2186,6 +2205,7 @@ impl<'db> Type<'db> { .iter() .all(|elem| elem.is_fully_static(db)), Type::Callable(callable) => callable.is_fully_static(db), + Type::TypeIs(type_is) => type_is.return_type(db).is_fully_static(db), } } @@ -2310,6 +2330,7 @@ impl<'db> Type<'db> { false } Type::AlwaysTruthy | Type::AlwaysFalsy => false, + Type::TypeIs(type_is) => type_is.is_bound(db), } } @@ -2367,6 +2388,8 @@ impl<'db> Type<'db> { false } + Type::TypeIs(type_is) => type_is.is_bound(db), + Type::Dynamic(_) | Type::Never | Type::Union(..) @@ -2495,7 +2518,8 @@ impl<'db> Type<'db> { | Type::TypeVar(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) - | Type::PropertyInstance(_) => None, + | Type::PropertyInstance(_) + | Type::TypeIs(_) => None, } } @@ -2595,7 +2619,9 @@ impl<'db> Type<'db> { }, Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name), - Type::BooleanLiteral(_) => KnownClass::Bool.to_instance(db).instance_member(db, name), + Type::BooleanLiteral(_) | Type::TypeIs(_) => { + KnownClass::Bool.to_instance(db).instance_member(db, name) + } Type::StringLiteral(_) | Type::LiteralString => { KnownClass::Str.to_instance(db).instance_member(db, name) } @@ -3116,7 +3142,8 @@ impl<'db> Type<'db> { | Type::SpecialForm(..) | Type::KnownInstance(..) | Type::PropertyInstance(..) - | Type::FunctionLiteral(..) => { + | Type::FunctionLiteral(..) + | Type::TypeIs(..) => { let fallback = self.instance_member(db, name_str); let result = self.invoke_descriptor_protocol( @@ -3381,9 +3408,11 @@ impl<'db> Type<'db> { }; let truthiness = match self { - Type::Dynamic(_) | Type::Never | Type::Callable(_) | Type::LiteralString => { - Truthiness::Ambiguous - } + Type::Dynamic(_) + | Type::Never + | Type::Callable(_) + | Type::LiteralString + | Type::TypeIs(_) => Truthiness::Ambiguous, Type::FunctionLiteral(_) | Type::BoundMethod(_) @@ -4348,7 +4377,8 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::Tuple(_) | Type::BoundSuper(_) - | Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(), + | Type::ModuleLiteral(_) + | Type::TypeIs(_) => CallableBinding::not_callable(self).into(), } } @@ -4836,7 +4866,8 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::BoundSuper(_) | Type::AlwaysTruthy - | Type::AlwaysFalsy => None, + | Type::AlwaysFalsy + | Type::TypeIs(_) => None, } } @@ -4902,7 +4933,8 @@ impl<'db> Type<'db> { | Type::FunctionLiteral(_) | Type::BoundSuper(_) | Type::ProtocolInstance(_) - | Type::PropertyInstance(_) => Err(InvalidTypeExpressionError { + | Type::PropertyInstance(_) + | Type::TypeIs(_) => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType( *self, scope_id )], @@ -5141,7 +5173,7 @@ impl<'db> Type<'db> { Type::SpecialForm(special_form) => special_form.to_meta_type(db), Type::PropertyInstance(_) => KnownClass::Property.to_class_literal(db), Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)), - Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db), + Type::BooleanLiteral(_) | Type::TypeIs(_) => KnownClass::Bool.to_class_literal(db), Type::BytesLiteral(_) => KnownClass::Bytes.to_class_literal(db), Type::IntLiteral(_) => KnownClass::Int.to_class_literal(db), Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class_literal(db), @@ -5315,6 +5347,8 @@ impl<'db> Type<'db> { .map(|ty| ty.apply_type_mapping(db, type_mapping)), ), + Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)), + Type::ModuleLiteral(_) | Type::IntLiteral(_) | Type::BooleanLiteral(_) @@ -5424,6 +5458,10 @@ impl<'db> Type<'db> { subclass_of.find_legacy_typevars(db, typevars); } + Type::TypeIs(type_is) => { + type_is.return_type(db).find_legacy_typevars(db, typevars); + } + Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy @@ -5553,8 +5591,9 @@ impl<'db> Type<'db> { | Self::Never | Self::Callable(_) | Self::AlwaysTruthy + | Self::AlwaysFalsy | Self::SpecialForm(_) - | Self::AlwaysFalsy => None, + | Self::TypeIs(_) => None, } } @@ -8476,6 +8515,54 @@ impl<'db> BoundSuperType<'db> { } } +#[salsa::interned(debug)] +pub struct TypeIsType<'db> { + return_type: Type<'db>, + /// The ID of the scope to which the place belongs + /// and the ID of the place itself within that scope. + place_info: Option<(ScopeId<'db>, ScopedPlaceId)>, +} + +impl<'db> TypeIsType<'db> { + pub fn place_name(self, db: &'db dyn Db) -> Option { + let (scope, place) = self.place_info(db)?; + let table = place_table(db, scope); + + Some(format!("{}", table.place_expr(place))) + } + + pub fn unbound(db: &'db dyn Db, ty: Type<'db>) -> Type<'db> { + Type::TypeIs(Self::new(db, ty, None)) + } + + pub fn bound( + db: &'db dyn Db, + return_type: Type<'db>, + scope: ScopeId<'db>, + place: ScopedPlaceId, + ) -> Type<'db> { + Type::TypeIs(Self::new(db, return_type, Some((scope, place)))) + } + + #[must_use] + pub fn bind(self, db: &'db dyn Db, scope: ScopeId<'db>, place: ScopedPlaceId) -> Type<'db> { + Self::bound(db, self.return_type(db), scope, place) + } + + #[must_use] + pub fn with_type(self, db: &'db dyn Db, ty: Type<'db>) -> Type<'db> { + Type::TypeIs(Self::new(db, ty, self.place_info(db))) + } + + pub fn is_bound(&self, db: &'db dyn Db) -> bool { + self.place_info(db).is_some() + } + + pub fn is_unbound(&self, db: &'db dyn Db) -> bool { + self.place_info(db).is_none() + } +} + // Make sure that the `Type` enum does not grow unexpectedly. #[cfg(not(debug_assertions))] #[cfg(target_pointer_width = "64")] diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 8e6e27a7efe264..c9c51647c9f58b 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -146,7 +146,8 @@ impl<'db> ClassBase<'db> { | Type::BoundSuper(_) | Type::ProtocolInstance(_) | Type::AlwaysFalsy - | Type::AlwaysTruthy => None, + | Type::AlwaysTruthy + | Type::TypeIs(_) => None, Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic), diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 8bcf8a8bb09a66..c613374c473098 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -54,6 +54,8 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&INVALID_SUPER_ARGUMENT); registry.register_lint(&INVALID_TYPE_CHECKING_CONSTANT); registry.register_lint(&INVALID_TYPE_FORM); + registry.register_lint(&INVALID_TYPE_GUARD_DEFINITION); + registry.register_lint(&INVALID_TYPE_GUARD_CALL); registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS); registry.register_lint(&MISSING_ARGUMENT); registry.register_lint(&NO_MATCHING_OVERLOAD); @@ -893,6 +895,62 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for type guard functions without + /// a first non-self-like non-keyword-only non-variadic parameter. + /// + /// ## Why is this bad? + /// Type narrowing functions must accept at least one positional argument + /// (non-static methods must accept another in addition to `self`/`cls`). + /// + /// Extra parameters/arguments are allowed but do not affect narrowing. + /// + /// ## Examples + /// ```python + /// from typing import TypeIs + /// + /// def f() -> TypeIs[int]: ... # Error, no parameter + /// def f(*, v: object) -> TypeIs[int]: ... # Error, no positional arguments allowed + /// def f(*args: object) -> TypeIs[int]: ... # Error, expect variadic arguments + /// class C: + /// def f(self) -> TypeIs[int]: ... # Error, only positional argument expected is `self` + /// ``` + pub(crate) static INVALID_TYPE_GUARD_DEFINITION = { + summary: "detects malformed type guard functions", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + +declare_lint! { + /// ## What it does + /// Checks for type guard function calls without a valid target. + /// + /// ## Why is this bad? + /// The first non-keyword non-variadic argument to a type guard function + /// is its target and must map to a symbol. + /// + /// Starred (`is_str(*a)`), literal (`is_str(42)`) and other non-symbol-like + /// expressions are invalid as narrowing targets. + /// + /// ## Examples + /// ```python + /// from typing import TypeIs + /// + /// def f(v: object) -> TypeIs[int]: ... + /// + /// f() # Error + /// f(*a) # Error + /// f(10) # Error + /// ``` + pub(crate) static INVALID_TYPE_GUARD_CALL = { + summary: "detects type guard function calls that has no narrowing effect", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for constrained [type variables] with only one constraint. diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 02f38c5701b253..fd2ad5bea6b478 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -211,6 +211,15 @@ impl Display for DisplayRepresentation<'_> { owner = bound_super.owner(self.db).into_type().display(self.db) ) } + Type::TypeIs(type_is) => { + f.write_str("TypeIs[")?; + type_is.return_type(self.db).display(self.db).fmt(f)?; + if let Some(name) = type_is.place_name(self.db) { + f.write_str(" @ ")?; + f.write_str(&name)?; + } + f.write_str("]") + } } } } diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index b79d232c70737b..296c9266d562bb 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -116,7 +116,8 @@ impl AllMembers { | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::TypeVar(_) - | Type::BoundSuper(_) => { + | Type::BoundSuper(_) + | Type::TypeIs(_) => { if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) { self.extend_with_class_members(db, class_literal); } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 7b3b79472f2d22..812e379ff9f0ef 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -68,7 +68,7 @@ use crate::semantic_index::narrowing_constraints::ConstraintKey; use crate::semantic_index::place::{ FileScopeId, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, ScopeId, ScopeKind, ScopedPlaceId, }; -use crate::semantic_index::{EagerSnapshotResult, SemanticIndex, semantic_index}; +use crate::semantic_index::{EagerSnapshotResult, SemanticIndex, place_table, semantic_index}; use crate::types::call::{ Argument, Binding, Bindings, CallArgumentTypes, CallArguments, CallError, }; @@ -78,13 +78,14 @@ use crate::types::diagnostic::{ CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, - INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_VARIABLE_CONSTRAINTS, - POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, - UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, - UNSUPPORTED_OPERATOR, report_implicit_return_type, report_invalid_arguments_to_annotated, - report_invalid_arguments_to_callable, report_invalid_assignment, - report_invalid_attribute_assignment, report_invalid_generator_function_return_type, - report_invalid_return_type, report_possibly_unbound_attribute, + INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, + INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, + TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, + UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, + report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, + report_invalid_assignment, report_invalid_attribute_assignment, + report_invalid_generator_function_return_type, report_invalid_return_type, + report_possibly_unbound_attribute, }; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, @@ -99,8 +100,8 @@ use crate::types::{ KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, - TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, - TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, + TypeArrayDisplay, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, + TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -672,6 +673,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.types.expressions.extend(inference.expressions.iter()); self.types.deferred.extend(inference.deferred.iter()); self.context.extend(inference.diagnostics()); + self.types.cycle_fallback_type = self + .types + .cycle_fallback_type + .or(inference.cycle_fallback_type); } fn file(&self) -> File { @@ -1904,6 +1909,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let declared_ty = self.file_expression_type(returns); + let expected_ty = match declared_ty { + Type::TypeIs(_) => KnownClass::Bool.to_instance(self.db()), + ty => ty, + }; let scope_id = self.index.node_scope(NodeWithScopeRef::Function(function)); if scope_id.is_generator_function(self.index) { @@ -1921,7 +1930,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if !inferred_return .to_instance(self.db()) - .is_assignable_to(self.db(), declared_ty) + .is_assignable_to(self.db(), expected_ty) { report_invalid_generator_function_return_type( &self.context, @@ -1947,7 +1956,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ty if ty.is_notimplemented(self.db()) => None, _ => Some(ty_range), }) - .filter(|ty_range| !ty_range.ty.is_assignable_to(self.db(), declared_ty)) + .filter(|ty_range| !ty_range.ty.is_assignable_to(self.db(), expected_ty)) { report_invalid_return_type( &self.context, @@ -1959,7 +1968,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let use_def = self.index.use_def_map(scope_id); if use_def.can_implicit_return(self.db()) - && !Type::none(self.db()).is_assignable_to(self.db(), declared_ty) + && !Type::none(self.db()).is_assignable_to(self.db(), expected_ty) { let no_return = self.return_types_and_ranges.is_empty(); report_implicit_return_type( @@ -3213,7 +3222,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::DataclassTransformer(_) | Type::TypeVar(..) | Type::AlwaysTruthy - | Type::AlwaysFalsy => { + | Type::AlwaysFalsy + | Type::TypeIs(_) => { let is_read_only = || { let dataclass_params = match object_ty { Type::NominalInstance(instance) => match instance.class { @@ -5800,7 +5810,45 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } } - bindings.return_type(self.db()) + + let db = self.db(); + let scope = self.scope(); + let return_ty = bindings.return_type(db); + + let find_narrowed_place = || match arguments.args.first() { + None => { + // This branch looks extraneous, especially in the face of `missing-arguments`. + // However, that lint won't be able to catch this: + // + // ```python + // def f(v: object = object()) -> TypeIs[int]: ... + // + // if f(): ... + // ``` + // + // TODO: Will this report things that is actually fine? + if let Some(builder) = self + .context + .report_lint(&INVALID_TYPE_GUARD_CALL, arguments) + { + builder.into_diagnostic("Type guard call does not have a target"); + } + None + } + Some(expr) => match PlaceExpr::try_from(expr) { + Ok(place_expr) => place_table(db, scope).place_id_by_expr(&place_expr), + Err(()) => None, + }, + }; + + match return_ty { + // TODO: TypeGuard + Type::TypeIs(type_is) => match find_narrowed_place() { + Some(place) => type_is.bind(db, scope, place), + None => return_ty, + }, + _ => return_ty, + } } Err(CallError(_, bindings)) => { @@ -6428,7 +6476,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::Tuple(_) | Type::BoundSuper(_) - | Type::TypeVar(_), + | Type::TypeVar(_) + | Type::TypeIs(_), ) => { let unary_dunder_method = match op { ast::UnaryOp::Invert => "__invert__", @@ -6759,7 +6808,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::Tuple(_) | Type::BoundSuper(_) - | Type::TypeVar(_), + | Type::TypeVar(_) + | Type::TypeIs(_), Type::FunctionLiteral(_) | Type::Callable(..) | Type::BoundMethod(_) @@ -6785,7 +6835,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::Tuple(_) | Type::BoundSuper(_) - | Type::TypeVar(_), + | Type::TypeVar(_) + | Type::TypeIs(_), op, ) => { // We either want to call lhs.__op__ or rhs.__rop__. The full decision tree from @@ -9552,10 +9603,22 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_type_expression(arguments_slice); todo_type!("`Required[]` type qualifier") } - SpecialFormType::TypeIs => { - self.infer_type_expression(arguments_slice); - todo_type!("`TypeIs[]` special form") - } + SpecialFormType::TypeIs => match arguments_slice { + ast::Expr::Tuple(_) => { + self.infer_type_expression(arguments_slice); + + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + let diag = builder.into_diagnostic(format_args!( + "Special form `{}` expected exactly one type parameter", + special_form.repr() + )); + diagnostic::add_type_expression_reference_link(diag); + } + + Type::unknown() + } + _ => TypeIsType::unbound(self.db(), self.infer_type_expression(arguments_slice)), + }, SpecialFormType::TypeGuard => { self.infer_type_expression(arguments_slice); todo_type!("`TypeGuard[]` special form") diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 9af855a100a1a7..9fa59dfcac1915 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -388,7 +388,6 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { let ast::ExprName { id, .. } = expr_name; let symbol = self.expect_expr_name_symbol(id); - let ty = if is_positive { Type::AlwaysFalsy.negate(self.db) } else { @@ -728,6 +727,29 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { // TODO: add support for PEP 604 union types on the right hand side of `isinstance` // and `issubclass`, for example `isinstance(x, str | (int | float))`. match callable_ty { + Type::FunctionLiteral(function_type) + if matches!( + function_type.known(self.db), + None | Some(KnownFunction::RevealType) + ) => + { + let return_ty = + inference.expression_type(expr_call.scoped_expression_id(self.db, scope)); + + let (guarded_ty, place) = match return_ty { + // TODO: TypeGuard + Type::TypeIs(type_is) => { + let (_, place) = type_is.place_info(self.db)?; + (type_is.return_type(self.db), place) + } + _ => return None, + }; + + Some(NarrowingConstraints::from_iter([( + place, + guarded_ty.negate_if(self.db, !is_positive), + )])) + } Type::FunctionLiteral(function_type) if expr_call.arguments.keywords.is_empty() => { let [first_arg, second_arg] = &*expr_call.arguments.args else { return None; diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 669d116fd9fc89..d4b1a5bcf4ece7 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::db::Db; use super::{ - DynamicType, SuperOwnerKind, TodoType, Type, class_base::ClassBase, + DynamicType, SuperOwnerKind, TodoType, Type, TypeIsType, class_base::ClassBase, subclass_of::SubclassOfInner, }; @@ -126,6 +126,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::SubclassOf(_), _) => Ordering::Less, (_, Type::SubclassOf(_)) => Ordering::Greater, + (Type::TypeIs(left), Type::TypeIs(right)) => typeis_ordering(db, *left, *right), + (Type::TypeIs(_), _) => Ordering::Less, + (_, Type::TypeIs(_)) => Ordering::Greater, + (Type::NominalInstance(left), Type::NominalInstance(right)) => left.class.cmp(&right.class), (Type::NominalInstance(_), _) => Ordering::Less, (_, Type::NominalInstance(_)) => Ordering::Greater, @@ -248,3 +252,25 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering (_, DynamicType::TodoPEP695ParamSpec) => Ordering::Greater, } } + +/// Determine a canonical order for two instances of [`TypeIsType`]. +/// +/// The following criteria are considered, in order: +/// * Boundness: Unbound precedes bound +/// * Symbol name: String comparison +/// * Guarded type: [`union_or_intersection_elements_ordering`] +fn typeis_ordering(db: &dyn Db, left: TypeIsType, right: TypeIsType) -> Ordering { + let (left_ty, right_ty) = (left.return_type(db), right.return_type(db)); + + match (left.place_info(db), right.place_info(db)) { + (None, Some(_)) => Ordering::Less, + (Some(_), None) => Ordering::Greater, + + (None, None) => union_or_intersection_elements_ordering(db, &left_ty, &right_ty), + + (Some(_), Some(_)) => match left.place_name(db).cmp(&right.place_name(db)) { + Ordering::Equal => union_or_intersection_elements_ordering(db, &left_ty, &right_ty), + ordering => ordering, + }, + } +} diff --git a/ty.schema.json b/ty.schema.json index d3180fb4db0e33..cfef1c1aef29a6 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -563,6 +563,26 @@ } ] }, + "invalid-type-guard-call": { + "title": "detects type guard function calls that has no narrowing effect", + "description": "## What it does\nChecks for type guard function calls without a valid target.\n\n## Why is this bad?\nThe first non-keyword non-variadic argument to a type guard function\nis its target and must map to a symbol.\n\nStarred (`is_str(*a)`), literal (`is_str(42)`) and other non-symbol-like\nexpressions are invalid as narrowing targets.\n\n## Examples\n```python\nfrom typing import TypeIs\n\ndef f(v: object) -> TypeIs[int]: ...\n\nf() # Error\nf(*a) # Error\nf(10) # Error\n```", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, + "invalid-type-guard-definition": { + "title": "detects malformed type guard functions", + "description": "## What it does\nChecks for type guard functions without\na first non-self-like non-keyword-only non-variadic parameter.\n\n## Why is this bad?\nType narrowing functions must accept at least one positional argument\n(non-static methods must accept another in addition to `self`/`cls`).\n\nExtra parameters/arguments are allowed but do not affect narrowing.\n\n## Examples\n```python\nfrom typing import TypeIs\n\ndef f() -> TypeIs[int]: ... # Error, no parameter\ndef f(*, v: object) -> TypeIs[int]: ... # Error, no positional arguments allowed\ndef f(*args: object) -> TypeIs[int]: ... # Error, expect variadic arguments\nclass C:\n def f(self) -> TypeIs[int]: ... # Error, only positional argument expected is `self`\n```", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "invalid-type-variable-constraints": { "title": "detects invalid type variable constraints", "description": "## What it does\nChecks for constrained [type variables] with only one constraint.\n\n## Why is this bad?\nA constrained type variable must have at least two constraints.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar('T', str) # invalid constrained TypeVar\n```\n\nUse instead:\n```python\nT = TypeVar('T', str, int) # valid constrained TypeVar\n# or\nT = TypeVar('T', bound=str) # valid bound TypeVar\n```\n\n[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar", From e4423044f8abc139b33f8f56061d03692ca2cc5f Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:07:02 -0400 Subject: [PATCH 415/487] [`ruff`] Validate arguments before offering a fix (`RUF056`) (#18631) ## Summary Fixes https://github.com/astral-sh/ruff/issues/18628 by avoiding a fix if there are "unknown" arguments, including any keyword arguments and more than the expected 2 positional arguments. I'm a bit on the fence here because it also seems reasonable to avoid a diagnostic at all. Especially in the final test case I added (`not my_dict.get(default=False)`), the hint suggesting to remove `default=False` seems pretty misleading. At the same time, I guess the diagnostic at least calls attention to the call site, which could help to fix the missing argument bug too. As I commented on the issue, I double-checked that keyword arguments are invalid as far back as Python 3.8, even though the positional-only marker was only added to the [docs](https://docs.python.org/3.11/library/stdtypes.html#dict.get) in 3.12 (link is to 3.11, showing its absence). ## Test Plan New tests derived from the bug report ## Stabilization This was planned to be stabilized in 0.12, and the bug is less severe than some others, but if there's nobody opposed, I will plan **not to stabilize** this one for now. --- .../resources/test/fixtures/ruff/RUF056.py | 46 ++-- .../ruff/rules/falsy_dict_get_fallback.rs | 33 ++- ..._rules__ruff__tests__RUF056_RUF056.py.snap | 217 +++++++++--------- 3 files changed, 162 insertions(+), 134 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF056.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF056.py index 831ec4bcead153..0755092b8bfdb0 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF056.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF056.py @@ -149,23 +149,39 @@ def inner(): value = not my_dict.get("key", 0.0) # [RUF056] value = not my_dict.get("key", "") # [RUF056] -# testing dict.get call using kwargs -value = not my_dict.get(key="key", default=False) # [RUF056] -value = not my_dict.get(default=[], key="key") # [RUF056] - # testing invalid dict.get call with inline comment value = not my_dict.get("key", # comment1 [] # comment2 ) # [RUF056] -# testing invalid dict.get call with kwargs and inline comment -value = not my_dict.get(key="key", # comment1 - default=False # comment2 - ) # [RUF056] -value = not my_dict.get(default=[], # comment1 - key="key" # comment2 - ) # [RUF056] - -# testing invalid dict.get calls -value = not my_dict.get(key="key", other="something", default=False) -value = not my_dict.get(default=False, other="something", key="test") \ No newline at end of file +# regression tests for https://github.com/astral-sh/ruff/issues/18628 +# we should avoid fixes when there are "unknown" arguments present, including +# extra positional arguments, either of the positional-only arguments passed as +# a keyword, or completely unknown keywords. + +# extra positional +not my_dict.get("key", False, "?!") + +# `default` is positional-only, so these are invalid +not my_dict.get("key", default=False) +not my_dict.get(key="key", default=False) +not my_dict.get(default=[], key="key") +not my_dict.get(default=False) +not my_dict.get(key="key", other="something", default=False) +not my_dict.get(default=False, other="something", key="test") + +# comments don't really matter here because of the kwargs but include them for +# completeness +not my_dict.get( + key="key", # comment1 + default=False, # comment2 +) # comment 3 +not my_dict.get( + default=[], # comment1 + key="key", # comment2 +) # comment 3 + +# the fix is arguably okay here because the same `takes no keyword arguments` +# TypeError is raised at runtime before and after the fix, but we still bail +# out for having an unrecognized number of arguments +not my_dict.get("key", False, foo=...) diff --git a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs index 64b0bcdc71768c..c7064b6a2c2ba2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/falsy_dict_get_fallback.rs @@ -5,7 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{Parentheses, remove_argument}; -use crate::{AlwaysFixableViolation, Applicability, Fix}; +use crate::{Applicability, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `dict.get(key, falsy_value)` calls in boolean test positions. @@ -28,21 +28,34 @@ use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ``` /// /// ## Fix safety -/// This rule's fix is marked as safe, unless the `dict.get()` call contains comments between arguments. +/// +/// This rule's fix is marked as safe, unless the `dict.get()` call contains comments between +/// arguments that will be deleted. +/// +/// ## Fix availability +/// +/// This rule's fix is unavailable in cases where invalid arguments are provided to `dict.get`. As +/// shown in the [documentation], `dict.get` takes two positional-only arguments, so invalid cases +/// are identified by the presence of more than two arguments or any keyword arguments. +/// +/// [documentation]: https://docs.python.org/3.13/library/stdtypes.html#dict.get #[derive(ViolationMetadata)] pub(crate) struct FalsyDictGetFallback; -impl AlwaysFixableViolation for FalsyDictGetFallback { +impl Violation for FalsyDictGetFallback { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { "Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.".to_string() } - fn fix_title(&self) -> String { - "Remove falsy fallback from `dict.get()`".to_string() + fn fix_title(&self) -> Option { + Some("Remove falsy fallback from `dict.get()`".to_string()) } } +/// RUF056 pub(crate) fn falsy_dict_get_fallback(checker: &Checker, expr: &Expr) { let semantic = checker.semantic(); @@ -89,6 +102,16 @@ pub(crate) fn falsy_dict_get_fallback(checker: &Checker, expr: &Expr) { let mut diagnostic = checker.report_diagnostic(FalsyDictGetFallback, fallback_arg.range()); + // All arguments to `dict.get` are positional-only. + if !call.arguments.keywords.is_empty() { + return; + } + + // And there are only two of them, at most. + if call.arguments.args.len() > 2 { + return; + } + let comment_ranges = checker.comment_ranges(); // Determine applicability based on the presence of comments diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF056_RUF056.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF056_RUF056.py.snap index c269084fef655e..044f2db0fe9b88 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF056_RUF056.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF056_RUF056.py.snap @@ -323,7 +323,7 @@ RUF056.py:149:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in 149 |+value = not my_dict.get("key") # [RUF056] 150 150 | value = not my_dict.get("key", "") # [RUF056] 151 151 | -152 152 | # testing dict.get call using kwargs +152 152 | # testing invalid dict.get call with inline comment RUF056.py:150:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. | @@ -332,7 +332,7 @@ RUF056.py:150:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in 150 | value = not my_dict.get("key", "") # [RUF056] | ^^ RUF056 151 | -152 | # testing dict.get call using kwargs +152 | # testing invalid dict.get call with inline comment | = help: Remove falsy fallback from `dict.get()` @@ -343,142 +343,131 @@ RUF056.py:150:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in 150 |-value = not my_dict.get("key", "") # [RUF056] 150 |+value = not my_dict.get("key") # [RUF056] 151 151 | -152 152 | # testing dict.get call using kwargs -153 153 | value = not my_dict.get(key="key", default=False) # [RUF056] +152 152 | # testing invalid dict.get call with inline comment +153 153 | value = not my_dict.get("key", # comment1 -RUF056.py:153:36: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. +RUF056.py:154:22: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. | -152 | # testing dict.get call using kwargs -153 | value = not my_dict.get(key="key", default=False) # [RUF056] - | ^^^^^^^^^^^^^ RUF056 -154 | value = not my_dict.get(default=[], key="key") # [RUF056] +152 | # testing invalid dict.get call with inline comment +153 | value = not my_dict.get("key", # comment1 +154 | [] # comment2 + | ^^ RUF056 +155 | ) # [RUF056] | = help: Remove falsy fallback from `dict.get()` -ℹ Safe fix +ℹ Unsafe fix 150 150 | value = not my_dict.get("key", "") # [RUF056] 151 151 | -152 152 | # testing dict.get call using kwargs -153 |-value = not my_dict.get(key="key", default=False) # [RUF056] - 153 |+value = not my_dict.get(key="key") # [RUF056] -154 154 | value = not my_dict.get(default=[], key="key") # [RUF056] -155 155 | -156 156 | # testing invalid dict.get call with inline comment - -RUF056.py:154:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. - | -152 | # testing dict.get call using kwargs -153 | value = not my_dict.get(key="key", default=False) # [RUF056] -154 | value = not my_dict.get(default=[], key="key") # [RUF056] - | ^^^^^^^^^^ RUF056 -155 | -156 | # testing invalid dict.get call with inline comment +152 152 | # testing invalid dict.get call with inline comment +153 |-value = not my_dict.get("key", # comment1 +154 |- [] # comment2 + 153 |+value = not my_dict.get("key" # comment2 +155 154 | ) # [RUF056] +156 155 | +157 156 | # regression tests for https://github.com/astral-sh/ruff/issues/18628 + +RUF056.py:163:24: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +162 | # extra positional +163 | not my_dict.get("key", False, "?!") + | ^^^^^ RUF056 +164 | +165 | # `default` is positional-only, so these are invalid | = help: Remove falsy fallback from `dict.get()` -ℹ Safe fix -151 151 | -152 152 | # testing dict.get call using kwargs -153 153 | value = not my_dict.get(key="key", default=False) # [RUF056] -154 |-value = not my_dict.get(default=[], key="key") # [RUF056] - 154 |+value = not my_dict.get(key="key") # [RUF056] -155 155 | -156 156 | # testing invalid dict.get call with inline comment -157 157 | value = not my_dict.get("key", # comment1 - -RUF056.py:158:22: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. - | -156 | # testing invalid dict.get call with inline comment -157 | value = not my_dict.get("key", # comment1 -158 | [] # comment2 - | ^^ RUF056 -159 | ) # [RUF056] +RUF056.py:166:24: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +165 | # `default` is positional-only, so these are invalid +166 | not my_dict.get("key", default=False) + | ^^^^^^^^^^^^^ RUF056 +167 | not my_dict.get(key="key", default=False) +168 | not my_dict.get(default=[], key="key") | = help: Remove falsy fallback from `dict.get()` -ℹ Unsafe fix -154 154 | value = not my_dict.get(default=[], key="key") # [RUF056] -155 155 | -156 156 | # testing invalid dict.get call with inline comment -157 |-value = not my_dict.get("key", # comment1 -158 |- [] # comment2 - 157 |+value = not my_dict.get("key" # comment2 -159 158 | ) # [RUF056] -160 159 | -161 160 | # testing invalid dict.get call with kwargs and inline comment - -RUF056.py:163:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. - | -161 | # testing invalid dict.get call with kwargs and inline comment -162 | value = not my_dict.get(key="key", # comment1 -163 | default=False # comment2 - | ^^^^^^^^^^^^^ RUF056 -164 | ) # [RUF056] -165 | value = not my_dict.get(default=[], # comment1 +RUF056.py:167:28: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +165 | # `default` is positional-only, so these are invalid +166 | not my_dict.get("key", default=False) +167 | not my_dict.get(key="key", default=False) + | ^^^^^^^^^^^^^ RUF056 +168 | not my_dict.get(default=[], key="key") +169 | not my_dict.get(default=False) | = help: Remove falsy fallback from `dict.get()` -ℹ Unsafe fix -159 159 | ) # [RUF056] -160 160 | -161 161 | # testing invalid dict.get call with kwargs and inline comment -162 |-value = not my_dict.get(key="key", # comment1 -163 |- default=False # comment2 - 162 |+value = not my_dict.get(key="key" # comment2 -164 163 | ) # [RUF056] -165 164 | value = not my_dict.get(default=[], # comment1 -166 165 | key="key" # comment2 - -RUF056.py:165:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. - | -163 | default=False # comment2 -164 | ) # [RUF056] -165 | value = not my_dict.get(default=[], # comment1 - | ^^^^^^^^^^ RUF056 -166 | key="key" # comment2 -167 | ) # [RUF056] +RUF056.py:168:17: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +166 | not my_dict.get("key", default=False) +167 | not my_dict.get(key="key", default=False) +168 | not my_dict.get(default=[], key="key") + | ^^^^^^^^^^ RUF056 +169 | not my_dict.get(default=False) +170 | not my_dict.get(key="key", other="something", default=False) | = help: Remove falsy fallback from `dict.get()` -ℹ Unsafe fix -162 162 | value = not my_dict.get(key="key", # comment1 -163 163 | default=False # comment2 -164 164 | ) # [RUF056] -165 |-value = not my_dict.get(default=[], # comment1 - 165 |+value = not my_dict.get(# comment1 -166 166 | key="key" # comment2 -167 167 | ) # [RUF056] -168 168 | - -RUF056.py:170:55: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. - | -169 | # testing invalid dict.get calls -170 | value = not my_dict.get(key="key", other="something", default=False) - | ^^^^^^^^^^^^^ RUF056 -171 | value = not my_dict.get(default=False, other="something", key="test") +RUF056.py:169:17: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +167 | not my_dict.get(key="key", default=False) +168 | not my_dict.get(default=[], key="key") +169 | not my_dict.get(default=False) + | ^^^^^^^^^^^^^ RUF056 +170 | not my_dict.get(key="key", other="something", default=False) +171 | not my_dict.get(default=False, other="something", key="test") | = help: Remove falsy fallback from `dict.get()` -ℹ Safe fix -167 167 | ) # [RUF056] -168 168 | -169 169 | # testing invalid dict.get calls -170 |-value = not my_dict.get(key="key", other="something", default=False) - 170 |+value = not my_dict.get(key="key", other="something") -171 171 | value = not my_dict.get(default=False, other="something", key="test") +RUF056.py:170:47: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +168 | not my_dict.get(default=[], key="key") +169 | not my_dict.get(default=False) +170 | not my_dict.get(key="key", other="something", default=False) + | ^^^^^^^^^^^^^ RUF056 +171 | not my_dict.get(default=False, other="something", key="test") + | + = help: Remove falsy fallback from `dict.get()` -RUF056.py:171:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. +RUF056.py:171:17: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. | -169 | # testing invalid dict.get calls -170 | value = not my_dict.get(key="key", other="something", default=False) -171 | value = not my_dict.get(default=False, other="something", key="test") - | ^^^^^^^^^^^^^ RUF056 +169 | not my_dict.get(default=False) +170 | not my_dict.get(key="key", other="something", default=False) +171 | not my_dict.get(default=False, other="something", key="test") + | ^^^^^^^^^^^^^ RUF056 +172 | +173 | # comments don't really matter here because of the kwargs but include them for | = help: Remove falsy fallback from `dict.get()` -ℹ Safe fix -168 168 | -169 169 | # testing invalid dict.get calls -170 170 | value = not my_dict.get(key="key", other="something", default=False) -171 |-value = not my_dict.get(default=False, other="something", key="test") - 171 |+value = not my_dict.get(other="something", key="test") +RUF056.py:177:5: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +175 | not my_dict.get( +176 | key="key", # comment1 +177 | default=False, # comment2 + | ^^^^^^^^^^^^^ RUF056 +178 | ) # comment 3 +179 | not my_dict.get( + | + = help: Remove falsy fallback from `dict.get()` + +RUF056.py:180:5: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +178 | ) # comment 3 +179 | not my_dict.get( +180 | default=[], # comment1 + | ^^^^^^^^^^ RUF056 +181 | key="key", # comment2 +182 | ) # comment 3 + | + = help: Remove falsy fallback from `dict.get()` + +RUF056.py:187:24: RUF056 Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy. + | +185 | # TypeError is raised at runtime before and after the fix, but we still bail +186 | # out for having an unrecognized number of arguments +187 | not my_dict.get("key", False, foo=...) + | ^^^^^ RUF056 + | + = help: Remove falsy fallback from `dict.get()` From 5e02d839d5778a0faae468b196d7b320efdbddbe Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sat, 14 Jun 2025 01:02:53 -0400 Subject: [PATCH 416/487] [ty] Avoid accessing class literal with incorrect AST (#18670) --- crates/ty_python_semantic/src/types/class.rs | 9 +++++---- crates/ty_python_semantic/src/types/diagnostic.rs | 12 +++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 165a0a3b85eb6f..5869411fedabcd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2021,8 +2021,8 @@ impl<'db> ClassLiteral<'db> { /// Returns a [`Span`] with the range of the class's header. /// /// See [`Self::header_range`] for more details. - pub(super) fn header_span(self, db: &'db dyn Db, module: &ParsedModuleRef) -> Span { - Span::from(self.file(db)).with_range(self.header_range(db, module)) + pub(super) fn header_span(self, db: &'db dyn Db) -> Span { + Span::from(self.file(db)).with_range(self.header_range(db)) } /// Returns the range of the class's "header": the class name @@ -2032,9 +2032,10 @@ impl<'db> ClassLiteral<'db> { /// class Foo(Bar, metaclass=Baz): ... /// ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` - pub(super) fn header_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> TextRange { + pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange { let class_scope = self.body_scope(db); - let class_node = class_scope.node(db).expect_class(module); + let module = parsed_module(db.upcast(), class_scope.file(db)).load(db.upcast()); + let class_node = class_scope.node(db).expect_class(&module); let class_name = &class_node.name; TextRange::new( class_name.start(), diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index c613374c473098..f0ecd96e466305 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1788,7 +1788,7 @@ pub(super) fn report_implicit_return_type( or `typing_extensions.Protocol` are considered protocol classes", ); sub_diagnostic.annotate( - Annotation::primary(class.header_span(db, context.module())).message(format_args!( + Annotation::primary(class.header_span(db)).message(format_args!( "`Protocol` not present in `{class}`'s immediate bases", class = class.name(db) )), @@ -1908,7 +1908,7 @@ pub(crate) fn report_bad_argument_to_get_protocol_members( class.name(db) ), ); - class_def_diagnostic.annotate(Annotation::primary(class.header_span(db, context.module()))); + class_def_diagnostic.annotate(Annotation::primary(class.header_span(db))); diagnostic.sub(class_def_diagnostic); diagnostic.info( @@ -1971,7 +1971,7 @@ pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol( ), ); class_def_diagnostic.annotate( - Annotation::primary(protocol.header_span(db, context.module())) + Annotation::primary(protocol.header_span(db)) .message(format_args!("`{class_name}` declared here")), ); diagnostic.sub(class_def_diagnostic); @@ -2002,7 +2002,7 @@ pub(crate) fn report_attempted_protocol_instantiation( format_args!("Protocol classes cannot be instantiated"), ); class_def_diagnostic.annotate( - Annotation::primary(protocol.header_span(db, context.module())) + Annotation::primary(protocol.header_span(db)) .message(format_args!("`{class_name}` declared as a protocol here")), ); diagnostic.sub(class_def_diagnostic); @@ -2016,9 +2016,7 @@ pub(crate) fn report_duplicate_bases( ) { let db = context.db(); - let Some(builder) = - context.report_lint(&DUPLICATE_BASE, class.header_range(db, context.module())) - else { + let Some(builder) = context.report_lint(&DUPLICATE_BASE, class.header_range(db)) else { return; }; From 8237d4670c9fd6df4e1dcf827a0d956bb39bd6a1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sun, 15 Jun 2025 07:53:06 +0200 Subject: [PATCH 417/487] Fix `\r` and `\r\n` handling in t- and f-string debug texts (#18673) --- .gitattributes | 3 +++ .../test/fixtures/ruff/.editorconfig | 6 ++++- .../ruff/f-string-carriage-return-newline.py | 8 ++++++ .../src/other/interpolated_string_element.rs | 21 ++++++++++++--- .../ruff_python_formatter/tests/normalizer.rs | 18 +++++++++++++ ...t@f-string-carriage-return-newline.py.snap | 27 +++++++++++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap diff --git a/.gitattributes b/.gitattributes index a12cb611c79346..7bc18040742d71 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,9 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf +crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py text eol=crlf +crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap text eol=crlf + crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/.editorconfig b/crates/ruff_python_formatter/resources/test/fixtures/ruff/.editorconfig index 762b7f0d533d3a..fd6eec1af8d060 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/.editorconfig +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/.editorconfig @@ -8,4 +8,8 @@ ij_formatter_enabled = false [docstring_tab_indentation.py] generated_code = true -ij_formatter_enabled = false \ No newline at end of file +ij_formatter_enabled = false + +[f-string-carriage-return-newline.py] +generated_code = true +ij_formatter_enabled = false diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py new file mode 100644 index 00000000000000..1ddc0eee67393e --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py @@ -0,0 +1,8 @@ +# Regression test for https://github.com/astral-sh/ruff/issues/18667 +f"{ +1= +}" + +t"{ +1= +}" diff --git a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs index e0b53331eac599..19e243f86a53c5 100644 --- a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ruff_formatter::{Buffer, RemoveSoftLinesBuffer, format_args, write}; +use ruff_formatter::{Buffer, FormatOptions as _, RemoveSoftLinesBuffer, format_args, write}; use ruff_python_ast::{ AnyStringFlags, ConversionFlag, Expr, InterpolatedElement, InterpolatedStringElement, InterpolatedStringLiteralElement, StringFlags, @@ -178,9 +178,9 @@ impl Format> for FormatInterpolatedElement<'_> { write!( f, [ - text(&debug_text.leading), + NormalizedDebugText(&debug_text.leading), verbatim_text(&**expression), - text(&debug_text.trailing), + NormalizedDebugText(&debug_text.trailing), ] )?; @@ -316,3 +316,18 @@ fn needs_bracket_spacing(expr: &Expr, context: &PyFormatContext) -> bool { Expr::Dict(_) | Expr::DictComp(_) | Expr::Set(_) | Expr::SetComp(_) ) } + +struct NormalizedDebugText<'a>(&'a str); + +impl Format> for NormalizedDebugText<'_> { + fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { + let normalized = normalize_newlines(self.0, ['\r']); + + f.write_element(FormatElement::Text { + text_width: TextWidth::from_text(&normalized, f.options().indent_width()), + text: normalized.into_owned().into_boxed_str(), + }); + + Ok(()) + } +} diff --git a/crates/ruff_python_formatter/tests/normalizer.rs b/crates/ruff_python_formatter/tests/normalizer.rs index b89ec5130b1ca0..e5237f8c5f2853 100644 --- a/crates/ruff_python_formatter/tests/normalizer.rs +++ b/crates/ruff_python_formatter/tests/normalizer.rs @@ -196,6 +196,24 @@ impl Transformer for Normalizer { transformer::walk_expr(self, expr); } + fn visit_interpolated_string_element( + &self, + interpolated_string_element: &mut InterpolatedStringElement, + ) { + let InterpolatedStringElement::Interpolation(interpolation) = interpolated_string_element + else { + return; + }; + + let Some(debug) = &mut interpolation.debug_text else { + return; + }; + + // Changing the newlines to the configured newline is okay because Python normalizes all newlines to `\n` + debug.leading = debug.leading.replace("\r\n", "\n").replace('\r', "\n"); + debug.trailing = debug.trailing.replace("\r\n", "\n").replace('\r', "\n"); + } + fn visit_string_literal(&self, string_literal: &mut ast::StringLiteral) { static STRIP_DOC_TESTS: LazyLock = LazyLock::new(|| { Regex::new( diff --git a/crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap new file mode 100644 index 00000000000000..c24f246a5e6876 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@f-string-carriage-return-newline.py.snap @@ -0,0 +1,27 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/f-string-carriage-return-newline.py +--- +## Input +```python +# Regression test for https://github.com/astral-sh/ruff/issues/18667 +f"{ +1= +}" + +t"{ +1= +}" +``` + +## Output +```python +# Regression test for https://github.com/astral-sh/ruff/issues/18667 +f"{ +1= +}" + +t"{ +1= +}" +``` From 782363b73644426672ecd85907be626e45309431 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 10:20:33 +0100 Subject: [PATCH 418/487] Sync vendored typeshed stubs (#18679) --- .../vendor/typeshed/source_commit.txt | 2 +- .../vendor/typeshed/stdlib/VERSIONS | 1 + .../vendor/typeshed/stdlib/_hashlib.pyi | 70 +++++++++-- .../vendor/typeshed/stdlib/_socket.pyi | 3 +- .../vendor/typeshed/stdlib/_zstd.pyi | 96 ++++++++++++++ .../vendor/typeshed/stdlib/asyncio/tasks.pyi | 26 ++-- .../vendor/typeshed/stdlib/bz2.pyi | 11 +- .../stdlib/compression/_common/_streams.pyi | 3 +- .../stdlib/compression/zstd/__init__.pyi | 87 +++++++++++++ .../stdlib/compression/zstd/_zstdfile.pyi | 117 ++++++++++++++++++ .../stdlib/email/_header_value_parser.pyi | 7 +- .../vendor/typeshed/stdlib/fractions.pyi | 35 ++++-- .../vendor/typeshed/stdlib/genericpath.pyi | 7 +- .../vendor/typeshed/stdlib/gzip.pyi | 13 +- .../vendor/typeshed/stdlib/lzma.pyi | 9 +- .../vendor/typeshed/stdlib/ntpath.pyi | 17 ++- .../vendor/typeshed/stdlib/posixpath.pyi | 20 ++- .../vendor/typeshed/stdlib/socket.pyi | 4 + .../vendor/typeshed/stdlib/tarfile.pyi | 17 ++- .../typeshed/stdlib/tkinter/__init__.pyi | 4 +- .../typeshed/stdlib/typing_extensions.pyi | 2 +- .../typeshed/stdlib/zoneinfo/__init__.pyi | 10 +- 22 files changed, 483 insertions(+), 78 deletions(-) create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/_zstd.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/__init__.pyi create mode 100644 crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/_zstdfile.pyi diff --git a/crates/ty_vendored/vendor/typeshed/source_commit.txt b/crates/ty_vendored/vendor/typeshed/source_commit.txt index 94bba17b2c184c..0b3c9f584962e3 100644 --- a/crates/ty_vendored/vendor/typeshed/source_commit.txt +++ b/crates/ty_vendored/vendor/typeshed/source_commit.txt @@ -1 +1 @@ -5a3c495d2f6fa9b68cd99f39feba4426e4d17ea9 +ecd5141cc036366cc9e3ca371096d6a14b0ccd13 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS b/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS index 1ecd8af6455953..c86bbb31466700 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS +++ b/crates/ty_vendored/vendor/typeshed/stdlib/VERSIONS @@ -76,6 +76,7 @@ _warnings: 3.0- _weakref: 3.0- _weakrefset: 3.0- _winapi: 3.3- +_zstd: 3.14- abc: 3.0- aifc: 3.0-3.12 annotationlib: 3.14- diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_hashlib.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_hashlib.pyi index 746b1657e2dbf6..8b7ef52cdffdf2 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_hashlib.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_hashlib.pyi @@ -60,19 +60,63 @@ def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... def get_fips_mode() -> int: ... def hmac_new(key: bytes | bytearray, msg: ReadableBuffer = b"", digestmod: _DigestMod = None) -> HMAC: ... -def new(name: str, string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_md5(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha1(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha3_224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha3_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha3_384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_sha3_512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... -def openssl_shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... -def openssl_shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... + +if sys.version_info >= (3, 13): + def new( + name: str, data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_md5( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha1( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha224( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha256( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha384( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha512( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha3_224( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha3_256( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha3_384( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_sha3_512( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASH: ... + def openssl_shake_128( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASHXOF: ... + def openssl_shake_256( + data: ReadableBuffer = b"", *, usedforsecurity: bool = True, string: ReadableBuffer | None = None + ) -> HASHXOF: ... + +else: + def new(name: str, string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_md5(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha1(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... + def openssl_shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... + def hmac_digest(key: bytes | bytearray, msg: ReadableBuffer, digest: str) -> bytes: ... def pbkdf2_hmac( hash_name: str, password: ReadableBuffer, salt: ReadableBuffer, iterations: int, dklen: int | None = None diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi index 06a8a2ba5fa060..41fdce87ec14d4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_socket.pyi @@ -229,6 +229,8 @@ if sys.platform != "win32": IP_RECVOPTS: int IP_RECVRETOPTS: int IP_RETOPTS: int +if sys.version_info >= (3, 13) and sys.platform == "linux": + CAN_RAW_ERR_FILTER: int if sys.version_info >= (3, 14): IP_RECVTTL: int @@ -246,7 +248,6 @@ if sys.version_info >= (3, 14): TCP_QUICKACK: int if sys.platform == "linux": - CAN_RAW_ERR_FILTER: int IP_FREEBIND: int IP_RECVORIGDSTADDR: int VMADDR_CID_LOCAL: int diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_zstd.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_zstd.pyi new file mode 100644 index 00000000000000..0648d898448b66 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/_zstd.pyi @@ -0,0 +1,96 @@ +from _typeshed import ReadableBuffer +from collections.abc import Mapping +from compression.zstd import CompressionParameter, DecompressionParameter +from typing import Final, Literal, final +from typing_extensions import Self, TypeAlias + +ZSTD_CLEVEL_DEFAULT: Final = 3 +ZSTD_DStreamOutSize: Final = 131072 +ZSTD_btlazy2: Final = 6 +ZSTD_btopt: Final = 7 +ZSTD_btultra: Final = 8 +ZSTD_btultra2: Final = 9 +ZSTD_c_chainLog: Final = 103 +ZSTD_c_checksumFlag: Final = 201 +ZSTD_c_compressionLevel: Final = 100 +ZSTD_c_contentSizeFlag: Final = 200 +ZSTD_c_dictIDFlag: Final = 202 +ZSTD_c_enableLongDistanceMatching: Final = 160 +ZSTD_c_hashLog: Final = 102 +ZSTD_c_jobSize: Final = 401 +ZSTD_c_ldmBucketSizeLog: Final = 163 +ZSTD_c_ldmHashLog: Final = 161 +ZSTD_c_ldmHashRateLog: Final = 164 +ZSTD_c_ldmMinMatch: Final = 162 +ZSTD_c_minMatch: Final = 105 +ZSTD_c_nbWorkers: Final = 400 +ZSTD_c_overlapLog: Final = 402 +ZSTD_c_searchLog: Final = 104 +ZSTD_c_strategy: Final = 107 +ZSTD_c_targetLength: Final = 106 +ZSTD_c_windowLog: Final = 101 +ZSTD_d_windowLogMax: Final = 100 +ZSTD_dfast: Final = 2 +ZSTD_fast: Final = 1 +ZSTD_greedy: Final = 3 +ZSTD_lazy: Final = 4 +ZSTD_lazy2: Final = 5 + +_ZstdCompressorContinue: TypeAlias = Literal[0] +_ZstdCompressorFlushBlock: TypeAlias = Literal[1] +_ZstdCompressorFlushFrame: TypeAlias = Literal[2] + +@final +class ZstdCompressor: + CONTINUE: Final = 0 + FLUSH_BLOCK: Final = 1 + FLUSH_FRAME: Final = 2 + def __init__( + self, level: int | None = None, options: Mapping[int, int] | None = None, zstd_dict: ZstdDict | None = None + ) -> None: ... + def compress( + self, /, data: ReadableBuffer, mode: _ZstdCompressorContinue | _ZstdCompressorFlushBlock | _ZstdCompressorFlushFrame = 0 + ) -> bytes: ... + def flush(self, /, mode: _ZstdCompressorFlushBlock | _ZstdCompressorFlushFrame = 2) -> bytes: ... + @property + def last_mode(self) -> _ZstdCompressorContinue | _ZstdCompressorFlushBlock | _ZstdCompressorFlushFrame: ... + +@final +class ZstdDecompressor: + def __init__(self, zstd_dict: ZstdDict | None = None, options: Mapping[int, int] | None = None) -> None: ... + def decompress(self, /, data: ReadableBuffer, max_length: int = -1) -> bytes: ... + @property + def eof(self) -> bool: ... + @property + def needs_input(self) -> bool: ... + @property + def unused_data(self) -> bytes: ... + +@final +class ZstdDict: + def __init__(self, dict_content: bytes, /, *, is_raw: bool = False) -> None: ... + def __len__(self, /) -> int: ... + @property + def as_digested_dict(self) -> tuple[Self, int]: ... + @property + def as_prefix(self) -> tuple[Self, int]: ... + @property + def as_undigested_dict(self) -> tuple[Self, int]: ... + @property + def dict_content(self) -> bytes: ... + @property + def dict_id(self) -> int: ... + +class ZstdError(Exception): ... + +def finalize_dict( + custom_dict_bytes: bytes, samples_bytes: bytes, samples_sizes: tuple[int, ...], dict_size: int, compression_level: int, / +) -> bytes: ... +def get_frame_info(frame_buffer: ReadableBuffer) -> tuple[int, int]: ... +def get_frame_size(frame_buffer: ReadableBuffer) -> int: ... +def get_param_bounds(parameter: int, is_compress: bool) -> tuple[int, int]: ... +def set_parameter_types(c_parameter_type: type[CompressionParameter], d_parameter_type: type[DecompressionParameter]) -> None: ... +def train_dict(samples_bytes: bytes, samples_sizes: tuple[int, ...], dict_size: int, /) -> bytes: ... + +zstd_version: Final[str] +zstd_version_number: Final[int] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/tasks.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/tasks.pyi index e42151213e69ce..a088e95af653d9 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/tasks.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/asyncio/tasks.pyi @@ -423,6 +423,25 @@ if sys.version_info >= (3, 12): else: def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... +if sys.version_info >= (3, 14): + def eager_task_factory( + loop: AbstractEventLoop | None, + coro: _TaskCompatibleCoro[_T_co], + *, + name: str | None = None, + context: Context | None = None, + eager_start: bool = True, + ) -> Task[_T_co]: ... + +elif sys.version_info >= (3, 12): + def eager_task_factory( + loop: AbstractEventLoop | None, + coro: _TaskCompatibleCoro[_T_co], + *, + name: str | None = None, + context: Context | None = None, + ) -> Task[_T_co]: ... + if sys.version_info >= (3, 12): _TaskT_co = TypeVar("_TaskT_co", bound=Task[Any], covariant=True) @@ -451,10 +470,3 @@ if sys.version_info >= (3, 12): def create_eager_task_factory( custom_task_constructor: _CustomTaskConstructor[_TaskT_co], ) -> _EagerTaskFactoryType[_TaskT_co]: ... - def eager_task_factory( - loop: AbstractEventLoop | None, - coro: _TaskCompatibleCoro[_T_co], - *, - name: str | None = None, - context: Context | None = None, - ) -> Task[_T_co]: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi index 0f9d00fbc633ed..dce6187a2da105 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/bz2.pyi @@ -2,7 +2,8 @@ import sys from _bz2 import BZ2Compressor as BZ2Compressor, BZ2Decompressor as BZ2Decompressor from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Iterable -from typing import IO, Literal, Protocol, SupportsIndex, TextIO, overload +from io import TextIOWrapper +from typing import IO, Literal, Protocol, SupportsIndex, overload from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 14): @@ -48,7 +49,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> TextIO: ... +) -> TextIOWrapper: ... @overload def open( filename: _WritableFileobj, @@ -66,7 +67,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> TextIO: ... +) -> TextIOWrapper: ... @overload def open( filename: StrOrBytesPath, @@ -84,7 +85,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> TextIO: ... +) -> TextIOWrapper: ... @overload def open( filename: StrOrBytesPath | _ReadableFileobj | _WritableFileobj, @@ -93,7 +94,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> BZ2File | TextIO: ... +) -> BZ2File | TextIOWrapper: ... class BZ2File(BaseStream, IO[bytes]): def __enter__(self) -> Self: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi index 6303a9b1d460c8..b8463973ec671d 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/_common/_streams.pyi @@ -1,10 +1,11 @@ from _typeshed import Incomplete, WriteableBuffer from collections.abc import Callable from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase -from typing import Any, Protocol +from typing import Any, Protocol, type_check_only BUFFER_SIZE = DEFAULT_BUFFER_SIZE +@type_check_only class _Reader(Protocol): def read(self, n: int, /) -> bytes: ... def seekable(self) -> bool: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/__init__.pyi new file mode 100644 index 00000000000000..24a9633c488e68 --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/__init__.pyi @@ -0,0 +1,87 @@ +import enum +from _typeshed import ReadableBuffer +from collections.abc import Iterable, Mapping +from compression.zstd._zstdfile import ZstdFile, open +from typing import Final, final + +import _zstd +from _zstd import ZstdCompressor, ZstdDecompressor, ZstdDict, ZstdError, get_frame_size, zstd_version + +__all__ = ( + # compression.zstd + "COMPRESSION_LEVEL_DEFAULT", + "compress", + "CompressionParameter", + "decompress", + "DecompressionParameter", + "finalize_dict", + "get_frame_info", + "Strategy", + "train_dict", + # compression.zstd._zstdfile + "open", + "ZstdFile", + # _zstd + "get_frame_size", + "zstd_version", + "zstd_version_info", + "ZstdCompressor", + "ZstdDecompressor", + "ZstdDict", + "ZstdError", +) + +zstd_version_info: Final[tuple[int, int, int]] +COMPRESSION_LEVEL_DEFAULT: Final = _zstd.ZSTD_CLEVEL_DEFAULT + +class FrameInfo: + decompressed_size: int + dictionary_id: int + def __init__(self, decompressed_size: int, dictionary_id: int) -> None: ... + +def get_frame_info(frame_buffer: ReadableBuffer) -> FrameInfo: ... +def train_dict(samples: Iterable[ReadableBuffer], dict_size: int) -> ZstdDict: ... +def finalize_dict(zstd_dict: ZstdDict, /, samples: Iterable[ReadableBuffer], dict_size: int, level: int) -> ZstdDict: ... +def compress( + data: ReadableBuffer, level: int | None = None, options: Mapping[int, int] | None = None, zstd_dict: ZstdDict | None = None +) -> bytes: ... +def decompress(data: ReadableBuffer, zstd_dict: ZstdDict | None = None, options: Mapping[int, int] | None = None) -> bytes: ... +@final +class CompressionParameter(enum.IntEnum): + compression_level = _zstd.ZSTD_c_compressionLevel + window_log = _zstd.ZSTD_c_windowLog + hash_log = _zstd.ZSTD_c_hashLog + chain_log = _zstd.ZSTD_c_chainLog + search_log = _zstd.ZSTD_c_searchLog + min_match = _zstd.ZSTD_c_minMatch + target_length = _zstd.ZSTD_c_targetLength + strategy = _zstd.ZSTD_c_strategy + enable_long_distance_matching = _zstd.ZSTD_c_enableLongDistanceMatching + ldm_hash_log = _zstd.ZSTD_c_ldmHashLog + ldm_min_match = _zstd.ZSTD_c_ldmMinMatch + ldm_bucket_size_log = _zstd.ZSTD_c_ldmBucketSizeLog + ldm_hash_rate_log = _zstd.ZSTD_c_ldmHashRateLog + content_size_flag = _zstd.ZSTD_c_contentSizeFlag + checksum_flag = _zstd.ZSTD_c_checksumFlag + dict_id_flag = _zstd.ZSTD_c_dictIDFlag + nb_workers = _zstd.ZSTD_c_nbWorkers + job_size = _zstd.ZSTD_c_jobSize + overlap_log = _zstd.ZSTD_c_overlapLog + def bounds(self) -> tuple[int, int]: ... + +@final +class DecompressionParameter(enum.IntEnum): + window_log_max = _zstd.ZSTD_d_windowLogMax + def bounds(self) -> tuple[int, int]: ... + +@final +class Strategy(enum.IntEnum): + fast = _zstd.ZSTD_fast + dfast = _zstd.ZSTD_dfast + greedy = _zstd.ZSTD_greedy + lazy = _zstd.ZSTD_lazy + lazy2 = _zstd.ZSTD_lazy2 + btlazy2 = _zstd.ZSTD_btlazy2 + btopt = _zstd.ZSTD_btopt + btultra = _zstd.ZSTD_btultra + btultra2 = _zstd.ZSTD_btultra2 diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/_zstdfile.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/_zstdfile.pyi new file mode 100644 index 00000000000000..045b2d35acfe0e --- /dev/null +++ b/crates/ty_vendored/vendor/typeshed/stdlib/compression/zstd/_zstdfile.pyi @@ -0,0 +1,117 @@ +from _typeshed import ReadableBuffer, StrOrBytesPath, SupportsWrite, WriteableBuffer +from collections.abc import Mapping +from compression._common import _streams +from compression.zstd import ZstdDict +from io import TextIOWrapper, _WrappedBuffer +from typing import Literal, overload, type_check_only +from typing_extensions import TypeAlias + +from _zstd import ZstdCompressor, _ZstdCompressorFlushBlock, _ZstdCompressorFlushFrame + +__all__ = ("ZstdFile", "open") + +_ReadBinaryMode: TypeAlias = Literal["r", "rb"] +_WriteBinaryMode: TypeAlias = Literal["w", "wb", "x", "xb", "a", "ab"] +_ReadTextMode: TypeAlias = Literal["rt"] +_WriteTextMode: TypeAlias = Literal["wt", "xt", "at"] + +@type_check_only +class _FileBinaryRead(_streams._Reader): + def close(self) -> None: ... + +@type_check_only +class _FileBinaryWrite(SupportsWrite[bytes]): + def close(self) -> None: ... + +class ZstdFile(_streams.BaseStream): + FLUSH_BLOCK = ZstdCompressor.FLUSH_BLOCK + FLUSH_FRAME = ZstdCompressor.FLUSH_FRAME + + @overload + def __init__( + self, + file: StrOrBytesPath | _FileBinaryRead, + /, + mode: _ReadBinaryMode = "r", + *, + level: None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + ) -> None: ... + @overload + def __init__( + self, + file: StrOrBytesPath | _FileBinaryWrite, + /, + mode: _WriteBinaryMode, + *, + level: int | None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + ) -> None: ... + def write(self, data: ReadableBuffer, /) -> int: ... + def flush(self, mode: _ZstdCompressorFlushBlock | _ZstdCompressorFlushFrame = 1) -> bytes: ... # type: ignore[override] + def read(self, size: int | None = -1) -> bytes: ... + def read1(self, size: int | None = -1) -> bytes: ... + def readinto(self, b: WriteableBuffer) -> int: ... + def readinto1(self, b: WriteableBuffer) -> int: ... + def readline(self, size: int | None = -1) -> bytes: ... + def seek(self, offset: int, whence: int = 0) -> int: ... + def peek(self, size: int = -1) -> bytes: ... + @property + def name(self) -> str | bytes: ... + @property + def mode(self) -> Literal["rb", "wb"]: ... + +@overload +def open( + file: StrOrBytesPath | _FileBinaryRead, + /, + mode: _ReadBinaryMode = "rb", + *, + level: None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, +) -> ZstdFile: ... +@overload +def open( + file: StrOrBytesPath | _FileBinaryWrite, + /, + mode: _WriteBinaryMode, + *, + level: int | None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, +) -> ZstdFile: ... +@overload +def open( + file: StrOrBytesPath | _WrappedBuffer, + /, + mode: _ReadTextMode, + *, + level: None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, +) -> TextIOWrapper: ... +@overload +def open( + file: StrOrBytesPath | _WrappedBuffer, + /, + mode: _WriteTextMode, + *, + level: int | None = None, + options: Mapping[int, int] | None = None, + zstd_dict: ZstdDict | None = None, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, +) -> TextIOWrapper: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/email/_header_value_parser.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/email/_header_value_parser.pyi index a8abfead921722..95ada186c4ec8b 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/email/_header_value_parser.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/email/_header_value_parser.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Iterable, Iterator from email.errors import HeaderParseError, MessageDefect from email.policy import Policy @@ -22,10 +21,8 @@ NLSET: Final[set[str]] # Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 SPECIALSNL: Final[set[str]] -if sys.version_info >= (3, 10): - # Added in Python 3.10.17, 3.11.12, 3.12.9, 3.13.2 (may still be backported to 3.9) - def make_quoted_pairs(value: Any) -> str: ... - +# Added in Python 3.9.23, 3.10.17, 3.11.12, 3.12.9, 3.13.2 +def make_quoted_pairs(value: Any) -> str: ... def quote_string(value: Any) -> str: ... rfc2047_matcher: Pattern[str] diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi index 83592eb583366b..16259fcfadc7c4 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/fractions.pyi @@ -107,16 +107,31 @@ class Fraction(Rational): def __rdivmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... @overload def __rdivmod__(a, b: float) -> tuple[float, Fraction]: ... - @overload - def __pow__(a, b: int) -> Fraction: ... - @overload - def __pow__(a, b: float | Fraction) -> float: ... - @overload - def __pow__(a, b: complex) -> complex: ... - @overload - def __rpow__(b, a: float | Fraction) -> float: ... - @overload - def __rpow__(b, a: complex) -> complex: ... + if sys.version_info >= (3, 14): + @overload + def __pow__(a, b: int, modulo: None = None) -> Fraction: ... + @overload + def __pow__(a, b: float | Fraction, modulo: None = None) -> float: ... + @overload + def __pow__(a, b: complex, modulo: None = None) -> complex: ... + else: + @overload + def __pow__(a, b: int) -> Fraction: ... + @overload + def __pow__(a, b: float | Fraction) -> float: ... + @overload + def __pow__(a, b: complex) -> complex: ... + if sys.version_info >= (3, 14): + @overload + def __rpow__(b, a: float | Fraction, modulo: None = None) -> float: ... + @overload + def __rpow__(b, a: complex, modulo: None = None) -> complex: ... + else: + @overload + def __rpow__(b, a: float | Fraction) -> float: ... + @overload + def __rpow__(b, a: complex) -> complex: ... + def __pos__(a) -> Fraction: ... def __neg__(a) -> Fraction: ... def __abs__(a) -> Fraction: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/genericpath.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/genericpath.pyi index 9d87c48fd52006..3caed77a661acd 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/genericpath.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/genericpath.pyi @@ -2,7 +2,7 @@ import os import sys from _typeshed import BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath, SupportsRichComparisonT from collections.abc import Sequence -from typing import Literal, overload +from typing import Literal, NewType, overload from typing_extensions import LiteralString __all__ = [ @@ -17,6 +17,7 @@ __all__ = [ "samefile", "sameopenfile", "samestat", + "ALLOW_MISSING", ] if sys.version_info >= (3, 12): __all__ += ["islink"] @@ -57,3 +58,7 @@ if sys.version_info >= (3, 13): def isjunction(path: StrOrBytesPath) -> bool: ... def isdevdrive(path: StrOrBytesPath) -> bool: ... def lexists(path: StrOrBytesPath) -> bool: ... + +# Added in Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 +_AllowMissingType = NewType("_AllowMissingType", object) +ALLOW_MISSING: _AllowMissingType diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi index 883456b1ddc3de..34ae92b4d8ed65 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/gzip.pyi @@ -1,6 +1,6 @@ import sys import zlib -from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath +from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath, WriteableBuffer from io import FileIO, TextIOWrapper from typing import Final, Literal, Protocol, overload from typing_extensions import TypeAlias @@ -157,8 +157,17 @@ class GzipFile(BaseStream): def seek(self, offset: int, whence: int = 0) -> int: ... def readline(self, size: int | None = -1) -> bytes: ... + if sys.version_info >= (3, 14): + def readinto(self, b: WriteableBuffer) -> int: ... + def readinto1(self, b: WriteableBuffer) -> int: ... + class _GzipReader(DecompressReader): def __init__(self, fp: _ReadableFileobj) -> None: ... -def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... +if sys.version_info >= (3, 14): + def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float = 0) -> bytes: ... + +else: + def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... + def decompress(data: ReadableBuffer) -> bytes: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi index b066d222466ba3..b7ef607b75cbf6 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/lzma.pyi @@ -35,7 +35,8 @@ from _lzma import ( is_check_supported as is_check_supported, ) from _typeshed import ReadableBuffer, StrOrBytesPath -from typing import IO, Literal, TextIO, overload +from io import TextIOWrapper +from typing import IO, Literal, overload from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 14): @@ -144,7 +145,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> TextIO: ... +) -> TextIOWrapper: ... @overload def open( filename: StrOrBytesPath, @@ -157,7 +158,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> TextIO: ... +) -> TextIOWrapper: ... @overload def open( filename: _PathOrFile, @@ -170,7 +171,7 @@ def open( encoding: str | None = None, errors: str | None = None, newline: str | None = None, -) -> LZMAFile | TextIO: ... +) -> LZMAFile | TextIOWrapper: ... def compress( data: ReadableBuffer, format: int = 1, check: int = -1, preset: int | None = None, filters: _FilterChain | None = None ) -> bytes: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/ntpath.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/ntpath.pyi index ebe305ef708c26..074df075b97276 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/ntpath.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/ntpath.pyi @@ -1,6 +1,8 @@ import sys from _typeshed import BytesPath, StrOrBytesPath, StrPath from genericpath import ( + ALLOW_MISSING as ALLOW_MISSING, + _AllowMissingType, commonprefix as commonprefix, exists as exists, getatime as getatime, @@ -89,6 +91,7 @@ __all__ = [ "sameopenfile", "samestat", "commonpath", + "ALLOW_MISSING", ] if sys.version_info >= (3, 12): __all__ += ["isjunction", "splitroot"] @@ -108,16 +111,10 @@ def join(path: StrPath, /, *paths: StrPath) -> str: ... def join(path: BytesPath, /, *paths: BytesPath) -> bytes: ... if sys.platform == "win32": - if sys.version_info >= (3, 10): - @overload - def realpath(path: PathLike[AnyStr], *, strict: bool = False) -> AnyStr: ... - @overload - def realpath(path: AnyStr, *, strict: bool = False) -> AnyStr: ... - else: - @overload - def realpath(path: PathLike[AnyStr]) -> AnyStr: ... - @overload - def realpath(path: AnyStr) -> AnyStr: ... + @overload + def realpath(path: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + @overload + def realpath(path: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... else: realpath = abspath diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/posixpath.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/posixpath.pyi index 3313667f1781b2..84e1b1e028bdee 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/posixpath.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/posixpath.pyi @@ -2,6 +2,8 @@ import sys from _typeshed import AnyOrLiteralStr, BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath from collections.abc import Iterable from genericpath import ( + ALLOW_MISSING as ALLOW_MISSING, + _AllowMissingType, commonprefix as commonprefix, exists as exists, getatime as getatime, @@ -61,6 +63,7 @@ __all__ = [ "relpath", "commonpath", ] +__all__ += ["ALLOW_MISSING"] if sys.version_info >= (3, 12): __all__ += ["isjunction", "splitroot"] if sys.version_info >= (3, 13): @@ -122,19 +125,10 @@ def join(a: LiteralString, /, *paths: LiteralString) -> LiteralString: ... def join(a: StrPath, /, *paths: StrPath) -> str: ... @overload def join(a: BytesPath, /, *paths: BytesPath) -> bytes: ... - -if sys.version_info >= (3, 10): - @overload - def realpath(filename: PathLike[AnyStr], *, strict: bool = False) -> AnyStr: ... - @overload - def realpath(filename: AnyStr, *, strict: bool = False) -> AnyStr: ... - -else: - @overload - def realpath(filename: PathLike[AnyStr]) -> AnyStr: ... - @overload - def realpath(filename: AnyStr) -> AnyStr: ... - +@overload +def realpath(filename: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... +@overload +def realpath(filename: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... @overload def relpath(path: LiteralString, start: LiteralString | None = None) -> LiteralString: ... @overload diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi index 1ee006235ee6c1..b4fa4381a72caa 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/socket.pyi @@ -773,6 +773,10 @@ if sys.platform == "linux": if sys.version_info < (3, 11): from _socket import CAN_RAW_ERR_FILTER as CAN_RAW_ERR_FILTER + __all__ += ["CAN_RAW_ERR_FILTER"] + if sys.version_info >= (3, 13): + from _socket import CAN_RAW_ERR_FILTER as CAN_RAW_ERR_FILTER + __all__ += ["CAN_RAW_ERR_FILTER"] if sys.platform == "linux": diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tarfile.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tarfile.pyi index 31094f87872dc5..a18ef0b823f9ba 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tarfile.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tarfile.pyi @@ -38,6 +38,8 @@ if sys.version_info >= (3, 12): "AbsolutePathError", "LinkOutsideDestinationError", ] +if sys.version_info >= (3, 13): + __all__ += ["LinkFallbackError"] _FilterFunction: TypeAlias = Callable[[TarInfo, str], TarInfo | None] _TarfileFilter: TypeAlias = Literal["fully_trusted", "tar", "data"] | _FilterFunction @@ -550,7 +552,14 @@ class TarFile: filter: _TarfileFilter | None = ..., ) -> None: ... def _extract_member( - self, tarinfo: TarInfo, targetpath: str, set_attrs: bool = True, numeric_owner: bool = False + self, + tarinfo: TarInfo, + targetpath: str, + set_attrs: bool = True, + numeric_owner: bool = False, + *, + filter_function: _FilterFunction | None = None, + extraction_root: str | None = None, ) -> None: ... # undocumented def extractfile(self, member: str | TarInfo) -> IO[bytes] | None: ... def makedir(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented @@ -559,6 +568,9 @@ class TarFile: def makefifo(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented def makedev(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented def makelink(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented + def makelink_with_filter( + self, tarinfo: TarInfo, targetpath: StrOrBytesPath, filter_function: _FilterFunction, extraction_root: str + ) -> None: ... # undocumented def chown(self, tarinfo: TarInfo, targetpath: StrOrBytesPath, numeric_owner: bool) -> None: ... # undocumented def chmod(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented def utime(self, tarinfo: TarInfo, targetpath: StrOrBytesPath) -> None: ... # undocumented @@ -607,6 +619,9 @@ class AbsoluteLinkError(FilterError): class LinkOutsideDestinationError(FilterError): def __init__(self, tarinfo: TarInfo, path: str) -> None: ... +class LinkFallbackError(FilterError): + def __init__(self, tarinfo: TarInfo, path: str) -> None: ... + def fully_trusted_filter(member: TarInfo, dest_path: str) -> TarInfo: ... def tar_filter(member: TarInfo, dest_path: str) -> TarInfo: ... def data_filter(member: TarInfo, dest_path: str) -> TarInfo: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi index 2a4657f86ce13c..db0e34d737a621 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/tkinter/__init__.pyi @@ -4,7 +4,7 @@ from _typeshed import Incomplete, MaybeNone, StrOrBytesPath from collections.abc import Callable, Iterable, Mapping, Sequence from tkinter.constants import * from tkinter.font import _FontDescription -from types import TracebackType +from types import GenericAlias, TracebackType from typing import Any, ClassVar, Generic, Literal, NamedTuple, Protocol, TypedDict, TypeVar, overload, type_check_only from typing_extensions import TypeAlias, TypeVarTuple, Unpack, deprecated @@ -308,6 +308,8 @@ class Event(Generic[_W_co]): type: EventType widget: _W_co delta: int + if sys.version_info >= (3, 14): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... def NoDefaultRoot() -> None: ... diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi index 07cd57ebc18f39..3f7c2571208146 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/typing_extensions.pyi @@ -697,6 +697,6 @@ class Sentinel: if sys.version_info >= (3, 14): def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - else: + elif sys.version_info >= (3, 10): def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/zoneinfo/__init__.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/zoneinfo/__init__.pyi index 35381758a1b7eb..e9f54fbf2a26cb 100644 --- a/crates/ty_vendored/vendor/typeshed/stdlib/zoneinfo/__init__.pyi +++ b/crates/ty_vendored/vendor/typeshed/stdlib/zoneinfo/__init__.pyi @@ -1,3 +1,4 @@ +import sys from collections.abc import Iterable from datetime import datetime, timedelta, tzinfo from typing_extensions import Self @@ -17,8 +18,13 @@ class ZoneInfo(tzinfo): def __new__(cls, key: str) -> Self: ... @classmethod def no_cache(cls, key: str) -> Self: ... - @classmethod - def from_file(cls, fobj: _IOBytes, /, key: str | None = None) -> Self: ... + if sys.version_info >= (3, 12): + @classmethod + def from_file(cls, file_obj: _IOBytes, /, key: str | None = None) -> Self: ... + else: + @classmethod + def from_file(cls, fobj: _IOBytes, /, key: str | None = None) -> Self: ... + @classmethod def clear_cache(cls, *, only_keys: Iterable[str] | None = None) -> None: ... def tzname(self, dt: datetime | None, /) -> str | None: ... From 3a430fa6da045f145fb86463c0d74c8fd5d7ea54 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sun, 15 Jun 2025 15:27:39 +0200 Subject: [PATCH 419/487] [ty] Allow overriding rules for specific files (#18648) --- Cargo.lock | 2 + crates/ruff_db/src/diagnostic/mod.rs | 70 ++ crates/ruff_db/src/lib.rs | 7 + crates/ruff_graph/src/db.rs | 2 +- crates/ruff_macros/src/lib.rs | 10 + crates/ruff_macros/src/rust_doc.rs | 62 ++ crates/ty/docs/configuration.md | 149 +++ crates/ty/src/args.rs | 11 +- crates/ty/tests/cli/config_option.rs | 6 +- crates/ty/tests/cli/file_selection.rs | 6 +- crates/ty/tests/cli/rule_selection.rs | 610 +++++++++++++ crates/ty_ide/src/db.rs | 2 +- crates/ty_project/Cargo.toml | 1 + crates/ty_project/src/combine.rs | 34 + crates/ty_project/src/db.rs | 8 +- crates/ty_project/src/glob.rs | 6 + crates/ty_project/src/glob/exclude.rs | 20 +- crates/ty_project/src/glob/include.rs | 25 +- crates/ty_project/src/glob/portable.rs | 125 ++- crates/ty_project/src/metadata/options.rs | 859 +++++++++++++----- crates/ty_project/src/metadata/settings.rs | 151 ++- crates/ty_project/src/metadata/value.rs | 12 + crates/ty_python_semantic/src/db.rs | 5 +- crates/ty_python_semantic/src/lint.rs | 2 +- .../ty_python_semantic/src/python_platform.rs | 9 +- crates/ty_python_semantic/src/suppression.rs | 7 +- .../ty_python_semantic/src/types/context.rs | 2 +- crates/ty_python_semantic/tests/corpus.rs | 2 +- crates/ty_test/src/db.rs | 2 +- fuzz/fuzz_targets/ty_check_invalid_syntax.rs | 6 +- ty.schema.json | 50 +- 31 files changed, 1948 insertions(+), 315 deletions(-) create mode 100644 crates/ruff_macros/src/rust_doc.rs diff --git a/Cargo.lock b/Cargo.lock index 03aa31a04bc2a0..9ff33789d01b3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1952,6 +1952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d31b8b7a99f71bdff4235faf9ce9eada0ad3562c8fbeb7d607d9f41a6ec569d" dependencies = [ "indexmap", + "serde", ] [[package]] @@ -3949,6 +3950,7 @@ dependencies = [ "globset", "insta", "notify", + "ordermap", "pep440_rs", "rayon", "regex", diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 07ad6371e5cb65..6caefbe0c90630 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -668,6 +668,73 @@ pub enum DiagnosticId { /// A glob pattern doesn't follow the expected syntax. InvalidGlob, + + /// An `include` glob without any patterns. + /// + /// ## Why is this bad? + /// An `include` glob without any patterns won't match any files. This is probably a mistake and + /// either the `include` should be removed or a pattern should be added. + /// + /// ## Example + /// ```toml + /// [src] + /// include = [] + /// ``` + /// + /// Use instead: + /// + /// ```toml + /// [src] + /// include = ["src"] + /// ``` + /// + /// or remove the `include` option. + EmptyInclude, + + /// An override configuration is unnecessary because it applies to all files. + /// + /// ## Why is this bad? + /// An overrides section that applies to all files is probably a mistake and can be rolled-up into the root configuration. + /// + /// ## Example + /// ```toml + /// [[overrides]] + /// [overrides.rules] + /// unused-reference = "ignore" + /// ``` + /// + /// Use instead: + /// + /// ```toml + /// [rules] + /// unused-reference = "ignore" + /// ``` + /// + /// or + /// + /// ```toml + /// [[overrides]] + /// include = ["test"] + /// + /// [overrides.rules] + /// unused-reference = "ignore" + /// ``` + UnnecessaryOverridesSection, + + /// An `overrides` section in the configuration that doesn't contain any overrides. + /// + /// ## Why is this bad? + /// An `overrides` section without any configuration overrides is probably a mistake. + /// It is either a leftover after removing overrides, or a user forgot to add any overrides, + /// or used an incorrect syntax to do so (e.g. used `rules` instead of `overrides.rules`). + /// + /// ## Example + /// ```toml + /// [[overrides]] + /// include = ["test"] + /// # no `[overrides.rules]` + /// ``` + UselessOverridesSection, } impl DiagnosticId { @@ -703,6 +770,9 @@ impl DiagnosticId { DiagnosticId::RevealedType => "revealed-type", DiagnosticId::UnknownRule => "unknown-rule", DiagnosticId::InvalidGlob => "invalid-glob", + DiagnosticId::EmptyInclude => "empty-include", + DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section", + DiagnosticId::UselessOverridesSection => "useless-overrides-section", } } diff --git a/crates/ruff_db/src/lib.rs b/crates/ruff_db/src/lib.rs index c970a99d12e4a7..dec4500c5d3e0d 100644 --- a/crates/ruff_db/src/lib.rs +++ b/crates/ruff_db/src/lib.rs @@ -59,6 +59,13 @@ pub fn max_parallelism() -> NonZeroUsize { }) } +/// Trait for types that can provide Rust documentation. +/// +/// Use `derive(RustDoc)` to automatically implement this trait for types that have a static string documentation. +pub trait RustDoc { + fn rust_doc() -> &'static str; +} + #[cfg(test)] mod tests { use std::sync::{Arc, Mutex}; diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index 89eb974cc8dc56..b7056ece58ad16 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -92,7 +92,7 @@ impl Db for ModuleDb { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/crates/ruff_macros/src/lib.rs b/crates/ruff_macros/src/lib.rs index 1f8089b7111603..858b5db56766e9 100644 --- a/crates/ruff_macros/src/lib.rs +++ b/crates/ruff_macros/src/lib.rs @@ -16,6 +16,7 @@ mod map_codes; mod newtype_index; mod rule_code_prefix; mod rule_namespace; +mod rust_doc; mod violation_metadata; #[proc_macro_derive(OptionsMetadata, attributes(option, doc, option_group))] @@ -27,6 +28,15 @@ pub fn derive_options_metadata(input: TokenStream) -> TokenStream { .into() } +#[proc_macro_derive(RustDoc)] +pub fn derive_rust_doc(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + rust_doc::derive_impl(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + #[proc_macro_derive(CombineOptions)] pub fn derive_combine_options(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/crates/ruff_macros/src/rust_doc.rs b/crates/ruff_macros/src/rust_doc.rs new file mode 100644 index 00000000000000..229f4322f35f9d --- /dev/null +++ b/crates/ruff_macros/src/rust_doc.rs @@ -0,0 +1,62 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Attribute, DeriveInput, Error, Lit, LitStr, Meta}; + +pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result { + let docs = get_docs(&input.attrs)?; + + let name = input.ident; + + let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl(); + + Ok(quote! { + #[automatically_derived] + impl #impl_generics ruff_db::RustDoc for #name #ty_generics #where_clause { + fn rust_doc() -> &'static str { + #docs + } + } + }) +} +/// Collect all doc comment attributes into a string +fn get_docs(attrs: &[Attribute]) -> syn::Result { + let mut explanation = String::new(); + for attr in attrs { + if attr.path().is_ident("doc") { + if let Some(lit) = parse_attr(["doc"], attr) { + let value = lit.value(); + // `/// ` adds + let line = value.strip_prefix(' ').unwrap_or(&value); + explanation.push_str(line); + explanation.push('\n'); + } else { + return Err(Error::new_spanned(attr, "unimplemented doc comment style")); + } + } + } + Ok(explanation) +} + +fn parse_attr<'a, const LEN: usize>( + path: [&'static str; LEN], + attr: &'a Attribute, +) -> Option<&'a LitStr> { + if let Meta::NameValue(name_value) = &attr.meta { + let path_idents = name_value + .path + .segments + .iter() + .map(|segment| &segment.ident); + + if path_idents.eq(path) { + if let syn::Expr::Lit(syn::ExprLit { + lit: Lit::Str(lit), .. + }) = &name_value.value + { + return Some(lit); + } + } + } + + None +} diff --git a/crates/ty/docs/configuration.md b/crates/ty/docs/configuration.md index 25cdb70a75b725..1ff58a5437cf9d 100644 --- a/crates/ty/docs/configuration.md +++ b/crates/ty/docs/configuration.md @@ -151,6 +151,116 @@ typeshed = "/path/to/custom/typeshed" --- +## `overrides` + +Configuration override that applies to specific files based on glob patterns. + +An override allows you to apply different rule configurations to specific +files or directories. Multiple overrides can match the same file, with +later overrides take precedence. + +### Precedence + +- Later overrides in the array take precedence over earlier ones +- Override rules take precedence over global rules for matching files + +### Examples + +```toml +# Relax rules for test files +[[tool.ty.overrides]] +include = ["tests/**", "**/test_*.py"] + +[tool.ty.overrides.rules] +possibly-unresolved-reference = "warn" + +# Ignore generated files but still check important ones +[[tool.ty.overrides]] +include = ["generated/**"] +exclude = ["generated/important.py"] + +[tool.ty.overrides.rules] +possibly-unresolved-reference = "ignore" +``` + + +#### `exclude` + +A list of file and directory patterns to exclude from this override. + +Patterns follow a syntax similar to `.gitignore`. +Exclude patterns take precedence over include patterns within the same override. + +If not specified, defaults to `[]` (excludes no files). + +**Default value**: `null` + +**Type**: `list[str]` + +**Example usage** (`pyproject.toml`): + +```toml +[[tool.ty.overrides]] +exclude = [ + "generated", + "*.proto", + "tests/fixtures/**", + "!tests/fixtures/important.py" # Include this one file +] +``` + +--- + +#### `include` + +A list of file and directory patterns to include for this override. + +The `include` option follows a similar syntax to `.gitignore` but reversed: +Including a file or directory will make it so that it (and its contents) +are affected by this override. + +If not specified, defaults to `["**"]` (matches all files). + +**Default value**: `null` + +**Type**: `list[str]` + +**Example usage** (`pyproject.toml`): + +```toml +[[tool.ty.overrides]] +include = [ + "src", + "tests", +] +``` + +--- + +#### `rules` + +Rule overrides for files matching the include/exclude patterns. + +These rules will be merged with the global rules, with override rules +taking precedence for matching files. You can set rules to different +severity levels or disable them entirely. + +**Default value**: `{...}` + +**Type**: `dict[RuleName, "ignore" | "warn" | "error"]` + +**Example usage** (`pyproject.toml`): + +```toml +[[tool.ty.overrides]] +include = ["src"] + +[tool.ty.overrides.rules] +possibly-unresolved-reference = "ignore" +``` + +--- + ## `src` #### `exclude` @@ -214,6 +324,45 @@ exclude = [ --- +#### `include` + +A list of files and directories to check. The `include` option +follows a similar syntax to `.gitignore` but reversed: +Including a file or directory will make it so that it (and its contents) +are type checked. + +- `./src/` matches only a directory +- `./src` matches both files and directories +- `src` matches a file or directory named `src` +- `*` matches any (possibly empty) sequence of characters (except `/`). +- `**` matches zero or more path components. + This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. + A sequence of more than two consecutive `*` characters is also invalid. +- `?` matches any single character except `/` +- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, + so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid. + +Unlike `exclude`, all paths are anchored relative to the project root (`src` only +matches `/src` and not `/test/src`). + +`exclude` takes precedence over `include`. + +**Default value**: `null` + +**Type**: `list[str]` + +**Example usage** (`pyproject.toml`): + +```toml +[tool.ty.src] +include = [ + "src", + "tests", +] +``` + +--- + #### `respect-ignore-files` Whether to automatically exclude files that are ignored by `.ignore`, diff --git a/crates/ty/src/args.rs b/crates/ty/src/args.rs index 99f0428f0ce205..2dc77377dd876f 100644 --- a/crates/ty/src/args.rs +++ b/crates/ty/src/args.rs @@ -205,14 +205,17 @@ impl CheckCommand { src: Some(SrcOptions { respect_ignore_files, exclude: self.exclude.map(|excludes| { - excludes - .iter() - .map(|exclude| RelativeExcludePattern::cli(exclude)) - .collect() + RangedValue::cli( + excludes + .iter() + .map(|exclude| RelativeExcludePattern::cli(exclude)) + .collect(), + ) }), ..SrcOptions::default() }), rules, + ..Options::default() }; // Merge with options passed in via --config options.combine(self.config.into_options().unwrap_or_default()) diff --git a/crates/ty/tests/cli/config_option.rs b/crates/ty/tests/cli/config_option.rs index 4ea24fa4f31edb..4732b2823659a9 100644 --- a/crates/ty/tests/cli/config_option.rs +++ b/crates/ty/tests/cli/config_option.rs @@ -128,7 +128,7 @@ fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> { #[test] fn cli_config_args_invalid_option() -> anyhow::Result<()> { let case = CliTest::with_file("test.py", r"print(1)")?; - assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r###" + assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r" success: false exit_code: 2 ----- stdout ----- @@ -138,13 +138,13 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> { | 1 | bad-option=true | ^^^^^^^^^^ - unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal` + unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `overrides` Usage: ty For more information, try '--help'. - "###); + "); Ok(()) } diff --git a/crates/ty/tests/cli/file_selection.rs b/crates/ty/tests/cli/file_selection.rs index 5e646a07cdfd2e..7c47e53bf5bbeb 100644 --- a/crates/ty/tests/cli/file_selection.rs +++ b/crates/ty/tests/cli/file_selection.rs @@ -639,7 +639,7 @@ fn invalid_include_pattern() -> anyhow::Result<()> { 2 | [src] 3 | include = [ 4 | "src/**test/" - | ^^^^^^^^^^^^^ Too many stars at position 5 in glob: `src/**test/` + | ^^^^^^^^^^^^^ Too many stars at position 5 5 | ] | "#); @@ -676,7 +676,7 @@ fn invalid_include_pattern_concise_output() -> anyhow::Result<()> { ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. ty failed - Cause: error[invalid-glob] ty.toml:4:5: Invalid include pattern: Too many stars at position 5 in glob: `src/**test/` + Cause: error[invalid-glob] ty.toml:4:5: Invalid include pattern: Too many stars at position 5 "); Ok(()) @@ -717,7 +717,7 @@ fn invalid_exclude_pattern() -> anyhow::Result<()> { 2 | [src] 3 | exclude = [ 4 | "../src" - | ^^^^^^^^ The parent directory operator (`..`) at position 1 is not allowed in glob: `../src` + | ^^^^^^^^ The parent directory operator (`..`) at position 1 is not allowed 5 | ] | "#); diff --git a/crates/ty/tests/cli/rule_selection.rs b/crates/ty/tests/cli/rule_selection.rs index d60c229263b2be..a78b38b403008a 100644 --- a/crates/ty/tests/cli/rule_selection.rs +++ b/crates/ty/tests/cli/rule_selection.rs @@ -290,3 +290,613 @@ fn cli_unknown_rules() -> anyhow::Result<()> { Ok(()) } + +/// Basic override functionality: override rules for specific files +#[test] +fn overrides_basic() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + unresolved-reference = "error" + + [[tool.ty.overrides]] + include = ["tests/**"] + + [tool.ty.overrides.rules] + division-by-zero = "warn" + unresolved-reference = "ignore" + "#, + ), + ( + "main.py", + r#" + y = 4 / 0 # division-by-zero: error (global) + x = 1 + prin(x) # unresolved-reference: error (global) + "#, + ), + ( + "tests/test_main.py", + r#" + y = 4 / 0 # division-by-zero: warn (override) + x = 1 + prin(x) # unresolved-reference: ignore (override) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> main.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: error (global) + | ^^^^^ + 3 | x = 1 + 4 | prin(x) # unresolved-reference: error (global) + | + info: rule `division-by-zero` was selected in the configuration file + + error[unresolved-reference]: Name `prin` used when not defined + --> main.py:4:1 + | + 2 | y = 4 / 0 # division-by-zero: error (global) + 3 | x = 1 + 4 | prin(x) # unresolved-reference: error (global) + | ^^^^ + | + info: rule `unresolved-reference` was selected in the configuration file + + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> tests/test_main.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: warn (override) + | ^^^^^ + 3 | x = 1 + 4 | prin(x) # unresolved-reference: ignore (override) + | + info: rule `division-by-zero` was selected in the configuration file + + Found 3 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "###); + + Ok(()) +} + +/// Multiple overrides: later overrides take precedence +#[test] +fn overrides_precedence() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + # First override: all test files + [[tool.ty.overrides]] + include = ["tests/**"] + [tool.ty.overrides.rules] + division-by-zero = "warn" + + # Second override: specific test file (takes precedence) + [[tool.ty.overrides]] + include = ["tests/important.py"] + [tool.ty.overrides.rules] + division-by-zero = "ignore" + "#, + ), + ( + "tests/test_main.py", + r#" + y = 4 / 0 # division-by-zero: warn (first override) + "#, + ), + ( + "tests/important.py", + r#" + y = 4 / 0 # division-by-zero: ignore (second override) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: true + exit_code: 0 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> tests/test_main.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: warn (first override) + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Override with exclude patterns +#[test] +fn overrides_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = ["tests/**"] + exclude = ["tests/important.py"] + [tool.ty.overrides.rules] + division-by-zero = "warn" + "#, + ), + ( + "tests/test_main.py", + r#" + y = 4 / 0 # division-by-zero: warn (override applies) + "#, + ), + ( + "tests/important.py", + r#" + y = 4 / 0 # division-by-zero: error (override excluded) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> tests/important.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: error (override excluded) + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> tests/test_main.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: warn (override applies) + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + Ok(()) +} + +/// Override without rules inherits global rules +#[test] +fn overrides_inherit_global() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "warn" + unresolved-reference = "error" + + [[tool.ty.overrides]] + include = ["tests/**"] + + [tool.ty.overrides.rules] + # Override only division-by-zero, unresolved-reference should inherit from global + division-by-zero = "ignore" + "#, + ), + ( + "main.py", + r#" + y = 4 / 0 # division-by-zero: warn (global) + prin(y) # unresolved-reference: error (global) + "#, + ), + ( + "tests/test_main.py", + r#" + y = 4 / 0 # division-by-zero: ignore (overridden) + prin(y) # unresolved-reference: error (inherited from global) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> main.py:2:5 + | + 2 | y = 4 / 0 # division-by-zero: warn (global) + | ^^^^^ + 3 | prin(y) # unresolved-reference: error (global) + | + info: rule `division-by-zero` was selected in the configuration file + + error[unresolved-reference]: Name `prin` used when not defined + --> main.py:3:1 + | + 2 | y = 4 / 0 # division-by-zero: warn (global) + 3 | prin(y) # unresolved-reference: error (global) + | ^^^^ + | + info: rule `unresolved-reference` was selected in the configuration file + + error[unresolved-reference]: Name `prin` used when not defined + --> tests/test_main.py:3:1 + | + 2 | y = 4 / 0 # division-by-zero: ignore (overridden) + 3 | prin(y) # unresolved-reference: error (inherited from global) + | ^^^^ + | + info: rule `unresolved-reference` was selected in the configuration file + + Found 3 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +/// ty warns about invalid glob patterns in override include patterns +#[test] +fn overrides_invalid_include_glob() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = ["tests/[invalid"] # Invalid glob: unclosed bracket + [tool.ty.overrides.rules] + division-by-zero = "warn" + "#, + ), + ( + "test.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: error[invalid-glob]: Invalid include pattern + --> pyproject.toml:6:12 + | + 5 | [[tool.ty.overrides]] + 6 | include = ["tests/[invalid"] # Invalid glob: unclosed bracket + | ^^^^^^^^^^^^^^^^ unclosed character class; missing ']' + 7 | [tool.ty.overrides.rules] + 8 | division-by-zero = "warn" + | + "#); + + Ok(()) +} + +/// ty warns about invalid glob patterns in override exclude patterns +#[test] +fn overrides_invalid_exclude_glob() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = ["tests/**"] + exclude = ["***/invalid"] # Invalid glob: triple asterisk + [tool.ty.overrides.rules] + division-by-zero = "warn" + "#, + ), + ( + "test.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + ty failed + Cause: error[invalid-glob]: Invalid exclude pattern + --> pyproject.toml:7:12 + | + 5 | [[tool.ty.overrides]] + 6 | include = ["tests/**"] + 7 | exclude = ["***/invalid"] # Invalid glob: triple asterisk + | ^^^^^^^^^^^^^ Too many stars at position 1 + 8 | [tool.ty.overrides.rules] + 9 | division-by-zero = "warn" + | + "#); + + Ok(()) +} + +/// ty warns when an overrides section has neither include nor exclude +#[test] +fn overrides_missing_include_exclude() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + # Missing both include and exclude - should warn + [tool.ty.overrides.rules] + division-by-zero = "warn" + "#, + ), + ( + "test.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + warning[unnecessary-overrides-section]: Unnecessary `overrides` section + --> pyproject.toml:5:1 + | + 3 | division-by-zero = "error" + 4 | + 5 | [[tool.ty.overrides]] + | ^^^^^^^^^^^^^^^^^^^^^ This overrides section applies to all files + 6 | # Missing both include and exclude - should warn + 7 | [tool.ty.overrides.rules] + | + info: It has no `include` or `exclude` option restricting the files + info: Restrict the files by adding a pattern to `include` or `exclude`... + info: or remove the `[[overrides]]` section and merge the configuration into the root `[rules]` table if the configuration should apply to all files + + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +/// ty warns when an overrides section has an empty include array +#[test] +fn overrides_empty_include() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = [] # Empty include - won't match any files + [tool.ty.overrides.rules] + division-by-zero = "warn" + "#, + ), + ( + "test.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + warning[empty-include]: Empty include matches no files + --> pyproject.toml:6:11 + | + 5 | [[tool.ty.overrides]] + 6 | include = [] # Empty include - won't match any files + | ^^ This `include` list is empty + 7 | [tool.ty.overrides.rules] + 8 | division-by-zero = "warn" + | + info: Remove the `include` option to match all files or add a pattern to match specific files + + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +/// ty warns when an overrides section has no actual overrides +#[test] +fn overrides_no_actual_overrides() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = ["*.py"] # Has patterns but no rule overrides + # Missing [tool.ty.overrides.rules] section entirely + "#, + ), + ( + "test.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + warning[useless-overrides-section]: Useless `overrides` section + --> pyproject.toml:5:1 + | + 3 | division-by-zero = "error" + 4 | + 5 | / [[tool.ty.overrides]] + 6 | | include = ["*.py"] # Has patterns but no rule overrides + | |__________________^ This overrides section configures no rules + 7 | # Missing [tool.ty.overrides.rules] section entirely + | + info: It has no `rules` table + info: Add a `[overrides.rules]` table... + info: or remove the `[[overrides]]` section if there's nothing to override + + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> test.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 2 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} + +/// ty warns about unknown rules specified in an overrides section +#[test] +fn overrides_unknown_rules() -> anyhow::Result<()> { + let case = CliTest::with_files([ + ( + "pyproject.toml", + r#" + [tool.ty.rules] + division-by-zero = "error" + + [[tool.ty.overrides]] + include = ["tests/**"] + + [tool.ty.overrides.rules] + division-by-zero = "warn" + division-by-zer = "error" # incorrect rule name + "#, + ), + ( + "main.py", + r#" + y = 4 / 0 + "#, + ), + ( + "tests/test_main.py", + r#" + y = 4 / 0 + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + warning[unknown-rule]: Unknown lint rule `division-by-zer` + --> pyproject.toml:10:1 + | + 8 | [tool.ty.overrides.rules] + 9 | division-by-zero = "warn" + 10 | division-by-zer = "error" # incorrect rule name + | ^^^^^^^^^^^^^^^ + | + + error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> main.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero + --> tests/test_main.py:2:5 + | + 2 | y = 4 / 0 + | ^^^^^ + | + info: rule `division-by-zero` was selected in the configuration file + + Found 3 diagnostics + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "#); + + Ok(()) +} diff --git a/crates/ty_ide/src/db.rs b/crates/ty_ide/src/db.rs index ff91bb3377b56a..9428292b1c2fbe 100644 --- a/crates/ty_ide/src/db.rs +++ b/crates/ty_ide/src/db.rs @@ -120,7 +120,7 @@ pub(crate) mod tests { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/crates/ty_project/Cargo.toml b/crates/ty_project/Cargo.toml index 707c9915ae5276..d0648255fd3a79 100644 --- a/crates/ty_project/Cargo.toml +++ b/crates/ty_project/Cargo.toml @@ -30,6 +30,7 @@ crossbeam = { workspace = true } globset = { workspace = true } notify = { workspace = true } pep440_rs = { workspace = true, features = ["version-ranges"] } +ordermap = { workspace = true, features = ["serde"] } rayon = { workspace = true } regex = { workspace = true } regex-automata = { workspace = true } diff --git a/crates/ty_project/src/combine.rs b/crates/ty_project/src/combine.rs index 69d6eae9db73e7..79fff36f7710d2 100644 --- a/crates/ty_project/src/combine.rs +++ b/crates/ty_project/src/combine.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, hash::BuildHasher}; +use ordermap::OrderMap; use ruff_db::system::SystemPathBuf; use ruff_python_ast::PythonVersion; use ty_python_semantic::{PythonPath, PythonPlatform}; @@ -111,6 +112,18 @@ where } } +impl Combine for OrderMap +where + K: Eq + std::hash::Hash, + S: BuildHasher, +{ + fn combine_with(&mut self, other: Self) { + for (k, v) in other { + self.entry(k).or_insert(v); + } + } +} + /// Implements [`Combine`] for a value that always returns `self` when combined with another value. macro_rules! impl_noop_combine { ($name:ident) => { @@ -150,6 +163,7 @@ impl_noop_combine!(String); #[cfg(test)] mod tests { use crate::combine::Combine; + use ordermap::OrderMap; use std::collections::HashMap; #[test] @@ -188,4 +202,24 @@ mod tests { ])) ); } + + #[test] + fn combine_order_map() { + let a: OrderMap = OrderMap::from_iter([(1, "a"), (2, "a"), (3, "a")]); + let b: OrderMap = OrderMap::from_iter([(0, "b"), (2, "b"), (5, "b")]); + + assert_eq!(None.combine(Some(b.clone())), Some(b.clone())); + assert_eq!(Some(a.clone()).combine(None), Some(a.clone())); + assert_eq!( + Some(a).combine(Some(b)), + // The value from `a` takes precedence + Some(OrderMap::from_iter([ + (1, "a"), + (2, "a"), + (3, "a"), + (0, "b"), + (5, "b") + ])) + ); + } } diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index ddcf8b95ab4dfa..654ae540c4e52f 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -1,6 +1,7 @@ use std::panic::{AssertUnwindSafe, RefUnwindSafe}; use std::sync::Arc; +use crate::metadata::settings::file_settings; use crate::{DEFAULT_LINT_REGISTRY, DummyReporter}; use crate::{Project, ProjectMetadata, Reporter}; use ruff_db::diagnostic::Diagnostic; @@ -162,8 +163,9 @@ impl SemanticDb for ProjectDatabase { project.is_file_open(self, file) } - fn rule_selection(&self) -> &RuleSelection { - self.project().rules(self) + fn rule_selection(&self, file: File) -> &RuleSelection { + let settings = file_settings(self, file); + settings.rules(self) } fn lint_registry(&self) -> &LintRegistry { @@ -340,7 +342,7 @@ pub(crate) mod tests { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: ruff_db::files::File) -> &RuleSelection { self.project().rules(self) } diff --git a/crates/ty_project/src/glob.rs b/crates/ty_project/src/glob.rs index 2c93fc47161d88..f681dee13b38b5 100644 --- a/crates/ty_project/src/glob.rs +++ b/crates/ty_project/src/glob.rs @@ -58,6 +58,12 @@ impl IncludeExcludeFilter { } } +impl std::fmt::Display for IncludeExcludeFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "include={}, exclude={}", &self.include, &self.exclude) + } +} + #[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum GlobFilterCheckMode { /// The paths are checked top-to-bottom and inclusion is determined diff --git a/crates/ty_project/src/glob/exclude.rs b/crates/ty_project/src/glob/exclude.rs index 1e6813a00c9cc7..9a0b1a409368af 100644 --- a/crates/ty_project/src/glob/exclude.rs +++ b/crates/ty_project/src/glob/exclude.rs @@ -6,6 +6,7 @@ //! * `/src/**` excludes all files and directories inside a directory named `src` but not `src` itself. //! * `!src` allows a file or directory named `src` anywhere in the path +use std::fmt::Formatter; use std::sync::Arc; use globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder}; @@ -63,6 +64,12 @@ impl ExcludeFilter { } } +impl std::fmt::Display for ExcludeFilter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(&self.ignore.globs).finish() + } +} + pub(crate) struct ExcludeFilterBuilder { ignore: GitignoreBuilder, } @@ -150,8 +157,8 @@ impl Gitignore { impl std::fmt::Debug for Gitignore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Gitignore") - .field("globs", &self.globs) + f.debug_tuple("Gitignore") + .field(&self.globs) .finish_non_exhaustive() } } @@ -230,13 +237,18 @@ impl GitignoreBuilder { /// Adds a gitignore like glob pattern to this builder. /// /// If the pattern could not be parsed as a glob, then an error is returned. - fn add(&mut self, mut pattern: &str) -> Result<&mut GitignoreBuilder, globset::Error> { + fn add( + &mut self, + pattern: &AbsolutePortableGlobPattern, + ) -> Result<&mut GitignoreBuilder, globset::Error> { let mut glob = IgnoreGlob { - original: pattern.to_string(), + original: pattern.relative().to_string(), is_allow: false, is_only_dir: false, }; + let mut pattern = pattern.absolute(); + // File names starting with `!` are escaped with a backslash. Strip the backslash. // This is not a negated pattern! if pattern.starts_with("\\!") { diff --git a/crates/ty_project/src/glob/include.rs b/crates/ty_project/src/glob/include.rs index b6365cf62711b7..dc9e10dd48a400 100644 --- a/crates/ty_project/src/glob/include.rs +++ b/crates/ty_project/src/glob/include.rs @@ -2,6 +2,7 @@ use globset::{Glob, GlobBuilder, GlobSet, GlobSetBuilder}; use regex_automata::dfa; use regex_automata::dfa::Automaton; use ruff_db::system::SystemPath; +use std::fmt::Formatter; use std::path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}; use tracing::warn; @@ -92,12 +93,18 @@ impl IncludeFilter { impl std::fmt::Debug for IncludeFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IncludeFilder") - .field("original_patterns", &self.original_patterns) + f.debug_tuple("IncludeFilter") + .field(&self.original_patterns) .finish_non_exhaustive() } } +impl std::fmt::Display for IncludeFilter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(&self.original_patterns).finish() + } +} + impl PartialEq for IncludeFilter { fn eq(&self, other: &Self) -> bool { self.original_patterns == other.original_patterns @@ -127,35 +134,35 @@ impl IncludeFilterBuilder { &mut self, input: &AbsolutePortableGlobPattern, ) -> Result<&mut Self, globset::Error> { - let mut glob = &**input; + let mut glob_pattern = input.absolute(); let mut only_directory = false; // A pattern ending with a `/` should only match directories. E.g. `src/` only matches directories // whereas `src` matches both files and directories. // We need to remove the `/` to ensure that a path missing the trailing `/` matches. - if let Some(after) = input.strip_suffix('/') { + if let Some(after) = glob_pattern.strip_suffix('/') { // Escaped `/` or `\` aren't allowed. `portable_glob::parse` will error only_directory = true; - glob = after; + glob_pattern = after; } // If regex ends with `/**`, only push that one glob and regex // Otherwise, push two regex, one for `/**` and one for without - let glob = GlobBuilder::new(glob) + let glob = GlobBuilder::new(glob_pattern) .literal_separator(true) // No need to support Windows-style paths, so the backslash can be used a escape. .backslash_escape(true) .build()?; - self.original_pattern.push(input.to_string()); + self.original_pattern.push(input.relative().to_string()); // `lib` is the same as `lib/**` // Add a glob that matches `lib` exactly, change the glob to `lib/**`. - if input.ends_with("**") { + if glob_pattern.ends_with("**") { self.push_prefix_regex(&glob); self.set.add(glob); } else { - let prefix_glob = GlobBuilder::new(&format!("{glob}/**")) + let prefix_glob = GlobBuilder::new(&format!("{glob_pattern}/**")) .literal_separator(true) // No need to support Windows-style paths, so the backslash can be used a escape. .backslash_escape(true) diff --git a/crates/ty_project/src/glob/portable.rs b/crates/ty_project/src/glob/portable.rs index 8de8d91188d041..7b147c4a61bc29 100644 --- a/crates/ty_project/src/glob/portable.rs +++ b/crates/ty_project/src/glob/portable.rs @@ -8,6 +8,7 @@ //! [Source](https://github.com/astral-sh/uv/blob/main/crates/uv-globfilter/src/portable_glob.rs) use ruff_db::system::SystemPath; +use std::error::Error as _; use std::ops::Deref; use std::{fmt::Write, path::MAIN_SEPARATOR}; use thiserror::Error; @@ -65,14 +66,12 @@ impl<'a> PortableGlobPattern<'a> { } if star_run >= 3 { return Err(PortableGlobError::TooManyStars { - glob: glob.to_string(), // We don't update pos for the stars. pos, }); } else if star_run == 2 { if chars.peek().is_some_and(|(_, c)| *c != '/') { return Err(PortableGlobError::TooManyStars { - glob: glob.to_string(), // We don't update pos for the stars. pos, }); @@ -83,10 +82,7 @@ impl<'a> PortableGlobPattern<'a> { start_or_slash = false; } else if c == '.' { if start_or_slash && matches!(chars.peek(), Some((_, '.'))) { - return Err(PortableGlobError::ParentDirectory { - pos, - glob: glob.to_string(), - }); + return Err(PortableGlobError::ParentDirectory { pos }); } start_or_slash = false; } else if c == '/' { @@ -99,7 +95,6 @@ impl<'a> PortableGlobPattern<'a> { break; } else { return Err(PortableGlobError::InvalidCharacterRange { - glob: glob.to_string(), pos, invalid: InvalidChar(c), }); @@ -111,24 +106,17 @@ impl<'a> PortableGlobPattern<'a> { Some((pos, '/' | '\\')) => { // For cross-platform compatibility, we don't allow forward slashes or // backslashes to be escaped. - return Err(PortableGlobError::InvalidEscapee { - glob: glob.to_string(), - pos, - }); + return Err(PortableGlobError::InvalidEscapee { pos }); } Some(_) => { // Escaped character } None => { - return Err(PortableGlobError::TrailingEscape { - glob: glob.to_string(), - pos, - }); + return Err(PortableGlobError::TrailingEscape { pos }); } } } else { return Err(PortableGlobError::InvalidCharacter { - glob: glob.to_string(), pos, invalid: InvalidChar(c), }); @@ -160,12 +148,18 @@ impl<'a> PortableGlobPattern<'a> { // Patterns that don't contain any `/`, e.g. `.venv` are unanchored patterns // that match anywhere. if !self.chars().any(|c| c == '/') { - return AbsolutePortableGlobPattern(self.to_string()); + return AbsolutePortableGlobPattern { + absolute: self.to_string(), + relative: self.pattern.to_string(), + }; } } if pattern.starts_with('/') { - return AbsolutePortableGlobPattern(pattern.to_string()); + return AbsolutePortableGlobPattern { + absolute: pattern.to_string(), + relative: self.pattern.to_string(), + }; } let mut rest = pattern; @@ -206,9 +200,15 @@ impl<'a> PortableGlobPattern<'a> { output.push_str(rest); if negated { // If the pattern is negated, we need to keep the leading `!`. - AbsolutePortableGlobPattern(format!("!{output}")) + AbsolutePortableGlobPattern { + absolute: format!("!{output}"), + relative: self.pattern.to_string(), + } } else { - AbsolutePortableGlobPattern(output) + AbsolutePortableGlobPattern { + absolute: output, + relative: self.pattern.to_string(), + } } } } @@ -225,53 +225,48 @@ impl Deref for PortableGlobPattern<'_> { /// /// E.g., `./src/**` becomes `/root/src/**` when anchored to `/root`. #[derive(Debug, Eq, PartialEq, Hash)] -pub(crate) struct AbsolutePortableGlobPattern(String); +pub(crate) struct AbsolutePortableGlobPattern { + absolute: String, + relative: String, +} -impl Deref for AbsolutePortableGlobPattern { - type Target = str; +impl AbsolutePortableGlobPattern { + /// Returns the absolute path of this glob pattern. + pub(crate) fn absolute(&self) -> &str { + &self.absolute + } - fn deref(&self) -> &Self::Target { - &self.0 + /// Returns the relative path of this glob pattern. + pub(crate) fn relative(&self) -> &str { + &self.relative } } #[derive(Debug, Error)] pub(crate) enum PortableGlobError { /// Shows the failing glob in the error message. - #[error(transparent)] + #[error("{desc}", desc=.0.description())] GlobError(#[from] globset::Error), - #[error( - "The parent directory operator (`..`) at position {pos} is not allowed in glob: `{glob}`" - )] - ParentDirectory { glob: String, pos: usize }, + #[error("The parent directory operator (`..`) at position {pos} is not allowed")] + ParentDirectory { pos: usize }, #[error( - "Invalid character `{invalid}` at position {pos} in glob: `{glob}`. hint: Characters can be escaped with a backslash" + "Invalid character `{invalid}` at position {pos}. hint: Characters can be escaped with a backslash" )] - InvalidCharacter { - glob: String, - pos: usize, - invalid: InvalidChar, - }, + InvalidCharacter { pos: usize, invalid: InvalidChar }, - #[error( - "Path separators can't be escaped, invalid character at position {pos} in glob: `{glob}`" - )] - InvalidEscapee { glob: String, pos: usize }, + #[error("Path separators can't be escaped, invalid character at position {pos}")] + InvalidEscapee { pos: usize }, - #[error("Invalid character `{invalid}` in range at position {pos} in glob: `{glob}`")] - InvalidCharacterRange { - glob: String, - pos: usize, - invalid: InvalidChar, - }, + #[error("Invalid character `{invalid}` in range at position {pos}")] + InvalidCharacterRange { pos: usize, invalid: InvalidChar }, - #[error("Too many stars at position {pos} in glob: `{glob}`")] - TooManyStars { glob: String, pos: usize }, + #[error("Too many stars at position {pos}")] + TooManyStars { pos: usize }, - #[error("Trailing backslash at position {pos} in glob: `{glob}`")] - TrailingEscape { glob: String, pos: usize }, + #[error("Trailing backslash at position {pos}")] + TrailingEscape { pos: usize }, } #[derive(Copy, Clone, Debug)] @@ -303,57 +298,57 @@ mod tests { assert_snapshot!( parse_err(".."), - @"The parent directory operator (`..`) at position 1 is not allowed in glob: `..`" + @"The parent directory operator (`..`) at position 1 is not allowed" ); assert_snapshot!( parse_err("licenses/.."), - @"The parent directory operator (`..`) at position 10 is not allowed in glob: `licenses/..`" + @"The parent directory operator (`..`) at position 10 is not allowed" ); assert_snapshot!( parse_err("licenses/LICEN!E.txt"), - @"Invalid character `!` at position 15 in glob: `licenses/LICEN!E.txt`. hint: Characters can be escaped with a backslash" + @"Invalid character `!` at position 15. hint: Characters can be escaped with a backslash" ); assert_snapshot!( parse_err("licenses/LICEN[!C]E.txt"), - @"Invalid character `!` in range at position 15 in glob: `licenses/LICEN[!C]E.txt`" + @"Invalid character `!` in range at position 15" ); assert_snapshot!( parse_err("licenses/LICEN[C?]E.txt"), - @"Invalid character `?` in range at position 16 in glob: `licenses/LICEN[C?]E.txt`" + @"Invalid character `?` in range at position 16" ); assert_snapshot!( parse_err("******"), - @"Too many stars at position 1 in glob: `******`" + @"Too many stars at position 1" ); assert_snapshot!( parse_err("licenses/**license"), - @"Too many stars at position 10 in glob: `licenses/**license`" + @"Too many stars at position 10" ); assert_snapshot!( parse_err("licenses/***/licenses.csv"), - @"Too many stars at position 10 in glob: `licenses/***/licenses.csv`" + @"Too many stars at position 10" ); assert_snapshot!( parse_err(r"**/@test"), - @"Invalid character `@` at position 4 in glob: `**/@test`. hint: Characters can be escaped with a backslash" + @"Invalid character `@` at position 4. hint: Characters can be escaped with a backslash" ); // Escapes are not allowed in strict PEP 639 mode assert_snapshot!( parse_err(r"public domain/Gulliver\\’s Travels.txt"), - @r"Invalid character ` ` at position 7 in glob: `public domain/Gulliver\\’s Travels.txt`. hint: Characters can be escaped with a backslash" + @r"Invalid character ` ` at position 7. hint: Characters can be escaped with a backslash" ); assert_snapshot!( parse_err(r"**/@test"), - @"Invalid character `@` at position 4 in glob: `**/@test`. hint: Characters can be escaped with a backslash" + @"Invalid character `@` at position 4. hint: Characters can be escaped with a backslash" ); // Escaping slashes is not allowed. assert_snapshot!( parse_err(r"licenses\\MIT.txt"), - @r"Path separators can't be escaped, invalid character at position 9 in glob: `licenses\\MIT.txt`" + @r"Path separators can't be escaped, invalid character at position 9" ); assert_snapshot!( parse_err(r"licenses\/MIT.txt"), - @r"Path separators can't be escaped, invalid character at position 9 in glob: `licenses\/MIT.txt`" + @r"Path separators can't be escaped, invalid character at position 9" ); } @@ -388,8 +383,8 @@ mod tests { #[track_caller] fn assert_absolute_path(pattern: &str, relative_to: impl AsRef, expected: &str) { let pattern = PortableGlobPattern::parse(pattern, true).unwrap(); - let absolute = pattern.into_absolute(relative_to); - assert_eq!(&*absolute, expected); + let pattern = pattern.into_absolute(relative_to); + assert_eq!(pattern.absolute(), expected); } #[test] diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 6c7696278cb6da..b81c8c43dc3c35 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -1,25 +1,29 @@ use crate::Db; -use crate::glob::{ - ExcludeFilterBuilder, IncludeExcludeFilter, IncludeFilterBuilder, PortableGlobPattern, -}; -use crate::metadata::settings::SrcSettings; +use crate::combine::Combine; +use crate::glob::{ExcludeFilter, IncludeExcludeFilter, IncludeFilter}; +use crate::metadata::settings::{OverrideSettings, SrcSettings}; use crate::metadata::value::{ RangedValue, RelativeExcludePattern, RelativeIncludePattern, RelativePathBuf, ValueSource, ValueSourceGuard, }; +use ordermap::OrderMap; +use ruff_db::RustDoc; use ruff_db::diagnostic::{ Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, Severity, Span, SubDiagnostic, }; use ruff_db::files::system_path_to_file; use ruff_db::system::{System, SystemPath, SystemPathBuf}; -use ruff_macros::{Combine, OptionsMetadata}; +use ruff_macros::{Combine, OptionsMetadata, RustDoc}; +use ruff_options_metadata::{OptionSet, OptionsMetadata, Visit}; use ruff_python_ast::PythonVersion; -use rustc_hash::FxHashMap; +use rustc_hash::FxHasher; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::{self, Debug, Display}; +use std::hash::BuildHasherDefault; +use std::ops::Deref; use std::sync::Arc; use thiserror::Error; use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection}; @@ -28,7 +32,7 @@ use ty_python_semantic::{ PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, }; -use super::settings::{Settings, TerminalSettings}; +use super::settings::{Override, Settings, TerminalSettings}; #[derive( Debug, Default, Clone, PartialEq, Eq, Combine, Serialize, Deserialize, OptionsMetadata, @@ -70,6 +74,15 @@ pub struct Options { #[serde(skip_serializing_if = "Option::is_none")] #[option_group] pub terminal: Option, + + /// Override configurations for specific file patterns. + /// + /// Each override specifies include/exclude patterns and rule configurations + /// that apply to matching files. Multiple overrides can match the same file, + /// with later overrides taking precedence. + #[serde(skip_serializing_if = "Option::is_none")] + #[option_group] + pub overrides: Option, } impl Options { @@ -228,7 +241,8 @@ impl Options { db: &dyn Db, project_root: &SystemPath, ) -> Result<(Settings, Vec), ToSettingsError> { - let (rules, diagnostics) = self.to_rule_selection(db); + let mut diagnostics = Vec::new(); + let rules = self.to_rule_selection(db, &mut diagnostics); let terminal_options = self.terminal.clone().unwrap_or_default(); let terminal = TerminalSettings { @@ -247,7 +261,15 @@ impl Options { }; let src = src_options - .to_settings(db, project_root) + .to_settings(db, project_root, &mut diagnostics) + .map_err(|err| ToSettingsError { + diagnostic: err, + output_format: terminal.output_format, + color: colored::control::SHOULD_COLORIZE.should_colorize(), + })?; + + let overrides = self + .to_overrides_settings(db, project_root, &mut diagnostics) .map_err(|err| ToSettingsError { diagnostic: err, output_format: terminal.output_format, @@ -258,80 +280,45 @@ impl Options { rules: Arc::new(rules), terminal, src, + overrides, }; Ok((settings, diagnostics)) } #[must_use] - fn to_rule_selection(&self, db: &dyn Db) -> (RuleSelection, Vec) { - let registry = db.lint_registry(); - let mut diagnostics = Vec::new(); - - // Initialize the selection with the defaults - let mut selection = RuleSelection::from_registry(registry); - - let rules = self - .rules - .as_ref() - .into_iter() - .flat_map(|rules| rules.inner.iter()); + fn to_rule_selection( + &self, + db: &dyn Db, + diagnostics: &mut Vec, + ) -> RuleSelection { + if let Some(rules) = self.rules.as_ref() { + rules.to_rule_selection(db, diagnostics) + } else { + RuleSelection::from_registry(db.lint_registry()) + } + } - for (rule_name, level) in rules { - let source = rule_name.source(); - match registry.get(rule_name) { - Ok(lint) => { - let lint_source = match source { - ValueSource::File(_) => LintSource::File, - ValueSource::Cli => LintSource::Cli, - }; - if let Ok(severity) = Severity::try_from(**level) { - selection.enable(lint, severity, lint_source); - } else { - selection.disable(lint); - } - } - Err(error) => { - // `system_path_to_file` can return `Err` if the file was deleted since the configuration - // was read. This should be rare and it should be okay to default to not showing a configuration - // file in that case. - let file = source - .file() - .and_then(|path| system_path_to_file(db.upcast(), path).ok()); + fn to_overrides_settings( + &self, + db: &dyn Db, + project_root: &SystemPath, + diagnostics: &mut Vec, + ) -> Result, Box> { + let override_options = self.overrides.as_deref().unwrap_or_default(); - // TODO: Add a note if the value was configured on the CLI - let diagnostic = match error { - GetLintError::Unknown(_) => OptionDiagnostic::new( - DiagnosticId::UnknownRule, - format!("Unknown lint rule `{rule_name}`"), - Severity::Warning, - ), - GetLintError::PrefixedWithCategory { suggestion, .. } => { - OptionDiagnostic::new( - DiagnosticId::UnknownRule, - format!( - "Unknown lint rule `{rule_name}`. Did you mean `{suggestion}`?" - ), - Severity::Warning, - ) - } + let mut overrides = Vec::with_capacity(override_options.len()); - GetLintError::Removed(_) => OptionDiagnostic::new( - DiagnosticId::UnknownRule, - format!("Unknown lint rule `{rule_name}`"), - Severity::Warning, - ), - }; + for override_option in override_options { + let override_instance = + override_option.to_override(db, project_root, self.rules.as_ref(), diagnostics)?; - let annotation = file.map(Span::from).map(|span| { - Annotation::primary(span.with_optional_range(rule_name.range())) - }); - diagnostics.push(diagnostic.with_annotation(annotation)); - } + if let Some(value) = override_instance { + overrides.push(value); } } - (selection, diagnostics) + Ok(overrides) } } @@ -479,7 +466,7 @@ pub struct SrcOptions { /// /// - `./src/` matches only a directory /// - `./src` matches both files and directories - /// - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) + /// - `src` matches a file or directory named `src` /// - `*` matches any (possibly empty) sequence of characters (except `/`). /// - `**` matches zero or more path components. /// This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. @@ -491,9 +478,19 @@ pub struct SrcOptions { /// Unlike `exclude`, all paths are anchored relative to the project root (`src` only /// matches `/src` and not `/test/src`). /// - /// `exclude` take precedence over `include`. + /// `exclude` takes precedence over `include`. #[serde(skip_serializing_if = "Option::is_none")] - pub include: Option>, + #[option( + default = r#"null"#, + value_type = r#"list[str]"#, + example = r#" + include = [ + "src", + "tests", + ] + "# + )] + pub include: Option>>, /// A list of file and directory patterns to exclude from type checking. /// @@ -548,7 +545,7 @@ pub struct SrcOptions { "# )] #[serde(skip_serializing_if = "Option::is_none")] - pub exclude: Option>, + pub exclude: Option>>, } impl SrcOptions { @@ -556,113 +553,276 @@ impl SrcOptions { &self, db: &dyn Db, project_root: &SystemPath, + diagnostics: &mut Vec, ) -> Result> { - let mut includes = IncludeFilterBuilder::new(); - let system = db.system(); - - if let Some(include) = self.include.as_ref() { - for pattern in include { - // Check the relative pattern for better error messages. - pattern.absolute(project_root, system) - .and_then(|include| Ok(includes.add(&include)?)) - .map_err(|err| { - let diagnostic = OptionDiagnostic::new( - DiagnosticId::InvalidGlob, - format!("Invalid include pattern: {err}"), - Severity::Error, - ); - - match pattern.source() { - ValueSource::File(file_path) => { - if let Ok(file) = system_path_to_file(db.upcast(), &**file_path) { - diagnostic - .with_message("Invalid include pattern") - .with_annotation(Some( - Annotation::primary( - Span::from(file) - .with_optional_range(pattern.range()), - ) - .message(err.to_string()), - )) - } else { - diagnostic.sub(Some(SubDiagnostic::new( - Severity::Info, - "The pattern is defined in the `src.include` option in your configuration file", - ))) - } - } - ValueSource::Cli => diagnostic.sub(Some(SubDiagnostic::new( - Severity::Info, - "The pattern was specified on the CLI using `--include`", - ))), + let include = build_include_filter( + db, + project_root, + self.include.as_ref(), + GlobFilterContext::SrcRoot, + diagnostics, + )?; + let exclude = build_exclude_filter( + db, + project_root, + self.exclude.as_ref(), + DEFAULT_SRC_EXCLUDES, + GlobFilterContext::SrcRoot, + )?; + let files = IncludeExcludeFilter::new(include, exclude); + + Ok(SrcSettings { + respect_ignore_files: self.respect_ignore_files.unwrap_or(true), + files, + }) + } +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize, Hash)] +#[serde(rename_all = "kebab-case", transparent)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct Rules { + #[cfg_attr(feature = "schemars", schemars(with = "schema::Rules"))] + inner: OrderMap, RangedValue, BuildHasherDefault>, +} + +impl FromIterator<(RangedValue, RangedValue)> for Rules { + fn from_iter, RangedValue)>>( + iter: T, + ) -> Self { + Self { + inner: iter.into_iter().collect(), + } + } +} + +impl Rules { + /// Convert the rules to a `RuleSelection` with diagnostics. + pub fn to_rule_selection( + &self, + db: &dyn Db, + diagnostics: &mut Vec, + ) -> RuleSelection { + let registry = db.lint_registry(); + + // Initialize the selection with the defaults + let mut selection = RuleSelection::from_registry(registry); + + for (rule_name, level) in &self.inner { + let source = rule_name.source(); + match registry.get(rule_name) { + Ok(lint) => { + let lint_source = match source { + ValueSource::File(_) => LintSource::File, + ValueSource::Cli => LintSource::Cli, + }; + if let Ok(severity) = Severity::try_from(**level) { + selection.enable(lint, severity, lint_source); + } else { + selection.disable(lint); + } + } + Err(error) => { + // `system_path_to_file` can return `Err` if the file was deleted since the configuration + // was read. This should be rare and it should be okay to default to not showing a configuration + // file in that case. + let file = source + .file() + .and_then(|path| system_path_to_file(db.upcast(), path).ok()); + + // TODO: Add a note if the value was configured on the CLI + let diagnostic = match error { + GetLintError::Unknown(_) => OptionDiagnostic::new( + DiagnosticId::UnknownRule, + format!("Unknown lint rule `{rule_name}`"), + Severity::Warning, + ), + GetLintError::PrefixedWithCategory { suggestion, .. } => { + OptionDiagnostic::new( + DiagnosticId::UnknownRule, + format!( + "Unknown lint rule `{rule_name}`. Did you mean `{suggestion}`?" + ), + Severity::Warning, + ) } - })?; + + GetLintError::Removed(_) => OptionDiagnostic::new( + DiagnosticId::UnknownRule, + format!("Unknown lint rule `{rule_name}`"), + Severity::Warning, + ), + }; + + let annotation = file.map(Span::from).map(|span| { + Annotation::primary(span.with_optional_range(rule_name.range())) + }); + diagnostics.push(diagnostic.with_annotation(annotation)); + } } - } else { - includes - .add( - &PortableGlobPattern::parse("**", false) - .unwrap() - .into_absolute(""), - ) - .unwrap(); } - let include = includes.build().map_err(|_| { - // https://github.com/BurntSushi/ripgrep/discussions/2927 - let diagnostic = OptionDiagnostic::new( - DiagnosticId::InvalidGlob, - "The `src.include` patterns resulted in a regex that is too large".to_string(), - Severity::Error, - ); - diagnostic.sub(Some(SubDiagnostic::new( + selection + } + + pub(super) fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +/// Default exclude patterns for src options. +const DEFAULT_SRC_EXCLUDES: &[&str] = &[ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "dist", + "node_modules", + "venv", +]; + +/// Helper function to build an include filter from patterns with proper error handling. +fn build_include_filter( + db: &dyn Db, + project_root: &SystemPath, + include_patterns: Option<&RangedValue>>, + context: GlobFilterContext, + diagnostics: &mut Vec, +) -> Result> { + use crate::glob::{IncludeFilterBuilder, PortableGlobPattern}; + + let system = db.system(); + let mut includes = IncludeFilterBuilder::new(); + + if let Some(include_patterns) = include_patterns { + if include_patterns.is_empty() { + // An override with an empty include `[]` won't match any files. + let mut diagnostic = OptionDiagnostic::new( + DiagnosticId::EmptyInclude, + "Empty include matches no files".to_string(), + Severity::Warning, + ) + .sub(SubDiagnostic::new( Severity::Info, - "Please open an issue on the ty repository and share the pattern that caused the error.", - ))) - })?; - - let mut excludes = ExcludeFilterBuilder::new(); - - // Add the default excludes first, so that a user can override them with a negated exclude pattern. - for pattern in [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".mypy_cache", - ".nox", - ".pants.d", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - "__pypackages__", - "_build", - "buck-out", - "dist", - "node_modules", - "venv", - ] { - PortableGlobPattern::parse(pattern, true) - .and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?)) - .unwrap_or_else(|err| { - panic!( - "Expected default exclude to be valid glob but adding it failed with: {err}" + "Remove the `include` option to match all files or add a pattern to match specific files", + )); + + // Add source annotation if we have source information + if let Some(source_file) = include_patterns.source().file() { + if let Ok(file) = system_path_to_file(db.upcast(), source_file) { + let annotation = Annotation::primary( + Span::from(file).with_optional_range(include_patterns.range()), ) - }); + .message("This `include` list is empty"); + diagnostic = diagnostic.with_annotation(Some(annotation)); + } + } + + diagnostics.push(diagnostic); + } + + for pattern in include_patterns { + pattern.absolute(project_root, system) + .and_then(|include| Ok(includes.add(&include)?)) + .map_err(|err| { + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + format!("Invalid include pattern `{pattern}`: {err}"), + Severity::Error, + ); + + match pattern.source() { + ValueSource::File(file_path) => { + if let Ok(file) = system_path_to_file(db.upcast(), &**file_path) { + diagnostic + .with_message("Invalid include pattern") + .with_annotation(Some( + Annotation::primary( + Span::from(file) + .with_optional_range(pattern.range()), + ) + .message(err.to_string()), + )) + } else { + diagnostic.sub(SubDiagnostic::new( + Severity::Info, + format!("The pattern is defined in the `{}` option in your configuration file", context.include_name()), + )) + } + } + ValueSource::Cli => diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "The pattern was specified on the CLI", + )), + } + })?; } + } else { + includes + .add( + &PortableGlobPattern::parse("**", false) + .unwrap() + .into_absolute(""), + ) + .unwrap(); + } + + includes.build().map_err(|_| { + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + format!("The `{}` patterns resulted in a regex that is too large", context.include_name()), + Severity::Error, + ); + Box::new(diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "Please open an issue on the ty repository and share the patterns that caused the error.", + ))) + }) +} - for exclude in self.exclude.as_deref().unwrap_or_default() { - // Check the relative path for better error messages. +/// Helper function to build an exclude filter from patterns with proper error handling. +fn build_exclude_filter( + db: &dyn Db, + project_root: &SystemPath, + exclude_patterns: Option<&RangedValue>>, + default_patterns: &[&str], + context: GlobFilterContext, +) -> Result> { + use crate::glob::{ExcludeFilterBuilder, PortableGlobPattern}; + + let system = db.system(); + let mut excludes = ExcludeFilterBuilder::new(); + + for pattern in default_patterns { + PortableGlobPattern::parse(pattern, true) + .and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?)) + .unwrap_or_else(|err| { + panic!("Expected default exclude to be valid glob but adding it failed with: {err}") + }); + } + + // Add user-specified excludes + if let Some(exclude_patterns) = exclude_patterns { + for exclude in exclude_patterns { exclude.absolute(project_root, system) .and_then(|pattern| Ok(excludes.add(&pattern)?)) .map_err(|err| { let diagnostic = OptionDiagnostic::new( DiagnosticId::InvalidGlob, - format!("Invalid exclude pattern: {err}"), + format!("Invalid exclude pattern `{exclude}`: {err}"), Severity::Error, ); @@ -679,54 +839,55 @@ impl SrcOptions { .message(err.to_string()), )) } else { - diagnostic.sub(Some(SubDiagnostic::new( + diagnostic.sub(SubDiagnostic::new( Severity::Info, - "The pattern is defined in the `src.exclude` option in your configuration file", - ))) + format!("The pattern is defined in the `{}` option in your configuration file", context.exclude_name()), + )) } } - ValueSource::Cli => diagnostic.sub(Some(SubDiagnostic::new( + ValueSource::Cli => diagnostic.sub(SubDiagnostic::new( Severity::Info, - "The pattern was specified on the CLI using `--exclude`", - ))), + "The pattern was specified on the CLI", + )), } })?; } - - let exclude = excludes.build().map_err(|_| { - // https://github.com/BurntSushi/ripgrep/discussions/2927 - let diagnostic = OptionDiagnostic::new( - DiagnosticId::InvalidGlob, - "The `src.exclude` patterns resulted in a regex that is too large".to_string(), - Severity::Error, - ); - diagnostic.sub(Some(SubDiagnostic::new( - Severity::Info, - "Please open an issue on the ty repository and share the pattern that caused the error.", - ))) - })?; - - Ok(SrcSettings { - respect_ignore_files: self.respect_ignore_files.unwrap_or(true), - files: IncludeExcludeFilter::new(include, exclude), - }) } + + excludes.build().map_err(|_| { + let diagnostic = OptionDiagnostic::new( + DiagnosticId::InvalidGlob, + format!("The `{}` patterns resulted in a regex that is too large", context.exclude_name()), + Severity::Error, + ); + Box::new(diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "Please open an issue on the ty repository and share the patterns that caused the error.", + ))) + }) } -#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case", transparent)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct Rules { - #[cfg_attr(feature = "schemars", schemars(with = "schema::Rules"))] - inner: FxHashMap, RangedValue>, +/// Context for filter operations, used in error messages +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum GlobFilterContext { + /// Source root configuration context + SrcRoot, + /// Override configuration context + Overrides, } -impl FromIterator<(RangedValue, RangedValue)> for Rules { - fn from_iter, RangedValue)>>( - iter: T, - ) -> Self { - Self { - inner: iter.into_iter().collect(), +impl GlobFilterContext { + fn include_name(self) -> &'static str { + match self { + Self::SrcRoot => "src.include", + Self::Overrides => "overrides.include", + } + } + + fn exclude_name(self) -> &'static str { + match self { + Self::SrcRoot => "src.exclude", + Self::Overrides => "overrides.exclude", } } } @@ -763,6 +924,284 @@ pub struct TerminalOptions { pub error_on_warning: Option, } +/// Configuration override that applies to specific files based on glob patterns. +/// +/// An override allows you to apply different rule configurations to specific +/// files or directories. Multiple overrides can match the same file, with +/// later overrides take precedence. +/// +/// ### Precedence +/// +/// - Later overrides in the array take precedence over earlier ones +/// - Override rules take precedence over global rules for matching files +/// +/// ### Examples +/// +/// ```toml +/// # Relax rules for test files +/// [[tool.ty.overrides]] +/// include = ["tests/**", "**/test_*.py"] +/// +/// [tool.ty.overrides.rules] +/// possibly-unresolved-reference = "warn" +/// +/// # Ignore generated files but still check important ones +/// [[tool.ty.overrides]] +/// include = ["generated/**"] +/// exclude = ["generated/important.py"] +/// +/// [tool.ty.overrides.rules] +/// possibly-unresolved-reference = "ignore" +/// ``` +#[derive(Debug, Default, Clone, PartialEq, Eq, Combine, Serialize, Deserialize, RustDoc)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(transparent)] +pub struct OverridesOptions(Vec>); + +impl OptionsMetadata for OverridesOptions { + fn documentation() -> Option<&'static str> { + Some(::rust_doc()) + } + + fn record(visit: &mut dyn Visit) { + OptionSet::of::().record(visit); + } +} + +impl Deref for OverridesOptions { + type Target = [RangedValue]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive( + Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize, OptionsMetadata, +)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct OverrideOptions { + /// A list of file and directory patterns to include for this override. + /// + /// The `include` option follows a similar syntax to `.gitignore` but reversed: + /// Including a file or directory will make it so that it (and its contents) + /// are affected by this override. + /// + /// If not specified, defaults to `["**"]` (matches all files). + #[serde(skip_serializing_if = "Option::is_none")] + #[option( + default = r#"null"#, + value_type = r#"list[str]"#, + example = r#" + [[tool.ty.overrides]] + include = [ + "src", + "tests", + ] + "# + )] + pub include: Option>>, + + /// A list of file and directory patterns to exclude from this override. + /// + /// Patterns follow a syntax similar to `.gitignore`. + /// Exclude patterns take precedence over include patterns within the same override. + /// + /// If not specified, defaults to `[]` (excludes no files). + #[serde(skip_serializing_if = "Option::is_none")] + #[option( + default = r#"null"#, + value_type = r#"list[str]"#, + example = r#" + [[tool.ty.overrides]] + exclude = [ + "generated", + "*.proto", + "tests/fixtures/**", + "!tests/fixtures/important.py" # Include this one file + ] + "# + )] + pub exclude: Option>>, + + /// Rule overrides for files matching the include/exclude patterns. + /// + /// These rules will be merged with the global rules, with override rules + /// taking precedence for matching files. You can set rules to different + /// severity levels or disable them entirely. + #[serde(skip_serializing_if = "Option::is_none")] + #[option( + default = r#"{...}"#, + value_type = r#"dict[RuleName, "ignore" | "warn" | "error"]"#, + example = r#" + [[tool.ty.overrides]] + include = ["src"] + + [tool.ty.overrides.rules] + possibly-unresolved-reference = "ignore" + "# + )] + pub rules: Option, +} + +impl RangedValue { + fn to_override( + &self, + db: &dyn Db, + project_root: &SystemPath, + global_rules: Option<&Rules>, + diagnostics: &mut Vec, + ) -> Result, Box> { + // First, warn about incorrect or useless overrides. + if self.rules.as_ref().is_none_or(Rules::is_empty) { + let mut diagnostic = OptionDiagnostic::new( + DiagnosticId::UselessOverridesSection, + "Useless `overrides` section".to_string(), + Severity::Warning, + ); + + diagnostic = if self.rules.is_none() { + diagnostic = diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "It has no `rules` table", + )); + diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "Add a `[overrides.rules]` table...", + )) + } else { + diagnostic = diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "The rules table is empty", + )); + diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "Add a rule to `[overrides.rules]` to override specific rules...", + )) + }; + + diagnostic = diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "or remove the `[[overrides]]` section if there's nothing to override", + )); + + // Add source annotation if we have source information + if let Some(source_file) = self.source().file() { + if let Ok(file) = system_path_to_file(db.upcast(), source_file) { + let annotation = + Annotation::primary(Span::from(file).with_optional_range(self.range())) + .message("This overrides section configures no rules"); + diagnostic = diagnostic.with_annotation(Some(annotation)); + } + } + + diagnostics.push(diagnostic); + // Return `None`, because this override doesn't override anything + return Ok(None); + } + + let include_missing = self.include.is_none(); + let exclude_empty = self + .exclude + .as_ref() + .is_none_or(|exclude| exclude.is_empty()); + + if include_missing && exclude_empty { + // Neither include nor exclude specified - applies to all files + let mut diagnostic = OptionDiagnostic::new( + DiagnosticId::UnnecessaryOverridesSection, + "Unnecessary `overrides` section".to_string(), + Severity::Warning, + ); + + diagnostic = if self.exclude.is_none() { + diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "It has no `include` or `exclude` option restricting the files", + )) + } else { + diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "It has no `include` option and `exclude` is empty", + )) + }; + + diagnostic = diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "Restrict the files by adding a pattern to `include` or `exclude`...", + )); + + diagnostic = diagnostic.sub(SubDiagnostic::new( + Severity::Info, + "or remove the `[[overrides]]` section and merge the configuration into the root `[rules]` table if the configuration should apply to all files", + )); + + // Add source annotation if we have source information + if let Some(source_file) = self.source().file() { + if let Ok(file) = system_path_to_file(db.upcast(), source_file) { + let annotation = + Annotation::primary(Span::from(file).with_optional_range(self.range())) + .message("This overrides section applies to all files"); + diagnostic = diagnostic.with_annotation(Some(annotation)); + } + } + + diagnostics.push(diagnostic); + } + + // The override is at least (partially) valid. + // Construct the matcher and resolve the settings. + let include = build_include_filter( + db, + project_root, + self.include.as_ref(), + GlobFilterContext::Overrides, + diagnostics, + )?; + + let exclude = build_exclude_filter( + db, + project_root, + self.exclude.as_ref(), + &[], + GlobFilterContext::Overrides, + )?; + + let files = IncludeExcludeFilter::new(include, exclude); + + // Merge global rules with override rules, with override rules taking precedence + let merged_rules = self + .rules + .clone() + .combine(global_rules.cloned()) + .expect("method to have early returned if rules is None"); + + // Convert merged rules to rule selection + let rule_selection = merged_rules.to_rule_selection(db, diagnostics); + + let override_instance = Override { + files, + options: Arc::new(InnerOverrideOptions { + rules: self.rules.clone(), + }), + settings: Arc::new(OverrideSettings { + rules: rule_selection, + }), + }; + + Ok(Some(override_instance)) + } +} + +/// The options for an override but without the include/exclude patterns. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Combine)] +pub(super) struct InnerOverrideOptions { + /// Raw rule options as specified in the configuration. + /// Used when multiple overrides match a file and need to be merged. + pub(super) rules: Option, +} + /// Error returned when the settings can't be resolved because of a hard error. #[derive(Debug)] pub struct ToSettingsError { @@ -886,7 +1325,7 @@ pub struct OptionDiagnostic { message: String, severity: Severity, annotation: Option, - sub: Option, + sub: Vec, } impl OptionDiagnostic { @@ -896,7 +1335,7 @@ impl OptionDiagnostic { message, severity, annotation: None, - sub: None, + sub: Vec::new(), } } @@ -914,8 +1353,9 @@ impl OptionDiagnostic { } #[must_use] - fn sub(self, sub: Option) -> Self { - OptionDiagnostic { sub, ..self } + fn sub(mut self, sub: SubDiagnostic) -> Self { + self.sub.push(sub); + self } pub(crate) fn to_diagnostic(&self) -> Diagnostic { @@ -923,6 +1363,11 @@ impl OptionDiagnostic { if let Some(annotation) = self.annotation.clone() { diag.annotate(annotation); } + + for sub in &self.sub { + diag.sub(sub.clone()); + } + diag } } diff --git a/crates/ty_project/src/metadata/settings.rs b/crates/ty_project/src/metadata/settings.rs index 156f72632d92c9..1c8424fd9ce676 100644 --- a/crates/ty_project/src/metadata/settings.rs +++ b/crates/ty_project/src/metadata/settings.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use ruff_db::diagnostic::DiagnosticFormat; +use ruff_db::{diagnostic::DiagnosticFormat, files::File}; use ty_python_semantic::lint::RuleSelection; -use crate::glob::IncludeExcludeFilter; +use crate::metadata::options::InnerOverrideOptions; +use crate::{Db, combine::Combine, glob::IncludeExcludeFilter}; /// The resolved [`super::Options`] for the project. /// @@ -23,6 +24,13 @@ pub struct Settings { pub(super) rules: Arc, pub(super) terminal: TerminalSettings, pub(super) src: SrcSettings, + + /// Settings for configuration overrides that apply to specific file patterns. + /// + /// Each override can specify include/exclude patterns and rule configurations + /// that apply to matching files. Multiple overrides can match the same file, + /// with later overrides taking precedence. + pub(super) overrides: Vec, } impl Settings { @@ -41,6 +49,10 @@ impl Settings { pub fn terminal(&self) -> &TerminalSettings { &self.terminal } + + pub fn overrides(&self) -> &[Override] { + &self.overrides + } } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -54,3 +66,138 @@ pub struct SrcSettings { pub respect_ignore_files: bool, pub files: IncludeExcludeFilter, } + +/// A single configuration override that applies to files matching specific patterns. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Override { + /// File pattern filter to determine which files this override applies to. + pub(super) files: IncludeExcludeFilter, + + /// The raw options as specified in the configuration (minus `include` and `exclude`. + /// Necessary to merge multiple overrides if necessary. + pub(super) options: Arc, + + /// Pre-resolved rule selection for this override alone. + /// Used for efficient lookup when only this override matches a file. + pub(super) settings: Arc, +} + +impl Override { + /// Returns whether this override applies to the given file path. + pub fn matches_file(&self, path: &ruff_db::system::SystemPath) -> bool { + use crate::glob::{GlobFilterCheckMode, IncludeResult}; + + matches!( + self.files + .is_file_included(path, GlobFilterCheckMode::Adhoc), + IncludeResult::Included + ) + } +} + +/// Resolves the settings for a given file. +#[salsa::tracked(returns(ref))] +pub(crate) fn file_settings(db: &dyn Db, file: File) -> FileSettings { + let settings = db.project().settings(db); + + let path = match file.path(db) { + ruff_db::files::FilePath::System(path) => path, + ruff_db::files::FilePath::SystemVirtual(_) | ruff_db::files::FilePath::Vendored(_) => { + return FileSettings::Global; + } + }; + + let mut matching_overrides = settings + .overrides() + .iter() + .filter(|over| over.matches_file(path)); + + let Some(first) = matching_overrides.next() else { + // If the file matches no override, it uses the global settings. + return FileSettings::Global; + }; + + let Some(second) = matching_overrides.next() else { + tracing::debug!("Applying override for file `{path}`: {}", first.files); + // If the file matches only one override, return that override's settings. + return FileSettings::File(Arc::clone(&first.settings)); + }; + + let mut filters = tracing::enabled!(tracing::Level::DEBUG) + .then(|| format!("({}), ({})", first.files, second.files)); + + let mut overrides = vec![Arc::clone(&first.options), Arc::clone(&second.options)]; + + for over in matching_overrides { + use std::fmt::Write; + + if let Some(filters) = &mut filters { + let _ = write!(filters, ", ({})", over.files); + } + + overrides.push(Arc::clone(&over.options)); + } + + if let Some(filters) = &filters { + tracing::debug!("Applying multiple overrides for file `{path}`: {filters}"); + } + + merge_overrides(db, overrides, ()) +} + +/// Merges multiple override options, caching the result. +/// +/// Overrides often apply to multiple files. This query ensures that we avoid +/// resolving the same override combinations multiple times. +/// +/// ## What's up with the `()` argument? +/// +/// This is to make Salsa happy because it requires that queries with only a single argument +/// take a salsa-struct as argument, which isn't the case here. The `()` enables salsa's +/// automatic interning for the arguments. +#[salsa::tracked] +fn merge_overrides(db: &dyn Db, overrides: Vec>, _: ()) -> FileSettings { + let mut overrides = overrides.into_iter().rev(); + let mut merged = (*overrides.next().unwrap()).clone(); + + for option in overrides { + merged.combine_with((*option).clone()); + } + + merged + .rules + .combine_with(db.project().metadata(db).options().rules.clone()); + + let Some(rules) = merged.rules else { + return FileSettings::Global; + }; + + // It's okay to ignore the errors here because the rules are eagerly validated + // during `overrides.to_settings()`. + let rules = rules.to_rule_selection(db, &mut Vec::new()); + FileSettings::File(Arc::new(OverrideSettings { rules })) +} + +/// The resolved settings for a file. +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum FileSettings { + /// The file uses the global settings. + Global, + + /// The file has specific override settings. + File(Arc), +} + +impl FileSettings { + pub fn rules<'a>(&'a self, db: &'a dyn Db) -> &'a RuleSelection { + match self { + FileSettings::Global => db.project().settings(db).rules(), + FileSettings::File(override_settings) => &override_settings.rules, + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct OverrideSettings { + pub(super) rules: RuleSelection, +} diff --git a/crates/ty_project/src/metadata/value.rs b/crates/ty_project/src/metadata/value.rs index 8e19e6b402f3cc..5d38185e134ef8 100644 --- a/crates/ty_project/src/metadata/value.rs +++ b/crates/ty_project/src/metadata/value.rs @@ -407,6 +407,12 @@ impl RelativeIncludePattern { } } +impl std::fmt::Display for RelativeIncludePattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + #[derive( Debug, Clone, @@ -456,3 +462,9 @@ impl RelativeExcludePattern { Ok(pattern.into_absolute(relative_to)) } } + +impl std::fmt::Display for RelativeExcludePattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} diff --git a/crates/ty_python_semantic/src/db.rs b/crates/ty_python_semantic/src/db.rs index 11c6e2922072ac..9e37ed3ca93562 100644 --- a/crates/ty_python_semantic/src/db.rs +++ b/crates/ty_python_semantic/src/db.rs @@ -7,7 +7,8 @@ use ruff_db::{Db as SourceDb, Upcast}; pub trait Db: SourceDb + Upcast { fn is_file_open(&self, file: File) -> bool; - fn rule_selection(&self) -> &RuleSelection; + /// Resolves the rule selection for a given file. + fn rule_selection(&self, file: File) -> &RuleSelection; fn lint_registry(&self) -> &LintRegistry; } @@ -126,7 +127,7 @@ pub(crate) mod tests { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/crates/ty_python_semantic/src/lint.rs b/crates/ty_python_semantic/src/lint.rs index 2acaa24b0f22b6..247bae7d787b32 100644 --- a/crates/ty_python_semantic/src/lint.rs +++ b/crates/ty_python_semantic/src/lint.rs @@ -32,7 +32,7 @@ pub struct LintMetadata { pub line: u32, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/ty_python_semantic/src/python_platform.rs b/crates/ty_python_semantic/src/python_platform.rs index 587f7779737a0d..0822165322faa7 100644 --- a/crates/ty_python_semantic/src/python_platform.rs +++ b/crates/ty_python_semantic/src/python_platform.rs @@ -1,10 +1,12 @@ use std::fmt::{Display, Formatter}; +use ruff_macros::RustDoc; + /// The target platform to assume when resolving types. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr( feature = "serde", - derive(serde::Serialize, serde::Deserialize), + derive(serde::Serialize, serde::Deserialize, RustDoc), serde(rename_all = "kebab-case") )] pub enum PythonPlatform { @@ -57,6 +59,7 @@ impl Default for PythonPlatform { #[cfg(feature = "schemars")] mod schema { use crate::PythonPlatform; + use ruff_db::RustDoc; use schemars::_serde_json::Value; use schemars::JsonSchema; use schemars::r#gen::SchemaGenerator; @@ -121,6 +124,10 @@ mod schema { ..SubschemaValidation::default() })), + metadata: Some(Box::new(Metadata { + description: Some(::rust_doc().to_string()), + ..Metadata::default() + })), ..SchemaObject::default() }) diff --git a/crates/ty_python_semantic/src/suppression.rs b/crates/ty_python_semantic/src/suppression.rs index 26518ec7fad64c..b5103f6581719a 100644 --- a/crates/ty_python_semantic/src/suppression.rs +++ b/crates/ty_python_semantic/src/suppression.rs @@ -290,7 +290,10 @@ impl<'a> CheckSuppressionsContext<'a> { } fn is_lint_disabled(&self, lint: &'static LintMetadata) -> bool { - !self.db.rule_selection().is_enabled(LintId::of(lint)) + !self + .db + .rule_selection(self.file) + .is_enabled(LintId::of(lint)) } fn report_lint( @@ -315,7 +318,7 @@ impl<'a> CheckSuppressionsContext<'a> { range: TextRange, message: fmt::Arguments, ) { - let Some(severity) = self.db.rule_selection().severity(LintId::of(lint)) else { + let Some(severity) = self.db.rule_selection(self.file).severity(LintId::of(lint)) else { return; }; diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index f36b19873a7ca1..f2cf03fac926fc 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -401,7 +401,7 @@ impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { let lint_id = LintId::of(lint); // Skip over diagnostics if the rule // is disabled. - let (severity, source) = ctx.db.rule_selection().get(lint_id)?; + let (severity, source) = ctx.db.rule_selection(ctx.file).get(lint_id)?; // If we're not in type checking mode, // we can bail now. if ctx.is_in_no_type_check() { diff --git a/crates/ty_python_semantic/tests/corpus.rs b/crates/ty_python_semantic/tests/corpus.rs index 0a9222a3d6f60b..c3c9b4297e970d 100644 --- a/crates/ty_python_semantic/tests/corpus.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -260,7 +260,7 @@ impl ty_python_semantic::Db for CorpusDb { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/crates/ty_test/src/db.rs b/crates/ty_test/src/db.rs index 348d61aa047177..5574dd42d32777 100644 --- a/crates/ty_test/src/db.rs +++ b/crates/ty_test/src/db.rs @@ -90,7 +90,7 @@ impl SemanticDb for Db { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs index 5635d75c3028fc..974a5ae0a99cee 100644 --- a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs +++ b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs @@ -18,8 +18,8 @@ use ruff_python_parser::{Mode, ParseOptions, parse_unchecked}; use ty_python_semantic::lint::LintRegistry; use ty_python_semantic::types::check_types; use ty_python_semantic::{ - Db as SemanticDb, Program, ProgramSettings, PythonPlatform, SearchPathSettings, - default_lint_registry, lint::RuleSelection, PythonVersionWithSource, + Db as SemanticDb, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + SearchPathSettings, default_lint_registry, lint::RuleSelection, }; /// Database that can be used for testing. @@ -95,7 +95,7 @@ impl SemanticDb for TestDb { !file.path(self).is_vendored_path() } - fn rule_selection(&self) -> &RuleSelection { + fn rule_selection(&self, _file: File) -> &RuleSelection { &self.rule_selection } diff --git a/ty.schema.json b/ty.schema.json index cfef1c1aef29a6..7485084b3865ec 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -14,6 +14,16 @@ } ] }, + "overrides": { + "description": "Override configurations for specific file patterns.\n\nEach override specifies include/exclude patterns and rule configurations that apply to matching files. Multiple overrides can match the same file, with later overrides taking precedence.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/OverrideOptions" + } + }, "rules": { "description": "Configures the enabled rules and their severity.\n\nSee [the rules documentation](https://ty.dev/rules) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule. * `warn`: Enable the rule and create a warning diagnostic. * `error`: Enable the rule and create an error diagnostic. ty will exit with a non-zero code if any error diagnostics are emitted.", "anyOf": [ @@ -147,7 +157,45 @@ } ] }, + "OverrideOptions": { + "type": "object", + "properties": { + "exclude": { + "description": "A list of file and directory patterns to exclude from this override.\n\nPatterns follow a syntax similar to `.gitignore`. Exclude patterns take precedence over include patterns within the same override.\n\nIf not specified, defaults to `[]` (excludes no files).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "include": { + "description": "A list of file and directory patterns to include for this override.\n\nThe `include` option follows a similar syntax to `.gitignore` but reversed: Including a file or directory will make it so that it (and its contents) are affected by this override.\n\nIf not specified, defaults to `[\"**\"]` (matches all files).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "rules": { + "description": "Rule overrides for files matching the include/exclude patterns.\n\nThese rules will be merged with the global rules, with override rules taking precedence for matching files. You can set rules to different severity levels or disable them entirely.", + "anyOf": [ + { + "$ref": "#/definitions/Rules" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "PythonPlatform": { + "description": "The target platform to assume when resolving types.\n", "anyOf": [ { "type": "string" @@ -882,7 +930,7 @@ } }, "include": { - "description": "A list of files and directories to check. The `include` option follows a similar syntax to `.gitignore` but reversed: Including a file or directory will make it so that it (and its contents) are type checked.\n\n- `./src/` matches only a directory - `./src` matches both files and directories - `src` matches files or directories named `src` anywhere in the tree (e.g. `./src` or `./tests/src`) - `*` matches any (possibly empty) sequence of characters (except `/`). - `**` matches zero or more path components. This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. A sequence of more than two consecutive `*` characters is also invalid. - `?` matches any single character except `/` - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.\n\nUnlike `exclude`, all paths are anchored relative to the project root (`src` only matches `/src` and not `/test/src`).\n\n`exclude` take precedence over `include`.", + "description": "A list of files and directories to check. The `include` option follows a similar syntax to `.gitignore` but reversed: Including a file or directory will make it so that it (and its contents) are type checked.\n\n- `./src/` matches only a directory - `./src` matches both files and directories - `src` matches a file or directory named `src` - `*` matches any (possibly empty) sequence of characters (except `/`). - `**` matches zero or more path components. This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error. A sequence of more than two consecutive `*` characters is also invalid. - `?` matches any single character except `/` - `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.\n\nUnlike `exclude`, all paths are anchored relative to the project root (`src` only matches `/src` and not `/test/src`).\n\n`exclude` takes precedence over `include`.", "type": [ "array", "null" From 37b2de90f814ca5a4ca866ebce4e48a65427cfa1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:01:54 +0200 Subject: [PATCH 420/487] Update to unicode 16 (#18700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ff33789d01b3c..8842ff4c8c25e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,7 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -112,7 +112,7 @@ dependencies = [ "anstyle", "anstyle-lossy", "html-escape", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -528,7 +528,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "windows-sys 0.59.0", ] @@ -1361,7 +1361,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "vt100", "web-time", ] @@ -2074,7 +2074,7 @@ checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c" dependencies = [ "once_cell", "serde", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "unscanny", "version-ranges", ] @@ -2095,7 +2095,7 @@ dependencies = [ "serde", "smallvec", "thiserror 1.0.69", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "url", "urlencoding", "version-ranges", @@ -2579,7 +2579,7 @@ dependencies = [ "snapbox", "toml", "tryfn", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -2715,7 +2715,7 @@ dependencies = [ "serde", "static_assertions", "tracing", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -2804,7 +2804,7 @@ dependencies = [ "toml", "typed-arena", "unicode-normalization", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "unicode_names2", "url", ] @@ -4202,9 +4202,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode_names2" From 0c18a5a7374f02896d837b91a9836caa59c0be20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:02:16 +0200 Subject: [PATCH 421/487] Update Rust crate pyproject-toml to v0.13.5 (#18697) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8842ff4c8c25e1..511f02e26c76b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2283,15 +2283,15 @@ dependencies = [ [[package]] name = "pyproject-toml" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643af57c3f36ba90a8b53e972727d8092f7408a9ebfbaf4c3d2c17b07c58d835" +checksum = "7b0f6160dc48298b9260d9b958ad1d7f96f6cd0b9df200b22329204e09334663" dependencies = [ "indexmap", "pep440_rs", "pep508_rs", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", "toml", ] From 4cdf128748f984718db39eb28f0e444e8a1ada0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:06:34 +0200 Subject: [PATCH 422/487] Update Rust crate toml to v0.8.23 (#18699) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 511f02e26c76b0..59efc660820ee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3354,9 +3354,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -3751,9 +3751,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -3763,18 +3763,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -3786,9 +3786,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tracing" From ccae65630a5ab5d469722d0afaf9c7745b07b7cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:06:48 +0200 Subject: [PATCH 423/487] Update Rust crate syn to v2.0.103 (#18698) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59efc660820ee1..963cc2515472ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3526,9 +3526,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", From 32a0d4bb2169558f557df6c9bde339b3c5e6aa77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:08:34 +0200 Subject: [PATCH 424/487] Update Rust crate libc to v0.2.173 (#18694) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 963cc2515472ae..dbccafa01f3689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,9 +1606,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libcst" From b38115ba959e1b898c6b8f2a71400cb471895e8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:08:57 +0200 Subject: [PATCH 425/487] Update Rust crate jiff to v0.2.15 (#18693) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbccafa01f3689..b4e843e56821e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,7 +492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -501,7 +501,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -915,7 +915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1459,7 +1459,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.1", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1513,9 +1513,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1523,14 +1523,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -3187,7 +3187,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3556,7 +3556,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4561,7 +4561,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] From 9b927265f98c5b58e8d8b70de077a4825caee39f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:09:18 +0200 Subject: [PATCH 426/487] Update Rust crate libcst to v1.8.2 (#18695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4e843e56821e8..c50a1f01b39041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1612,9 +1612,9 @@ checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libcst" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac076e37f8fe6bcddbb6c3282897e6e9498b254907ccbfc806dc8f9f1491f02" +checksum = "ae28ddc5b90c3e3146a21d051ca095cbc8d932ad8714cf65ddf71a9abb35684c" dependencies = [ "annotate-snippets", "libcst_derive", @@ -1627,9 +1627,9 @@ dependencies = [ [[package]] name = "libcst_derive" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf4a12c744a301b216c4f0cb73542709ab15e6dadbb06966ac05864109d05da" +checksum = "dc2de5c2f62bcf8a4f7290b1854388b262c4b68f1db1a3ee3ef6d4c1319b00a3" dependencies = [ "quote", "syn", From 5383bcc49757f020024b3f7506bc96fda3d1e318 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:09:33 +0200 Subject: [PATCH 427/487] Update Rust crate clap to v4.5.40 (#18692) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c50a1f01b39041..c8febfce5afe38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", From cb2ae8d9ac285bb864b13d21369a05c61ed819b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:10:19 +0200 Subject: [PATCH 428/487] Update dependency react-resizable-panels to v3.0.3 (#18691) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- playground/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/playground/package-lock.json b/playground/package-lock.json index c140b0524c105c..2de3477265fa55 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -5279,9 +5279,9 @@ "license": "MIT" }, "node_modules/react-resizable-panels": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.2.tgz", - "integrity": "sha512-j4RNII75fnHkLnbsTb5G5YsDvJsSEZrJK2XSF2z0Tc2jIonYlIVir/Yh/5LvcUFCfs1HqrMAoiBFmIrRjC4XnA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.3.tgz", + "integrity": "sha512-7HA8THVBHTzhDK4ON0tvlGXyMAJN1zBeRpuyyremSikgYh2ku6ltD7tsGQOcXx4NKPrZtYCm/5CBr+dkruTGQw==", "license": "MIT", "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -6477,7 +6477,7 @@ "monaco-editor": "^0.52.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-resizable-panels": "^3.0.0", + "react-resizable-panels": "^3.0.3", "ruff_wasm": "file:ruff_wasm", "shared": "0.0.0", "smol-toml": "^1.3.0" @@ -6493,7 +6493,7 @@ "@monaco-editor/react": "^4.7.0", "classnames": "^2.3.2", "react": "^19.0.0", - "react-resizable-panels": "^3.0.0" + "react-resizable-panels": "^3.0.3" } }, "ty": { @@ -6504,10 +6504,10 @@ "classnames": "^2.5.1", "lz-string": "^1.5.0", "monaco-editor": "^0.52.2", - "pyodide": "^0.27.7", + "pyodide": "^0.27.4", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-resizable-panels": "^3.0.0", + "react-resizable-panels": "^3.0.3", "shared": "0.0.0", "smol-toml": "^1.3.1", "ty_wasm": "file:ty_wasm" From d715c1fef8be510bba377d9dddf19cdd68cf100a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:10:43 +0200 Subject: [PATCH 429/487] Update Rust crate memchr to v2.7.5 (#18696) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8febfce5afe38..892dd3afd55623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1753,9 +1753,9 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" From 2f3bd2490026dae4a1b9239f0e5aeadd80e9084d Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 16 Jun 2025 09:47:52 +0200 Subject: [PATCH 430/487] [ty] Correctly label typeshed-sync PRs (#18702) ## Summary Ref: https://github.com/astral-sh/ruff/pull/18679#issuecomment-2973593785 --------- Co-authored-by: Alex Waygood --- .github/workflows/sync_typeshed.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_typeshed.yaml b/.github/workflows/sync_typeshed.yaml index cbdb4a0df37cd7..c384d8868aadad 100644 --- a/.github/workflows/sync_typeshed.yaml +++ b/.github/workflows/sync_typeshed.yaml @@ -60,7 +60,7 @@ jobs: cd ruff git push --force origin typeshedbot/sync-typeshed gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr - gh pr create --title "Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "internal" + gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty" create-issue-on-failure: name: Create an issue if the typeshed sync failed From 869d7bf9a8c037a51e5ac319ee2286987bc54d76 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 16 Jun 2025 07:44:08 -0400 Subject: [PATCH 431/487] [ty] Stabilize completions (#18650) Specifically, this PR reverts "Make completions an opt-in LSP feature (#17921)", corresponding to commit 51e2effd2d6b6cf61bd245a3219168fe661d1a7d. In practice, this means you don't need to opt into completions working by enabling experimental features. i.e., I was able to remove this from my LSP configuration: ``` "experimental": { "completions": { "enable": true } }, ``` There's still a lot of work left to do to make completions awesome, but I think it's in a state where it would be useful to get real user feedback. It's also meaningfully using ty to provide completions that use type information. Ref astral-sh/ty#86 --- crates/ty_ide/src/completion.rs | 1 - crates/ty_server/src/server.rs | 20 +++++++------------- crates/ty_server/src/session.rs | 1 - crates/ty_server/src/session/settings.rs | 24 ------------------------ 4 files changed, 7 insertions(+), 39 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 40064ceaccdc26..a8b93080b4f759 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -9,7 +9,6 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Db; use crate::find_node::covering_node; -#[derive(Debug, Clone)] pub struct Completion { pub label: String, } diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 4846e0219c5ff5..1d1f78293de1c8 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -2,7 +2,7 @@ use self::schedule::spawn_main_loop; use crate::PositionEncoding; -use crate::session::{AllSettings, ClientSettings, Experimental, Session}; +use crate::session::{AllSettings, ClientSettings, Session}; use lsp_server::Connection; use lsp_types::{ ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability, @@ -53,8 +53,7 @@ impl Server { let client_capabilities = init_params.capabilities; let position_encoding = Self::find_best_position_encoding(&client_capabilities); - let server_capabilities = - Self::server_capabilities(position_encoding, global_settings.experimental.as_ref()); + let server_capabilities = Self::server_capabilities(position_encoding); let connection = connection.initialize_finish( id, @@ -154,10 +153,7 @@ impl Server { .unwrap_or_default() } - fn server_capabilities( - position_encoding: PositionEncoding, - experimental: Option<&Experimental>, - ) -> ServerCapabilities { + fn server_capabilities(position_encoding: PositionEncoding) -> ServerCapabilities { ServerCapabilities { position_encoding: Some(position_encoding.into()), diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions { @@ -177,12 +173,10 @@ impl Server { inlay_hint_provider: Some(lsp_types::OneOf::Right( InlayHintServerCapabilities::Options(InlayHintOptions::default()), )), - completion_provider: experimental - .is_some_and(Experimental::is_completions_enabled) - .then_some(lsp_types::CompletionOptions { - trigger_characters: Some(vec!['.'.to_string()]), - ..Default::default() - }), + completion_provider: Some(lsp_types::CompletionOptions { + trigger_characters: Some(vec!['.'.to_string()]), + ..Default::default() + }), ..Default::default() } } diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index f488ddecc987f1..f76138c13990c8 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -16,7 +16,6 @@ pub(crate) use self::capabilities::ResolvedClientCapabilities; pub use self::index::DocumentQuery; pub(crate) use self::settings::AllSettings; pub use self::settings::ClientSettings; -pub(crate) use self::settings::Experimental; use crate::document::{DocumentKey, DocumentVersion, NotebookDocument}; use crate::session::request_queue::RequestQueue; use crate::system::{AnySystemPath, LSPSystem}; diff --git a/crates/ty_server/src/session/settings.rs b/crates/ty_server/src/session/settings.rs index bbf2363f4afd66..ad33e15cca3bb6 100644 --- a/crates/ty_server/src/session/settings.rs +++ b/crates/ty_server/src/session/settings.rs @@ -7,35 +7,11 @@ use serde::Deserialize; /// Maps a workspace URI to its associated client settings. Used during server initialization. pub(crate) type WorkspaceSettingsMap = FxHashMap; -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct Completions { - enable: Option, -} - -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -pub(crate) struct Experimental { - completions: Option, -} - -impl Experimental { - /// Returns `true` if completions are enabled in the settings. - pub(crate) fn is_completions_enabled(&self) -> bool { - self.completions - .as_ref() - .is_some_and(|completions| completions.enable.unwrap_or_default()) - } -} - /// This is a direct representation of the settings schema sent by the client. #[derive(Debug, Deserialize, Default)] #[cfg_attr(test, derive(PartialEq, Eq))] #[serde(rename_all = "camelCase")] pub struct ClientSettings { - pub(crate) experimental: Option, // These settings are only needed for tracing, and are only read from the global configuration. // These will not be in the resolved settings. #[serde(flatten)] From ee3152dace3db003a941f4a6e7e19930f5b0d1f5 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:41:43 -0400 Subject: [PATCH 432/487] Drop confusing second `*` from glob pattern example (#18709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary -- As @AlexWaygood noted on the 0.12 release blog post draft, the existing example is a bit confusing. Either `**/*.py` or just `*.py`, as I went with here, makes more sense, although the old version (`scripts/**.py`) also worked when I tested it. However, this probably shouldn't be relied upon since the [globset](https://docs.rs/globset/latest/globset/#syntax) docs say: > Using ** anywhere else is illegal where "anywhere else" comes after the listing of the three valid positions: 1. At the start of a pattern (`**/`) 2. At the end of a pattern (`/**`) 3. Or directly between two slashes (`/**/`) I think the current version is luckily treated the same as a single `*`, and the default globbing settings allow it to match subdirectories such that the new example pattern will apply to the whole `scripts` tree in a project like this: ``` . ├── README.md ├── pyproject.toml ├── scripts │   ├── matching.py │   └── sub │   └── nested.py └── src └── main.py ``` Test Plan -- Local testing of the new pattern, but the specifics of the pattern aren't as important as having a more intuitive-looking/correct example. --- crates/ruff_workspace/src/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index b68fc08ea96829..8c6b630f28c8e5 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -353,7 +353,7 @@ pub struct Options { scope = "per-file-target-version", example = r#" # Override the project-wide Python version for a developer scripts directory: - "scripts/**.py" = "py312" + "scripts/*.py" = "py312" "# )] pub per_file_target_version: Option>, From a84289986242d57424103737c3f8152f88d61cd8 Mon Sep 17 00:00:00 2001 From: Juriah Date: Mon, 16 Jun 2025 17:47:17 +0300 Subject: [PATCH 433/487] [`flake8-pyi`] Fix `custom-typevar-for-self` with string annotations (`PYI019`) (#18311) ## Summary Solves #18257 ## Test Plan Snapshots updated with some cases (negative, positive, mixed annotations). --- .../test/fixtures/flake8_pyi/PYI019_0.py | 31 +++++++ .../test/fixtures/flake8_pyi/PYI019_0.pyi | 33 +++++++ .../rules/custom_type_var_for_self.rs | 13 ++- ...flake8_pyi__tests__PYI019_PYI019_0.py.snap | 93 +++++++++++++++++++ ...lake8_pyi__tests__PYI019_PYI019_0.pyi.snap | 93 +++++++++++++++++++ 5 files changed, 261 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.py index e502d8fc1f79d3..0762e9f15c486d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.py @@ -181,3 +181,34 @@ def m[S](self: S) -> S: class MetaTestClass(type): def m(cls: MetaType) -> MetaType: return cls + + +from __future__ import annotations + +class BadClassWithStringTypeHints: + def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + + @classmethod + def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + + + @classmethod + def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + + + @classmethod + def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + + +class BadSubscriptReturnTypeWithStringTypeHints: + @classmethod + def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + + +class GoodClassWiStringTypeHints: + @classmethod + def good_cls_method_with_mixed_annotations(cls: "type[Self]", arg: str) -> Self: ... + @staticmethod + def good_static_method_with_string_annotations(arg: "_S") -> "_S": ... + @classmethod + def good_class_method_with_args_string_annotations(cls, arg1: "_S", arg2: "_S") -> "_S": ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.pyi index 1f2f17fc1b3031..bffdb13dbb5588 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019_0.pyi @@ -172,3 +172,36 @@ MetaType = TypeVar("MetaType") class MetaTestClass(type): def m(cls: MetaType) -> MetaType: return cls + + +from __future__ import annotations + + +class BadClassWithStringTypeHints: + def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + + @classmethod + def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + + + @classmethod + def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + + + @classmethod + def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + + +class BadSubscriptReturnTypeWithStringTypeHints: + @classmethod + def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + + +class GoodClassWithStringTypeHints: + @classmethod + def good_cls_method_with_mixed_annotations(cls: "type[Self]", arg: str) -> Self: ... + @staticmethod + def good_static_method_with_string_annotations(arg: "_S") -> "_S": ... + @classmethod + def good_class_method_with_args_string_annotations(cls, arg1: "_S", arg2: "_S") -> "_S": ... + diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs index d2a8d8527a5310..91a8a9f140080b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/custom_type_var_for_self.rs @@ -142,9 +142,18 @@ pub(crate) fn custom_type_var_instead_of_self(checker: &Checker, binding: &Bindi return; }; - let Some(self_or_cls_annotation) = self_or_cls_parameter.annotation() else { + let Some(self_or_cls_annotation_unchecked) = self_or_cls_parameter.annotation() else { return; }; + let self_or_cls_annotation = match self_or_cls_annotation_unchecked { + ast::Expr::StringLiteral(literal_expr) => { + let Ok(parsed_expr) = checker.parse_type_annotation(literal_expr) else { + return; + }; + parsed_expr.expression() + } + _ => self_or_cls_annotation_unchecked, + }; let Some(parent_class) = current_scope.kind.as_class() else { return; }; @@ -202,7 +211,7 @@ pub(crate) fn custom_type_var_instead_of_self(checker: &Checker, binding: &Bindi function_def, custom_typevar, self_or_cls_parameter, - self_or_cls_annotation, + self_or_cls_annotation_unchecked, ) }); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap index 1bbfbef8e2c361..4071c9595fbb82 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.py.snap @@ -690,3 +690,96 @@ PYI019_0.py:173:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` 174 174 | type S = int 175 175 | print(S) # not a reference to the type variable, so not touched by the autofix 176 176 | return 42 + +PYI019_0.py:189:52: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +188 | class BadClassWithStringTypeHints: +189 | def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +190 | +191 | @classmethod + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +186 186 | from __future__ import annotations +187 187 | +188 188 | class BadClassWithStringTypeHints: +189 |- def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + 189 |+ def bad_instance_method_with_string_annotations(self, arg: str) -> "Self": ... # PYI019 +190 190 | +191 191 | @classmethod +192 192 | def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + +PYI019_0.py:192:49: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +191 | @classmethod +192 | def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +189 189 | def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 +190 190 | +191 191 | @classmethod +192 |- def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + 192 |+ def bad_class_method_with_string_annotations(cls) -> "Self": ... # PYI019 +193 193 | +194 194 | +195 195 | @classmethod + +PYI019_0.py:196:50: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +195 | @classmethod +196 | def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +193 193 | +194 194 | +195 195 | @classmethod +196 |- def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + 196 |+ def bad_class_method_with_mixed_annotations_1(cls) -> Self: ... # PYI019 +197 197 | +198 198 | +199 199 | @classmethod + +PYI019_0.py:200:50: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +199 | @classmethod +200 | def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +197 197 | +198 198 | +199 199 | @classmethod +200 |- def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + 200 |+ def bad_class_method_with_mixed_annotations_1(cls) -> "Self": ... # PYI019 +201 201 | +202 202 | +203 203 | class BadSubscriptReturnTypeWithStringTypeHints: + +PYI019_0.py:205:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +203 | class BadSubscriptReturnTypeWithStringTypeHints: +204 | @classmethod +205 | def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +202 202 | +203 203 | class BadSubscriptReturnTypeWithStringTypeHints: +204 204 | @classmethod +205 |- def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + 205 |+ def m(cls) -> "type[Self]": ... # PYI019 +206 206 | +207 207 | +208 208 | class GoodClassWiStringTypeHints: diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap index 7c5b794bd56218..9b4647f3fe363a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI019_PYI019_0.pyi.snap @@ -682,3 +682,96 @@ PYI019_0.pyi:167:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` 168 168 | 169 169 | 170 170 | MetaType = TypeVar("MetaType") + +PYI019_0.pyi:181:52: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +180 | class BadClassWithStringTypeHints: +181 | def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 +182 | +183 | @classmethod + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +178 178 | +179 179 | +180 180 | class BadClassWithStringTypeHints: +181 |- def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 + 181 |+ def bad_instance_method_with_string_annotations(self, arg: str) -> "Self": ... # PYI019 +182 182 | +183 183 | @classmethod +184 184 | def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + +PYI019_0.pyi:184:49: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +183 | @classmethod +184 | def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +181 181 | def bad_instance_method_with_string_annotations(self: "_S", arg: str) -> "_S": ... # PYI019 +182 182 | +183 183 | @classmethod +184 |- def bad_class_method_with_string_annotations(cls: "type[_S]") -> "_S": ... # PYI019 + 184 |+ def bad_class_method_with_string_annotations(cls) -> "Self": ... # PYI019 +185 185 | +186 186 | +187 187 | @classmethod + +PYI019_0.pyi:188:50: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +187 | @classmethod +188 | def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +185 185 | +186 186 | +187 187 | @classmethod +188 |- def bad_class_method_with_mixed_annotations_1(cls: "type[_S]") -> _S: ... # PYI019 + 188 |+ def bad_class_method_with_mixed_annotations_1(cls) -> Self: ... # PYI019 +189 189 | +190 190 | +191 191 | @classmethod + +PYI019_0.pyi:192:50: PYI019 [*] Use `Self` instead of custom TypeVar `_S` + | +191 | @classmethod +192 | def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `_S` with `Self` + +ℹ Safe fix +189 189 | +190 190 | +191 191 | @classmethod +192 |- def bad_class_method_with_mixed_annotations_1(cls: type[_S]) -> "_S": ... # PYI019 + 192 |+ def bad_class_method_with_mixed_annotations_1(cls) -> "Self": ... # PYI019 +193 193 | +194 194 | +195 195 | class BadSubscriptReturnTypeWithStringTypeHints: + +PYI019_0.pyi:197:10: PYI019 [*] Use `Self` instead of custom TypeVar `S` + | +195 | class BadSubscriptReturnTypeWithStringTypeHints: +196 | @classmethod +197 | def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI019 + | + = help: Replace TypeVar `S` with `Self` + +ℹ Safe fix +194 194 | +195 195 | class BadSubscriptReturnTypeWithStringTypeHints: +196 196 | @classmethod +197 |- def m[S](cls: "type[S]") -> "type[S]": ... # PYI019 + 197 |+ def m(cls) -> "type[Self]": ... # PYI019 +198 198 | +199 199 | +200 200 | class GoodClassWithStringTypeHints: From c5b58187da1809c778d886595ec50f7db68fd3b9 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 16 Jun 2025 11:44:42 -0500 Subject: [PATCH 434/487] Add syntax error when conversion flag does not immediately follow exclamation mark (#18706) Closes #18671 Note that while this has, I believe, always been invalid syntax, it was reported as a different syntax error until Python 3.12: Python 3.11: ```pycon >>> x = 1 >>> f"{x! s}" File "", line 1 f"{x! s}" ^ SyntaxError: f-string: invalid conversion character: expected 's', 'r', or 'a' ``` Python 3.12: ```pycon >>> x = 1 >>> f"{x! s}" File "", line 1 f"{x! s}" ^^^ SyntaxError: f-string: conversion type must come right after the exclamanation mark ``` --- .../test/fixtures/ruff/expression/fstring.py | 8 +- .../test/fixtures/ruff/expression/tstring.py | 8 +- .../format@expression__fstring.py.snap | 16 +- .../format@expression__tstring.py.snap | 12 +- ...f_string_conversion_follows_exclamation.py | 3 + crates/ruff_python_parser/src/error.rs | 11 +- .../src/parser/expression.rs | 13 ++ ...ing_conversion_follows_exclamation.py.snap | 186 ++++++++++++++++++ 8 files changed, 219 insertions(+), 38 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_conversion_follows_exclamation.py.snap diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py index d3a19d24e82389..f70290c01d3392 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py @@ -267,17 +267,13 @@ # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = f"aaaaaaaaa { x ! r }" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. -x = f"aaaaaaaaa { x = ! r }" +x = f"aaaaaaaaa { x = !r }" # Combine conversion flags with format specifiers -x = f"{x = ! s +x = f"{x = !s :>0 }" diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py index 31087f16108a65..f430faab9b563a 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py @@ -265,17 +265,13 @@ # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = t"aaaaaaaaa { x ! r }" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. -x = t"aaaaaaaaa { x = ! r }" +x = t"aaaaaaaaa { x = !r }" # Combine conversion flags with format specifiers -x = t"{x = ! s +x = t"{x = !s :>0 }" diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap index b357a01b9f6cb2..913dcaf547dd4b 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap @@ -273,17 +273,13 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = f"aaaaaaaaa { x ! r }" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. -x = f"aaaaaaaaa { x = ! r }" +x = f"aaaaaaaaa { x = !r }" # Combine conversion flags with format specifiers -x = f"{x = ! s +x = f"{x = !s :>0 }" @@ -1036,10 +1032,6 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = f"aaaaaaaaa {x!r}" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. @@ -1836,10 +1828,6 @@ aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = f"aaaaaaaaa {x!r}" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap index 5c08140e3e0344..e916c1ee27e485 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap @@ -271,17 +271,13 @@ aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = t"aaaaaaaaa { x ! r }" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. -x = t"aaaaaaaaa { x = ! r }" +x = t"aaaaaaaaa { x = !r }" # Combine conversion flags with format specifiers -x = t"{x = ! s +x = t"{x = !s :>0 }" @@ -1032,10 +1028,6 @@ aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + cccccccccccc # Conversion flags # -# This is not a valid Python code because of the additional whitespace between the `!` -# and conversion type. But, our parser isn't strict about this. This should probably be -# removed once we have a strict parser. -x = t"aaaaaaaaa {x!r}" # Even in the case of debug expressions, we only need to preserve the whitespace within # the expression part of the replacement field. diff --git a/crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py b/crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py new file mode 100644 index 00000000000000..17d523150fe581 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py @@ -0,0 +1,3 @@ +f"{x! s}" +t"{x! s}" +f"{x! z}" diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index b7bacffb57d57a..ad00df0f763010 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -63,13 +63,16 @@ pub enum InterpolatedStringErrorType { UnterminatedTripleQuotedString, /// A lambda expression without parentheses was encountered. LambdaWithoutParentheses, + /// Conversion flag does not immediately follow exclamation. + ConversionFlagNotImmediatelyAfterExclamation, } impl std::fmt::Display for InterpolatedStringErrorType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use InterpolatedStringErrorType::{ - InvalidConversionFlag, LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace, - UnterminatedString, UnterminatedTripleQuotedString, + ConversionFlagNotImmediatelyAfterExclamation, InvalidConversionFlag, + LambdaWithoutParentheses, SingleRbrace, UnclosedLbrace, UnterminatedString, + UnterminatedTripleQuotedString, }; match self { UnclosedLbrace => write!(f, "expecting '}}'"), @@ -80,6 +83,10 @@ impl std::fmt::Display for InterpolatedStringErrorType { LambdaWithoutParentheses => { write!(f, "lambda expressions are not allowed without parentheses") } + ConversionFlagNotImmediatelyAfterExclamation => write!( + f, + "conversion type must come right after the exclamation mark" + ), } } } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 9eff91aadb1710..e1f5f8c1245aae 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1771,6 +1771,19 @@ impl<'src> Parser<'src> { let conversion = if self.eat(TokenKind::Exclamation) { let conversion_flag_range = self.current_token_range(); if self.at(TokenKind::Name) { + // test_err f_string_conversion_follows_exclamation + // f"{x! s}" + // t"{x! s}" + // f"{x! z}" + if self.prev_token_end != conversion_flag_range.start() { + self.add_error( + ParseErrorType::from_interpolated_string_error( + InterpolatedStringErrorType::ConversionFlagNotImmediatelyAfterExclamation, + string_kind, + ), + TextRange::new(self.prev_token_end, conversion_flag_range.start()), + ); + } let TokenValue::Name(name) = self.bump_value(TokenKind::Name) else { unreachable!(); }; diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_conversion_follows_exclamation.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_conversion_follows_exclamation.py.snap new file mode 100644 index 00000000000000..f3725bcbab3fb6 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@f_string_conversion_follows_exclamation.py.snap @@ -0,0 +1,186 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/f_string_conversion_follows_exclamation.py +--- +## AST + +``` +Module( + ModModule { + node_index: AtomicNodeIndex(..), + range: 0..30, + body: [ + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 0..9, + value: FString( + ExprFString { + node_index: AtomicNodeIndex(..), + range: 0..9, + value: FStringValue { + inner: Single( + FString( + FString { + range: 0..9, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 2..8, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 3..4, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Str, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 10..19, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 10..19, + value: TStringValue { + inner: Single( + TString( + TString { + range: 10..19, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 12..18, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 13..14, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: Str, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 20..29, + value: FString( + ExprFString { + node_index: AtomicNodeIndex(..), + range: 20..29, + value: FStringValue { + inner: Single( + FString( + FString { + range: 20..29, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 22..28, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 23..24, + id: Name("x"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | f"{x! s}" + | ^ Syntax Error: f-string: conversion type must come right after the exclamation mark +2 | t"{x! s}" +3 | f"{x! z}" + | + + + | +1 | f"{x! s}" +2 | t"{x! s}" + | ^ Syntax Error: t-string: conversion type must come right after the exclamation mark +3 | f"{x! z}" + | + + + | +1 | f"{x! s}" +2 | t"{x! s}" +3 | f"{x! z}" + | ^ Syntax Error: f-string: conversion type must come right after the exclamation mark + | + + + | +1 | f"{x! s}" +2 | t"{x! s}" +3 | f"{x! z}" + | ^ Syntax Error: f-string: invalid conversion character + | From c3aa9655468864892a23a2c2dfdd58d726b418c9 Mon Sep 17 00:00:00 2001 From: Denys Kyslytsyn <71435127+twentyone212121@users.noreply.github.com> Date: Tue, 17 Jun 2025 02:03:54 +0900 Subject: [PATCH 435/487] [`ruff`] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) (#17368) This PR aims to close #16605. ## Summary This PR introduces a new rule (`RUF061`) that detects non-contextmanager usage of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call`. This pattern is discouraged and [was proposed in flake8-pytest-style](https://github.com/m-burst/flake8-pytest-style/pull/332), but the corresponding PR has been open for over a month without activity. Additionally, this PR provides an unsafe fix for simple cases where the non-contextmanager form can be transformed into the context manager form. Examples of supported patterns are listed in `RUF061_raises.py`, `RUF061_warns.py`, and `RUF061_deprecated_call.py` test files. The more complex case from the original issue (involving two separate statements): ```python excinfo = pytest.raises(ValueError, int, "hello") assert excinfo.match("^invalid literal") ``` is getting fixed like this: ```python with pytest.raises(ValueError) as excinfo: int("hello") assert excinfo.match("^invalid literal") ``` Putting match in the raises call requires multi-statement transformation, which I am not sure how to implement. ## Test Plan New test files were added to cover various usages of the non-contextmanager form of pytest.raises, warns, and deprecated_call. --- .../fixtures/ruff/RUF061_deprecated_call.py | 25 ++ .../test/fixtures/ruff/RUF061_raises.py | 40 +++ .../test/fixtures/ruff/RUF061_warns.py | 25 ++ .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/ruff/mod.rs | 3 + .../ruff/rules/legacy_form_pytest_raises.rs | 307 ++++++++++++++++++ .../ruff_linter/src/rules/ruff/rules/mod.rs | 2 + ...sts__RUF061_RUF061_deprecated_call.py.snap | 57 ++++ ..._ruff__tests__RUF061_RUF061_raises.py.snap | 114 +++++++ ...__ruff__tests__RUF061_RUF061_warns.py.snap | 57 ++++ ruff.schema.json | 1 + 12 files changed, 635 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py create mode 100644 crates/ruff_linter/src/rules/ruff/rules/legacy_form_pytest_raises.rs create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_deprecated_call.py.snap create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_raises.py.snap create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_warns.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py new file mode 100644 index 00000000000000..63b08e487fbe5a --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py @@ -0,0 +1,25 @@ +import warnings +import pytest + + +def raise_deprecation_warning(s): + warnings.warn(s, DeprecationWarning) + return s + + +def test_ok(): + with pytest.deprecated_call(): + raise_deprecation_warning("") + + +def test_error_trivial(): + pytest.deprecated_call(raise_deprecation_warning, "deprecated") + + +def test_error_assign(): + s = pytest.deprecated_call(raise_deprecation_warning, "deprecated") + print(s) + + +def test_error_lambda(): + pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning)) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py new file mode 100644 index 00000000000000..d4e3d900cd59fb --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py @@ -0,0 +1,40 @@ +import pytest + + +def func(a, b): + return a / b + + +def test_ok(): + with pytest.raises(ValueError): + raise ValueError + + +def test_ok_as(): + with pytest.raises(ValueError) as excinfo: + raise ValueError + + +def test_error_trivial(): + pytest.raises(ZeroDivisionError, func, 1, b=0) + + +def test_error_match(): + pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero") + + +def test_error_assign(): + excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0) + + +def test_error_kwargs(): + pytest.raises(func=func, expected_exception=ZeroDivisionError) + + +def test_error_multi_statement(): + excinfo = pytest.raises(ValueError, int, "hello") + assert excinfo.match("^invalid literal") + + +def test_error_lambda(): + pytest.raises(ZeroDivisionError, lambda: 1 / 0) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py new file mode 100644 index 00000000000000..d94573a0905745 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py @@ -0,0 +1,25 @@ +import warnings +import pytest + + +def raise_user_warning(s): + warnings.warn(s, UserWarning) + return s + + +def test_ok(): + with pytest.warns(UserWarning): + raise_user_warning("") + + +def test_error_trivial(): + pytest.warns(UserWarning, raise_user_warning, "warning") + + +def test_error_assign(): + s = pytest.warns(UserWarning, raise_user_warning, "warning") + print(s) + + +def test_error_lambda(): + pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning)) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index f695c198ab482f..377a50cc396a4c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -975,6 +975,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { ]) { flake8_pytest_style::rules::raises_call(checker, call); } + if checker.enabled(Rule::LegacyFormPytestRaises) { + ruff::rules::legacy_raises_warns_deprecated_call(checker, call); + } if checker.any_enabled(&[Rule::PytestWarnsWithoutWarning, Rule::PytestWarnsTooBroad]) { flake8_pytest_style::rules::warns_call(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c31e989a46c1b3..5ae727ce32d00a 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1027,6 +1027,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip), (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable), (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection), + (Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), (Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 39cab6081954a3..a9aeb31ae2688a 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -100,6 +100,9 @@ mod tests { #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_2.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_3.py"))] #[test_case(Rule::InEmptyCollection, Path::new("RUF060.py"))] + #[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_raises.py"))] + #[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_warns.py"))] + #[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_deprecated_call.py"))] #[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))] #[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))] #[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/rules/legacy_form_pytest_raises.rs b/crates/ruff_linter/src/rules/ruff/rules/legacy_form_pytest_raises.rs new file mode 100644 index 00000000000000..351d548334a10f --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/legacy_form_pytest_raises.rs @@ -0,0 +1,307 @@ +use itertools::{Either, Itertools}; +use ruff_diagnostics::{Edit, Fix}; +use ruff_macros::{ViolationMetadata, derive_message_formats}; +use ruff_python_ast::{self as ast, AtomicNodeIndex, Expr, Stmt, StmtExpr, StmtWith, WithItem}; +use ruff_python_semantic::SemanticModel; +use ruff_python_trivia::{has_leading_content, has_trailing_content, leading_indentation}; +use ruff_source_file::UniversalNewlines; +use ruff_text_size::{Ranged, TextRange}; +use std::fmt; + +use crate::{FixAvailability, Violation, checkers::ast::Checker}; + +/// ## What it does +/// Checks for non-contextmanager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call`. +/// +/// ## Why is this bad? +/// The context-manager form is more readable, easier to extend, and supports additional kwargs. +/// +/// ## Example +/// ```python +/// import pytest +/// +/// +/// excinfo = pytest.raises(ValueError, int, "hello") +/// pytest.warns(UserWarning, my_function, arg) +/// pytest.deprecated_call(my_deprecated_function, arg1, arg2) +/// ``` +/// +/// Use instead: +/// ```python +/// import pytest +/// +/// +/// with pytest.raises(ValueError) as excinfo: +/// int("hello") +/// with pytest.warns(UserWarning): +/// my_function(arg) +/// with pytest.deprecated_call(): +/// my_deprecated_function(arg1, arg2) +/// ``` +/// +/// ## References +/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises) +/// - [`pytest` documentation: `pytest.warns`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-warns) +/// - [`pytest` documentation: `pytest.deprecated_call`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-deprecated-call) +#[derive(ViolationMetadata)] +pub(crate) struct LegacyFormPytestRaises { + context_type: PytestContextType, +} + +impl Violation for LegacyFormPytestRaises { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + format!( + "Use context-manager form of `pytest.{}()`", + self.context_type + ) + } + + fn fix_title(&self) -> Option { + Some(format!( + "Use `pytest.{}()` as a context-manager", + self.context_type + )) + } +} + +/// Enum representing the type of pytest context manager +#[derive(PartialEq, Clone, Copy)] +enum PytestContextType { + Raises, + Warns, + DeprecatedCall, +} + +impl fmt::Display for PytestContextType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Self::Raises => "raises", + Self::Warns => "warns", + Self::DeprecatedCall => "deprecated_call", + }; + write!(f, "{name}") + } +} + +impl PytestContextType { + fn from_expr_name(func: &Expr, semantic: &SemanticModel) -> Option { + semantic + .resolve_qualified_name(func) + .and_then(|qualified_name| match qualified_name.segments() { + ["pytest", "raises"] => Some(Self::Raises), + ["pytest", "warns"] => Some(Self::Warns), + ["pytest", "deprecated_call"] => Some(Self::DeprecatedCall), + _ => None, + }) + } + + fn expected_arg(self) -> Option<(&'static str, usize)> { + match self { + Self::Raises => Some(("expected_exception", 0)), + Self::Warns => Some(("expected_warning", 0)), + Self::DeprecatedCall => None, + } + } + + fn func_arg(self) -> (&'static str, usize) { + match self { + Self::Raises | Self::Warns => ("func", 1), + Self::DeprecatedCall => ("func", 0), + } + } +} + +pub(crate) fn legacy_raises_warns_deprecated_call(checker: &Checker, call: &ast::ExprCall) { + let semantic = checker.semantic(); + let Some(context_type) = PytestContextType::from_expr_name(&call.func, semantic) else { + return; + }; + + let (func_arg_name, func_arg_position) = context_type.func_arg(); + if call + .arguments + .find_argument(func_arg_name, func_arg_position) + .is_none() + { + return; + } + + let mut diagnostic = + checker.report_diagnostic(LegacyFormPytestRaises { context_type }, call.range()); + + let stmt = semantic.current_statement(); + if !has_leading_content(stmt.start(), checker.source()) + && !has_trailing_content(stmt.end(), checker.source()) + { + if let Some(with_stmt) = try_fix_legacy_call(context_type, stmt, semantic) { + let generated = checker.generator().stmt(&Stmt::With(with_stmt)); + let first_line = checker.locator().line_str(stmt.start()); + let indentation = leading_indentation(first_line); + let mut indented = String::new(); + for (idx, line) in generated.universal_newlines().enumerate() { + if idx == 0 { + indented.push_str(&line); + } else { + indented.push_str(checker.stylist().line_ending().as_str()); + indented.push_str(indentation); + indented.push_str(&line); + } + } + + diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( + indented, + stmt.range(), + ))); + } + } +} + +fn try_fix_legacy_call( + context_type: PytestContextType, + stmt: &Stmt, + semantic: &SemanticModel, +) -> Option { + match stmt { + Stmt::Expr(StmtExpr { value, .. }) => { + let call = value.as_call_expr()?; + + // Handle two patterns for legacy calls: + // 1. Direct usage: `pytest.raises(ZeroDivisionError, func, 1, b=0)` + // 2. With match method: `pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")` + // + // The second branch specifically looks for raises().match() pattern which only exists for + // `raises` (not `warns`/`deprecated_call`) since only `raises` returns an ExceptionInfo + // object with a .match() method. We need to preserve this match condition when converting + // to context manager form. + if PytestContextType::from_expr_name(&call.func, semantic) == Some(context_type) { + generate_with_statement(context_type, call, None, None, None) + } else if let PytestContextType::Raises = context_type { + let inner_raises_call = call + .func + .as_attribute_expr() + .filter(|expr_attribute| &expr_attribute.attr == "match") + .and_then(|expr_attribute| expr_attribute.value.as_call_expr()) + .filter(|inner_call| { + PytestContextType::from_expr_name(&inner_call.func, semantic) + == Some(PytestContextType::Raises) + })?; + let match_arg = call.arguments.args.first(); + generate_with_statement(context_type, inner_raises_call, match_arg, None, None) + } else { + None + } + } + Stmt::Assign(ast::StmtAssign { targets, value, .. }) => { + let call = value.as_call_expr().filter(|call| { + PytestContextType::from_expr_name(&call.func, semantic) == Some(context_type) + })?; + let (optional_vars, assign_targets) = match context_type { + PytestContextType::Raises => { + let [target] = targets.as_slice() else { + return None; + }; + (Some(target), None) + } + PytestContextType::Warns | PytestContextType::DeprecatedCall => { + (None, Some(targets.as_slice())) + } + }; + + generate_with_statement(context_type, call, None, optional_vars, assign_targets) + } + _ => None, + } +} + +fn generate_with_statement( + context_type: PytestContextType, + legacy_call: &ast::ExprCall, + match_arg: Option<&Expr>, + optional_vars: Option<&Expr>, + assign_targets: Option<&[Expr]>, +) -> Option { + let expected = if let Some((name, position)) = context_type.expected_arg() { + Some(legacy_call.arguments.find_argument_value(name, position)?) + } else { + None + }; + + let (func_arg_name, func_arg_position) = context_type.func_arg(); + let func = legacy_call + .arguments + .find_argument_value(func_arg_name, func_arg_position)?; + + let (func_args, func_keywords): (Vec<_>, Vec<_>) = legacy_call + .arguments + .arguments_source_order() + .skip(if expected.is_some() { 2 } else { 1 }) + .partition_map(|arg_or_keyword| match arg_or_keyword { + ast::ArgOrKeyword::Arg(expr) => Either::Left(expr.clone()), + ast::ArgOrKeyword::Keyword(keyword) => Either::Right(keyword.clone()), + }); + + let context_call = ast::ExprCall { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + func: legacy_call.func.clone(), + arguments: ast::Arguments { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + args: expected.cloned().as_slice().into(), + keywords: match_arg + .map(|expr| ast::Keyword { + node_index: AtomicNodeIndex::dummy(), + // Take range from the original expression so that the keyword + // argument is generated after positional arguments + range: expr.range(), + arg: Some(ast::Identifier::new("match", TextRange::default())), + value: expr.clone(), + }) + .as_slice() + .into(), + }, + }; + + let func_call = ast::ExprCall { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + func: Box::new(func.clone()), + arguments: ast::Arguments { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + args: func_args.into(), + keywords: func_keywords.into(), + }, + }; + + let body = if let Some(assign_targets) = assign_targets { + Stmt::Assign(ast::StmtAssign { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + targets: assign_targets.to_vec(), + value: Box::new(func_call.into()), + }) + } else { + Stmt::Expr(StmtExpr { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + value: Box::new(func_call.into()), + }) + }; + + Some(StmtWith { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + is_async: false, + items: vec![WithItem { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::default(), + context_expr: context_call.into(), + optional_vars: optional_vars.map(|var| Box::new(var.clone())), + }], + body: vec![body], + }) +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 4706c713c6e910..a8344ad9874d5b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -21,6 +21,7 @@ pub(crate) use invalid_formatter_suppression_comment::*; pub(crate) use invalid_index_type::*; pub(crate) use invalid_pyproject_toml::*; pub(crate) use invalid_rule_code::*; +pub(crate) use legacy_form_pytest_raises::*; pub(crate) use map_int_version_parsing::*; pub(crate) use missing_fstring_syntax::*; pub(crate) use mutable_class_default::*; @@ -82,6 +83,7 @@ mod invalid_formatter_suppression_comment; mod invalid_index_type; mod invalid_pyproject_toml; mod invalid_rule_code; +mod legacy_form_pytest_raises; mod map_int_version_parsing; mod missing_fstring_syntax; mod mutable_class_default; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_deprecated_call.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_deprecated_call.py.snap new file mode 100644 index 00000000000000..57f0c51fe18bbd --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_deprecated_call.py.snap @@ -0,0 +1,57 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF061_deprecated_call.py:16:5: RUF061 [*] Use context-manager form of `pytest.deprecated_call()` + | +15 | def test_error_trivial(): +16 | pytest.deprecated_call(raise_deprecation_warning, "deprecated") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.deprecated_call()` as a context-manager + +ℹ Unsafe fix +13 13 | +14 14 | +15 15 | def test_error_trivial(): +16 |- pytest.deprecated_call(raise_deprecation_warning, "deprecated") + 16 |+ with pytest.deprecated_call(): + 17 |+ raise_deprecation_warning("deprecated") +17 18 | +18 19 | +19 20 | def test_error_assign(): + +RUF061_deprecated_call.py:20:9: RUF061 [*] Use context-manager form of `pytest.deprecated_call()` + | +19 | def test_error_assign(): +20 | s = pytest.deprecated_call(raise_deprecation_warning, "deprecated") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 +21 | print(s) + | + = help: Use `pytest.deprecated_call()` as a context-manager + +ℹ Unsafe fix +17 17 | +18 18 | +19 19 | def test_error_assign(): +20 |- s = pytest.deprecated_call(raise_deprecation_warning, "deprecated") + 20 |+ with pytest.deprecated_call(): + 21 |+ s = raise_deprecation_warning("deprecated") +21 22 | print(s) +22 23 | +23 24 | + +RUF061_deprecated_call.py:25:5: RUF061 [*] Use context-manager form of `pytest.deprecated_call()` + | +24 | def test_error_lambda(): +25 | pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.deprecated_call()` as a context-manager + +ℹ Unsafe fix +22 22 | +23 23 | +24 24 | def test_error_lambda(): +25 |- pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning)) + 25 |+ with pytest.deprecated_call(): + 26 |+ (lambda: warnings.warn("", DeprecationWarning))() diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_raises.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_raises.py.snap new file mode 100644 index 00000000000000..be2efdaee92610 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_raises.py.snap @@ -0,0 +1,114 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF061_raises.py:19:5: RUF061 [*] Use context-manager form of `pytest.raises()` + | +18 | def test_error_trivial(): +19 | pytest.raises(ZeroDivisionError, func, 1, b=0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +16 16 | +17 17 | +18 18 | def test_error_trivial(): +19 |- pytest.raises(ZeroDivisionError, func, 1, b=0) + 19 |+ with pytest.raises(ZeroDivisionError): + 20 |+ func(1, b=0) +20 21 | +21 22 | +22 23 | def test_error_match(): + +RUF061_raises.py:23:5: RUF061 [*] Use context-manager form of `pytest.raises()` + | +22 | def test_error_match(): +23 | pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +20 20 | +21 21 | +22 22 | def test_error_match(): +23 |- pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero") + 23 |+ with pytest.raises(ZeroDivisionError, match="division by zero"): + 24 |+ func(1, b=0) +24 25 | +25 26 | +26 27 | def test_error_assign(): + +RUF061_raises.py:27:15: RUF061 [*] Use context-manager form of `pytest.raises()` + | +26 | def test_error_assign(): +27 | excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +24 24 | +25 25 | +26 26 | def test_error_assign(): +27 |- excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0) + 27 |+ with pytest.raises(ZeroDivisionError) as excinfo: + 28 |+ func(1, b=0) +28 29 | +29 30 | +30 31 | def test_error_kwargs(): + +RUF061_raises.py:31:5: RUF061 [*] Use context-manager form of `pytest.raises()` + | +30 | def test_error_kwargs(): +31 | pytest.raises(func=func, expected_exception=ZeroDivisionError) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +28 28 | +29 29 | +30 30 | def test_error_kwargs(): +31 |- pytest.raises(func=func, expected_exception=ZeroDivisionError) + 31 |+ with pytest.raises(ZeroDivisionError): + 32 |+ func() +32 33 | +33 34 | +34 35 | def test_error_multi_statement(): + +RUF061_raises.py:35:15: RUF061 [*] Use context-manager form of `pytest.raises()` + | +34 | def test_error_multi_statement(): +35 | excinfo = pytest.raises(ValueError, int, "hello") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 +36 | assert excinfo.match("^invalid literal") + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +32 32 | +33 33 | +34 34 | def test_error_multi_statement(): +35 |- excinfo = pytest.raises(ValueError, int, "hello") + 35 |+ with pytest.raises(ValueError) as excinfo: + 36 |+ int("hello") +36 37 | assert excinfo.match("^invalid literal") +37 38 | +38 39 | + +RUF061_raises.py:40:5: RUF061 [*] Use context-manager form of `pytest.raises()` + | +39 | def test_error_lambda(): +40 | pytest.raises(ZeroDivisionError, lambda: 1 / 0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.raises()` as a context-manager + +ℹ Unsafe fix +37 37 | +38 38 | +39 39 | def test_error_lambda(): +40 |- pytest.raises(ZeroDivisionError, lambda: 1 / 0) + 40 |+ with pytest.raises(ZeroDivisionError): + 41 |+ (lambda: 1 / 0)() diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_warns.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_warns.py.snap new file mode 100644 index 00000000000000..e47eb6307e3e83 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF061_RUF061_warns.py.snap @@ -0,0 +1,57 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF061_warns.py:16:5: RUF061 [*] Use context-manager form of `pytest.warns()` + | +15 | def test_error_trivial(): +16 | pytest.warns(UserWarning, raise_user_warning, "warning") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.warns()` as a context-manager + +ℹ Unsafe fix +13 13 | +14 14 | +15 15 | def test_error_trivial(): +16 |- pytest.warns(UserWarning, raise_user_warning, "warning") + 16 |+ with pytest.warns(UserWarning): + 17 |+ raise_user_warning("warning") +17 18 | +18 19 | +19 20 | def test_error_assign(): + +RUF061_warns.py:20:9: RUF061 [*] Use context-manager form of `pytest.warns()` + | +19 | def test_error_assign(): +20 | s = pytest.warns(UserWarning, raise_user_warning, "warning") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 +21 | print(s) + | + = help: Use `pytest.warns()` as a context-manager + +ℹ Unsafe fix +17 17 | +18 18 | +19 19 | def test_error_assign(): +20 |- s = pytest.warns(UserWarning, raise_user_warning, "warning") + 20 |+ with pytest.warns(UserWarning): + 21 |+ s = raise_user_warning("warning") +21 22 | print(s) +22 23 | +23 24 | + +RUF061_warns.py:25:5: RUF061 [*] Use context-manager form of `pytest.warns()` + | +24 | def test_error_lambda(): +25 | pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061 + | + = help: Use `pytest.warns()` as a context-manager + +ℹ Unsafe fix +22 22 | +23 23 | +24 24 | def test_error_lambda(): +25 |- pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning)) + 25 |+ with pytest.warns(UserWarning): + 26 |+ (lambda: warnings.warn("", UserWarning))() diff --git a/ruff.schema.json b/ruff.schema.json index 7bf97547310a7c..4a186a05683497 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -4039,6 +4039,7 @@ "RUF059", "RUF06", "RUF060", + "RUF061", "RUF1", "RUF10", "RUF100", From 2b15f1d240d44ae775855cb69ef2b1bed592f3a1 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Bodas <55339528+abhijeetbodas2001@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:57:55 +0530 Subject: [PATCH 436/487] [ty] Support `dataclasses.KW_ONLY` (#18677) --- .../resources/mdtest/dataclasses.md | 43 +++++++++++++++++++ crates/ty_python_semantic/src/types/class.rs | 30 ++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses.md index 4616e59e224548..699a624173d8b2 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses.md @@ -713,6 +713,49 @@ But calling `asdict` on the class object is not allowed: asdict(Foo) ``` +## `dataclasses.KW_ONLY` + +If an attribute is annotated with `dataclasses.KW_ONLY`, it is not added to the synthesized +`__init__` of the class. Instead, this special marker annotation causes Python at runtime to ensure +that all annotations following it have keyword-only parameters generated for them in the class's +synthesized `__init__` method. + +```toml +[environment] +python-version = "3.10" +``` + +```py +from dataclasses import dataclass, field, KW_ONLY + +@dataclass +class C: + x: int + _: KW_ONLY + y: str + +# error: [missing-argument] +# error: [too-many-positional-arguments] +C(3, "") + +C(3, y="") +``` + +Using `KW_ONLY` to annotate more than one field in a dataclass causes a `TypeError` to be raised at +runtime: + +```py +@dataclass +class Fails: + a: int + b: KW_ONLY + c: str + + # TODO: we should emit an error here + # (two different names with `KW_ONLY` annotations in the same dataclass means the class fails at runtime) + d: KW_ONLY +``` + ## Other special cases ### `dataclasses.dataclass` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 5869411fedabcd..708a8e4f40f0f9 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1313,9 +1313,21 @@ impl<'db> ClassLiteral<'db> { let field_policy = CodeGeneratorKind::from_class(db, self)?; let signature_from_fields = |mut parameters: Vec<_>| { + let mut kw_only_field_seen = false; for (name, (mut attr_ty, mut default_ty)) in self.fields(db, specialization, field_policy) { + if attr_ty + .into_nominal_instance() + .is_some_and(|instance| instance.class.is_known(db, KnownClass::KwOnly)) + { + // Attributes annotated with `dataclass.KW_ONLY` are not present in the synthesized + // `__init__` method, ; they only used to indicate that the parameters after this are + // keyword-only. + kw_only_field_seen = true; + continue; + } + // The descriptor handling below is guarded by this fully-static check, because dynamic // types like `Any` are valid (data) descriptors: since they have all possible attributes, // they also have a (callable) `__set__` method. The problem is that we can't determine @@ -1360,8 +1372,12 @@ impl<'db> ClassLiteral<'db> { } } - let mut parameter = - Parameter::positional_or_keyword(name).with_annotated_type(attr_ty); + let mut parameter = if kw_only_field_seen { + Parameter::keyword_only(name) + } else { + Parameter::positional_or_keyword(name) + } + .with_annotated_type(attr_ty); if let Some(default_ty) = default_ty { parameter = parameter.with_default_type(default_ty); @@ -2149,6 +2165,7 @@ pub enum KnownClass { NotImplementedType, // dataclasses Field, + KwOnly, // _typeshed._type_checker_internals NamedTupleFallback, } @@ -2234,6 +2251,7 @@ impl<'db> KnownClass { | Self::NotImplementedType | Self::Classmethod | Self::Field + | Self::KwOnly | Self::NamedTupleFallback => Truthiness::Ambiguous, } } @@ -2309,6 +2327,7 @@ impl<'db> KnownClass { | Self::NotImplementedType | Self::UnionType | Self::Field + | Self::KwOnly | Self::NamedTupleFallback => false, } } @@ -2385,6 +2404,7 @@ impl<'db> KnownClass { } Self::NotImplementedType => "_NotImplementedType", Self::Field => "Field", + Self::KwOnly => "KW_ONLY", Self::NamedTupleFallback => "NamedTupleFallback", } } @@ -2615,6 +2635,7 @@ impl<'db> KnownClass { | Self::Deque | Self::OrderedDict => KnownModule::Collections, Self::Field => KnownModule::Dataclasses, + Self::KwOnly => KnownModule::Dataclasses, Self::NamedTupleFallback => KnownModule::TypeCheckerInternals, } } @@ -2679,6 +2700,7 @@ impl<'db> KnownClass { | Self::NamedTuple | Self::NewType | Self::Field + | Self::KwOnly | Self::NamedTupleFallback => false, } } @@ -2745,6 +2767,7 @@ impl<'db> KnownClass { | Self::NamedTuple | Self::NewType | Self::Field + | Self::KwOnly | Self::NamedTupleFallback => false, } } @@ -2818,6 +2841,7 @@ impl<'db> KnownClass { } "_NotImplementedType" => Self::NotImplementedType, "Field" => Self::Field, + "KW_ONLY" => Self::KwOnly, "NamedTupleFallback" => Self::NamedTupleFallback, _ => return None, }; @@ -2874,6 +2898,7 @@ impl<'db> KnownClass { | Self::AsyncGeneratorType | Self::WrapperDescriptorType | Self::Field + | Self::KwOnly | Self::NamedTupleFallback => module == self.canonical_module(db), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::SpecialForm @@ -3079,6 +3104,7 @@ mod tests { KnownClass::UnionType => PythonVersion::PY310, KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => PythonVersion::PY311, KnownClass::GenericAlias => PythonVersion::PY39, + KnownClass::KwOnly => PythonVersion::PY310, _ => PythonVersion::PY37, }; From 5e57e4680feee0838888a5dad603f54b3e30313c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 16 Jun 2025 18:38:55 +0100 Subject: [PATCH 437/487] [ty] Use more parallelism when running corpus tests (#18711) --- crates/ty_python_semantic/tests/corpus.rs | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/ty_python_semantic/tests/corpus.rs b/crates/ty_python_semantic/tests/corpus.rs index c3c9b4297e970d..2f72a4bce3b119 100644 --- a/crates/ty_python_semantic/tests/corpus.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -12,6 +12,8 @@ use ty_python_semantic::{ SearchPathSettings, default_lint_registry, }; +use test_case::test_case; + fn get_cargo_workspace_root() -> anyhow::Result { Ok(SystemPathBuf::from(String::from_utf8( std::process::Command::new("cargo") @@ -39,19 +41,16 @@ fn parser_no_panic() -> anyhow::Result<()> { )) } -#[test] -fn linter_af_no_panic() -> anyhow::Result<()> { +#[test_case("a-e")] +#[test_case("f")] +#[test_case("g-o")] +#[test_case("p")] +#[test_case("q-z")] +#[test_case("!a-z")] +fn linter_no_panic(range: &str) -> anyhow::Result<()> { let workspace_root = get_cargo_workspace_root()?; run_corpus_tests(&format!( - "{workspace_root}/crates/ruff_linter/resources/test/fixtures/[a-f]*/**/*.py" - )) -} - -#[test] -fn linter_gz_no_panic() -> anyhow::Result<()> { - let workspace_root = get_cargo_workspace_root()?; - run_corpus_tests(&format!( - "{workspace_root}/crates/ruff_linter/resources/test/fixtures/[g-z]*/**/*.py" + "{workspace_root}/crates/ruff_linter/resources/test/fixtures/[{range}]*/**/*.py" )) } @@ -63,11 +62,15 @@ fn linter_stubs_no_panic() -> anyhow::Result<()> { )) } -#[test] -fn typeshed_no_panic() -> anyhow::Result<()> { +#[test_case("a-e")] +#[test_case("f-k")] +#[test_case("l-p")] +#[test_case("q-z")] +#[test_case("!a-z")] +fn typeshed_no_panic(range: &str) -> anyhow::Result<()> { let workspace_root = get_cargo_workspace_root()?; run_corpus_tests(&format!( - "{workspace_root}/crates/ty_vendored/vendor/typeshed/**/*.pyi" + "{workspace_root}/crates/ty_vendored/vendor/typeshed/stdlib/[{range}]*.pyi" )) } From 373a3bfcd670e30c5ced457e1f184a44ac9a6d0a Mon Sep 17 00:00:00 2001 From: Felix Scherz Date: Mon, 16 Jun 2025 19:46:17 +0200 Subject: [PATCH 438/487] [ty] allow `T: Never` as subtype of `Never` (#18687) --- .../resources/mdtest/type_compendium/never.md | 7 +++++-- .../resources/mdtest/type_properties/materialization.md | 2 -- crates/ty_python_semantic/src/types.rs | 7 +++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md index 98bff577a289c1..fbef3e571b55d4 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/never.md @@ -5,11 +5,11 @@ ## `Never` is a subtype of every type The `Never` type is the bottom type of Python's type system. It is a subtype of every type, but no -type is a subtype of `Never`, except for `Never` itself. +type is a subtype of `Never`, except for `Never` itself or type variables with upper bound `Never`. ```py from ty_extensions import static_assert, is_subtype_of -from typing_extensions import Never +from typing_extensions import Never, TypeVar class C: ... @@ -19,6 +19,9 @@ static_assert(is_subtype_of(Never, C)) static_assert(is_subtype_of(Never, Never)) static_assert(not is_subtype_of(int, Never)) + +T = TypeVar("T", bound=Never) +static_assert(is_subtype_of(T, Never)) ``` ## `Never` is assignable to every type diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md index 638ad5ff20887d..cf03b08d21e341 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md @@ -337,8 +337,6 @@ def bounded_by_gradual[T: Any](t: T) -> None: # Bottom materialization of `T: Any` is `T: Never` static_assert(is_fully_static(TypeOf[bottom_materialization(T)])) - # TODO: This should not error, see https://github.com/astral-sh/ty/issues/638 - # error: [static-assert-error] static_assert(is_subtype_of(TypeOf[bottom_materialization(T)], Never)) def constrained_by_gradual[T: (int, Any)](t: T) -> None: diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 08deb8327cd123..d02d7845082457 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1204,9 +1204,7 @@ impl<'db> Type<'db> { // `Never` is the bottom type, the empty set. // It is a subtype of all other fully static types. - // No other fully static type is a subtype of `Never`. (Type::Never, _) => true, - (_, Type::Never) => false, // Everything is a subtype of `object`. (_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true, @@ -1260,6 +1258,11 @@ impl<'db> Type<'db> { true } + // `Never` is the bottom type, the empty set. + // Other than one unlikely edge case (TypeVars bound to `Never`), + // no other fully static type is a subtype of `Never`. + (_, Type::Never) => false, + (Type::Union(union), _) => union .elements(db) .iter() From 83b0cde2fc9a2074077ddc3c3a37a7c5a4f23b22 Mon Sep 17 00:00:00 2001 From: chiri Date: Mon, 16 Jun 2025 21:13:47 +0300 Subject: [PATCH 439/487] [`refurb`] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments (#18645) ## Summary /closes #18639 ## Test Plan update snapshots --------- Co-authored-by: Brent Westbrook --- .../resources/test/fixtures/refurb/FURB163.py | 30 +++ .../rules/refurb/rules/redundant_log_base.rs | 20 +- ...es__refurb__tests__FURB163_FURB163.py.snap | 173 +++++++++++++++++- 3 files changed, 216 insertions(+), 7 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py index b8aca2ebe8fac2..d87f5b7f64969a 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB163.py @@ -43,3 +43,33 @@ def log(*args): math.log(1, 2.0001) math.log(1, 10.0001) + + +# see: https://github.com/astral-sh/ruff/issues/18639 +math.log(1, 10 # comment + ) + +math.log(1, + 10 # comment + ) + +math.log(1 # comment + , # comment + 10 # comment + ) + +math.log( + 1 # comment + , + 10 # comment +) + +math.log(4.13e223, 2) +math.log(4.14e223, 10) + + +def print_log(*args): + try: + print(math.log(*args, math.e)) + except TypeError as e: + print(repr(e)) diff --git a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs index f1063af584b449..e2b202c86de41c 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, Number}; use ruff_text_size::Ranged; @@ -36,6 +37,13 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// math.log10(4) /// ``` /// +/// ## Fix safety +/// This fix is marked unsafe when the argument is a starred expression, as this changes +/// the call semantics and may raise runtime errors. It is also unsafe if comments are +/// present within the call, as they will be removed. Additionally, `math.log(x, base)` +/// and `math.log2(x)` / `math.log10(x)` may differ due to floating-point rounding, so +/// the fix is also unsafe when making this transformation. +/// /// ## References /// - [Python documentation: `math.log`](https://docs.python.org/3/library/math.html#math.log) /// - [Python documentation: `math.log2`](https://docs.python.org/3/library/math.html#math.log2) @@ -141,9 +149,19 @@ fn generate_fix(checker: &Checker, call: &ast::ExprCall, base: Base, arg: &Expr) call.start(), checker.semantic(), )?; + let number = checker.locator().slice(arg); - Ok(Fix::safe_edits( + + Ok(Fix::applicable_edits( Edit::range_replacement(format!("{binding}({number})"), call.range()), [edit], + if (matches!(base, Base::Two | Base::Ten)) + || arg.is_starred_expr() + || checker.comment_ranges().intersects(call.range()) + { + Applicability::Unsafe + } else { + Applicability::Safe + }, )) } diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB163_FURB163.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB163_FURB163.py.snap index 0985c7f1c91179..dd5eb1647a4cf7 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB163_FURB163.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB163_FURB163.py.snap @@ -11,7 +11,7 @@ FURB163.py:4:1: FURB163 [*] Prefer `math.log2(1)` over `math.log` with a redunda | = help: Replace with `math.log2(1)` -ℹ Safe fix +ℹ Unsafe fix 1 1 | import math 2 2 | 3 3 | # Errors @@ -32,7 +32,7 @@ FURB163.py:5:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redund | = help: Replace with `math.log10(1)` -ℹ Safe fix +ℹ Unsafe fix 2 2 | 3 3 | # Errors 4 4 | math.log(1, 2) @@ -74,7 +74,7 @@ FURB163.py:8:1: FURB163 [*] Prefer `math.log2(foo)` over `math.log` with a redun | = help: Replace with `math.log2(foo)` -ℹ Safe fix +ℹ Unsafe fix 5 5 | math.log(1, 10) 6 6 | math.log(1, math.e) 7 7 | foo = ... @@ -95,7 +95,7 @@ FURB163.py:9:1: FURB163 [*] Prefer `math.log10(foo)` over `math.log` with a redu | = help: Replace with `math.log10(foo)` -ℹ Safe fix +ℹ Unsafe fix 6 6 | math.log(1, math.e) 7 7 | foo = ... 8 8 | math.log(foo, 2) @@ -136,7 +136,7 @@ FURB163.py:11:1: FURB163 [*] Prefer `math.log2(1)` over `math.log` with a redund | = help: Replace with `math.log2(1)` -ℹ Safe fix +ℹ Unsafe fix 8 8 | math.log(foo, 2) 9 9 | math.log(foo, 10) 10 10 | math.log(foo, math.e) @@ -157,7 +157,7 @@ FURB163.py:12:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redun | = help: Replace with `math.log10(1)` -ℹ Safe fix +ℹ Unsafe fix 9 9 | math.log(foo, 10) 10 10 | math.log(foo, math.e) 11 11 | math.log(1, 2.0) @@ -166,3 +166,164 @@ FURB163.py:12:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redun 13 13 | 14 14 | # OK 15 15 | math.log2(1) + +FURB163.py:49:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redundant base + | +48 | # see: https://github.com/astral-sh/ruff/issues/18639 +49 | / math.log(1, 10 # comment +50 | | ) + | |__________^ FURB163 +51 | +52 | math.log(1, + | + = help: Replace with `math.log10(1)` + +ℹ Unsafe fix +46 46 | +47 47 | +48 48 | # see: https://github.com/astral-sh/ruff/issues/18639 +49 |-math.log(1, 10 # comment +50 |- ) + 49 |+math.log10(1) +51 50 | +52 51 | math.log(1, +53 52 | 10 # comment + +FURB163.py:52:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redundant base + | +50 | ) +51 | +52 | / math.log(1, +53 | | 10 # comment +54 | | ) + | |__________^ FURB163 +55 | +56 | math.log(1 # comment + | + = help: Replace with `math.log10(1)` + +ℹ Unsafe fix +49 49 | math.log(1, 10 # comment +50 50 | ) +51 51 | +52 |-math.log(1, +53 |- 10 # comment +54 |- ) + 52 |+math.log10(1) +55 53 | +56 54 | math.log(1 # comment +57 55 | , # comment + +FURB163.py:56:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redundant base + | +54 | ) +55 | +56 | / math.log(1 # comment +57 | | , # comment +58 | | 10 # comment +59 | | ) + | |__________^ FURB163 +60 | +61 | math.log( + | + = help: Replace with `math.log10(1)` + +ℹ Unsafe fix +53 53 | 10 # comment +54 54 | ) +55 55 | +56 |-math.log(1 # comment +57 |- , # comment +58 |- 10 # comment +59 |- ) + 56 |+math.log10(1) +60 57 | +61 58 | math.log( +62 59 | 1 # comment + +FURB163.py:61:1: FURB163 [*] Prefer `math.log10(1)` over `math.log` with a redundant base + | +59 | ) +60 | +61 | / math.log( +62 | | 1 # comment +63 | | , +64 | | 10 # comment +65 | | ) + | |_^ FURB163 +66 | +67 | math.log(4.13e223, 2) + | + = help: Replace with `math.log10(1)` + +ℹ Unsafe fix +58 58 | 10 # comment +59 59 | ) +60 60 | +61 |-math.log( +62 |- 1 # comment +63 |- , +64 |- 10 # comment +65 |-) + 61 |+math.log10(1) +66 62 | +67 63 | math.log(4.13e223, 2) +68 64 | math.log(4.14e223, 10) + +FURB163.py:67:1: FURB163 [*] Prefer `math.log2(4.13e223)` over `math.log` with a redundant base + | +65 | ) +66 | +67 | math.log(4.13e223, 2) + | ^^^^^^^^^^^^^^^^^^^^^ FURB163 +68 | math.log(4.14e223, 10) + | + = help: Replace with `math.log2(4.13e223)` + +ℹ Unsafe fix +64 64 | 10 # comment +65 65 | ) +66 66 | +67 |-math.log(4.13e223, 2) + 67 |+math.log2(4.13e223) +68 68 | math.log(4.14e223, 10) +69 69 | +70 70 | + +FURB163.py:68:1: FURB163 [*] Prefer `math.log10(4.14e223)` over `math.log` with a redundant base + | +67 | math.log(4.13e223, 2) +68 | math.log(4.14e223, 10) + | ^^^^^^^^^^^^^^^^^^^^^^ FURB163 + | + = help: Replace with `math.log10(4.14e223)` + +ℹ Unsafe fix +65 65 | ) +66 66 | +67 67 | math.log(4.13e223, 2) +68 |-math.log(4.14e223, 10) + 68 |+math.log10(4.14e223) +69 69 | +70 70 | +71 71 | def print_log(*args): + +FURB163.py:73:15: FURB163 [*] Prefer `math.log(*args)` over `math.log` with a redundant base + | +71 | def print_log(*args): +72 | try: +73 | print(math.log(*args, math.e)) + | ^^^^^^^^^^^^^^^^^^^^^^^ FURB163 +74 | except TypeError as e: +75 | print(repr(e)) + | + = help: Replace with `math.log(*args)` + +ℹ Unsafe fix +70 70 | +71 71 | def print_log(*args): +72 72 | try: +73 |- print(math.log(*args, math.e)) + 73 |+ print(math.log(*args)) +74 74 | except TypeError as e: +75 75 | print(repr(e)) From 7880a2079432bca9f523a1f4a6264dd123e91777 Mon Sep 17 00:00:00 2001 From: Nikolas Hearp <154913660+njhearp@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:02:30 -0400 Subject: [PATCH 440/487] [pylint] Fix `PLW0128` to check assignment targets in square brackets and after asterisks (#18665) ## Summary This fixes PLW0128 to check for redeclared assignments in square brackets and after asterisks. Fixes #18660 --- .../pylint/redeclared_assigned_name.py | 3 ++ .../pylint/rules/redeclared_assigned_name.rs | 8 ++++ ...__PLW0128_redeclared_assigned_name.py.snap | 48 +++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py b/crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py index 50b3a27925c362..3eff6b43de097c 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py @@ -2,5 +2,8 @@ FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128 FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 +FIRST, *FIRST = (1, 2) # PLW0128 FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK diff --git a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs index fa5dfe01825552..b453c1e0c3a8ea 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redeclared_assigned_name.rs @@ -57,6 +57,14 @@ fn check_expr(checker: &Checker, expr: &Expr, names: &mut Vec) { check_expr(checker, target, names); } } + Expr::List(list) => { + for target in list { + check_expr(checker, target, names); + } + } + Expr::Starred(starred) => { + check_expr(checker, &starred.value, names); + } Expr::Name(ast::ExprName { id, .. }) => { if checker.settings.dummy_variable_rgx.is_match(id) { // Ignore dummy variable assignments diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0128_redeclared_assigned_name.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0128_redeclared_assigned_name.py.snap index c3f54bdf9b68d7..13c70ddf96eacd 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0128_redeclared_assigned_name.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0128_redeclared_assigned_name.py.snap @@ -25,6 +25,7 @@ redeclared_assigned_name.py:3:9: PLW0128 Redeclared variable `FIRST` in assignme 3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 | ^^^^^ PLW0128 4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 | redeclared_assigned_name.py:3:32: PLW0128 Redeclared variable `FIRST` in assignment @@ -34,6 +35,7 @@ redeclared_assigned_name.py:3:32: PLW0128 Redeclared variable `FIRST` in assignm 3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 | ^^^^^ PLW0128 4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 | redeclared_assigned_name.py:4:23: PLW0128 Redeclared variable `FIRST` in assignment @@ -42,8 +44,8 @@ redeclared_assigned_name.py:4:23: PLW0128 Redeclared variable `FIRST` in assignm 3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 | ^^^^^ PLW0128 -5 | -6 | FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 | redeclared_assigned_name.py:4:30: PLW0128 Redeclared variable `SECOND` in assignment @@ -52,6 +54,44 @@ redeclared_assigned_name.py:4:30: PLW0128 Redeclared variable `SECOND` in assign 3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 | ^^^^^^ PLW0128 -5 | -6 | FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 + | + +redeclared_assigned_name.py:5:9: PLW0128 Redeclared variable `FIRST` in assignment + | +3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128 +4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 + | ^^^^^ PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 +7 | FIRST, *FIRST = (1, 2) # PLW0128 + | + +redeclared_assigned_name.py:6:9: PLW0128 Redeclared variable `FIRST` in assignment + | +4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 + | ^^^^^ PLW0128 +7 | FIRST, *FIRST = (1, 2) # PLW0128 + | + +redeclared_assigned_name.py:6:32: PLW0128 Redeclared variable `FIRST` in assignment + | +4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128 +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 + | ^^^^^ PLW0128 +7 | FIRST, *FIRST = (1, 2) # PLW0128 + | + +redeclared_assigned_name.py:7:9: PLW0128 Redeclared variable `FIRST` in assignment + | +5 | FIRST, [FIRST, SECOND] = (1, (1, 2)) # PLW0128 +6 | FIRST, [FIRST, SECOND, [THIRD, FIRST]] = (1, (1, 2)) # PLW0128 +7 | FIRST, *FIRST = (1, 2) # PLW0128 + | ^^^^^ PLW0128 +8 | +9 | FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK | From cff5adf3244b2e69dcfc8aaca498a38e9a7cb487 Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 16 Jun 2025 16:09:31 -0300 Subject: [PATCH 441/487] [`pyupgrade`] Suppress `UP008` diagnostic if `super` symbol is not builtin (#18688) ## Summary Fixes #18684 ## Test Plan Add regression test --- .../test/fixtures/pyupgrade/UP008.py | 17 ++++++++++++++++- .../rules/super_call_with_parameters.rs | 10 +++------- ...er__rules__pyupgrade__tests__UP008.py.snap | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py index d1a5ed5923d5de..59459132996a26 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py @@ -89,4 +89,19 @@ def foo(self): class B(A): def bar(self): - super(__class__, self).foo() \ No newline at end of file + super(__class__, self).foo() + + +# see: https://github.com/astral-sh/ruff/issues/18684 +class C: + def f(self): + super = print + super(C, self) + + +import builtins + + +class C: + def f(self): + builtins.super(C, self) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index ba67756946f420..c98c917374d1be 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -66,7 +66,7 @@ impl AlwaysFixableViolation for SuperCallWithParameters { pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall) { // Only bother going through the super check at all if we're in a `super` call. // (We check this in `super_args` too, so this is just an optimization.) - if !is_super_call_with_arguments(call) { + if !is_super_call_with_arguments(call, checker) { return; } let scope = checker.semantic().current_scope(); @@ -167,10 +167,6 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall } /// Returns `true` if a call is an argumented `super` invocation. -fn is_super_call_with_arguments(call: &ast::ExprCall) -> bool { - if let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() { - id == "super" && !call.arguments.is_empty() - } else { - false - } +fn is_super_call_with_arguments(call: &ast::ExprCall, checker: &Checker) -> bool { + checker.semantic().match_builtin_expr(&call.func, "super") && !call.arguments.is_empty() } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap index 47a3305a796a9d..8a25f8e14bf7d3 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap @@ -142,3 +142,22 @@ UP008.py:92:14: UP008 [*] Use `super()` instead of `super(__class__, self)` 91 91 | def bar(self): 92 |- super(__class__, self).foo() 92 |+ super().foo() +93 93 | +94 94 | +95 95 | # see: https://github.com/astral-sh/ruff/issues/18684 + +UP008.py:107:23: UP008 [*] Use `super()` instead of `super(__class__, self)` + | +105 | class C: +106 | def f(self): +107 | builtins.super(C, self) + | ^^^^^^^^^ UP008 + | + = help: Remove `__super__` parameters + +ℹ Unsafe fix +104 104 | +105 105 | class C: +106 106 | def f(self): +107 |- builtins.super(C, self) + 107 |+ builtins.super() From 2b731d19b9b9bfa7f574a5a963bd6a2f14fc584a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 16 Jun 2025 22:58:05 +0100 Subject: [PATCH 442/487] [ty] Fix panic when attempting to provide autocompletions for an instance of a class that assigns attributes to `self[0]` (#18707) --- crates/ty_ide/src/completion.rs | 15 +++++ .../src/semantic_index/builder.rs | 6 +- .../src/semantic_index/place.rs | 57 ++++++++++++++++--- .../src/types/ide_support.rs | 6 +- 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index a8b93080b4f759..5fa6cb09f99515 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1835,6 +1835,21 @@ f"{f. test.assert_completions_include("method"); } + #[test] + fn no_panic_for_attribute_table_that_contains_subscript() { + let test = cursor_test( + r#" +class Point: + def orthogonal_direction(self): + self[0].is_zero + +def test_point(p2: Point): + p2. +"#, + ); + test.assert_completions_include("orthogonal_direction"); + } + impl CursorTest { fn completions(&self) -> String { self.completions_if(|_| true) diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index b8e3dc5d0e6120..c63c357d9fdc3e 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -1964,12 +1964,14 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { if let Ok(mut place_expr) = PlaceExpr::try_from(expr) { - if self.is_method_of_class().is_some() { + if self.is_method_of_class().is_some() + && place_expr.is_instance_attribute_candidate() + { // We specifically mark attribute assignments to the first parameter of a method, // i.e. typically `self` or `cls`. let accessed_object_refers_to_first_parameter = self .current_first_parameter_name - .is_some_and(|fst| place_expr.root_name().as_str() == fst); + .is_some_and(|fst| place_expr.root_name() == fst); if accessed_object_refers_to_first_parameter && place_expr.is_member() { place_expr.mark_instance_attribute(); diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs index f8fa9e147468ab..edbff982863a36 100644 --- a/crates/ty_python_semantic/src/semantic_index/place.rs +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -191,16 +191,59 @@ impl PlaceExpr { &self.root_name } + /// If the place expression has the form `.` + /// (meaning it *may* be an instance attribute), + /// return `Some()`. Else, return `None`. + /// + /// This method is internal to the semantic-index submodule. + /// It *only* checks that the AST structure of the `Place` is + /// correct. It does not check whether the `Place` actually occurred in + /// a method context, or whether the `` actually refers to the first + /// parameter of the method (i.e. `self`). To answer those questions, + /// use [`Self::as_instance_attribute`]. + pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> { + if self.sub_segments.len() == 1 { + self.sub_segments[0].as_member() + } else { + None + } + } + + /// Return `true` if the place expression has the form `.`, + /// indicating that it *may* be an instance attribute if we are in a method context. + /// + /// This method is internal to the semantic-index submodule. + /// It *only* checks that the AST structure of the `Place` is + /// correct. It does not check whether the `Place` actually occurred in + /// a method context, or whether the `` actually refers to the first + /// parameter of the method (i.e. `self`). To answer those questions, + /// use [`Self::is_instance_attribute`]. + pub(super) fn is_instance_attribute_candidate(&self) -> bool { + self.as_instance_attribute_candidate().is_some() + } + /// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)? pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool { - self.is_instance_attribute() - && self.sub_segments.len() == 1 - && self.sub_segments[0].as_member().unwrap().as_str() == name + self.as_instance_attribute().map(Name::as_str) == Some(name) } /// Is the place an instance attribute? - pub fn is_instance_attribute(&self) -> bool { - self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE) + pub(crate) fn is_instance_attribute(&self) -> bool { + let is_instance_attribute = self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE); + if is_instance_attribute { + debug_assert!(self.is_instance_attribute_candidate()); + } + is_instance_attribute + } + + /// Return `Some()` if the place expression is an instance attribute. + pub(crate) fn as_instance_attribute(&self) -> Option<&Name> { + if self.is_instance_attribute() { + debug_assert!(self.as_instance_attribute_candidate().is_some()); + self.as_instance_attribute_candidate() + } else { + None + } } /// Is the place used in its containing scope? @@ -570,9 +613,9 @@ impl PlaceTable { self.places().filter(|place_expr| place_expr.is_name()) } - pub fn instance_attributes(&self) -> impl Iterator { + pub fn instance_attributes(&self) -> impl Iterator { self.places() - .filter(|place_expr| place_expr.is_instance_attribute()) + .filter_map(|place_expr| place_expr.as_instance_attribute()) } /// Returns the place named `name`. diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 296c9266d562bb..1e491e478edbfc 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -185,10 +185,8 @@ impl AllMembers { let index = semantic_index(db, file); for function_scope_id in attribute_scopes(db, class_body_scope) { let place_table = index.place_table(function_scope_id); - for instance_attribute in place_table.instance_attributes() { - let name = instance_attribute.sub_segments()[0].as_member().unwrap(); - self.members.insert(name.clone()); - } + self.members + .extend(place_table.instance_attributes().cloned()); } } } From c22f809049b2a99368d14e64df1a5e57adc50520 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 17 Jun 2025 07:39:42 +0200 Subject: [PATCH 443/487] Hug closing `}` when f-string expression has a format specifier (#18704) --- .../test/fixtures/ruff/expression/fstring.py | 48 ++++-- .../test/fixtures/ruff/expression/tstring.py | 26 ++- .../src/comments/placement.rs | 47 ++--- crates/ruff_python_formatter/src/context.rs | 6 +- .../src/other/interpolated_string.rs | 4 +- .../src/other/interpolated_string_element.rs | 147 ++++++++-------- .../src/string/normalize.rs | 2 +- ...black_compatibility@cases__pep_701.py.snap | 17 +- .../format@expression__fstring.py.snap | 160 +++++++++++++----- .../format@expression__tstring.py.snap | 64 ++++--- .../ty_python_semantic/src/python_platform.rs | 4 +- 11 files changed, 325 insertions(+), 200 deletions(-) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py index f70290c01d3392..6a0cbc158116a0 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py @@ -242,18 +242,22 @@ }" # comment 19 # comment 20 -# Single-quoted f-strings with a format specificer can be multiline -f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee" +# The specifier of an f-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. +f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable +:.3f} ddddddddddddddd eeeeeeee" + +# The same applies for triple quoted f-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f} ddddddddddddddd eeeeeeee""" +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f +} ddddddddddddddd eeeeeeee""" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee""" - -# But, we can break the ones which don't have a format specifier -f"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { - xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd + # comment + :.3f} cccccccccc""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier @@ -285,6 +289,13 @@ # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + + x = f""" { # comment 22 x = :.0{y # comment 23 @@ -309,6 +320,21 @@ # comment 28 } woah {x}" + +f"""{foo + :a{ + a # comment 29 + # comment 30 + } +}""" + +# Regression test for https://github.com/astral-sh/ruff/issues/18672 +f"{ + # comment 31 + foo + :> +}" + # Assignment statement # Even though this f-string has multiline expression, thus allowing us to break it at the diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py index f430faab9b563a..a0321e4eb373ef 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tstring.py @@ -240,18 +240,20 @@ }" # comment 19 # comment 20 -# Single-quoted t-strings with a format specificer can be multiline +# The specifier of a t-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee" + variable + :.3f} ddddddddddddddd eeeeeeee" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee""" +# The same applies for triple quoted t-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f} ddddddddddddddd eeeeeeee""" +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f +} ddddddddddddddd eeeeeeee""" -# But, we can break the ones which don't have a format specifier -t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { - xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier @@ -283,6 +285,12 @@ # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + x = t""" { # comment 22 x = :.0{y # comment 23 diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 63075a7a0a7b1f..dd19db96b277f6 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -321,28 +321,33 @@ fn handle_enclosed_comment<'a>( }, AnyNodeRef::FString(fstring) => CommentPlacement::dangling(fstring, comment), AnyNodeRef::TString(tstring) => CommentPlacement::dangling(tstring, comment), - AnyNodeRef::InterpolatedElement(_) => { - // Handle comments after the format specifier (should be rare): - // - // ```python - // f"literal { - // expr:.3f - // # comment - // }" - // ``` - // - // This is a valid comment placement. - if matches!( - comment.preceding_node(), - Some( - AnyNodeRef::InterpolatedElement(_) - | AnyNodeRef::InterpolatedStringLiteralElement(_) - ) - ) { - CommentPlacement::trailing(comment.enclosing_node(), comment) - } else { - handle_bracketed_end_of_line_comment(comment, source) + AnyNodeRef::InterpolatedElement(element) => { + if let Some(preceding) = comment.preceding_node() { + if comment.line_position().is_own_line() && element.format_spec.is_some() { + return if comment.following_node().is_some() { + // Own line comment before format specifier + // ```py + // aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + // aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd + // # comment + // :.3f} cccccccccc""" + // ``` + CommentPlacement::trailing(preceding, comment) + } else { + // TODO: This can be removed once format specifiers with a newline are a syntax error. + // This is to handle cases like: + // ```py + // x = f"{x !s + // :>0 + // # comment 21 + // }" + // ``` + CommentPlacement::trailing(element, comment) + }; + } } + + handle_bracketed_end_of_line_comment(comment, source) } AnyNodeRef::ExprList(_) diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index b1b3a7941a3ec9..bddd6d84e08b18 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -7,7 +7,7 @@ use ruff_python_parser::Tokens; use crate::PyFormatOptions; use crate::comments::Comments; -use crate::other::interpolated_string_element::InterpolatedElementContext; +use crate::other::interpolated_string::InterpolatedStringContext; pub struct PyFormatContext<'a> { options: PyFormatOptions, @@ -143,7 +143,7 @@ pub(crate) enum InterpolatedStringState { /// curly brace in `f"foo {x}"`. /// /// The containing `FStringContext` is the surrounding f-string context. - InsideInterpolatedElement(InterpolatedElementContext), + InsideInterpolatedElement(InterpolatedStringContext), /// The formatter is outside an f-string. #[default] Outside, @@ -153,7 +153,7 @@ impl InterpolatedStringState { pub(crate) fn can_contain_line_breaks(self) -> Option { match self { InterpolatedStringState::InsideInterpolatedElement(context) => { - Some(context.can_contain_line_breaks()) + Some(context.is_multiline()) } InterpolatedStringState::Outside => None, } diff --git a/crates/ruff_python_formatter/src/other/interpolated_string.rs b/crates/ruff_python_formatter/src/other/interpolated_string.rs index 7a0c8b3c1c116d..c146395587500f 100644 --- a/crates/ruff_python_formatter/src/other/interpolated_string.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string.rs @@ -21,8 +21,8 @@ impl InterpolatedStringContext { self.enclosing_flags } - pub(crate) const fn layout(self) -> InterpolatedStringLayout { - self.layout + pub(crate) const fn is_multiline(self) -> bool { + matches!(self.layout, InterpolatedStringLayout::Multiline) } } diff --git a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs index 19e243f86a53c5..3111776cf8fadb 100644 --- a/crates/ruff_python_formatter/src/other/interpolated_string_element.rs +++ b/crates/ruff_python_formatter/src/other/interpolated_string_element.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use ruff_formatter::{Buffer, FormatOptions as _, RemoveSoftLinesBuffer, format_args, write}; use ruff_python_ast::{ AnyStringFlags, ConversionFlag, Expr, InterpolatedElement, InterpolatedStringElement, - InterpolatedStringLiteralElement, StringFlags, + InterpolatedStringLiteralElement, }; use ruff_text_size::{Ranged, TextSlice}; @@ -78,52 +78,10 @@ impl Format> for FormatFStringLiteralElement<'_> { } } -/// Context representing an f-string expression element. -#[derive(Clone, Copy, Debug)] -pub(crate) struct InterpolatedElementContext { - /// The context of the parent f-string containing this expression element. - parent_context: InterpolatedStringContext, - /// Indicates whether this expression element has format specifier or not. - has_format_spec: bool, -} - -impl InterpolatedElementContext { - /// Returns the [`InterpolatedStringContext`] containing this expression element. - pub(crate) fn interpolated_string(self) -> InterpolatedStringContext { - self.parent_context - } - - /// Returns `true` if the expression element can contain line breaks. - pub(crate) fn can_contain_line_breaks(self) -> bool { - self.parent_context.layout().is_multiline() - // For a triple-quoted f-string, the element can't be formatted into multiline if it - // has a format specifier because otherwise the newline would be treated as part of the - // format specifier. - // - // Given the following f-string: - // ```python - // f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f} ddddddddddddddd eeeeeeee""" - // ``` - // - // We can't format it as: - // ```python - // f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - // variable:.3f - // } ddddddddddddddd eeeeeeee""" - // ``` - // - // Here, the format specifier string would become ".3f\n", which is not what we want. - // But, if the original source code already contained a newline, they'll be preserved. - // - // The Python version is irrelevant in this case. - && !(self.parent_context.flags().is_triple_quoted() && self.has_format_spec) - } -} - /// Formats an f-string expression element. pub(crate) struct FormatInterpolatedElement<'a> { element: &'a InterpolatedElement, - context: InterpolatedElementContext, + context: InterpolatedStringContext, } impl<'a> FormatInterpolatedElement<'a> { @@ -131,13 +89,7 @@ impl<'a> FormatInterpolatedElement<'a> { element: &'a InterpolatedElement, context: InterpolatedStringContext, ) -> Self { - Self { - element, - context: InterpolatedElementContext { - parent_context: context, - has_format_spec: element.format_spec.is_some(), - }, - } + Self { element, context } } } @@ -151,6 +103,8 @@ impl Format> for FormatInterpolatedElement<'_> { .. } = self.element; + let expression = &**expression; + if let Some(debug_text) = debug_text { token("{").fmt(f)?; @@ -179,7 +133,7 @@ impl Format> for FormatInterpolatedElement<'_> { f, [ NormalizedDebugText(&debug_text.leading), - verbatim_text(&**expression), + verbatim_text(expression), NormalizedDebugText(&debug_text.trailing), ] )?; @@ -202,6 +156,8 @@ impl Format> for FormatInterpolatedElement<'_> { let comments = f.context().comments().clone(); let dangling_item_comments = comments.dangling(self.element); + let multiline = self.context.is_multiline(); + // If an expression starts with a `{`, we need to add a space before the // curly brace to avoid turning it into a literal curly with `{{`. // @@ -216,7 +172,7 @@ impl Format> for FormatInterpolatedElement<'_> { // added to maintain consistency. let bracket_spacing = needs_bracket_spacing(expression, f.context()).then_some(format_with(|f| { - if self.context.can_contain_line_breaks() { + if multiline { soft_line_break_or_space().fmt(f) } else { space().fmt(f) @@ -241,30 +197,48 @@ impl Format> for FormatInterpolatedElement<'_> { } if let Some(format_spec) = format_spec.as_deref() { + // ```py + // f"{ + // foo + // # comment 27 + // :test}" + // ``` + if comments.has_trailing_own_line(expression) { + soft_line_break().fmt(f)?; + } + token(":").fmt(f)?; for element in &format_spec.elements { - FormatInterpolatedStringElement::new( - element, - self.context.interpolated_string(), - ) - .fmt(f)?; + FormatInterpolatedStringElement::new(element, self.context).fmt(f)?; } - - // These trailing comments can only occur if the format specifier is - // present. For example, - // - // ```python - // f"{ - // x:.3f - // # comment - // }" - // ``` - // - // Any other trailing comments are attached to the expression itself. - trailing_comments(comments.trailing(self.element)).fmt(f)?; } + // These trailing comments can only occur if the format specifier is + // present. For example, + // + // ```python + // f"{ + // x:.3f + // # comment + // }" + // ``` + + // This can also be triggered outside of a format spec, at + // least until https://github.com/astral-sh/ruff/issues/18632 is a syntax error + // TODO(https://github.com/astral-sh/ruff/issues/18632) Remove this + // and double check if it is still necessary for the triple quoted case + // once this is a syntax error. + // ```py + // f"{ + // foo + // :{x} + // # comment 28 + // } woah {x}" + // ``` + // Any other trailing comments are attached to the expression itself. + trailing_comments(comments.trailing(self.element)).fmt(f)?; + if conversion.is_none() && format_spec.is_none() { bracket_spacing.fmt(f)?; } @@ -283,12 +257,31 @@ impl Format> for FormatInterpolatedElement<'_> { { let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); - if self.context.can_contain_line_breaks() { - group(&format_args![ - open_parenthesis_comments, - soft_block_indent(&item) - ]) - .fmt(&mut f)?; + if self.context.is_multiline() { + // TODO: The `or comments.has_trailing...` can be removed once newlines in format specs are a syntax error. + // This is to support the following case: + // ```py + // x = f"{x !s + // :>0 + // # comment 21 + // }" + // ``` + if format_spec.is_none() || comments.has_trailing_own_line(self.element) { + group(&format_args![ + open_parenthesis_comments, + soft_block_indent(&item) + ]) + .fmt(&mut f)?; + } else { + // For strings ending with a format spec, don't add a newline between the end of the format spec + // and closing curly brace because that is invalid syntax for single quoted strings and + // the newline is preserved as part of the format spec for triple quoted strings. + group(&format_args![ + open_parenthesis_comments, + indent(&format_args![soft_line_break(), item]) + ]) + .fmt(&mut f)?; + } } else { let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); diff --git a/crates/ruff_python_formatter/src/string/normalize.rs b/crates/ruff_python_formatter/src/string/normalize.rs index 1e694a66ff1508..b7aa0605d78d6b 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -50,7 +50,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { if let InterpolatedStringState::InsideInterpolatedElement(parent_context) = self.context.interpolated_string_state() { - let parent_flags = parent_context.interpolated_string().flags(); + let parent_flags = parent_context.flags(); if !parent_flags.is_triple_quoted() || string.flags().is_triple_quoted() { // This logic is even necessary when using preserve and the target python version doesn't support PEP701 because // we might end up joining two f-strings that have different quote styles, in which case we need to alternate the quotes diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__pep_701.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__pep_701.py.snap index 1b074e6b45c67d..e31f4723b97ea3 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__pep_701.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__pep_701.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py -snapshot_kind: text --- ## Input @@ -170,7 +169,7 @@ rf"\{"a"}" x = """foo {{ {2 + 2}bar baz""" -@@ -28,74 +26,62 @@ +@@ -28,55 +26,48 @@ x = f"""foo {{ {2 + 2}bar {{ baz""" @@ -242,12 +241,7 @@ rf"\{"a"}" f"{2+2=}" f"{2+2 = }" - f"{ 2 + 2 = }" - --f"""foo { -- datetime.datetime.now():%Y -+f"""foo {datetime.datetime.now():%Y - %m +@@ -88,14 +79,10 @@ %d }""" @@ -264,7 +258,7 @@ rf"\{"a"}" ) f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \ -@@ -105,8 +91,10 @@ +@@ -105,8 +92,10 @@ rf"\{{\}}" f""" @@ -277,7 +271,7 @@ rf"\{"a"}" """ value: str = f"""foo -@@ -124,13 +112,15 @@ +@@ -124,13 +113,15 @@ f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}' @@ -378,7 +372,8 @@ f"{2+2=}" f"{2+2 = }" f"{ 2 + 2 = }" -f"""foo {datetime.datetime.now():%Y +f"""foo { + datetime.datetime.now():%Y %m %d }""" diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap index 913dcaf547dd4b..1d5f69f2346236 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap @@ -248,18 +248,22 @@ f"{ # comment 15 }" # comment 19 # comment 20 -# Single-quoted f-strings with a format specificer can be multiline -f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee" +# The specifier of an f-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. +f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable +:.3f} ddddddddddddddd eeeeeeee" + +# The same applies for triple quoted f-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f} ddddddddddddddd eeeeeeee""" +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f +} ddddddddddddddd eeeeeeee""" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee""" - -# But, we can break the ones which don't have a format specifier -f"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { - xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd + # comment + :.3f} cccccccccc""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier @@ -291,6 +295,13 @@ x = f"{x !s # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + + x = f""" { # comment 22 x = :.0{y # comment 23 @@ -315,6 +326,21 @@ f"{ # comment 26 # comment 28 } woah {x}" + +f"""{foo + :a{ + a # comment 29 + # comment 30 + } +}""" + +# Regression test for https://github.com/astral-sh/ruff/issues/18672 +f"{ + # comment 31 + foo + :> +}" + # Assignment statement # Even though this f-string has multiline expression, thus allowing us to break it at the @@ -1008,26 +1034,32 @@ f"{ # comment 15 }" # comment 19 # comment 20 -# Single-quoted f-strings with a format specificer can be multiline +# The specifier of an f-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f -} ddddddddddddddd eeeeeeee" + variable:.3f} ddddddddddddddd eeeeeeee" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f} ddddddddddddddd eeeeeeee""" +# The same applies for triple quoted f-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee""" +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f +} ddddddddddddddd eeeeeeee""" -# But, we can break the ones which don't have a format specifier -f"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr {xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { - xxxxxxxxxxxxxxxxxxxx -} bbbbbbbbbbbb""" +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd + # comment + :.3f} cccccccccc""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier -aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment } cccccccccc""" -aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment} cccccccccc""" # Conversion flags @@ -1047,6 +1079,13 @@ x = f"{ # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + + x = f""" { # comment 22 x = :.0{y # comment 23 @@ -1071,6 +1110,19 @@ f"{ # comment 26 # comment 28 } woah {x}" + +f"""{ + foo:a{ + a # comment 29 + # comment 30 + } +}""" + +# Regression test for https://github.com/astral-sh/ruff/issues/18672 +f"{ + # comment 31 + foo:>}" + # Assignment statement # Even though this f-string has multiline expression, thus allowing us to break it at the @@ -1236,10 +1288,12 @@ aaaaaaaaaaaaaaaaaa = ( ) # The newline is only considered when it's a tripled-quoted f-string. -aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment -aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment # Remove the parentheses here @@ -1804,26 +1858,32 @@ f"{ # comment 15 }" # comment 19 # comment 20 -# Single-quoted f-strings with a format specificer can be multiline +# The specifier of an f-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f -} ddddddddddddddd eeeeeeee" + variable:.3f} ddddddddddddddd eeeeeeee" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f} ddddddddddddddd eeeeeeee""" +# The same applies for triple quoted f-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee""" +f"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f +} ddddddddddddddd eeeeeeee""" -# But, we can break the ones which don't have a format specifier -f"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr {xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { - xxxxxxxxxxxxxxxxxxxx -} bbbbbbbbbbbb""" +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd + # comment + :.3f} cccccccccc""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier -aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment } cccccccccc""" -aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment} cccccccccc""" # Conversion flags @@ -1843,6 +1903,13 @@ x = f"{ # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + + x = f""" { # comment 22 x = :.0{y # comment 23 @@ -1867,6 +1934,19 @@ f"{ # comment 26 # comment 28 } woah {x}" + +f"""{ + foo:a{ + a # comment 29 + # comment 30 + } +}""" + +# Regression test for https://github.com/astral-sh/ruff/issues/18672 +f"{ + # comment 31 + foo:>}" + # Assignment statement # Even though this f-string has multiline expression, thus allowing us to break it at the @@ -2032,10 +2112,12 @@ aaaaaaaaaaaaaaaaaa = ( ) # The newline is only considered when it's a tripled-quoted f-string. -aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment -aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = f"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment # Remove the parentheses here diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap index e916c1ee27e485..fef750a2a92256 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__tstring.py.snap @@ -246,18 +246,20 @@ t"{ # comment 15 }" # comment 19 # comment 20 -# Single-quoted t-strings with a format specificer can be multiline +# The specifier of a t-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee" + variable + :.3f} ddddddddddddddd eeeeeeee" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f} ddddddddddddddd eeeeeeee""" +# The same applies for triple quoted t-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f} ddddddddddddddd eeeeeeee""" +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable + :.3f +} ddddddddddddddd eeeeeeee""" -# But, we can break the ones which don't have a format specifier -t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr { - xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxxxxxx } bbbbbbbbbbbb""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier @@ -289,6 +291,12 @@ x = t"{x !s # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + x = t""" { # comment 22 x = :.0{y # comment 23 @@ -1004,26 +1012,28 @@ t"{ # comment 15 }" # comment 19 # comment 20 -# Single-quoted t-strings with a format specificer can be multiline +# The specifier of a t-string must hug the closing `}` because a multiline format specifier is invalid syntax in a single +# quoted f-string. t"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { - variable:.3f -} ddddddddddddddd eeeeeeee" + variable:.3f} ddddddddddddddd eeeeeeee" -# But, if it's triple-quoted then we can't or the format specificer will have a -# trailing newline -t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc {variable:.3f} ddddddddddddddd eeeeeeee""" +# The same applies for triple quoted t-strings, except that we need to preserve the newline before the closing `}`. +# or we risk altering the meaning of the f-string. +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f} ddddddddddddddd eeeeeeee""" +t"""aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccc { + variable:.3f +} ddddddddddddddd eeeeeeee""" -# But, we can break the ones which don't have a format specifier -t"""fooooooooooooooooooo barrrrrrrrrrrrrrrrrrr {xxxxxxxxxxxxxxx:.3f} aaaaaaaaaaaaaaaaa { - xxxxxxxxxxxxxxxxxxxx -} bbbbbbbbbbbb""" # Throw in a random comment in it but surprise, this is not a comment but just a text # which is part of the format specifier -aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment } cccccccccc""" -aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f +aaaaaaaaaaa = t"""asaaaaaaaaaaaaaaaa { + aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f # comment} cccccccccc""" # Conversion flags @@ -1043,6 +1053,12 @@ x = t"{ # comment 21 }" +x = f"{ + x!s:>{ + 0 + # comment 21-2 + }}" + x = t""" { # comment 22 x = :.0{y # comment 23 @@ -1232,10 +1248,12 @@ aaaaaaaaaaaaaaaaaa = ( ) # The newline is only considered when it's a tripled-quoted t-string. -aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment -aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{a:.3f +aaaaaaaaaaaaaaaaaa = t"""testeeeeeeeeeeeeeeeeeeeeeeeee{ + a:.3f }moreeeeeeeeeeeeeeeeeetest""" # comment # Remove the parentheses here diff --git a/crates/ty_python_semantic/src/python_platform.rs b/crates/ty_python_semantic/src/python_platform.rs index 0822165322faa7..573165f82edab7 100644 --- a/crates/ty_python_semantic/src/python_platform.rs +++ b/crates/ty_python_semantic/src/python_platform.rs @@ -1,12 +1,10 @@ use std::fmt::{Display, Formatter}; -use ruff_macros::RustDoc; - /// The target platform to assume when resolving types. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr( feature = "serde", - derive(serde::Serialize, serde::Deserialize, RustDoc), + derive(serde::Serialize, serde::Deserialize, ruff_macros::RustDoc), serde(rename_all = "kebab-case") )] pub enum PythonPlatform { From 3a77768f7948863882485afcff994b8951c8ba9d Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Jun 2025 09:24:28 +0200 Subject: [PATCH 444/487] [ty] Reachability constraints (#18621) ## Summary * Completely removes the concept of visibility constraints. Reachability constraints are now used to model the static visibility of bindings and declarations. Reachability constraints are *much* easier to reason about / work with, since they are applied at the beginning of a branch, and not applied retroactively. Removing the duplication between visibility and reachability constraints also leads to major code simplifications [^1]. For an overview of how the new constraint system works, see the updated doc comment in `reachability_constraints.rs`. * Fixes a [control-flow modeling bug (panic)](https://github.com/astral-sh/ty/issues/365) involving `break` statements in loops * Fixes a [bug where](https://github.com/astral-sh/ty/issues/624) where `elif` branches would have wrong reachability constraints * Fixes a [bug where](https://github.com/astral-sh/ty/issues/648) code after infinite loops would not be considered unreachble * Fixes a panic on the `pywin32` ecosystem project, which we should be able to move to `good.txt` once this has been merged. * Removes some false positives in unreachable code because we infer `Never` more often, due to the fact that reachability constraints now apply retroactively to *all* active bindings, not just to bindings inside a branch. * As one example, this removes the `division-by-zero` diagnostic from https://github.com/astral-sh/ty/issues/443 because we now infer `Never` for the divisor. * Supersedes and includes similar test changes as https://github.com/astral-sh/ruff/pull/18392 closes https://github.com/astral-sh/ty/issues/365 closes https://github.com/astral-sh/ty/issues/624 closes https://github.com/astral-sh/ty/issues/642 closes https://github.com/astral-sh/ty/issues/648 ## Benchmarks Benchmarks on black, pandas, and sympy showed that this is neither a performance improvement, nor a regression. ## Test Plan Regression tests for: - [x] https://github.com/astral-sh/ty/issues/365 - [x] https://github.com/astral-sh/ty/issues/624 - [x] https://github.com/astral-sh/ty/issues/642 - [x] https://github.com/astral-sh/ty/issues/648 [^1]: I'm afraid this is something that @carljm advocated for since the beginning, and I'm not sure anymore why we have never seriously tried this before. So I suggest we do *not* attempt to do a historical deep dive to find out exactly why this ever became so complicated, and just enjoy the fact that we eventually arrived here. --------- Co-authored-by: Carl Meyer --- crates/ty_ide/src/completion.rs | 17 +- .../resources/mdtest/import/star.md | 4 +- .../mdtest/narrow/conditionals/boolean.md | 4 +- .../resources/mdtest/narrow/truthiness.md | 2 +- .../resources/mdtest/narrow/type.md | 3 +- .../mdtest/statically_known_branches.md | 33 ++ .../resources/mdtest/unreachable.md | 64 ++-- crates/ty_python_semantic/src/place.rs | 138 ++++---- .../ty_python_semantic/src/semantic_index.rs | 2 +- .../src/semantic_index/builder.rs | 220 ++++-------- .../src/semantic_index/place.rs | 8 +- .../src/semantic_index/predicate.rs | 8 +- ...traints.rs => reachability_constraints.rs} | 332 ++++++++++-------- .../src/semantic_index/use_def.rs | 332 ++++++------------ .../src/semantic_index/use_def/place_state.rs | 261 +++++++------- crates/ty_python_semantic/src/types.rs | 4 + crates/ty_python_semantic/src/types/class.rs | 28 +- crates/ty_python_semantic/src/types/infer.rs | 25 +- 18 files changed, 681 insertions(+), 804 deletions(-) rename crates/ty_python_semantic/src/semantic_index/{visibility_constraints.rs => reachability_constraints.rs} (68%) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 5fa6cb09f99515..ae0f5d59002121 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1202,7 +1202,7 @@ print(f\"{some } #[test] - fn statically_invisible_symbols() { + fn statically_unreachable_symbols() { let test = cursor_test( "\ if 1 + 2 != 3: @@ -1850,6 +1850,21 @@ def test_point(p2: Point): test.assert_completions_include("orthogonal_direction"); } + #[test] + fn regression_test_issue_642() { + // Regression test for https://github.com/astral-sh/ty/issues/642 + + let test = cursor_test( + r#" + match 0: + case 1 i: + pass + "#, + ); + + assert_snapshot!(test.completions(), @r""); + } + impl CursorTest { fn completions(&self) -> String { self.completions_if(|_| true) diff --git a/crates/ty_python_semantic/resources/mdtest/import/star.md b/crates/ty_python_semantic/resources/mdtest/import/star.md index a20f7259ef3fff..5b00e498127e48 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/star.md +++ b/crates/ty_python_semantic/resources/mdtest/import/star.md @@ -827,8 +827,8 @@ if sys.version_info >= (3, 12): from exporter import * # it's correct to have no diagnostics here as this branch is unreachable - reveal_type(A) # revealed: Unknown - reveal_type(B) # revealed: bool + reveal_type(A) # revealed: Never + reveal_type(B) # revealed: Never else: from exporter import * diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/boolean.md b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/boolean.md index 8b9394a380522f..666c5e6b68281f 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/boolean.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/boolean.md @@ -52,7 +52,7 @@ def _(x: A | B): if False and isinstance(x, A): # TODO: should emit an `unreachable code` diagnostic - reveal_type(x) # revealed: A + reveal_type(x) # revealed: Never else: reveal_type(x) # revealed: A | B @@ -65,7 +65,7 @@ def _(x: A | B): reveal_type(x) # revealed: A | B else: # TODO: should emit an `unreachable code` diagnostic - reveal_type(x) # revealed: B & ~A + reveal_type(x) # revealed: Never reveal_type(x) # revealed: A | B ``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/truthiness.md b/crates/ty_python_semantic/resources/mdtest/narrow/truthiness.md index b797ee4a16e7e1..d6df7c3276e1b1 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/truthiness.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/truthiness.md @@ -199,7 +199,7 @@ def f(x: Literal[0, 1], y: Literal["", "hello"]): reveal_type(y) # revealed: Literal["", "hello"] if (x or not x) and (y and not y): - reveal_type(x) # revealed: Literal[0, 1] + reveal_type(x) # revealed: Never reveal_type(y) # revealed: Never else: # ~(x or not x) or ~(y and not y) diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/type.md b/crates/ty_python_semantic/resources/mdtest/narrow/type.md index c5d8bfbe1f9438..5487a1af41ac3d 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/type.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/type.md @@ -127,7 +127,8 @@ class B: ... def _[T](x: A | B): if type(x) is A[str]: - reveal_type(x) # revealed: (A[int] & A[Unknown]) | (B & A[Unknown]) + # `type()` never returns a generic alias, so `type(x)` cannot be `A[str]` + reveal_type(x) # revealed: Never else: reveal_type(x) # revealed: A[int] | B ``` diff --git a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md index 63db73d206eb84..1417714ac2817b 100644 --- a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md @@ -994,6 +994,39 @@ else: reveal_type(x) # revealed: Literal[1] ``` +#### `if` nested inside `while True` + +These are regression test for . First, make sure that we +do not panic in the original scenario: + +```py +def flag() -> bool: + return True + +while True: + if flag(): + break + else: + c = 1 + break + +c # error: [possibly-unresolved-reference] +``` + +And also check that we understand control flow correctly: + +```py +c = 1 + +while True: + if False: + c = 2 + break + break + +reveal_type(c) # revealed: Literal[1] +``` + ## `match` statements ```toml diff --git a/crates/ty_python_semantic/resources/mdtest/unreachable.md b/crates/ty_python_semantic/resources/mdtest/unreachable.md index 0348816b49d6fc..d5cf7b53fcc17b 100644 --- a/crates/ty_python_semantic/resources/mdtest/unreachable.md +++ b/crates/ty_python_semantic/resources/mdtest/unreachable.md @@ -72,6 +72,17 @@ def f2(): # TODO: we should mark this as unreachable print("unreachable") + +def f3(): + if False: + return + elif True: + return + else: + pass + + # TODO: we should mark this as unreachable + print("unreachable") ``` ### `Never` / `NoReturn` @@ -211,9 +222,8 @@ reachable or not. Some developers like to use things like early `return` stateme and for this use case, it is helpful to still see some diagnostics in unreachable sections. We currently follow the second approach, but we do not attempt to provide the full set of -diagnostics in unreachable sections. In fact, we silence a certain category of diagnostics -(`unresolved-reference`, `unresolved-attribute`, …), in order to avoid *incorrect* diagnostics. In -the future, we may revisit this decision. +diagnostics in unreachable sections. In fact, a large number of diagnostics are suppressed in +unreachable code, simply due to the fact that we infer `Never` for most of the symbols. ### Use of variables in unreachable code @@ -301,7 +311,8 @@ elif sys.version_info >= (3, 11): elif sys.version_info >= (3, 10): pass else: - pass + # This branch is also unreachable, because the previous `elif` branch is always true + ExceptionGroup # no error here ``` And for nested `if` statements: @@ -378,6 +389,18 @@ while sys.version_info >= (3, 11): ExceptionGroup ``` +### Infinite loops + +We also do not emit diagnostics in unreachable code after an infinite loop: + +```py +def f(): + while True: + pass + + ExceptionGroup # no error here +``` + ### Silencing errors for actually unknown symbols We currently also silence diagnostics for symbols that are not actually defined anywhere. It is @@ -500,33 +523,26 @@ def f(): 1 / 0 # error: [division-by-zero] ``` -## Limitations of the current approach +### Conflicting type information -The current approach of silencing only a subset of diagnostics in unreachable code leads to some -problems, and we may want to re-evaluate this decision in the future. To illustrate, consider the -following example: +We also support cases where type information for symbols conflicts between mutually exclusive +branches: ```py -if False: +import sys + +if sys.version_info >= (3, 11): x: int = 1 else: x: str = "a" -if False: - # TODO We currently emit a false positive here: - # error: [invalid-assignment] "Object of type `Literal["a"]` is not assignable to `int`" +if sys.version_info >= (3, 11): other: int = x else: other: str = x ``` -The problem here originates from the fact that the type of `x` in the `False` branch conflicts with -the visible type of `x` in the `True` branch. When we type-check the lower `False` branch, we only -see the visible definition of `x`, which has a type of `str`. - -In principle, this means that all diagnostics that depend on type information from "outside" the -unreachable section should be silenced. Similar problems to the one above can occur for other rule -types as well: +This is also supported for function calls, attribute accesses, etc.: ```py from typing import Literal @@ -554,24 +570,14 @@ else: number: Literal[0] = 0 if False: - # TODO - # error: [invalid-argument-type] f(2) - # TODO - # error: [unknown-argument] g(a=2, b=3) - # TODO - # error: [invalid-assignment] C.x = 2 d: D = D() - # TODO - # error: [call-non-callable] d() - # TODO - # error: [division-by-zero] 1 / number ``` diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 253cef46e87c6f..9129edee8c5e10 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -755,65 +755,63 @@ fn place_from_bindings_impl<'db>( requires_explicit_reexport: RequiresExplicitReExport, ) -> Place<'db> { let predicates = bindings_with_constraints.predicates; - let visibility_constraints = bindings_with_constraints.visibility_constraints; + let reachability_constraints = bindings_with_constraints.reachability_constraints; let mut bindings_with_constraints = bindings_with_constraints.peekable(); let is_non_exported = |binding: Definition<'db>| { requires_explicit_reexport.is_yes() && !is_reexported(db, binding) }; - let unbound_visibility_constraint = match bindings_with_constraints.peek() { + let unbound_reachability_constraint = match bindings_with_constraints.peek() { Some(BindingWithConstraints { binding, - visibility_constraint, + reachability_constraint, narrowing_constraint: _, - }) if binding.is_undefined_or(is_non_exported) => Some(*visibility_constraint), + }) if binding.is_undefined_or(is_non_exported) => Some(*reachability_constraint), _ => None, }; - let mut deleted_visibility = Truthiness::AlwaysFalse; + let mut deleted_reachability = Truthiness::AlwaysFalse; // Evaluate this lazily because we don't always need it (for example, if there are no visible - // bindings at all, we don't need it), and it can cause us to evaluate visibility constraint + // bindings at all, we don't need it), and it can cause us to evaluate reachability constraint // expressions, which is extra work and can lead to cycles. - let unbound_visibility = || { - unbound_visibility_constraint - .map(|visibility_constraint| { - visibility_constraints.evaluate(db, predicates, visibility_constraint) - }) - .unwrap_or(Truthiness::AlwaysFalse) + let unbound_reachability = || { + unbound_reachability_constraint.map(|reachability_constraint| { + reachability_constraints.evaluate(db, predicates, reachability_constraint) + }) }; let mut types = bindings_with_constraints.filter_map( |BindingWithConstraints { binding, narrowing_constraint, - visibility_constraint, + reachability_constraint, }| { - let binding = - match binding { - DefinitionState::Defined(binding) => binding, - DefinitionState::Undefined => { - return None; - } - DefinitionState::Deleted => { - deleted_visibility = deleted_visibility.or( - visibility_constraints.evaluate(db, predicates, visibility_constraint) - ); - return None; - } - }; + let binding = match binding { + DefinitionState::Defined(binding) => binding, + DefinitionState::Undefined => { + return None; + } + DefinitionState::Deleted => { + deleted_reachability = deleted_reachability.or( + reachability_constraints.evaluate(db, predicates, reachability_constraint) + ); + return None; + } + }; if is_non_exported(binding) { return None; } - let static_visibility = - visibility_constraints.evaluate(db, predicates, visibility_constraint); + let static_reachability = + reachability_constraints.evaluate(db, predicates, reachability_constraint); - if static_visibility.is_always_false() { - // We found a binding that we have statically determined to not be visible from - // the use of the place that we are investigating. There are three interesting - // cases to consider: + if static_reachability.is_always_false() { + // If the static reachability evaluates to false, the binding is either not reachable + // from the start of the scope, or there is no control flow path from that binding to + // the use of the place that we are investigating. There are three interesting cases + // to consider: // // ```py // def f1(): @@ -827,35 +825,37 @@ fn place_from_bindings_impl<'db>( // use(y) // // def f3(flag: bool): - // z = 1 // if flag: + // z = 1 + // else: // z = 2 // return // use(z) // ``` // - // In the first case, there is a single binding for `x`, and due to the statically - // known `False` condition, it is not visible at the use of `x`. However, we *can* - // see/reach the start of the scope from `use(x)`. This means that `x` is unbound - // and we should return `None`. + // In the first case, there is a single binding for `x`, but it is not reachable from + // the start of the scope. However, the use of `x` is reachable (`unbound_reachability` + // is not always-false). This means that `x` is unbound and we should return `None`. // - // In the second case, `y` is also not visible at the use of `y`, but here, we can - // not see/reach the start of the scope. There is only one path of control flow, - // and it passes through that binding of `y` (which we can not see). This implies - // that we are in an unreachable section of code. We return `Never` in order to - // silence the `unresolve-reference` diagnostic that would otherwise be emitted at - // the use of `y`. + // In the second case, the binding of `y` is reachable, but there is no control flow + // path from the beginning of the scope, through that binding, to the use of `y` that + // we are investigating. There is also no control flow path from the start of the + // scope, through the implicit `y = ` binding, to the use of `y`. This means + // that `unbound_reachability` is always false. Since there are no other bindings, no + // control flow path can reach this use of `y`, implying that we are in unreachable + // section of code. We return `Never` in order to silence the `unresolve-reference` + // diagnostic that would otherwise be emitted at the use of `y`. // - // In the third case, we have two bindings for `z`. The first one is visible, so we - // consider the case that we now encounter the second binding `z = 2`, which is not - // visible due to the early return. We *also* can not see the start of the scope - // from `use(z)` because both paths of control flow pass through a binding of `z`. - // The `z = 1` binding is visible, and so we are *not* in an unreachable section of - // code. However, it is still okay to return `Never` in this case, because we will - // union the types of all bindings, and `Never` will be eliminated automatically. - - if unbound_visibility().is_always_false() { - // The scope-start is not visible + // In the third case, we have two bindings for `z`. The first one is visible (there + // is a path of control flow from the start of the scope, through that binding, to + // the use of `z`). So we consider the case that we now encounter the second binding + // `z = 2`, which is not visible due to the early return. The `z = ` binding + // is not live (shadowed by the other bindings), so `unbound_reachability` is `None`. + // Here, we are *not* in an unreachable section of code. However, it is still okay to + // return `Never` in this case, because we will union the types of all bindings, and + // `Never` will be eliminated automatically. + + if unbound_reachability().is_none_or(Truthiness::is_always_false) { return Some(Type::Never); } return None; @@ -867,14 +867,14 @@ fn place_from_bindings_impl<'db>( ); if let Some(first) = types.next() { - let boundness = match unbound_visibility() { - Truthiness::AlwaysTrue => { + let boundness = match unbound_reachability() { + Some(Truthiness::AlwaysTrue) => { unreachable!( - "If we have at least one binding, the scope-start should not be definitely visible" + "If we have at least one binding, the implicit `unbound` binding should not be definitely visible" ) } - Truthiness::AlwaysFalse => Boundness::Bound, - Truthiness::Ambiguous => Boundness::PossiblyUnbound, + Some(Truthiness::AlwaysFalse) | None => Boundness::Bound, + Some(Truthiness::Ambiguous) => Boundness::PossiblyUnbound, }; let ty = if let Some(second) = types.next() { @@ -882,7 +882,7 @@ fn place_from_bindings_impl<'db>( } else { first }; - match deleted_visibility { + match deleted_reachability { Truthiness::AlwaysFalse => Place::Type(ty, boundness), Truthiness::AlwaysTrue => Place::Unbound, Truthiness::Ambiguous => Place::Type(ty, Boundness::PossiblyUnbound), @@ -903,19 +903,19 @@ fn place_from_declarations_impl<'db>( requires_explicit_reexport: RequiresExplicitReExport, ) -> PlaceFromDeclarationsResult<'db> { let predicates = declarations.predicates; - let visibility_constraints = declarations.visibility_constraints; + let reachability_constraints = declarations.reachability_constraints; let mut declarations = declarations.peekable(); let is_non_exported = |declaration: Definition<'db>| { requires_explicit_reexport.is_yes() && !is_reexported(db, declaration) }; - let undeclared_visibility = match declarations.peek() { + let undeclared_reachability = match declarations.peek() { Some(DeclarationWithConstraint { declaration, - visibility_constraint, + reachability_constraint, }) if declaration.is_undefined_or(is_non_exported) => { - visibility_constraints.evaluate(db, predicates, *visibility_constraint) + reachability_constraints.evaluate(db, predicates, *reachability_constraint) } _ => Truthiness::AlwaysFalse, }; @@ -923,7 +923,7 @@ fn place_from_declarations_impl<'db>( let mut types = declarations.filter_map( |DeclarationWithConstraint { declaration, - visibility_constraint, + reachability_constraint, }| { let DefinitionState::Defined(declaration) = declaration else { return None; @@ -933,10 +933,10 @@ fn place_from_declarations_impl<'db>( return None; } - let static_visibility = - visibility_constraints.evaluate(db, predicates, visibility_constraint); + let static_reachability = + reachability_constraints.evaluate(db, predicates, reachability_constraint); - if static_visibility.is_always_false() { + if static_reachability.is_always_false() { None } else { Some(declaration_type(db, declaration)) @@ -964,10 +964,10 @@ fn place_from_declarations_impl<'db>( first }; if conflicting.is_empty() { - let boundness = match undeclared_visibility { + let boundness = match undeclared_reachability { Truthiness::AlwaysTrue => { unreachable!( - "If we have at least one declaration, the scope-start should not be definitely visible" + "If we have at least one declaration, the implicit `unbound` binding should not be definitely visible" ) } Truthiness::AlwaysFalse => Boundness::Bound, diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index 0b71130d1eb08c..c39a5b12df8e49 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -33,8 +33,8 @@ pub(crate) mod narrowing_constraints; pub mod place; pub(crate) mod predicate; mod re_exports; +mod reachability_constraints; mod use_def; -mod visibility_constraints; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index c63c357d9fdc3e..3e608a5c6e990a 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -40,12 +40,12 @@ use crate::semantic_index::predicate::{ StarImportPlaceholderPredicate, }; use crate::semantic_index::re_exports::exported_names; +use crate::semantic_index::reachability_constraints::{ + ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, +}; use crate::semantic_index::use_def::{ EagerSnapshotKey, FlowSnapshot, ScopedEagerSnapshotId, UseDefMapBuilder, }; -use crate::semantic_index::visibility_constraints::{ - ScopedVisibilityConstraintId, VisibilityConstraintsBuilder, -}; use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue}; use crate::{Db, Program}; @@ -157,7 +157,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { builder.push_scope_with_parent( NodeWithScopeRef::Module, None, - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, ); builder @@ -237,7 +237,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { &mut self, node: NodeWithScopeRef, parent: Option, - reachability: ScopedVisibilityConstraintId, + reachability: ScopedReachabilityConstraintId, ) { let children_start = self.scopes.next_index() + 1; @@ -354,9 +354,9 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { &self.use_def_maps[scope_id] } - fn current_visibility_constraints_mut(&mut self) -> &mut VisibilityConstraintsBuilder { + fn current_reachability_constraints_mut(&mut self) -> &mut ReachabilityConstraintsBuilder { let scope_id = self.current_scope(); - &mut self.use_def_maps[scope_id].visibility_constraints + &mut self.use_def_maps[scope_id].reachability_constraints } fn current_ast_ids(&mut self) -> &mut AstIdsBuilder { @@ -576,55 +576,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { id } - /// Records a previously added visibility constraint by applying it to all live bindings - /// and declarations. - fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { - self.current_use_def_map_mut() - .record_visibility_constraint(constraint); - } - - /// Negates the given visibility constraint and then adds it to all live bindings and declarations. - fn record_negated_visibility_constraint( - &mut self, - constraint: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { - let id = self - .current_visibility_constraints_mut() - .add_not_constraint(constraint); - self.record_visibility_constraint_id(id); - id - } - - /// Records a visibility constraint by applying it to all live bindings and declarations. - fn record_visibility_constraint( - &mut self, - predicate: Predicate<'db>, - ) -> ScopedVisibilityConstraintId { - let predicate_id = self.current_use_def_map_mut().add_predicate(predicate); - let id = self - .current_visibility_constraints_mut() - .add_atom(predicate_id); - self.record_visibility_constraint_id(id); - id - } - - /// Records that all remaining statements in the current block are unreachable, and therefore - /// not visible. + /// Records that all remaining statements in the current block are unreachable. fn mark_unreachable(&mut self) { self.current_use_def_map_mut().mark_unreachable(); } - /// Records a visibility constraint that always evaluates to "ambiguous". - fn record_ambiguous_visibility(&mut self) { - self.current_use_def_map_mut() - .record_visibility_constraint(ScopedVisibilityConstraintId::AMBIGUOUS); - } - - /// Simplifies (resets) visibility constraints on all live bindings and declarations that did - /// not see any new definitions since the given snapshot. - fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + /// Records a reachability constraint that always evaluates to "ambiguous". + fn record_ambiguous_reachability(&mut self) { self.current_use_def_map_mut() - .simplify_visibility_constraints(snapshot); + .record_reachability_constraint(ScopedReachabilityConstraintId::AMBIGUOUS); } /// Record a constraint that affects the reachability of the current position in the semantic @@ -634,7 +594,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { fn record_reachability_constraint( &mut self, predicate: Predicate<'db>, - ) -> ScopedVisibilityConstraintId { + ) -> ScopedReachabilityConstraintId { let predicate_id = self.add_predicate(predicate); self.record_reachability_constraint_id(predicate_id) } @@ -643,22 +603,22 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { fn record_reachability_constraint_id( &mut self, predicate_id: ScopedPredicateId, - ) -> ScopedVisibilityConstraintId { - let visibility_constraint = self - .current_visibility_constraints_mut() + ) -> ScopedReachabilityConstraintId { + let reachability_constraint = self + .current_reachability_constraints_mut() .add_atom(predicate_id); self.current_use_def_map_mut() - .record_reachability_constraint(visibility_constraint); - visibility_constraint + .record_reachability_constraint(reachability_constraint); + reachability_constraint } - /// Record the negation of a given reachability/visibility constraint. + /// Record the negation of a given reachability constraint. fn record_negated_reachability_constraint( &mut self, - reachability_constraint: ScopedVisibilityConstraintId, + reachability_constraint: ScopedReachabilityConstraintId, ) { let negated_constraint = self - .current_visibility_constraints_mut() + .current_reachability_constraints_mut() .add_not_constraint(reachability_constraint); self.current_use_def_map_mut() .record_reachability_constraint(negated_constraint); @@ -1139,12 +1099,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let pre_return_state = matches!(last_stmt, ast::Stmt::Return(_)) .then(|| builder.flow_snapshot()); builder.visit_stmt(last_stmt); - let scope_start_visibility = - builder.current_use_def_map().scope_start_visibility; + let reachability = builder.current_use_def_map().reachability; if let Some(pre_return_state) = pre_return_state { builder.flow_restore(pre_return_state); - builder.current_use_def_map_mut().scope_start_visibility = - scope_start_visibility; + builder.current_use_def_map_mut().reachability = reachability; } } @@ -1297,11 +1255,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { continue; }; - // In order to understand the visibility of definitions created by a `*` import, - // we need to know the visibility of the global-scope definitions in the + // In order to understand the reachability of definitions created by a `*` import, + // we need to know the reachability of the global-scope definitions in the // `referenced_module` the symbols imported from. Much like predicates for `if` - // statements can only have their visibility constraints resolved at type-inference - // time, the visibility of these global-scope definitions in the external module + // statements can only have their reachability constraints resolved at type-inference + // time, the reachability of these global-scope definitions in the external module // cannot be resolved at this point. As such, we essentially model each definition // stemming from a `from exporter *` import as something like: // @@ -1328,7 +1286,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { self.current_use_def_map().single_place_snapshot(symbol_id); self.push_additional_definition(symbol_id, node_ref); self.current_use_def_map_mut() - .record_and_negate_star_import_visibility_constraint( + .record_and_negate_star_import_reachability_constraint( star_import, symbol_id, pre_definition, @@ -1387,7 +1345,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // reachability constraint on the `msg` expression. // // The other important part is the ``. This lets us skip the usual merging of - // flow states and simplification of visibility constraints, since there is no way + // flow states and simplification of reachability constraints, since there is no way // of getting out of that `msg` branch. We simply restore to the post-test state. self.visit_expr(test); @@ -1399,12 +1357,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { self.record_narrowing_constraint(negated_predicate); self.record_reachability_constraint(negated_predicate); self.visit_expr(msg); - self.record_visibility_constraint(negated_predicate); self.flow_restore(post_test); } self.record_narrowing_constraint(predicate); - self.record_visibility_constraint(predicate); self.record_reachability_constraint(predicate); } @@ -1479,13 +1435,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { self.visit_expr(&node.test); let mut no_branch_taken = self.flow_snapshot(); let mut last_predicate = self.record_expression_narrowing_constraint(&node.test); - let mut reachability_constraint = + let mut last_reachability_constraint = self.record_reachability_constraint(last_predicate); self.visit_body(&node.body); - let visibility_constraint_id = self.record_visibility_constraint(last_predicate); - let mut vis_constraints = vec![visibility_constraint_id]; - let mut post_clauses: Vec = vec![]; let elif_else_clauses = node .elif_else_clauses @@ -1509,38 +1462,27 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // we can only take an elif/else branch if none of the previous ones were // taken self.flow_restore(no_branch_taken.clone()); + self.record_negated_narrowing_constraint(last_predicate); - self.record_negated_reachability_constraint(reachability_constraint); + self.record_negated_reachability_constraint(last_reachability_constraint); - let elif_predicate = if let Some(elif_test) = clause_test { + if let Some(elif_test) = clause_test { self.visit_expr(elif_test); // A test expression is evaluated whether the branch is taken or not no_branch_taken = self.flow_snapshot(); - reachability_constraint = - self.record_reachability_constraint(last_predicate); - let predicate = self.record_expression_narrowing_constraint(elif_test); - Some(predicate) - } else { - None - }; - self.visit_body(clause_body); + last_predicate = self.record_expression_narrowing_constraint(elif_test); - for id in &vis_constraints { - self.record_negated_visibility_constraint(*id); - } - if let Some(elif_predicate) = elif_predicate { - last_predicate = elif_predicate; - let id = self.record_visibility_constraint(elif_predicate); - vis_constraints.push(id); + last_reachability_constraint = + self.record_reachability_constraint(last_predicate); } + + self.visit_body(clause_body); } for post_clause_state in post_clauses { self.flow_merge(post_clause_state); } - - self.simplify_visibility_constraints(no_branch_taken); } ast::Stmt::While(ast::StmtWhile { test, @@ -1555,57 +1497,49 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let predicate = self.record_expression_narrowing_constraint(test); self.record_reachability_constraint(predicate); - // We need multiple copies of the visibility constraint for the while condition, + // We need multiple copies of the reachability constraint for the while condition, // since we need to model situations where the first evaluation of the condition // returns True, but a later evaluation returns False. let first_predicate_id = self.current_use_def_map_mut().add_predicate(predicate); let later_predicate_id = self.current_use_def_map_mut().add_predicate(predicate); - let first_vis_constraint_id = self - .current_visibility_constraints_mut() - .add_atom(first_predicate_id); let later_vis_constraint_id = self - .current_visibility_constraints_mut() + .current_reachability_constraints_mut() .add_atom(later_predicate_id); - let outer_loop = self.push_loop(); - self.visit_body(body); - let this_loop = self.pop_loop(outer_loop); - // If the body is executed, we know that we've evaluated the condition at least // once, and that the first evaluation was True. We might not have evaluated the // condition more than once, so we can't assume that later evaluations were True. - // So the body's full visibility constraint is `first`. - let body_vis_constraint_id = first_vis_constraint_id; - self.record_visibility_constraint_id(body_vis_constraint_id); + // So the body's full reachability constraint is `first`. + self.record_reachability_constraint_id(first_predicate_id); + + let outer_loop = self.push_loop(); + self.visit_body(body); + let this_loop = self.pop_loop(outer_loop); // We execute the `else` once the condition evaluates to false. This could happen // without ever executing the body, if the condition is false the first time it's // tested. So the starting flow state of the `else` clause is the union of: - // - the pre-loop state with a visibility constraint that the first evaluation of + // - the pre-loop state with a reachability constraint that the first evaluation of // the while condition was false, - // - the post-body state (which already has a visibility constraint that the - // first evaluation was true) with a visibility constraint that a _later_ + // - the post-body state (which already has a reachability constraint that the + // first evaluation was true) with a reachability constraint that a _later_ // evaluation of the while condition was false. // To model this correctly, we need two copies of the while condition constraint, // since the first and later evaluations might produce different results. let post_body = self.flow_snapshot(); - self.flow_restore(pre_loop.clone()); - self.record_negated_visibility_constraint(first_vis_constraint_id); + self.flow_restore(pre_loop); self.flow_merge(post_body); self.record_negated_narrowing_constraint(predicate); + self.record_negated_reachability_constraint(later_vis_constraint_id); self.visit_body(orelse); - self.record_negated_visibility_constraint(later_vis_constraint_id); // Breaking out of a while loop bypasses the `else` clause, so merge in the break // states after visiting `else`. for break_state in this_loop.break_states { let snapshot = self.flow_snapshot(); self.flow_restore(break_state); - self.record_visibility_constraint_id(body_vis_constraint_id); self.flow_merge(snapshot); } - - self.simplify_visibility_constraints(pre_loop); } ast::Stmt::With(ast::StmtWith { items, @@ -1652,7 +1586,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let iter_expr = self.add_standalone_expression(iter); self.visit_expr(iter); - self.record_ambiguous_visibility(); + self.record_ambiguous_reachability(); let pre_loop = self.flow_snapshot(); @@ -1709,10 +1643,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { &case.pattern, case.guard.as_deref(), ); - let vis_constraint_id = self.record_reachability_constraint(match_predicate); + let reachability_constraint = + self.record_reachability_constraint(match_predicate); let match_success_guard_failure = case.guard.as_ref().map(|guard| { let guard_expr = self.add_standalone_expression(guard); + // We could also add the guard expression as a reachability constraint, but + // it seems unlikely that both the case predicate as well as the guard are + // statically known conditions, so we currently don't model that. + self.record_ambiguous_reachability(); self.visit_expr(guard); let post_guard_eval = self.flow_snapshot(); let predicate = Predicate { @@ -1726,8 +1665,6 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { match_success_guard_failure }); - self.record_visibility_constraint_id(vis_constraint_id); - self.visit_body(&case.body); post_case_snapshots.push(self.flow_snapshot()); @@ -1738,6 +1675,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // snapshots into. self.flow_restore(no_case_matched.clone()); self.record_negated_narrowing_constraint(match_predicate); + self.record_negated_reachability_constraint(reachability_constraint); if let Some(match_success_guard_failure) = match_success_guard_failure { self.flow_merge(match_success_guard_failure); } else { @@ -1748,15 +1686,12 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { debug_assert!(case.guard.is_none()); } - self.record_negated_visibility_constraint(vis_constraint_id); no_case_matched = self.flow_snapshot(); } for post_clause_state in post_case_snapshots { self.flow_merge(post_clause_state); } - - self.simplify_visibility_constraints(no_case_matched); } ast::Stmt::Try(ast::StmtTry { body, @@ -1767,7 +1702,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { range: _, node_index: _, }) => { - self.record_ambiguous_visibility(); + self.record_ambiguous_reachability(); // Save the state prior to visiting any of the `try` block. // @@ -2136,16 +2071,13 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let predicate = self.record_expression_narrowing_constraint(test); let reachability_constraint = self.record_reachability_constraint(predicate); self.visit_expr(body); - let visibility_constraint = self.record_visibility_constraint(predicate); let post_body = self.flow_snapshot(); - self.flow_restore(pre_if.clone()); + self.flow_restore(pre_if); self.record_negated_narrowing_constraint(predicate); self.record_negated_reachability_constraint(reachability_constraint); self.visit_expr(orelse); - self.record_negated_visibility_constraint(visibility_constraint); self.flow_merge(post_body); - self.simplify_visibility_constraints(pre_if); } ast::Expr::ListComp( list_comprehension @ ast::ExprListComp { @@ -2203,18 +2135,17 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { node_index: _, op, }) => { - let pre_op = self.flow_snapshot(); - let mut snapshots = vec![]; - let mut visibility_constraints = vec![]; + let mut reachability_constraints = vec![]; for (index, value) in values.iter().enumerate() { - self.visit_expr(value); - - for vid in &visibility_constraints { - self.record_visibility_constraint_id(*vid); + for id in &reachability_constraints { + self.current_use_def_map_mut() + .record_reachability_constraint(*id); // TODO: nicer API } + self.visit_expr(value); + // For the last value, we don't need to model control flow. There is no short-circuiting // anymore. if index < values.len() - 1 { @@ -2223,37 +2154,32 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::BoolOp::And => self.add_predicate(predicate), ast::BoolOp::Or => self.add_negated_predicate(predicate), }; - let visibility_constraint = self - .current_visibility_constraints_mut() + let reachability_constraint = self + .current_reachability_constraints_mut() .add_atom(predicate_id); let after_expr = self.flow_snapshot(); // We first model the short-circuiting behavior. We take the short-circuit // path here if all of the previous short-circuit paths were not taken, so - // we record all previously existing visibility constraints, and negate the + // we record all previously existing reachability constraints, and negate the // one for the current expression. - for vid in &visibility_constraints { - self.record_visibility_constraint_id(*vid); - } - self.record_negated_visibility_constraint(visibility_constraint); + + self.record_negated_reachability_constraint(reachability_constraint); snapshots.push(self.flow_snapshot()); // Then we model the non-short-circuiting behavior. Here, we need to delay - // the application of the visibility constraint until after the expression + // the application of the reachability constraint until after the expression // has been evaluated, so we only push it onto the stack here. self.flow_restore(after_expr); self.record_narrowing_constraint_id(predicate_id); - self.record_reachability_constraint_id(predicate_id); - visibility_constraints.push(visibility_constraint); + reachability_constraints.push(reachability_constraint); } } for snapshot in snapshots { self.flow_merge(snapshot); } - - self.simplify_visibility_constraints(pre_op); } ast::Expr::StringLiteral(_) => { // Track reachability of string literals, as they could be a stringified annotation diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs index edbff982863a36..2560a70c4f1927 100644 --- a/crates/ty_python_semantic/src/semantic_index/place.rs +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -15,7 +15,7 @@ use smallvec::{SmallVec, smallvec}; use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::node_key::NodeKey; -use crate::semantic_index::visibility_constraints::ScopedVisibilityConstraintId; +use crate::semantic_index::reachability_constraints::ScopedReachabilityConstraintId; use crate::semantic_index::{PlaceSet, SemanticIndex, semantic_index}; #[derive(Debug, Clone, PartialEq, Eq, Hash, salsa::Update)] @@ -480,7 +480,7 @@ pub struct Scope { parent: Option, node: NodeWithScopeKind, descendants: Range, - reachability: ScopedVisibilityConstraintId, + reachability: ScopedReachabilityConstraintId, } impl Scope { @@ -488,7 +488,7 @@ impl Scope { parent: Option, node: NodeWithScopeKind, descendants: Range, - reachability: ScopedVisibilityConstraintId, + reachability: ScopedReachabilityConstraintId, ) -> Self { Scope { parent, @@ -522,7 +522,7 @@ impl Scope { self.kind().is_eager() } - pub(crate) fn reachability(&self) -> ScopedVisibilityConstraintId { + pub(crate) fn reachability(&self) -> ScopedReachabilityConstraintId { self.reachability } } diff --git a/crates/ty_python_semantic/src/semantic_index/predicate.rs b/crates/ty_python_semantic/src/semantic_index/predicate.rs index 06b71396e70405..9259ab72e43a26 100644 --- a/crates/ty_python_semantic/src/semantic_index/predicate.rs +++ b/crates/ty_python_semantic/src/semantic_index/predicate.rs @@ -4,8 +4,8 @@ //! //! - [_Narrowing constraints_][crate::semantic_index::narrowing_constraints] constrain the type of //! a binding that is visible at a particular use. -//! - [_Visibility constraints_][crate::semantic_index::visibility_constraints] determine the -//! static visibility of a binding, and the reachability of a statement. +//! - [_Reachability constraints_][crate::semantic_index::reachability_constraints] determine the +//! static reachability of a binding, and the reachability of a statement or expression. use ruff_db::files::File; use ruff_index::{IndexVec, newtype_index}; @@ -65,7 +65,7 @@ pub(crate) enum PredicateNode<'db> { StarImportPlaceholder(StarImportPlaceholderPredicate<'db>), } -/// Pattern kinds for which we support type narrowing and/or static visibility analysis. +/// Pattern kinds for which we support type narrowing and/or static reachability analysis. #[derive(Debug, Clone, Hash, PartialEq, salsa::Update)] pub(crate) enum PatternPredicateKind<'db> { Singleton(Singleton), @@ -99,7 +99,7 @@ impl<'db> PatternPredicate<'db> { /// A "placeholder predicate" that is used to model the fact that the boundness of a /// (possible) definition or declaration caused by a `*` import cannot be fully determined -/// until type-inference time. This is essentially the same as a standard visibility constraint, +/// until type-inference time. This is essentially the same as a standard reachability constraint, /// so we reuse the [`Predicate`] infrastructure to model it. /// /// To illustrate, say we have a module `exporter.py` like so: diff --git a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs similarity index 68% rename from crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs rename to crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs index 60db138c57da9a..2cef92945b8abb 100644 --- a/crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -1,61 +1,37 @@ -//! # Visibility constraints -//! -//! During semantic index building, we collect visibility constraints for each binding and -//! declaration. These constraints are then used during type-checking to determine the static -//! visibility of a certain definition. This allows us to re-analyze control flow during type -//! checking, potentially "hiding" some branches that we can statically determine to never be -//! taken. Consider the following example first. We added implicit "unbound" definitions at the -//! start of the scope. Note how visibility constraints can apply to bindings outside of the -//! if-statement: -//! ```py -//! x = # not a live binding for the use of x below, shadowed by `x = 1` -//! y = # visibility constraint: ~test +//! # Reachability constraints //! -//! x = 1 # visibility constraint: ~test +//! During semantic index building, we record so-called reachability constraints that keep track +//! of a set of conditions that need to apply in order for a certain statement or expression to +//! be reachable from the start of the scope. As an example, consider the following situation where +//! we have just processed two `if`-statements: +//! ```py //! if test: -//! x = 2 # visibility constraint: test -//! -//! y = 2 # visibility constraint: test -//! -//! use(x) -//! use(y) +//! //! ``` -//! The static truthiness of the `test` condition can either be always-false, ambiguous, or -//! always-true. Similarly, we have the same three options when evaluating a visibility constraint. -//! This outcome determines the visibility of a definition: always-true means that the definition -//! is definitely visible for a given use, always-false means that the definition is definitely -//! not visible, and ambiguous means that we might see this definition or not. In the latter case, -//! we need to consider both options during type inference and boundness analysis. For the example -//! above, these are the possible type inference / boundness results for the uses of `x` and `y`: +//! In this case, we would record a reachability constraint of `test`, which would later allow us +//! to re-analyze the control flow during type-checking, once we actually know the static truthiness +//! of `test`. When evaluating a constraint, there are three possible outcomes: always true, always +//! false, or ambiguous. For a simple constraint like this, always-true and always-false correspond +//! to the case in which we can infer that the type of `test` is `Literal[True]` or `Literal[False]`. +//! In any other case, like if the type of `test` is `bool` or `Unknown`, we can not statically +//! determine whether `test` is truthy or falsy, so the outcome would be "ambiguous". //! -//! ```text -//! | `test` truthiness | `~test` truthiness | type of `x` | boundness of `y` | -//! |-------------------|--------------------|-----------------|------------------| -//! | always false | always true | `Literal[1]` | unbound | -//! | ambiguous | ambiguous | `Literal[1, 2]` | possibly unbound | -//! | always true | always false | `Literal[2]` | bound | -//! ``` //! -//! ### Sequential constraints (ternary AND) +//! ## Sequential constraints (ternary AND) //! -//! As we have seen above, visibility constraints can apply outside of a control flow element. -//! So we need to consider the possibility that multiple constraints apply to the same binding. -//! Here, we consider what happens if multiple `if`-statements lead to a sequence of constraints. -//! Consider the following example: +//! Whenever control flow branches, we record reachability constraints. If we already have a +//! constraint, we create a new one using a ternary AND operation. Consider the following example: //! ```py -//! x = 0 -//! //! if test1: -//! x = 1 -//! -//! if test2: -//! x = 2 +//! if test2: +//! //! ``` -//! The binding `x = 2` is easy to analyze. Its visibility corresponds to the truthiness of `test2`. -//! For the `x = 1` binding, things are a bit more interesting. It is always visible if `test1` is -//! always-true *and* `test2` is always-false. It is never visible if `test1` is always-false *or* -//! `test2` is always-true. And it is ambiguous otherwise. This corresponds to a ternary *test1 AND -//! ~test2* operation in three-valued Kleene logic [Kleene]: +//! Here, we would accumulate a reachability constraint of `test1 AND test2`. We can statically +//! determine that this position is *always* reachable only if both `test1` and `test2` are +//! always true. On the other hand, we can statically determine that this position is *never* +//! reachable if *either* `test1` or `test2` is always false. In any other case, we can not +//! determine whether this position is reachable or not, so the outcome is "ambiguous". This +//! corresponds to a ternary *AND* operation in [Kleene] logic: //! //! ```text //! | AND | always-false | ambiguous | always-true | @@ -65,38 +41,27 @@ //! | always true | always-false | ambiguous | always-true | //! ``` //! -//! The `x = 0` binding can be handled similarly, with the difference that both `test1` and `test2` -//! are negated: -//! ```py -//! x = 0 # ~test1 AND ~test2 -//! -//! if test1: -//! x = 1 # test1 AND ~test2 //! -//! if test2: -//! x = 2 # test2 -//! ``` -//! -//! ### Merged constraints (ternary OR) +//! ## Merged constraints (ternary OR) //! -//! Finally, we consider what happens in "parallel" control flow. Consider the following example -//! where we have omitted the test condition for the outer `if` for clarity: +//! We also need to consider the case where control flow merges again. Consider a case like this: //! ```py -//! x = 0 -//! -//! if <…>: +//! def _(): //! if test1: -//! x = 1 -//! else: -//! if test2: -//! x = 2 +//! pass +//! elif test2: +//! pass +//! else: +//! return //! -//! use(x) +//! //! ``` -//! At the usage of `x`, i.e. after control flow has been merged again, the visibility of the `x = -//! 0` binding behaves as follows: the binding is always visible if `test1` is always-false *or* -//! `test2` is always-false; and it is never visible if `test1` is always-true *and* `test2` is -//! always-true. This corresponds to a ternary *OR* operation in Kleene logic: +//! Here, the first branch has a `test1` constraint, and the second branch has a `test2` constraint. +//! The third branch ends in a terminal statement [^1]. When we merge control flow, we need to consider +//! the reachability through either the first or the second branch. The current position is only +//! *definitely* unreachable if both `test1` and `test2` are always false. It is definitely +//! reachable if *either* `test1` or `test2` is always true. In any other case, we can not statically +//! determine whether it is reachable or not. This operation corresponds to a ternary *OR* operation: //! //! ```text //! | OR | always-false | ambiguous | always-true | @@ -106,40 +71,95 @@ //! | always true | always-true | always-true | always-true | //! ``` //! -//! Using this, we can annotate the visibility constraints for the example above: -//! ```py -//! x = 0 # ~test1 OR ~test2 +//! [^1]: What's actually happening here is that we merge all three branches using a ternary OR. The +//! third branch has a reachability constraint of `always-false`, and `t OR always-false` is equal +//! to `t` (see first column in that table), so it was okay to omit the third branch in the discussion +//! above. //! -//! if <…>: -//! if test1: -//! x = 1 # test1 +//! +//! ## Negation +//! +//! Control flow elements like `if-elif-else` or `match` statements can also lead to negated +//! constraints. For example, we record a constraint of `~test` for the `else` branch here: +//! ```py +//! if test: +//! pass //! else: -//! if test2: -//! x = 2 # test2 +//! +//! ``` //! -//! use(x) +//! ## Explicit ambiguity +//! +//! In some cases, we explicitly record an “ambiguous” constraint. We do this when branching on +//! something that we can not (or intentionally do not want to) analyze statically. `for` loops are +//! one example: +//! ```py +//! def _(): +//! for _ in range(2): +//! return +//! +//! //! ``` +//! If we would not record any constraints at the branching point, we would have an `always-true` +//! reachability for the no-loop branch, and a `always-false` reachability for the branch which enters +//! the loop. Merging those would lead to a reachability of `always-true OR always-false = always-true`, +//! i.e. we would consider the end of the scope to be unconditionally reachable, which is not correct. //! -//! ### Explicit ambiguity +//! Recording an ambiguous constraint at the branching point modifies the constraints in both branches to +//! `always-true AND ambiguous = ambiguous` and `always-false AND ambiguous = always-false`, respectively. +//! Merging these two using OR correctly leads to `ambiguous` for the end-of-scope reachability. //! -//! In some cases, we explicitly add an “ambiguous” constraint to all bindings -//! in a certain control flow path. We do this when branching on something that we can not (or -//! intentionally do not want to) analyze statically. `for` loops are one example: +//! +//! ## Reachability constraints and bindings +//! +//! To understand how reachability constraints apply to bindings in particular, consider the following +//! example: //! ```py -//! x = +//! x = # not a live binding for the use of x below, shadowed by `x = 1` +//! y = # reachability constraint: ~test //! -//! for _ in range(2): -//! x = 1 +//! x = 1 # reachability constraint: ~test +//! if test: +//! x = 2 # reachability constraint: test +//! +//! y = 2 # reachability constraint: test +//! +//! use(x) +//! use(y) +//! ``` +//! Both the type and the boundness of `x` and `y` are affected by reachability constraints: +//! +//! ```text +//! | `test` truthiness | type of `x` | boundness of `y` | +//! |-------------------|-----------------|------------------| +//! | always false | `Literal[1]` | unbound | +//! | ambiguous | `Literal[1, 2]` | possibly unbound | +//! | always true | `Literal[2]` | bound | //! ``` -//! Here, we report an ambiguous visibility constraint before branching off. If we don't do this, -//! the `x = ` binding would be considered unconditionally visible in the no-loop case. -//! And since the other branch does not have the live `x = ` binding, we would incorrectly -//! create a state where the `x = ` binding is always visible. +//! +//! To achieve this, we apply reachability constraints retroactively to bindings that came before +//! the branching point. In the example above, the `x = 1` binding has a `test` constraint in the +//! `if` branch, and a `~test` constraint in the implicit `else` branch. Since it is shadowed by +//! `x = 2` in the `if` branch, we are only left with the `~test` constraint after control flow +//! has merged again. +//! +//! For live bindings, the reachability constraint therefore refers to the following question: +//! Is the binding reachable from the start of the scope, and is there a control flow path from +//! that binding to a use of that symbol at the current position? +//! +//! In the example above, `x = 1` is always reachable, but that binding can only reach the use of +//! `x` at the current position if `test` is falsy. +//! +//! To handle boundness correctly, we also add implicit `y = ` bindings at the start of +//! the scope. This allows us to determine whether a symbol is definitely bound (if that implicit +//! `y = ` binding is not visible), possibly unbound (if the reachability constraint +//! evaluates to `Ambiguous`), or definitely unbound (in case the `y = ` binding is +//! always visible). //! //! //! ### Representing formulas //! -//! Given everything above, we can represent a visibility constraint as a _ternary formula_. This +//! Given everything above, we can represent a reachability constraint as a _ternary formula_. This //! is like a boolean formula (which maps several true/false variables to a single true/false //! result), but which allows the third "ambiguous" value in addition to "true" and "false". //! @@ -166,7 +186,7 @@ //! formulas (which have the same outputs for every combination of inputs) are represented by //! exactly the same graph node. (Because of interning, this is not _equal_ nodes, but _identical_ //! ones.) That means that we can compare formulas for equivalence in constant time, and in -//! particular, can check whether a visibility constraint is statically always true or false, +//! particular, can check whether a reachability constraint is statically always true or false, //! regardless of any Python program state, by seeing if the constraint's formula is the "true" or //! "false" leaf node. //! @@ -204,18 +224,18 @@ use crate::types::{Truthiness, Type, infer_expression_type}; /// for a particular [`Predicate`], if your formula needs to consider how a particular runtime /// property might be different at different points in the execution of the program. /// -/// Visibility constraints are normalized, so equivalent constraints are guaranteed to have equal +/// reachability constraints are normalized, so equivalent constraints are guaranteed to have equal /// IDs. #[derive(Clone, Copy, Eq, Hash, PartialEq)] -pub(crate) struct ScopedVisibilityConstraintId(u32); +pub(crate) struct ScopedReachabilityConstraintId(u32); -impl std::fmt::Debug for ScopedVisibilityConstraintId { +impl std::fmt::Debug for ScopedReachabilityConstraintId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut f = f.debug_tuple("ScopedVisibilityConstraintId"); + let mut f = f.debug_tuple("ScopedReachabilityConstraintId"); match *self { // We use format_args instead of rendering the strings directly so that we don't get - // any quotes in the output: ScopedVisibilityConstraintId(AlwaysTrue) instead of - // ScopedVisibilityConstraintId("AlwaysTrue"). + // any quotes in the output: ScopedReachabilityConstraintId(AlwaysTrue) instead of + // ScopedReachabilityConstraintId("AlwaysTrue"). ALWAYS_TRUE => f.field(&format_args!("AlwaysTrue")), AMBIGUOUS => f.field(&format_args!("Ambiguous")), ALWAYS_FALSE => f.field(&format_args!("AlwaysFalse")), @@ -237,34 +257,34 @@ impl std::fmt::Debug for ScopedVisibilityConstraintId { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] struct InteriorNode { - /// A "variable" that is evaluated as part of a TDD ternary function. For visibility + /// A "variable" that is evaluated as part of a TDD ternary function. For reachability /// constraints, this is a `Predicate` that represents some runtime property of the Python /// code that we are evaluating. atom: ScopedPredicateId, - if_true: ScopedVisibilityConstraintId, - if_ambiguous: ScopedVisibilityConstraintId, - if_false: ScopedVisibilityConstraintId, + if_true: ScopedReachabilityConstraintId, + if_ambiguous: ScopedReachabilityConstraintId, + if_false: ScopedReachabilityConstraintId, } -impl ScopedVisibilityConstraintId { +impl ScopedReachabilityConstraintId { /// A special ID that is used for an "always true" / "always visible" constraint. - pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId = - ScopedVisibilityConstraintId(0xffff_ffff); + pub(crate) const ALWAYS_TRUE: ScopedReachabilityConstraintId = + ScopedReachabilityConstraintId(0xffff_ffff); /// A special ID that is used for an ambiguous constraint. - pub(crate) const AMBIGUOUS: ScopedVisibilityConstraintId = - ScopedVisibilityConstraintId(0xffff_fffe); + pub(crate) const AMBIGUOUS: ScopedReachabilityConstraintId = + ScopedReachabilityConstraintId(0xffff_fffe); /// A special ID that is used for an "always false" / "never visible" constraint. - pub(crate) const ALWAYS_FALSE: ScopedVisibilityConstraintId = - ScopedVisibilityConstraintId(0xffff_fffd); + pub(crate) const ALWAYS_FALSE: ScopedReachabilityConstraintId = + ScopedReachabilityConstraintId(0xffff_fffd); fn is_terminal(self) -> bool { self.0 >= SMALLEST_TERMINAL.0 } } -impl Idx for ScopedVisibilityConstraintId { +impl Idx for ScopedReachabilityConstraintId { #[inline] fn new(value: usize) -> Self { assert!(value <= (SMALLEST_TERMINAL.0 as usize)); @@ -280,35 +300,41 @@ impl Idx for ScopedVisibilityConstraintId { } // Rebind some constants locally so that we don't need as many qualifiers below. -const ALWAYS_TRUE: ScopedVisibilityConstraintId = ScopedVisibilityConstraintId::ALWAYS_TRUE; -const AMBIGUOUS: ScopedVisibilityConstraintId = ScopedVisibilityConstraintId::AMBIGUOUS; -const ALWAYS_FALSE: ScopedVisibilityConstraintId = ScopedVisibilityConstraintId::ALWAYS_FALSE; -const SMALLEST_TERMINAL: ScopedVisibilityConstraintId = ALWAYS_FALSE; +const ALWAYS_TRUE: ScopedReachabilityConstraintId = ScopedReachabilityConstraintId::ALWAYS_TRUE; +const AMBIGUOUS: ScopedReachabilityConstraintId = ScopedReachabilityConstraintId::AMBIGUOUS; +const ALWAYS_FALSE: ScopedReachabilityConstraintId = ScopedReachabilityConstraintId::ALWAYS_FALSE; +const SMALLEST_TERMINAL: ScopedReachabilityConstraintId = ALWAYS_FALSE; -/// A collection of visibility constraints for a given scope. +/// A collection of reachability constraints for a given scope. #[derive(Debug, PartialEq, Eq, salsa::Update)] -pub(crate) struct VisibilityConstraints { - interiors: IndexVec, +pub(crate) struct ReachabilityConstraints { + interiors: IndexVec, } #[derive(Debug, Default, PartialEq, Eq)] -pub(crate) struct VisibilityConstraintsBuilder { - interiors: IndexVec, - interior_cache: FxHashMap, - not_cache: FxHashMap, +pub(crate) struct ReachabilityConstraintsBuilder { + interiors: IndexVec, + interior_cache: FxHashMap, + not_cache: FxHashMap, and_cache: FxHashMap< - (ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - ScopedVisibilityConstraintId, + ( + ScopedReachabilityConstraintId, + ScopedReachabilityConstraintId, + ), + ScopedReachabilityConstraintId, >, or_cache: FxHashMap< - (ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - ScopedVisibilityConstraintId, + ( + ScopedReachabilityConstraintId, + ScopedReachabilityConstraintId, + ), + ScopedReachabilityConstraintId, >, } -impl VisibilityConstraintsBuilder { - pub(crate) fn build(self) -> VisibilityConstraints { - VisibilityConstraints { +impl ReachabilityConstraintsBuilder { + pub(crate) fn build(self) -> ReachabilityConstraints { + ReachabilityConstraints { interiors: self.interiors, } } @@ -318,8 +344,8 @@ impl VisibilityConstraintsBuilder { /// any internal node, since they are leaf nodes. fn cmp_atoms( &self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, + a: ScopedReachabilityConstraintId, + b: ScopedReachabilityConstraintId, ) -> Ordering { if a == b || (a.is_terminal() && b.is_terminal()) { Ordering::Equal @@ -332,9 +358,9 @@ impl VisibilityConstraintsBuilder { } } - /// Adds an interior node, ensuring that we always use the same visibility constraint ID for + /// Adds an interior node, ensuring that we always use the same reachability constraint ID for /// equal nodes. - fn add_interior(&mut self, node: InteriorNode) -> ScopedVisibilityConstraintId { + fn add_interior(&mut self, node: InteriorNode) -> ScopedReachabilityConstraintId { // If the true and false branches lead to the same node, we can override the ambiguous // branch to go there too. And this node is then redundant and can be reduced. if node.if_true == node.if_false { @@ -347,7 +373,7 @@ impl VisibilityConstraintsBuilder { .or_insert_with(|| self.interiors.push(node)) } - /// Adds a new visibility constraint that checks a single [`Predicate`]. + /// Adds a new reachability constraint that checks a single [`Predicate`]. /// /// [`ScopedPredicateId`]s are the “variables” that are evaluated by a TDD. A TDD variable has /// the same value no matter how many times it appears in the ternary formula that the TDD @@ -361,7 +387,7 @@ impl VisibilityConstraintsBuilder { pub(crate) fn add_atom( &mut self, predicate: ScopedPredicateId, - ) -> ScopedVisibilityConstraintId { + ) -> ScopedReachabilityConstraintId { self.add_interior(InteriorNode { atom: predicate, if_true: ALWAYS_TRUE, @@ -370,11 +396,11 @@ impl VisibilityConstraintsBuilder { }) } - /// Adds a new visibility constraint that is the ternary NOT of an existing one. + /// Adds a new reachability constraint that is the ternary NOT of an existing one. pub(crate) fn add_not_constraint( &mut self, - a: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { + a: ScopedReachabilityConstraintId, + ) -> ScopedReachabilityConstraintId { if a == ALWAYS_TRUE { return ALWAYS_FALSE; } else if a == AMBIGUOUS { @@ -400,12 +426,12 @@ impl VisibilityConstraintsBuilder { result } - /// Adds a new visibility constraint that is the ternary OR of two existing ones. + /// Adds a new reachability constraint that is the ternary OR of two existing ones. pub(crate) fn add_or_constraint( &mut self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { + a: ScopedReachabilityConstraintId, + b: ScopedReachabilityConstraintId, + ) -> ScopedReachabilityConstraintId { match (a, b) { (ALWAYS_TRUE, _) | (_, ALWAYS_TRUE) => return ALWAYS_TRUE, (ALWAYS_FALSE, other) | (other, ALWAYS_FALSE) => return other, @@ -466,12 +492,12 @@ impl VisibilityConstraintsBuilder { result } - /// Adds a new visibility constraint that is the ternary AND of two existing ones. + /// Adds a new reachability constraint that is the ternary AND of two existing ones. pub(crate) fn add_and_constraint( &mut self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { + a: ScopedReachabilityConstraintId, + b: ScopedReachabilityConstraintId, + ) -> ScopedReachabilityConstraintId { match (a, b) { (ALWAYS_FALSE, _) | (_, ALWAYS_FALSE) => return ALWAYS_FALSE, (ALWAYS_TRUE, other) | (other, ALWAYS_TRUE) => return other, @@ -533,13 +559,13 @@ impl VisibilityConstraintsBuilder { } } -impl VisibilityConstraints { - /// Analyze the statically known visibility for a given visibility constraint. +impl ReachabilityConstraints { + /// Analyze the statically known reachability for a given constraint. pub(crate) fn evaluate<'db>( &self, db: &'db dyn Db, predicates: &Predicates<'db>, - mut id: ScopedVisibilityConstraintId, + mut id: ScopedReachabilityConstraintId, ) -> Truthiness { loop { let node = match id { diff --git a/crates/ty_python_semantic/src/semantic_index/use_def.rs b/crates/ty_python_semantic/src/semantic_index/use_def.rs index 39647ac7faa164..cb57055ab877f9 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def.rs @@ -221,43 +221,8 @@ //! snapshot (which has `x = 3` as the only live binding). The result of this merge is that we now //! have two live bindings of `x`: `x = 3` and `x = 4`. //! -//! Another piece of information that the `UseDefMap` needs to provide are visibility constraints. -//! These are similar to the narrowing constraints, but apply to bindings and declarations within a -//! control flow path. Consider the following example: -//! ```py -//! x = 1 -//! if test: -//! x = 2 -//! y = "y" -//! ``` -//! In principle, there are two possible control flow paths here. However, if we can statically -//! infer `test` to be always truthy or always falsy (that is, `__bool__` of `test` is of type -//! `Literal[True]` or `Literal[False]`), we can rule out one of the possible paths. To support -//! this feature, we record a visibility constraint of `test` to all live bindings and declarations -//! *after* visiting the body of the `if` statement. And we record a negative visibility constraint -//! `~test` to all live bindings/declarations in the (implicit) `else` branch. For the example -//! above, we would record the following visibility constraints (adding the implicit "unbound" -//! definitions for clarity): -//! ```py -//! x = # not live, shadowed by `x = 1` -//! y = # visibility constraint: ~test -//! -//! x = 1 # visibility constraint: ~test -//! if test: -//! x = 2 # visibility constraint: test -//! y = "y" # visibility constraint: test -//! ``` -//! When we encounter a use of `x` after this `if` statement, we would record two live bindings: `x -//! = 1` with a constraint of `~test`, and `x = 2` with a constraint of `test`. In type inference, -//! when we iterate over all live bindings, we can evaluate these constraints to determine if a -//! particular binding is actually visible. For example, if `test` is always truthy, we only see -//! the `x = 2` binding. If `test` is always falsy, we only see the `x = 1` binding. And if the -//! `__bool__` method of `test` returns type `bool`, we can see both bindings. -//! -//! Note that we also record visibility constraints for the start of the scope. This is important -//! to determine if a place is definitely bound, possibly unbound, or definitely unbound. In the -//! example above, The `y = ` binding is constrained by `~test`, so `y` would only be -//! definitely-bound if `test` is always truthy. +//! Another piece of information that the `UseDefMap` needs to provide are reachability constraints. +//! See [`reachability_constraints.rs`] for more details, in particular how they apply to bindings. //! //! The [`UseDefMapBuilder`] itself just exposes methods for taking a snapshot, resetting to a //! snapshot, and merging a snapshot into the current state. The logic using these methods lives in @@ -282,8 +247,8 @@ use crate::semantic_index::place::{FileScopeId, PlaceExpr, ScopeKind, ScopedPlac use crate::semantic_index::predicate::{ Predicate, Predicates, PredicatesBuilder, ScopedPredicateId, StarImportPlaceholderPredicate, }; -use crate::semantic_index::visibility_constraints::{ - ScopedVisibilityConstraintId, VisibilityConstraints, VisibilityConstraintsBuilder, +use crate::semantic_index::reachability_constraints::{ + ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, }; use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint}; @@ -302,14 +267,14 @@ pub(crate) struct UseDefMap<'db> { /// Array of narrowing constraints in this scope. narrowing_constraints: NarrowingConstraints, - /// Array of visibility constraints in this scope. - visibility_constraints: VisibilityConstraints, + /// Array of reachability constraints in this scope. + reachability_constraints: ReachabilityConstraints, /// [`Bindings`] reaching a [`ScopedUseId`]. bindings_by_use: IndexVec, /// Tracks whether or not a given AST node is reachable from the start of the scope. - node_reachability: FxHashMap, + node_reachability: FxHashMap, /// If the definition is a binding (only) -- `x = 1` for example -- then we need /// [`Declarations`] to know whether this binding is permitted by the live declarations. @@ -335,20 +300,24 @@ pub(crate) struct UseDefMap<'db> { /// eager scope. eager_snapshots: EagerSnapshots, - /// Whether or not the start of the scope is visible. + /// Whether or not the end of the scope is reachable. + /// /// This is used to check if the function can implicitly return `None`. /// For example: - /// - /// ```python - /// def f(cond: bool) -> int: + /// ```py + /// def f(cond: bool) -> int | None: /// if cond: /// return 1 + /// + /// def g() -> int: + /// if True: + /// return 1 /// ``` /// - /// In this case, the function may implicitly return `None`. + /// Function `f` may implicitly return `None`, but `g` cannot. /// - /// This is used by `UseDefMap::can_implicit_return`. - scope_start_visibility: ScopedVisibilityConstraintId, + /// This is used by [`UseDefMap::can_implicitly_return_none`]. + end_of_scope_reachability: ScopedReachabilityConstraintId, } impl<'db> UseDefMap<'db> { @@ -378,12 +347,11 @@ impl<'db> UseDefMap<'db> { pub(super) fn is_reachable( &self, db: &dyn crate::Db, - reachability: ScopedVisibilityConstraintId, + reachability: ScopedReachabilityConstraintId, ) -> bool { - !self - .visibility_constraints + self.reachability_constraints .evaluate(db, &self.predicates, reachability) - .is_always_false() + .may_be_true() } /// Check whether or not a given expression is reachable from the start of the scope. This @@ -392,8 +360,8 @@ impl<'db> UseDefMap<'db> { /// analysis. #[track_caller] pub(super) fn is_node_reachable(&self, db: &dyn crate::Db, node_key: NodeKey) -> bool { - !self - .visibility_constraints + self + .reachability_constraints .evaluate( db, &self.predicates, @@ -402,7 +370,7 @@ impl<'db> UseDefMap<'db> { .get(&node_key) .expect("`is_node_reachable` should only be called on AST nodes with recorded reachability"), ) - .is_always_false() + .may_be_true() } pub(crate) fn public_bindings( @@ -467,20 +435,23 @@ impl<'db> UseDefMap<'db> { } /// This function is intended to be called only once inside `TypeInferenceBuilder::infer_function_body`. - pub(crate) fn can_implicit_return(&self, db: &dyn crate::Db) -> bool { + pub(crate) fn can_implicitly_return_none(&self, db: &dyn crate::Db) -> bool { !self - .visibility_constraints - .evaluate(db, &self.predicates, self.scope_start_visibility) + .reachability_constraints + .evaluate(db, &self.predicates, self.end_of_scope_reachability) .is_always_false() } - pub(crate) fn is_binding_visible( + pub(crate) fn is_binding_reachable( &self, db: &dyn crate::Db, binding: &BindingWithConstraints<'_, 'db>, ) -> Truthiness { - self.visibility_constraints - .evaluate(db, &self.predicates, binding.visibility_constraint) + self.reachability_constraints.evaluate( + db, + &self.predicates, + binding.reachability_constraint, + ) } fn bindings_iterator<'map>( @@ -491,7 +462,7 @@ impl<'db> UseDefMap<'db> { all_definitions: &self.all_definitions, predicates: &self.predicates, narrowing_constraints: &self.narrowing_constraints, - visibility_constraints: &self.visibility_constraints, + reachability_constraints: &self.reachability_constraints, inner: bindings.iter(), } } @@ -503,7 +474,7 @@ impl<'db> UseDefMap<'db> { DeclarationsIterator { all_definitions: &self.all_definitions, predicates: &self.predicates, - visibility_constraints: &self.visibility_constraints, + reachability_constraints: &self.reachability_constraints, inner: declarations.iter(), } } @@ -538,7 +509,7 @@ pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { all_definitions: &'map IndexVec>, pub(crate) predicates: &'map Predicates<'db>, pub(crate) narrowing_constraints: &'map NarrowingConstraints, - pub(crate) visibility_constraints: &'map VisibilityConstraints, + pub(crate) reachability_constraints: &'map ReachabilityConstraints, inner: LiveBindingsIterator<'map>, } @@ -558,7 +529,7 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { constraint_ids: narrowing_constraints .iter_predicates(live_binding.narrowing_constraint), }, - visibility_constraint: live_binding.visibility_constraint, + reachability_constraint: live_binding.reachability_constraint, }) } } @@ -568,7 +539,7 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: DefinitionState<'db>, pub(crate) narrowing_constraint: ConstraintsIterator<'map, 'db>, - pub(crate) visibility_constraint: ScopedVisibilityConstraintId, + pub(crate) reachability_constraint: ScopedReachabilityConstraintId, } pub(crate) struct ConstraintsIterator<'map, 'db> { @@ -618,13 +589,13 @@ impl<'db> ConstraintsIterator<'_, 'db> { pub(crate) struct DeclarationsIterator<'map, 'db> { all_definitions: &'map IndexVec>, pub(crate) predicates: &'map Predicates<'db>, - pub(crate) visibility_constraints: &'map VisibilityConstraints, + pub(crate) reachability_constraints: &'map ReachabilityConstraints, inner: LiveDeclarationsIterator<'map>, } pub(crate) struct DeclarationWithConstraint<'db> { pub(crate) declaration: DefinitionState<'db>, - pub(crate) visibility_constraint: ScopedVisibilityConstraintId, + pub(crate) reachability_constraint: ScopedReachabilityConstraintId, } impl<'db> Iterator for DeclarationsIterator<'_, 'db> { @@ -634,11 +605,11 @@ impl<'db> Iterator for DeclarationsIterator<'_, 'db> { self.inner.next().map( |LiveDeclaration { declaration, - visibility_constraint, + reachability_constraint, }| { DeclarationWithConstraint { declaration: self.all_definitions[*declaration], - visibility_constraint: *visibility_constraint, + reachability_constraint: *reachability_constraint, } }, ) @@ -651,8 +622,7 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { place_states: IndexVec, - scope_start_visibility: ScopedVisibilityConstraintId, - reachability: ScopedVisibilityConstraintId, + reachability: ScopedReachabilityConstraintId, } #[derive(Debug)] @@ -666,60 +636,18 @@ pub(super) struct UseDefMapBuilder<'db> { /// Builder of narrowing constraints. pub(super) narrowing_constraints: NarrowingConstraintsBuilder, - /// Builder of visibility constraints. - pub(super) visibility_constraints: VisibilityConstraintsBuilder, - - /// A constraint which describes the visibility of the unbound/undeclared state, i.e. - /// whether or not a use of a place at the current point in control flow would see - /// the fake `x = ` binding at the start of the scope. This is important for - /// cases like the following, where we need to hide the implicit unbound binding in - /// the "else" branch: - /// ```py - /// # x = - /// - /// if True: - /// x = 1 - /// - /// use(x) # the `x = ` binding is not visible here - /// ``` - pub(super) scope_start_visibility: ScopedVisibilityConstraintId, + /// Builder of reachability constraints. + pub(super) reachability_constraints: ReachabilityConstraintsBuilder, /// Live bindings at each so-far-recorded use. bindings_by_use: IndexVec, - /// Tracks whether or not the scope start is visible at the current point in control flow. - /// This is subtly different from `scope_start_visibility`, as we apply these constraints - /// at the beginning of a branch. Visibility constraints, on the other hand, need to be - /// applied at the end of a branch, as we apply them retroactively to all live bindings: - /// ```py - /// y = 1 - /// - /// if test: - /// # we record a reachability constraint of [test] here, - /// # so that it can affect the use of `x`: - /// - /// x # we store a reachability constraint of [test] for this use of `x` - /// - /// y = 2 - /// - /// # we record a visibility constraint of [test] here, which retroactively affects - /// # the `y = 1` and the `y = 2` binding. - /// else: - /// # we record a reachability constraint of [~test] here. - /// - /// pass - /// - /// # we record a visibility constraint of [~test] here, which retroactively affects - /// # the `y = 1` binding. - /// - /// use(y) - /// ``` - /// Depending on the value of `test`, the `y = 1`, `y = 2`, or both bindings may be visible. - /// The use of `x` is recorded with a reachability constraint of `[test]`. - pub(super) reachability: ScopedVisibilityConstraintId, + /// Tracks whether or not the current point in control flow is reachable from the + /// start of the scope. + pub(super) reachability: ScopedReachabilityConstraintId, /// Tracks whether or not a given AST node is reachable from the start of the scope. - node_reachability: FxHashMap, + node_reachability: FxHashMap, /// Live declarations for each so-far-recorded binding. declarations_by_binding: FxHashMap, Declarations>, @@ -744,10 +672,9 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: IndexVec::from_iter([DefinitionState::Undefined]), predicates: PredicatesBuilder::default(), narrowing_constraints: NarrowingConstraintsBuilder::default(), - visibility_constraints: VisibilityConstraintsBuilder::default(), - scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, + reachability_constraints: ReachabilityConstraintsBuilder::default(), bindings_by_use: IndexVec::new(), - reachability: ScopedVisibilityConstraintId::ALWAYS_TRUE, + reachability: ScopedReachabilityConstraintId::ALWAYS_TRUE, node_reachability: FxHashMap::default(), declarations_by_binding: FxHashMap::default(), bindings_by_declaration: FxHashMap::default(), @@ -757,14 +684,20 @@ impl<'db> UseDefMapBuilder<'db> { } } pub(super) fn mark_unreachable(&mut self) { - self.record_visibility_constraint(ScopedVisibilityConstraintId::ALWAYS_FALSE); - self.reachability = ScopedVisibilityConstraintId::ALWAYS_FALSE; + self.reachability = ScopedReachabilityConstraintId::ALWAYS_FALSE; + + for state in &mut self.place_states { + state.record_reachability_constraint( + &mut self.reachability_constraints, + ScopedReachabilityConstraintId::ALWAYS_FALSE, + ); + } } pub(super) fn add_place(&mut self, place: ScopedPlaceId) { let new_place = self .place_states - .push(PlaceState::undefined(self.scope_start_visibility)); + .push(PlaceState::undefined(self.reachability)); debug_assert_eq!(place, new_place); } @@ -780,7 +713,7 @@ impl<'db> UseDefMapBuilder<'db> { .insert(binding, place_state.declarations().clone()); place_state.record_binding( def_id, - self.scope_start_visibility, + self.reachability, self.is_class_scope, is_place_name, ); @@ -798,136 +731,85 @@ impl<'db> UseDefMapBuilder<'db> { } } - pub(super) fn record_visibility_constraint( - &mut self, - constraint: ScopedVisibilityConstraintId, - ) { - for state in &mut self.place_states { - state.record_visibility_constraint(&mut self.visibility_constraints, constraint); - } - self.scope_start_visibility = self - .visibility_constraints - .add_and_constraint(self.scope_start_visibility, constraint); - } - /// Snapshot the state of a single place at the current point in control flow. /// - /// This is only used for `*`-import visibility constraints, which are handled differently - /// to most other visibility constraints. See the doc-comment for - /// [`Self::record_and_negate_star_import_visibility_constraint`] for more details. + /// This is only used for `*`-import reachability constraints, which are handled differently + /// to most other reachability constraints. See the doc-comment for + /// [`Self::record_and_negate_star_import_reachability_constraint`] for more details. pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState { self.place_states[place].clone() } - /// This method exists solely for handling `*`-import visibility constraints. + /// This method exists solely for handling `*`-import reachability constraints. /// - /// The reason why we add visibility constraints for [`Definition`]s created by `*` imports + /// The reason why we add reachability constraints for [`Definition`]s created by `*` imports /// is laid out in the doc-comment for [`StarImportPlaceholderPredicate`]. But treating these - /// visibility constraints in the use-def map the same way as all other visibility constraints + /// reachability constraints in the use-def map the same way as all other reachability constraints /// was shown to lead to [significant regressions] for small codebases where typeshed /// dominates. (Although `*` imports are not common generally, they are used in several /// important places by typeshed.) /// /// To solve these regressions, it was observed that we could do significantly less work for /// `*`-import definitions. We do a number of things differently here to our normal handling of - /// visibility constraints: + /// reachability constraints: /// - /// - We only apply and negate the visibility constraints to a single symbol, rather than to + /// - We only apply and negate the reachability constraints to a single symbol, rather than to /// all symbols. This is possible here because, unlike most definitions, we know in advance that /// exactly one definition occurs inside the "if-true" predicate branch, and we know exactly /// which definition it is. /// - /// Doing things this way is cheaper in and of itself. However, it also allows us to avoid - /// calling [`Self::simplify_visibility_constraints`] after the constraint has been applied to - /// the "if-predicate-true" branch and negated for the "if-predicate-false" branch. Simplifying - /// the visibility constraints is only important for places that did not have any new - /// definitions inside either the "if-predicate-true" branch or the "if-predicate-false" branch. - /// /// - We only snapshot the state for a single place prior to the definition, rather than doing /// expensive calls to [`Self::snapshot`]. Again, this is possible because we know /// that only a single definition occurs inside the "if-predicate-true" predicate branch. /// /// - Normally we take care to check whether an "if-predicate-true" branch or an - /// "if-predicate-false" branch contains a terminal statement: these can affect the visibility + /// "if-predicate-false" branch contains a terminal statement: these can affect the reachability /// of symbols defined inside either branch. However, in the case of `*`-import definitions, /// this is unnecessary (and therefore not done in this method), since we know that a `*`-import /// predicate cannot create a terminal statement inside either branch. /// /// [significant regressions]: https://github.com/astral-sh/ruff/pull/17286#issuecomment-2786755746 - pub(super) fn record_and_negate_star_import_visibility_constraint( + pub(super) fn record_and_negate_star_import_reachability_constraint( &mut self, star_import: StarImportPlaceholderPredicate<'db>, symbol: ScopedPlaceId, pre_definition_state: PlaceState, ) { let predicate_id = self.add_predicate(star_import.into()); - let visibility_id = self.visibility_constraints.add_atom(predicate_id); - let negated_visibility_id = self - .visibility_constraints - .add_not_constraint(visibility_id); + let reachability_id = self.reachability_constraints.add_atom(predicate_id); + let negated_reachability_id = self + .reachability_constraints + .add_not_constraint(reachability_id); let mut post_definition_state = std::mem::replace(&mut self.place_states[symbol], pre_definition_state); post_definition_state - .record_visibility_constraint(&mut self.visibility_constraints, visibility_id); + .record_reachability_constraint(&mut self.reachability_constraints, reachability_id); - self.place_states[symbol] - .record_visibility_constraint(&mut self.visibility_constraints, negated_visibility_id); + self.place_states[symbol].record_reachability_constraint( + &mut self.reachability_constraints, + negated_reachability_id, + ); self.place_states[symbol].merge( post_definition_state, &mut self.narrowing_constraints, - &mut self.visibility_constraints, + &mut self.reachability_constraints, ); } - /// This method resets the visibility constraints for all places to a previous state - /// *if* there have been no new declarations or bindings since then. Consider the - /// following example: - /// ```py - /// x = 0 - /// y = 0 - /// if test_a: - /// y = 1 - /// elif test_b: - /// y = 2 - /// elif test_c: - /// y = 3 - /// - /// # RESET - /// ``` - /// We build a complex visibility constraint for the `y = 0` binding. We build the same - /// constraint for the `x = 0` binding as well, but at the `RESET` point, we can get rid - /// of it, as the `if`-`elif`-`elif` chain doesn't include any new bindings of `x`. - pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { - debug_assert!(self.place_states.len() >= snapshot.place_states.len()); - - // If there are any control flow paths that have become unreachable between `snapshot` and - // now, then it's not valid to simplify any visibility constraints to `snapshot`. - if self.scope_start_visibility != snapshot.scope_start_visibility { - return; - } - - // Note that this loop terminates when we reach a place not present in the snapshot. - // This means we keep visibility constraints for all new places, which is intended, - // since these places have been introduced in the corresponding branch, which might - // be subject to visibility constraints. We only simplify/reset visibility constraints - // for places that have the same bindings and declarations present compared to the - // snapshot. - for (current, snapshot) in self.place_states.iter_mut().zip(snapshot.place_states) { - current.simplify_visibility_constraints(snapshot); - } - } - pub(super) fn record_reachability_constraint( &mut self, - constraint: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { + constraint: ScopedReachabilityConstraintId, + ) { self.reachability = self - .visibility_constraints + .reachability_constraints .add_and_constraint(self.reachability, constraint); - self.reachability + + for state in &mut self.place_states { + state.record_reachability_constraint(&mut self.reachability_constraints, constraint); + } } pub(super) fn record_declaration( @@ -941,7 +823,7 @@ impl<'db> UseDefMapBuilder<'db> { let place_state = &mut self.place_states[place]; self.bindings_by_declaration .insert(declaration, place_state.bindings().clone()); - place_state.record_declaration(def_id); + place_state.record_declaration(def_id, self.reachability); } pub(super) fn record_declaration_and_binding( @@ -956,10 +838,10 @@ impl<'db> UseDefMapBuilder<'db> { .all_definitions .push(DefinitionState::Defined(definition)); let place_state = &mut self.place_states[place]; - place_state.record_declaration(def_id); + place_state.record_declaration(def_id, self.reachability); place_state.record_binding( def_id, - self.scope_start_visibility, + self.reachability, self.is_class_scope, is_place_name, ); @@ -970,7 +852,7 @@ impl<'db> UseDefMapBuilder<'db> { let place_state = &mut self.place_states[place]; place_state.record_binding( def_id, - self.scope_start_visibility, + self.reachability, self.is_class_scope, is_place_name, ); @@ -1024,7 +906,6 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { place_states: self.place_states.clone(), - scope_start_visibility: self.scope_start_visibility, reachability: self.reachability, } } @@ -1039,16 +920,13 @@ impl<'db> UseDefMapBuilder<'db> { // Restore the current visible-definitions state to the given snapshot. self.place_states = snapshot.place_states; - self.scope_start_visibility = snapshot.scope_start_visibility; self.reachability = snapshot.reachability; // If the snapshot we are restoring is missing some places we've recorded since, we need // to fill them in so the place IDs continue to line up. Since they don't exist in the // snapshot, the correct state to fill them in with is "undefined". - self.place_states.resize( - num_places, - PlaceState::undefined(self.scope_start_visibility), - ); + self.place_states + .resize(num_places, PlaceState::undefined(self.reachability)); } /// Merge the given snapshot into the current state, reflecting that we might have taken either @@ -1059,13 +937,13 @@ impl<'db> UseDefMapBuilder<'db> { // unreachable, we can leave it out of the merged result entirely. Note that we cannot // perform any type inference at this point, so this is largely limited to unreachability // via terminal statements. If a flow's reachability depends on an expression in the code, - // we will include the flow in the merged result; the visibility constraints of its + // we will include the flow in the merged result; the reachability constraints of its // bindings will include this reachability condition, so that later during type inference, // we can determine whether any particular binding is non-visible due to unreachability. - if snapshot.scope_start_visibility == ScopedVisibilityConstraintId::ALWAYS_FALSE { + if snapshot.reachability == ScopedReachabilityConstraintId::ALWAYS_FALSE { return; } - if self.scope_start_visibility == ScopedVisibilityConstraintId::ALWAYS_FALSE { + if self.reachability == ScopedReachabilityConstraintId::ALWAYS_FALSE { self.restore(snapshot); return; } @@ -1081,24 +959,20 @@ impl<'db> UseDefMapBuilder<'db> { current.merge( snapshot, &mut self.narrowing_constraints, - &mut self.visibility_constraints, + &mut self.reachability_constraints, ); } else { current.merge( - PlaceState::undefined(snapshot.scope_start_visibility), + PlaceState::undefined(snapshot.reachability), &mut self.narrowing_constraints, - &mut self.visibility_constraints, + &mut self.reachability_constraints, ); // Place not present in snapshot, so it's unbound/undeclared from that path. } } - self.scope_start_visibility = self - .visibility_constraints - .add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility); - self.reachability = self - .visibility_constraints + .reachability_constraints .add_or_constraint(self.reachability, snapshot.reachability); } @@ -1115,14 +989,14 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: self.all_definitions, predicates: self.predicates.build(), narrowing_constraints: self.narrowing_constraints.build(), - visibility_constraints: self.visibility_constraints.build(), + reachability_constraints: self.reachability_constraints.build(), bindings_by_use: self.bindings_by_use, node_reachability: self.node_reachability, public_places: self.place_states, declarations_by_binding: self.declarations_by_binding, bindings_by_declaration: self.bindings_by_declaration, eager_snapshots: self.eager_snapshots, - scope_start_visibility: self.scope_start_visibility, + end_of_scope_reachability: self.reachability, } } } diff --git a/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs b/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs index d73134ead65a70..44857e66b93590 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def/place_state.rs @@ -49,8 +49,8 @@ use smallvec::{SmallVec, smallvec}; use crate::semantic_index::narrowing_constraints::{ NarrowingConstraintsBuilder, ScopedNarrowingConstraint, ScopedNarrowingConstraintPredicate, }; -use crate::semantic_index::visibility_constraints::{ - ScopedVisibilityConstraintId, VisibilityConstraintsBuilder, +use crate::semantic_index::reachability_constraints::{ + ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, }; /// A newtype-index for a definition in a particular scope. @@ -76,7 +76,7 @@ impl ScopedDefinitionId { const INLINE_DEFINITIONS_PER_PLACE: usize = 4; /// Live declarations for a single place at some point in control flow, with their -/// corresponding visibility constraints. +/// corresponding reachability constraints. #[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)] pub(super) struct Declarations { /// A list of live declarations for this place, sorted by their `ScopedDefinitionId` @@ -87,16 +87,16 @@ pub(super) struct Declarations { #[derive(Clone, Debug, PartialEq, Eq)] pub(super) struct LiveDeclaration { pub(super) declaration: ScopedDefinitionId, - pub(super) visibility_constraint: ScopedVisibilityConstraintId, + pub(super) reachability_constraint: ScopedReachabilityConstraintId, } pub(super) type LiveDeclarationsIterator<'a> = std::slice::Iter<'a, LiveDeclaration>; impl Declarations { - fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { + fn undeclared(reachability_constraint: ScopedReachabilityConstraintId) -> Self { let initial_declaration = LiveDeclaration { declaration: ScopedDefinitionId::UNBOUND, - visibility_constraint: scope_start_visibility, + reachability_constraint, }; Self { live_declarations: smallvec![initial_declaration], @@ -104,24 +104,28 @@ impl Declarations { } /// Record a newly-encountered declaration for this place. - fn record_declaration(&mut self, declaration: ScopedDefinitionId) { + fn record_declaration( + &mut self, + declaration: ScopedDefinitionId, + reachability_constraint: ScopedReachabilityConstraintId, + ) { // The new declaration replaces all previous live declaration in this path. self.live_declarations.clear(); self.live_declarations.push(LiveDeclaration { declaration, - visibility_constraint: ScopedVisibilityConstraintId::ALWAYS_TRUE, + reachability_constraint, }); } - /// Add given visibility constraint to all live declarations. - pub(super) fn record_visibility_constraint( + /// Add given reachability constraint to all live declarations. + pub(super) fn record_reachability_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraintsBuilder, - constraint: ScopedVisibilityConstraintId, + reachability_constraints: &mut ReachabilityConstraintsBuilder, + constraint: ScopedReachabilityConstraintId, ) { for declaration in &mut self.live_declarations { - declaration.visibility_constraint = visibility_constraints - .add_and_constraint(declaration.visibility_constraint, constraint); + declaration.reachability_constraint = reachability_constraints + .add_and_constraint(declaration.reachability_constraint, constraint); } } @@ -130,46 +134,24 @@ impl Declarations { self.live_declarations.iter() } - /// Iterate over the IDs of each currently live declaration for this place - fn iter_declarations(&self) -> impl Iterator + '_ { - self.iter().map(|lb| lb.declaration) - } - - fn simplify_visibility_constraints(&mut self, other: Declarations) { - // If the set of live declarations hasn't changed, don't simplify. - if self.live_declarations.len() != other.live_declarations.len() - || !self.iter_declarations().eq(other.iter_declarations()) - { - return; - } - - for (declaration, other_declaration) in self - .live_declarations - .iter_mut() - .zip(other.live_declarations) - { - declaration.visibility_constraint = other_declaration.visibility_constraint; - } - } - - fn merge(&mut self, b: Self, visibility_constraints: &mut VisibilityConstraintsBuilder) { + fn merge(&mut self, b: Self, reachability_constraints: &mut ReachabilityConstraintsBuilder) { let a = std::mem::take(self); // Invariant: merge_join_by consumes the two iterators in sorted order, which ensures that // the merged `live_declarations` vec remains sorted. If a definition is found in both `a` // and `b`, we compose the constraints from the two paths in an appropriate way - // (intersection for narrowing constraints; ternary OR for visibility constraints). If a + // (intersection for narrowing constraints; ternary OR for reachability constraints). If a // definition is found in only one path, it is used as-is. let a = a.live_declarations.into_iter(); let b = b.live_declarations.into_iter(); for zipped in a.merge_join_by(b, |a, b| a.declaration.cmp(&b.declaration)) { match zipped { EitherOrBoth::Both(a, b) => { - let visibility_constraint = visibility_constraints - .add_or_constraint(a.visibility_constraint, b.visibility_constraint); + let reachability_constraint = reachability_constraints + .add_or_constraint(a.reachability_constraint, b.reachability_constraint); self.live_declarations.push(LiveDeclaration { declaration: a.declaration, - visibility_constraint, + reachability_constraint, }); } @@ -193,7 +175,7 @@ pub(super) enum EagerSnapshot { } /// Live bindings for a single place at some point in control flow. Each live binding comes -/// with a set of narrowing constraints and a visibility constraint. +/// with a set of narrowing constraints and a reachability constraint. #[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)] pub(super) struct Bindings { /// The narrowing constraint applicable to the "unbound" binding, if we need access to it even @@ -217,17 +199,17 @@ impl Bindings { pub(super) struct LiveBinding { pub(super) binding: ScopedDefinitionId, pub(super) narrowing_constraint: ScopedNarrowingConstraint, - pub(super) visibility_constraint: ScopedVisibilityConstraintId, + pub(super) reachability_constraint: ScopedReachabilityConstraintId, } pub(super) type LiveBindingsIterator<'a> = std::slice::Iter<'a, LiveBinding>; impl Bindings { - fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { + fn unbound(reachability_constraint: ScopedReachabilityConstraintId) -> Self { let initial_binding = LiveBinding { binding: ScopedDefinitionId::UNBOUND, narrowing_constraint: ScopedNarrowingConstraint::empty(), - visibility_constraint: scope_start_visibility, + reachability_constraint, }; Self { unbound_narrowing_constraint: None, @@ -239,7 +221,7 @@ impl Bindings { pub(super) fn record_binding( &mut self, binding: ScopedDefinitionId, - visibility_constraint: ScopedVisibilityConstraintId, + reachability_constraint: ScopedReachabilityConstraintId, is_class_scope: bool, is_place_name: bool, ) { @@ -254,7 +236,7 @@ impl Bindings { self.live_bindings.push(LiveBinding { binding, narrowing_constraint: ScopedNarrowingConstraint::empty(), - visibility_constraint, + reachability_constraint, }); } @@ -270,15 +252,15 @@ impl Bindings { } } - /// Add given visibility constraint to all live bindings. - pub(super) fn record_visibility_constraint( + /// Add given reachability constraint to all live bindings. + pub(super) fn record_reachability_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraintsBuilder, - constraint: ScopedVisibilityConstraintId, + reachability_constraints: &mut ReachabilityConstraintsBuilder, + constraint: ScopedReachabilityConstraintId, ) { for binding in &mut self.live_bindings { - binding.visibility_constraint = visibility_constraints - .add_and_constraint(binding.visibility_constraint, constraint); + binding.reachability_constraint = reachability_constraints + .add_and_constraint(binding.reachability_constraint, constraint); } } @@ -287,29 +269,11 @@ impl Bindings { self.live_bindings.iter() } - /// Iterate over the IDs of each currently live binding for this place - fn iter_bindings(&self) -> impl Iterator + '_ { - self.iter().map(|lb| lb.binding) - } - - fn simplify_visibility_constraints(&mut self, other: Bindings) { - // If the set of live bindings hasn't changed, don't simplify. - if self.live_bindings.len() != other.live_bindings.len() - || !self.iter_bindings().eq(other.iter_bindings()) - { - return; - } - - for (binding, other_binding) in self.live_bindings.iter_mut().zip(other.live_bindings) { - binding.visibility_constraint = other_binding.visibility_constraint; - } - } - fn merge( &mut self, b: Self, narrowing_constraints: &mut NarrowingConstraintsBuilder, - visibility_constraints: &mut VisibilityConstraintsBuilder, + reachability_constraints: &mut ReachabilityConstraintsBuilder, ) { let a = std::mem::take(self); @@ -324,7 +288,7 @@ impl Bindings { // Invariant: merge_join_by consumes the two iterators in sorted order, which ensures that // the merged `live_bindings` vec remains sorted. If a definition is found in both `a` and // `b`, we compose the constraints from the two paths in an appropriate way (intersection - // for narrowing constraints; ternary OR for visibility constraints). If a definition is + // for narrowing constraints; ternary OR for reachability constraints). If a definition is // found in only one path, it is used as-is. let a = a.live_bindings.into_iter(); let b = b.live_bindings.into_iter(); @@ -337,14 +301,14 @@ impl Bindings { let narrowing_constraint = narrowing_constraints .intersect_constraints(a.narrowing_constraint, b.narrowing_constraint); - // For visibility constraints, we merge them using a ternary OR operation: - let visibility_constraint = visibility_constraints - .add_or_constraint(a.visibility_constraint, b.visibility_constraint); + // For reachability constraints, we merge them using a ternary OR operation: + let reachability_constraint = reachability_constraints + .add_or_constraint(a.reachability_constraint, b.reachability_constraint); self.live_bindings.push(LiveBinding { binding: a.binding, narrowing_constraint, - visibility_constraint, + reachability_constraint, }); } @@ -364,10 +328,10 @@ pub(in crate::semantic_index) struct PlaceState { impl PlaceState { /// Return a new [`PlaceState`] representing an unbound, undeclared place. - pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { + pub(super) fn undefined(reachability: ScopedReachabilityConstraintId) -> Self { Self { - declarations: Declarations::undeclared(scope_start_visibility), - bindings: Bindings::unbound(scope_start_visibility), + declarations: Declarations::undeclared(reachability), + bindings: Bindings::unbound(reachability), } } @@ -375,14 +339,14 @@ impl PlaceState { pub(super) fn record_binding( &mut self, binding_id: ScopedDefinitionId, - visibility_constraint: ScopedVisibilityConstraintId, + reachability_constraint: ScopedReachabilityConstraintId, is_class_scope: bool, is_place_name: bool, ) { debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND); self.bindings.record_binding( binding_id, - visibility_constraint, + reachability_constraint, is_class_scope, is_place_name, ); @@ -398,31 +362,26 @@ impl PlaceState { .record_narrowing_constraint(narrowing_constraints, constraint); } - /// Add given visibility constraint to all live bindings. - pub(super) fn record_visibility_constraint( + /// Add given reachability constraint to all live bindings. + pub(super) fn record_reachability_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraintsBuilder, - constraint: ScopedVisibilityConstraintId, + reachability_constraints: &mut ReachabilityConstraintsBuilder, + constraint: ScopedReachabilityConstraintId, ) { self.bindings - .record_visibility_constraint(visibility_constraints, constraint); + .record_reachability_constraint(reachability_constraints, constraint); self.declarations - .record_visibility_constraint(visibility_constraints, constraint); - } - - /// Simplifies this snapshot to have the same visibility constraints as a previous point in the - /// control flow, but only if the set of live bindings or declarations for this place hasn't - /// changed. - pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: PlaceState) { - self.bindings - .simplify_visibility_constraints(snapshot_state.bindings); - self.declarations - .simplify_visibility_constraints(snapshot_state.declarations); + .record_reachability_constraint(reachability_constraints, constraint); } /// Record a newly-encountered declaration of this place. - pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { - self.declarations.record_declaration(declaration_id); + pub(super) fn record_declaration( + &mut self, + declaration_id: ScopedDefinitionId, + reachability_constraint: ScopedReachabilityConstraintId, + ) { + self.declarations + .record_declaration(declaration_id, reachability_constraint); } /// Merge another [`PlaceState`] into this one. @@ -430,12 +389,12 @@ impl PlaceState { &mut self, b: PlaceState, narrowing_constraints: &mut NarrowingConstraintsBuilder, - visibility_constraints: &mut VisibilityConstraintsBuilder, + reachability_constraints: &mut ReachabilityConstraintsBuilder, ) { self.bindings - .merge(b.bindings, narrowing_constraints, visibility_constraints); + .merge(b.bindings, narrowing_constraints, reachability_constraints); self.declarations - .merge(b.declarations, visibility_constraints); + .merge(b.declarations, reachability_constraints); } pub(super) fn bindings(&self) -> &Bindings { @@ -488,7 +447,7 @@ mod tests { .map( |LiveDeclaration { declaration, - visibility_constraint: _, + reachability_constraint: _, }| { if *declaration == ScopedDefinitionId::UNBOUND { "undeclared".into() @@ -504,7 +463,7 @@ mod tests { #[test] fn unbound() { let narrowing_constraints = NarrowingConstraintsBuilder::default(); - let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); assert_bindings(&narrowing_constraints, &sym, &["unbound<>"]); } @@ -512,10 +471,10 @@ mod tests { #[test] fn with() { let narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym.record_binding( ScopedDefinitionId::from_u32(1), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); @@ -526,10 +485,10 @@ mod tests { #[test] fn record_constraint() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym.record_binding( ScopedDefinitionId::from_u32(1), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); @@ -542,23 +501,23 @@ mod tests { #[test] fn merge() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut visibility_constraints = VisibilityConstraintsBuilder::default(); + let mut reachability_constraints = ReachabilityConstraintsBuilder::default(); // merging the same definition with the same constraint keeps the constraint - let mut sym1a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym1a.record_binding( ScopedDefinitionId::from_u32(1), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); let predicate = ScopedPredicateId::from_u32(0).into(); sym1a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym1b.record_binding( ScopedDefinitionId::from_u32(1), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); @@ -568,26 +527,26 @@ mod tests { sym1a.merge( sym1b, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); let mut sym1 = sym1a; assert_bindings(&narrowing_constraints, &sym1, &["1<0>"]); // merging the same definition with differing constraints drops all constraints - let mut sym2a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym2a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym2a.record_binding( ScopedDefinitionId::from_u32(2), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); let predicate = ScopedPredicateId::from_u32(1).into(); sym2a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym1b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym1b.record_binding( ScopedDefinitionId::from_u32(2), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); @@ -597,28 +556,28 @@ mod tests { sym2a.merge( sym1b, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); let sym2 = sym2a; assert_bindings(&narrowing_constraints, &sym2, &["2<>"]); // merging a constrained definition with unbound keeps both - let mut sym3a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let mut sym3a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym3a.record_binding( ScopedDefinitionId::from_u32(3), - ScopedVisibilityConstraintId::ALWAYS_TRUE, + ScopedReachabilityConstraintId::ALWAYS_TRUE, false, true, ); let predicate = ScopedPredicateId::from_u32(3).into(); sym3a.record_narrowing_constraint(&mut narrowing_constraints, predicate); - let sym2b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym2b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym3a.merge( sym2b, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); let sym3 = sym3a; assert_bindings(&narrowing_constraints, &sym3, &["unbound<>", "3<3>"]); @@ -627,7 +586,7 @@ mod tests { sym1.merge( sym3, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); let sym = sym1; assert_bindings(&narrowing_constraints, &sym, &["unbound<>", "1<0>", "3<3>"]); @@ -635,24 +594,33 @@ mod tests { #[test] fn no_declaration() { - let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); assert_declarations(&sym, &["undeclared"]); } #[test] fn record_declaration() { - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); + sym.record_declaration( + ScopedDefinitionId::from_u32(1), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); assert_declarations(&sym, &["1"]); } #[test] fn record_declaration_override() { - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - sym.record_declaration(ScopedDefinitionId::from_u32(2)); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); + sym.record_declaration( + ScopedDefinitionId::from_u32(1), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); + sym.record_declaration( + ScopedDefinitionId::from_u32(2), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); assert_declarations(&sym, &["2"]); } @@ -660,17 +628,23 @@ mod tests { #[test] fn record_declaration_merge() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut visibility_constraints = VisibilityConstraintsBuilder::default(); - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); + let mut reachability_constraints = ReachabilityConstraintsBuilder::default(); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); + sym.record_declaration( + ScopedDefinitionId::from_u32(1), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); - let mut sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym2.record_declaration(ScopedDefinitionId::from_u32(2)); + let mut sym2 = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); + sym2.record_declaration( + ScopedDefinitionId::from_u32(2), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); sym.merge( sym2, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); assert_declarations(&sym, &["1", "2"]); @@ -679,16 +653,19 @@ mod tests { #[test] fn record_declaration_merge_partial_undeclared() { let mut narrowing_constraints = NarrowingConstraintsBuilder::default(); - let mut visibility_constraints = VisibilityConstraintsBuilder::default(); - let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); + let mut reachability_constraints = ReachabilityConstraintsBuilder::default(); + let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); + sym.record_declaration( + ScopedDefinitionId::from_u32(1), + ScopedReachabilityConstraintId::ALWAYS_TRUE, + ); - let sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + let sym2 = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE); sym.merge( sym2, &mut narrowing_constraints, - &mut visibility_constraints, + &mut reachability_constraints, ); assert_declarations(&sym, &["undeclared", "1"]); diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index d02d7845082457..4c7a15799f999c 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7052,6 +7052,10 @@ impl Truthiness { matches!(self, Truthiness::AlwaysFalse) } + pub(crate) const fn may_be_true(self) -> bool { + !self.is_always_false() + } + pub(crate) const fn is_always_true(self) -> bool { matches!(self, Truthiness::AlwaysTrue) } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 708a8e4f40f0f9..4c729caee92f9d 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1637,8 +1637,8 @@ impl<'db> ClassLiteral<'db> { let method_scope = method_scope_id.to_scope_id(db, file); let method_map = use_def_map(db, method_scope); - // The attribute assignment inherits the visibility of the method which contains it - let is_method_visible = + // The attribute assignment inherits the reachability of the method which contains it + let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function(&module) { let method = index.expect_single_definition(method_def); let method_place = class_table.place_id_by_name(&method_def.name).unwrap(); @@ -1646,13 +1646,13 @@ impl<'db> ClassLiteral<'db> { .public_bindings(method_place) .find_map(|bind| { (bind.binding.is_defined_and(|def| def == method)) - .then(|| class_map.is_binding_visible(db, &bind)) + .then(|| class_map.is_binding_reachable(db, &bind)) }) .unwrap_or(Truthiness::AlwaysFalse) } else { Truthiness::AlwaysFalse }; - if is_method_visible.is_always_false() { + if is_method_reachable.is_always_false() { continue; } @@ -1663,7 +1663,7 @@ impl<'db> ClassLiteral<'db> { for attribute_assignment in attribute_assignments { if let DefinitionState::Undefined = attribute_assignment.binding { // Store the implicit unbound binding here so that we can delay the - // computation of `unbound_visibility` to the point when we actually + // computation of `unbound_reachability` to the point when we actually // need it. This is an optimization for the common case where the // `unbound` binding is the only binding of the `name` attribute, // i.e. if there is no `self.name = …` assignment in this method. @@ -1675,8 +1675,8 @@ impl<'db> ClassLiteral<'db> { continue; }; match method_map - .is_binding_visible(db, &attribute_assignment) - .and(is_method_visible) + .is_binding_reachable(db, &attribute_assignment) + .and(is_method_reachable) { Truthiness::AlwaysTrue => { is_attribute_bound = Truthiness::AlwaysTrue; @@ -1691,17 +1691,17 @@ impl<'db> ClassLiteral<'db> { } } - // There is at least one attribute assignment that may be visible, - // so if `unbound_visibility` is always false then this attribute is considered bound. - // TODO: this is incomplete logic since the attributes bound after termination are considered visible. - let unbound_visibility = unbound_binding + // There is at least one attribute assignment that may be reachable, so if `unbound_reachability` is + // always false then this attribute is considered bound. + // TODO: this is incomplete logic since the attributes bound after termination are considered reachable. + let unbound_reachability = unbound_binding .as_ref() - .map(|binding| method_map.is_binding_visible(db, binding)) + .map(|binding| method_map.is_binding_reachable(db, binding)) .unwrap_or(Truthiness::AlwaysFalse); - if unbound_visibility + if unbound_reachability .negate() - .and(is_method_visible) + .and(is_method_reachable) .is_always_true() { is_attribute_bound = Truthiness::AlwaysTrue; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 812e379ff9f0ef..8e1752e7fb6713 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1853,6 +1853,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { function.known(self.db()), Some(KnownFunction::Overload | KnownFunction::AbstractMethod) ), + Type::Never => { + // In unreachable code, we infer `Never` for decorators like `typing.overload`. + // Return `true` here to avoid false positive `invalid-return-type` lints for + // `@overload`ed functions without a body in unreachable code. + true + } _ => false, } }) @@ -1967,7 +1973,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); } let use_def = self.index.use_def_map(scope_id); - if use_def.can_implicit_return(self.db()) + if use_def.can_implicitly_return_none(self.db()) && !Type::none(self.db()).is_assignable_to(self.db(), expected_ty) { let no_return = self.return_types_and_ranges.is_empty(); @@ -9200,6 +9206,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } = subscript; match value_ty { + Type::Never => { + // This case can be entered when we use a type annotation like `Literal[1]` + // in unreachable code, since we infer `Never` for `Literal`. We call + // `infer_expression` (instead of `infer_type_expression`) here to avoid + // false-positive `invalid-type-form` diagnostics (`1` is not a valid type + // expression). + self.infer_expression(&subscript.slice); + Type::unknown() + } Type::ClassLiteral(literal) if literal.is_known(self.db(), KnownClass::Any) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic("Type `typing.Any` expected no type parameter"); @@ -10303,16 +10318,16 @@ mod tests { } /// Test that a symbol known to be unbound in a scope does not still trigger cycle-causing - /// visibility-constraint checks in that scope. + /// reachability-constraint checks in that scope. #[test] - fn unbound_symbol_no_visibility_constraint_check() { + fn unbound_symbol_no_reachability_constraint_check() { let mut db = setup_db(); // If the bug we are testing for is not fixed, what happens is that when inferring the // `flag: bool = True` definitions, we look up `bool` as a deferred name (thus from end of - // scope), and because of the early return its "unbound" binding has a visibility + // scope), and because of the early return its "unbound" binding has a reachability // constraint of `~flag`, which we evaluate, meaning we have to evaluate the definition of - // `flag` -- and we are in a cycle. With the fix, we short-circuit evaluating visibility + // `flag` -- and we are in a cycle. With the fix, we short-circuit evaluating reachability // constraints on "unbound" if a symbol is otherwise not bound. db.write_dedented( "src/a.py", From a1c69ca46008bd4b602dc5cfae30de7594947eb6 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Jun 2025 09:52:26 +0200 Subject: [PATCH 445/487] [ty] Enable ecosystem check for 'pywin32' (#18716) ## Summary Follow-up to #18621 --- crates/ty_python_semantic/resources/primer/bad.txt | 1 - crates/ty_python_semantic/resources/primer/good.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index 41010335f3a01e..b3d6aa33b1ee79 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -13,7 +13,6 @@ pandera # stack overflow pip # vendors packaging, see above pylint # cycle panics (self-recursive type alias) pyodide # too many cycle iterations -pywin32 # bad use-def map (binding with definitely-visible unbound) setuptools # vendors packaging, see above spack # slow, success, but mypy-primer hangs processing the output spark # too many iterations diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index ea322176ea05bb..8d5dc6443874c3 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -92,6 +92,7 @@ pytest-robotframework python-chess python-htmlgen python-sop +pywin32 rclip rich rotki From 390918e7901f81556d659c687a83188e7ad91cfa Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 17 Jun 2025 13:50:45 +0530 Subject: [PATCH 446/487] [ty] Add `python.ty.disableLanguageServices` config (#18230) ## Summary PR adding support for it in the VS Code extension: https://github.com/astral-sh/ty-vscode/pull/36 This PR adds support for `python.ty.disableLanguageServices` to the ty language server by accepting this as server setting. This has the same issue as https://github.com/astral-sh/ty/issues/282 in that it only works when configured globally. Fixing that requires support for multiple workspaces in the server itself. I also went ahead and did a similar refactor as the Ruff server to use "Options" and "Settings" to keep the code consistent although the combine functionality doesn't exists yet because workspace settings isn't supported in the ty server. ## Test Plan Refer to https://github.com/astral-sh/ty-vscode/pull/36 for the test demo. --- crates/ty_server/src/lib.rs | 2 +- crates/ty_server/src/server.rs | 50 ++++-- .../src/server/api/requests/completion.rs | 4 + .../api/requests/goto_type_definition.rs | 4 + .../src/server/api/requests/hover.rs | 4 + .../src/server/api/requests/inlay_hints.rs | 4 + crates/ty_server/src/session.rs | 21 ++- crates/ty_server/src/session/index.rs | 14 +- crates/ty_server/src/session/options.rs | 158 ++++++++++++++++++ crates/ty_server/src/session/settings.rs | 114 +------------ 10 files changed, 239 insertions(+), 136 deletions(-) create mode 100644 crates/ty_server/src/session/options.rs diff --git a/crates/ty_server/src/lib.rs b/crates/ty_server/src/lib.rs index cd7d71a73c1868..9c7ad00e4eeb59 100644 --- a/crates/ty_server/src/lib.rs +++ b/crates/ty_server/src/lib.rs @@ -1,7 +1,7 @@ use crate::server::{ConnectionInitializer, Server}; use anyhow::Context; pub use document::{NotebookDocument, PositionEncoding, TextDocument}; -pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session}; +pub use session::{DocumentQuery, DocumentSnapshot, Session}; use std::num::NonZeroUsize; mod document; diff --git a/crates/ty_server/src/server.rs b/crates/ty_server/src/server.rs index 1d1f78293de1c8..7271c1291977d1 100644 --- a/crates/ty_server/src/server.rs +++ b/crates/ty_server/src/server.rs @@ -2,7 +2,7 @@ use self::schedule::spawn_main_loop; use crate::PositionEncoding; -use crate::session::{AllSettings, ClientSettings, Session}; +use crate::session::{AllOptions, ClientOptions, Session}; use lsp_server::Connection; use lsp_types::{ ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability, @@ -42,10 +42,10 @@ impl Server { ) -> crate::Result { let (id, init_params) = connection.initialize_start()?; - let AllSettings { - global_settings, - mut workspace_settings, - } = AllSettings::from_value( + let AllOptions { + global: global_options, + workspace: mut workspace_options, + } = AllOptions::from_value( init_params .initialization_options .unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())), @@ -68,17 +68,20 @@ impl Server { let client = Client::new(main_loop_sender.clone(), connection.sender.clone()); crate::logging::init_logging( - global_settings.tracing.log_level.unwrap_or_default(), - global_settings.tracing.log_file.as_deref(), + global_options.tracing.log_level.unwrap_or_default(), + global_options.tracing.log_file.as_deref(), ); let mut workspace_for_url = |url: Url| { - let Some(workspace_settings) = workspace_settings.as_mut() else { - return (url, ClientSettings::default()); + let Some(workspace_settings) = workspace_options.as_mut() else { + return (url, ClientOptions::default()); }; let settings = workspace_settings.remove(&url).unwrap_or_else(|| { - tracing::warn!("No workspace settings found for {}", url); - ClientSettings::default() + tracing::warn!( + "No workspace options found for {}, using default options", + url + ); + ClientOptions::default() }); (url, settings) }; @@ -86,16 +89,27 @@ impl Server { let workspaces = init_params .workspace_folders .filter(|folders| !folders.is_empty()) - .map(|folders| folders.into_iter().map(|folder| { - workspace_for_url(folder.uri) - }).collect()) + .map(|folders| { + folders + .into_iter() + .map(|folder| workspace_for_url(folder.uri)) + .collect() + }) .or_else(|| { - tracing::warn!("No workspace(s) were provided during initialization. Using the current working directory as a default workspace..."); - let uri = Url::from_file_path(std::env::current_dir().ok()?).ok()?; + let current_dir = std::env::current_dir().ok()?; + tracing::warn!( + "No workspace(s) were provided during initialization. \ + Using the current working directory as a default workspace: {}", + current_dir.display() + ); + let uri = Url::from_file_path(current_dir).ok()?; Some(vec![workspace_for_url(uri)]) }) .ok_or_else(|| { - anyhow::anyhow!("Failed to get the current working directory while creating a default workspace.") + anyhow::anyhow!( + "Failed to get the current working directory while creating a \ + default workspace." + ) })?; let workspaces = if workspaces.len() > 1 { @@ -121,7 +135,7 @@ impl Server { session: Session::new( &client_capabilities, position_encoding, - global_settings, + global_options, &workspaces, )?, client_capabilities, diff --git a/crates/ty_server/src/server/api/requests/completion.rs b/crates/ty_server/src/server/api/requests/completion.rs index 11d61b1450e584..7567a00987943b 100644 --- a/crates/ty_server/src/server/api/requests/completion.rs +++ b/crates/ty_server/src/server/api/requests/completion.rs @@ -30,6 +30,10 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { _client: &Client, params: CompletionParams, ) -> crate::server::Result> { + if snapshot.client_settings().is_language_services_disabled() { + return Ok(None); + } + let Some(file) = snapshot.file(db) else { tracing::debug!("Failed to resolve file for {:?}", params); return Ok(None); diff --git a/crates/ty_server/src/server/api/requests/goto_type_definition.rs b/crates/ty_server/src/server/api/requests/goto_type_definition.rs index 197e61dd0696a3..ead442c1a59072 100644 --- a/crates/ty_server/src/server/api/requests/goto_type_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_type_definition.rs @@ -28,6 +28,10 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler { _client: &Client, params: GotoTypeDefinitionParams, ) -> crate::server::Result> { + if snapshot.client_settings().is_language_services_disabled() { + return Ok(None); + } + let Some(file) = snapshot.file(db) else { tracing::debug!("Failed to resolve file for {:?}", params); return Ok(None); diff --git a/crates/ty_server/src/server/api/requests/hover.rs b/crates/ty_server/src/server/api/requests/hover.rs index f244cc81a3f6f5..6919e172372c01 100644 --- a/crates/ty_server/src/server/api/requests/hover.rs +++ b/crates/ty_server/src/server/api/requests/hover.rs @@ -28,6 +28,10 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler { _client: &Client, params: HoverParams, ) -> crate::server::Result> { + if snapshot.client_settings().is_language_services_disabled() { + return Ok(None); + } + let Some(file) = snapshot.file(db) else { tracing::debug!("Failed to resolve file for {:?}", params); return Ok(None); diff --git a/crates/ty_server/src/server/api/requests/inlay_hints.rs b/crates/ty_server/src/server/api/requests/inlay_hints.rs index bba7cd6ba26c86..62ffe111a2436b 100644 --- a/crates/ty_server/src/server/api/requests/inlay_hints.rs +++ b/crates/ty_server/src/server/api/requests/inlay_hints.rs @@ -27,6 +27,10 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { _client: &Client, params: InlayHintParams, ) -> crate::server::Result>> { + if snapshot.client_settings().is_language_services_disabled() { + return Ok(None); + } + let Some(file) = snapshot.file(db) else { tracing::debug!("Failed to resolve file for {:?}", params); return Ok(None); diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index f76138c13990c8..2f4ded2fa6a05c 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use anyhow::anyhow; use lsp_types::{ClientCapabilities, TextDocumentContentChangeEvent, Url}; +use options::GlobalOptions; use ruff_db::Db; use ruff_db::files::{File, system_path_to_file}; use ruff_db::system::SystemPath; @@ -14,8 +15,8 @@ use ty_project::{ProjectDatabase, ProjectMetadata}; pub(crate) use self::capabilities::ResolvedClientCapabilities; pub use self::index::DocumentQuery; -pub(crate) use self::settings::AllSettings; -pub use self::settings::ClientSettings; +pub(crate) use self::options::{AllOptions, ClientOptions}; +use self::settings::ClientSettings; use crate::document::{DocumentKey, DocumentVersion, NotebookDocument}; use crate::session::request_queue::RequestQueue; use crate::system::{AnySystemPath, LSPSystem}; @@ -24,6 +25,7 @@ use crate::{PositionEncoding, TextDocument}; mod capabilities; pub(crate) mod client; pub(crate) mod index; +mod options; mod request_queue; mod settings; @@ -58,12 +60,13 @@ impl Session { pub(crate) fn new( client_capabilities: &ClientCapabilities, position_encoding: PositionEncoding, - global_settings: ClientSettings, - workspace_folders: &[(Url, ClientSettings)], + global_options: GlobalOptions, + workspace_folders: &[(Url, ClientOptions)], ) -> crate::Result { let mut workspaces = BTreeMap::new(); - let index = Arc::new(index::Index::new(global_settings)); + let index = Arc::new(index::Index::new(global_options.into_settings())); + // TODO: Consider workspace settings for (url, _) in workspace_folders { let path = url .to_file_path() @@ -168,6 +171,7 @@ impl Session { let key = self.key_from_url(url).ok()?; Some(DocumentSnapshot { resolved_client_capabilities: self.resolved_client_capabilities.clone(), + client_settings: self.index().global_settings(), document_ref: self.index().make_document_ref(&key)?, position_encoding: self.position_encoding, }) @@ -303,6 +307,7 @@ impl Drop for MutIndexGuard<'_> { #[derive(Debug)] pub struct DocumentSnapshot { resolved_client_capabilities: Arc, + client_settings: Arc, document_ref: index::DocumentQuery, position_encoding: PositionEncoding, } @@ -312,7 +317,7 @@ impl DocumentSnapshot { &self.resolved_client_capabilities } - pub fn query(&self) -> &index::DocumentQuery { + pub(crate) fn query(&self) -> &index::DocumentQuery { &self.document_ref } @@ -320,6 +325,10 @@ impl DocumentSnapshot { self.position_encoding } + pub(crate) fn client_settings(&self) -> &ClientSettings { + &self.client_settings + } + pub(crate) fn file(&self, db: &dyn Db) -> Option { match AnySystemPath::try_from_url(self.document_ref.file_url()).ok()? { AnySystemPath::System(path) => system_path_to_file(db, path).ok(), diff --git a/crates/ty_server/src/session/index.rs b/crates/ty_server/src/session/index.rs index 3a15b1c9a3f85c..16c430ddfe42b5 100644 --- a/crates/ty_server/src/session/index.rs +++ b/crates/ty_server/src/session/index.rs @@ -3,16 +3,15 @@ use std::sync::Arc; use lsp_types::Url; use rustc_hash::FxHashMap; +use crate::session::settings::ClientSettings; use crate::{ PositionEncoding, TextDocument, document::{DocumentKey, DocumentVersion, NotebookDocument}, system::AnySystemPath, }; -use super::ClientSettings; - /// Stores and tracks all open documents in a session, along with their associated settings. -#[derive(Default, Debug)] +#[derive(Debug)] pub(crate) struct Index { /// Maps all document file paths to the associated document controller documents: FxHashMap, @@ -21,8 +20,7 @@ pub(crate) struct Index { notebook_cells: FxHashMap, /// Global settings provided by the client. - #[expect(dead_code)] - global_settings: ClientSettings, + global_settings: Arc, } impl Index { @@ -30,7 +28,7 @@ impl Index { Self { documents: FxHashMap::default(), notebook_cells: FxHashMap::default(), - global_settings, + global_settings: Arc::new(global_settings), } } @@ -177,6 +175,10 @@ impl Index { Ok(()) } + pub(crate) fn global_settings(&self) -> Arc { + self.global_settings.clone() + } + fn document_controller_for_key( &mut self, key: &DocumentKey, diff --git a/crates/ty_server/src/session/options.rs b/crates/ty_server/src/session/options.rs new file mode 100644 index 00000000000000..c9422f33f76ece --- /dev/null +++ b/crates/ty_server/src/session/options.rs @@ -0,0 +1,158 @@ +use std::path::PathBuf; + +use lsp_types::Url; +use rustc_hash::FxHashMap; +use serde::Deserialize; + +use crate::logging::LogLevel; +use crate::session::settings::ClientSettings; + +pub(crate) type WorkspaceOptionsMap = FxHashMap; + +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub(crate) struct GlobalOptions { + #[serde(flatten)] + client: ClientOptions, + + // These settings are only needed for tracing, and are only read from the global configuration. + // These will not be in the resolved settings. + #[serde(flatten)] + pub(crate) tracing: TracingOptions, +} + +impl GlobalOptions { + pub(crate) fn into_settings(self) -> ClientSettings { + ClientSettings { + disable_language_services: self + .client + .python + .and_then(|python| python.ty) + .and_then(|ty| ty.disable_language_services) + .unwrap_or_default(), + } + } +} + +/// This is a direct representation of the workspace settings schema, which inherits the schema of +/// [`ClientOptions`] and adds extra fields to describe the workspace it applies to. +#[derive(Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct WorkspaceOptions { + #[serde(flatten)] + options: ClientOptions, + workspace: Url, +} + +/// This is a direct representation of the settings schema sent by the client. +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub(crate) struct ClientOptions { + /// Settings under the `python.*` namespace in VS Code that are useful for the ty language + /// server. + python: Option, +} + +// TODO(dhruvmanila): We need to mirror the "python.*" namespace on the server side but ideally it +// would be useful to instead use `workspace/configuration` instead. This would be then used to get +// all settings and not just the ones in "python.*". + +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct Python { + ty: Option, +} + +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +struct Ty { + disable_language_services: Option, +} + +/// This is a direct representation of the settings schema sent by the client. +/// Settings needed to initialize tracing. These will only be read from the global configuration. +#[derive(Debug, Deserialize, Default)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(rename_all = "camelCase")] +pub(crate) struct TracingOptions { + pub(crate) log_level: Option, + + /// Path to the log file - tildes and environment variables are supported. + pub(crate) log_file: Option, +} + +/// This is the exact schema for initialization options sent in by the client during +/// initialization. +#[derive(Debug, Deserialize)] +#[cfg_attr(test, derive(PartialEq, Eq))] +#[serde(untagged)] +enum InitializationOptions { + #[serde(rename_all = "camelCase")] + HasWorkspaces { + #[serde(rename = "globalSettings")] + global: GlobalOptions, + #[serde(rename = "settings")] + workspace: Vec, + }, + GlobalOnly { + #[serde(default)] + settings: GlobalOptions, + }, +} + +impl Default for InitializationOptions { + fn default() -> Self { + Self::GlobalOnly { + settings: GlobalOptions::default(), + } + } +} + +/// Built from the initialization options provided by the client. +#[derive(Debug)] +pub(crate) struct AllOptions { + pub(crate) global: GlobalOptions, + /// If this is `None`, the client only passed in global settings. + pub(crate) workspace: Option, +} + +impl AllOptions { + /// Initializes the controller from the serialized initialization options. This fails if + /// `options` are not valid initialization options. + pub(crate) fn from_value(options: serde_json::Value) -> Self { + Self::from_init_options( + serde_json::from_value(options) + .map_err(|err| { + tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); + }) + .unwrap_or_default(), + ) + } + + fn from_init_options(options: InitializationOptions) -> Self { + let (global_options, workspace_options) = match options { + InitializationOptions::GlobalOnly { settings: options } => (options, None), + InitializationOptions::HasWorkspaces { + global: global_options, + workspace: workspace_options, + } => (global_options, Some(workspace_options)), + }; + + Self { + global: global_options, + workspace: workspace_options.map(|workspace_options| { + workspace_options + .into_iter() + .map(|workspace_options| { + (workspace_options.workspace, workspace_options.options) + }) + .collect() + }), + } + } +} diff --git a/crates/ty_server/src/session/settings.rs b/crates/ty_server/src/session/settings.rs index ad33e15cca3bb6..aba23a5c1480a9 100644 --- a/crates/ty_server/src/session/settings.rs +++ b/crates/ty_server/src/session/settings.rs @@ -1,110 +1,14 @@ -use std::path::PathBuf; - -use lsp_types::Url; -use rustc_hash::FxHashMap; -use serde::Deserialize; - -/// Maps a workspace URI to its associated client settings. Used during server initialization. -pub(crate) type WorkspaceSettingsMap = FxHashMap; - -/// This is a direct representation of the settings schema sent by the client. -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -pub struct ClientSettings { - // These settings are only needed for tracing, and are only read from the global configuration. - // These will not be in the resolved settings. - #[serde(flatten)] - pub(crate) tracing: TracingSettings, -} - -/// Settings needed to initialize tracing. These will only be -/// read from the global configuration. -#[derive(Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -pub(crate) struct TracingSettings { - pub(crate) log_level: Option, - /// Path to the log file - tildes and environment variables are supported. - pub(crate) log_file: Option, -} - -/// This is a direct representation of the workspace settings schema, -/// which inherits the schema of [`ClientSettings`] and adds extra fields -/// to describe the workspace it applies to. -#[derive(Debug, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(rename_all = "camelCase")] -struct WorkspaceSettings { - #[serde(flatten)] - settings: ClientSettings, - workspace: Url, -} - -/// This is the exact schema for initialization options sent in by the client -/// during initialization. -#[derive(Debug, Deserialize)] +/// Resolved client settings for a specific document. These settings are meant to be +/// used directly by the server, and are *not* a 1:1 representation with how the client +/// sends them. +#[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq, Eq))] -#[serde(untagged)] -enum InitializationOptions { - #[serde(rename_all = "camelCase")] - HasWorkspaces { - global_settings: ClientSettings, - #[serde(rename = "settings")] - workspace_settings: Vec, - }, - GlobalOnly { - #[serde(default)] - settings: ClientSettings, - }, -} - -/// Built from the initialization options provided by the client. -#[derive(Debug)] -pub(crate) struct AllSettings { - pub(crate) global_settings: ClientSettings, - /// If this is `None`, the client only passed in global settings. - pub(crate) workspace_settings: Option, -} - -impl AllSettings { - /// Initializes the controller from the serialized initialization options. - /// This fails if `options` are not valid initialization options. - pub(crate) fn from_value(options: serde_json::Value) -> Self { - Self::from_init_options( - serde_json::from_value(options) - .map_err(|err| { - tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default client settings..."); - }) - .unwrap_or_default(), - ) - } - - fn from_init_options(options: InitializationOptions) -> Self { - let (global_settings, workspace_settings) = match options { - InitializationOptions::GlobalOnly { settings } => (settings, None), - InitializationOptions::HasWorkspaces { - global_settings, - workspace_settings, - } => (global_settings, Some(workspace_settings)), - }; - - Self { - global_settings, - workspace_settings: workspace_settings.map(|workspace_settings| { - workspace_settings - .into_iter() - .map(|settings| (settings.workspace, settings.settings)) - .collect() - }), - } - } +pub(crate) struct ClientSettings { + pub(super) disable_language_services: bool, } -impl Default for InitializationOptions { - fn default() -> Self { - Self::GlobalOnly { - settings: ClientSettings::default(), - } +impl ClientSettings { + pub(crate) fn is_language_services_disabled(&self) -> bool { + self.disable_language_services } } From 342b2665db8a540d6410a6c13a94cc78742a1152 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama <45118249+mtshiba@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:07:46 +0900 Subject: [PATCH 447/487] [ty] basic narrowing on attribute and subscript expressions (#17643) ## Summary This PR closes astral-sh/ty#164. This PR introduces a basic type narrowing mechanism for attribute/subscript expressions. Member accesses, int literal subscripts, string literal subscripts are supported (same as mypy and pyright). ## Test Plan New test cases are added to `mdtest/narrow/complex_target.md`. --------- Co-authored-by: David Peter --- .../resources/mdtest/attributes.md | 3 +- .../resources/mdtest/narrow/assignment.md | 6 + .../resources/mdtest/narrow/complex_target.md | 224 ++++++++++++ .../mdtest/narrow/conditionals/nested.md | 38 +- .../resources/mdtest/narrow/type_guards.md | 6 +- .../resources/primer/bad.txt | 1 + .../resources/primer/good.txt | 1 - crates/ty_python_semantic/src/place.rs | 5 +- .../ty_python_semantic/src/semantic_index.rs | 4 +- .../src/semantic_index/builder.rs | 23 +- .../semantic_index/narrowing_constraints.rs | 2 + .../src/semantic_index/place.rs | 279 ++++++++------- .../src/semantic_index/use_def.rs | 45 ++- crates/ty_python_semantic/src/types/infer.rs | 331 ++++++++++++------ crates/ty_python_semantic/src/types/narrow.rs | 96 ++--- 15 files changed, 738 insertions(+), 326 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 71288e51093439..53643da8ecdb09 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -751,7 +751,8 @@ reveal_type(C.pure_class_variable) # revealed: Unknown # and the assignment is properly attributed to the class method. # error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object ``" C.pure_class_variable = "overwritten on class" - +# TODO: should be no error +# error: [unresolved-attribute] "Attribute `pure_class_variable` can only be accessed on instances, not on the class object `` itself." reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"] c_instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md index 73d676a2a371b6..d5a59ad275ceff 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md @@ -87,6 +87,12 @@ class _: reveal_type(a.y) # revealed: Unknown | None reveal_type(a.z) # revealed: Unknown | None +a = A() +# error: [unresolved-attribute] +a.dynamically_added = 0 +# error: [unresolved-attribute] +reveal_type(a.dynamically_added) # revealed: Literal[0] + # error: [unresolved-reference] does.nt.exist = 0 # error: [unresolved-reference] diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md b/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md new file mode 100644 index 00000000000000..c74f24a0aa3b41 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md @@ -0,0 +1,224 @@ +# Narrowing for complex targets (attribute expressions, subscripts) + +We support type narrowing for attributes and subscripts. + +## Attribute narrowing + +### Basic + +```py +from ty_extensions import Unknown + +class C: + x: int | None = None + +c = C() + +reveal_type(c.x) # revealed: int | None + +if c.x is not None: + reveal_type(c.x) # revealed: int +else: + reveal_type(c.x) # revealed: None + +if c.x is not None: + c.x = None + +reveal_type(c.x) # revealed: None + +c = C() + +if c.x is None: + c.x = 1 + +reveal_type(c.x) # revealed: int + +class _: + reveal_type(c.x) # revealed: int + +c = C() + +class _: + if c.x is None: + c.x = 1 + reveal_type(c.x) # revealed: int + +# TODO: should be `int` +reveal_type(c.x) # revealed: int | None + +class D: + x = None + +def unknown() -> Unknown: + return 1 + +d = D() +reveal_type(d.x) # revealed: Unknown | None +d.x = 1 +reveal_type(d.x) # revealed: Literal[1] +d.x = unknown() +reveal_type(d.x) # revealed: Unknown +``` + +Narrowing can be "reset" by assigning to the attribute: + +```py +c = C() + +if c.x is None: + reveal_type(c.x) # revealed: None + c.x = 1 + reveal_type(c.x) # revealed: Literal[1] + c.x = None + reveal_type(c.x) # revealed: None + +reveal_type(c.x) # revealed: int | None +``` + +Narrowing can also be "reset" by assigning to the object: + +```py +c = C() + +if c.x is None: + reveal_type(c.x) # revealed: None + c = C() + reveal_type(c.x) # revealed: int | None + +reveal_type(c.x) # revealed: int | None +``` + +### Multiple predicates + +```py +class C: + value: str | None + +def foo(c: C): + if c.value and len(c.value): + reveal_type(c.value) # revealed: str & ~AlwaysFalsy + + # error: [invalid-argument-type] "Argument to function `len` is incorrect: Expected `Sized`, found `str | None`" + if len(c.value) and c.value: + reveal_type(c.value) # revealed: str & ~AlwaysFalsy + + if c.value is None or not len(c.value): + reveal_type(c.value) # revealed: str | None + else: # c.value is not None and len(c.value) + # TODO: should be # `str & ~AlwaysFalsy` + reveal_type(c.value) # revealed: str +``` + +### Generic class + +```toml +[environment] +python-version = "3.12" +``` + +```py +class C[T]: + x: T + y: T + + def __init__(self, x: T): + self.x = x + self.y = x + +def f(a: int | None): + c = C(a) + reveal_type(c.x) # revealed: int | None + reveal_type(c.y) # revealed: int | None + if c.x is not None: + reveal_type(c.x) # revealed: int + # In this case, it may seem like we can narrow it down to `int`, + # but different values ​​may be reassigned to `x` and `y` in another place. + reveal_type(c.y) # revealed: int | None + +def g[T](c: C[T]): + reveal_type(c.x) # revealed: T + reveal_type(c.y) # revealed: T + reveal_type(c) # revealed: C[T] + + if isinstance(c.x, int): + reveal_type(c.x) # revealed: T & int + reveal_type(c.y) # revealed: T + reveal_type(c) # revealed: C[T] + if isinstance(c.x, int) and isinstance(c.y, int): + reveal_type(c.x) # revealed: T & int + reveal_type(c.y) # revealed: T & int + # TODO: Probably better if inferred as `C[T & int]` (mypy and pyright don't support this) + reveal_type(c) # revealed: C[T] +``` + +### With intermediate scopes + +```py +class C: + def __init__(self): + self.x: int | None = None + self.y: int | None = None + +c = C() +reveal_type(c.x) # revealed: int | None +if c.x is not None: + reveal_type(c.x) # revealed: int + reveal_type(c.y) # revealed: int | None + +if c.x is not None: + def _(): + reveal_type(c.x) # revealed: Unknown | int | None + +def _(): + if c.x is not None: + reveal_type(c.x) # revealed: (Unknown & ~None) | int +``` + +## Subscript narrowing + +### Number subscript + +```py +def _(t1: tuple[int | None, int | None], t2: tuple[int, int] | tuple[None, None]): + if t1[0] is not None: + reveal_type(t1[0]) # revealed: int + reveal_type(t1[1]) # revealed: int | None + + n = 0 + if t1[n] is not None: + # Non-literal subscript narrowing are currently not supported, as well as mypy, pyright + reveal_type(t1[0]) # revealed: int | None + reveal_type(t1[n]) # revealed: int | None + reveal_type(t1[1]) # revealed: int | None + + if t2[0] is not None: + # TODO: should be int + reveal_type(t2[0]) # revealed: Unknown & ~None + # TODO: should be int + reveal_type(t2[1]) # revealed: Unknown +``` + +### String subscript + +```py +def _(d: dict[str, str | None]): + if d["a"] is not None: + reveal_type(d["a"]) # revealed: str + reveal_type(d["b"]) # revealed: str | None +``` + +## Combined attribute and subscript narrowing + +```py +class C: + def __init__(self): + self.x: tuple[int | None, int | None] = (None, None) + +class D: + def __init__(self): + self.c: tuple[C] | None = None + +d = D() +if d.c is not None and d.c[0].x[0] is not None: + reveal_type(d.c[0].x[0]) # revealed: int +``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md index b3b077f1bcb718..033ca89d3e4642 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md @@ -135,7 +135,7 @@ class _: class _3: reveal_type(a) # revealed: A # TODO: should be `D | None` - reveal_type(a.b.c1.d) # revealed: D + reveal_type(a.b.c1.d) # revealed: Unknown a.b.c1 = C() a.b.c1.d = D() @@ -173,12 +173,10 @@ def f(x: str | None): reveal_type(g) # revealed: str if a.x is not None: - # TODO(#17643): should be `Unknown | str` - reveal_type(a.x) # revealed: Unknown | str | None + reveal_type(a.x) # revealed: (Unknown & ~None) | str if l[0] is not None: - # TODO(#17643): should be `str` - reveal_type(l[0]) # revealed: str | None + reveal_type(l[0]) # revealed: str class C: if x is not None: @@ -191,12 +189,10 @@ def f(x: str | None): reveal_type(g) # revealed: str if a.x is not None: - # TODO(#17643): should be `Unknown | str` - reveal_type(a.x) # revealed: Unknown | str | None + reveal_type(a.x) # revealed: (Unknown & ~None) | str if l[0] is not None: - # TODO(#17643): should be `str` - reveal_type(l[0]) # revealed: str | None + reveal_type(l[0]) # revealed: str # TODO: should be str # This could be fixed if we supported narrowing with if clauses in comprehensions. @@ -241,22 +237,18 @@ def f(x: str | None): reveal_type(a.x) # revealed: Unknown | str | None class D: - # TODO(#17643): should be `Unknown | str` - reveal_type(a.x) # revealed: Unknown | str | None + reveal_type(a.x) # revealed: (Unknown & ~None) | str - # TODO(#17643): should be `Unknown | str` - [reveal_type(a.x) for _ in range(1)] # revealed: Unknown | str | None + [reveal_type(a.x) for _ in range(1)] # revealed: (Unknown & ~None) | str if l[0] is not None: def _(): reveal_type(l[0]) # revealed: str | None class D: - # TODO(#17643): should be `str` - reveal_type(l[0]) # revealed: str | None + reveal_type(l[0]) # revealed: str - # TODO(#17643): should be `str` - [reveal_type(l[0]) for _ in range(1)] # revealed: str | None + [reveal_type(l[0]) for _ in range(1)] # revealed: str ``` ### Narrowing constraints introduced in multiple scopes @@ -299,24 +291,20 @@ def f(x: str | Literal[1] | None): if a.x is not None: def _(): if a.x != 1: - # TODO(#17643): should be `Unknown | str | None` - reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None + reveal_type(a.x) # revealed: (Unknown & ~Literal[1]) | str | None class D: if a.x != 1: - # TODO(#17643): should be `Unknown | str` - reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None + reveal_type(a.x) # revealed: (Unknown & ~Literal[1] & ~None) | str if l[0] is not None: def _(): if l[0] != 1: - # TODO(#17643): should be `str | None` - reveal_type(l[0]) # revealed: str | Literal[1] | None + reveal_type(l[0]) # revealed: str | None class D: if l[0] != 1: - # TODO(#17643): should be `str` - reveal_type(l[0]) # revealed: str | Literal[1] | None + reveal_type(l[0]) # revealed: str ``` ### Narrowing constraints with bindings in class scope, and nested scopes diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md index c65a3b22c695ba..da19b569488fcf 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md @@ -220,8 +220,7 @@ def _(a: tuple[str, int] | tuple[int, str], c: C[Any]): if reveal_type(is_int(a[0])): # revealed: TypeIs[int @ a[0]] # TODO: Should be `tuple[int, str]` reveal_type(a) # revealed: tuple[str, int] | tuple[int, str] - # TODO: Should be `int` - reveal_type(a[0]) # revealed: Unknown + reveal_type(a[0]) # revealed: Unknown & int # TODO: Should be `TypeGuard[str @ c.v]` if reveal_type(guard_str(c.v)): # revealed: @Todo(`TypeGuard[]` special form) @@ -231,8 +230,7 @@ def _(a: tuple[str, int] | tuple[int, str], c: C[Any]): if reveal_type(is_int(c.v)): # revealed: TypeIs[int @ c.v] reveal_type(c) # revealed: C[Any] - # TODO: Should be `int` - reveal_type(c.v) # revealed: Any + reveal_type(c.v) # revealed: Any & int ``` Indirect usage is supported within the same scope: diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index b3d6aa33b1ee79..f92289d6ec91ff 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -17,4 +17,5 @@ setuptools # vendors packaging, see above spack # slow, success, but mypy-primer hangs processing the output spark # too many iterations steam.py # hangs (single threaded) +tornado # bad use-def map (https://github.com/astral-sh/ty/issues/365) xarray # too many iterations diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index 8d5dc6443874c3..be085564974b12 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -110,7 +110,6 @@ stone strawberry streamlit sympy -tornado trio twine typeshed-stats diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 9129edee8c5e10..33d89ccd77d95e 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -661,6 +661,7 @@ fn place_by_id<'db>( // See mdtest/known_constants.md#user-defined-type_checking for details. let is_considered_non_modifiable = place_table(db, scope) .place_expr(place_id) + .expr .is_name_and(|name| matches!(name, "__slots__" | "TYPE_CHECKING")); if scope.file(db).is_stub(db.upcast()) { @@ -1124,8 +1125,8 @@ mod implicit_globals { module_type_symbol_table .places() - .filter(|symbol| symbol.is_declared() && symbol.is_name()) - .map(semantic_index::place::PlaceExpr::expect_name) + .filter(|place| place.is_declared() && place.is_name()) + .map(semantic_index::place::PlaceExprWithFlags::expect_name) .filter(|symbol_name| { !matches!(&***symbol_name, "__dict__" | "__getattr__" | "__init__") }) diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index c39a5b12df8e49..b584d61b1168a2 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -37,8 +37,8 @@ mod reachability_constraints; mod use_def; pub(crate) use self::use_def::{ - BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, - DeclarationsIterator, + ApplicableConstraints, BindingWithConstraints, BindingWithConstraintsIterator, + DeclarationWithConstraint, DeclarationsIterator, }; type PlaceSet = hashbrown::HashMap; diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index 3e608a5c6e990a..1fc3e3dd1ff7f8 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -33,7 +33,7 @@ use crate::semantic_index::definition::{ use crate::semantic_index::expression::{Expression, ExpressionKind}; use crate::semantic_index::place::{ FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, - PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId, + PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId, }; use crate::semantic_index::predicate::{ PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, ScopedPredicateId, @@ -295,6 +295,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { // If the scope that we just popped off is an eager scope, we need to "lock" our view of // which bindings reach each of the uses in the scope. Loop through each enclosing scope, // looking for any that bind each place. + // TODO: Bindings in eager nested scopes also need to be recorded. For example: + // ```python + // class C: + // x: int | None = None + // c = C() + // class _: + // c.x = 1 + // reveal_type(c.x) # revealed: Literal[1] + // ``` for enclosing_scope_info in self.scope_stack.iter().rev() { let enclosing_scope_id = enclosing_scope_info.file_scope_id; let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind(); @@ -306,7 +315,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { // it may refer to the enclosing scope bindings // so we also need to snapshot the bindings of the enclosing scope. - let Some(enclosing_place_id) = enclosing_place_table.place_id_by_expr(nested_place) + let Some(enclosing_place_id) = + enclosing_place_table.place_id_by_expr(&nested_place.expr) else { continue; }; @@ -388,7 +398,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { /// Add a place to the place table and the use-def map. /// Return the [`ScopedPlaceId`] that uniquely identifies the place in both. - fn add_place(&mut self, place_expr: PlaceExpr) -> ScopedPlaceId { + fn add_place(&mut self, place_expr: PlaceExprWithFlags) -> ScopedPlaceId { let (place_id, added) = self.current_place_table().add_place(place_expr); if added { self.current_use_def_map_mut().add_place(place_id); @@ -1863,7 +1873,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { walk_stmt(self, stmt); for target in targets { if let Ok(target) = PlaceExpr::try_from(target) { - let place_id = self.add_place(target); + let place_id = self.add_place(PlaceExprWithFlags::new(target)); self.current_place_table().mark_place_used(place_id); self.delete_binding(place_id); } @@ -1898,7 +1908,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::Expr::Name(ast::ExprName { ctx, .. }) | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { - if let Ok(mut place_expr) = PlaceExpr::try_from(expr) { + if let Ok(place_expr) = PlaceExpr::try_from(expr) { + let mut place_expr = PlaceExprWithFlags::new(place_expr); if self.is_method_of_class().is_some() && place_expr.is_instance_attribute_candidate() { @@ -1906,7 +1917,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // i.e. typically `self` or `cls`. let accessed_object_refers_to_first_parameter = self .current_first_parameter_name - .is_some_and(|fst| place_expr.root_name() == fst); + .is_some_and(|fst| place_expr.expr.root_name() == fst); if accessed_object_refers_to_first_parameter && place_expr.is_member() { place_expr.mark_instance_attribute(); diff --git a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs index fa6280ead67cee..48297e1da6c4c3 100644 --- a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs @@ -30,6 +30,7 @@ use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage}; use crate::semantic_index::ast_ids::ScopedUseId; +use crate::semantic_index::place::FileScopeId; use crate::semantic_index::predicate::ScopedPredicateId; /// A narrowing constraint associated with a live binding. @@ -42,6 +43,7 @@ pub(crate) type ScopedNarrowingConstraint = List, - flags: PlaceFlags, } impl std::fmt::Display for PlaceExpr { @@ -151,23 +148,27 @@ impl TryFrom<&ast::Expr> for PlaceExpr { } } +impl TryFrom> for PlaceExpr { + type Error = (); + + fn try_from(expr: ast::ExprRef) -> Result { + match expr { + ast::ExprRef::Name(name) => Ok(PlaceExpr::name(name.id.clone())), + ast::ExprRef::Attribute(attr) => PlaceExpr::try_from(attr), + ast::ExprRef::Subscript(subscript) => PlaceExpr::try_from(subscript), + _ => Err(()), + } + } +} + impl PlaceExpr { - pub(super) fn name(name: Name) -> Self { + pub(crate) fn name(name: Name) -> Self { Self { root_name: name, sub_segments: smallvec![], - flags: PlaceFlags::empty(), } } - fn insert_flags(&mut self, flags: PlaceFlags) { - self.flags.insert(flags); - } - - pub(super) fn mark_instance_attribute(&mut self) { - self.flags.insert(PlaceFlags::IS_INSTANCE_ATTRIBUTE); - } - pub(crate) fn root_name(&self) -> &Name { &self.root_name } @@ -191,6 +192,66 @@ impl PlaceExpr { &self.root_name } + /// Is the place just a name? + pub fn is_name(&self) -> bool { + self.sub_segments.is_empty() + } + + pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool { + self.is_name() && f(&self.root_name) + } + + /// Does the place expression have the form `.member`? + pub fn is_member(&self) -> bool { + self.sub_segments + .last() + .is_some_and(|last| last.as_member().is_some()) + } + + fn root_exprs(&self) -> RootExprs<'_> { + RootExprs { + expr_ref: self.into(), + len: self.sub_segments.len(), + } + } +} + +/// A [`PlaceExpr`] with flags, e.g. whether it is used, bound, an instance attribute, etc. +#[derive(Eq, PartialEq, Debug)] +pub struct PlaceExprWithFlags { + pub(crate) expr: PlaceExpr, + flags: PlaceFlags, +} + +impl std::fmt::Display for PlaceExprWithFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.expr.fmt(f) + } +} + +impl PlaceExprWithFlags { + pub(crate) fn new(expr: PlaceExpr) -> Self { + PlaceExprWithFlags { + expr, + flags: PlaceFlags::empty(), + } + } + + fn name(name: Name) -> Self { + PlaceExprWithFlags { + expr: PlaceExpr::name(name), + flags: PlaceFlags::empty(), + } + } + + fn insert_flags(&mut self, flags: PlaceFlags) { + self.flags.insert(flags); + } + + pub(super) fn mark_instance_attribute(&mut self) { + self.flags.insert(PlaceFlags::IS_INSTANCE_ATTRIBUTE); + } + /// If the place expression has the form `.` /// (meaning it *may* be an instance attribute), /// return `Some()`. Else, return `None`. @@ -202,8 +263,8 @@ impl PlaceExpr { /// parameter of the method (i.e. `self`). To answer those questions, /// use [`Self::as_instance_attribute`]. pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> { - if self.sub_segments.len() == 1 { - self.sub_segments[0].as_member() + if self.expr.sub_segments.len() == 1 { + self.expr.sub_segments[0].as_member() } else { None } @@ -227,6 +288,16 @@ impl PlaceExpr { self.as_instance_attribute().map(Name::as_str) == Some(name) } + /// Return `Some()` if the place expression is an instance attribute. + pub(crate) fn as_instance_attribute(&self) -> Option<&Name> { + if self.is_instance_attribute() { + debug_assert!(self.as_instance_attribute_candidate().is_some()); + self.as_instance_attribute_candidate() + } else { + None + } + } + /// Is the place an instance attribute? pub(crate) fn is_instance_attribute(&self) -> bool { let is_instance_attribute = self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE); @@ -236,14 +307,12 @@ impl PlaceExpr { is_instance_attribute } - /// Return `Some()` if the place expression is an instance attribute. - pub(crate) fn as_instance_attribute(&self) -> Option<&Name> { - if self.is_instance_attribute() { - debug_assert!(self.as_instance_attribute_candidate().is_some()); - self.as_instance_attribute_candidate() - } else { - None - } + pub(crate) fn is_name(&self) -> bool { + self.expr.is_name() + } + + pub(crate) fn is_member(&self) -> bool { + self.expr.is_member() } /// Is the place used in its containing scope? @@ -261,56 +330,58 @@ impl PlaceExpr { self.flags.contains(PlaceFlags::IS_DECLARED) } - /// Is the place just a name? - pub fn is_name(&self) -> bool { - self.sub_segments.is_empty() + pub(crate) fn as_name(&self) -> Option<&Name> { + self.expr.as_name() } - pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool { - self.is_name() && f(&self.root_name) + pub(crate) fn expect_name(&self) -> &Name { + self.expr.expect_name() } +} - /// Does the place expression have the form `.member`? - pub fn is_member(&self) -> bool { - self.sub_segments - .last() - .is_some_and(|last| last.as_member().is_some()) +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub struct PlaceExprRef<'a> { + pub(crate) root_name: &'a Name, + pub(crate) sub_segments: &'a [PlaceExprSubSegment], +} + +impl PartialEq for PlaceExprRef<'_> { + fn eq(&self, other: &PlaceExpr) -> bool { + self.root_name == &other.root_name && self.sub_segments == &other.sub_segments[..] } +} - pub(crate) fn segments(&self) -> PlaceSegments { - PlaceSegments { - root_name: Some(&self.root_name), - sub_segments: &self.sub_segments, - } +impl PartialEq> for PlaceExpr { + fn eq(&self, other: &PlaceExprRef<'_>) -> bool { + &self.root_name == other.root_name && &self.sub_segments[..] == other.sub_segments } +} - // TODO: Ideally this would iterate PlaceSegments instead of RootExprs, both to reduce - // allocation and to avoid having both flagged and non-flagged versions of PlaceExprs. - fn root_exprs(&self) -> RootExprs<'_> { - RootExprs { - expr: self, - len: self.sub_segments.len(), +impl<'e> From<&'e PlaceExpr> for PlaceExprRef<'e> { + fn from(expr: &'e PlaceExpr) -> Self { + PlaceExprRef { + root_name: &expr.root_name, + sub_segments: &expr.sub_segments, } } } struct RootExprs<'e> { - expr: &'e PlaceExpr, + expr_ref: PlaceExprRef<'e>, len: usize, } -impl Iterator for RootExprs<'_> { - type Item = PlaceExpr; +impl<'e> Iterator for RootExprs<'e> { + type Item = PlaceExprRef<'e>; fn next(&mut self) -> Option { if self.len == 0 { return None; } self.len -= 1; - Some(PlaceExpr { - root_name: self.expr.root_name.clone(), - sub_segments: self.expr.sub_segments[..self.len].iter().cloned().collect(), - flags: PlaceFlags::empty(), + Some(PlaceExprRef { + root_name: self.expr_ref.root_name, + sub_segments: &self.expr_ref.sub_segments[..self.len], }) } } @@ -333,41 +404,6 @@ bitflags! { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PlaceSegment<'a> { - /// A first segment of a place expression (root name), e.g. `x` in `x.y.z[0]`. - Name(&'a ast::name::Name), - Member(&'a ast::name::Name), - IntSubscript(&'a ast::Int), - StringSubscript(&'a str), -} - -#[derive(Debug, PartialEq, Eq)] -pub struct PlaceSegments<'a> { - root_name: Option<&'a ast::name::Name>, - sub_segments: &'a [PlaceExprSubSegment], -} - -impl<'a> Iterator for PlaceSegments<'a> { - type Item = PlaceSegment<'a>; - - fn next(&mut self) -> Option { - if let Some(name) = self.root_name.take() { - return Some(PlaceSegment::Name(name)); - } - if self.sub_segments.is_empty() { - return None; - } - let segment = &self.sub_segments[0]; - self.sub_segments = &self.sub_segments[1..]; - Some(match segment { - PlaceExprSubSegment::Member(name) => PlaceSegment::Member(name), - PlaceExprSubSegment::IntSubscript(int) => PlaceSegment::IntSubscript(int), - PlaceExprSubSegment::StringSubscript(string) => PlaceSegment::StringSubscript(string), - }) - } -} - /// ID that uniquely identifies a place in a file. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FilePlaceId { @@ -575,7 +611,7 @@ impl ScopeKind { #[derive(Default, salsa::Update)] pub struct PlaceTable { /// The place expressions in this scope. - places: IndexVec, + places: IndexVec, /// The set of places. place_set: PlaceSet, @@ -586,7 +622,7 @@ impl PlaceTable { self.places.shrink_to_fit(); } - pub(crate) fn place_expr(&self, place_id: impl Into) -> &PlaceExpr { + pub(crate) fn place_expr(&self, place_id: impl Into) -> &PlaceExprWithFlags { &self.places[place_id.into()] } @@ -594,10 +630,10 @@ impl PlaceTable { pub(crate) fn root_place_exprs( &self, place_expr: &PlaceExpr, - ) -> impl Iterator { + ) -> impl Iterator { place_expr .root_exprs() - .filter_map(|place_expr| self.place_by_expr(&place_expr)) + .filter_map(|place_expr| self.place_by_expr(place_expr)) } #[expect(unused)] @@ -605,11 +641,11 @@ impl PlaceTable { self.places.indices() } - pub fn places(&self) -> impl Iterator { + pub fn places(&self) -> impl Iterator { self.places.iter() } - pub fn symbols(&self) -> impl Iterator { + pub fn symbols(&self) -> impl Iterator { self.places().filter(|place_expr| place_expr.is_name()) } @@ -620,19 +656,16 @@ impl PlaceTable { /// Returns the place named `name`. #[allow(unused)] // used in tests - pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExpr> { + pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExprWithFlags> { let id = self.place_id_by_name(name)?; Some(self.place_expr(id)) } - /// Returns the flagged place by the unflagged place expression. - /// - /// TODO: Ideally this would take a [`PlaceSegments`] instead of [`PlaceExpr`], to avoid the - /// awkward distinction between "flagged" (canonical) and unflagged [`PlaceExpr`]; in that - /// world, we would only create [`PlaceExpr`] in semantic indexing; in type inference we'd - /// create [`PlaceSegments`] if we need to look up a [`PlaceExpr`]. The [`PlaceTable`] would - /// need to gain the ability to hash and look up by a [`PlaceSegments`]. - pub(crate) fn place_by_expr(&self, place_expr: &PlaceExpr) -> Option<&PlaceExpr> { + /// Returns the flagged place. + pub(crate) fn place_by_expr<'e>( + &self, + place_expr: impl Into>, + ) -> Option<&PlaceExprWithFlags> { let id = self.place_id_by_expr(place_expr)?; Some(self.place_expr(id)) } @@ -650,12 +683,16 @@ impl PlaceTable { } /// Returns the [`ScopedPlaceId`] of the place expression. - pub(crate) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option { + pub(crate) fn place_id_by_expr<'e>( + &self, + place_expr: impl Into>, + ) -> Option { + let place_expr = place_expr.into(); let (id, ()) = self .place_set .raw_entry() .from_hash(Self::hash_place_expr(place_expr), |id| { - self.place_expr(*id).segments() == place_expr.segments() + self.place_expr(*id).expr == place_expr })?; Some(*id) @@ -673,10 +710,12 @@ impl PlaceTable { hasher.finish() } - fn hash_place_expr(place_expr: &PlaceExpr) -> u64 { + fn hash_place_expr<'e>(place_expr: impl Into>) -> u64 { + let place_expr = place_expr.into(); + let mut hasher = FxHasher::default(); - place_expr.root_name().as_str().hash(&mut hasher); - for segment in &place_expr.sub_segments { + place_expr.root_name.as_str().hash(&mut hasher); + for segment in place_expr.sub_segments { match segment { PlaceExprSubSegment::Member(name) => name.hash(&mut hasher), PlaceExprSubSegment::IntSubscript(int) => int.hash(&mut hasher), @@ -725,11 +764,11 @@ impl PlaceTableBuilder { match entry { RawEntryMut::Occupied(entry) => (*entry.key(), false), RawEntryMut::Vacant(entry) => { - let symbol = PlaceExpr::name(name); + let symbol = PlaceExprWithFlags::name(name); let id = self.table.places.push(symbol); entry.insert_with_hasher(hash, id, (), |id| { - PlaceTable::hash_place_expr(&self.table.places[*id]) + PlaceTable::hash_place_expr(&self.table.places[*id].expr) }); let new_id = self.associated_place_ids.push(vec![]); debug_assert_eq!(new_id, id); @@ -738,23 +777,25 @@ impl PlaceTableBuilder { } } - pub(super) fn add_place(&mut self, place_expr: PlaceExpr) -> (ScopedPlaceId, bool) { - let hash = PlaceTable::hash_place_expr(&place_expr); - let entry = self.table.place_set.raw_entry_mut().from_hash(hash, |id| { - self.table.places[*id].segments() == place_expr.segments() - }); + pub(super) fn add_place(&mut self, place_expr: PlaceExprWithFlags) -> (ScopedPlaceId, bool) { + let hash = PlaceTable::hash_place_expr(&place_expr.expr); + let entry = self + .table + .place_set + .raw_entry_mut() + .from_hash(hash, |id| self.table.places[*id].expr == place_expr.expr); match entry { RawEntryMut::Occupied(entry) => (*entry.key(), false), RawEntryMut::Vacant(entry) => { let id = self.table.places.push(place_expr); entry.insert_with_hasher(hash, id, (), |id| { - PlaceTable::hash_place_expr(&self.table.places[*id]) + PlaceTable::hash_place_expr(&self.table.places[*id].expr) }); let new_id = self.associated_place_ids.push(vec![]); debug_assert_eq!(new_id, id); - for root in self.table.places[id].root_exprs() { - if let Some(root_id) = self.table.place_id_by_expr(&root) { + for root in self.table.places[id].expr.root_exprs() { + if let Some(root_id) = self.table.place_id_by_expr(root) { self.associated_place_ids[root_id].push(id); } } @@ -775,7 +816,7 @@ impl PlaceTableBuilder { self.table.places[id].insert_flags(PlaceFlags::IS_USED); } - pub(super) fn places(&self) -> impl Iterator { + pub(super) fn places(&self) -> impl Iterator { self.table.places() } @@ -783,7 +824,7 @@ impl PlaceTableBuilder { self.table.place_id_by_expr(place_expr) } - pub(super) fn place_expr(&self, place_id: impl Into) -> &PlaceExpr { + pub(super) fn place_expr(&self, place_id: impl Into) -> &PlaceExprWithFlags { self.table.place_expr(place_id) } diff --git a/crates/ty_python_semantic/src/semantic_index/use_def.rs b/crates/ty_python_semantic/src/semantic_index/use_def.rs index cb57055ab877f9..8ac7f918119844 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def.rs @@ -237,19 +237,21 @@ use self::place_state::{ LiveDeclarationsIterator, PlaceState, ScopedDefinitionId, }; use crate::node_key::NodeKey; -use crate::semantic_index::EagerSnapshotResult; use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::narrowing_constraints::{ ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator, }; -use crate::semantic_index::place::{FileScopeId, PlaceExpr, ScopeKind, ScopedPlaceId}; +use crate::semantic_index::place::{ + FileScopeId, PlaceExpr, PlaceExprWithFlags, ScopeKind, ScopedPlaceId, +}; use crate::semantic_index::predicate::{ Predicate, Predicates, PredicatesBuilder, ScopedPredicateId, StarImportPlaceholderPredicate, }; use crate::semantic_index::reachability_constraints::{ ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, }; +use crate::semantic_index::{EagerSnapshotResult, SemanticIndex}; use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint}; mod place_state; @@ -320,6 +322,11 @@ pub(crate) struct UseDefMap<'db> { end_of_scope_reachability: ScopedReachabilityConstraintId, } +pub(crate) enum ApplicableConstraints<'map, 'db> { + UnboundBinding(ConstraintsIterator<'map, 'db>), + ConstrainedBindings(BindingWithConstraintsIterator<'map, 'db>), +} + impl<'db> UseDefMap<'db> { pub(crate) fn bindings_at_use( &self, @@ -328,19 +335,33 @@ impl<'db> UseDefMap<'db> { self.bindings_iterator(&self.bindings_by_use[use_id]) } - pub(crate) fn narrowing_constraints_at_use( + pub(crate) fn applicable_constraints( &self, constraint_key: ConstraintKey, - ) -> ConstraintsIterator<'_, 'db> { - let constraint = match constraint_key { - ConstraintKey::NarrowingConstraint(constraint) => constraint, + enclosing_scope: FileScopeId, + expr: &PlaceExpr, + index: &'db SemanticIndex, + ) -> ApplicableConstraints<'_, 'db> { + match constraint_key { + ConstraintKey::NarrowingConstraint(constraint) => { + ApplicableConstraints::UnboundBinding(ConstraintsIterator { + predicates: &self.predicates, + constraint_ids: self.narrowing_constraints.iter_predicates(constraint), + }) + } + ConstraintKey::EagerNestedScope(nested_scope) => { + let EagerSnapshotResult::FoundBindings(bindings) = + index.eager_snapshot(enclosing_scope, expr, nested_scope) + else { + unreachable!( + "The result of `SemanticIndex::eager_snapshot` must be `FoundBindings`" + ) + }; + ApplicableConstraints::ConstrainedBindings(bindings) + } ConstraintKey::UseId(use_id) => { - self.bindings_by_use[use_id].unbound_narrowing_constraint() + ApplicableConstraints::ConstrainedBindings(self.bindings_at_use(use_id)) } - }; - ConstraintsIterator { - predicates: &self.predicates, - constraint_ids: self.narrowing_constraints.iter_predicates(constraint), } } @@ -884,7 +905,7 @@ impl<'db> UseDefMapBuilder<'db> { &mut self, enclosing_place: ScopedPlaceId, scope: ScopeKind, - enclosing_place_expr: &PlaceExpr, + enclosing_place_expr: &PlaceExprWithFlags, ) -> ScopedEagerSnapshotId { // Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible), // so we never need to save eager scope bindings in a class scope. diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 8e1752e7fb6713..39f0227d4f7b0e 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -60,7 +60,7 @@ use crate::semantic_index::ast_ids::{ }; use crate::semantic_index::definition::{ AnnotatedAssignmentDefinitionKind, AssignmentDefinitionKind, ComprehensionDefinitionKind, - Definition, DefinitionKind, DefinitionNodeKey, ExceptHandlerDefinitionKind, + Definition, DefinitionKind, DefinitionNodeKey, DefinitionState, ExceptHandlerDefinitionKind, ForStmtDefinitionKind, TargetKind, WithItemDefinitionKind, }; use crate::semantic_index::expression::{Expression, ExpressionKind}; @@ -68,7 +68,9 @@ use crate::semantic_index::narrowing_constraints::ConstraintKey; use crate::semantic_index::place::{ FileScopeId, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, ScopeId, ScopeKind, ScopedPlaceId, }; -use crate::semantic_index::{EagerSnapshotResult, SemanticIndex, place_table, semantic_index}; +use crate::semantic_index::{ + ApplicableConstraints, EagerSnapshotResult, SemanticIndex, place_table, semantic_index, +}; use crate::types::call::{ Argument, Binding, Bindings, CallArgumentTypes, CallArguments, CallError, }; @@ -746,6 +748,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .expression_type(expr.scoped_expression_id(self.db(), self.scope())) } + fn try_expression_type(&self, expr: &ast::Expr) -> Option> { + self.types + .try_expression_type(expr.scoped_expression_id(self.db(), self.scope())) + } + /// Get the type of an expression from any scope in the same file. /// /// If the expression is in the current scope, and we are inferring the entire scope, just look @@ -1510,13 +1517,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let global_use_def_map = self.index.use_def_map(FileScopeId::global()); let place_id = binding.place(self.db()); - let expr = place_table.place_expr(place_id); + let place = place_table.place_expr(place_id); let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, place_id); let declarations = if skip_non_global_scopes { match self .index .place_table(FileScopeId::global()) - .place_id_by_expr(expr) + .place_id_by_expr(&place.expr) { Some(id) => global_use_def_map.public_declarations(id), // This case is a syntax error (load before global declaration) but ignore that here @@ -1527,18 +1534,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; let declared_ty = place_from_declarations(self.db(), declarations) - .and_then(|place| { - Ok(if matches!(place.place, Place::Type(_, Boundness::Bound)) { - place - } else if skip_non_global_scopes - || self.scope().file_scope_id(self.db()).is_global() - { - let module_type_declarations = - module_type_implicit_global_declaration(self.db(), expr)?; - place.or_fall_back_to(self.db(), || module_type_declarations) - } else { - place - }) + .and_then(|place_and_quals| { + Ok( + if matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) { + place_and_quals + } else if skip_non_global_scopes + || self.scope().file_scope_id(self.db()).is_global() + { + let module_type_declarations = + module_type_implicit_global_declaration(self.db(), &place.expr)?; + place_and_quals.or_fall_back_to(self.db(), || module_type_declarations) + } else { + place_and_quals + }, + ) }) .map( |PlaceAndQualifiers { @@ -1576,10 +1585,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) .unwrap_or_else(|(ty, conflicting)| { // TODO point out the conflicting declarations in the diagnostic? - let expr = place_table.place_expr(binding.place(db)); + let place = place_table.place_expr(binding.place(db)); if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) { builder.into_diagnostic(format_args!( - "Conflicting declared types for `{expr}`: {}", + "Conflicting declared types for `{place}`: {}", conflicting.display(db) )); } @@ -1590,6 +1599,54 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // allow declarations to override inference in case of invalid assignment bound_ty = declared_ty; } + // In the following cases, the bound type may not be the same as the RHS value type. + if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node { + let value_ty = self + .try_expression_type(value) + .unwrap_or_else(|| self.infer_maybe_standalone_expression(value)); + // If the member is a data descriptor, the RHS value may differ from the value actually assigned. + if value_ty + .class_member(db, attr.id.clone()) + .place + .ignore_possibly_unbound() + .is_some_and(|ty| ty.may_be_data_descriptor(db)) + { + bound_ty = declared_ty; + } + } else if let AnyNodeRef::ExprSubscript(ast::ExprSubscript { value, .. }) = node { + let value_ty = self + .try_expression_type(value) + .unwrap_or_else(|| self.infer_expression(value)); + // Arbitrary `__getitem__`/`__setitem__` methods on a class do not + // necessarily guarantee that the passed-in value for `__setitem__` is stored and + // can be retrieved unmodified via `__getitem__`. Therefore, we currently only + // perform assignment-based narrowing on a few built-in classes (`list`, `dict`, + // `bytesarray`, `TypedDict` and `collections` types) where we are confident that + // this kind of narrowing can be performed soundly. This is the same approach as + // pyright. TODO: Other standard library classes may also be considered safe. Also, + // subclasses of these safe classes that do not override `__getitem__/__setitem__` + // may be considered safe. + let safe_mutable_classes = [ + KnownClass::List.to_instance(db), + KnownClass::Dict.to_instance(db), + KnownClass::Bytearray.to_instance(db), + KnownClass::DefaultDict.to_instance(db), + SpecialFormType::ChainMap.instance_fallback(db), + SpecialFormType::Counter.instance_fallback(db), + SpecialFormType::Deque.instance_fallback(db), + SpecialFormType::OrderedDict.instance_fallback(db), + SpecialFormType::TypedDict.instance_fallback(db), + ]; + if safe_mutable_classes.iter().all(|safe_mutable_class| { + !value_ty.is_equivalent_to(db, *safe_mutable_class) + && value_ty + .generic_origin(db) + .zip(safe_mutable_class.generic_origin(db)) + .is_none_or(|(l, r)| l != r) + }) { + bound_ty = declared_ty; + } + } self.types.bindings.insert(binding, bound_ty); } @@ -1624,9 +1681,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Fallback to bindings declared on `types.ModuleType` if it's a global symbol let scope = self.scope().file_scope_id(self.db()); let place_table = self.index.place_table(scope); - let expr = place_table.place_expr(declaration.place(self.db())); - if scope.is_global() && expr.is_name() { - module_type_implicit_global_symbol(self.db(), expr.expect_name()) + let place = place_table.place_expr(declaration.place(self.db())); + if scope.is_global() && place.is_name() { + module_type_implicit_global_symbol(self.db(), place.expect_name()) } else { Place::Unbound.into() } @@ -1677,9 +1734,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let file_scope_id = self.scope().file_scope_id(self.db()); if file_scope_id.is_global() { let place_table = self.index.place_table(file_scope_id); - let expr = place_table.place_expr(definition.place(self.db())); + let place = place_table.place_expr(definition.place(self.db())); if let Some(module_type_implicit_declaration) = - module_type_implicit_global_declaration(self.db(), expr) + module_type_implicit_global_declaration(self.db(), &place.expr) .ok() .and_then(|place| place.place.ignore_possibly_unbound()) { @@ -1691,11 +1748,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.context.report_lint(&INVALID_DECLARATION, node) { let mut diagnostic = builder.into_diagnostic(format_args!( - "Cannot shadow implicit global attribute `{expr}` with declaration of type `{}`", + "Cannot shadow implicit global attribute `{place}` with declaration of type `{}`", declared_type.display(self.db()) )); diagnostic.info(format_args!("The global symbol `{}` must always have a type assignable to `{}`", - expr, + place, module_type_implicit_declaration.display(self.db()) )); } @@ -5920,7 +5977,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // Perform narrowing with applicable constraints between the current scope and the enclosing scope. - fn narrow_with_applicable_constraints( + fn narrow_place_with_applicable_constraints( &self, expr: &PlaceExpr, mut ty: Type<'db>, @@ -5929,11 +5986,69 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let db = self.db(); for (enclosing_scope_file_id, constraint_key) in constraint_keys { let use_def = self.index.use_def_map(*enclosing_scope_file_id); - let constraints = use_def.narrowing_constraints_at_use(*constraint_key); let place_table = self.index.place_table(*enclosing_scope_file_id); let place = place_table.place_id_by_expr(expr).unwrap(); - ty = constraints.narrow(db, ty, place); + match use_def.applicable_constraints( + *constraint_key, + *enclosing_scope_file_id, + expr, + self.index, + ) { + ApplicableConstraints::UnboundBinding(constraint) => { + ty = constraint.narrow(db, ty, place); + } + // Performs narrowing based on constrained bindings. + // This handling must be performed even if narrowing is attempted and failed using `infer_place_load`. + // The result of `infer_place_load` can be applied as is only when its boundness is `Bound`. + // For example, this handling is required in the following case: + // ```python + // class C: + // x: int | None = None + // c = C() + // # c.x: int | None = + // if c.x is None: + // c.x = 1 + // # else: c.x: int = + // # `c.x` is not definitely bound here + // reveal_type(c.x) # revealed: int + // ``` + ApplicableConstraints::ConstrainedBindings(bindings) => { + let reachability_constraints = bindings.reachability_constraints; + let predicates = bindings.predicates; + let mut union = UnionBuilder::new(db); + for binding in bindings { + let static_reachability = reachability_constraints.evaluate( + db, + predicates, + binding.reachability_constraint, + ); + if static_reachability.is_always_false() { + continue; + } + match binding.binding { + DefinitionState::Defined(definition) => { + let binding_ty = binding_type(db, definition); + union = union.add( + binding.narrowing_constraint.narrow(db, binding_ty, place), + ); + } + DefinitionState::Undefined | DefinitionState::Deleted => { + union = + union.add(binding.narrowing_constraint.narrow(db, ty, place)); + } + } + } + // If there are no visible bindings, the union becomes `Never`. + // Since an unbound binding is recorded even for an undefined place, + // this can only happen if the code is unreachable + // and therefore it is correct to set the result to `Never`. + let union = union.build(); + if union.is_assignable_to(db, ty) { + ty = union; + } + } + } } ty } @@ -5956,7 +6071,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // These are looked up as attributes on `types.ModuleType`. .or_fall_back_to(db, || { module_type_implicit_global_symbol(db, symbol_name).map_type(|ty| { - self.narrow_with_applicable_constraints(&expr, ty, &constraint_keys) + self.narrow_place_with_applicable_constraints(&expr, ty, &constraint_keys) }) }) // Not found in globals? Fallback to builtins @@ -6028,7 +6143,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - /// Infer the type of a place expression, assuming a load context. + /// Infer the type of a place expression from definitions, assuming a load context. + /// This method also returns the [`ConstraintKey`]s for each scope associated with `expr`, + /// which is used to narrow by condition rather than by assignment. fn infer_place_load( &self, expr: &PlaceExpr, @@ -6041,6 +6158,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut constraint_keys = vec![]; let (local_scope_place, use_id) = self.infer_local_place_load(expr, expr_ref); + if let Some(use_id) = use_id { + constraint_keys.push((file_scope_id, ConstraintKey::UseId(use_id))); + } let place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || { let has_bindings_in_this_scope = match place_table.place_by_expr(expr) { @@ -6081,7 +6201,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for root_expr in place_table.root_place_exprs(expr) { let mut expr_ref = expr_ref; - for _ in 0..(expr.sub_segments().len() - root_expr.sub_segments().len()) { + for _ in 0..(expr.sub_segments().len() - root_expr.expr.sub_segments().len()) { match expr_ref { ast::ExprRef::Attribute(attribute) => { expr_ref = ast::ExprRef::from(&attribute.value); @@ -6092,16 +6212,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { _ => unreachable!(), } } - let (parent_place, _use_id) = self.infer_local_place_load(root_expr, expr_ref); + let (parent_place, _use_id) = + self.infer_local_place_load(&root_expr.expr, expr_ref); if let Place::Type(_, _) = parent_place { return Place::Unbound.into(); } } - if let Some(use_id) = use_id { - constraint_keys.push((file_scope_id, ConstraintKey::UseId(use_id))); - } - // Walk up parent scopes looking for a possible enclosing scope that may have a // definition of this name visible to us (would be `LOAD_DEREF` at runtime.) // Note that we skip the scope containing the use that we are resolving, since we @@ -6144,15 +6261,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { { continue; } - return place_from_bindings(db, bindings) - .map_type(|ty| { - self.narrow_with_applicable_constraints( - expr, - ty, - &constraint_keys, - ) - }) - .into(); + let place = place_from_bindings(db, bindings).map_type(|ty| { + self.narrow_place_with_applicable_constraints( + expr, + ty, + &constraint_keys, + ) + }); + constraint_keys.push(( + enclosing_scope_file_id, + ConstraintKey::EagerNestedScope(file_scope_id), + )); + return place.into(); } // There are no visible bindings / constraint here. // Don't fall back to non-eager place resolution. @@ -6163,7 +6283,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { { if enclosing_root_place.is_bound() { if let Place::Type(_, _) = - place(db, enclosing_scope_id, enclosing_root_place).place + place(db, enclosing_scope_id, &enclosing_root_place.expr) + .place { return Place::Unbound.into(); } @@ -6190,7 +6311,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // isn't bound in that scope, we should get an unbound name, not continue // falling back to other scopes / globals / builtins. return place(db, enclosing_scope_id, expr).map_type(|ty| { - self.narrow_with_applicable_constraints(expr, ty, &constraint_keys) + self.narrow_place_with_applicable_constraints(expr, ty, &constraint_keys) }); } } @@ -6215,15 +6336,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { )); } EagerSnapshotResult::FoundBindings(bindings) => { - return place_from_bindings(db, bindings) - .map_type(|ty| { - self.narrow_with_applicable_constraints( - expr, - ty, - &constraint_keys, - ) - }) - .into(); + let place = place_from_bindings(db, bindings).map_type(|ty| { + self.narrow_place_with_applicable_constraints( + expr, + ty, + &constraint_keys, + ) + }); + constraint_keys.push(( + FileScopeId::global(), + ConstraintKey::EagerNestedScope(file_scope_id), + )); + return place.into(); } // There are no visible bindings / constraint here. EagerSnapshotResult::NotFound => { @@ -6238,7 +6362,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; explicit_global_symbol(db, self.file(), name).map_type(|ty| { - self.narrow_with_applicable_constraints(expr, ty, &constraint_keys) + self.narrow_place_with_applicable_constraints(expr, ty, &constraint_keys) }) }) }); @@ -6302,6 +6426,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } + fn narrow_expr_with_applicable_constraints<'r>( + &self, + target: impl Into>, + target_ty: Type<'db>, + constraint_keys: &[(FileScopeId, ConstraintKey)], + ) -> Type<'db> { + let target = target.into(); + + if let Ok(place_expr) = PlaceExpr::try_from(target) { + self.narrow_place_with_applicable_constraints(&place_expr, target_ty, constraint_keys) + } else { + target_ty + } + } + /// Infer the type of a [`ast::ExprAttribute`] expression, assuming a load context. fn infer_attribute_load(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> { let ast::ExprAttribute { @@ -6314,27 +6453,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let value_type = self.infer_maybe_standalone_expression(value); let db = self.db(); + let mut constraint_keys = vec![]; - // If `attribute` is a valid reference, we attempt type narrowing by assignment. + let mut assigned_type = None; if let Ok(place_expr) = PlaceExpr::try_from(attribute) { - let member = value_type.class_member(db, attr.id.clone()); - // If the member is a data descriptor, the value most recently assigned - // to the attribute may not necessarily be obtained here. - if member - .place - .ignore_possibly_unbound() - .is_none_or(|ty| !ty.may_be_data_descriptor(db)) - { - let (resolved, _) = - self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute)); - if let Place::Type(ty, Boundness::Bound) = resolved.place { - return ty; - } + let (resolved, keys) = + self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute)); + constraint_keys.extend(keys); + if let Place::Type(ty, Boundness::Bound) = resolved.place { + assigned_type = Some(ty); } } - value_type + let resolved_type = value_type .member(db, &attr.id) + .map_type(|ty| self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)) .unwrap_with_diagnostic(|lookup_error| match lookup_error { LookupError::Unbound(_) => { let report_unresolved_attribute = self.is_reachable(attribute); @@ -6394,7 +6527,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { type_when_bound } - }).inner_type() + }) + .inner_type(); + // Even if we can obtain the attribute type based on the assignments, we still perform default type inference + // (to report errors). + assigned_type.unwrap_or(resolved_type) } fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> { @@ -7839,46 +7976,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { slice, ctx: _, } = subscript; - let db = self.db(); let value_ty = self.infer_expression(value); + let mut constraint_keys = vec![]; // If `value` is a valid reference, we attempt type narrowing by assignment. if !value_ty.is_unknown() { if let Ok(expr) = PlaceExpr::try_from(subscript) { - // Type narrowing based on assignment to a subscript expression is generally - // unsound, because arbitrary `__getitem__`/`__setitem__` methods on a class do not - // necessarily guarantee that the passed-in value for `__setitem__` is stored and - // can be retrieved unmodified via `__getitem__`. Therefore, we currently only - // perform assignment-based narrowing on a few built-in classes (`list`, `dict`, - // `bytesarray`, `TypedDict` and `collections` types) where we are confident that - // this kind of narrowing can be performed soundly. This is the same approach as - // pyright. TODO: Other standard library classes may also be considered safe. Also, - // subclasses of these safe classes that do not override `__getitem__/__setitem__` - // may be considered safe. - let safe_mutable_classes = [ - KnownClass::List.to_instance(db), - KnownClass::Dict.to_instance(db), - KnownClass::Bytearray.to_instance(db), - KnownClass::DefaultDict.to_instance(db), - SpecialFormType::ChainMap.instance_fallback(db), - SpecialFormType::Counter.instance_fallback(db), - SpecialFormType::Deque.instance_fallback(db), - SpecialFormType::OrderedDict.instance_fallback(db), - SpecialFormType::TypedDict.instance_fallback(db), - ]; - if safe_mutable_classes.iter().any(|safe_mutable_class| { - value_ty.is_equivalent_to(db, *safe_mutable_class) - || value_ty - .generic_origin(db) - .zip(safe_mutable_class.generic_origin(db)) - .is_some_and(|(l, r)| l == r) - }) { - let (place, _) = - self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript)); - if let Place::Type(ty, Boundness::Bound) = place.place { - self.infer_expression(slice); - return ty; - } + let (place, keys) = + self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript)); + constraint_keys.extend(keys); + if let Place::Type(ty, Boundness::Bound) = place.place { + // Even if we can obtain the subscript type based on the assignments, we still perform default type inference + // (to store the expression type and to report errors). + let slice_ty = self.infer_expression(slice); + self.infer_subscript_expression_types(value, value_ty, slice_ty); + return ty; } } } @@ -7908,7 +8020,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let slice_ty = self.infer_expression(slice); - self.infer_subscript_expression_types(value, value_ty, slice_ty) + let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty); + self.narrow_expr_with_applicable_constraints(subscript, result_ty, &constraint_keys) } fn infer_explicit_class_specialization( diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 9fa59dfcac1915..2538a37335c3af 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -1,7 +1,7 @@ use crate::Db; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::expression::Expression; -use crate::semantic_index::place::{PlaceTable, ScopeId, ScopedPlaceId}; +use crate::semantic_index::place::{PlaceExpr, PlaceTable, ScopeId, ScopedPlaceId}; use crate::semantic_index::place_table; use crate::semantic_index::predicate::{ PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, @@ -247,13 +247,12 @@ fn negate_if<'db>(constraints: &mut NarrowingConstraints<'db>, db: &'db dyn Db, } } -fn expr_name(expr: &ast::Expr) -> Option<&ast::name::Name> { +fn place_expr(expr: &ast::Expr) -> Option { match expr { - ast::Expr::Named(ast::ExprNamed { target, .. }) => match target.as_ref() { - ast::Expr::Name(ast::ExprName { id, .. }) => Some(id), - _ => None, - }, - ast::Expr::Name(ast::ExprName { id, .. }) => Some(id), + ast::Expr::Name(name) => Some(PlaceExpr::name(name.id.clone())), + ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr).ok(), + ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript).ok(), + ast::Expr::Named(named) => PlaceExpr::try_from(named.target.as_ref()).ok(), _ => None, } } @@ -314,7 +313,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { is_positive: bool, ) -> Option> { match expression_node { - ast::Expr::Name(name) => Some(self.evaluate_expr_name(name, is_positive)), + ast::Expr::Name(_) | ast::Expr::Attribute(_) | ast::Expr::Subscript(_) => { + self.evaluate_simple_expr(expression_node, is_positive) + } ast::Expr::Compare(expr_compare) => { self.evaluate_expr_compare(expr_compare, expression, is_positive) } @@ -374,27 +375,27 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { } #[track_caller] - fn expect_expr_name_symbol(&self, symbol: &str) -> ScopedPlaceId { + fn expect_place(&self, place_expr: &PlaceExpr) -> ScopedPlaceId { self.places() - .place_id_by_name(symbol) - .expect("We should always have a symbol for every `Name` node") + .place_id_by_expr(place_expr) + .expect("We should always have a place for every `PlaceExpr`") } - fn evaluate_expr_name( + fn evaluate_simple_expr( &mut self, - expr_name: &ast::ExprName, + expr: &ast::Expr, is_positive: bool, - ) -> NarrowingConstraints<'db> { - let ast::ExprName { id, .. } = expr_name; + ) -> Option> { + let target = place_expr(expr)?; + let place = self.expect_place(&target); - let symbol = self.expect_expr_name_symbol(id); let ty = if is_positive { Type::AlwaysFalsy.negate(self.db) } else { Type::AlwaysTruthy.negate(self.db) }; - NarrowingConstraints::from_iter([(symbol, ty)]) + Some(NarrowingConstraints::from_iter([(place, ty)])) } fn evaluate_expr_named( @@ -402,11 +403,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { expr_named: &ast::ExprNamed, is_positive: bool, ) -> Option> { - if let ast::Expr::Name(expr_name) = expr_named.target.as_ref() { - Some(self.evaluate_expr_name(expr_name, is_positive)) - } else { - None - } + self.evaluate_simple_expr(&expr_named.target, is_positive) } fn evaluate_expr_eq(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option> { @@ -598,7 +595,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { fn is_narrowing_target_candidate(expr: &ast::Expr) -> bool { matches!( expr, - ast::Expr::Name(_) | ast::Expr::Call(_) | ast::Expr::Named(_) + ast::Expr::Name(_) + | ast::Expr::Attribute(_) + | ast::Expr::Subscript(_) + | ast::Expr::Call(_) + | ast::Expr::Named(_) ) } @@ -644,13 +645,16 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { last_rhs_ty = Some(rhs_ty); match left { - ast::Expr::Name(_) | ast::Expr::Named(_) => { - if let Some(id) = expr_name(left) { - let symbol = self.expect_expr_name_symbol(id); + ast::Expr::Name(_) + | ast::Expr::Attribute(_) + | ast::Expr::Subscript(_) + | ast::Expr::Named(_) => { + if let Some(left) = place_expr(left) { let op = if is_positive { *op } else { op.negate() }; if let Some(ty) = self.evaluate_expr_compare_op(lhs_ty, rhs_ty, op) { - constraints.insert(symbol, ty); + let place = self.expect_place(&left); + constraints.insert(place, ty); } } } @@ -674,9 +678,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { } }; - let id = match &**args { - [first] => match expr_name(first) { - Some(id) => id, + let target = match &**args { + [first] => match place_expr(first) { + Some(target) => target, None => continue, }, _ => continue, @@ -699,9 +703,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { .into_class_literal() .is_some_and(|c| c.is_known(self.db, KnownClass::Type)) { - let symbol = self.expect_expr_name_symbol(id); + let place = self.expect_place(&target); constraints.insert( - symbol, + place, Type::instance(self.db, rhs_class.unknown_specialization(self.db)), ); } @@ -754,9 +758,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { let [first_arg, second_arg] = &*expr_call.arguments.args else { return None; }; - let first_arg = expr_name(first_arg)?; + let first_arg = place_expr(first_arg)?; let function = function_type.known(self.db)?; - let symbol = self.expect_expr_name_symbol(first_arg); + let place = self.expect_place(&first_arg); if function == KnownFunction::HasAttr { let attr = inference @@ -774,7 +778,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { ); return Some(NarrowingConstraints::from_iter([( - symbol, + place, constraint.negate_if(self.db, !is_positive), )])); } @@ -788,7 +792,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { .generate_constraint(self.db, class_info_ty) .map(|constraint| { NarrowingConstraints::from_iter([( - symbol, + place, constraint.negate_if(self.db, !is_positive), )]) }) @@ -814,15 +818,15 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { subject: Expression<'db>, singleton: ast::Singleton, ) -> Option> { - let symbol = self - .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); + let subject = place_expr(subject.node_ref(self.db, self.module))?; + let place = self.expect_place(&subject); let ty = match singleton { ast::Singleton::None => Type::none(self.db), ast::Singleton::True => Type::BooleanLiteral(true), ast::Singleton::False => Type::BooleanLiteral(false), }; - Some(NarrowingConstraints::from_iter([(symbol, ty)])) + Some(NarrowingConstraints::from_iter([(place, ty)])) } fn evaluate_match_pattern_class( @@ -830,11 +834,12 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { subject: Expression<'db>, cls: Expression<'db>, ) -> Option> { - let symbol = self - .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); + let subject = place_expr(subject.node_ref(self.db, self.module))?; + let place = self.expect_place(&subject); + let ty = infer_same_file_expression_type(self.db, cls, self.module).to_instance(self.db)?; - Some(NarrowingConstraints::from_iter([(symbol, ty)])) + Some(NarrowingConstraints::from_iter([(place, ty)])) } fn evaluate_match_pattern_value( @@ -842,10 +847,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { subject: Expression<'db>, value: Expression<'db>, ) -> Option> { - let symbol = self - .expect_expr_name_symbol(&subject.node_ref(self.db, self.module).as_name_expr()?.id); + let subject = place_expr(subject.node_ref(self.db, self.module))?; + let place = self.expect_place(&subject); + let ty = infer_same_file_expression_type(self.db, value, self.module); - Some(NarrowingConstraints::from_iter([(symbol, ty)])) + Some(NarrowingConstraints::from_iter([(place, ty)])) } fn evaluate_match_pattern_or( From 1d458d431453feb57f17f581e4cc2a0d7cb7230c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 17 Jun 2025 10:40:50 +0100 Subject: [PATCH 448/487] [ty] Fix panics when pulling types for various special forms that have the wrong number of parameters (#18642) --- .../resources/mdtest/annotations/any.md | 2 - .../resources/mdtest/type_api.md | 26 ++- .../src/types/diagnostic.rs | 20 ++ crates/ty_python_semantic/src/types/infer.rs | 185 +++++++++++------- 4 files changed, 153 insertions(+), 80 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/any.md b/crates/ty_python_semantic/resources/mdtest/annotations/any.md index a35b18168e5c95..d4b1e6f502224e 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/any.md @@ -139,8 +139,6 @@ x: int = MagicMock() ## Invalid - - `Any` cannot be parameterized: ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_api.md b/crates/ty_python_semantic/resources/mdtest/type_api.md index 8074e1fa432da6..7608f59311d418 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_api.md +++ b/crates/ty_python_semantic/resources/mdtest/type_api.md @@ -14,8 +14,6 @@ directly. ### Negation - - ```py from typing import Literal from ty_extensions import Not, static_assert @@ -25,8 +23,12 @@ def negate(n1: Not[int], n2: Not[Not[int]], n3: Not[Not[Not[int]]]) -> None: reveal_type(n2) # revealed: int reveal_type(n3) # revealed: ~int -# error: "Special form `ty_extensions.Not` expected exactly one type parameter" +# error: "Special form `ty_extensions.Not` expected exactly 1 type argument, got 2" n: Not[int, str] +# error: [invalid-type-form] "Special form `ty_extensions.Not` expected exactly 1 type argument, got 0" +o: Not[()] + +p: Not[(int,)] def static_truthiness(not_one: Not[Literal[1]]) -> None: # TODO: `bool` is not incorrect, but these would ideally be `Literal[True]` and `Literal[False]` @@ -373,8 +375,6 @@ static_assert(not is_single_valued(Literal["a"] | Literal["b"])) ## `TypeOf` - - We use `TypeOf` to get the inferred type of an expression. This is useful when we want to refer to it in a type expression. For example, if we want to make sure that the class literal type `str` is a subtype of `type[str]`, we can not use `is_subtype_of(str, type[str])`, as that would test if the @@ -400,13 +400,13 @@ class Derived(Base): ... ```py def type_of_annotation() -> None: t1: TypeOf[Base] = Base - t2: TypeOf[Base] = Derived # error: [invalid-assignment] + t2: TypeOf[(Base,)] = Derived # error: [invalid-assignment] # Note how this is different from `type[…]` which includes subclasses: s1: type[Base] = Base s2: type[Base] = Derived # no error here -# error: "Special form `ty_extensions.TypeOf` expected exactly one type parameter" +# error: "Special form `ty_extensions.TypeOf` expected exactly 1 type argument, got 3" t: TypeOf[int, str, bytes] # error: [invalid-type-form] "`ty_extensions.TypeOf` requires exactly one argument when used in a type expression" @@ -416,8 +416,6 @@ def f(x: TypeOf) -> None: ## `CallableTypeOf` - - The `CallableTypeOf` special form can be used to extract the `Callable` structural type inhabited by a given callable object. This can be used to get the externally visibly signature of the object, which can then be used to test various type properties. @@ -436,15 +434,23 @@ def f2() -> int: def f3(x: int, y: str) -> None: return -# error: [invalid-type-form] "Special form `ty_extensions.CallableTypeOf` expected exactly one type parameter" +# error: [invalid-type-form] "Special form `ty_extensions.CallableTypeOf` expected exactly 1 type argument, got 2" c1: CallableTypeOf[f1, f2] # error: [invalid-type-form] "Expected the first argument to `ty_extensions.CallableTypeOf` to be a callable object, but got an object of type `Literal["foo"]`" c2: CallableTypeOf["foo"] +# error: [invalid-type-form] "Expected the first argument to `ty_extensions.CallableTypeOf` to be a callable object, but got an object of type `Literal["foo"]`" +c20: CallableTypeOf[("foo",)] + # error: [invalid-type-form] "`ty_extensions.CallableTypeOf` requires exactly one argument when used in a type expression" def f(x: CallableTypeOf) -> None: reveal_type(x) # revealed: Unknown + +c3: CallableTypeOf[(f3,)] + +# error: [invalid-type-form] "Special form `ty_extensions.CallableTypeOf` expected exactly 1 type argument, got 0" +c4: CallableTypeOf[()] ``` Using it in annotation to reveal the signature of the callable object: diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index f0ecd96e466305..f85d77dcff9920 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1888,6 +1888,26 @@ pub(crate) fn report_invalid_arguments_to_annotated( )); } +pub(crate) fn report_invalid_argument_number_to_special_form( + context: &InferContext, + subscript: &ast::ExprSubscript, + special_form: SpecialFormType, + received_arguments: usize, + expected_arguments: u8, +) { + let noun = if expected_arguments == 1 { + "type argument" + } else { + "type arguments" + }; + if let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "Special form `{special_form}` expected exactly {expected_arguments} {noun}, \ + got {received_arguments}", + )); + } +} + pub(crate) fn report_bad_argument_to_get_protocol_members( context: &InferContext, call: &ast::ExprCall, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 39f0227d4f7b0e..b2f941da31e0e8 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -84,10 +84,10 @@ use crate::types::diagnostic::{ INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, - report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, - report_invalid_assignment, report_invalid_attribute_assignment, - report_invalid_generator_function_return_type, report_invalid_return_type, - report_possibly_unbound_attribute, + report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated, + report_invalid_arguments_to_callable, report_invalid_assignment, + report_invalid_attribute_assignment, report_invalid_generator_function_return_type, + report_invalid_return_type, report_possibly_unbound_attribute, }; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, @@ -9329,6 +9329,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::unknown() } Type::ClassLiteral(literal) if literal.is_known(self.db(), KnownClass::Any) => { + self.infer_expression(slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic("Type `typing.Any` expected no type parameter"); } @@ -9558,20 +9559,33 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } // Type API special forms - SpecialFormType::Not => match arguments_slice { - ast::Expr::Tuple(_) => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "Special form `{special_form}` expected exactly one type parameter", - )); + SpecialFormType::Not => { + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + let num_arguments = arguments.len(); + let negated_type = if num_arguments == 1 { + self.infer_type_expression(&arguments[0]).negate(db) + } else { + for argument in arguments { + self.infer_type_expression(argument); } + report_invalid_argument_number_to_special_form( + &self.context, + subscript, + special_form, + num_arguments, + 1, + ); Type::unknown() + }; + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, negated_type); } - _ => { - let argument_type = self.infer_type_expression(arguments_slice); - argument_type.negate(db) - } - }, + negated_type + } SpecialFormType::Intersection => { let elements = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), @@ -9589,70 +9603,105 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } ty } - SpecialFormType::TypeOf => match arguments_slice { - ast::Expr::Tuple(_) => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "Special form `{special_form}` expected exactly one type parameter", - )); + SpecialFormType::TypeOf => { + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + let num_arguments = arguments.len(); + let type_of_type = if num_arguments == 1 { + // N.B. This uses `infer_expression` rather than `infer_type_expression` + self.infer_expression(&arguments[0]) + } else { + for argument in arguments { + self.infer_type_expression(argument); } + report_invalid_argument_number_to_special_form( + &self.context, + subscript, + special_form, + num_arguments, + 1, + ); Type::unknown() + }; + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, type_of_type); } - _ => { - // NB: This calls `infer_expression` instead of `infer_type_expression`. + type_of_type + } - self.infer_expression(arguments_slice) - } - }, - SpecialFormType::CallableTypeOf => match arguments_slice { - ast::Expr::Tuple(_) => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "Special form `{special_form}` expected exactly one type parameter", - )); + SpecialFormType::CallableTypeOf => { + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + let num_arguments = arguments.len(); + + if num_arguments != 1 { + for argument in arguments { + self.infer_expression(argument); } - Type::unknown() + report_invalid_argument_number_to_special_form( + &self.context, + subscript, + special_form, + num_arguments, + 1, + ); + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, Type::unknown()); + } + return Type::unknown(); } - _ => { - let argument_type = self.infer_expression(arguments_slice); - let bindings = argument_type.bindings(db); - - // SAFETY: This is enforced by the constructor methods on `Bindings` even in - // the case of a non-callable union. - let callable_binding = bindings - .into_iter() - .next() - .expect("`Bindings` should have at least one `CallableBinding`"); - - let mut signature_iter = callable_binding.into_iter().map(|binding| { - if argument_type.is_bound_method() { - binding.signature.bind_self() - } else { - binding.signature.clone() - } - }); - let Some(signature) = signature_iter.next() else { - if let Some(builder) = self - .context - .report_lint(&INVALID_TYPE_FORM, arguments_slice) - { - builder.into_diagnostic(format_args!( - "Expected the first argument to `{special_form}` \ + let argument_type = self.infer_expression(&arguments[0]); + let bindings = argument_type.bindings(db); + + // SAFETY: This is enforced by the constructor methods on `Bindings` even in + // the case of a non-callable union. + let callable_binding = bindings + .into_iter() + .next() + .expect("`Bindings` should have at least one `CallableBinding`"); + + let mut signature_iter = callable_binding.into_iter().map(|binding| { + if argument_type.is_bound_method() { + binding.signature.bind_self() + } else { + binding.signature.clone() + } + }); + + let Some(signature) = signature_iter.next() else { + if let Some(builder) = self + .context + .report_lint(&INVALID_TYPE_FORM, arguments_slice) + { + builder.into_diagnostic(format_args!( + "Expected the first argument to `{special_form}` \ to be a callable object, \ but got an object of type `{actual_type}`", - actual_type = argument_type.display(db) - )); - } - return Type::unknown(); - }; + actual_type = argument_type.display(db) + )); + } + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, Type::unknown()); + } + return Type::unknown(); + }; - let signature = CallableSignature::from_overloads( - std::iter::once(signature).chain(signature_iter), - ); - Type::Callable(CallableType::new(db, signature, false)) + let signature = CallableSignature::from_overloads( + std::iter::once(signature).chain(signature_iter), + ); + let callable_type_of = Type::Callable(CallableType::new(db, signature, false)); + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, callable_type_of); } - }, + callable_type_of + } SpecialFormType::ChainMap => self.infer_parameterized_legacy_typing_alias( subscript, From c7e020df6ba2333514a2736ebdeffd112e79f05a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 17 Jun 2025 15:35:09 +0530 Subject: [PATCH 449/487] [ty] Filter overloads based on `Any` / `Unknown` (#18607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes: astral-sh/ty#552 This PR adds support for step 5 of the overload call evaluation algorithm which specifies: > For all arguments, determine whether all possible materializations of the argument’s type are > assignable to the corresponding parameter type for each of the remaining overloads. If so, > eliminate all of the subsequent remaining overloads. The algorithm works in two parts: 1. Find out the participating parameter indexes. These are the parameters that aren't gradual equivalent to one or more parameter types at the same index in other overloads. 2. Loop over each overload and check whether that would be the _final_ overload for the argument types i.e., the remaining overloads will never be matched against these argument types For step 1, the participating parameter indexes are computed by just comparing whether all the parameter types at the corresponding index for all the overloads are **gradual equivalent**. The step 2 of the algorithm used is described in [this comment](https://github.com/astral-sh/ty/issues/552#issuecomment-2969165421). ## Test Plan Update the overload call tests. --- .../resources/mdtest/call/overloads.md | 472 ++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 4 +- .../ty_python_semantic/src/types/call/bind.rs | 269 ++++++++-- 3 files changed, 700 insertions(+), 45 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md index 2258035524679e..d49ee126394840 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -399,3 +399,475 @@ def _(x: SomeEnum): # TODO: This should be `A | B | C` once enums are supported and are expanded reveal_type(f(x)) # revealed: A ``` + +## Filtering overloads with variadic arguments and parameters + +TODO + +## Filtering based on `Any` / `Unknown` + +This is the step 5 of the overload call evaluation algorithm which specifies that: + +> For all arguments, determine whether all possible materializations of the argument’s type are +> assignable to the corresponding parameter type for each of the remaining overloads. If so, +> eliminate all of the subsequent remaining overloads. + +This is only performed if the previous step resulted in more than one matching overload. + +### Single list argument + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +@overload +def f(x: list[int]) -> int: ... +@overload +def f(x: list[Any]) -> int: ... +@overload +def f(x: Any) -> str: ... +``` + +For the above definition, anything other than `list` should match the last overload: + +```py +from typing import Any + +from overloaded import f + +# Anything other than `list` should match the last overload +reveal_type(f(1)) # revealed: str + +def _(list_int: list[int], list_any: list[Any]): + reveal_type(f(list_int)) # revealed: int + reveal_type(f(list_any)) # revealed: int +``` + +### Single list argument (ambiguous) + +The overload definition is the same as above, but the return type of the second overload is changed +to `str` to make the overload matching ambiguous if the argument is a `list[Any]`. + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +@overload +def f(x: list[int]) -> int: ... +@overload +def f(x: list[Any]) -> str: ... +@overload +def f(x: Any) -> str: ... +``` + +```py +from typing import Any + +from overloaded import f + +# Anything other than `list` should match the last overload +reveal_type(f(1)) # revealed: str + +def _(list_int: list[int], list_any: list[Any]): + # All materializations of `list[int]` are assignable to `list[int]`, so it matches the first + # overload. + reveal_type(f(list_int)) # revealed: int + + # All materializations of `list[Any]` are assignable to `list[int]` and `list[Any]`, but the + # return type of first and second overloads are not equivalent, so the overload matching + # is ambiguous. + reveal_type(f(list_any)) # revealed: Unknown +``` + +### Single tuple argument + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +@overload +def f(x: tuple[int, str]) -> int: ... +@overload +def f(x: tuple[int, Any]) -> int: ... +@overload +def f(x: Any) -> str: ... +``` + +```py +from typing import Any + +from overloaded import f + +reveal_type(f("a")) # revealed: str +reveal_type(f((1, "b"))) # revealed: int +reveal_type(f((1, 2))) # revealed: int + +def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]): + # All materializations are assignable to first overload, so second and third overloads are + # eliminated + reveal_type(f(int_str)) # revealed: int + + # All materializations are assignable to second overload, so the third overload is eliminated; + # the return type of first and second overload is equivalent + reveal_type(f(int_any)) # revealed: int + + # All materializations of `tuple[Any, Any]` are assignable to the parameters of all the + # overloads, but the return types aren't equivalent, so the overload matching is ambiguous + reveal_type(f(any_any)) # revealed: Unknown +``` + +### Multiple arguments + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +class A: ... +class B: ... + +@overload +def f(x: list[int], y: tuple[int, str]) -> A: ... +@overload +def f(x: list[Any], y: tuple[int, Any]) -> A: ... +@overload +def f(x: list[Any], y: tuple[Any, Any]) -> B: ... +``` + +```py +from typing import Any + +from overloaded import A, f + +def _(list_int: list[int], list_any: list[Any], int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]): + # All materializations of both argument types are assignable to the first overload, so the + # second and third overloads are filtered out + reveal_type(f(list_int, int_str)) # revealed: A + + # All materialization of first argument is assignable to first overload and for the second + # argument, they're assignable to the second overload, so the third overload is filtered out + reveal_type(f(list_int, int_any)) # revealed: A + + # All materialization of first argument is assignable to second overload and for the second + # argument, they're assignable to the first overload, so the third overload is filtered out + reveal_type(f(list_any, int_str)) # revealed: A + + # All materializations of both arguments are assignable to the second overload, so the third + # overload is filtered out + reveal_type(f(list_any, int_any)) # revealed: A + + # All materializations of first argument is assignable to the second overload and for the second + # argument, they're assignable to the third overload, so no overloads are filtered out; the + # return types of the remaining overloads are not equivalent, so overload matching is ambiguous + reveal_type(f(list_int, any_any)) # revealed: Unknown +``` + +### `LiteralString` and `str` + +`overloaded.pyi`: + +```pyi +from typing import overload +from typing_extensions import LiteralString + +@overload +def f(x: LiteralString) -> LiteralString: ... +@overload +def f(x: str) -> str: ... +``` + +```py +from typing import Any +from typing_extensions import LiteralString + +from overloaded import f + +def _(literal: LiteralString, string: str, any: Any): + reveal_type(f(literal)) # revealed: LiteralString + reveal_type(f(string)) # revealed: str + + # `Any` matches both overloads, but the return types are not equivalent. + # Pyright and mypy both reveal `str` here, contrary to the spec. + reveal_type(f(any)) # revealed: Unknown +``` + +### Generics + +`overloaded.pyi`: + +```pyi +from typing import Any, TypeVar, overload + +_T = TypeVar("_T") + +class A: ... +class B: ... + +@overload +def f(x: list[int]) -> A: ... +@overload +def f(x: list[_T]) -> _T: ... +@overload +def f(x: Any) -> B: ... +``` + +```py +from typing import Any + +from overloaded import f + +def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any): + reveal_type(f(list_int)) # revealed: A + # TODO: Should be `str` + reveal_type(f(list_str)) # revealed: Unknown + reveal_type(f(list_any)) # revealed: Unknown + reveal_type(f(any)) # revealed: Unknown +``` + +### Generics (multiple arguments) + +`overloaded.pyi`: + +```pyi +from typing import Any, TypeVar, overload + +_T = TypeVar("_T") + +@overload +def f(x: int, y: Any) -> int: ... +@overload +def f(x: str, y: _T) -> _T: ... +``` + +```py +from typing import Any + +from overloaded import f + +def _(integer: int, string: str, any: Any, list_any: list[Any]): + reveal_type(f(integer, string)) # revealed: int + reveal_type(f(string, integer)) # revealed: int + + # This matches the second overload and is _not_ the case of ambiguous overload matching. + reveal_type(f(string, any)) # revealed: Any + + reveal_type(f(string, list_any)) # revealed: list[Any] +``` + +### Generic `self` + +`overloaded.pyi`: + +```pyi +from typing import Any, overload, TypeVar, Generic + +_T = TypeVar("_T") + +class A(Generic[_T]): + @overload + def method(self: "A[int]") -> int: ... + @overload + def method(self: "A[Any]") -> int: ... + +class B(Generic[_T]): + @overload + def method(self: "B[int]") -> int: ... + @overload + def method(self: "B[Any]") -> str: ... +``` + +```py +from typing import Any + +from overloaded import A, B + +def _(a_int: A[int], a_str: A[str], a_any: A[Any]): + reveal_type(a_int.method()) # revealed: int + reveal_type(a_str.method()) # revealed: int + reveal_type(a_any.method()) # revealed: int + +def _(b_int: B[int], b_str: B[str], b_any: B[Any]): + reveal_type(b_int.method()) # revealed: int + reveal_type(b_str.method()) # revealed: str + reveal_type(b_any.method()) # revealed: Unknown +``` + +### Variadic argument + +TODO: A variadic parameter is being assigned to a number of parameters of the same type + +### Non-participating fully-static parameter + +Ref: + +A non-participating parameter would be the one where the set of materializations of the argument +type, that are assignable to the parameter type at the same index, is same for the overloads for +which step 5 needs to be performed. + +`overloaded.pyi`: + +```pyi +from typing import Literal, overload + +@overload +def f(x: str, *, flag: Literal[True]) -> int: ... +@overload +def f(x: str, *, flag: Literal[False] = ...) -> str: ... +@overload +def f(x: str, *, flag: bool = ...) -> int | str: ... +``` + +In the following example, for the `f(any, flag=True)` call, the materializations of first argument +type `Any` that are assignable to `str` is same for overloads 1 and 3 (at the time of step 5), so +for the purposes of overload matching that parameter can be ignored. If `Any` materializes to +anything that's not assignable to `str`, all of the overloads would already be filtered out which +will raise a `no-matching-overload` error. + +```py +from typing import Any + +from overloaded import f + +def _(any: Any): + reveal_type(f(any, flag=True)) # revealed: int + reveal_type(f(any, flag=False)) # revealed: str +``` + +### Non-participating gradual parameter + +`overloaded.pyi`: + +```pyi +from typing import Any, Literal, overload + +@overload +def f(x: tuple[str, Any], *, flag: Literal[True]) -> int: ... +@overload +def f(x: tuple[str, Any], *, flag: Literal[False] = ...) -> str: ... +@overload +def f(x: tuple[str, Any], *, flag: bool = ...) -> int | str: ... +``` + +```py +from typing import Any + +from overloaded import f + +def _(any: Any): + reveal_type(f(any, flag=True)) # revealed: int + reveal_type(f(any, flag=False)) # revealed: str +``` + +### Argument type expansion + +This filtering can also happen for each of the expanded argument lists. + +#### No ambiguity + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +class A: ... +class B: ... + +@overload +def f(x: tuple[A, B]) -> A: ... +@overload +def f(x: tuple[B, A]) -> B: ... +@overload +def f(x: tuple[A, Any]) -> A: ... +@overload +def f(x: tuple[B, Any]) -> B: ... +``` + +Here, the argument `tuple[A | B, Any]` doesn't match any of the overloads, so we perform argument +type expansion which results in two argument lists: + +1. `tuple[A, Any]` +1. `tuple[B, Any]` + +The first argument list matches overload 1 and 3 via `Any` materialization for which the return +types are equivalent (`A`). Similarly, the second argument list matches overload 2 and 4 via `Any` +materialization for which the return types are equivalent (`B`). The final return type for the call +will be the union of the return types. + +```py +from typing import Any + +from overloaded import A, B, f + +def _(arg: tuple[A | B, Any]): + reveal_type(f(arg)) # revealed: A | B +``` + +#### One argument list ambiguous + +The example used here is same as the previous one, but the return type of the last overload is +changed so that it's not equivalent to the return type of the second overload, creating an ambiguous +matching for the second argument list. + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: tuple[A, B]) -> A: ... +@overload +def f(x: tuple[B, A]) -> B: ... +@overload +def f(x: tuple[A, Any]) -> A: ... +@overload +def f(x: tuple[B, Any]) -> C: ... +``` + +```py +from typing import Any + +from overloaded import A, B, C, f + +def _(arg: tuple[A | B, Any]): + reveal_type(f(arg)) # revealed: A | Unknown +``` + +#### Both argument lists ambiguous + +Here, both argument lists created by expanding the argument type are ambiguous, so the final return +type is `Any`. + +`overloaded.pyi`: + +```pyi +from typing import Any, overload + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: tuple[A, B]) -> A: ... +@overload +def f(x: tuple[B, A]) -> B: ... +@overload +def f(x: tuple[A, Any]) -> C: ... +@overload +def f(x: tuple[B, Any]) -> C: ... +``` + +```py +from typing import Any + +from overloaded import A, B, C, f + +def _(arg: tuple[A | B, Any]): + reveal_type(f(arg)) # revealed: Unknown +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 4c7a15799f999c..4a28fe40c86ca0 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5834,9 +5834,9 @@ impl<'db> KnownInstanceType<'db> { #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DynamicType { - // An explicitly annotated `typing.Any` + /// An explicitly annotated `typing.Any` Any, - // An unannotated value, or a dynamic type resulting from an error + /// An unannotated value, or a dynamic type resulting from an error Unknown, /// Temporary type for symbols that can't be inferred yet because of missing implementations. /// diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 45f7d5694da007..fb41f559af4693 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,6 +3,8 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. +use std::collections::HashSet; + use itertools::Itertools; use ruff_db::parsed::parsed_module; use smallvec::{SmallVec, smallvec}; @@ -1029,7 +1031,7 @@ impl<'db> From> for Bindings<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, - return_type: None, + overload_call_return_type: None, overloads: smallvec![from], }; Bindings { @@ -1068,13 +1070,21 @@ pub(crate) struct CallableBinding<'db> { /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. pub(crate) bound_type: Option>, - /// The return type of this callable. + /// The return type of this overloaded callable. + /// + /// This is [`Some`] only in the following cases: + /// 1. Argument type expansion was performed and one of the expansions evaluated successfully + /// for all of the argument lists, or + /// 2. Overload call evaluation was ambiguous, meaning that multiple overloads matched the + /// argument lists, but they all had different return types + /// + /// For (1), the final return type is the union of all the return types of the matched + /// overloads for the expanded argument lists. /// - /// This is only `Some` if it's an overloaded callable, "argument type expansion" was - /// performed, and one of the expansion evaluated successfully for all of the argument lists. - /// This type is then the union of all the return types of the matched overloads for the - /// expanded argument lists. - return_type: Option>, + /// For (2), the final return type is [`Unknown`]. + /// + /// [`Unknown`]: crate::types::DynamicType::Unknown + overload_call_return_type: Option>, /// The bindings of each overload of this callable. Will be empty if the type is not callable. /// @@ -1097,7 +1107,7 @@ impl<'db> CallableBinding<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, - return_type: None, + overload_call_return_type: None, overloads, } } @@ -1108,7 +1118,7 @@ impl<'db> CallableBinding<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, - return_type: None, + overload_call_return_type: None, overloads: smallvec![], } } @@ -1176,7 +1186,7 @@ impl<'db> CallableBinding<'db> { } }; - let snapshotter = MatchingOverloadsSnapshotter::new(matching_overload_indexes); + let snapshotter = CallableBindingSnapshotter::new(matching_overload_indexes); // State of the bindings _before_ evaluating (type checking) the matching overloads using // the non-expanded argument types. @@ -1196,9 +1206,13 @@ impl<'db> CallableBinding<'db> { // If only one overload evaluates without error, it is the winning match. return; } - MatchingOverloadIndex::Multiple(_) => { + MatchingOverloadIndex::Multiple(indexes) => { // If two or more candidate overloads remain, proceed to step 4. - // TODO: Step 4 and Step 5 goes here... + // TODO: Step 4 + + // Step 5 + self.filter_overloads_using_any_or_unknown(db, argument_types.types(), &indexes); + // We're returning here because this shouldn't lead to argument type expansion. return; } @@ -1225,7 +1239,7 @@ impl<'db> CallableBinding<'db> { // This is the merged state of the bindings after evaluating all of the expanded // argument lists. This will be the final state to restore the bindings to if all of // the expanded argument lists evaluated successfully. - let mut merged_evaluation_state: Option> = None; + let mut merged_evaluation_state: Option> = None; let mut return_types = Vec::new(); @@ -1241,10 +1255,16 @@ impl<'db> CallableBinding<'db> { MatchingOverloadIndex::Single(index) => { Some(self.overloads[index].return_type()) } - MatchingOverloadIndex::Multiple(index) => { - // TODO: Step 4 and Step 5 goes here... but for now we just use the return - // type of the first matched overload. - Some(self.overloads[index[0]].return_type()) + MatchingOverloadIndex::Multiple(matching_overload_indexes) => { + // TODO: Step 4 + + self.filter_overloads_using_any_or_unknown( + db, + expanded_argument_types, + &matching_overload_indexes, + ); + + Some(self.return_type()) } }; @@ -1274,17 +1294,23 @@ impl<'db> CallableBinding<'db> { } if return_types.len() == expanded_argument_lists.len() { - // If the number of return types is equal to the number of expanded argument lists, - // they all evaluated successfully. So, we need to combine their return types by - // union to determine the final return type. - self.return_type = Some(UnionType::from_elements(db, return_types)); - // Restore the bindings state to the one that merges the bindings state evaluating // each of the expanded argument list. + // + // Note that this needs to happen *before* setting the return type, because this + // will restore the return type to the one before argument type expansion. if let Some(merged_evaluation_state) = merged_evaluation_state { snapshotter.restore(self, merged_evaluation_state); } + // If the number of return types is equal to the number of expanded argument lists, + // they all evaluated successfully. So, we need to combine their return types by + // union to determine the final return type. + self.overload_call_return_type = + Some(OverloadCallReturnType::ArgumentTypeExpansion( + UnionType::from_elements(db, return_types), + )); + return; } } @@ -1296,6 +1322,137 @@ impl<'db> CallableBinding<'db> { snapshotter.restore(self, post_evaluation_snapshot); } + /// Filter overloads based on [`Any`] or [`Unknown`] argument types. + /// + /// This is the step 5 of the [overload call evaluation algorithm][1]. + /// + /// The filtering works on the remaining overloads that are present at the + /// `matching_overload_indexes` and are filtered out by marking them as unmatched overloads + /// using the [`mark_as_unmatched_overload`] method. + /// + /// [`Any`]: crate::types::DynamicType::Any + /// [`Unknown`]: crate::types::DynamicType::Unknown + /// [`mark_as_unmatched_overload`]: Binding::mark_as_unmatched_overload + /// [1]: https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation + fn filter_overloads_using_any_or_unknown( + &mut self, + db: &'db dyn Db, + argument_types: &[Type<'db>], + matching_overload_indexes: &[usize], + ) { + // These are the parameter indexes that matches the arguments that participate in the + // filtering process. + // + // The parameter types at these indexes have at least one overload where the type isn't + // gradual equivalent to the parameter types at the same index for other overloads. + let mut participating_parameter_indexes = HashSet::new(); + + // These only contain the top materialized argument types for the corresponding + // participating parameter indexes. + let mut top_materialized_argument_types = vec![]; + + for (argument_index, argument_type) in argument_types.iter().enumerate() { + let mut first_parameter_type: Option> = None; + let mut participating_parameter_index = None; + + for overload_index in matching_overload_indexes { + let overload = &self.overloads[*overload_index]; + let Some(parameter_index) = overload.argument_parameters[argument_index] else { + // There is no parameter for this argument in this overload. + break; + }; + // TODO: For an unannotated `self` / `cls` parameter, the type should be + // `typing.Self` / `type[typing.Self]` + let current_parameter_type = overload.signature.parameters()[parameter_index] + .annotated_type() + .unwrap_or(Type::unknown()); + if let Some(first_parameter_type) = first_parameter_type { + if !first_parameter_type.is_gradual_equivalent_to(db, current_parameter_type) { + participating_parameter_index = Some(parameter_index); + break; + } + } else { + first_parameter_type = Some(current_parameter_type); + } + } + + if let Some(parameter_index) = participating_parameter_index { + participating_parameter_indexes.insert(parameter_index); + top_materialized_argument_types.push(argument_type.top_materialization(db)); + } + } + + let top_materialized_argument_type = + TupleType::from_elements(db, top_materialized_argument_types); + + // A flag to indicate whether we've found the overload that makes the remaining overloads + // unmatched for the given argument types. + let mut filter_remaining_overloads = false; + + for (upto, current_index) in matching_overload_indexes.iter().enumerate() { + if filter_remaining_overloads { + self.overloads[*current_index].mark_as_unmatched_overload(); + continue; + } + let mut parameter_types = Vec::with_capacity(argument_types.len()); + for argument_index in 0..argument_types.len() { + // The parameter types at the current argument index. + let mut current_parameter_types = vec![]; + for overload_index in &matching_overload_indexes[..=upto] { + let overload = &self.overloads[*overload_index]; + let Some(parameter_index) = overload.argument_parameters[argument_index] else { + // There is no parameter for this argument in this overload. + continue; + }; + if !participating_parameter_indexes.contains(¶meter_index) { + // This parameter doesn't participate in the filtering process. + continue; + } + // TODO: For an unannotated `self` / `cls` parameter, the type should be + // `typing.Self` / `type[typing.Self]` + let parameter_type = overload.signature.parameters()[parameter_index] + .annotated_type() + .unwrap_or(Type::unknown()); + current_parameter_types.push(parameter_type); + } + if current_parameter_types.is_empty() { + continue; + } + parameter_types.push(UnionType::from_elements(db, current_parameter_types)); + } + if top_materialized_argument_type + .is_assignable_to(db, TupleType::from_elements(db, parameter_types)) + { + filter_remaining_overloads = true; + } + } + + // Once this filtering process is applied for all arguments, examine the return types of + // the remaining overloads. If the resulting return types for all remaining overloads are + // equivalent, proceed to step 6. + let are_return_types_equivalent_for_all_matching_overloads = { + let mut matching_overloads = self.matching_overloads(); + if let Some(first_overload_return_type) = matching_overloads + .next() + .map(|(_, overload)| overload.return_type()) + { + matching_overloads.all(|(_, overload)| { + overload + .return_type() + .is_equivalent_to(db, first_overload_return_type) + }) + } else { + // No matching overload + true + } + }; + + if !are_return_types_equivalent_for_all_matching_overloads { + // Overload matching is ambiguous. + self.overload_call_return_type = Some(OverloadCallReturnType::Ambiguous); + } + } + fn as_result(&self) -> Result<(), CallErrorKind> { if !self.is_callable() { return Err(CallErrorKind::NotCallable); @@ -1370,8 +1527,11 @@ impl<'db> CallableBinding<'db> { /// For an invalid call to an overloaded function, we return `Type::unknown`, since we cannot /// make any useful conclusions about which overload was intended to be called. pub(crate) fn return_type(&self) -> Type<'db> { - if let Some(return_type) = self.return_type { - return return_type; + if let Some(overload_call_return_type) = self.overload_call_return_type { + return match overload_call_return_type { + OverloadCallReturnType::ArgumentTypeExpansion(return_type) => return_type, + OverloadCallReturnType::Ambiguous => Type::unknown(), + }; } if let Some((_, first_overload)) = self.matching_overloads().next() { return first_overload.return_type(); @@ -1521,6 +1681,12 @@ impl<'a, 'db> IntoIterator for &'a CallableBinding<'db> { } } +#[derive(Debug, Copy, Clone)] +enum OverloadCallReturnType<'db> { + ArgumentTypeExpansion(Type<'db>), + Ambiguous, +} + #[derive(Debug)] enum MatchingOverloadIndex { /// No matching overloads found. @@ -1855,6 +2021,11 @@ impl<'db> Binding<'db> { .map(|(arg_and_type, _)| arg_and_type) } + /// Mark this overload binding as an unmatched overload. + fn mark_as_unmatched_overload(&mut self) { + self.errors.push(BindingError::UnmatchedOverload); + } + fn report_diagnostics( &self, context: &InferContext<'db, '_>, @@ -1915,23 +2086,27 @@ struct BindingSnapshot<'db> { errors: Vec>, } -/// Represents the snapshot of the matched overload bindings. -/// -/// The reason that this only contains the matched overloads are: -/// 1. Avoid creating snapshots for the overloads that have been filtered by the arity check -/// 2. Avoid duplicating errors when merging the snapshots on a successful evaluation of all the -/// expanded argument lists #[derive(Clone, Debug)] -struct MatchingOverloadsSnapshot<'db>(Vec<(usize, BindingSnapshot<'db>)>); +struct CallableBindingSnapshot<'db> { + overload_return_type: Option>, -impl<'db> MatchingOverloadsSnapshot<'db> { + /// Represents the snapshot of the matched overload bindings. + /// + /// The reason that this only contains the matched overloads are: + /// 1. Avoid creating snapshots for the overloads that have been filtered by the arity check + /// 2. Avoid duplicating errors when merging the snapshots on a successful evaluation of all + /// the expanded argument lists + matching_overloads: Vec<(usize, BindingSnapshot<'db>)>, +} + +impl<'db> CallableBindingSnapshot<'db> { /// Update the state of the matched overload bindings in this snapshot with the current /// state in the given `binding`. fn update(&mut self, binding: &CallableBinding<'db>) { // Here, the `snapshot` is the state of this binding for the previous argument list and // `binding` would contain the state after evaluating the current argument list. for (snapshot, binding) in self - .0 + .matching_overloads .iter_mut() .map(|(index, snapshot)| (snapshot, &binding.overloads[*index])) { @@ -1967,13 +2142,13 @@ impl<'db> MatchingOverloadsSnapshot<'db> { /// A helper to take snapshots of the matched overload bindings for the current state of the /// bindings. -struct MatchingOverloadsSnapshotter(Vec); +struct CallableBindingSnapshotter(Vec); -impl MatchingOverloadsSnapshotter { +impl CallableBindingSnapshotter { /// Creates a new snapshotter for the given indexes of the matched overloads. fn new(indexes: Vec) -> Self { debug_assert!(indexes.len() > 1); - MatchingOverloadsSnapshotter(indexes) + CallableBindingSnapshotter(indexes) } /// Takes a snapshot of the current state of the matched overload bindings. @@ -1981,23 +2156,26 @@ impl MatchingOverloadsSnapshotter { /// # Panics /// /// Panics if the indexes of the matched overloads are not valid for the given binding. - fn take<'db>(&self, binding: &CallableBinding<'db>) -> MatchingOverloadsSnapshot<'db> { - MatchingOverloadsSnapshot( - self.0 + fn take<'db>(&self, binding: &CallableBinding<'db>) -> CallableBindingSnapshot<'db> { + CallableBindingSnapshot { + overload_return_type: binding.overload_call_return_type, + matching_overloads: self + .0 .iter() .map(|index| (*index, binding.overloads[*index].snapshot())) .collect(), - ) + } } /// Restores the state of the matched overload bindings from the given snapshot. fn restore<'db>( &self, binding: &mut CallableBinding<'db>, - snapshot: MatchingOverloadsSnapshot<'db>, + snapshot: CallableBindingSnapshot<'db>, ) { - debug_assert_eq!(self.0.len(), snapshot.0.len()); - for (index, snapshot) in snapshot.0 { + debug_assert_eq!(self.0.len(), snapshot.matching_overloads.len()); + binding.overload_call_return_type = snapshot.overload_return_type; + for (index, snapshot) in snapshot.matching_overloads { binding.overloads[index].restore(snapshot); } } @@ -2140,6 +2318,9 @@ pub(crate) enum BindingError<'db> { /// We use this variant to report errors in `property.__get__` and `property.__set__`, which /// can occur when the call to the underlying getter/setter fails. InternalCallError(&'static str), + /// This overload binding of the callable does not match the arguments. + // TODO: We could expand this with an enum to specify why the overload is unmatched. + UnmatchedOverload, } impl<'db> BindingError<'db> { @@ -2332,6 +2513,8 @@ impl<'db> BindingError<'db> { } } } + + Self::UnmatchedOverload => {} } } From 913f136d33fbc1b7d20fc91190431ccef2e2834c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 17 Jun 2025 11:10:34 +0100 Subject: [PATCH 450/487] [ty] Offer "Did you mean...?" suggestions for unresolved `from` imports and unresolved attributes (#18705) Co-authored-by: Brent Westbrook --- _typos.toml | 2 + crates/ty/docs/rules.md | 112 +++--- .../resources/mdtest/attributes.md | 51 +++ .../resources/mdtest/import/basic.md | 36 ++ ..._obvi\342\200\246_(bf7b28ef99f0ec16).snap" | 115 ++++++ ...hat_h\342\200\246_(3caffc60d8390adf).snap" | 69 ++++ .../ty_python_semantic/src/semantic_model.rs | 2 +- crates/ty_python_semantic/src/types.rs | 4 +- .../types/{ide_support.rs => all_members.rs} | 6 + .../ty_python_semantic/src/types/call/bind.rs | 4 +- crates/ty_python_semantic/src/types/class.rs | 4 + .../src/types/diagnostic.rs | 9 +- .../src/types/diagnostic/levenshtein.rs | 378 ++++++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 98 +++-- 14 files changed, 794 insertions(+), 96 deletions(-) create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" create mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" rename crates/ty_python_semantic/src/types/{ide_support.rs => all_members.rs} (96%) create mode 100644 crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs diff --git a/_typos.toml b/_typos.toml index 24406f10bba161..f1cd0c0ef32978 100644 --- a/_typos.toml +++ b/_typos.toml @@ -8,6 +8,8 @@ extend-exclude = [ # words naturally. It's annoying to have to make all # of them actually words. So just ignore typos here. "crates/ty_ide/src/completion.rs", + # Same for "Did you mean...?" levenshtein tests. + "crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs", ] [default.extend-words] diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index b36ccae6576bef..740f5acb088777 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L94) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L138) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L164) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L189) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L215) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L371) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L390) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L410) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L415) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L455) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1459) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L528) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L549) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L603) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L608) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L629) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L634) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L705) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L710) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L748) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L753) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L768) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L773) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L436) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L816) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L657) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L662) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L879) ## `invalid-type-guard-call` @@ -1045,7 +1045,7 @@ f(10) # Error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931) ## `invalid-type-guard-definition` @@ -1078,7 +1078,7 @@ class C: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L898) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903) ## `invalid-type-variable-constraints` @@ -1112,7 +1112,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959) ## `missing-argument` @@ -1136,7 +1136,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988) ## `no-matching-overload` @@ -1164,7 +1164,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1007) ## `non-subscriptable` @@ -1187,7 +1187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1025) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1030) ## `not-iterable` @@ -1212,7 +1212,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1048) ## `parameter-already-assigned` @@ -1238,7 +1238,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1094) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1099) ## `raw-string-type-annotation` @@ -1297,7 +1297,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435) ## `subclass-of-final-class` @@ -1325,7 +1325,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1185) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190) ## `too-many-positional-arguments` @@ -1351,7 +1351,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235) ## `type-assertion-failure` @@ -1378,7 +1378,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213) ## `unavailable-implicit-super-arguments` @@ -1422,7 +1422,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256) ## `unknown-argument` @@ -1448,7 +1448,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1308) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) ## `unresolved-attribute` @@ -1475,7 +1475,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1334) ## `unresolved-import` @@ -1499,7 +1499,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1356) ## `unresolved-reference` @@ -1523,7 +1523,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375) ## `unsupported-bool-conversion` @@ -1559,7 +1559,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1068) ## `unsupported-operator` @@ -1586,7 +1586,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394) ## `zero-stepsize-in-slice` @@ -1610,7 +1610,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1416) ## `invalid-ignore-comment` @@ -1666,7 +1666,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1115) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120) ## `possibly-unbound-implicit-call` @@ -1697,7 +1697,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L112) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) ## `possibly-unbound-import` @@ -1728,7 +1728,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1142) ## `redundant-cast` @@ -1754,7 +1754,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1482) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1487) ## `undefined-reveal` @@ -1777,7 +1777,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295) ## `unknown-rule` @@ -1845,7 +1845,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L490) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L495) ## `division-by-zero` @@ -1868,7 +1868,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L241) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246) ## `possibly-unresolved-reference` @@ -1895,7 +1895,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1163) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1168) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 53643da8ecdb09..0b0f7d50189e82 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -2167,6 +2167,57 @@ reveal_type(Foo.BAR.value) # revealed: @Todo(Attribute access on enum classes) reveal_type(Foo.__members__) # revealed: @Todo(Attribute access on enum classes) ``` +## Suggestions for obvious typos + + + +For obvious typos, we add a "Did you mean...?" suggestion to the diagnostic. + +```py +import collections + +print(collections.dequee) # error: [unresolved-attribute] +``` + +But the suggestion is suppressed if the only close matches start with a leading underscore: + +```py +class Foo: + _bar = 42 + +print(Foo.bar) # error: [unresolved-attribute] +``` + +The suggestion is not suppressed if the typo itself starts with a leading underscore, however: + +```py +print(Foo._barr) # error: [unresolved-attribute] +``` + +And in method contexts, the suggestion is never suppressed if accessing an attribute on an instance +of the method's enclosing class: + +```py +class Bar: + _attribute = 42 + + def f(self, x: "Bar"): + # TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below + print(self.attribute) + + # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, + # because we're in a method context and `x` is an instance of the enclosing class. + print(x.attribute) # error: [unresolved-attribute] + +class Baz: + def f(self, x: Bar): + # No suggestion is given here, because: + # - the good suggestions all start with underscores + # - the typo does not start with an underscore + # - We *are* in a method context, but `x` is not an instance of the enclosing class + print(x.attribute) # error: [unresolved-attribute] +``` + ## References Some of the tests in the *Class and instance variables* section draw inspiration from diff --git a/crates/ty_python_semantic/resources/mdtest/import/basic.md b/crates/ty_python_semantic/resources/mdtest/import/basic.md index 8e7538190ef06a..01d8045b97ed13 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/import/basic.md @@ -205,3 +205,39 @@ python-version = "3.13" import aifc # error: [unresolved-import] from distutils import sysconfig # error: [unresolved-import] ``` + +## `from` import that has a typo + +We offer a "Did you mean?" subdiagnostic suggestion if there's a name in the module that's +reasonably similar to the unresolved member. + + + +`foo.py`: + +```py +from collections import dequee # error: [unresolved-import] +``` + +However, we suppress the suggestion if the only close matches in the module start with a leading +underscore: + +`bar.py`: + +```py +from baz import foo # error: [unresolved-import] +``` + +`baz.py`: + +```py +_foo = 42 +``` + +The suggestion is never suppressed if the typo itself starts with a leading underscore, however: + +`eggs.py`: + +```py +from baz import _fooo # error: [unresolved-import] +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" new file mode 100644 index 00000000000000..350467007b6e3e --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" @@ -0,0 +1,115 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: attributes.md - Attributes - Suggestions for obvious typos +mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | import collections + 2 | + 3 | print(collections.dequee) # error: [unresolved-attribute] + 4 | class Foo: + 5 | _bar = 42 + 6 | + 7 | print(Foo.bar) # error: [unresolved-attribute] + 8 | print(Foo._barr) # error: [unresolved-attribute] + 9 | class Bar: +10 | _attribute = 42 +11 | +12 | def f(self, x: "Bar"): +13 | # TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below +14 | print(self.attribute) +15 | +16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, +17 | # because we're in a method context and `x` is an instance of the enclosing class. +18 | print(x.attribute) # error: [unresolved-attribute] +19 | +20 | class Baz: +21 | def f(self, x: Bar): +22 | # No suggestion is given here, because: +23 | # - the good suggestions all start with underscores +24 | # - the typo does not start with an underscore +25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class +26 | print(x.attribute) # error: [unresolved-attribute] +``` + +# Diagnostics + +``` +error[unresolved-attribute]: Type `` has no attribute `dequee` + --> src/mdtest_snippet.py:3:7 + | +1 | import collections +2 | +3 | print(collections.dequee) # error: [unresolved-attribute] + | ^^^^^^^^^^^^^^^^^^ Did you mean `deque`? +4 | class Foo: +5 | _bar = 42 + | +info: rule `unresolved-attribute` is enabled by default + +``` + +``` +error[unresolved-attribute]: Type `` has no attribute `bar` + --> src/mdtest_snippet.py:7:7 + | +5 | _bar = 42 +6 | +7 | print(Foo.bar) # error: [unresolved-attribute] + | ^^^^^^^ +8 | print(Foo._barr) # error: [unresolved-attribute] +9 | class Bar: + | +info: rule `unresolved-attribute` is enabled by default + +``` + +``` +error[unresolved-attribute]: Type `` has no attribute `_barr` + --> src/mdtest_snippet.py:8:7 + | + 7 | print(Foo.bar) # error: [unresolved-attribute] + 8 | print(Foo._barr) # error: [unresolved-attribute] + | ^^^^^^^^^ Did you mean `_bar`? + 9 | class Bar: +10 | _attribute = 42 + | +info: rule `unresolved-attribute` is enabled by default + +``` + +``` +error[unresolved-attribute]: Type `Bar` has no attribute `attribute` + --> src/mdtest_snippet.py:18:15 + | +16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, +17 | # because we're in a method context and `x` is an instance of the enclosing class. +18 | print(x.attribute) # error: [unresolved-attribute] + | ^^^^^^^^^^^ Did you mean `_attribute`? +19 | +20 | class Baz: + | +info: rule `unresolved-attribute` is enabled by default + +``` + +``` +error[unresolved-attribute]: Type `Bar` has no attribute `attribute` + --> src/mdtest_snippet.py:26:15 + | +24 | # - the typo does not start with an underscore +25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class +26 | print(x.attribute) # error: [unresolved-attribute] + | ^^^^^^^^^^^ + | +info: rule `unresolved-attribute` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" new file mode 100644 index 00000000000000..0b237ac4def491 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" @@ -0,0 +1,69 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: basic.md - Structures - `from` import that has a typo +mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md +--- + +# Python source files + +## foo.py + +``` +1 | from collections import dequee # error: [unresolved-import] +``` + +## bar.py + +``` +1 | from baz import foo # error: [unresolved-import] +``` + +## baz.py + +``` +1 | _foo = 42 +``` + +## eggs.py + +``` +1 | from baz import _fooo # error: [unresolved-import] +``` + +# Diagnostics + +``` +error[unresolved-import]: Module `collections` has no member `dequee` + --> src/foo.py:1:25 + | +1 | from collections import dequee # error: [unresolved-import] + | ^^^^^^ Did you mean `deque`? + | +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Module `baz` has no member `foo` + --> src/bar.py:1:17 + | +1 | from baz import foo # error: [unresolved-import] + | ^^^ + | +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Module `baz` has no member `_fooo` + --> src/eggs.py:1:17 + | +1 | from baz import _fooo # error: [unresolved-import] + | ^^^^^ Did you mean `_foo`? + | +info: rule `unresolved-import` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 9237e75ee2ece4..e174010fe828a8 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -10,7 +10,7 @@ use crate::module_resolver::{Module, resolve_module}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::place::FileScopeId; use crate::semantic_index::semantic_index; -use crate::types::ide_support::all_declarations_and_bindings; +use crate::types::all_members::all_declarations_and_bindings; use crate::types::{Type, binding_type, infer_scope_types}; pub struct SemanticModel<'db> { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 4a28fe40c86ca0..84462c97d5b7de 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -38,6 +38,7 @@ use crate::semantic_index::definition::Definition; use crate::semantic_index::place::{ScopeId, ScopedPlaceId}; use crate::semantic_index::{imported_modules, place_table, semantic_index}; use crate::suppression::check_suppressions; +pub use crate::types::all_members::all_members; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; @@ -46,7 +47,6 @@ use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; -pub use crate::types::ide_support::all_members; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; @@ -58,6 +58,7 @@ use instance::Protocol; pub use instance::{NominalInstanceType, ProtocolInstanceType}; pub use special_form::SpecialFormType; +pub(crate) mod all_members; mod builder; mod call; mod class; @@ -67,7 +68,6 @@ mod diagnostic; mod display; mod function; mod generics; -pub(crate) mod ide_support; mod infer; mod instance; mod mro; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/all_members.rs similarity index 96% rename from crates/ty_python_semantic/src/types/ide_support.rs rename to crates/ty_python_semantic/src/types/all_members.rs index 1e491e478edbfc..6bcdb2d8478fb1 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/all_members.rs @@ -1,3 +1,9 @@ +//! Routines to gather all members of a type. +//! +//! This is used in autocompletion logic from the `ty_ide` crate, +//! but it is also used in the `ty_python_semantic` crate to provide +//! "Did you mean...?" suggestions in diagnostics. + use crate::Db; use crate::place::{imported_symbol, place_from_bindings, place_from_declarations}; use crate::semantic_index::place::ScopeId; diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index fb41f559af4693..d34a12898ca6d8 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -27,7 +27,7 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, - WrapperDescriptorKind, ide_support, todo_type, + WrapperDescriptorKind, all_members, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -669,7 +669,7 @@ impl<'db> Bindings<'db> { if let [Some(ty)] = overload.parameter_types() { overload.set_return_type(TupleType::from_elements( db, - ide_support::all_members(db, *ty) + all_members::all_members(db, *ty) .into_iter() .sorted() .map(|member| Type::string_literal(db, &member)), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 4c729caee92f9d..cf7cfbc401b7b8 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -225,6 +225,10 @@ pub enum ClassType<'db> { #[salsa::tracked] impl<'db> ClassType<'db> { + pub(super) fn is_protocol(self, db: &'db dyn Db) -> bool { + self.class_literal(db).0.is_protocol(db) + } + pub(super) fn normalized(self, db: &'db dyn Db) -> Self { match self { Self::NonGeneric(_) => self, diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index f85d77dcff9920..454dd24adbf5df 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -17,12 +17,17 @@ use crate::types::string_annotation::{ use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; +pub(crate) use levenshtein::{ + HideUnderscoredSuggestions, find_best_suggestion_for_unresolved_member, +}; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::Formatter; +mod levenshtein; + /// Registers all known type check lints. pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&CALL_NON_CALLABLE); @@ -2212,7 +2217,7 @@ fn report_invalid_base<'ctx, 'db>( /// misconfigured their Python version. pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions( db: &dyn Db, - mut diagnostic: LintDiagnosticGuard, + diagnostic: &mut LintDiagnosticGuard, full_submodule_name: &ModuleName, parent_module: &Module, ) { @@ -2247,5 +2252,5 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions( version_range = version_range.diagnostic_display(), )); - add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules"); + add_inferred_python_version_hint_to_diagnostic(db, diagnostic, "resolving modules"); } diff --git a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs new file mode 100644 index 00000000000000..11fc59674e0d3c --- /dev/null +++ b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs @@ -0,0 +1,378 @@ +//! Infrastructure for providing "Did you mean..?" suggestions to attach to diagnostics. +//! +//! This is a Levenshtein implementation that is mainly ported from the implementation +//! CPython uses to provide suggestions in its own exception messages. +//! The tests similarly owe much to CPython's test suite. +//! Many thanks to Pablo Galindo Salgado and others for implementing the original +//! feature in CPython! + +use crate::Db; +use crate::types::{Type, all_members}; + +use indexmap::IndexSet; +use ruff_python_ast::name::Name; + +/// Given a type and an unresolved member name, find the best suggestion for a member name +/// that is similar to the unresolved member name. +/// +/// This function is used to provide suggestions for subdiagnostics attached to +/// `unresolved-attribute`, `unresolved-import`, and `unresolved-reference` diagnostics. +pub(crate) fn find_best_suggestion_for_unresolved_member<'db>( + db: &'db dyn Db, + obj: Type<'db>, + unresolved_member: &str, + hide_underscored_suggestions: HideUnderscoredSuggestions, +) -> Option { + find_best_suggestion( + all_members(db, obj), + unresolved_member, + hide_underscored_suggestions, + ) +} + +/// Whether to hide suggestions that start with an underscore. +/// +/// If the typo itself starts with an underscore, this policy is ignored. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum HideUnderscoredSuggestions { + Yes, + No, +} + +impl HideUnderscoredSuggestions { + const fn is_no(self) -> bool { + matches!(self, HideUnderscoredSuggestions::No) + } +} + +fn find_best_suggestion( + options: O, + unresolved_member: &str, + hide_underscored_suggestions: HideUnderscoredSuggestions, +) -> Option +where + O: IntoIterator, + I: ExactSizeIterator, +{ + if unresolved_member.is_empty() { + return None; + } + + let options = options.into_iter(); + + // Don't spend a *huge* amount of time computing suggestions if there are many candidates. + // This limit is fairly arbitrary and can be adjusted as needed. + if options.len() > 4096 { + return None; + } + + // Filter out the unresolved member itself. + // Otherwise (due to our implementation of implicit instance attributes), + // we end up giving bogus suggestions like this: + // + // ```python + // class Foo: + // _attribute = 42 + // def bar(self): + // print(self.attribute) # error: unresolved attribute `attribute`; did you mean `attribute`? + // ``` + let options = options.filter(|name| name != unresolved_member); + + let mut options: IndexSet = + if hide_underscored_suggestions.is_no() || unresolved_member.starts_with('_') { + options.collect() + } else { + options.filter(|name| !name.starts_with('_')).collect() + }; + options.sort_unstable(); + find_best_suggestion_impl(options, unresolved_member) +} + +fn find_best_suggestion_impl(options: IndexSet, unresolved_member: &str) -> Option { + let mut best_suggestion = None; + + for member in options { + let mut max_distance = + (member.chars().count() + unresolved_member.chars().count() + 3) * MOVE_COST / 6; + + if let Some((_, best_distance)) = best_suggestion { + if best_distance > 0 { + max_distance = max_distance.min(best_distance - 1); + } + } + + let current_distance = levenshtein_distance(unresolved_member, &member, max_distance); + if current_distance > max_distance { + continue; + } + + if best_suggestion + .as_ref() + .is_none_or(|(_, best_score)| ¤t_distance < best_score) + { + best_suggestion = Some((member, current_distance)); + } + } + + best_suggestion.map(|(suggestion, _)| suggestion) +} + +/// Determine the "cost" of converting `string_a` to `string_b`. +fn substitution_cost(char_a: char, char_b: char) -> CharacterMatch { + if char_a == char_b { + return CharacterMatch::Exact; + } + + let char_a_lowercase = char_a.to_lowercase(); + let char_b_lowercase = char_b.to_lowercase(); + + if char_a_lowercase.len() == char_b_lowercase.len() + && char_a_lowercase.zip(char_b_lowercase).all(|(a, b)| a == b) + { + return CharacterMatch::CaseInsensitive; + } + + CharacterMatch::None +} + +/// The result of comparing two characters. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum CharacterMatch { + Exact, + CaseInsensitive, + None, +} + +/// The cost of a Levenshtein insertion, deletion, or substitution. +/// It should be the same as `CharacterMatch::None` cast to a `usize`. +/// +/// This is used instead of the conventional unit cost to give these differences a higher cost than +/// casing differences, which CPython assigns a cost of 1. +const MOVE_COST: usize = CharacterMatch::None as usize; + +/// Returns the [Levenshtein edit distance] between strings `string_a` and `string_b`. +/// Uses the [Wagner-Fischer algorithm] to speed up the calculation. +/// +/// [Levenshtein edit distance]: https://en.wikipedia.org/wiki/Levenshtein_distance +/// [Wagner-Fischer algorithm]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm +fn levenshtein_distance(string_a: &str, string_b: &str, max_cost: usize) -> usize { + if string_a == string_b { + return 0; + } + + let string_a_chars: Vec = string_a.chars().collect(); + let string_b_chars: Vec = string_b.chars().collect(); + + // Trim away common affixes + let pre = string_a_chars + .iter() + .zip(string_b_chars.iter()) + .take_while(|(a, b)| a == b) + .count(); + let string_a_chars = &string_a_chars[pre..]; + let string_b_chars = &string_b_chars[pre..]; + + // Trim away common suffixes + let post = string_a_chars + .iter() + .rev() + .zip(string_b_chars.iter().rev()) + .take_while(|(a, b)| a == b) + .count(); + let mut string_a_chars = &string_a_chars[..string_a_chars.len() - post]; + let mut string_b_chars = &string_b_chars[..string_b_chars.len() - post]; + + let mut string_a_len = string_a_chars.len(); + let mut string_b_len = string_b_chars.len(); + + // Short-circuit if either string is empty after trimming affixes/suffixes + if string_a_len == 0 || string_b_len == 0 { + return MOVE_COST * (string_a_len + string_b_len); + } + + // `string_a` should refer to the shorter of the two strings. + // This enables us to create a smaller buffer in the main loop below. + if string_b_chars.len() < string_a_chars.len() { + std::mem::swap(&mut string_a_chars, &mut string_b_chars); + std::mem::swap(&mut string_a_len, &mut string_b_len); + } + + // Quick fail if a match is impossible. + if (string_b_len - string_a_len) * MOVE_COST > max_cost { + return max_cost + 1; + } + + let mut row = vec![0; string_a_len]; + for (i, v) in (MOVE_COST..MOVE_COST * (string_a_len + 1)) + .step_by(MOVE_COST) + .enumerate() + { + row[i] = v; + } + + let mut result = 0; + + for (b_index, b_char) in string_b_chars + .iter() + .copied() + .enumerate() + .take(string_b_len) + { + result = b_index * MOVE_COST; + let mut distance = result; + let mut minimum = usize::MAX; + for index in 0..string_a_len { + let substitute = distance + substitution_cost(b_char, string_a_chars[index]) as usize; + distance = row[index]; + let insert_delete = result.min(distance) + MOVE_COST; + result = insert_delete.min(substitute); + + row[index] = result; + if result < minimum { + minimum = result; + } + } + + if minimum > max_cost { + return max_cost + 1; + } + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + /// Given a list of candidates, this test asserts that the best suggestion + /// for the typo `bluch` is what we'd expect. + /// + /// This test is ported from + #[test_case(&["noise", "more_noise", "a", "bc", "bluchin"], "bluchin"; "test for additional characters")] + #[test_case(&["noise", "more_noise", "a", "bc", "blech"], "blech"; "test for substituted characters")] + #[test_case(&["noise", "more_noise", "a", "bc", "blch"], "blch"; "test for eliminated characters")] + #[test_case(&["blach", "bluc"], "blach"; "substitutions are preferred over eliminations")] + #[test_case(&["blach", "bluchi"], "blach"; "substitutions are preferred over additions")] + #[test_case(&["blucha", "bluc"], "bluc"; "eliminations are preferred over additions")] + #[test_case(&["Luch", "fluch", "BLuch"], "BLuch"; "case changes are preferred over substitutions")] + fn test_good_suggestions(candidate_list: &[&str], expected_suggestion: &str) { + let candidates: Vec = candidate_list.iter().copied().map(Name::from).collect(); + let suggestion = find_best_suggestion(candidates, "bluch", HideUnderscoredSuggestions::No); + assert_eq!(suggestion.as_deref(), Some(expected_suggestion)); + } + + /// Test ported from + #[test] + fn underscored_names_not_suggested_if_hide_policy_set_to_yes() { + let suggestion = find_best_suggestion( + [Name::from("_bluch")], + "bluch", + HideUnderscoredSuggestions::Yes, + ); + if let Some(suggestion) = suggestion { + panic!( + "Expected no suggestions for `bluch` due to `HideUnderscoredSuggestions::Yes` but `{suggestion}` was suggested" + ); + } + } + + /// Test ported from + #[test_case("_blach")] + #[test_case("_luch")] + fn underscored_names_are_suggested_if_hide_policy_set_to_yes_when_typo_is_underscored( + typo: &str, + ) { + let suggestion = find_best_suggestion( + [Name::from("_bluch")], + typo, + HideUnderscoredSuggestions::Yes, + ); + assert_eq!(suggestion.as_deref(), Some("_bluch")); + } + + /// Test ported from + #[test_case("_luch")] + #[test_case("_bluch")] + fn non_underscored_names_always_suggested_even_if_typo_underscored(typo: &str) { + let suggestion = + find_best_suggestion([Name::from("bluch")], typo, HideUnderscoredSuggestions::Yes); + assert_eq!(suggestion.as_deref(), Some("bluch")); + } + + /// This asserts that we do not offer silly suggestions for very small names. + /// The test is ported from + #[test_case("b")] + #[test_case("v")] + #[test_case("m")] + #[test_case("py")] + fn test_bad_suggestions_do_not_trigger_for_small_names(typo: &str) { + let candidates = ["vvv", "mom", "w", "id", "pytho"].map(Name::from); + let suggestion = find_best_suggestion(candidates, typo, HideUnderscoredSuggestions::No); + if let Some(suggestion) = suggestion { + panic!("Expected no suggestions for `{typo}` but `{suggestion}` was suggested"); + } + } + + /// Test ported from + #[test] + fn test_no_suggestion_for_very_different_attribute() { + assert_eq!( + find_best_suggestion( + [Name::from("blech")], + "somethingverywrong", + HideUnderscoredSuggestions::No + ), + None + ); + } + + /// These tests are from the Levenshtein Wikipedia article, updated to match CPython's + /// implementation (just doubling the score to accommodate the MOVE_COST) + #[test_case("kitten", "sitting", 6)] + #[test_case("uninformed", "uniformed", 2)] + #[test_case("flaw", "lawn", 4)] + fn test_levenshtein_distance_calculation_wikipedia_examples( + string_a: &str, + string_b: &str, + expected_distance: usize, + ) { + assert_eq!( + levenshtein_distance(string_a, string_b, usize::MAX), + expected_distance + ); + } + + /// Test ported from + #[test_case("", "", 0)] + #[test_case("", "a", 2)] + #[test_case("a", "A", 1)] + #[test_case("Apple", "Aple", 2)] + #[test_case("Banana", "B@n@n@", 6)] + #[test_case("Cherry", "Cherry!", 2)] + #[test_case("---0---", "------", 2)] + #[test_case("abc", "y", 6)] + #[test_case("aa", "bb", 4)] + #[test_case("aaaaa", "AAAAA", 5)] + #[test_case("wxyz", "wXyZ", 2)] + #[test_case("wxyz", "wXyZ123", 8)] + #[test_case("Python", "Java", 12)] + #[test_case("Java", "C#", 8)] + #[test_case("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2)] + #[test_case("CPython", "PyPy", 10)] + #[test_case("CPython", "pypy", 11)] + #[test_case("AttributeError", "AttributeErrop", 2)] + #[test_case("AttributeError", "AttributeErrorTests", 10)] + #[test_case("ABA", "AAB", 4)] + fn test_levenshtein_distance_calculation_cpython_examples( + string_a: &str, + string_b: &str, + expected_distance: usize, + ) { + assert_eq!( + levenshtein_distance(string_a, string_b, 4044), + expected_distance + ); + } +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b2f941da31e0e8..c192432f57a59c 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -77,17 +77,18 @@ use crate::types::call::{ use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, - CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, - INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, - INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, - INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, + CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, HideUnderscoredSuggestions, INCONSISTENT_MRO, + INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, + INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, + INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, - UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, - report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated, - report_invalid_arguments_to_callable, report_invalid_assignment, - report_invalid_attribute_assignment, report_invalid_generator_function_return_type, - report_invalid_return_type, report_possibly_unbound_attribute, + UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, find_best_suggestion_for_unresolved_member, + report_implicit_return_type, report_invalid_argument_number_to_special_form, + report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, + report_invalid_assignment, report_invalid_attribute_assignment, + report_invalid_generator_function_return_type, report_invalid_return_type, + report_possibly_unbound_attribute, }; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, @@ -1854,7 +1855,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { /// is a class scope OR the immediate parent scope is an annotation scope /// and the grandparent scope is a class scope. This means it has different /// behaviour to the [`nearest_enclosing_class`] function. - fn class_context_of_current_method(&self) -> Option> { + fn class_context_of_current_method(&self) -> Option> { let current_scope_id = self.scope().file_scope_id(self.db()); let current_scope = self.index.scope(current_scope_id); if current_scope.kind() != ScopeKind::Function { @@ -1879,7 +1880,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { 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).into_class_literal() + binding_type(self.db(), class_definition).to_class_type(self.db()) } /// Returns `true` if the current scope is the function body scope of a function overload (that @@ -2039,7 +2040,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { returns.range(), declared_ty, has_empty_body, - enclosing_class_context, + enclosing_class_context.map(|class| class.class_literal(self.db()).0), no_return, ); } @@ -4415,6 +4416,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return; } + // Now we know the import cannot be resolved. Several things remain to do: + // - Add `Unknown` as the stored type for the definition. + // - Maybe: add a diagnostic. + // - If emitting a diagnostic: see if we can add helpful subdiagnostics. + self.add_unknown_declaration_with_binding(alias.into(), definition); if &alias.name == "*" { @@ -4432,18 +4438,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return; }; - let diagnostic = builder.into_diagnostic(format_args!( + let mut diagnostic = builder.into_diagnostic(format_args!( "Module `{module_name}` has no member `{name}`" )); if let Some(full_submodule_name) = full_submodule_name { hint_if_stdlib_submodule_exists_on_other_versions( self.db(), - diagnostic, + &mut diagnostic, &full_submodule_name, &module, ); } + + if let Some(suggestion) = find_best_suggestion_for_unresolved_member( + self.db(), + module_ty, + name, + HideUnderscoredSuggestions::Yes, + ) { + diagnostic.set_primary_message(format_args!("Did you mean `{suggestion}`?",)); + } } fn infer_return_statement(&mut self, ret: &ast::StmtReturn) { @@ -6400,7 +6415,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let attribute_exists = self .class_context_of_current_method() .and_then(|class| { - Type::instance(self.db(), class.default_specialization(self.db())) + Type::instance(self.db(), class) .member(self.db(), id) .place .ignore_possibly_unbound() @@ -6494,24 +6509,41 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .context .report_lint(&UNRESOLVED_ATTRIBUTE, attribute) { - if bound_on_instance { - builder.into_diagnostic( - format_args!( - "Attribute `{}` can only be accessed on instances, \ - not on the class object `{}` itself.", - attr.id, - value_type.display(db) - ), - ); - } else { - builder.into_diagnostic( - format_args!( - "Type `{}` has no attribute `{}`", - value_type.display(db), - attr.id - ), - ); - } + let mut diagnostic = if bound_on_instance { + builder.into_diagnostic( + format_args!( + "Attribute `{}` can only be accessed on instances, \ + not on the class object `{}` itself.", + attr.id, + value_type.display(db) + ), + ) + } else { + builder.into_diagnostic( + format_args!( + "Type `{}` has no attribute `{}`", + value_type.display(db), + attr.id + ), + ) + }; + + let underscore_policy = if self + .class_context_of_current_method() + .is_some_and(|class|value_type.is_subtype_of(db, Type::instance(db, class))) + { + HideUnderscoredSuggestions::No + } else { + HideUnderscoredSuggestions::Yes + }; + + if let Some(suggestion) = + find_best_suggestion_for_unresolved_member(db, value_type, &attr.id, underscore_policy) + { + diagnostic.set_primary_message(format_args!( + "Did you mean `{suggestion}`?", + )); + } } } From 8aea383f29e65959383d3a226df58ffa31a29e2b Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 6 Jun 2025 12:01:35 -0500 Subject: [PATCH 451/487] [`refurb`] Stabilize fix safety for `readlines-in-for` (`FURB129`) (#18496) Note that the preview behavior was not documented (shame on us!) so the documentation was not modified. --------- Co-authored-by: Brent Westbrook --- crates/ruff_linter/src/preview.rs | 10 +- .../rules/flake8_simplify/rules/ast_with.rs | 4 +- crates/ruff_linter/src/rules/refurb/mod.rs | 18 - .../rules/refurb/rules/readlines_in_for.rs | 7 +- ...es__refurb__tests__FURB129_FURB129.py.snap | 34 +- ...b__tests__preview__FURB129_FURB129.py.snap | 336 ------------------ 6 files changed, 24 insertions(+), 385 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 50b4fac603c048..8cc426d566703c 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -126,11 +126,9 @@ pub(crate) const fn is_check_file_level_directives_enabled(settings: &LinterSett settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/17644 -pub(crate) const fn is_readlines_in_for_fix_safe_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - -pub(crate) const fn multiple_with_statements_fix_safe_enabled(settings: &LinterSettings) -> bool { +// https://github.com/astral-sh/ruff/pull/18208 +pub(crate) const fn is_multiple_with_statements_fix_safe_enabled( + settings: &LinterSettings, +) -> bool { settings.preview.is_enabled() } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs index 537fb1f9d905d3..e98ef097234c1b 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs @@ -10,7 +10,7 @@ use super::fix_with; use crate::Fix; use crate::checkers::ast::Checker; use crate::fix::edits::fits; -use crate::preview::multiple_with_statements_fix_safe_enabled; +use crate::preview::is_multiple_with_statements_fix_safe_enabled; use crate::{FixAvailability, Violation}; /// ## What it does @@ -195,7 +195,7 @@ pub(crate) fn multiple_with_statements( checker.settings.tab_size, ) }) { - if multiple_with_statements_fix_safe_enabled(checker.settings) { + if is_multiple_with_statements_fix_safe_enabled(checker.settings) { Ok(Some(Fix::safe_edit(edit))) } else { Ok(Some(Fix::unsafe_edit(edit))) diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index a14531e43aa4af..9f74b865b6ab7d 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -62,24 +62,6 @@ mod tests { Ok(()) } - #[test_case(Rule::ReadlinesInFor, Path::new("FURB129.py"))] - fn preview(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("refurb").join(path).as_path(), - &settings::LinterSettings { - preview: settings::types::PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test] fn write_whole_file_python_39() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs index f945b8fdb2c928..aad840bcf172ea 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/readlines_in_for.rs @@ -7,7 +7,6 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::pad_end; -use crate::preview::is_readlines_in_for_fix_safe_enabled; use crate::{AlwaysFixableViolation, Edit, Fix}; /// ## What it does @@ -106,9 +105,5 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) { }; let mut diagnostic = checker.report_diagnostic(ReadlinesInFor, expr_call.range()); - diagnostic.set_fix(if is_readlines_in_for_fix_safe_enabled(checker.settings) { - Fix::safe_edit(edit) - } else { - Fix::unsafe_edit(edit) - }); + diagnostic.set_fix(Fix::safe_edit(edit)); } diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap index ce9c375ba06500..f466c31940af82 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB129_FURB129.py.snap @@ -12,7 +12,7 @@ FURB129.py:7:18: FURB129 [*] Instead of calling `readlines()`, iterate over file | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 4 4 | 5 5 | # Errors 6 6 | with open("FURB129.py") as f: @@ -33,7 +33,7 @@ FURB129.py:9:35: FURB129 [*] Instead of calling `readlines()`, iterate over file | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 6 6 | with open("FURB129.py") as f: 7 7 | for _line in f.readlines(): 8 8 | pass @@ -53,7 +53,7 @@ FURB129.py:10:35: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 7 7 | for _line in f.readlines(): 8 8 | pass 9 9 | a = [line.lower() for line in f.readlines()] @@ -74,7 +74,7 @@ FURB129.py:11:49: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 8 8 | pass 9 9 | a = [line.lower() for line in f.readlines()] 10 10 | b = {line.upper() for line in f.readlines()} @@ -93,7 +93,7 @@ FURB129.py:14:18: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 11 11 | c = {line.lower(): line.upper() for line in f.readlines()} 12 12 | 13 13 | with Path("FURB129.py").open() as f: @@ -113,7 +113,7 @@ FURB129.py:17:14: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 14 14 | for _line in f.readlines(): 15 15 | pass 16 16 | @@ -133,7 +133,7 @@ FURB129.py:20:14: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 17 17 | for _line in open("FURB129.py").readlines(): 18 18 | pass 19 19 | @@ -154,7 +154,7 @@ FURB129.py:26:18: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 23 23 | 24 24 | def func(): 25 25 | f = Path("FURB129.py").open() @@ -173,7 +173,7 @@ FURB129.py:32:18: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 29 29 | 30 30 | 31 31 | def func(f: io.BytesIO): @@ -194,7 +194,7 @@ FURB129.py:38:22: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 35 35 | 36 36 | def func(): 37 37 | with (open("FURB129.py") as f, foo as bar): @@ -213,7 +213,7 @@ FURB129.py:47:17: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 44 44 | import builtins 45 45 | 46 46 | with builtins.open("FURB129.py") as f: @@ -232,7 +232,7 @@ FURB129.py:54:17: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 51 51 | from builtins import open as o 52 52 | 53 53 | with o("FURB129.py") as f: @@ -252,7 +252,7 @@ FURB129.py:93:17: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 90 90 | 91 91 | # https://github.com/astral-sh/ruff/issues/18231 92 92 | with open("furb129.py") as f: @@ -270,7 +270,7 @@ FURB129.py:97:23: FURB129 [*] Instead of calling `readlines()`, iterate over fil | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 94 94 | pass 95 95 | 96 96 | with open("furb129.py") as f: @@ -290,7 +290,7 @@ FURB129.py:101:17: FURB129 [*] Instead of calling `readlines()`, iterate over fi | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 98 98 | 99 99 | 100 100 | with open("furb129.py") as f: @@ -310,7 +310,7 @@ FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over fi | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 100 100 | with open("furb129.py") as f: 101 101 | for line in (((f))).readlines(): 102 102 | pass @@ -328,7 +328,7 @@ FURB129.py:107:29: FURB129 [*] Instead of calling `readlines()`, iterate over fi | = help: Remove `readlines()` -ℹ Unsafe fix +ℹ Safe fix 104 104 | pass 105 105 | 106 106 | # Test case for issue #17683 (missing space before keyword) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap deleted file mode 100644 index f466c31940af82..00000000000000 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview__FURB129_FURB129.py.snap +++ /dev/null @@ -1,336 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/refurb/mod.rs ---- -FURB129.py:7:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -5 | # Errors -6 | with open("FURB129.py") as f: -7 | for _line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -8 | pass -9 | a = [line.lower() for line in f.readlines()] - | - = help: Remove `readlines()` - -ℹ Safe fix -4 4 | -5 5 | # Errors -6 6 | with open("FURB129.py") as f: -7 |- for _line in f.readlines(): - 7 |+ for _line in f: -8 8 | pass -9 9 | a = [line.lower() for line in f.readlines()] -10 10 | b = {line.upper() for line in f.readlines()} - -FURB129.py:9:35: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | - 7 | for _line in f.readlines(): - 8 | pass - 9 | a = [line.lower() for line in f.readlines()] - | ^^^^^^^^^^^^^ FURB129 -10 | b = {line.upper() for line in f.readlines()} -11 | c = {line.lower(): line.upper() for line in f.readlines()} - | - = help: Remove `readlines()` - -ℹ Safe fix -6 6 | with open("FURB129.py") as f: -7 7 | for _line in f.readlines(): -8 8 | pass -9 |- a = [line.lower() for line in f.readlines()] - 9 |+ a = [line.lower() for line in f] -10 10 | b = {line.upper() for line in f.readlines()} -11 11 | c = {line.lower(): line.upper() for line in f.readlines()} -12 12 | - -FURB129.py:10:35: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | - 8 | pass - 9 | a = [line.lower() for line in f.readlines()] -10 | b = {line.upper() for line in f.readlines()} - | ^^^^^^^^^^^^^ FURB129 -11 | c = {line.lower(): line.upper() for line in f.readlines()} - | - = help: Remove `readlines()` - -ℹ Safe fix -7 7 | for _line in f.readlines(): -8 8 | pass -9 9 | a = [line.lower() for line in f.readlines()] -10 |- b = {line.upper() for line in f.readlines()} - 10 |+ b = {line.upper() for line in f} -11 11 | c = {line.lower(): line.upper() for line in f.readlines()} -12 12 | -13 13 | with Path("FURB129.py").open() as f: - -FURB129.py:11:49: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | - 9 | a = [line.lower() for line in f.readlines()] -10 | b = {line.upper() for line in f.readlines()} -11 | c = {line.lower(): line.upper() for line in f.readlines()} - | ^^^^^^^^^^^^^ FURB129 -12 | -13 | with Path("FURB129.py").open() as f: - | - = help: Remove `readlines()` - -ℹ Safe fix -8 8 | pass -9 9 | a = [line.lower() for line in f.readlines()] -10 10 | b = {line.upper() for line in f.readlines()} -11 |- c = {line.lower(): line.upper() for line in f.readlines()} - 11 |+ c = {line.lower(): line.upper() for line in f} -12 12 | -13 13 | with Path("FURB129.py").open() as f: -14 14 | for _line in f.readlines(): - -FURB129.py:14:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -13 | with Path("FURB129.py").open() as f: -14 | for _line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -15 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -11 11 | c = {line.lower(): line.upper() for line in f.readlines()} -12 12 | -13 13 | with Path("FURB129.py").open() as f: -14 |- for _line in f.readlines(): - 14 |+ for _line in f: -15 15 | pass -16 16 | -17 17 | for _line in open("FURB129.py").readlines(): - -FURB129.py:17:14: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -15 | pass -16 | -17 | for _line in open("FURB129.py").readlines(): - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB129 -18 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -14 14 | for _line in f.readlines(): -15 15 | pass -16 16 | -17 |-for _line in open("FURB129.py").readlines(): - 17 |+for _line in open("FURB129.py"): -18 18 | pass -19 19 | -20 20 | for _line in Path("FURB129.py").open().readlines(): - -FURB129.py:20:14: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -18 | pass -19 | -20 | for _line in Path("FURB129.py").open().readlines(): - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB129 -21 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -17 17 | for _line in open("FURB129.py").readlines(): -18 18 | pass -19 19 | -20 |-for _line in Path("FURB129.py").open().readlines(): - 20 |+for _line in Path("FURB129.py").open(): -21 21 | pass -22 22 | -23 23 | - -FURB129.py:26:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -24 | def func(): -25 | f = Path("FURB129.py").open() -26 | for _line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -27 | pass -28 | f.close() - | - = help: Remove `readlines()` - -ℹ Safe fix -23 23 | -24 24 | def func(): -25 25 | f = Path("FURB129.py").open() -26 |- for _line in f.readlines(): - 26 |+ for _line in f: -27 27 | pass -28 28 | f.close() -29 29 | - -FURB129.py:32:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -31 | def func(f: io.BytesIO): -32 | for _line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -33 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -29 29 | -30 30 | -31 31 | def func(f: io.BytesIO): -32 |- for _line in f.readlines(): - 32 |+ for _line in f: -33 33 | pass -34 34 | -35 35 | - -FURB129.py:38:22: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -36 | def func(): -37 | with (open("FURB129.py") as f, foo as bar): -38 | for _line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -39 | pass -40 | for _line in bar.readlines(): - | - = help: Remove `readlines()` - -ℹ Safe fix -35 35 | -36 36 | def func(): -37 37 | with (open("FURB129.py") as f, foo as bar): -38 |- for _line in f.readlines(): - 38 |+ for _line in f: -39 39 | pass -40 40 | for _line in bar.readlines(): -41 41 | pass - -FURB129.py:47:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -46 | with builtins.open("FURB129.py") as f: -47 | for line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -48 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -44 44 | import builtins -45 45 | -46 46 | with builtins.open("FURB129.py") as f: -47 |- for line in f.readlines(): - 47 |+ for line in f: -48 48 | pass -49 49 | -50 50 | - -FURB129.py:54:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -53 | with o("FURB129.py") as f: -54 | for line in f.readlines(): - | ^^^^^^^^^^^^^ FURB129 -55 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -51 51 | from builtins import open as o -52 52 | -53 53 | with o("FURB129.py") as f: -54 |- for line in f.readlines(): - 54 |+ for line in f: -55 55 | pass -56 56 | -57 57 | - -FURB129.py:93:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -91 | # https://github.com/astral-sh/ruff/issues/18231 -92 | with open("furb129.py") as f: -93 | for line in (f).readlines(): - | ^^^^^^^^^^^^^^^ FURB129 -94 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -90 90 | -91 91 | # https://github.com/astral-sh/ruff/issues/18231 -92 92 | with open("furb129.py") as f: -93 |- for line in (f).readlines(): - 93 |+ for line in (f): -94 94 | pass -95 95 | -96 96 | with open("furb129.py") as f: - -FURB129.py:97:23: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -96 | with open("furb129.py") as f: -97 | [line for line in (f).readlines()] - | ^^^^^^^^^^^^^^^ FURB129 - | - = help: Remove `readlines()` - -ℹ Safe fix -94 94 | pass -95 95 | -96 96 | with open("furb129.py") as f: -97 |- [line for line in (f).readlines()] - 97 |+ [line for line in (f)] -98 98 | -99 99 | -100 100 | with open("furb129.py") as f: - -FURB129.py:101:17: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -100 | with open("furb129.py") as f: -101 | for line in (((f))).readlines(): - | ^^^^^^^^^^^^^^^^^^^ FURB129 -102 | pass -103 | for line in(f).readlines(): - | - = help: Remove `readlines()` - -ℹ Safe fix -98 98 | -99 99 | -100 100 | with open("furb129.py") as f: -101 |- for line in (((f))).readlines(): - 101 |+ for line in (((f))): -102 102 | pass -103 103 | for line in(f).readlines(): -104 104 | pass - -FURB129.py:103:16: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -101 | for line in (((f))).readlines(): -102 | pass -103 | for line in(f).readlines(): - | ^^^^^^^^^^^^^^^ FURB129 -104 | pass - | - = help: Remove `readlines()` - -ℹ Safe fix -100 100 | with open("furb129.py") as f: -101 101 | for line in (((f))).readlines(): -102 102 | pass -103 |- for line in(f).readlines(): - 103 |+ for line in(f): -104 104 | pass -105 105 | -106 106 | # Test case for issue #17683 (missing space before keyword) - -FURB129.py:107:29: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly - | -106 | # Test case for issue #17683 (missing space before keyword) -107 | print([line for line in f.readlines()if True]) - | ^^^^^^^^^^^^^ FURB129 - | - = help: Remove `readlines()` - -ℹ Safe fix -104 104 | pass -105 105 | -106 106 | # Test case for issue #17683 (missing space before keyword) -107 |- print([line for line in f.readlines()if True]) - 107 |+ print([line for line in f if True]) From c063940d52725abf66d3cef0918becf38ba9c227 Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 6 Jun 2025 12:40:11 -0500 Subject: [PATCH 452/487] [`ruff`] Stabilize checking in presence of slices for `collection-literal-concatenation` (`RUF005`) (#18500) --- crates/ruff_linter/src/preview.rs | 7 ---- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- .../rules/collection_literal_concatenation.rs | 32 +++++++------------ ...ruff__tests__RUF005_RUF005_slices.py.snap} | 0 4 files changed, 12 insertions(+), 29 deletions(-) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF005_RUF005_slices.py.snap => ruff_linter__rules__ruff__tests__RUF005_RUF005_slices.py.snap} (100%) diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 8cc426d566703c..e88d683be6730e 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -104,13 +104,6 @@ pub(crate) const fn is_unicode_to_unicode_confusables_enabled(settings: &LinterS settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/17078 -pub(crate) const fn is_support_slices_in_literal_concatenation_enabled( - settings: &LinterSettings, -) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/11370 pub(crate) const fn is_undefined_export_in_dunder_init_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index a9aeb31ae2688a..2f6296f60c26fb 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -24,6 +24,7 @@ mod tests { use crate::{assert_messages, settings}; #[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005.py"))] + #[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005_slices.py"))] #[test_case(Rule::AsyncioDanglingTask, Path::new("RUF006.py"))] #[test_case(Rule::ZipInsteadOfPairwise, Path::new("RUF007.py"))] #[test_case(Rule::MutableDataclassDefault, Path::new("RUF008.py"))] @@ -486,7 +487,6 @@ mod tests { #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] #[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))] #[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))] - #[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005_slices.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index ed18e22a87c8f3..f8cd2b9c362382 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -4,7 +4,6 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; -use crate::preview::is_support_slices_in_literal_concatenation_enabled; use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does @@ -97,7 +96,7 @@ enum Type { } /// Recursively merge all the tuples and lists in the expression. -fn concatenate_expressions(expr: &Expr, should_support_slices: bool) -> Option<(Expr, Type)> { +fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> { let Expr::BinOp(ast::ExprBinOp { left, op: Operator::Add, @@ -110,22 +109,18 @@ fn concatenate_expressions(expr: &Expr, should_support_slices: bool) -> Option<( }; let new_left = match left.as_ref() { - Expr::BinOp(ast::ExprBinOp { .. }) => { - match concatenate_expressions(left, should_support_slices) { - Some((new_left, _)) => new_left, - None => *left.clone(), - } - } + Expr::BinOp(ast::ExprBinOp { .. }) => match concatenate_expressions(left) { + Some((new_left, _)) => new_left, + None => *left.clone(), + }, _ => *left.clone(), }; let new_right = match right.as_ref() { - Expr::BinOp(ast::ExprBinOp { .. }) => { - match concatenate_expressions(right, should_support_slices) { - Some((new_right, _)) => new_right, - None => *right.clone(), - } - } + Expr::BinOp(ast::ExprBinOp { .. }) => match concatenate_expressions(right) { + Some((new_right, _)) => new_right, + None => *right.clone(), + }, _ => *right.clone(), }; @@ -153,9 +148,7 @@ fn concatenate_expressions(expr: &Expr, should_support_slices: bool) -> Option<( make_splat_elts(splat_element, other_elements, splat_at_left) } // Subscripts are also considered safe-ish to splat if the indexer is a slice. - Expr::Subscript(ast::ExprSubscript { slice, .. }) - if should_support_slices && matches!(&**slice, Expr::Slice(_)) => - { + Expr::Subscript(ast::ExprSubscript { slice, .. }) if matches!(&**slice, Expr::Slice(_)) => { make_splat_elts(splat_element, other_elements, splat_at_left) } // If the splat element is itself a list/tuple, insert them in the other list/tuple. @@ -202,10 +195,7 @@ pub(crate) fn collection_literal_concatenation(checker: &Checker, expr: &Expr) { return; } - let should_support_slices = - is_support_slices_in_literal_concatenation_enabled(checker.settings); - - let Some((new_expr, type_)) = concatenate_expressions(expr, should_support_slices) else { + let Some((new_expr, type_)) = concatenate_expressions(expr) else { return; }; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF005_RUF005_slices.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF005_RUF005_slices.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF005_RUF005_slices.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF005_RUF005_slices.py.snap From 18a134ae1f48311b870d07a2a93bab93d7364be2 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:21:58 -0400 Subject: [PATCH 453/487] [ruff] Stabilize `class-with-mixed-type-vars` (RUF053) (#18512) This PR stabilizes the RUF053 rule by moving it from preview to stable status for the 0.12.0 release. ## Summary - **Rule**: RUF053 (`class-with-mixed-type-vars`) - **Purpose**: Detects classes that have both PEP 695 type parameter lists while also inheriting from `typing.Generic` - **Change**: Move from `RuleGroup::Preview` to `RuleGroup::Stable` in `codes.rs` and migrate preview tests to stable tests ## Verification Links - **Tests**: [ruff/mod.rs](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/ruff/mod.rs#L98) - Shows RUF053 moved from preview_rules to main rules test function - **Documentation**: https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars/ - Current documentation shows preview status that will be automatically updated --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- ...p => ruff_linter__rules__ruff__tests__RUF053_RUF053.py.snap} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF053_RUF053.py.snap => ruff_linter__rules__ruff__tests__RUF053_RUF053.py.snap} (100%) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 5ae727ce32d00a..c3ae3c02ee1c49 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1019,7 +1019,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "049") => (RuleGroup::Preview, rules::ruff::rules::DataclassEnum), (Ruff, "051") => (RuleGroup::Stable, rules::ruff::rules::IfKeyInDictDel), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), - (Ruff, "053") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars), + (Ruff, "053") => (RuleGroup::Stable, rules::ruff::rules::ClassWithMixedTypeVars), (Ruff, "054") => (RuleGroup::Preview, rules::ruff::rules::IndentedFormFeed), (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 2f6296f60c26fb..9f281970acfecc 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -95,6 +95,7 @@ mod tests { #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))] #[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))] #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))] + #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] #[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_0.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_1.py"))] @@ -484,7 +485,6 @@ mod tests { #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))] - #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] #[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))] #[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF053_RUF053.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF053_RUF053.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF053_RUF053.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF053_RUF053.py.snap From 02b5376a3c74bcaeeaf99433d31cf80ed1edd747 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:44:31 -0400 Subject: [PATCH 454/487] [refurb] Stabilize `fromisoformat-replace-z` (FURB162) (#18510) This PR stabilizes the FURB162 rule by moving it from preview to stable status for the 0.12.0 release. ## Summary - **Rule**: FURB162 (`fromisoformat-replace-z`) - **Purpose**: Detects unnecessary timezone replacement operations when calling `datetime.fromisoformat()` - **Change**: Move from `RuleGroup::Preview` to `RuleGroup::Stable` in `codes.rs` ## Verification Links - **Tests**: [refurb/mod.rs](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/refurb/mod.rs#L54) - Confirms FURB162 has only standard tests, no preview-specific test cases - **Documentation**: https://docs.astral.sh/ruff/rules/fromisoformat-replace-z/ - Current documentation shows preview status that will be automatically updated --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c3ae3c02ee1c49..c8330bfdc81611 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1130,7 +1130,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "156") => (RuleGroup::Preview, rules::refurb::rules::HardcodedStringCharset), (Refurb, "157") => (RuleGroup::Preview, rules::refurb::rules::VerboseDecimalConstructor), (Refurb, "161") => (RuleGroup::Stable, rules::refurb::rules::BitCount), - (Refurb, "162") => (RuleGroup::Preview, rules::refurb::rules::FromisoformatReplaceZ), + (Refurb, "162") => (RuleGroup::Stable, rules::refurb::rules::FromisoformatReplaceZ), (Refurb, "163") => (RuleGroup::Stable, rules::refurb::rules::RedundantLogBase), (Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat), (Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr), From 5cf2c40d1363a5ee556cd5982033a61c387dd47c Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 6 Jun 2025 16:46:43 -0500 Subject: [PATCH 455/487] [`flake8-simplify`] Stabilize further simplification to binary expressions in autofix for `if-else-block-instead-of-if-exp` (`SIM108`) (#18506) --- crates/ruff_linter/src/preview.rs | 5 - .../src/rules/flake8_simplify/mod.rs | 1 - .../rules/if_else_block_instead_of_if_exp.rs | 29 +- ...ke8_simplify__tests__SIM108_SIM108.py.snap | 18 +- ...ify__tests__preview__SIM108_SIM108.py.snap | 382 ------------------ 5 files changed, 22 insertions(+), 413 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM108_SIM108.py.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index e88d683be6730e..dcf496e1b61cab 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -73,11 +73,6 @@ pub(crate) const fn is_only_add_return_none_at_end_enabled(settings: &LinterSett settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/12796 -pub(crate) const fn is_simplify_ternary_to_binary_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/16719 pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs index 1041df4615c687..2e0bd735dcc17d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs @@ -58,7 +58,6 @@ mod tests { Ok(()) } - #[test_case(Rule::IfElseBlockInsteadOfIfExp, Path::new("SIM108.py"))] #[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index 44c89d37c3ba61..1166a272e32392 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -7,13 +7,14 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::fits; -use crate::preview::is_simplify_ternary_to_binary_enabled; use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does -/// Check for `if`-`else`-blocks that can be replaced with a ternary operator. -/// Moreover, in [preview], check if these ternary expressions can be -/// further simplified to binary expressions. +/// Check for `if`-`else`-blocks that can be replaced with a ternary +/// or binary operator. +/// +/// The lint is suppressed if the suggested replacement would exceed +/// the maximum line length configured in [pycodestyle.max-line-length]. /// /// ## Why is this bad? /// `if`-`else`-blocks that assign a value to a variable in both branches can @@ -33,7 +34,7 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// bar = x if foo else y /// ``` /// -/// Or, in [preview]: +/// Or: /// /// ```python /// if cond: @@ -57,8 +58,8 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// ## References /// - [Python documentation: Conditional expressions](https://docs.python.org/3/reference/expressions.html#conditional-expressions) /// -/// [preview]: https://docs.astral.sh/ruff/preview/ /// [code coverage]: https://github.com/nedbat/coveragepy/issues/509 +/// [pycodestyle.max-line-length]: https://docs.astral.sh/ruff/settings/#lint_pycodestyle_max-line-length #[derive(ViolationMetadata)] pub(crate) struct IfElseBlockInsteadOfIfExp { /// The ternary or binary expression to replace the `if`-`else`-block. @@ -184,16 +185,12 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: // // The match statement below implements the following // logic: - // - If `test == body_value` and preview enabled, replace with `target_var = test or else_value` - // - If `test == not body_value` and preview enabled, replace with `target_var = body_value and else_value` - // - If `not test == body_value` and preview enabled, replace with `target_var = body_value and else_value` + // - If `test == body_value`, replace with `target_var = test or else_value` + // - If `test == not body_value`, replace with `target_var = body_value and else_value` + // - If `not test == body_value`, replace with `target_var = body_value and else_value` // - Otherwise, replace with `target_var = body_value if test else else_value` - let (contents, assignment_kind) = match ( - is_simplify_ternary_to_binary_enabled(checker.settings), - test, - body_value, - ) { - (true, test_node, body_node) + let (contents, assignment_kind) = match (test, body_value) { + (test_node, body_node) if ComparableExpr::from(test_node) == ComparableExpr::from(body_node) && !contains_effect(test_node, |id| checker.semantic().has_builtin_binding(id)) => { @@ -201,7 +198,7 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: let binary = assignment_binary_or(target_var, body_value, else_value); (checker.generator().stmt(&binary), AssignmentKind::Binary) } - (true, test_node, body_node) + (test_node, body_node) if (test_node.as_unary_op_expr().is_some_and(|op_expr| { op_expr.op.is_not() && ComparableExpr::from(&op_expr.operand) == ComparableExpr::from(body_node) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap index a18c5fdac5b1be..b4d70317ad555e 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap @@ -118,7 +118,7 @@ SIM108.py:117:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of ` | = help: Replace `if`-`else`-block with `x = 3 if True else 5` -SIM108.py:141:1: SIM108 [*] Use ternary operator `z = cond if cond else other_cond` instead of `if`-`else`-block +SIM108.py:141:1: SIM108 [*] Use binary operator `z = cond or other_cond` instead of `if`-`else`-block | 139 | # SIM108 - should suggest 140 | # z = cond or other_cond @@ -130,7 +130,7 @@ SIM108.py:141:1: SIM108 [*] Use ternary operator `z = cond if cond else other_co 145 | 146 | # SIM108 - should suggest | - = help: Replace `if`-`else`-block with `z = cond if cond else other_cond` + = help: Replace `if`-`else`-block with `z = cond or other_cond` ℹ Unsafe fix 138 138 | @@ -140,12 +140,12 @@ SIM108.py:141:1: SIM108 [*] Use ternary operator `z = cond if cond else other_co 142 |- z = cond 143 |-else: 144 |- z = other_cond - 141 |+z = cond if cond else other_cond + 141 |+z = cond or other_cond 145 142 | 146 143 | # SIM108 - should suggest 147 144 | # z = cond and other_cond -SIM108.py:148:1: SIM108 [*] Use ternary operator `z = cond if not cond else other_cond` instead of `if`-`else`-block +SIM108.py:148:1: SIM108 [*] Use binary operator `z = cond and other_cond` instead of `if`-`else`-block | 146 | # SIM108 - should suggest 147 | # z = cond and other_cond @@ -157,7 +157,7 @@ SIM108.py:148:1: SIM108 [*] Use ternary operator `z = cond if not cond else othe 152 | 153 | # SIM108 - should suggest | - = help: Replace `if`-`else`-block with `z = cond if not cond else other_cond` + = help: Replace `if`-`else`-block with `z = cond and other_cond` ℹ Unsafe fix 145 145 | @@ -167,12 +167,12 @@ SIM108.py:148:1: SIM108 [*] Use ternary operator `z = cond if not cond else othe 149 |- z = cond 150 |-else: 151 |- z = other_cond - 148 |+z = cond if not cond else other_cond + 148 |+z = cond and other_cond 152 149 | 153 150 | # SIM108 - should suggest 154 151 | # z = not cond and other_cond -SIM108.py:155:1: SIM108 [*] Use ternary operator `z = not cond if cond else other_cond` instead of `if`-`else`-block +SIM108.py:155:1: SIM108 [*] Use binary operator `z = not cond and other_cond` instead of `if`-`else`-block | 153 | # SIM108 - should suggest 154 | # z = not cond and other_cond @@ -184,7 +184,7 @@ SIM108.py:155:1: SIM108 [*] Use ternary operator `z = not cond if cond else othe 159 | 160 | # SIM108 does not suggest | - = help: Replace `if`-`else`-block with `z = not cond if cond else other_cond` + = help: Replace `if`-`else`-block with `z = not cond and other_cond` ℹ Unsafe fix 152 152 | @@ -194,7 +194,7 @@ SIM108.py:155:1: SIM108 [*] Use ternary operator `z = not cond if cond else othe 156 |- z = not cond 157 |-else: 158 |- z = other_cond - 155 |+z = not cond if cond else other_cond + 155 |+z = not cond and other_cond 159 156 | 160 157 | # SIM108 does not suggest 161 158 | # a binary option in these cases, diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM108_SIM108.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM108_SIM108.py.snap deleted file mode 100644 index b4d70317ad555e..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM108_SIM108.py.snap +++ /dev/null @@ -1,382 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs ---- -SIM108.py:2:1: SIM108 [*] Use ternary operator `b = c if a else d` instead of `if`-`else`-block - | -1 | # SIM108 -2 | / if a: -3 | | b = c -4 | | else: -5 | | b = d - | |_________^ SIM108 -6 | -7 | # OK - | - = help: Replace `if`-`else`-block with `b = c if a else d` - -ℹ Unsafe fix -1 1 | # SIM108 -2 |-if a: -3 |- b = c -4 |-else: -5 |- b = d - 2 |+b = c if a else d -6 3 | -7 4 | # OK -8 5 | b = c if a else d - -SIM108.py:30:5: SIM108 [*] Use ternary operator `b = 1 if a else 2` instead of `if`-`else`-block - | -28 | pass -29 | else: -30 | / if a: -31 | | b = 1 -32 | | else: -33 | | b = 2 - | |_____________^ SIM108 - | - = help: Replace `if`-`else`-block with `b = 1 if a else 2` - -ℹ Unsafe fix -27 27 | if True: -28 28 | pass -29 29 | else: -30 |- if a: -31 |- b = 1 -32 |- else: -33 |- b = 2 - 30 |+ b = 1 if a else 2 -34 31 | -35 32 | -36 33 | import sys - -SIM108.py:58:1: SIM108 Use ternary operator `abc = x if x > 0 else -x` instead of `if`-`else`-block - | -57 | # SIM108 (without fix due to comments) -58 | / if x > 0: -59 | | # test test -60 | | abc = x -61 | | else: -62 | | # test test test -63 | | abc = -x - | |____________^ SIM108 - | - = help: Replace `if`-`else`-block with `abc = x if x > 0 else -x` - -SIM108.py:82:1: SIM108 [*] Use ternary operator `b = "cccccccccccccccccccccccccccccccccß" if a else "ddddddddddddddddddddddddddddddddd💣"` instead of `if`-`else`-block - | -81 | # SIM108 -82 | / if a: -83 | | b = "cccccccccccccccccccccccccccccccccß" -84 | | else: -85 | | b = "ddddddddddddddddddddddddddddddddd💣" - | |_____________________________________________^ SIM108 - | - = help: Replace `if`-`else`-block with `b = "cccccccccccccccccccccccccccccccccß" if a else "ddddddddddddddddddddddddddddddddd💣"` - -ℹ Unsafe fix -79 79 | -80 80 | -81 81 | # SIM108 -82 |-if a: -83 |- b = "cccccccccccccccccccccccccccccccccß" -84 |-else: -85 |- b = "ddddddddddddddddddddddddddddddddd💣" - 82 |+b = "cccccccccccccccccccccccccccccccccß" if a else "ddddddddddddddddddddddddddddddddd💣" -86 83 | -87 84 | -88 85 | # OK (too long) - -SIM108.py:105:1: SIM108 Use ternary operator `exitcode = 0 if True else 1` instead of `if`-`else`-block - | -104 | # SIM108 (without fix due to trailing comment) -105 | / if True: -106 | | exitcode = 0 -107 | | else: -108 | | exitcode = 1 # Trailing comment - | |________________^ SIM108 - | - = help: Replace `if`-`else`-block with `exitcode = 0 if True else 1` - -SIM108.py:112:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block - | -111 | # SIM108 -112 | / if True: x = 3 # Foo -113 | | else: x = 5 - | |___________^ SIM108 - | - = help: Replace `if`-`else`-block with `x = 3 if True else 5` - -SIM108.py:117:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block - | -116 | # SIM108 -117 | / if True: # Foo -118 | | x = 3 -119 | | else: -120 | | x = 5 - | |_________^ SIM108 - | - = help: Replace `if`-`else`-block with `x = 3 if True else 5` - -SIM108.py:141:1: SIM108 [*] Use binary operator `z = cond or other_cond` instead of `if`-`else`-block - | -139 | # SIM108 - should suggest -140 | # z = cond or other_cond -141 | / if cond: -142 | | z = cond -143 | | else: -144 | | z = other_cond - | |__________________^ SIM108 -145 | -146 | # SIM108 - should suggest - | - = help: Replace `if`-`else`-block with `z = cond or other_cond` - -ℹ Unsafe fix -138 138 | -139 139 | # SIM108 - should suggest -140 140 | # z = cond or other_cond -141 |-if cond: -142 |- z = cond -143 |-else: -144 |- z = other_cond - 141 |+z = cond or other_cond -145 142 | -146 143 | # SIM108 - should suggest -147 144 | # z = cond and other_cond - -SIM108.py:148:1: SIM108 [*] Use binary operator `z = cond and other_cond` instead of `if`-`else`-block - | -146 | # SIM108 - should suggest -147 | # z = cond and other_cond -148 | / if not cond: -149 | | z = cond -150 | | else: -151 | | z = other_cond - | |__________________^ SIM108 -152 | -153 | # SIM108 - should suggest - | - = help: Replace `if`-`else`-block with `z = cond and other_cond` - -ℹ Unsafe fix -145 145 | -146 146 | # SIM108 - should suggest -147 147 | # z = cond and other_cond -148 |-if not cond: -149 |- z = cond -150 |-else: -151 |- z = other_cond - 148 |+z = cond and other_cond -152 149 | -153 150 | # SIM108 - should suggest -154 151 | # z = not cond and other_cond - -SIM108.py:155:1: SIM108 [*] Use binary operator `z = not cond and other_cond` instead of `if`-`else`-block - | -153 | # SIM108 - should suggest -154 | # z = not cond and other_cond -155 | / if cond: -156 | | z = not cond -157 | | else: -158 | | z = other_cond - | |__________________^ SIM108 -159 | -160 | # SIM108 does not suggest - | - = help: Replace `if`-`else`-block with `z = not cond and other_cond` - -ℹ Unsafe fix -152 152 | -153 153 | # SIM108 - should suggest -154 154 | # z = not cond and other_cond -155 |-if cond: -156 |- z = not cond -157 |-else: -158 |- z = other_cond - 155 |+z = not cond and other_cond -159 156 | -160 157 | # SIM108 does not suggest -161 158 | # a binary option in these cases, - -SIM108.py:167:1: SIM108 [*] Use ternary operator `z = 1 if True else other` instead of `if`-`else`-block - | -165 | # (Of course, these specific expressions -166 | # should be simplified for other reasons...) -167 | / if True: -168 | | z = 1 -169 | | else: -170 | | z = other - | |_____________^ SIM108 -171 | -172 | if False: - | - = help: Replace `if`-`else`-block with `z = 1 if True else other` - -ℹ Unsafe fix -164 164 | # so, e.g. `True == 1`. -165 165 | # (Of course, these specific expressions -166 166 | # should be simplified for other reasons...) -167 |-if True: -168 |- z = 1 -169 |-else: -170 |- z = other - 167 |+z = 1 if True else other -171 168 | -172 169 | if False: -173 170 | z = 1 - -SIM108.py:172:1: SIM108 [*] Use ternary operator `z = 1 if False else other` instead of `if`-`else`-block - | -170 | z = other -171 | -172 | / if False: -173 | | z = 1 -174 | | else: -175 | | z = other - | |_____________^ SIM108 -176 | -177 | if 1: - | - = help: Replace `if`-`else`-block with `z = 1 if False else other` - -ℹ Unsafe fix -169 169 | else: -170 170 | z = other -171 171 | -172 |-if False: -173 |- z = 1 -174 |-else: -175 |- z = other - 172 |+z = 1 if False else other -176 173 | -177 174 | if 1: -178 175 | z = True - -SIM108.py:177:1: SIM108 [*] Use ternary operator `z = True if 1 else other` instead of `if`-`else`-block - | -175 | z = other -176 | -177 | / if 1: -178 | | z = True -179 | | else: -180 | | z = other - | |_____________^ SIM108 -181 | -182 | # SIM108 does not suggest a binary option in this - | - = help: Replace `if`-`else`-block with `z = True if 1 else other` - -ℹ Unsafe fix -174 174 | else: -175 175 | z = other -176 176 | -177 |-if 1: -178 |- z = True -179 |-else: -180 |- z = other - 177 |+z = True if 1 else other -181 178 | -182 179 | # SIM108 does not suggest a binary option in this -183 180 | # case, since we'd be reducing the number of calls - -SIM108.py:185:1: SIM108 [*] Use ternary operator `z = foo() if foo() else other` instead of `if`-`else`-block - | -183 | # case, since we'd be reducing the number of calls -184 | # from Two to one. -185 | / if foo(): -186 | | z = foo() -187 | | else: -188 | | z = other - | |_____________^ SIM108 -189 | -190 | # SIM108 does not suggest a binary option in this - | - = help: Replace `if`-`else`-block with `z = foo() if foo() else other` - -ℹ Unsafe fix -182 182 | # SIM108 does not suggest a binary option in this -183 183 | # case, since we'd be reducing the number of calls -184 184 | # from Two to one. -185 |-if foo(): -186 |- z = foo() -187 |-else: -188 |- z = other - 185 |+z = foo() if foo() else other -189 186 | -190 187 | # SIM108 does not suggest a binary option in this -191 188 | # case, since we'd be reducing the number of calls - -SIM108.py:193:1: SIM108 [*] Use ternary operator `z = not foo() if foo() else other` instead of `if`-`else`-block - | -191 | # case, since we'd be reducing the number of calls -192 | # from Two to one. -193 | / if foo(): -194 | | z = not foo() -195 | | else: -196 | | z = other - | |_____________^ SIM108 - | - = help: Replace `if`-`else`-block with `z = not foo() if foo() else other` - -ℹ Unsafe fix -190 190 | # SIM108 does not suggest a binary option in this -191 191 | # case, since we'd be reducing the number of calls -192 192 | # from Two to one. -193 |-if foo(): -194 |- z = not foo() -195 |-else: -196 |- z = other - 193 |+z = not foo() if foo() else other -197 194 | -198 195 | -199 196 | # These two cases double as tests for f-string quote preservation. The first - -SIM108.py:202:1: SIM108 [*] Use ternary operator `var = "str" if cond else f"{first}-{second}"` instead of `if`-`else`-block - | -200 | # f-string should preserve its double quotes, and the second should preserve -201 | # single quotes -202 | / if cond: -203 | | var = "str" -204 | | else: -205 | | var = f"{first}-{second}" - | |_____________________________^ SIM108 -206 | -207 | if cond: - | - = help: Replace `if`-`else`-block with `var = "str" if cond else f"{first}-{second}"` - -ℹ Unsafe fix -199 199 | # These two cases double as tests for f-string quote preservation. The first -200 200 | # f-string should preserve its double quotes, and the second should preserve -201 201 | # single quotes -202 |-if cond: -203 |- var = "str" -204 |-else: -205 |- var = f"{first}-{second}" - 202 |+var = "str" if cond else f"{first}-{second}" -206 203 | -207 204 | if cond: -208 205 | var = "str" - -SIM108.py:207:1: SIM108 [*] Use ternary operator `var = "str" if cond else f'{first}-{second}'` instead of `if`-`else`-block - | -205 | var = f"{first}-{second}" -206 | -207 | / if cond: -208 | | var = "str" -209 | | else: -210 | | var = f'{first}-{second}' - | |_____________________________^ SIM108 - | - = help: Replace `if`-`else`-block with `var = "str" if cond else f'{first}-{second}'` - -ℹ Unsafe fix -204 204 | else: -205 205 | var = f"{first}-{second}" -206 206 | -207 |-if cond: -208 |- var = "str" -209 |-else: -210 |- var = f'{first}-{second}' - 207 |+var = "str" if cond else f'{first}-{second}' From 9f2ae1f56896a2ccbd1a284424e01a5f4eb44a9b Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 6 Jun 2025 16:51:49 -0500 Subject: [PATCH 456/487] [`ruff`] Stabilize checking for file-level directives in `unused-noqa` (`RUF100`) (#18497) Note that the preview behavior was not documented (shame on us!) so the documentation was not modified. --------- Co-authored-by: Brent Westbrook --- crates/ruff_linter/src/checkers/noqa.rs | 30 ++++++++---------------- crates/ruff_linter/src/preview.rs | 5 ---- crates/ruff_linter/src/rules/ruff/mod.rs | 20 ++++++---------- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index d87e4343a96e82..2f830479325ede 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -12,7 +12,6 @@ use crate::fix::edits::delete_comment; use crate::noqa::{ Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping, }; -use crate::preview::is_check_file_level_directives_enabled; use crate::registry::{AsRule, Rule, RuleSet}; use crate::rule_redirects::get_redirect_target; use crate::rules::pygrep_hooks; @@ -112,25 +111,16 @@ pub(crate) fn check_noqa( && !exemption.includes(Rule::UnusedNOQA) && !per_file_ignores.contains(Rule::UnusedNOQA) { - let directives: Vec<_> = if is_check_file_level_directives_enabled(settings) { - noqa_directives - .lines() - .iter() - .map(|line| (&line.directive, &line.matches, false)) - .chain( - file_noqa_directives - .lines() - .iter() - .map(|line| (&line.parsed_file_exemption, &line.matches, true)), - ) - .collect() - } else { - noqa_directives - .lines() - .iter() - .map(|line| (&line.directive, &line.matches, false)) - .collect() - }; + let directives = noqa_directives + .lines() + .iter() + .map(|line| (&line.directive, &line.matches, false)) + .chain( + file_noqa_directives + .lines() + .iter() + .map(|line| (&line.parsed_file_exemption, &line.matches, true)), + ); for (directive, matches, is_file_level) in directives { match directive { Directive::All(directive) => { diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index dcf496e1b61cab..6a3c7e818b6081 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -109,11 +109,6 @@ pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) -> settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/17061 -pub(crate) const fn is_check_file_level_directives_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/18208 pub(crate) const fn is_multiple_with_statements_fix_safe_enabled( settings: &LinterSettings, diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 9f281970acfecc..ed197de51037e2 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -327,10 +327,7 @@ mod tests { fn ruff_noqa_filedirective_unused() -> Result<()> { let diagnostics = test_path( Path::new("ruff/RUF100_6.py"), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rules(vec![Rule::UnusedNOQA]) - }, + &settings::LinterSettings::for_rules(vec![Rule::UnusedNOQA]), )?; assert_messages!(diagnostics); Ok(()) @@ -340,15 +337,12 @@ mod tests { fn ruff_noqa_filedirective_unused_last_of_many() -> Result<()> { let diagnostics = test_path( Path::new("ruff/RUF100_7.py"), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rules(vec![ - Rule::UnusedNOQA, - Rule::FStringMissingPlaceholders, - Rule::LineTooLong, - Rule::UnusedVariable, - ]) - }, + &settings::LinterSettings::for_rules(vec![ + Rule::UnusedNOQA, + Rule::FStringMissingPlaceholders, + Rule::LineTooLong, + Rule::UnusedVariable, + ]), )?; assert_messages!(diagnostics); Ok(()) From bf53bc42564dcfb28c28d135e23455f78c7e1249 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sat, 7 Jun 2025 11:30:45 -0500 Subject: [PATCH 457/487] [syntax errors] Stabilize version-specific unsupported syntax errors (#18522) --- crates/ruff/tests/lint.rs | 11 ++++++----- crates/ruff_linter/src/linter.rs | 8 ++------ crates/ruff_linter/src/preview.rs | 5 ----- crates/ruff_wasm/tests/api.rs | 2 +- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index 49191f5099b46c..fa605516250032 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -5436,14 +5436,15 @@ match 2: print("it's one") "# ), - @r" - success: true - exit_code: 0 + @r###" + success: false + exit_code: 1 ----- stdout ----- - All checks passed! + test.py:2:1: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10) + Found 1 error. ----- stderr ----- - " + "### ); // syntax error on 3.9 with preview diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 2b31495b2a9ca0..422c1348c58dfb 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -30,7 +30,7 @@ use crate::fix::{FixResult, fix_file}; use crate::message::Message; use crate::noqa::add_noqa; use crate::package::PackageRoot; -use crate::preview::{is_py314_support_enabled, is_unsupported_syntax_enabled}; +use crate::preview::is_py314_support_enabled; use crate::registry::{AsRule, Rule, RuleSet}; #[cfg(any(feature = "test-rules", test))] use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule}; @@ -447,11 +447,7 @@ pub fn check_path( } } - let syntax_errors = if is_unsupported_syntax_enabled(settings) { - parsed.unsupported_syntax_errors() - } else { - &[] - }; + let syntax_errors = parsed.unsupported_syntax_errors(); diagnostics_to_messages( diagnostics, diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 6a3c7e818b6081..7ed2a0333303f8 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -13,11 +13,6 @@ pub(crate) const fn is_semantic_errors_enabled(settings: &LinterSettings) -> boo settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/16429 -pub(crate) const fn is_unsupported_syntax_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } diff --git a/crates/ruff_wasm/tests/api.rs b/crates/ruff_wasm/tests/api.rs index 32177e6bb6361a..036a435de0ec4b 100644 --- a/crates/ruff_wasm/tests/api.rs +++ b/crates/ruff_wasm/tests/api.rs @@ -65,7 +65,7 @@ fn syntax_error() { fn unsupported_syntax_error() { check!( "match 2:\n case 1: ...", - r#"{"preview": true, "target-version": "py39"}"#, + r#"{"target-version": "py39"}"#, [ExpandedMessage { code: None, message: "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)".to_string(), From c1610e2eaf8f380bf28e19393a627de1b7c4ef85 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 8 Jun 2025 07:53:18 -0500 Subject: [PATCH 458/487] [semantic errors] Stabilize semantic errors (#18523) --- crates/ruff_linter/src/checkers/ast/mod.rs | 6 ++---- crates/ruff_linter/src/preview.rs | 6 ------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 7a3addd4da6f78..4c3f490a65d4f1 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -65,7 +65,7 @@ use crate::docstrings::extraction::ExtractionTarget; use crate::importer::{ImportRequest, Importer, ResolutionError}; use crate::noqa::NoqaMapping; use crate::package::PackageRoot; -use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled}; +use crate::preview::is_undefined_export_in_dunder_init_enabled; use crate::registry::{AsRule, Rule}; use crate::rules::pyflakes::rules::{ LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction, @@ -663,9 +663,7 @@ impl SemanticSyntaxContext for Checker<'_> { | SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_) | SemanticSyntaxErrorKind::DuplicateParameter(_) | SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => { - if is_semantic_errors_enabled(self.settings) { - self.semantic_errors.borrow_mut().push(error); - } + self.semantic_errors.borrow_mut().push(error); } } } diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 7ed2a0333303f8..6520352c4a0e92 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -7,12 +7,6 @@ use crate::settings::LinterSettings; -// https://github.com/astral-sh/ruff/issues/17412 -// https://github.com/astral-sh/ruff/issues/11934 -pub(crate) const fn is_semantic_errors_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } From 7211660f8bc9722cec3cab5720da6e5ccf038ceb Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 8 Jun 2025 13:05:18 -0500 Subject: [PATCH 459/487] [`flake8-pyi`] Stabilize autofix for `future-annotations-in-stub` (`PYI044`) (#18518) --- crates/ruff_linter/src/preview.rs | 5 --- .../ruff_linter/src/rules/flake8_pyi/mod.rs | 19 ---------- .../rules/future_annotations_in_stub.rs | 28 +++++++------- ..._flake8_pyi__tests__PYI044_PYI044.pyi.snap | 20 +++++++++- ...yi__tests__preview__PYI044_PYI044.pyi.snap | 38 ------------------- 5 files changed, 31 insertions(+), 79 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 6520352c4a0e92..c7d0fed7fab267 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -52,11 +52,6 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/12676 -pub(crate) const fn is_fix_future_annotations_in_stub_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/11074 pub(crate) const fn is_only_add_return_none_at_end_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index ea59b73d1c96fe..605bf896b23026 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -11,7 +11,6 @@ mod tests { use crate::registry::Rule; use crate::rules::pep8_naming; - use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -172,22 +171,4 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } - - #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("flake8_pyi").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 398f4ad22e33db..20c70336eb6420 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -3,7 +3,7 @@ use ruff_python_ast::StmtImportFrom; use ruff_macros::{ViolationMetadata, derive_message_formats}; use crate::{Fix, FixAvailability, Violation}; -use crate::{checkers::ast::Checker, fix, preview::is_fix_future_annotations_in_stub_enabled}; +use crate::{checkers::ast::Checker, fix}; /// ## What it does /// Checks for the presence of the `from __future__ import annotations` import @@ -55,20 +55,18 @@ pub(crate) fn from_future_import(checker: &Checker, target: &StmtImportFrom) { let mut diagnostic = checker.report_diagnostic(FutureAnnotationsInStub, *range); - if is_fix_future_annotations_in_stub_enabled(checker.settings) { - let stmt = checker.semantic().current_statement(); + let stmt = checker.semantic().current_statement(); - diagnostic.try_set_fix(|| { - let edit = fix::edits::remove_unused_imports( - std::iter::once("annotations"), - stmt, - None, - checker.locator(), - checker.stylist(), - checker.indexer(), - )?; + diagnostic.try_set_fix(|| { + let edit = fix::edits::remove_unused_imports( + std::iter::once("annotations"), + stmt, + None, + checker.locator(), + checker.stylist(), + checker.indexer(), + )?; - Ok(Fix::safe_edit(edit)) - }); - } + Ok(Fix::safe_edit(edit)) + }); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap index bb018c27edb74b..4ae21763be4171 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI044_PYI044.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI044.pyi:2:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics +PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. @@ -10,7 +10,14 @@ PYI044.pyi:2:1: PYI044 `from __future__ import annotations` has no effect in stu | = help: Remove `from __future__ import annotations` -PYI044.pyi:3:1: PYI044 `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics +ℹ Safe fix +1 1 | # Bad import. +2 |-from __future__ import annotations # PYI044. +3 2 | from __future__ import annotations, with_statement # PYI044. +4 3 | +5 4 | # Good imports. + +PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics | 1 | # Bad import. 2 | from __future__ import annotations # PYI044. @@ -20,3 +27,12 @@ PYI044.pyi:3:1: PYI044 `from __future__ import annotations` has no effect in stu 5 | # Good imports. | = help: Remove `from __future__ import annotations` + +ℹ Safe fix +1 1 | # Bad import. +2 2 | from __future__ import annotations # PYI044. +3 |-from __future__ import annotations, with_statement # PYI044. + 3 |+from __future__ import with_statement # PYI044. +4 4 | +5 5 | # Good imports. +6 6 | from __future__ import with_statement diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap deleted file mode 100644 index 4ae21763be4171..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI044_PYI044.pyi.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- -PYI044.pyi:2:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics - | -1 | # Bad import. -2 | from __future__ import annotations # PYI044. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 -3 | from __future__ import annotations, with_statement # PYI044. - | - = help: Remove `from __future__ import annotations` - -ℹ Safe fix -1 1 | # Bad import. -2 |-from __future__ import annotations # PYI044. -3 2 | from __future__ import annotations, with_statement # PYI044. -4 3 | -5 4 | # Good imports. - -PYI044.pyi:3:1: PYI044 [*] `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics - | -1 | # Bad import. -2 | from __future__ import annotations # PYI044. -3 | from __future__ import annotations, with_statement # PYI044. - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI044 -4 | -5 | # Good imports. - | - = help: Remove `from __future__ import annotations` - -ℹ Safe fix -1 1 | # Bad import. -2 2 | from __future__ import annotations # PYI044. -3 |-from __future__ import annotations, with_statement # PYI044. - 3 |+from __future__ import with_statement # PYI044. -4 4 | -5 5 | # Good imports. -6 6 | from __future__ import with_statement From 00e9de8db90dec4fb6c8ab9e0ed3a942f16f66ae Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 8 Jun 2025 13:07:45 -0500 Subject: [PATCH 460/487] [`flake8-bandit`] Stabilize more trusted inputs in `subprocess-without-shell-equals-true` (`S603`) (#18521) --- crates/ruff_linter/src/preview.rs | 7 - .../src/rules/flake8_bandit/mod.rs | 1 - .../flake8_bandit/rules/shell_injection.rs | 5 +- ...s__flake8_bandit__tests__S603_S603.py.snap | 94 ------------- ..._bandit__tests__preview__S603_S603.py.snap | 125 ------------------ 5 files changed, 1 insertion(+), 231 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S603_S603.py.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index c7d0fed7fab267..3bc09c0ea22d0b 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -18,13 +18,6 @@ pub(crate) const fn is_full_path_match_source_strategy_enabled(settings: &Linter // Rule-specific behavior -// https://github.com/astral-sh/ruff/pull/17136 -pub(crate) const fn is_shell_injection_only_trusted_input_enabled( - settings: &LinterSettings, -) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/15541 pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs index 868367d6f76354..48f3fe251f6ce8 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs @@ -104,7 +104,6 @@ mod tests { #[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))] #[test_case(Rule::SuspiciousNonCryptographicRandomUsage, Path::new("S311.py"))] #[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))] - #[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs index e8bc2204f1e7d0..6a423ea5b8f175 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs @@ -7,7 +7,6 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::Violation; -use crate::preview::is_shell_injection_only_trusted_input_enabled; use crate::{ checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal, }; @@ -325,9 +324,7 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { } // S603 _ => { - if !is_trusted_input(arg) - || !is_shell_injection_only_trusted_input_enabled(checker.settings) - { + if !is_trusted_input(arg) { if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) { checker.report_diagnostic( SubprocessWithoutShellEqualsTrue, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S603_S603.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S603_S603.py.snap index 13003f8def1c37..2b8d7c974f8554 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S603_S603.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S603_S603.py.snap @@ -106,74 +106,6 @@ S603.py:21:1: S603 `subprocess` call: check for execution of untrusted input 23 | # Literals are fine, they're trusted. | -S603.py:24:1: S603 `subprocess` call: check for execution of untrusted input - | -23 | # Literals are fine, they're trusted. -24 | run("true") - | ^^^ S603 -25 | Popen(["true"]) -26 | Popen("true", shell=False) - | - -S603.py:25:1: S603 `subprocess` call: check for execution of untrusted input - | -23 | # Literals are fine, they're trusted. -24 | run("true") -25 | Popen(["true"]) - | ^^^^^ S603 -26 | Popen("true", shell=False) -27 | call("true", shell=False) - | - -S603.py:26:1: S603 `subprocess` call: check for execution of untrusted input - | -24 | run("true") -25 | Popen(["true"]) -26 | Popen("true", shell=False) - | ^^^^^ S603 -27 | call("true", shell=False) -28 | check_call("true", shell=False) - | - -S603.py:27:1: S603 `subprocess` call: check for execution of untrusted input - | -25 | Popen(["true"]) -26 | Popen("true", shell=False) -27 | call("true", shell=False) - | ^^^^ S603 -28 | check_call("true", shell=False) -29 | check_output("true", shell=False) - | - -S603.py:28:1: S603 `subprocess` call: check for execution of untrusted input - | -26 | Popen("true", shell=False) -27 | call("true", shell=False) -28 | check_call("true", shell=False) - | ^^^^^^^^^^ S603 -29 | check_output("true", shell=False) -30 | run("true", shell=False) - | - -S603.py:29:1: S603 `subprocess` call: check for execution of untrusted input - | -27 | call("true", shell=False) -28 | check_call("true", shell=False) -29 | check_output("true", shell=False) - | ^^^^^^^^^^^^ S603 -30 | run("true", shell=False) - | - -S603.py:30:1: S603 `subprocess` call: check for execution of untrusted input - | -28 | check_call("true", shell=False) -29 | check_output("true", shell=False) -30 | run("true", shell=False) - | ^^^ S603 -31 | -32 | # Not through assignments though. - | - S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input | 32 | # Not through assignments though. @@ -184,15 +116,6 @@ S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input 36 | # Instant named expressions are fine. | -S603.py:37:1: S603 `subprocess` call: check for execution of untrusted input - | -36 | # Instant named expressions are fine. -37 | run(c := "true") - | ^^^ S603 -38 | -39 | # But non-instant are not. - | - S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input | 39 | # But non-instant are not. @@ -200,20 +123,3 @@ S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input 41 | run(e) | ^^^ S603 | - -S603.py:46:1: S603 `subprocess` call: check for execution of untrusted input - | -44 | # https://github.com/astral-sh/ruff/issues/17798 -45 | # Tuple literals are trusted -46 | check_output(("literal", "cmd", "using", "tuple"), text=True) - | ^^^^^^^^^^^^ S603 -47 | Popen(("literal", "cmd", "using", "tuple")) - | - -S603.py:47:1: S603 `subprocess` call: check for execution of untrusted input - | -45 | # Tuple literals are trusted -46 | check_output(("literal", "cmd", "using", "tuple"), text=True) -47 | Popen(("literal", "cmd", "using", "tuple")) - | ^^^^^ S603 - | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S603_S603.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S603_S603.py.snap deleted file mode 100644 index 2b8d7c974f8554..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S603_S603.py.snap +++ /dev/null @@ -1,125 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs ---- -S603.py:5:1: S603 `subprocess` call: check for execution of untrusted input - | -3 | # Different Popen wrappers are checked. -4 | a = input() -5 | Popen(a, shell=False) - | ^^^^^ S603 -6 | call(a, shell=False) -7 | check_call(a, shell=False) - | - -S603.py:6:1: S603 `subprocess` call: check for execution of untrusted input - | -4 | a = input() -5 | Popen(a, shell=False) -6 | call(a, shell=False) - | ^^^^ S603 -7 | check_call(a, shell=False) -8 | check_output(a, shell=False) - | - -S603.py:7:1: S603 `subprocess` call: check for execution of untrusted input - | -5 | Popen(a, shell=False) -6 | call(a, shell=False) -7 | check_call(a, shell=False) - | ^^^^^^^^^^ S603 -8 | check_output(a, shell=False) -9 | run(a, shell=False) - | - -S603.py:8:1: S603 `subprocess` call: check for execution of untrusted input - | -6 | call(a, shell=False) -7 | check_call(a, shell=False) -8 | check_output(a, shell=False) - | ^^^^^^^^^^^^ S603 -9 | run(a, shell=False) - | - -S603.py:9:1: S603 `subprocess` call: check for execution of untrusted input - | - 7 | check_call(a, shell=False) - 8 | check_output(a, shell=False) - 9 | run(a, shell=False) - | ^^^ S603 -10 | -11 | # Falsey values are treated as false. - | - -S603.py:12:1: S603 `subprocess` call: check for execution of untrusted input - | -11 | # Falsey values are treated as false. -12 | Popen(a, shell=0) - | ^^^^^ S603 -13 | Popen(a, shell=[]) -14 | Popen(a, shell={}) - | - -S603.py:13:1: S603 `subprocess` call: check for execution of untrusted input - | -11 | # Falsey values are treated as false. -12 | Popen(a, shell=0) -13 | Popen(a, shell=[]) - | ^^^^^ S603 -14 | Popen(a, shell={}) -15 | Popen(a, shell=None) - | - -S603.py:14:1: S603 `subprocess` call: check for execution of untrusted input - | -12 | Popen(a, shell=0) -13 | Popen(a, shell=[]) -14 | Popen(a, shell={}) - | ^^^^^ S603 -15 | Popen(a, shell=None) - | - -S603.py:15:1: S603 `subprocess` call: check for execution of untrusted input - | -13 | Popen(a, shell=[]) -14 | Popen(a, shell={}) -15 | Popen(a, shell=None) - | ^^^^^ S603 -16 | -17 | # Unknown values are treated as falsey. - | - -S603.py:18:1: S603 `subprocess` call: check for execution of untrusted input - | -17 | # Unknown values are treated as falsey. -18 | Popen(a, shell=True if True else False) - | ^^^^^ S603 -19 | -20 | # No value is also caught. - | - -S603.py:21:1: S603 `subprocess` call: check for execution of untrusted input - | -20 | # No value is also caught. -21 | Popen(a) - | ^^^^^ S603 -22 | -23 | # Literals are fine, they're trusted. - | - -S603.py:34:1: S603 `subprocess` call: check for execution of untrusted input - | -32 | # Not through assignments though. -33 | cmd = ["true"] -34 | run(cmd) - | ^^^ S603 -35 | -36 | # Instant named expressions are fine. - | - -S603.py:41:1: S603 `subprocess` call: check for execution of untrusted input - | -39 | # But non-instant are not. -40 | (e := "echo") -41 | run(e) - | ^^^ S603 - | From 1f70ceba0cbb7d5b8351047eb2a120e532626300 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 8 Jun 2025 13:07:58 -0500 Subject: [PATCH 461/487] [`flake8-boolean-trap`] Stabilize lint `bool` suprtypes in `boolean-type-hint-positional-argument` (`FBT001`) (#18520) Feel free to complain about the rephrasing in the docs! --- crates/ruff_linter/src/preview.rs | 5 - .../src/rules/flake8_boolean_trap/mod.rs | 19 ---- .../boolean_type_hint_positional_argument.rs | 30 +---- ...e8_boolean_trap__tests__FBT001_FBT.py.snap | 15 ++- ...n_trap__tests__preview__FBT001_FBT.py.snap | 105 ------------------ 5 files changed, 18 insertions(+), 156 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__preview__FBT001_FBT.py.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 3bc09c0ea22d0b..32f4b06a5bc70d 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -23,11 +23,6 @@ pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSe settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/7501 -pub(crate) const fn is_bool_subtype_of_annotation_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/10759 pub(crate) const fn is_comprehension_with_min_max_sum_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs index 8268ca726b27db..3307cb949d869a 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs @@ -12,7 +12,6 @@ mod tests { use crate::registry::Rule; use crate::settings::LinterSettings; - use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -29,24 +28,6 @@ mod tests { Ok(()) } - #[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("flake8_boolean_trap").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } - #[test] fn extend_allowed_callable() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index 51bd830a66fd9e..24dff35099b1ff 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -7,12 +7,12 @@ use ruff_python_semantic::analyze::visibility; use crate::Violation; use crate::checkers::ast::Checker; -use crate::preview::is_bool_subtype_of_annotation_enabled; use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; /// ## What it does /// Checks for the use of boolean positional arguments in function definitions, -/// as determined by the presence of a `bool` type hint. +/// as determined by the presence of a type hint containing `bool` as an +/// evident subtype - e.g. `bool`, `bool | int`, `typing.Optional[bool]`, etc. /// /// ## Why is this bad? /// Calling a function with boolean positional arguments is confusing as the @@ -30,9 +30,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; /// Dunder methods that define operators are exempt from this rule, as are /// setters and `@override` definitions. /// -/// In [preview], this rule will also flag annotations that include boolean -/// variants, like `bool | int`. -/// /// ## Example /// /// ```python @@ -96,8 +93,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; /// ## References /// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls) /// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/) -/// -/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct BooleanTypeHintPositionalArgument; @@ -128,14 +123,8 @@ pub(crate) fn boolean_type_hint_positional_argument( let Some(annotation) = parameter.annotation() else { continue; }; - if is_bool_subtype_of_annotation_enabled(checker.settings) { - if !match_annotation_to_complex_bool(annotation, checker.semantic()) { - continue; - } - } else { - if !match_annotation_to_literal_bool(annotation) { - continue; - } + if !match_annotation_to_complex_bool(annotation, checker.semantic()) { + continue; } // Allow Boolean type hints in setters. @@ -161,17 +150,6 @@ pub(crate) fn boolean_type_hint_positional_argument( } } -/// Returns `true` if the annotation is a boolean type hint (e.g., `bool`). -fn match_annotation_to_literal_bool(annotation: &Expr) -> bool { - match annotation { - // Ex) `True` - Expr::Name(name) => &name.id == "bool", - // Ex) `"True"` - Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool", - _ => false, - } -} - /// Returns `true` if the annotation is a boolean type hint (e.g., `bool`), or a type hint that /// includes boolean as a variant (e.g., `bool | int`). fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT001_FBT.py.snap b/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT001_FBT.py.snap index ea925637b517e2..ee91fa49f86587 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT001_FBT.py.snap +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT001_FBT.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs -snapshot_kind: text --- FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition | @@ -89,3 +88,17 @@ FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition | ^^^^^ FBT001 91 | pass | + +FBT.py:100:10: FBT001 Boolean-typed positional argument in function definition + | +100 | def func(x: Union[list, Optional[int | str | float | bool]]): + | ^ FBT001 +101 | pass + | + +FBT.py:104:10: FBT001 Boolean-typed positional argument in function definition + | +104 | def func(x: bool | str): + | ^ FBT001 +105 | pass + | diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__preview__FBT001_FBT.py.snap b/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__preview__FBT001_FBT.py.snap deleted file mode 100644 index d3ab33ec5c3df4..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__preview__FBT001_FBT.py.snap +++ /dev/null @@ -1,105 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs -snapshot_kind: text ---- -FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition - | -2 | posonly_nohint, -3 | posonly_nonboolhint: int, -4 | posonly_boolhint: bool, - | ^^^^^^^^^^^^^^^^ FBT001 -5 | posonly_boolstrhint: "bool", -6 | /, - | - -FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition - | -3 | posonly_nonboolhint: int, -4 | posonly_boolhint: bool, -5 | posonly_boolstrhint: "bool", - | ^^^^^^^^^^^^^^^^^^^ FBT001 -6 | /, -7 | offset, - | - -FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition - | - 8 | posorkw_nonvalued_nohint, - 9 | posorkw_nonvalued_nonboolhint: int, -10 | posorkw_nonvalued_boolhint: bool, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -11 | posorkw_nonvalued_boolstrhint: "bool", -12 | posorkw_boolvalued_nohint=True, - | - -FBT.py:11:5: FBT001 Boolean-typed positional argument in function definition - | - 9 | posorkw_nonvalued_nonboolhint: int, -10 | posorkw_nonvalued_boolhint: bool, -11 | posorkw_nonvalued_boolstrhint: "bool", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -12 | posorkw_boolvalued_nohint=True, -13 | posorkw_boolvalued_nonboolhint: int = True, - | - -FBT.py:14:5: FBT001 Boolean-typed positional argument in function definition - | -12 | posorkw_boolvalued_nohint=True, -13 | posorkw_boolvalued_nonboolhint: int = True, -14 | posorkw_boolvalued_boolhint: bool = True, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -15 | posorkw_boolvalued_boolstrhint: "bool" = True, -16 | posorkw_nonboolvalued_nohint=1, - | - -FBT.py:15:5: FBT001 Boolean-typed positional argument in function definition - | -13 | posorkw_boolvalued_nonboolhint: int = True, -14 | posorkw_boolvalued_boolhint: bool = True, -15 | posorkw_boolvalued_boolstrhint: "bool" = True, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -16 | posorkw_nonboolvalued_nohint=1, -17 | posorkw_nonboolvalued_nonboolhint: int = 2, - | - -FBT.py:18:5: FBT001 Boolean-typed positional argument in function definition - | -16 | posorkw_nonboolvalued_nohint=1, -17 | posorkw_nonboolvalued_nonboolhint: int = 2, -18 | posorkw_nonboolvalued_boolhint: bool = 3, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4, -20 | *, - | - -FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition - | -17 | posorkw_nonboolvalued_nonboolhint: int = 2, -18 | posorkw_nonboolvalued_boolhint: bool = 3, -19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001 -20 | *, -21 | kwonly_nonvalued_nohint, - | - -FBT.py:90:19: FBT001 Boolean-typed positional argument in function definition - | -89 | # FBT001: Boolean positional arg in function definition -90 | def foo(self, value: bool) -> None: - | ^^^^^ FBT001 -91 | pass - | - -FBT.py:100:10: FBT001 Boolean-typed positional argument in function definition - | -100 | def func(x: Union[list, Optional[int | str | float | bool]]): - | ^ FBT001 -101 | pass - | - -FBT.py:104:10: FBT001 Boolean-typed positional argument in function definition - | -104 | def func(x: bool | str): - | ^ FBT001 -105 | pass - | From 620b84443b76bc2c4d6e1b3f44f74d5da67dc18d Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:54:20 -0400 Subject: [PATCH 462/487] [pyupgrade] Stabilize private-type-parameter (UP049) (#18515) ## Summary Stabilizes the UP049 rule (private-type-parameter) by moving it from Preview to Stable. UP049 detects and fixes the use of private type parameters (those with leading underscores) in PEP 695 generic classes and functions. ## Test plan - Verified that UP049 tests pass: `crates/ruff_linter/src/rules/pyupgrade/mod.rs` - Ran full test suite with `make test` - Confirmed that no test migration was needed as UP049 was already in the main `rules` test function ## Rule documentation https://docs.astral.sh/ruff/rules/private-type-parameter/ --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c8330bfdc81611..06320914a37bd7 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -552,7 +552,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "045") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional), (Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass), (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), - (Pyupgrade, "049") => (RuleGroup::Preview, rules::pyupgrade::rules::PrivateTypeParameter), + (Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter), (Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType), // pydocstyle From cd245d292e6c0f79af18107bf0b403924fa05b7b Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 12:44:14 -0500 Subject: [PATCH 463/487] Stabilize `verbose-decimal-constructor` (`FURB157`) (#18556) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 06320914a37bd7..b6f5ae32a71e00 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1128,7 +1128,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant), (Refurb, "154") => (RuleGroup::Preview, rules::refurb::rules::RepeatedGlobal), (Refurb, "156") => (RuleGroup::Preview, rules::refurb::rules::HardcodedStringCharset), - (Refurb, "157") => (RuleGroup::Preview, rules::refurb::rules::VerboseDecimalConstructor), + (Refurb, "157") => (RuleGroup::Stable, rules::refurb::rules::VerboseDecimalConstructor), (Refurb, "161") => (RuleGroup::Stable, rules::refurb::rules::BitCount), (Refurb, "162") => (RuleGroup::Stable, rules::refurb::rules::FromisoformatReplaceZ), (Refurb, "163") => (RuleGroup::Stable, rules::refurb::rules::RedundantLogBase), From c948be495ab273e9c091514d1e2a59e76b32a746 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 12:49:01 -0500 Subject: [PATCH 464/487] [`ruff`] Stabilize `invalid-formatter-suppression-comment` (`RUF028`) (#18555) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index b6f5ae32a71e00..fae5332df58f55 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -997,7 +997,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "024") => (RuleGroup::Stable, rules::ruff::rules::MutableFromkeysValue), (Ruff, "026") => (RuleGroup::Stable, rules::ruff::rules::DefaultFactoryKwarg), (Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax), - (Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment), + (Ruff, "028") => (RuleGroup::Stable, rules::ruff::rules::InvalidFormatterSuppressionComment), (Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync), (Ruff, "030") => (RuleGroup::Stable, rules::ruff::rules::AssertWithPrintMessage), (Ruff, "031") => (RuleGroup::Preview, rules::ruff::rules::IncorrectlyParenthesizedTupleInSubscript), From e559e21e93cd989e7736bc857ee110153b637ef0 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 12:51:12 -0500 Subject: [PATCH 465/487] [`pylint`] Stabilize `import-outside-top-level` (`PLC0415`) (#18554) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index fae5332df58f55..88e343edc229c3 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -201,7 +201,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0207") => (RuleGroup::Preview, rules::pylint::rules::MissingMaxsplitArg), (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), - (Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel), + (Pylint, "C0415") => (RuleGroup::Stable, rules::pylint::rules::ImportOutsideTopLevel), (Pylint, "C1802") => (RuleGroup::Stable, rules::pylint::rules::LenTest), (Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString), (Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName), From 72a4c3ed83e86e3484404437c4ef67ef74a9deb4 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 13:12:10 -0500 Subject: [PATCH 466/487] Stabilize `int-on-sliced-str` (`FURB166`) (#18558) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 88e343edc229c3..cd1d71010d52ec 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1133,7 +1133,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "162") => (RuleGroup::Stable, rules::refurb::rules::FromisoformatReplaceZ), (Refurb, "163") => (RuleGroup::Stable, rules::refurb::rules::RedundantLogBase), (Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat), - (Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr), + (Refurb, "166") => (RuleGroup::Stable, rules::refurb::rules::IntOnSlicedStr), (Refurb, "167") => (RuleGroup::Stable, rules::refurb::rules::RegexFlagAlias), (Refurb, "168") => (RuleGroup::Stable, rules::refurb::rules::IsinstanceTypeNone), (Refurb, "169") => (RuleGroup::Stable, rules::refurb::rules::TypeNoneComparison), From 2dafc5a8bd1f3d0cba1555e16855d0e926efaee2 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 13:47:34 -0500 Subject: [PATCH 467/487] Stabilize `eq-without-hash` (`PLW1641`) (#18561) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index cd1d71010d52ec..c9ab94b4aa1bb0 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -303,7 +303,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn), (Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck), (Pylint, "W1514") => (RuleGroup::Preview, rules::pylint::rules::UnspecifiedEncoding), - (Pylint, "W1641") => (RuleGroup::Preview, rules::pylint::rules::EqWithoutHash), + (Pylint, "W1641") => (RuleGroup::Stable, rules::pylint::rules::EqWithoutHash), (Pylint, "W2101") => (RuleGroup::Stable, rules::pylint::rules::UselessWithLock), (Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName), (Pylint, "W3201") => (RuleGroup::Preview, rules::pylint::rules::BadDunderMethodName), From 0a1c6cb70bb944f3adda739003e2ba05d91a5e27 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 14:00:39 -0500 Subject: [PATCH 468/487] Stabilize `unnecessary-round` (`RUF057`) (#18563) --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- ...p => ruff_linter__rules__ruff__tests__RUF057_RUF057.py.snap} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap => ruff_linter__rules__ruff__tests__RUF057_RUF057.py.snap} (100%) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c9ab94b4aa1bb0..ae513b022e6b4e 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1023,7 +1023,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "054") => (RuleGroup::Preview, rules::ruff::rules::IndentedFormFeed), (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback), - (Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound), + (Ruff, "057") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryRound), (Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip), (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable), (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index ed197de51037e2..ae885b13561019 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -97,6 +97,7 @@ mod tests { #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))] #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] #[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))] + #[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_0.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_1.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_2.py"))] @@ -475,7 +476,6 @@ mod tests { #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))] #[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))] - #[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))] #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))] #[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF057_RUF057.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF057_RUF057.py.snap From 7de8a0b429c52c575ebb7ea1754e2697eb9a8043 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 14:03:53 -0500 Subject: [PATCH 469/487] Stabilize `check-and-remove-from-set` (`FURB132`) (#18560) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index ae513b022e6b4e..2eb8cbbaf13937 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1119,7 +1119,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "122") => (RuleGroup::Preview, rules::refurb::rules::ForLoopWrites), (Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor), (Refurb, "131") => (RuleGroup::Preview, rules::refurb::rules::DeleteFullSlice), - (Refurb, "132") => (RuleGroup::Preview, rules::refurb::rules::CheckAndRemoveFromSet), + (Refurb, "132") => (RuleGroup::Stable, rules::refurb::rules::CheckAndRemoveFromSet), (Refurb, "136") => (RuleGroup::Stable, rules::refurb::rules::IfExprMinMax), (Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap), (Refurb, "142") => (RuleGroup::Preview, rules::refurb::rules::ForLoopSetMutations), From 2a1fed93277139535f36d4280b47d398d9d72fb5 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 14:05:14 -0500 Subject: [PATCH 470/487] Stabilize `nan-comparison` (`PLW0177`) (#18559) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 2eb8cbbaf13937..75827f1c6a9a62 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -281,7 +281,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { #[cfg(any(feature = "test-rules", test))] (Pylint, "W0101") => (RuleGroup::Preview, rules::pylint::rules::UnreachableCode), (Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda), - (Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison), + (Pylint, "W0177") => (RuleGroup::Stable, rules::pylint::rules::NanComparison), (Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop), (Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable), (Pylint, "W0128") => (RuleGroup::Stable, rules::pylint::rules::RedeclaredAssignedName), From 7efbf469ddd24fa6041681624b977c6f9c00cd4b Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 14:07:36 -0500 Subject: [PATCH 471/487] Stabilize `pytest-parameter-with-default-argument` (`PT028`) (#18566) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 75827f1c6a9a62..0264c9e2bff692 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -846,7 +846,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8PytestStyle, "025") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture), (Flake8PytestStyle, "026") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters), (Flake8PytestStyle, "027") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion), - (Flake8PytestStyle, "028") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument), + (Flake8PytestStyle, "028") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument), (Flake8PytestStyle, "029") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithoutWarning), (Flake8PytestStyle, "030") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsTooBroad), (Flake8PytestStyle, "031") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements), From 1278e3442a7c73e5692d80262e187f9520cd38cb Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 14:09:36 -0500 Subject: [PATCH 472/487] Stabilize `pytest-warns-with-multiple-statements` (`PT031`) (#18569) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 0264c9e2bff692..cf04d9515b9083 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -849,7 +849,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8PytestStyle, "028") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument), (Flake8PytestStyle, "029") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithoutWarning), (Flake8PytestStyle, "030") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsTooBroad), - (Flake8PytestStyle, "031") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements), + (Flake8PytestStyle, "031") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements), // flake8-pie (Flake8Pie, "790") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryPlaceholder), From ebd2a275593dc2f46b6786e11e5d0a154f138334 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 15:10:17 -0500 Subject: [PATCH 473/487] Stabilize `for-loop-writes` (`FURB122`) (#18565) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index cf04d9515b9083..c4474cd5076e79 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1116,7 +1116,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend), (Refurb, "116") => (RuleGroup::Preview, rules::refurb::rules::FStringNumberFormat), (Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator), - (Refurb, "122") => (RuleGroup::Preview, rules::refurb::rules::ForLoopWrites), + (Refurb, "122") => (RuleGroup::Stable, rules::refurb::rules::ForLoopWrites), (Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor), (Refurb, "131") => (RuleGroup::Preview, rules::refurb::rules::DeleteFullSlice), (Refurb, "132") => (RuleGroup::Stable, rules::refurb::rules::CheckAndRemoveFromSet), From 56f2aaaebca5281344dd583fdac20df6cbe0f0e1 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 16:16:10 -0500 Subject: [PATCH 474/487] Stabilize `pytest-warns-too-broad` (`PT030`) (#18568) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index c4474cd5076e79..0581769073c5bc 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -848,7 +848,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8PytestStyle, "027") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion), (Flake8PytestStyle, "028") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestParameterWithDefaultArgument), (Flake8PytestStyle, "029") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsWithoutWarning), - (Flake8PytestStyle, "030") => (RuleGroup::Preview, rules::flake8_pytest_style::rules::PytestWarnsTooBroad), + (Flake8PytestStyle, "030") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestWarnsTooBroad), (Flake8PytestStyle, "031") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestWarnsWithMultipleStatements), // flake8-pie From de4fc5b171d8b4a9dfd44ef9b551f1257661ab60 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 9 Jun 2025 17:25:14 -0500 Subject: [PATCH 475/487] [`pyupgrade`] Stabilize `non-pep604-annotation-optional` (`UP045`) and preview behavior for `non-pep604-annotation-union` (`UP007`) (#18505) --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/preview.rs | 5 -- crates/ruff_linter/src/rules/pyupgrade/mod.rs | 17 +---- .../pyupgrade/rules/use_pep604_annotation.rs | 11 +--- ...er__rules__pyupgrade__tests__UP045.py.snap | 66 +++++++++---------- ...tests__future_annotations_pep_604_p37.snap | 6 +- ...sts__future_annotations_pep_604_py310.snap | 6 +- ...ules__pyupgrade__tests__up007_preview.snap | 5 -- 8 files changed, 45 insertions(+), 73 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__up007_preview.snap diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 0581769073c5bc..ebe281d0f4ff2d 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -549,7 +549,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum), (Pyupgrade, "043") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryDefaultTypeArgs), (Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack), - (Pyupgrade, "045") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional), + (Pyupgrade, "045") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604AnnotationOptional), (Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass), (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), (Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter), diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 32f4b06a5bc70d..023e918c55eafe 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -61,11 +61,6 @@ pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSe settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/15313 -pub(crate) const fn is_defer_optional_to_up045_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/8473 pub(crate) const fn is_unicode_to_unicode_confusables_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 3f0388787b7a3b..0ae7cf1b92ee7b 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -15,7 +15,7 @@ mod tests { use crate::registry::Rule; use crate::rules::pyupgrade; - use crate::settings::types::PreviewMode; + use crate::test::test_path; use crate::{assert_messages, settings}; @@ -43,7 +43,7 @@ mod tests { #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_2.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_3.py"))] #[test_case(Rule::NonPEP604AnnotationUnion, Path::new("UP007.py"))] - #[test_case(Rule::NonPEP604AnnotationUnion, Path::new("UP045.py"))] + #[test_case(Rule::NonPEP604AnnotationOptional, Path::new("UP045.py"))] #[test_case(Rule::NonPEP604Isinstance, Path::new("UP038.py"))] #[test_case(Rule::OSErrorAlias, Path::new("UP024_0.py"))] #[test_case(Rule::OSErrorAlias, Path::new("UP024_1.py"))] @@ -122,19 +122,6 @@ mod tests { Ok(()) } - #[test] - fn up007_preview() -> Result<()> { - let diagnostics = test_path( - Path::new("pyupgrade/UP045.py"), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(Rule::NonPEP604AnnotationUnion) - }, - )?; - assert_messages!(diagnostics); - Ok(()) - } - #[test] fn async_timeout_error_alias_not_applied_py310() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index bc49d8df08de5b..4cf39797754279 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -8,7 +8,6 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::fix::edits::pad; -use crate::preview::is_defer_optional_to_up045_enabled; use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does @@ -39,8 +38,7 @@ use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// foo: int | str = 1 /// ``` /// -/// ## Preview -/// In preview mode, this rule only checks for usages of `typing.Union`, +/// Note that this rule only checks for usages of `typing.Union`, /// while `UP045` checks for `typing.Optional`. /// /// ## Fix safety @@ -149,11 +147,8 @@ pub(crate) fn non_pep604_annotation( match operator { Pep604Operator::Optional => { - let guard = if is_defer_optional_to_up045_enabled(checker.settings) { - checker.report_diagnostic_if_enabled(NonPEP604AnnotationOptional, expr.range()) - } else { - checker.report_diagnostic_if_enabled(NonPEP604AnnotationUnion, expr.range()) - }; + let guard = + checker.report_diagnostic_if_enabled(NonPEP604AnnotationOptional, expr.range()); let Some(mut diagnostic) = guard else { return; diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap index 9e78420b083c09..bc50bc86883d83 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP045.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -UP045.py:5:10: UP007 [*] Use `X | Y` for type annotations +UP045.py:5:10: UP045 [*] Use `X | None` for type annotations | 5 | def f(x: Optional[str]) -> None: - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 6 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 2 2 | from typing import Optional @@ -19,13 +19,13 @@ UP045.py:5:10: UP007 [*] Use `X | Y` for type annotations 7 7 | 8 8 | -UP045.py:9:10: UP007 [*] Use `X | Y` for type annotations +UP045.py:9:10: UP045 [*] Use `X | None` for type annotations | 9 | def f(x: typing.Optional[str]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^^^^^^^^ UP045 10 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 6 6 | ... @@ -37,14 +37,14 @@ UP045.py:9:10: UP007 [*] Use `X | Y` for type annotations 11 11 | 12 12 | -UP045.py:14:8: UP007 [*] Use `X | Y` for type annotations +UP045.py:14:8: UP045 [*] Use `X | None` for type annotations | 13 | def f() -> None: 14 | x: Optional[str] - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 15 | x = Optional[str] | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 11 11 | @@ -56,22 +56,22 @@ UP045.py:14:8: UP007 [*] Use `X | Y` for type annotations 16 16 | 17 17 | -UP045.py:15:9: UP007 Use `X | Y` for type annotations +UP045.py:15:9: UP045 Use `X | None` for type annotations | 13 | def f() -> None: 14 | x: Optional[str] 15 | x = Optional[str] - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 | - = help: Convert to `X | Y` + = help: Convert to `X | None` -UP045.py:18:15: UP007 [*] Use `X | Y` for type annotations +UP045.py:18:15: UP045 [*] Use `X | None` for type annotations | 18 | def f(x: list[Optional[int]]) -> None: - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 19 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 15 15 | x = Optional[str] @@ -83,31 +83,31 @@ UP045.py:18:15: UP007 [*] Use `X | Y` for type annotations 20 20 | 21 21 | -UP045.py:22:10: UP007 Use `X | Y` for type annotations +UP045.py:22:10: UP045 Use `X | None` for type annotations | 22 | def f(x: Optional[int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^^^^^^^^^ UP045 23 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` -UP045.py:26:10: UP007 Use `X | Y` for type annotations +UP045.py:26:10: UP045 Use `X | None` for type annotations | 26 | def f(x: Optional[str, int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP045 27 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` -UP045.py:30:10: UP007 Use `X | Y` for type annotations +UP045.py:30:10: UP045 Use `X | None` for type annotations | 30 | def f(x: Optional[int, float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^^^^^^^^ UP045 31 | ... | - = help: Convert to `X | Y` + = help: Convert to `X | None` -UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations +UP045.py:36:28: UP045 [*] Use `X | None` for type annotations | 34 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 35 | class ServiceRefOrValue: @@ -116,9 +116,9 @@ UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations 37 | | list[ServiceSpecificationRef] 38 | | | list[ServiceSpecification] 39 | | ] = None - | |_____^ UP007 + | |_____^ UP045 | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 33 33 | @@ -133,14 +133,14 @@ UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations 41 38 | 42 39 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -UP045.py:44:28: UP007 [*] Use `X | Y` for type annotations +UP045.py:44:28: UP045 [*] Use `X | None` for type annotations | 42 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 43 | class ServiceRefOrValue: 44 | service_specification: Optional[str]is not True = None - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 41 41 | @@ -152,11 +152,11 @@ UP045.py:44:28: UP007 [*] Use `X | Y` for type annotations 46 46 | 47 47 | # Test for: https://github.com/astral-sh/ruff/issues/18508 -UP045.py:49:6: UP007 Use `X | Y` for type annotations +UP045.py:49:6: UP045 Use `X | None` for type annotations | 47 | # Test for: https://github.com/astral-sh/ruff/issues/18508 48 | # Optional[None] should not be offered a fix 49 | foo: Optional[None] = None - | ^^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^^ UP045 | - = help: Convert to `X | Y` + = help: Convert to `X | None` diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap index 525b392e7c1560..29df4b48bb879a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations +future_annotations.py:40:4: UP045 [*] Use `X | None` for type annotations | 40 | x: Optional[int] = None - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 41 | 42 | MyList: TypeAlias = Union[List[int], List[str]] | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Unsafe fix 37 37 | return y diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap index f88a3b32fb3273..163b0a8cb56066 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap @@ -1,14 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations +future_annotations.py:40:4: UP045 [*] Use `X | None` for type annotations | 40 | x: Optional[int] = None - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP045 41 | 42 | MyList: TypeAlias = Union[List[int], List[str]] | - = help: Convert to `X | Y` + = help: Convert to `X | None` ℹ Safe fix 37 37 | return y diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__up007_preview.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__up007_preview.snap deleted file mode 100644 index c996a51d3844d8..00000000000000 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__up007_preview.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pyupgrade/mod.rs -snapshot_kind: text ---- - From ad9ae4e2b6b1a832eef4d61b846d5964c0f6a9d6 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:56:27 -0400 Subject: [PATCH 476/487] [`flake8-logging`] Stabilize `exc-info-outside-except-handler` (`LOG014`) (#18517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Stabilizes LOG014 (exc-info-outside-except-handler) rule by changing it from Preview to Stable ## Test plan - ✅ Rule is already tested in main test function, no migration needed - ✅ `make check` passes - ✅ `make test` passes ## Rule Documentation - [Test file](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/flake8_logging/mod.rs#L22-L23) - [Rule documentation](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler/) --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index ebe281d0f4ff2d..60dcfb38c613c7 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1152,7 +1152,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Logging, "004") => (RuleGroup::Preview, rules::flake8_logging::rules::LogExceptionOutsideExceptHandler), (Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo), (Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn), - (Flake8Logging, "014") => (RuleGroup::Preview, rules::flake8_logging::rules::ExcInfoOutsideExceptHandler), + (Flake8Logging, "014") => (RuleGroup::Stable, rules::flake8_logging::rules::ExcInfoOutsideExceptHandler), (Flake8Logging, "015") => (RuleGroup::Stable, rules::flake8_logging::rules::RootLoggerCall), _ => return None, From 72c8dc006f4825f26750110483cb262e61ebbb91 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:43:25 -0400 Subject: [PATCH 477/487] Stabilize `starmap-zip` (`RUF058`) (#18525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Stabilizes RUF058 (starmap-zip) rule by changing it from Preview to Stable - Migrates test cases from preview_rules to main rules function - Updates snapshots accordingly and removes old preview snapshots ## Test plan - ✅ Migrated tests from preview to main test function - ✅ `make check` passes - ✅ `make test` passes - ✅ `make citest` passes (no leftover snapshots) ## Rule Documentation - [Test file](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/ruff/mod.rs#L103-L104) - [Rule documentation](https://docs.astral.sh/ruff/rules/starmap-zip/) --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/ruff/mod.rs | 4 ++-- ... ruff_linter__rules__ruff__tests__RUF058_RUF058_0.py.snap} | 0 ... ruff_linter__rules__ruff__tests__RUF058_RUF058_1.py.snap} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_0.py.snap => ruff_linter__rules__ruff__tests__RUF058_RUF058_0.py.snap} (100%) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_1.py.snap => ruff_linter__rules__ruff__tests__RUF058_RUF058_1.py.snap} (100%) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 60dcfb38c613c7..e77971ddd5f735 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1024,7 +1024,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback), (Ruff, "057") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryRound), - (Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip), + (Ruff, "058") => (RuleGroup::Stable, rules::ruff::rules::StarmapZip), (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable), (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection), (Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index ae885b13561019..81dab73fc48802 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -98,6 +98,8 @@ mod tests { #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] #[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))] #[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))] + #[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))] + #[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_0.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_1.py"))] #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_2.py"))] @@ -477,8 +479,6 @@ mod tests { #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))] #[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))] #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] - #[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))] - #[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))] #[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))] #[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_0.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF058_RUF058_0.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_0.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF058_RUF058_0.py.snap diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_1.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF058_RUF058_1.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF058_RUF058_1.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF058_RUF058_1.py.snap From 7072cf69b447ed56856647664741e3ba7555ae9a Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 11 Jun 2025 08:50:46 +0200 Subject: [PATCH 478/487] Remove rust-toolchain.toml from sdist (#17925) Closes https://github.com/astral-sh/ruff/issues/17909 --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2957b55d808fb8..680b857edfe723 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,9 +53,6 @@ exclude = [ "crates/ruff_linter/resources/test/fixtures/**/*", "crates/ruff_linter/src/rules/*/snapshots/**/*" ] -include = [ - "rust-toolchain.toml" -] [tool.ruff] target-version = "py38" From ce176b1acf6f31a0c393266ae70f7eb0dd933314 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 12 Jun 2025 08:18:41 -0500 Subject: [PATCH 479/487] Stabilize `unnecessary-dict-index-lookup` (`PLR1733`) (#18571) Co-authored-by: Brent Westbrook --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/ruff/rules/unused_async.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index e77971ddd5f735..2ec717a4fa4598 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -270,7 +270,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias), (Pylint, "R1730") => (RuleGroup::Stable, rules::pylint::rules::IfStmtMinMax), (Pylint, "R1716") => (RuleGroup::Stable, rules::pylint::rules::BooleanChainedComparison), - (Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup), + (Pylint, "R1733") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDictIndexLookup), (Pylint, "R1736") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryListIndexLookup), (Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison), (Pylint, "R2044") => (RuleGroup::Stable, rules::pylint::rules::EmptyComment), diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs index a3de93719e3a6a..b7c55dc0b0e439 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs @@ -7,6 +7,7 @@ use ruff_python_semantic::analyze::function_type::is_stub; use crate::Violation; use crate::checkers::ast::Checker; + use crate::rules::fastapi::rules::is_fastapi_route; /// ## What it does From b01195b166e60a4f54175ad2f659c69f10381469 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 12 Jun 2025 08:18:56 -0500 Subject: [PATCH 480/487] Stabilize `dataclass-enum` (`RUF049`) (#18570) Co-authored-by: Brent Westbrook --- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/ruff/mod.rs | 2 +- ...p => ruff_linter__rules__ruff__tests__RUF049_RUF049.py.snap} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/ruff_linter/src/rules/ruff/snapshots/{ruff_linter__rules__ruff__tests__preview__RUF049_RUF049.py.snap => ruff_linter__rules__ruff__tests__RUF049_RUF049.py.snap} (100%) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 2ec717a4fa4598..92a843aa4138a2 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1016,7 +1016,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "046") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryCastToInt), (Ruff, "047") => (RuleGroup::Preview, rules::ruff::rules::NeedlessElse), (Ruff, "048") => (RuleGroup::Stable, rules::ruff::rules::MapIntVersionParsing), - (Ruff, "049") => (RuleGroup::Preview, rules::ruff::rules::DataclassEnum), + (Ruff, "049") => (RuleGroup::Stable, rules::ruff::rules::DataclassEnum), (Ruff, "051") => (RuleGroup::Stable, rules::ruff::rules::IfKeyInDictDel), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), (Ruff, "053") => (RuleGroup::Stable, rules::ruff::rules::ClassWithMixedTypeVars), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 81dab73fc48802..5873a8d1ed9cab 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -93,6 +93,7 @@ mod tests { #[test_case(Rule::NeedlessElse, Path::new("RUF047_try.py"))] #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))] #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))] + #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] #[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))] #[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))] #[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))] @@ -478,7 +479,6 @@ mod tests { #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))] #[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))] - #[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))] #[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))] #[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF049_RUF049.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF049_RUF049.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF049_RUF049.py.snap rename to crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF049_RUF049.py.snap From 34dc8e0531186400a66fbae9e623d4d320a774e8 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:06:22 -0400 Subject: [PATCH 481/487] [`flake8-bandit`] Remove `suspicious-xmle-tree-usage` (`S320`) (#18617) Summary -- Closes #13707. The rule was deprecated in 0.10 (#16680) and slated for removal in either this or the next release. Test Plan -- N/a --- crates/ruff_linter/src/codes.rs | 2 +- .../src/rules/flake8_bandit/rules/suspicious_function_call.rs | 4 ++-- ruff.schema.json | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 92a843aa4138a2..d2de55efba0ff6 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -660,7 +660,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bandit, "317") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLSaxUsage), (Flake8Bandit, "318") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLMiniDOMUsage), (Flake8Bandit, "319") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousXMLPullDOMUsage), - (Flake8Bandit, "320") => (RuleGroup::Deprecated, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage), + (Flake8Bandit, "320") => (RuleGroup::Removed, rules::flake8_bandit::rules::SuspiciousXMLETreeUsage), (Flake8Bandit, "321") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousFTPLibUsage), (Flake8Bandit, "323") => (RuleGroup::Stable, rules::flake8_bandit::rules::SuspiciousUnverifiedContextUsage), (Flake8Bandit, "324") => (RuleGroup::Stable, rules::flake8_bandit::rules::HashlibInsecureHashFunction), diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index f558707fec23c1..5fcc3e6ed5367b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -789,9 +789,9 @@ impl Violation for SuspiciousXMLPullDOMUsage { } } -/// ## Deprecation +/// ## Removed /// -/// This rule was deprecated as the `lxml` library has been modified to address +/// This rule was removed as the `lxml` library has been modified to address /// known vulnerabilities and unsafe defaults. As such, the `defusedxml` /// library is no longer necessary, `defusedxml` has [deprecated] its `lxml` /// module. diff --git a/ruff.schema.json b/ruff.schema.json index 4a186a05683497..e8c6c30e853bdf 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -4089,7 +4089,6 @@ "S318", "S319", "S32", - "S320", "S321", "S323", "S324", From 33c8c7569d7188abf62fd70f01b4e65783debcc9 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:26:15 -0400 Subject: [PATCH 482/487] [`pandas-vet`] Deprecate `pandas-df-variable-name` (`PD901`) (#18618) Summary -- Deprecates PD901 as part of #7710. I don't feel particularly strongly about this one, though I have certainly used `df` as a dataframe name in the past, just going through the open issues in the 0.12 milestone. Test Plan -- N/a --- crates/ruff_linter/src/codes.rs | 2 +- .../src/rules/pandas_vet/rules/assignment_to_df.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index d2de55efba0ff6..732c2aee11fc6b 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -752,7 +752,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (PandasVet, "013") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfDotStack), (PandasVet, "015") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfPdMerge), (PandasVet, "101") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck), - (PandasVet, "901") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasDfVariableName), + (PandasVet, "901") => (RuleGroup::Deprecated, rules::pandas_vet::rules::PandasDfVariableName), // flake8-errmsg (Flake8ErrMsg, "101") => (RuleGroup::Stable, rules::flake8_errmsg::rules::RawStringInException), diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs index b24fd8f0bbd5cf..3c0617b7087ef0 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/assignment_to_df.rs @@ -4,6 +4,10 @@ use ruff_text_size::Ranged; use crate::{Violation, checkers::ast::Checker}; +/// ## Deprecated +/// +/// This rule has been deprecated as it's highly opinionated and overly strict in most cases. +/// /// ## What it does /// Checks for assignments to the variable `df`. /// From 6754e94abcc9148e4443bf491cca40752ab354bc Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:27:46 -0400 Subject: [PATCH 483/487] [`pyupgrade`] Stabilize `non-pep695-generic-class` (`UP046`) (#18519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Stabilizes UP046 (non-pep695-generic-class) rule by changing it from Preview to Stable ## Test plan - ✅ Rule is already tested in main test function, no migration needed - ✅ `make check` passes - ✅ `make test` passes ## Rule Documentation - [Test file](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/pyupgrade/mod.rs#L109-L110) - [Rule documentation](https://docs.astral.sh/ruff/rules/non-pep695-generic-class/) --- crates/ruff_linter/src/codes.rs | 2 +- .../rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 732c2aee11fc6b..202d56e6fa80e5 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -550,7 +550,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "043") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryDefaultTypeArgs), (Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack), (Pyupgrade, "045") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604AnnotationOptional), - (Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass), + (Pyupgrade, "046") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericClass), (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), (Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter), (Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs index 56137e4a93495f..f7b4db71209a43 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs @@ -32,7 +32,7 @@ use super::{ /// fix. /// /// Not all type checkers fully support PEP 695 yet, so even valid fixes suggested by this rule may -/// cause type checking to fail. +/// cause type checking to [fail]. /// /// ## Fix safety /// @@ -84,6 +84,7 @@ use super::{ /// [PYI059]: https://docs.astral.sh/ruff/rules/generic-not-last-base-class/ /// [UP047]: https://docs.astral.sh/ruff/rules/non-pep695-generic-function/ /// [UP049]: https://docs.astral.sh/ruff/rules/private-type-parameter/ +/// [fail]: https://github.com/python/mypy/issues/18507 #[derive(ViolationMetadata)] pub(crate) struct NonPEP695GenericClass { name: String, From 50f84808bcd30e28a698b6fca58a2fa76113887c Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:39:14 -0400 Subject: [PATCH 484/487] [`pyupgrade`] Stabilize `non-pep695-generic-function` (`UP047`) (#18524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Stabilizes UP047 (non-pep695-generic-function) rule by changing it from Preview to Stable ## Test plan - ✅ Rule is already tested in main test function, no migration needed - ✅ `make check` passes - ✅ `make test` passes ## Rule Documentation - [Test file](https://github.com/astral-sh/ruff/blob/main/crates/ruff_linter/src/rules/pyupgrade/mod.rs#L111) - [Rule documentation](https://docs.astral.sh/ruff/rules/non-pep695-generic-function/) --- crates/ruff_linter/src/codes.rs | 2 +- .../pyupgrade/rules/pep695/non_pep695_generic_function.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 202d56e6fa80e5..80fcc0708f3782 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -551,7 +551,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack), (Pyupgrade, "045") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604AnnotationOptional), (Pyupgrade, "046") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericClass), - (Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction), + (Pyupgrade, "047") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericFunction), (Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter), (Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs index 3ab3a0b6b3773a..645fbcad61d982 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs @@ -25,7 +25,7 @@ use super::{DisplayTypeVars, TypeVarReferenceVisitor, check_type_vars, in_nested /// in Python 3.13. /// /// Not all type checkers fully support PEP 695 yet, so even valid fixes suggested by this rule may -/// cause type checking to fail. +/// cause type checking to [fail]. /// /// ## Fix safety /// @@ -76,6 +76,7 @@ use super::{DisplayTypeVars, TypeVarReferenceVisitor, check_type_vars, in_nested /// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/ /// [UP046]: https://docs.astral.sh/ruff/rules/non-pep695-generic-class/ /// [UP049]: https://docs.astral.sh/ruff/rules/private-type-parameter/ +/// [fail]: https://github.com/python/mypy/issues/18507 #[derive(ViolationMetadata)] pub(crate) struct NonPEP695GenericFunction { name: String, From a93992fa3075fcf3e625aa9413f914a5d2921af8 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 12 Jun 2025 11:10:22 -0500 Subject: [PATCH 485/487] [`flake8-return`] Stabilize only add `return None` at the end when fixing `implicit-return` (`RET503`) (#18516) This involved slightly more code changes than usual for a stabilization - so maybe worth double-checking the logic! I did verify by hand that the new stable behavior on the test fixture matches the old preview behavior, even after the internal refactor. --- crates/ruff_linter/src/preview.rs | 5 - .../src/rules/flake8_return/mod.rs | 21 +- .../src/rules/flake8_return/rules/function.rs | 87 ++-- ...lake8_return__tests__RET503_RET503.py.snap | 214 +++++---- ...urn__tests__preview__RET503_RET503.py.snap | 449 ------------------ 5 files changed, 150 insertions(+), 626 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__preview__RET503_RET503.py.snap diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 023e918c55eafe..e67efdec3d2740 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -40,11 +40,6 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet settings.preview.is_enabled() } -// https://github.com/astral-sh/ruff/pull/11074 -pub(crate) const fn is_only_add_return_none_at_end_enabled(settings: &LinterSettings) -> bool { - settings.preview.is_enabled() -} - // https://github.com/astral-sh/ruff/pull/16719 pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_return/mod.rs b/crates/ruff_linter/src/rules/flake8_return/mod.rs index 8876e2b5a306d4..bfad9cf6ba09c2 100644 --- a/crates/ruff_linter/src/rules/flake8_return/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_return/mod.rs @@ -11,11 +11,10 @@ mod tests { use anyhow::Result; use test_case::test_case; + use crate::assert_messages; use crate::registry::Rule; use crate::settings::LinterSettings; - use crate::settings::types::PreviewMode; use crate::test::test_path; - use crate::{assert_messages, settings}; #[test_case(Rule::UnnecessaryReturnNone, Path::new("RET501.py"))] #[test_case(Rule::ImplicitReturnValue, Path::new("RET502.py"))] @@ -34,22 +33,4 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } - - #[test_case(Rule::ImplicitReturn, Path::new("RET503.py"))] - fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!( - "preview__{}_{}", - rule_code.noqa_code(), - path.to_string_lossy() - ); - let diagnostics = test_path( - Path::new("flake8_return").join(path).as_path(), - &settings::LinterSettings { - preview: PreviewMode::Enabled, - ..settings::LinterSettings::for_rule(rule_code) - }, - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } } diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 3ba3e975274a72..c4bdcb4f3cad5e 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -16,7 +16,6 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::edits; use crate::fix::edits::adjust_indentation; -use crate::preview::is_only_add_return_none_at_end_enabled; use crate::registry::Rule; use crate::rules::flake8_return::helpers::end_of_last_statement; use crate::{AlwaysFixableViolation, FixAvailability, Violation}; @@ -462,67 +461,53 @@ fn add_return_none(checker: &Checker, stmt: &Stmt, range: TextRange) { } } -/// Returns a list of all implicit returns in the given statement. -/// -/// Note: The function should be refactored to `has_implicit_return` with an early return (when seeing the first implicit return) -/// when removing the preview gating. -fn implicit_returns<'a>(checker: &Checker, stmt: &'a Stmt) -> Vec<&'a Stmt> { +fn has_implicit_return(checker: &Checker, stmt: &Stmt) -> bool { match stmt { Stmt::If(ast::StmtIf { body, elif_else_clauses, .. }) => { - let mut implicit_stmts = body + if body .last() - .map(|last| implicit_returns(checker, last)) - .unwrap_or_default(); - - for clause in elif_else_clauses { - implicit_stmts.extend( - clause - .body - .last() - .iter() - .flat_map(|last| implicit_returns(checker, last)), - ); + .is_some_and(|last| has_implicit_return(checker, last)) + { + return true; + } + + if elif_else_clauses.iter().any(|clause| { + clause + .body + .last() + .is_some_and(|last| has_implicit_return(checker, last)) + }) { + return true; } // Check if we don't have an else clause - if matches!( + matches!( elif_else_clauses.last(), None | Some(ast::ElifElseClause { test: Some(_), .. }) - ) { - implicit_stmts.push(stmt); - } - implicit_stmts + ) } - Stmt::Assert(ast::StmtAssert { test, .. }) if is_const_false(test) => vec![], - Stmt::While(ast::StmtWhile { test, .. }) if is_const_true(test) => vec![], + Stmt::Assert(ast::StmtAssert { test, .. }) if is_const_false(test) => false, + Stmt::While(ast::StmtWhile { test, .. }) if is_const_true(test) => false, Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => { if let Some(last_stmt) = orelse.last() { - implicit_returns(checker, last_stmt) + has_implicit_return(checker, last_stmt) } else { - vec![stmt] - } - } - Stmt::Match(ast::StmtMatch { cases, .. }) => { - let mut implicit_stmts = vec![]; - for case in cases { - implicit_stmts.extend( - case.body - .last() - .into_iter() - .flat_map(|last_stmt| implicit_returns(checker, last_stmt)), - ); + true } - implicit_stmts } + Stmt::Match(ast::StmtMatch { cases, .. }) => cases.iter().any(|case| { + case.body + .last() + .is_some_and(|last| has_implicit_return(checker, last)) + }), Stmt::With(ast::StmtWith { body, .. }) => body .last() - .map(|last_stmt| implicit_returns(checker, last_stmt)) - .unwrap_or_default(), - Stmt::Return(_) | Stmt::Raise(_) | Stmt::Try(_) => vec![], + .is_some_and(|last_stmt| has_implicit_return(checker, last_stmt)), + Stmt::Return(_) | Stmt::Raise(_) | Stmt::Try(_) => false, Stmt::Expr(ast::StmtExpr { value, .. }) if matches!( value.as_ref(), @@ -530,28 +515,16 @@ fn implicit_returns<'a>(checker: &Checker, stmt: &'a Stmt) -> Vec<&'a Stmt> { if is_noreturn_func(func, checker.semantic()) ) => { - vec![] - } - _ => { - vec![stmt] + false } + _ => true, } } /// RET503 fn implicit_return(checker: &Checker, function_def: &ast::StmtFunctionDef, stmt: &Stmt) { - let implicit_stmts = implicit_returns(checker, stmt); - - if implicit_stmts.is_empty() { - return; - } - - if is_only_add_return_none_at_end_enabled(checker.settings) { + if has_implicit_return(checker, stmt) { add_return_none(checker, stmt, function_def.range()); - } else { - for implicit_stmt in implicit_stmts { - add_return_none(checker, implicit_stmt, implicit_stmt.range()); - } } } diff --git a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET503_RET503.py.snap b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET503_RET503.py.snap index af2cbb50470d33..91c961e50974d6 100644 --- a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET503_RET503.py.snap +++ b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET503_RET503.py.snap @@ -1,11 +1,11 @@ --- source: crates/ruff_linter/src/rules/flake8_return/mod.rs --- -RET503.py:21:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:20:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | 19 | # if/elif/else -20 | def x(y): -21 | / if not y: +20 | / def x(y): +21 | | if not y: 22 | | return 1 | |________________^ RET503 23 | # error @@ -21,32 +21,34 @@ RET503.py:21:5: RET503 [*] Missing explicit `return` at the end of function able 24 25 | 25 26 | -RET503.py:28:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:26:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -26 | def x(y): -27 | if not y: -28 | print() # error - | ^^^^^^^ RET503 -29 | else: -30 | return 2 +26 | / def x(y): +27 | | if not y: +28 | | print() # error +29 | | else: +30 | | return 2 + | |________________^ RET503 | = help: Add explicit `return` statement ℹ Unsafe fix -26 26 | def x(y): -27 27 | if not y: 28 28 | print() # error - 29 |+ return None -29 30 | else: -30 31 | return 2 +29 29 | else: +30 30 | return 2 + 31 |+ return None 31 32 | +32 33 | +33 34 | def x(y): -RET503.py:37:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:33:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -35 | return 1 -36 | -37 | print() # error - | ^^^^^^^ RET503 +33 | / def x(y): +34 | | if not y: +35 | | return 1 +36 | | +37 | | print() # error + | |___________^ RET503 | = help: Add explicit `return` statement @@ -59,11 +61,11 @@ RET503.py:37:5: RET503 [*] Missing explicit `return` at the end of function able 39 40 | 40 41 | # for -RET503.py:42:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:41:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | 40 | # for -41 | def x(y): -42 | / for i in range(10): +41 | / def x(y): +42 | | for i in range(10): 43 | | if i > 10: 44 | | return i | |____________________^ RET503 @@ -80,12 +82,15 @@ RET503.py:42:5: RET503 [*] Missing explicit `return` at the end of function able 46 47 | 47 48 | -RET503.py:53:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:48:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -51 | return i -52 | else: -53 | print() # error - | ^^^^^^^ RET503 +48 | / def x(y): +49 | | for i in range(10): +50 | | if i > 10: +51 | | return i +52 | | else: +53 | | print() # error + | |_______________^ RET503 | = help: Add explicit `return` statement @@ -93,17 +98,19 @@ RET503.py:53:9: RET503 [*] Missing explicit `return` at the end of function able 51 51 | return i 52 52 | else: 53 53 | print() # error - 54 |+ return None + 54 |+ return None 54 55 | 55 56 | 56 57 | # A nonexistent function -RET503.py:60:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:57:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -58 | if x > 0: -59 | return False -60 | no_such_function() # error - | ^^^^^^^^^^^^^^^^^^ RET503 +56 | # A nonexistent function +57 | / def func_unknown(x): +58 | | if x > 0: +59 | | return False +60 | | no_such_function() # error + | |______________________^ RET503 | = help: Add explicit `return` statement @@ -116,12 +123,14 @@ RET503.py:60:5: RET503 [*] Missing explicit `return` at the end of function able 62 63 | 63 64 | # A function that does return the control -RET503.py:67:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:64:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -65 | if x > 0: -66 | return False -67 | print("", end="") # error - | ^^^^^^^^^^^^^^^^^ RET503 +63 | # A function that does return the control +64 | / def func_no_noreturn(x): +65 | | if x > 0: +66 | | return False +67 | | print("", end="") # error + | |_____________________^ RET503 | = help: Add explicit `return` statement @@ -134,11 +143,11 @@ RET503.py:67:5: RET503 [*] Missing explicit `return` at the end of function able 69 70 | 70 71 | ### -RET503.py:83:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:82:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | 81 | # last line in while loop -82 | def x(y): -83 | / while i > 0: +82 | / def x(y): +83 | | while i > 0: 84 | | if y > 0: 85 | | return 1 86 | | y += 1 @@ -155,11 +164,11 @@ RET503.py:83:5: RET503 [*] Missing explicit `return` at the end of function able 88 89 | 89 90 | # exclude empty functions -RET503.py:114:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:113:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | 112 | # return value within loop -113 | def bar1(x, y, z): -114 | / for i in x: +113 | / def bar1(x, y, z): +114 | | for i in x: 115 | | if i > y: 116 | | break 117 | | return z @@ -176,10 +185,10 @@ RET503.py:114:5: RET503 [*] Missing explicit `return` at the end of function abl 119 120 | 120 121 | def bar3(x, y, z): -RET503.py:121:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:120:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -120 | def bar3(x, y, z): -121 | / for i in x: +120 | / def bar3(x, y, z): +121 | | for i in x: 122 | | if i > y: 123 | | if z: 124 | | break @@ -199,10 +208,10 @@ RET503.py:121:5: RET503 [*] Missing explicit `return` at the end of function abl 129 130 | 130 131 | def bar1(x, y, z): -RET503.py:131:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:130:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -130 | def bar1(x, y, z): -131 | / for i in x: +130 | / def bar1(x, y, z): +131 | | for i in x: 132 | | if i < y: 133 | | continue 134 | | return z @@ -219,10 +228,10 @@ RET503.py:131:5: RET503 [*] Missing explicit `return` at the end of function abl 136 137 | 137 138 | def bar3(x, y, z): -RET503.py:138:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:137:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -137 | def bar3(x, y, z): -138 | / for i in x: +137 | / def bar3(x, y, z): +138 | | for i in x: 139 | | if i < y: 140 | | if z: 141 | | continue @@ -242,11 +251,13 @@ RET503.py:138:5: RET503 [*] Missing explicit `return` at the end of function abl 146 147 | 147 148 | def prompts(self, foo): -RET503.py:275:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:271:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -273 | return False -274 | -275 | / for value in values: +271 | / def nested(values): +272 | | if not values: +273 | | return False +274 | | +275 | | for value in values: 276 | | print(value) | |____________________^ RET503 | @@ -261,12 +272,16 @@ RET503.py:275:5: RET503 [*] Missing explicit `return` at the end of function abl 278 279 | 279 280 | def while_true(): -RET503.py:292:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:287:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -290 | return 1 -291 | case 1: -292 | print() # error - | ^^^^^^^ RET503 +286 | # match +287 | / def x(y): +288 | | match y: +289 | | case 0: +290 | | return 1 +291 | | case 1: +292 | | print() # error + | |___________________^ RET503 | = help: Add explicit `return` statement @@ -274,16 +289,16 @@ RET503.py:292:13: RET503 [*] Missing explicit `return` at the end of function ab 290 290 | return 1 291 291 | case 1: 292 292 | print() # error - 293 |+ return None + 293 |+ return None 293 294 | 294 295 | 295 296 | def foo(baz: str) -> str: -RET503.py:301:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:300:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | 299 | def end_of_statement(): -300 | def example(): -301 | / if True: +300 | / def example(): +301 | | if True: 302 | | return "" | |_____________________^ RET503 | @@ -298,10 +313,10 @@ RET503.py:301:9: RET503 [*] Missing explicit `return` at the end of function abl 304 305 | 305 306 | def example(): -RET503.py:306:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:305:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -305 | def example(): -306 | / if True: +305 | / def example(): +306 | | if True: 307 | | return "" | |_____________________^ RET503 | @@ -316,10 +331,10 @@ RET503.py:306:9: RET503 [*] Missing explicit `return` at the end of function abl 309 310 | 310 311 | def example(): -RET503.py:311:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:310:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -310 | def example(): -311 | / if True: +310 | / def example(): +311 | | if True: 312 | | return "" # type: ignore | |_____________________^ RET503 | @@ -334,10 +349,10 @@ RET503.py:311:9: RET503 [*] Missing explicit `return` at the end of function abl 314 315 | 315 316 | def example(): -RET503.py:316:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:315:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -315 | def example(): -316 | / if True: +315 | / def example(): +316 | | if True: 317 | | return "" ; | |_____________________^ RET503 | @@ -352,10 +367,10 @@ RET503.py:316:9: RET503 [*] Missing explicit `return` at the end of function abl 319 320 | 320 321 | def example(): -RET503.py:321:9: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:320:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -320 | def example(): -321 | / if True: +320 | / def example(): +321 | | if True: 322 | | return "" \ | |_____________________^ RET503 323 | ; # type: ignore @@ -371,12 +386,13 @@ RET503.py:321:9: RET503 [*] Missing explicit `return` at the end of function abl 325 326 | 326 327 | def end_of_file(): -RET503.py:329:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:326:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -327 | if False: -328 | return 1 -329 | x = 2 \ - | ^^^^^ RET503 +326 | / def end_of_file(): +327 | | if False: +328 | | return 1 +329 | | x = 2 \ + | |_________^ RET503 | = help: Add explicit `return` statement @@ -389,12 +405,15 @@ RET503.py:329:5: RET503 [*] Missing explicit `return` at the end of function abl 332 333 | 333 334 | # function return type annotation NoReturn -RET503.py:403:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:398:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -401 | else: -402 | with c: -403 | d - | ^ RET503 +398 | / def f(): +399 | | if a: +400 | | return b +401 | | else: +402 | | with c: +403 | | d + | |_____________^ RET503 | = help: Add explicit `return` statement @@ -402,17 +421,22 @@ RET503.py:403:13: RET503 [*] Missing explicit `return` at the end of function ab 401 401 | else: 402 402 | with c: 403 403 | d - 404 |+ return None + 404 |+ return None 404 405 | 405 406 | 406 407 | -RET503.py:418:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value +RET503.py:413:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value | -416 | if x == 5: -417 | return 5 -418 | bar() - | ^^^^^ RET503 +411 | # the semantic model hasn't yet seen `bar`'s declaration. +412 | # Supporting nested functions requires making this a deferred rule. +413 | / def foo(x: int) -> int: +414 | | def bar() -> NoReturn: +415 | | abort() +416 | | if x == 5: +417 | | return 5 +418 | | bar() + | |_________^ RET503 | = help: Add explicit `return` statement diff --git a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__preview__RET503_RET503.py.snap b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__preview__RET503_RET503.py.snap deleted file mode 100644 index 91c961e50974d6..00000000000000 --- a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__preview__RET503_RET503.py.snap +++ /dev/null @@ -1,449 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_return/mod.rs ---- -RET503.py:20:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -19 | # if/elif/else -20 | / def x(y): -21 | | if not y: -22 | | return 1 - | |________________^ RET503 -23 | # error - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -20 20 | def x(y): -21 21 | if not y: -22 22 | return 1 - 23 |+ return None -23 24 | # error -24 25 | -25 26 | - -RET503.py:26:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -26 | / def x(y): -27 | | if not y: -28 | | print() # error -29 | | else: -30 | | return 2 - | |________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -28 28 | print() # error -29 29 | else: -30 30 | return 2 - 31 |+ return None -31 32 | -32 33 | -33 34 | def x(y): - -RET503.py:33:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -33 | / def x(y): -34 | | if not y: -35 | | return 1 -36 | | -37 | | print() # error - | |___________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -35 35 | return 1 -36 36 | -37 37 | print() # error - 38 |+ return None -38 39 | -39 40 | -40 41 | # for - -RET503.py:41:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -40 | # for -41 | / def x(y): -42 | | for i in range(10): -43 | | if i > 10: -44 | | return i - | |____________________^ RET503 -45 | # error - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -42 42 | for i in range(10): -43 43 | if i > 10: -44 44 | return i - 45 |+ return None -45 46 | # error -46 47 | -47 48 | - -RET503.py:48:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -48 | / def x(y): -49 | | for i in range(10): -50 | | if i > 10: -51 | | return i -52 | | else: -53 | | print() # error - | |_______________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -51 51 | return i -52 52 | else: -53 53 | print() # error - 54 |+ return None -54 55 | -55 56 | -56 57 | # A nonexistent function - -RET503.py:57:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -56 | # A nonexistent function -57 | / def func_unknown(x): -58 | | if x > 0: -59 | | return False -60 | | no_such_function() # error - | |______________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -58 58 | if x > 0: -59 59 | return False -60 60 | no_such_function() # error - 61 |+ return None -61 62 | -62 63 | -63 64 | # A function that does return the control - -RET503.py:64:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -63 | # A function that does return the control -64 | / def func_no_noreturn(x): -65 | | if x > 0: -66 | | return False -67 | | print("", end="") # error - | |_____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -65 65 | if x > 0: -66 66 | return False -67 67 | print("", end="") # error - 68 |+ return None -68 69 | -69 70 | -70 71 | ### - -RET503.py:82:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -81 | # last line in while loop -82 | / def x(y): -83 | | while i > 0: -84 | | if y > 0: -85 | | return 1 -86 | | y += 1 - | |______________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -84 84 | if y > 0: -85 85 | return 1 -86 86 | y += 1 - 87 |+ return None -87 88 | -88 89 | -89 90 | # exclude empty functions - -RET503.py:113:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -112 | # return value within loop -113 | / def bar1(x, y, z): -114 | | for i in x: -115 | | if i > y: -116 | | break -117 | | return z - | |________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -115 115 | if i > y: -116 116 | break -117 117 | return z - 118 |+ return None -118 119 | -119 120 | -120 121 | def bar3(x, y, z): - -RET503.py:120:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -120 | / def bar3(x, y, z): -121 | | for i in x: -122 | | if i > y: -123 | | if z: -124 | | break -125 | | else: -126 | | return z -127 | | return None - | |___________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -125 125 | else: -126 126 | return z -127 127 | return None - 128 |+ return None -128 129 | -129 130 | -130 131 | def bar1(x, y, z): - -RET503.py:130:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -130 | / def bar1(x, y, z): -131 | | for i in x: -132 | | if i < y: -133 | | continue -134 | | return z - | |________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -132 132 | if i < y: -133 133 | continue -134 134 | return z - 135 |+ return None -135 136 | -136 137 | -137 138 | def bar3(x, y, z): - -RET503.py:137:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -137 | / def bar3(x, y, z): -138 | | for i in x: -139 | | if i < y: -140 | | if z: -141 | | continue -142 | | else: -143 | | return z -144 | | return None - | |___________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -142 142 | else: -143 143 | return z -144 144 | return None - 145 |+ return None -145 146 | -146 147 | -147 148 | def prompts(self, foo): - -RET503.py:271:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -271 | / def nested(values): -272 | | if not values: -273 | | return False -274 | | -275 | | for value in values: -276 | | print(value) - | |____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -274 274 | -275 275 | for value in values: -276 276 | print(value) - 277 |+ return None -277 278 | -278 279 | -279 280 | def while_true(): - -RET503.py:287:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -286 | # match -287 | / def x(y): -288 | | match y: -289 | | case 0: -290 | | return 1 -291 | | case 1: -292 | | print() # error - | |___________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -290 290 | return 1 -291 291 | case 1: -292 292 | print() # error - 293 |+ return None -293 294 | -294 295 | -295 296 | def foo(baz: str) -> str: - -RET503.py:300:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -299 | def end_of_statement(): -300 | / def example(): -301 | | if True: -302 | | return "" - | |_____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -300 300 | def example(): -301 301 | if True: -302 302 | return "" - 303 |+ return None -303 304 | -304 305 | -305 306 | def example(): - -RET503.py:305:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -305 | / def example(): -306 | | if True: -307 | | return "" - | |_____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -305 305 | def example(): -306 306 | if True: -307 307 | return "" - 308 |+ return None -308 309 | -309 310 | -310 311 | def example(): - -RET503.py:310:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -310 | / def example(): -311 | | if True: -312 | | return "" # type: ignore - | |_____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -310 310 | def example(): -311 311 | if True: -312 312 | return "" # type: ignore - 313 |+ return None -313 314 | -314 315 | -315 316 | def example(): - -RET503.py:315:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -315 | / def example(): -316 | | if True: -317 | | return "" ; - | |_____________________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -315 315 | def example(): -316 316 | if True: -317 317 | return "" ; - 318 |+ return None -318 319 | -319 320 | -320 321 | def example(): - -RET503.py:320:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -320 | / def example(): -321 | | if True: -322 | | return "" \ - | |_____________________^ RET503 -323 | ; # type: ignore - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -321 321 | if True: -322 322 | return "" \ -323 323 | ; # type: ignore - 324 |+ return None -324 325 | -325 326 | -326 327 | def end_of_file(): - -RET503.py:326:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -326 | / def end_of_file(): -327 | | if False: -328 | | return 1 -329 | | x = 2 \ - | |_________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -328 328 | return 1 -329 329 | x = 2 \ -330 330 | - 331 |+ return None -331 332 | -332 333 | -333 334 | # function return type annotation NoReturn - -RET503.py:398:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -398 | / def f(): -399 | | if a: -400 | | return b -401 | | else: -402 | | with c: -403 | | d - | |_____________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -401 401 | else: -402 402 | with c: -403 403 | d - 404 |+ return None -404 405 | -405 406 | -406 407 | - -RET503.py:413:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value - | -411 | # the semantic model hasn't yet seen `bar`'s declaration. -412 | # Supporting nested functions requires making this a deferred rule. -413 | / def foo(x: int) -> int: -414 | | def bar() -> NoReturn: -415 | | abort() -416 | | if x == 5: -417 | | return 5 -418 | | bar() - | |_________^ RET503 - | - = help: Add explicit `return` statement - -ℹ Unsafe fix -415 415 | abort() -416 416 | if x == 5: -417 417 | return 5 -418 |- bar() - 418 |+ bar() - 419 |+ return None From 685eac10e5cf64b73e73b6c517969cf30f259a3a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 17 Jun 2025 15:48:09 +0100 Subject: [PATCH 486/487] Revert "[ty] Offer "Did you mean...?" suggestions for unresolved `from` imports and unresolved attributes (#18705)" (#18721) --- _typos.toml | 2 - crates/ty/docs/rules.md | 112 +++--- .../resources/mdtest/attributes.md | 51 --- .../resources/mdtest/import/basic.md | 36 -- ..._obvi\342\200\246_(bf7b28ef99f0ec16).snap" | 115 ------ ...hat_h\342\200\246_(3caffc60d8390adf).snap" | 69 ---- .../ty_python_semantic/src/semantic_model.rs | 2 +- crates/ty_python_semantic/src/types.rs | 4 +- .../ty_python_semantic/src/types/call/bind.rs | 4 +- crates/ty_python_semantic/src/types/class.rs | 4 - .../src/types/diagnostic.rs | 9 +- .../src/types/diagnostic/levenshtein.rs | 378 ------------------ .../types/{all_members.rs => ide_support.rs} | 6 - crates/ty_python_semantic/src/types/infer.rs | 98 ++--- 14 files changed, 96 insertions(+), 794 deletions(-) delete mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" delete mode 100644 "crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" delete mode 100644 crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs rename crates/ty_python_semantic/src/types/{all_members.rs => ide_support.rs} (96%) diff --git a/_typos.toml b/_typos.toml index f1cd0c0ef32978..24406f10bba161 100644 --- a/_typos.toml +++ b/_typos.toml @@ -8,8 +8,6 @@ extend-exclude = [ # words naturally. It's annoying to have to make all # of them actually words. So just ignore typos here. "crates/ty_ide/src/completion.rs", - # Same for "Did you mean...?" levenshtein tests. - "crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs", ] [default.extend-words] diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 740f5acb088777..b36ccae6576bef 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L94) ## `conflicting-argument-forms` @@ -83,7 +83,7 @@ f(int) # error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L138) ## `conflicting-declarations` @@ -113,7 +113,7 @@ a = 1 ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L164) ## `conflicting-metaclass` @@ -144,7 +144,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L189) ## `cyclic-class-definition` @@ -175,7 +175,7 @@ class B(A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L215) ## `duplicate-base` @@ -201,7 +201,7 @@ class B(A, A): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259) ## `escape-character-in-forward-annotation` @@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280) ## `inconsistent-mro` @@ -367,7 +367,7 @@ class C(A, B): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L371) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366) ## `index-out-of-bounds` @@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L390) ## `invalid-argument-type` @@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L415) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L410) ## `invalid-assignment` @@ -445,7 +445,7 @@ a: int = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L455) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) ## `invalid-attribute-access` @@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1459) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454) ## `invalid-base` @@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) ## `invalid-context-manager` @@ -527,7 +527,7 @@ with 1: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L528) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523) ## `invalid-declaration` @@ -555,7 +555,7 @@ a: str ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L549) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544) ## `invalid-exception-caught` @@ -596,7 +596,7 @@ except ZeroDivisionError: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567) ## `invalid-generic-class` @@ -627,7 +627,7 @@ class C[U](Generic[T]): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L608) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L603) ## `invalid-legacy-type-variable` @@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L634) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L629) ## `invalid-metaclass` @@ -692,7 +692,7 @@ class B(metaclass=f): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678) ## `invalid-overload` @@ -740,7 +740,7 @@ def foo(x: int) -> int: ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L710) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L705) ## `invalid-parameter-default` @@ -765,7 +765,7 @@ def f(a: int = ''): ... ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L753) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L748) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338) ## `invalid-raise` @@ -846,7 +846,7 @@ def g(): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L773) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L768) ## `invalid-return-type` @@ -870,7 +870,7 @@ def func() -> int: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L436) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431) ## `invalid-super-argument` @@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L816) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811) ## `invalid-syntax-in-forward-annotation` @@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L662) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L657) ## `invalid-type-checking-constant` @@ -983,7 +983,7 @@ TYPE_CHECKING = '' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850) ## `invalid-type-form` @@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L879) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874) ## `invalid-type-guard-call` @@ -1045,7 +1045,7 @@ f(10) # Error ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) ## `invalid-type-guard-definition` @@ -1078,7 +1078,7 @@ class C: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L898) ## `invalid-type-variable-constraints` @@ -1112,7 +1112,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954) ## `missing-argument` @@ -1136,7 +1136,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) ## `no-matching-overload` @@ -1164,7 +1164,7 @@ func("string") # error: [no-matching-overload] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1007) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002) ## `non-subscriptable` @@ -1187,7 +1187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1030) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1025) ## `not-iterable` @@ -1212,7 +1212,7 @@ for i in 34: # TypeError: 'int' object is not iterable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1048) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043) ## `parameter-already-assigned` @@ -1238,7 +1238,7 @@ f(1, x=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1099) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1094) ## `raw-string-type-annotation` @@ -1297,7 +1297,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430) ## `subclass-of-final-class` @@ -1325,7 +1325,7 @@ class B(A): ... # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1185) ## `too-many-positional-arguments` @@ -1351,7 +1351,7 @@ f("foo") # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) ## `type-assertion-failure` @@ -1378,7 +1378,7 @@ def _(x: int): ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208) ## `unavailable-implicit-super-arguments` @@ -1422,7 +1422,7 @@ class A: ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) ## `unknown-argument` @@ -1448,7 +1448,7 @@ f(x=1, y=2) # Error raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1308) ## `unresolved-attribute` @@ -1475,7 +1475,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1334) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) ## `unresolved-import` @@ -1499,7 +1499,7 @@ import foo # ModuleNotFoundError: No module named 'foo' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1356) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) ## `unresolved-reference` @@ -1523,7 +1523,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) ## `unsupported-bool-conversion` @@ -1559,7 +1559,7 @@ b1 < b2 < b1 # exception raised here ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1068) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063) ## `unsupported-operator` @@ -1586,7 +1586,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389) ## `zero-stepsize-in-slice` @@ -1610,7 +1610,7 @@ l[1:10:0] # ValueError: slice step cannot be zero ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1416) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411) ## `invalid-ignore-comment` @@ -1666,7 +1666,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1115) ## `possibly-unbound-implicit-call` @@ -1697,7 +1697,7 @@ A()[0] # TypeError: 'A' object is not subscriptable ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L112) ## `possibly-unbound-import` @@ -1728,7 +1728,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1142) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137) ## `redundant-cast` @@ -1754,7 +1754,7 @@ cast(int, f()) # Redundant ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1487) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1482) ## `undefined-reveal` @@ -1777,7 +1777,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) ## `unknown-rule` @@ -1845,7 +1845,7 @@ class D(C): ... # error: [unsupported-base] ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L495) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L490) ## `division-by-zero` @@ -1868,7 +1868,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L241) ## `possibly-unresolved-reference` @@ -1895,7 +1895,7 @@ print(x) # NameError: name 'x' is not defined ### Links * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) -* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1168) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1163) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 0b0f7d50189e82..53643da8ecdb09 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -2167,57 +2167,6 @@ reveal_type(Foo.BAR.value) # revealed: @Todo(Attribute access on enum classes) reveal_type(Foo.__members__) # revealed: @Todo(Attribute access on enum classes) ``` -## Suggestions for obvious typos - - - -For obvious typos, we add a "Did you mean...?" suggestion to the diagnostic. - -```py -import collections - -print(collections.dequee) # error: [unresolved-attribute] -``` - -But the suggestion is suppressed if the only close matches start with a leading underscore: - -```py -class Foo: - _bar = 42 - -print(Foo.bar) # error: [unresolved-attribute] -``` - -The suggestion is not suppressed if the typo itself starts with a leading underscore, however: - -```py -print(Foo._barr) # error: [unresolved-attribute] -``` - -And in method contexts, the suggestion is never suppressed if accessing an attribute on an instance -of the method's enclosing class: - -```py -class Bar: - _attribute = 42 - - def f(self, x: "Bar"): - # TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below - print(self.attribute) - - # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, - # because we're in a method context and `x` is an instance of the enclosing class. - print(x.attribute) # error: [unresolved-attribute] - -class Baz: - def f(self, x: Bar): - # No suggestion is given here, because: - # - the good suggestions all start with underscores - # - the typo does not start with an underscore - # - We *are* in a method context, but `x` is not an instance of the enclosing class - print(x.attribute) # error: [unresolved-attribute] -``` - ## References Some of the tests in the *Class and instance variables* section draw inspiration from diff --git a/crates/ty_python_semantic/resources/mdtest/import/basic.md b/crates/ty_python_semantic/resources/mdtest/import/basic.md index 01d8045b97ed13..8e7538190ef06a 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/import/basic.md @@ -205,39 +205,3 @@ python-version = "3.13" import aifc # error: [unresolved-import] from distutils import sysconfig # error: [unresolved-import] ``` - -## `from` import that has a typo - -We offer a "Did you mean?" subdiagnostic suggestion if there's a name in the module that's -reasonably similar to the unresolved member. - - - -`foo.py`: - -```py -from collections import dequee # error: [unresolved-import] -``` - -However, we suppress the suggestion if the only close matches in the module start with a leading -underscore: - -`bar.py`: - -```py -from baz import foo # error: [unresolved-import] -``` - -`baz.py`: - -```py -_foo = 42 -``` - -The suggestion is never suppressed if the typo itself starts with a leading underscore, however: - -`eggs.py`: - -```py -from baz import _fooo # error: [unresolved-import] -``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" deleted file mode 100644 index 350467007b6e3e..00000000000000 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi\342\200\246_(bf7b28ef99f0ec16).snap" +++ /dev/null @@ -1,115 +0,0 @@ ---- -source: crates/ty_test/src/lib.rs -expression: snapshot ---- ---- -mdtest name: attributes.md - Attributes - Suggestions for obvious typos -mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md ---- - -# Python source files - -## mdtest_snippet.py - -``` - 1 | import collections - 2 | - 3 | print(collections.dequee) # error: [unresolved-attribute] - 4 | class Foo: - 5 | _bar = 42 - 6 | - 7 | print(Foo.bar) # error: [unresolved-attribute] - 8 | print(Foo._barr) # error: [unresolved-attribute] - 9 | class Bar: -10 | _attribute = 42 -11 | -12 | def f(self, x: "Bar"): -13 | # TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below -14 | print(self.attribute) -15 | -16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, -17 | # because we're in a method context and `x` is an instance of the enclosing class. -18 | print(x.attribute) # error: [unresolved-attribute] -19 | -20 | class Baz: -21 | def f(self, x: Bar): -22 | # No suggestion is given here, because: -23 | # - the good suggestions all start with underscores -24 | # - the typo does not start with an underscore -25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class -26 | print(x.attribute) # error: [unresolved-attribute] -``` - -# Diagnostics - -``` -error[unresolved-attribute]: Type `` has no attribute `dequee` - --> src/mdtest_snippet.py:3:7 - | -1 | import collections -2 | -3 | print(collections.dequee) # error: [unresolved-attribute] - | ^^^^^^^^^^^^^^^^^^ Did you mean `deque`? -4 | class Foo: -5 | _bar = 42 - | -info: rule `unresolved-attribute` is enabled by default - -``` - -``` -error[unresolved-attribute]: Type `` has no attribute `bar` - --> src/mdtest_snippet.py:7:7 - | -5 | _bar = 42 -6 | -7 | print(Foo.bar) # error: [unresolved-attribute] - | ^^^^^^^ -8 | print(Foo._barr) # error: [unresolved-attribute] -9 | class Bar: - | -info: rule `unresolved-attribute` is enabled by default - -``` - -``` -error[unresolved-attribute]: Type `` has no attribute `_barr` - --> src/mdtest_snippet.py:8:7 - | - 7 | print(Foo.bar) # error: [unresolved-attribute] - 8 | print(Foo._barr) # error: [unresolved-attribute] - | ^^^^^^^^^ Did you mean `_bar`? - 9 | class Bar: -10 | _attribute = 42 - | -info: rule `unresolved-attribute` is enabled by default - -``` - -``` -error[unresolved-attribute]: Type `Bar` has no attribute `attribute` - --> src/mdtest_snippet.py:18:15 - | -16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not, -17 | # because we're in a method context and `x` is an instance of the enclosing class. -18 | print(x.attribute) # error: [unresolved-attribute] - | ^^^^^^^^^^^ Did you mean `_attribute`? -19 | -20 | class Baz: - | -info: rule `unresolved-attribute` is enabled by default - -``` - -``` -error[unresolved-attribute]: Type `Bar` has no attribute `attribute` - --> src/mdtest_snippet.py:26:15 - | -24 | # - the typo does not start with an underscore -25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class -26 | print(x.attribute) # error: [unresolved-attribute] - | ^^^^^^^^^^^ - | -info: rule `unresolved-attribute` is enabled by default - -``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" deleted file mode 100644 index 0b237ac4def491..00000000000000 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h\342\200\246_(3caffc60d8390adf).snap" +++ /dev/null @@ -1,69 +0,0 @@ ---- -source: crates/ty_test/src/lib.rs -expression: snapshot ---- ---- -mdtest name: basic.md - Structures - `from` import that has a typo -mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md ---- - -# Python source files - -## foo.py - -``` -1 | from collections import dequee # error: [unresolved-import] -``` - -## bar.py - -``` -1 | from baz import foo # error: [unresolved-import] -``` - -## baz.py - -``` -1 | _foo = 42 -``` - -## eggs.py - -``` -1 | from baz import _fooo # error: [unresolved-import] -``` - -# Diagnostics - -``` -error[unresolved-import]: Module `collections` has no member `dequee` - --> src/foo.py:1:25 - | -1 | from collections import dequee # error: [unresolved-import] - | ^^^^^^ Did you mean `deque`? - | -info: rule `unresolved-import` is enabled by default - -``` - -``` -error[unresolved-import]: Module `baz` has no member `foo` - --> src/bar.py:1:17 - | -1 | from baz import foo # error: [unresolved-import] - | ^^^ - | -info: rule `unresolved-import` is enabled by default - -``` - -``` -error[unresolved-import]: Module `baz` has no member `_fooo` - --> src/eggs.py:1:17 - | -1 | from baz import _fooo # error: [unresolved-import] - | ^^^^^ Did you mean `_foo`? - | -info: rule `unresolved-import` is enabled by default - -``` diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index e174010fe828a8..9237e75ee2ece4 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -10,7 +10,7 @@ use crate::module_resolver::{Module, resolve_module}; use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::place::FileScopeId; use crate::semantic_index::semantic_index; -use crate::types::all_members::all_declarations_and_bindings; +use crate::types::ide_support::all_declarations_and_bindings; use crate::types::{Type, binding_type, infer_scope_types}; pub struct SemanticModel<'db> { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 84462c97d5b7de..4a28fe40c86ca0 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -38,7 +38,6 @@ use crate::semantic_index::definition::Definition; use crate::semantic_index::place::{ScopeId, ScopedPlaceId}; use crate::semantic_index::{imported_modules, place_table, semantic_index}; use crate::suppression::check_suppressions; -pub use crate::types::all_members::all_members; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; @@ -47,6 +46,7 @@ use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; +pub use crate::types::ide_support::all_members; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; @@ -58,7 +58,6 @@ use instance::Protocol; pub use instance::{NominalInstanceType, ProtocolInstanceType}; pub use special_form::SpecialFormType; -pub(crate) mod all_members; mod builder; mod call; mod class; @@ -68,6 +67,7 @@ mod diagnostic; mod display; mod function; mod generics; +pub(crate) mod ide_support; mod infer; mod instance; mod mro; diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d34a12898ca6d8..fb41f559af4693 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -27,7 +27,7 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, - WrapperDescriptorKind, all_members, todo_type, + WrapperDescriptorKind, ide_support, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -669,7 +669,7 @@ impl<'db> Bindings<'db> { if let [Some(ty)] = overload.parameter_types() { overload.set_return_type(TupleType::from_elements( db, - all_members::all_members(db, *ty) + ide_support::all_members(db, *ty) .into_iter() .sorted() .map(|member| Type::string_literal(db, &member)), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index cf7cfbc401b7b8..4c729caee92f9d 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -225,10 +225,6 @@ pub enum ClassType<'db> { #[salsa::tracked] impl<'db> ClassType<'db> { - pub(super) fn is_protocol(self, db: &'db dyn Db) -> bool { - self.class_literal(db).0.is_protocol(db) - } - pub(super) fn normalized(self, db: &'db dyn Db) -> Self { match self { Self::NonGeneric(_) => self, diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 454dd24adbf5df..f85d77dcff9920 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -17,17 +17,12 @@ use crate::types::string_annotation::{ use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; -pub(crate) use levenshtein::{ - HideUnderscoredSuggestions, find_best_suggestion_for_unresolved_member, -}; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::Formatter; -mod levenshtein; - /// Registers all known type check lints. pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&CALL_NON_CALLABLE); @@ -2217,7 +2212,7 @@ fn report_invalid_base<'ctx, 'db>( /// misconfigured their Python version. pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions( db: &dyn Db, - diagnostic: &mut LintDiagnosticGuard, + mut diagnostic: LintDiagnosticGuard, full_submodule_name: &ModuleName, parent_module: &Module, ) { @@ -2252,5 +2247,5 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions( version_range = version_range.diagnostic_display(), )); - add_inferred_python_version_hint_to_diagnostic(db, diagnostic, "resolving modules"); + add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules"); } diff --git a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs deleted file mode 100644 index 11fc59674e0d3c..00000000000000 --- a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! Infrastructure for providing "Did you mean..?" suggestions to attach to diagnostics. -//! -//! This is a Levenshtein implementation that is mainly ported from the implementation -//! CPython uses to provide suggestions in its own exception messages. -//! The tests similarly owe much to CPython's test suite. -//! Many thanks to Pablo Galindo Salgado and others for implementing the original -//! feature in CPython! - -use crate::Db; -use crate::types::{Type, all_members}; - -use indexmap::IndexSet; -use ruff_python_ast::name::Name; - -/// Given a type and an unresolved member name, find the best suggestion for a member name -/// that is similar to the unresolved member name. -/// -/// This function is used to provide suggestions for subdiagnostics attached to -/// `unresolved-attribute`, `unresolved-import`, and `unresolved-reference` diagnostics. -pub(crate) fn find_best_suggestion_for_unresolved_member<'db>( - db: &'db dyn Db, - obj: Type<'db>, - unresolved_member: &str, - hide_underscored_suggestions: HideUnderscoredSuggestions, -) -> Option { - find_best_suggestion( - all_members(db, obj), - unresolved_member, - hide_underscored_suggestions, - ) -} - -/// Whether to hide suggestions that start with an underscore. -/// -/// If the typo itself starts with an underscore, this policy is ignored. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum HideUnderscoredSuggestions { - Yes, - No, -} - -impl HideUnderscoredSuggestions { - const fn is_no(self) -> bool { - matches!(self, HideUnderscoredSuggestions::No) - } -} - -fn find_best_suggestion( - options: O, - unresolved_member: &str, - hide_underscored_suggestions: HideUnderscoredSuggestions, -) -> Option -where - O: IntoIterator, - I: ExactSizeIterator, -{ - if unresolved_member.is_empty() { - return None; - } - - let options = options.into_iter(); - - // Don't spend a *huge* amount of time computing suggestions if there are many candidates. - // This limit is fairly arbitrary and can be adjusted as needed. - if options.len() > 4096 { - return None; - } - - // Filter out the unresolved member itself. - // Otherwise (due to our implementation of implicit instance attributes), - // we end up giving bogus suggestions like this: - // - // ```python - // class Foo: - // _attribute = 42 - // def bar(self): - // print(self.attribute) # error: unresolved attribute `attribute`; did you mean `attribute`? - // ``` - let options = options.filter(|name| name != unresolved_member); - - let mut options: IndexSet = - if hide_underscored_suggestions.is_no() || unresolved_member.starts_with('_') { - options.collect() - } else { - options.filter(|name| !name.starts_with('_')).collect() - }; - options.sort_unstable(); - find_best_suggestion_impl(options, unresolved_member) -} - -fn find_best_suggestion_impl(options: IndexSet, unresolved_member: &str) -> Option { - let mut best_suggestion = None; - - for member in options { - let mut max_distance = - (member.chars().count() + unresolved_member.chars().count() + 3) * MOVE_COST / 6; - - if let Some((_, best_distance)) = best_suggestion { - if best_distance > 0 { - max_distance = max_distance.min(best_distance - 1); - } - } - - let current_distance = levenshtein_distance(unresolved_member, &member, max_distance); - if current_distance > max_distance { - continue; - } - - if best_suggestion - .as_ref() - .is_none_or(|(_, best_score)| ¤t_distance < best_score) - { - best_suggestion = Some((member, current_distance)); - } - } - - best_suggestion.map(|(suggestion, _)| suggestion) -} - -/// Determine the "cost" of converting `string_a` to `string_b`. -fn substitution_cost(char_a: char, char_b: char) -> CharacterMatch { - if char_a == char_b { - return CharacterMatch::Exact; - } - - let char_a_lowercase = char_a.to_lowercase(); - let char_b_lowercase = char_b.to_lowercase(); - - if char_a_lowercase.len() == char_b_lowercase.len() - && char_a_lowercase.zip(char_b_lowercase).all(|(a, b)| a == b) - { - return CharacterMatch::CaseInsensitive; - } - - CharacterMatch::None -} - -/// The result of comparing two characters. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum CharacterMatch { - Exact, - CaseInsensitive, - None, -} - -/// The cost of a Levenshtein insertion, deletion, or substitution. -/// It should be the same as `CharacterMatch::None` cast to a `usize`. -/// -/// This is used instead of the conventional unit cost to give these differences a higher cost than -/// casing differences, which CPython assigns a cost of 1. -const MOVE_COST: usize = CharacterMatch::None as usize; - -/// Returns the [Levenshtein edit distance] between strings `string_a` and `string_b`. -/// Uses the [Wagner-Fischer algorithm] to speed up the calculation. -/// -/// [Levenshtein edit distance]: https://en.wikipedia.org/wiki/Levenshtein_distance -/// [Wagner-Fischer algorithm]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm -fn levenshtein_distance(string_a: &str, string_b: &str, max_cost: usize) -> usize { - if string_a == string_b { - return 0; - } - - let string_a_chars: Vec = string_a.chars().collect(); - let string_b_chars: Vec = string_b.chars().collect(); - - // Trim away common affixes - let pre = string_a_chars - .iter() - .zip(string_b_chars.iter()) - .take_while(|(a, b)| a == b) - .count(); - let string_a_chars = &string_a_chars[pre..]; - let string_b_chars = &string_b_chars[pre..]; - - // Trim away common suffixes - let post = string_a_chars - .iter() - .rev() - .zip(string_b_chars.iter().rev()) - .take_while(|(a, b)| a == b) - .count(); - let mut string_a_chars = &string_a_chars[..string_a_chars.len() - post]; - let mut string_b_chars = &string_b_chars[..string_b_chars.len() - post]; - - let mut string_a_len = string_a_chars.len(); - let mut string_b_len = string_b_chars.len(); - - // Short-circuit if either string is empty after trimming affixes/suffixes - if string_a_len == 0 || string_b_len == 0 { - return MOVE_COST * (string_a_len + string_b_len); - } - - // `string_a` should refer to the shorter of the two strings. - // This enables us to create a smaller buffer in the main loop below. - if string_b_chars.len() < string_a_chars.len() { - std::mem::swap(&mut string_a_chars, &mut string_b_chars); - std::mem::swap(&mut string_a_len, &mut string_b_len); - } - - // Quick fail if a match is impossible. - if (string_b_len - string_a_len) * MOVE_COST > max_cost { - return max_cost + 1; - } - - let mut row = vec![0; string_a_len]; - for (i, v) in (MOVE_COST..MOVE_COST * (string_a_len + 1)) - .step_by(MOVE_COST) - .enumerate() - { - row[i] = v; - } - - let mut result = 0; - - for (b_index, b_char) in string_b_chars - .iter() - .copied() - .enumerate() - .take(string_b_len) - { - result = b_index * MOVE_COST; - let mut distance = result; - let mut minimum = usize::MAX; - for index in 0..string_a_len { - let substitute = distance + substitution_cost(b_char, string_a_chars[index]) as usize; - distance = row[index]; - let insert_delete = result.min(distance) + MOVE_COST; - result = insert_delete.min(substitute); - - row[index] = result; - if result < minimum { - minimum = result; - } - } - - if minimum > max_cost { - return max_cost + 1; - } - } - - result -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - /// Given a list of candidates, this test asserts that the best suggestion - /// for the typo `bluch` is what we'd expect. - /// - /// This test is ported from - #[test_case(&["noise", "more_noise", "a", "bc", "bluchin"], "bluchin"; "test for additional characters")] - #[test_case(&["noise", "more_noise", "a", "bc", "blech"], "blech"; "test for substituted characters")] - #[test_case(&["noise", "more_noise", "a", "bc", "blch"], "blch"; "test for eliminated characters")] - #[test_case(&["blach", "bluc"], "blach"; "substitutions are preferred over eliminations")] - #[test_case(&["blach", "bluchi"], "blach"; "substitutions are preferred over additions")] - #[test_case(&["blucha", "bluc"], "bluc"; "eliminations are preferred over additions")] - #[test_case(&["Luch", "fluch", "BLuch"], "BLuch"; "case changes are preferred over substitutions")] - fn test_good_suggestions(candidate_list: &[&str], expected_suggestion: &str) { - let candidates: Vec = candidate_list.iter().copied().map(Name::from).collect(); - let suggestion = find_best_suggestion(candidates, "bluch", HideUnderscoredSuggestions::No); - assert_eq!(suggestion.as_deref(), Some(expected_suggestion)); - } - - /// Test ported from - #[test] - fn underscored_names_not_suggested_if_hide_policy_set_to_yes() { - let suggestion = find_best_suggestion( - [Name::from("_bluch")], - "bluch", - HideUnderscoredSuggestions::Yes, - ); - if let Some(suggestion) = suggestion { - panic!( - "Expected no suggestions for `bluch` due to `HideUnderscoredSuggestions::Yes` but `{suggestion}` was suggested" - ); - } - } - - /// Test ported from - #[test_case("_blach")] - #[test_case("_luch")] - fn underscored_names_are_suggested_if_hide_policy_set_to_yes_when_typo_is_underscored( - typo: &str, - ) { - let suggestion = find_best_suggestion( - [Name::from("_bluch")], - typo, - HideUnderscoredSuggestions::Yes, - ); - assert_eq!(suggestion.as_deref(), Some("_bluch")); - } - - /// Test ported from - #[test_case("_luch")] - #[test_case("_bluch")] - fn non_underscored_names_always_suggested_even_if_typo_underscored(typo: &str) { - let suggestion = - find_best_suggestion([Name::from("bluch")], typo, HideUnderscoredSuggestions::Yes); - assert_eq!(suggestion.as_deref(), Some("bluch")); - } - - /// This asserts that we do not offer silly suggestions for very small names. - /// The test is ported from - #[test_case("b")] - #[test_case("v")] - #[test_case("m")] - #[test_case("py")] - fn test_bad_suggestions_do_not_trigger_for_small_names(typo: &str) { - let candidates = ["vvv", "mom", "w", "id", "pytho"].map(Name::from); - let suggestion = find_best_suggestion(candidates, typo, HideUnderscoredSuggestions::No); - if let Some(suggestion) = suggestion { - panic!("Expected no suggestions for `{typo}` but `{suggestion}` was suggested"); - } - } - - /// Test ported from - #[test] - fn test_no_suggestion_for_very_different_attribute() { - assert_eq!( - find_best_suggestion( - [Name::from("blech")], - "somethingverywrong", - HideUnderscoredSuggestions::No - ), - None - ); - } - - /// These tests are from the Levenshtein Wikipedia article, updated to match CPython's - /// implementation (just doubling the score to accommodate the MOVE_COST) - #[test_case("kitten", "sitting", 6)] - #[test_case("uninformed", "uniformed", 2)] - #[test_case("flaw", "lawn", 4)] - fn test_levenshtein_distance_calculation_wikipedia_examples( - string_a: &str, - string_b: &str, - expected_distance: usize, - ) { - assert_eq!( - levenshtein_distance(string_a, string_b, usize::MAX), - expected_distance - ); - } - - /// Test ported from - #[test_case("", "", 0)] - #[test_case("", "a", 2)] - #[test_case("a", "A", 1)] - #[test_case("Apple", "Aple", 2)] - #[test_case("Banana", "B@n@n@", 6)] - #[test_case("Cherry", "Cherry!", 2)] - #[test_case("---0---", "------", 2)] - #[test_case("abc", "y", 6)] - #[test_case("aa", "bb", 4)] - #[test_case("aaaaa", "AAAAA", 5)] - #[test_case("wxyz", "wXyZ", 2)] - #[test_case("wxyz", "wXyZ123", 8)] - #[test_case("Python", "Java", 12)] - #[test_case("Java", "C#", 8)] - #[test_case("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2)] - #[test_case("CPython", "PyPy", 10)] - #[test_case("CPython", "pypy", 11)] - #[test_case("AttributeError", "AttributeErrop", 2)] - #[test_case("AttributeError", "AttributeErrorTests", 10)] - #[test_case("ABA", "AAB", 4)] - fn test_levenshtein_distance_calculation_cpython_examples( - string_a: &str, - string_b: &str, - expected_distance: usize, - ) { - assert_eq!( - levenshtein_distance(string_a, string_b, 4044), - expected_distance - ); - } -} diff --git a/crates/ty_python_semantic/src/types/all_members.rs b/crates/ty_python_semantic/src/types/ide_support.rs similarity index 96% rename from crates/ty_python_semantic/src/types/all_members.rs rename to crates/ty_python_semantic/src/types/ide_support.rs index 6bcdb2d8478fb1..1e491e478edbfc 100644 --- a/crates/ty_python_semantic/src/types/all_members.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -1,9 +1,3 @@ -//! Routines to gather all members of a type. -//! -//! This is used in autocompletion logic from the `ty_ide` crate, -//! but it is also used in the `ty_python_semantic` crate to provide -//! "Did you mean...?" suggestions in diagnostics. - use crate::Db; use crate::place::{imported_symbol, place_from_bindings, place_from_declarations}; use crate::semantic_index::place::ScopeId; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index c192432f57a59c..b2f941da31e0e8 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -77,18 +77,17 @@ use crate::types::call::{ use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, - CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, HideUnderscoredSuggestions, INCONSISTENT_MRO, - INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, - INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, - INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, + CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, + INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, + INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, + INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, - UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, find_best_suggestion_for_unresolved_member, - report_implicit_return_type, report_invalid_argument_number_to_special_form, - report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, - report_invalid_assignment, report_invalid_attribute_assignment, - report_invalid_generator_function_return_type, report_invalid_return_type, - report_possibly_unbound_attribute, + UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, + report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated, + report_invalid_arguments_to_callable, report_invalid_assignment, + report_invalid_attribute_assignment, report_invalid_generator_function_return_type, + report_invalid_return_type, report_possibly_unbound_attribute, }; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, @@ -1855,7 +1854,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { /// is a class scope OR the immediate parent scope is an annotation scope /// and the grandparent scope is a class scope. This means it has different /// behaviour to the [`nearest_enclosing_class`] function. - fn class_context_of_current_method(&self) -> Option> { + fn class_context_of_current_method(&self) -> Option> { let current_scope_id = self.scope().file_scope_id(self.db()); let current_scope = self.index.scope(current_scope_id); if current_scope.kind() != ScopeKind::Function { @@ -1880,7 +1879,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { 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).into_class_literal() } /// Returns `true` if the current scope is the function body scope of a function overload (that @@ -2040,7 +2039,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { returns.range(), declared_ty, has_empty_body, - enclosing_class_context.map(|class| class.class_literal(self.db()).0), + enclosing_class_context, no_return, ); } @@ -4416,11 +4415,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return; } - // Now we know the import cannot be resolved. Several things remain to do: - // - Add `Unknown` as the stored type for the definition. - // - Maybe: add a diagnostic. - // - If emitting a diagnostic: see if we can add helpful subdiagnostics. - self.add_unknown_declaration_with_binding(alias.into(), definition); if &alias.name == "*" { @@ -4438,27 +4432,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return; }; - let mut diagnostic = builder.into_diagnostic(format_args!( + let diagnostic = builder.into_diagnostic(format_args!( "Module `{module_name}` has no member `{name}`" )); if let Some(full_submodule_name) = full_submodule_name { hint_if_stdlib_submodule_exists_on_other_versions( self.db(), - &mut diagnostic, + diagnostic, &full_submodule_name, &module, ); } - - if let Some(suggestion) = find_best_suggestion_for_unresolved_member( - self.db(), - module_ty, - name, - HideUnderscoredSuggestions::Yes, - ) { - diagnostic.set_primary_message(format_args!("Did you mean `{suggestion}`?",)); - } } fn infer_return_statement(&mut self, ret: &ast::StmtReturn) { @@ -6415,7 +6400,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let attribute_exists = self .class_context_of_current_method() .and_then(|class| { - Type::instance(self.db(), class) + Type::instance(self.db(), class.default_specialization(self.db())) .member(self.db(), id) .place .ignore_possibly_unbound() @@ -6509,41 +6494,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .context .report_lint(&UNRESOLVED_ATTRIBUTE, attribute) { - let mut diagnostic = if bound_on_instance { - builder.into_diagnostic( - format_args!( - "Attribute `{}` can only be accessed on instances, \ - not on the class object `{}` itself.", - attr.id, - value_type.display(db) - ), - ) - } else { - builder.into_diagnostic( - format_args!( - "Type `{}` has no attribute `{}`", - value_type.display(db), - attr.id - ), - ) - }; - - let underscore_policy = if self - .class_context_of_current_method() - .is_some_and(|class|value_type.is_subtype_of(db, Type::instance(db, class))) - { - HideUnderscoredSuggestions::No - } else { - HideUnderscoredSuggestions::Yes - }; - - if let Some(suggestion) = - find_best_suggestion_for_unresolved_member(db, value_type, &attr.id, underscore_policy) - { - diagnostic.set_primary_message(format_args!( - "Did you mean `{suggestion}`?", - )); - } + if bound_on_instance { + builder.into_diagnostic( + format_args!( + "Attribute `{}` can only be accessed on instances, \ + not on the class object `{}` itself.", + attr.id, + value_type.display(db) + ), + ); + } else { + builder.into_diagnostic( + format_args!( + "Type `{}` has no attribute `{}`", + value_type.display(db), + attr.id + ), + ); + } } } From 87f0feb21a1af67195bebf94d811770393f9f447 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:05:59 -0400 Subject: [PATCH 487/487] Bump 0.12.0 (#18724) - [x] Updated changelog - [x] Updated breaking changes --- BREAKING_CHANGES.md | 34 +++++++ CHANGELOG.md | 157 +++++++++++++++++++++++++++++- Cargo.lock | 6 +- README.md | 6 +- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_wasm/Cargo.toml | 2 +- docs/integrations.md | 8 +- docs/tutorial.md | 2 +- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 11 files changed, 203 insertions(+), 20 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 5124ef6a13841b..7dc0583db580da 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -1,5 +1,39 @@ # Breaking Changes +## 0.12.0 + +- **Detection of more syntax errors** + + Ruff now detects version-related syntax errors, such as the use of the `match` + statement on Python versions before 3.10, and syntax errors emitted by + CPython's compiler, such as irrefutable `match` patterns before the final + `case` arm. + +- **New default Python version handling for syntax errors** + + Ruff will default to the _latest_ supported Python version (3.13) when + checking for the version-related syntax errors mentioned above to prevent + false positives in projects without a Python version configured. The default + in all other cases, like applying lint rules, is unchanged and remains at the + minimum supported Python version (3.9). + +- **Updated f-string formatting** + + Ruff now formats multi-line f-strings with format specifiers to avoid adding a + line break after the format specifier. This addresses a change to the Python + grammar in version 3.13.4 that made such a line break a syntax error. + +- **`rust-toolchain.toml` is no longer included in source distributions** + + The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's + minimum supported Rust version (MSRV) for development and building release + artifacts. However, when present in source distributions, it would also cause + downstream package maintainers to pull in the same Rust toolchain, even if + their available toolchain was MSRV-compatible. + +- **[`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/) + (`S320`) has been removed** + ## 0.11.0 This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 382ff304eb51ef..ebfb1bdcd615f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,145 @@ # Changelog +## 0.12.0 + +Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration +guide and overview of the changes! + +### Breaking changes + +- **Detection of more syntax errors** + + Ruff now detects version-related syntax errors, such as the use of the `match` + statement on Python versions before 3.10, and syntax errors emitted by + CPython's compiler, such as irrefutable `match` patterns before the final + `case` arm. + +- **New default Python version handling for syntax errors** + + Ruff will default to the _latest_ supported Python version (3.13) when + checking for the version-related syntax errors mentioned above to prevent + false positives in projects without a Python version configured. The default + in all other cases, like applying lint rules, is unchanged and remains at the + minimum supported Python version (3.9). + +- **Updated f-string formatting** + + Ruff now formats multi-line f-strings with format specifiers to avoid adding a + line break after the format specifier. This addresses a change to the Python + grammar in version 3.13.4 that made such a line break a syntax error. + +- **`rust-toolchain.toml` is no longer included in source distributions** + + The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's + minimum supported Rust version (MSRV) for development and building release + artifacts. However, when present in source distributions, it would also cause + downstream package maintainers to pull in the same Rust toolchain, even if + their available toolchain was MSRV-compatible. + +### Removed Rules + +The following rules have been removed: + +- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/) + (`S320`) + +### Deprecated Rules + +The following rules have been deprecated: + +- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/) + +### Stabilization + +The following rules have been stabilized and are no longer in preview: + +- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`) +- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`) +- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`) +- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`) +- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`) +- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`) +- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`) +- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`) +- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`) +- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`) +- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`) +- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`) +- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`) +- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`) +- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`) +- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`) +- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`) +- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`) +- [`non-pep604-annotation-optional`](https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional) (`UP045`) +- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`) +- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`) +- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`) + +The following behaviors have been stabilized: + +- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in + addition to list literals and variables. +- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe. +- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify + expressions to use `or` instead of an `if` expression, where possible. +- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well + as inline comments. +- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings, + as well as lists and tuples of literal strings, as trusted input. +- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that + include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to + plain `bool` annotations. +- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules. + `UP007` now applies only to `typing.Union`, while + [`non-pep604-annotation-optional`] (`UP045`) checks for use of + `typing.Optional`. `UP045` has also been stabilized in this release, but you + may need to update existing `include`, `ignore`, or `noqa` settings to + accommodate this change. + +### Preview features + +- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368)) +- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664)) + +### Bug fixes + +- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706)) +- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542)) +- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673)) +- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704)) +- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611)) +- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428)) +- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472)) +- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479)) +- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688)) +- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592)) +- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614)) +- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598)) +- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631)) +- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509)) +- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665)) + +### Rule changes + +- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408)) +- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532)) +- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311)) +- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545)) +- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478)) +- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645)) + +### Server + +- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627)) + +### Documentation + +- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709)) +- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491)) +- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613)) +- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615)) + ## 0.11.13 ### Preview features @@ -391,7 +531,7 @@ See also, the "Remapped rules" section which may result in disabled rules. - **More robust noqa parsing** ([#16483](https://github.com/astral-sh/ruff/pull/16483)) - The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [*Error suppression*](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification. + The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [_Error suppression_](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification. - **Avoid unnecessary parentheses around with statements with a single context manager and a trailing comment** ([#14005](https://github.com/astral-sh/ruff/pull/14005)) @@ -1803,7 +1943,7 @@ The following fixes have been stabilized: ## 0.5.6 -Ruff 0.5.6 automatically enables linting and formatting of notebooks in *preview mode*. +Ruff 0.5.6 automatically enables linting and formatting of notebooks in _preview mode_. You can opt-out of this behavior by adding `*.ipynb` to the `extend-exclude` setting. ```toml @@ -2556,7 +2696,7 @@ To setup `ruff server` with your editor, refer to the [README.md](https://github ### Server -*This section is devoted to updates for our new language server, written in Rust.* +_This section is devoted to updates for our new language server, written in Rust._ - Enable ruff-specific source actions ([#10916](https://github.com/astral-sh/ruff/pull/10916)) - Refreshes diagnostics for open files when file configuration is changed ([#10988](https://github.com/astral-sh/ruff/pull/10988)) @@ -3963,7 +4103,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/). - \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815)) - \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811)) -*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).* +_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._ ### Configuration @@ -4038,3 +4178,12 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/). ### Playground - Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824)) + +[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument +[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation +[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp +[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional +[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union +[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for +[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true +[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa diff --git a/Cargo.lock b/Cargo.lock index 892dd3afd55623..2e523ecd842371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2510,7 +2510,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.11.13" +version = "0.12.0" dependencies = [ "anyhow", "argfile", @@ -2748,7 +2748,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.11.13" +version = "0.12.0" dependencies = [ "aho-corasick", "anyhow", @@ -3085,7 +3085,7 @@ dependencies = [ [[package]] name = "ruff_wasm" -version = "0.11.13" +version = "0.12.0" dependencies = [ "console_error_panic_hook", "console_log", diff --git a/README.md b/README.md index 1ee2807cb06ee3..9fb98ba564c5d6 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. -curl -LsSf https://astral.sh/ruff/0.11.13/install.sh | sh -powershell -c "irm https://astral.sh/ruff/0.11.13/install.ps1 | iex" +curl -LsSf https://astral.sh/ruff/0.12.0/install.sh | sh +powershell -c "irm https://astral.sh/ruff/0.12.0/install.ps1 | iex" ``` You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), @@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 8c6081ee000928..d785c59c5fc608 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.11.13" +version = "0.12.0" publish = true authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index a73b5df5bff724..cd8b06aa3e81cd 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.11.13" +version = "0.12.0" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_wasm/Cargo.toml b/crates/ruff_wasm/Cargo.toml index 6ba3a5c321bde7..29c1db44d59244 100644 --- a/crates/ruff_wasm/Cargo.toml +++ b/crates/ruff_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_wasm" -version = "0.11.13" +version = "0.12.0" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/docs/integrations.md b/docs/integrations.md index 2229d2a8ee0b4d..5ccf329b8423a8 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma stage: build interruptible: true image: - name: ghcr.io/astral-sh/ruff:0.11.13-alpine + name: ghcr.io/astral-sh/ruff:0.12.0-alpine before_script: - cd $CI_PROJECT_DIR - ruff --version @@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter. - id: ruff @@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter. - id: ruff @@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter. - id: ruff diff --git a/docs/tutorial.md b/docs/tutorial.md index 520e167c290a6c..e8544301b7d377 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 680b857edfe723..16b6f1b3b45301 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.11.13" +version = "0.12.0" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index 83a4a94e7ce529..5ab09e478a77d4 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scripts" -version = "0.11.13" +version = "0.12.0" description = "" authors = ["Charles Marsh "]